0. 前置き
React.js は v19 から公式な UMD が非提供になりました。
esm.sh 等の代替案が提示されていますが、状況は好転していません。
esm.sh は、v18 まで提供されていた UMD の .js を一度に取得する構造になっておらず、再帰的な取得が必要です。
プログラミングの環境には一貫性が必要です。
学習時には不確定要素を排除する必要があり、業務ではライブラリのサニタイズチェックも必要です。
動作に常時インターネットが必須とあっては、これらを保証することができません。
(<script src="https://よそ様のドメイン/いろいろ.js"> の心配が消えない)
本稿では Linux 版の npm を使用して UMD (IIFE)*1 の React.js を作成します。
WSL2 や LXC でも作成できるので、React.js を作成したら環境ごと削除しても良いでしょう。
*1ESM は import でローカルファイルを読み込めないためスタンドアロンの環境では UMD を使わざるを得ません。
1. React.js の作成
1-1. Node.js をインストールする。
RHEL 系はこちら。1-2. ビルドに必要なモジュールをインストールする。
$ su
# dnf install -y nodejs
# exit
$ node -v ; npm -v
v22.22.0
10.9.4
Ubuntu はこちら。
$ sudo apt update
$ sudo apt install -y nodejs npm
$ node -v ; npm -v
v18.19.1
9.2.0
$ mkdir -p ./my-app/1-3. React.js を作成する。
$ cd ./my-app/
/my-app/$ npm init -y
/my-app/$ npm install esbuild @babel/standalone
/my-app/$ npm install react react-dom
(バージョンを限定する場合は、react@19 react-dom@19 等と指定する)
インストールしたモジュールは ./node_modules/ に格納される。
./node_modules/ から1-4. Babel を取り出す。
react/package.json, react-dom/package.json (の client 節)
をインポートし、それぞれを React, ReactDOM として公開する。
/my-app/$ npx esbuild --bundle --minify --format=iife \
--outfile=React.js --define:process.env.NODE_ENV='"production"' \
--global-name=MyReact \
<< EOF
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
export { React, ReactDOM };
EOF
/my-app/$ ls -ogh React.js
→ このファイルを hello.html の設置場所へコピーする。
-rw-r--r--. 1 189K 4月 14 22:31 React.js
RHEL 系なら scp。WSL2 なら cp React.js /mnt/c/Users/who/.
/my-app/$ cp node_modules/@babel/standalone/babel.min.js .
/my-app/$ ls -ogh babel.min.js
→ このファイルを hello.html の設置場所へコピーする。
-rw-r--r--. 1 3.0M 4月 14 22:25 babel.min.js
2. HTML の作成と表示
2-1. ReactDOM をテストする。
• hello.html を作成する。2-2. JSX をテストする。
(上記 1-3,1-4 で作成した React.js, babel.min.js と同じ場所に作成する)
1 2 3 4 5 6 7 8 9 10 11 12 13<!DOCTYPE html> <body> <div id="msg" align="center">ここにメッセージが入る</div> <script src="./React.js"></script> <script> const { React, ReactDOM } = MyReact; // ビルド時に指定した --global-name const mesg = React.createElement("h1", null, "Hello, React"); const root = ReactDOM.createRoot(document.getElementById("msg")); root.render(mesg); </script> </body> </html>
• hello.html を Web ブラウザで開く。
(以下は Firefox 149.0.2 で表示した例)
![]()
• jsx.html を作成する。2-3. useState をテストする。
1 2 3 4 5 6 7 8 9 10 11 12 13 14<!DOCTYPE html> <body> <div id="msg" align="center">ここにメッセージが入る</div> <script src="./React.js"></script> <script src="./babel.min.js"></script> <script type="text/babel"> const { React, ReactDOM } = MyReact; const mesg = <h1 style={{ textAlign: "center" }}>Hello, JSX</h1>; const root = ReactDOM.createRoot(document.getElementById('msg')); root.render(mesg); </script> </body> </html>
• jsx.html を Web ブラウザで開く。
(以下は Firefox 149.0.2 で表示した例)
![]()
• useState.html を作成する。2-4. useEffect をテストする。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28<!DOCTYPE html> <body> <div id="msg" align="center">ここにメッセージが入る</div> <script src="./React.js"></script> <script src="./babel.min.js"></script> <script type="text/babel"> const { React, ReactDOM } = MyReact; const { useState } = React; function App() { const [mesg, setMesg] = useState("Click the button."); return ( <div> <h1>{mesg}</h1> <button onClick={() => setMesg("Left arrow was Clicked")}>←</button> <button onClick={() => setMesg("Right arrow was Clicked")}>→</button> </div> ); } const mesg = <App /> const root = ReactDOM.createRoot(document.getElementById('msg')); root.render(mesg); </script> </body> </html>
• useState.html を Web ブラウザで開く。
(以下は Firefox 149.0.2 で表示した例)
![]()
• useEffect.html を作成する。2-5. 自作フックをテストする。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30<!DOCTYPE html> <body> <div id="msg" align="center">ここにメッセージが入る</div> <script src="./React.js"></script> <script src="./babel.min.js"></script> <script type="text/babel"> const { React, ReactDOM } = MyReact; const { useState, useEffect } = React; function App() { const [mesg, setMesg ] = useState("初期値"); const [count, setCount] = useState(0); useEffect(() => {setMesg(1 + count + "回目")}, [count]); return ( <div> <h1>useEffect: {mesg}</h1> <button onClick={() => setCount(count + 1)}>Click Me</button> </div> ); } const mesg = <App /> const root = ReactDOM.createRoot(document.getElementById('msg')); root.render(mesg); </script> </body> </html>
• useEffect.html を Web ブラウザで開く。
(以下は Firefox 149.0.2 で表示した例)
![]()
• myHook.html を作成する。2-6. ファイル分割をテストする。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42<!DOCTYPE html> <body> <div id="msg" align="center">ここにメッセージが入る</div> <script src="./React.js"></script> <script src="./babel.min.js"></script> <script type="text/babel"> const { React, ReactDOM } = MyReact; const { useState } = React; function useSp(initVal) { const [count, setCount] = useState(initVal); const increment = () => { setCount(count + 1) }; const decrement = () => { setCount(count - 1) }; return { count, increment, decrement }; } function App() { const sp1 = useSp(0); const sp2 = useSp(0); return ( <div> <div> SP1: { sp1.count } <button onClick={sp1.increment}>+1</button> <button onClick={sp1.decrement}>-1</button> </div> <div> SP2: { sp2.count } <button onClick={sp2.increment}>+1</button> <button onClick={sp2.decrement}>-1</button> </div> </div> ); } const mesg = <App /> const root = ReactDOM.createRoot(document.getElementById('msg')); root.render(mesg); </script> </body> </html>
• myHook.html を Web ブラウザで開く。
(以下は Firefox 149.0.2 で表示した例)
![]()
• myHookSp.js を作成する。
(上記 2-5 からフック useSp() を切り出したもの)
1 2 3 4 5 6function useSp(initVal) { const [count, setCount] = useState(initVal); const increment = () => { setCount(count + 1) }; const decrement = () => { setCount(count - 1) }; return { count, increment, decrement }; }
• myHookRoot.html を作成する。
(上記 2-5 から useSp() を除外して <script src=…> を追加したもの)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36<!DOCTYPE html> <body> <div id="msg" align="center">ここにメッセージが入る</div> <script src="./React.js"></script> <script src="./babel.min.js"></script> <script src="./myHookSp.js"></script> <script type="text/babel"> const { React, ReactDOM } = MyReact; const { useState } = React; function App() { const sp1 = useSp(0); const sp2 = useSp(0); return ( <div> <div> SP1: { sp1.count } <button onClick={sp1.increment}>+1</button> <button onClick={sp1.decrement}>-1</button> </div> <div> SP2: { sp2.count } <button onClick={sp2.increment}>+1</button> <button onClick={sp2.decrement}>-1</button> </div> </div> ); } const mesg = <App /> const root = ReactDOM.createRoot(document.getElementById('msg')); root.render(mesg); </script> </body> </html>
• myHookRoot.html を Web ブラウザで開く。
(ファイル分割しただけなので、表示・機能ともに上記 2-5 と同じ)
![]()