新旧 JSX Transform と @jsx・@jsxImportSource がやっていることにちょっとだけ詳しくなる
何この記事?
emotion で必要な pragma1 について知ろうとしたら、React v17 の the New JSX Transform にちょっとだけ詳しくなれた、その幸せの共有
脳みそ止めて import React from "react"
とか、 /* @jsx jsx */
とかしていたものが、スッキリしました。
ことの発端 1 (読み飛ばしていいヤツ)
Next.js(v10)でemotionを使おうとしたら、エラーが出た
% npx create-next-app next-v-10 # Next.js のプロジェクト作って
% cd next-v-10
% npm i @emotion/react
% hogehoge # emotion のあれを付けて
% npm run dev # サーバーを起動...
error - ./pages/index.js
SyntaxError: next-v-10/pages/index.js: pragma and pragmaFrag cannot be set when runtime is automatic.
> 1 | /* @jsx jsx */
| ^
2 | import {jsx, css} from "@emotion/react";
3 | import Head from 'next/head'
4 | import styles from '../styles/Home.module.css'
at transformFile.next (<anonymous>)
at run.next (<anonymous>)
…ので、公式ドキュメント(#jsx-pragma) を見てみました(意訳注意)。
css prop を使うときは、the jsx pragma (
/** @jsx jsx */
)を使う the new JSX runtimes の React を使用している場合、/** @jsx jsx */
pragma の代わりに、/** @jsxImportSource @emotion/react */
を使う
解決策(/** @jsxImportSource @emotion/react */
を使う)は分かったけれど… 💭
ことの発端 2
👊😊 emotion 使いてぇ〜! => 💻/* @jsx jsx */
or 💻/* @jsxImportSource @emotion/react */
が板につくのもいいけれど、
- なんで pragma で css prop を解決できるんだ?
- なんで @jsx, @jsxImportSource で使い分ける必要があるんだ (React の classic, automatic runtime ってなに)?
を解決して、スッキリしたくなった。
pragma ってなに
とりあえず ググる 。
どうやら、pragma はコンパイラに何かしらの指示を渡すもののことらしい。
つまり今回のケースでは、pragma(/*@jsx jsx*/
)はコンパイラ(Babel, TypeScript)へ何かしらの指示を渡すものなんですね。
JSX pragma ってなに
じゃあ /*@jsx jsx*/
で何を コンパイラに伝えているか、ですが
そもそも JSX ってなに
JSX just provides syntactic sugar
JSX は React コンポーネントを簡潔に記述するためのシンタックスシュガーです。
コンパイラは、JSX を ブラウザが理解できる JS に変換する時、JSX 記法の部分(<div></div>
)を JS の関数(React.createElement("div")
)に変換しているわけです。
-
Babel によるコンパイル(playground )。
// from const hoge = <div>children</div>; // to const hoge = /*#__PURE__*/ React.createElement("div", null, "children");
-
TypeScript によるコンパイル (playground )。
// from import React from "react"; const hoge = <div>children</div>; // to import React from "react"; const hoge = React.createElement("div", null, "children");
今まで jsx ファイルを作るときにおまじないのように import React from "react"
を書いていたけれど、変換後に React オブジェクトが読み込まれるからそれを解決するために import していたんだね!スッキリ 🌟
つまり、JSX pragma って
Babel は @babel/plugin-transform-react-jsx で pragma のサポートを提供しているみたいです。 TypeScript は この PR(#21218) で pragma の 機能が導入されたようです。
それぞれ、pragma によって JSX 記法から変換する JS の関数を指定するために pragma をサポートしています。
例えば emotion の場合、 標準の関数(React.createElement
) から css prop を使える 関数(jsx()
)2に pragma で置き換えています。
emotion.sh/docs/css-prop#get-started
compiled jsx code will use emotion’s jsx function instead of React.createElement
Getting Stared ののっけから核心が書いてある…。ものすごく遠回りした気分
New JSX Transform(classic/automatic runtime) ってなに
ここで React のドキュメント Introducing the New JSX Transform に行きます。 Babel と協力して、React v17 で新しい JSX Transform を提供するようです。 (新しい JSX Transform のメリット、古いものの課題はスキップします。)
今までこれ(↓)を
import React from "react";
const App = () => <h1>Hello World</h1>;
こう(↓)変換してたもの (前述の変換と同じ)が、
import React from "react";
const App = () => React.createElement("h1", null, "Hello world");
これ(↓)を
const App = () => <h1>Hello World</h1>;
こう(↓)変換するように変わりました。
import { jsx as _jsx } from "react/jsx-runtime";
const App = () => _jsx("h1", { children: "Hello world" });
大事なポイントは、React コンポーネントを定義する関数(jsx as _jsx
)をコンパイル時に自動でインポートしてくれている点です。
変換後コード内で React オブジェクトを読み込まなくなったため、自分で import React from "react"
する必要がなくなっています。
以前の変換で使用するものが classic runtime、新しい変換で使用するものが automatic runtime ってことだね。スッキリ 🌟🌟
つまり@jsxImportSource がやっていることは
前述の通り、変換後のコードが新旧 transform で大きくことなるため、標準の関数(React.createElement()
or _jsx()
)から別の関数(emotion のjsx()
)に置き換える手段も変わりました。
the importSource option で行います(公式ドキュメント )。
つまり、
新しい transform でこれ(↓)が
const hoge = <div>children</div>;
このように(↓)、変換時に自動でインポートしてくれる require("react/jsx-runtime")
を
var _jsxRuntime = require("react/jsx-runtime");
const hoge = /*#__PURE__*/ (0, _jsxRuntime.jsx)("div", {
children: "children",
});
このように(↓)、@jsxImportSource
を使うことで
/* @jsxImportSource hogehoge */
const hoge = <div>children</div>;
変換時に自動インポートするものを require("hogehoge/jsx-runtime")
に変えることが出来る。
var _jsxRuntime = require("hogehoge/jsx-runtime");
/* @jsxImportSource hogehoge */
const hoge = (0, _jsxRuntime.jsx)("div", {
children: "children",
});
playground(OPTIONS React Runtime を automatic にしてください)
とどのつまり
- Babel や TypeScript は JSX を
React.createElement
を使った形式(JS)に変換してくれる。 - emotion が使う css prop は標準の
React.createElement
にないから、pragma(/* @jsx jsx */
) を使ってimport { jsx } from '@emotion/react'
のjsx()
で React コンポーネントを定義するようにする。 - React v17 が the New JSX Transform を導入したことで、JSX の変換結果が変わった。
- 新しい変換済みのコードに併せて、標準の関数(
require("react/jsx-runtime")
)を置き換えるために、importSource option(/* @jsxImportSource @emotion/react */
)を使って、require("@emotion/react/jsx-runtime")
で React コンポーネントを定義するようにする。
余談
ライブラリ側はもちろん新しい transform をサポートする必要があるため、emotion ではこんな issues で報告・解決されていました。
Footnotes
-
emotion 使いてぇ〜! 👊😊 => これ
/* @jsx jsx */
↩