🐥

Denoとdeno_emitを使ってReactをコンパイルする

2024/03/10に公開

概要

Denoとdeno_emitを使ってシンプルにReactをコンパイルする方法をまとめる。

環境

  • deno@1.41.1
  • deno_emit@0.38.2
  • react@18.2.0

ファイル構成

.
├── app.tsx
├── deno.jsonc
├── deno.lock
├── deps.ts
├── index.html  // 最終的に表示するHTML
├── index.js   // Reactのtsxをコンパイルして生成するJavaScript
├── index.tsx
└── build.ts

対象のReactファイル(app.tsx)をコンパイルして、index.jsに出力。
index.htmlからそれを読み込む。

HTML

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Deno React Samp</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="index.js" defer></script>
  </body>
</html>

対象のReactファイル

deps.ts
export * as ReactDOM from "https://esm.sh/react-dom@18.2.0";
export * as React from "https://esm.sh/react@18.2.0";
app.tsx
import { React } from "./deps.ts";

export default function App() {
  return (
    <div>
      <h1>Hello, world</h1>
    </div>
  );
}

ビルドスクリプト

build.ts
import { bundle } from "https://deno.land/x/emit@0.38.2/mod.ts";

const url = new URL("./index.tsx", import.meta.url);
const result = await bundle(url, {
  minify: false,
});
console.log(result.code);
await Deno.writeTextFile("./index.js", result.code);

deno_reactは、シンプルにTypeScriptをJavaScriptにコンパイル(トランスパイル)するためのtranspile 関数と、依存関係を全てバンドルするための bundle 関数を提供している。

今回はReactの依存コードも全てindex.jsに含めるためにbundle関数を使用。

https://github.com/denoland/deno_emit/blob/main/js/README.md

設定

deno.jsonc
{
  "tasks": {
    "build": "deno run --allow-net --allow-env --allow-read --allow-write build.ts"
  },
  "compilerOptions": {
    // Deno.xxxを認識するためにdeno.nsが必要
    // documentなどを認識するためにdomが必要
    "lib": ["deno.ns", "dom", "esnext"]
  },
  "imports": {
    // react/jsx-runtimeを自動でimportするため必要
    "react/": "https://esm.sh/react@18.2.0/"
  }
}

Deno自体は、jsxをサポートしており、jsxのランタイムはデフォルトで、reactが使われるようになっている。
ReactはReact17以降、jsへの変換方法を刷新していて、 react/jsx-runtime というモジュールが使われる。

どのモジュールを使ってImportするかは、jsxImportSourceで指定する。

// この記述をファイルの先頭に書くことで、どのソースからjsxをコンパイルするか指定できる
/** @jsxImportSource https://esm.sh/preact */

export function App() {
  return (
    <div>
      <h1>Hello, world!</h1>
    </div>
  );
}

deno.jsonimports に記載することでこの @jsxImportSource は省略できる。

ビルド

deno run --allow-net --allow-env --allow-read --allow-write build.ts

気になっていること

deno_emitのビルドだとimportsによるモジュール解決ができない?

deps.ts
export * as ReactDOM from "https://esm.sh/react-dom@18.2.0";
export * as React from "https://esm.sh/react@18.2.0";

depsにかかれているimportをdeno.jsonのimportに書きURL表記を省略しようとしたが、deno_emitでのビルドでエラーが出てしまったため、URL表記に戻した。

Reactのimportを省略できない

imports に reactを書くことで、 @jsxImportSource を省略できるが、deno_emitでビルドするとReactのコードを一緒にバンドルしてくれず、

import { React } from "./deps.ts";

は書く必要があった。

Discussion