LoginSignup
0
0

More than 1 year has passed since last update.

DartからNode.js用のJavaScriptを作成する

Last updated at Posted at 2022-10-24

概要

Dartにはdartdevcやdart2jsといったJavaScriptへのトランスパイラが含まれていますが、どちらも出力されたJavaScriptそのままではNode.jsで実行しようとしてもエラーが出て使えません。
しかしながら、Dart SassはDartコードからJavaScriptのトランスパイルによりNode.js向けのsassコンパイラの作成に成功しているようです。
この記事はDart SassがどのようにしてNode.js向けのJavaScriptトランスパイルを実現したのか自分なりに探った結果を残しておこうというものです。

使用パッケージ

Node.js型定義 node_interop

Node.jsの型定義ファイルです。全部はカバーしていないようです。どれくらいカバーされているかはGitHubのページで確認してください。

関連パッケージにnode_io、node_http、build_node_compilersがあるようですが、node_io、node_httpはdart:io、dart:httpをNode.jsで実装したラッパーのようです。今回は使いません。
またbuild_node_compilersはNode.js用にトランスパイルするためのビルダだそうですが、長らく更新されていない上にNull Safetyにも対応していないので使い物になりません。Dart Saasでも使われていません。

Dart用タスクランナーGrinder

Node.jsでいうところのgulpみたいなものです。Dart言語でタスクを定義できます。

今回のトランスパイルに使う分には別パッケージで用意されているタスクを利用するだけなので本格的に理解する必要はありません。

日本語解説記事

Node.js JavaScriptトランスパイル用追加スクリプトnode_preamble

トランスパイルしたJavaScriptコードをNode.jsで動かすために必要な追加のJavaScriptコードを出力するパッケージです。

このパッケージ単体ではJavaScriptコードを出力するだけでトランスパイル自体には影響しないので、トランスパイル後のJavaScriptコードにこのパッケージが出力するJavaScriptコードを追加する処理を書く必要があるのですが、その処理は別パッケージのタスクに定義済みなので、今回のトランスパイルではこのパッケージをインストールするだけでOKです。

配布パッケージ作成用Grinderタスク集cli_pkg

Dart(コマンドライン)アプリケーションをリリースするためのGrinderタスクをまとめたものです。

この中に、JavaScriptにトランスパイルしてnpmパッケージを作成するタスクがあるのでこれを利用します。npmパッケージまで作らなくてもトランスパイルするタスクだけを単独で実行することができます。

トランスパイルするタスクのコードを見る感じdart2jsの出力にいろいろコードを付け足したり書き換えたりしているようで力業感がスゴいです。

細かいオプションなんかのドキュメントは見当たらなかったのでDart Sassのタスク設定を参考に推測するしかなさそう。

ビルド手順

インストール

※Dart(Flutter)とNode.js(npm)のインストールとPATHへの登録は省略。

grinderのインストール

grinderのCLIであるgrindが使えるようにグローバルインストールしておきます。

dart pub global activate grinder

ファイルの準備

Node.jsのJavaScriptトランスパイルではDartコマンドやFlutterコマンド等でプロジェクトのひな型を作る必要はありません、というかひな形を作ってはいけません

以下のような構成でファイルを手動で準備します。多分これがトランスパイルに必要な最小限の設定ファイルだと思います。
package.jsonはnpm initで生成してもいいかもしれません。

プロジェクトフォルダ/
 ├─ lib/
 │  └─ main.dart
 ├─ tool/
 │  └─ grind.dart
 ├─ package.json
 └─ pubspec.yaml
pubspec.yaml
name: nodetest
description: An absolute bare-bones web app.
version: 1.0.0
# homepage: https://www.example.com

environment:
  sdk: '>=2.18.1 <3.0.0'

dependencies:
  js:
  node_interop:

dev_dependencies:
  cli_pkg:
  grinder:
  node_preamble:

いつの間にかgrind.dartに設定するオプションにESM対応が追加されていたので追加しました(2023/3/31現在)

tool/grind.dart
import 'package:cli_pkg/cli_pkg.dart' as pkg;
import 'package:grinder/grinder.dart';

void main(List<String> args) {
  pkg.jsModuleMainLibrary.value = "lib/main.dart";
  //pkg.jsEsmExports.value = {};  //ESM対応にする場合
  pkg.addAllTasks();
  grind(args);
}

package.json
{
  "name": "test"
}

Dartコードはとりあえずこんな感じで。

lib/main.dart
import 'dart:js';
import 'package:js/js.dart';
import 'package:js/js_util.dart';
import 'package:node_interop/node.dart';

void main() {
  print("Hello world!!.");
}

手動での準備が面倒な場合は一応上記ファイルをgithubにまとめましたので、cloneするかzipダウンロードして解凍してください。

パッケージのインストール

pubspec.yamlで設定したパッケージをインストールします。

dart pub get

トランスパイル

開発用のトランスパイルとリリース用のトランスパイルの2種類のタスクがあります。
デフォルトの設定ではリリース用でもminifyはしないようなので違いは最適化の有無と末尾の//# sourceMappingURLの有無になるようです。
dart2jsに渡すオプションはtool/grind.dartで開発用とリリース用で個別に設定できるようです。

いつの間にかやり方が変わっていたので修正しています(2023/3/31現在)

開発用

grind pkg-npm-dev

リリース用

grind pkg-npm-release

実行

トランスパイルタスクの実行に成功するとbuild/npm/(package.jsonのnameに設定した文字列).dart.jsbuild/npm/(package.jsonのnameに設定した文字列).default.js等が生成されるので、(package.jsonのnameに設定した文字列).default.jsの方をnodeコマンドで実行します。

node ./build/npm/test.default.js

grind.dartでEMS対応のオプションを有効にした場合はbuild/npm/(package.jsonのnameに設定した文字列).default.cjsも生成されているので、.cjsのほうを実行します。

node ./build/npm/test.default.cjs

感想

正直DartのJavaScriptトランスパイラはどう考えても必要のなさそうなランタイムらしきコードを大量に出力するのでトランスパイルされたJavaScriptファイルを見ると眩暈がします。
main(){}だけの何もしないコードをリリース用ビルドしても出力されたJavaScriptは3000行を超えファイルサイズも110KBになります。
せめて使わない部分は出力から除外するコードストリッピングをしてくれればなぁ・・・

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0