React プロジェクトの src 内のコンポーネントから諸事情によりウェブサイト外部の SCRIPT を読む方法のメモ
諸事情の例
- コンポーネントの render 時に CDN やどこかしらから
<script src="https://example.com/hogehoge.js" crossorigin></script>
とかしたい - コンポーネントの render 時に .jsx/.tsx の翻訳時には有効ではない ECMAScript コードをちょろまかしたい
4つの方法
もっとあるというのはさておき。
1. react-script-tag
この方法では DOM 構造的には in-situ な感じで <script>
がぶちこまれます。
準備:
# .jsx 向けしか存在しないので .tsx では @ts-ignore して使います yarn add react-script-tag
実用:
import React, { Component } from "react"; // note: ↓ .tsx ならこのコメントディレクティブを付けないとエラーになります。 .jsx なら不要です // @ts-ignore import ScriptTag from "react-script-tag"; export default class MyAwesomeMainCanvas extends Component { render() { return ( <div> <canvas id="main-canvas"></canvas> <ScriptTag src="runner.main-canvas.something.js" /> </div> ); } }
2. react-helmet
この方法では DOM 構造的には <head>
へ <script>
(というか実際には任意の何かしら、 <title>
とか <link>
でも) をぶちこみます。
注意:
- この方法を採用すると依存ライブラリーの都合か strict mode で警告↓がでて不穏になります。
<script>
用途だけが目的の場合は他の方法がよいかもしれません。 ( Issue -> https://github.com/nfl/react-helmet/issues/426 )- おまけ情報:
react-helmet
の代わりに<script>
をreact-script-tag
へ変更する際に<title>
についても代替が必要な場合は react-document-title & @types/react-document-title が便利です。
- おまけ情報:
index.js:1 Warning: Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. See https://fb.me/react-unsafe-component-lifecycles for details. * Move code with side effects to componentDidMount, and set initial state in the constructor. Please update the following components: SideEffect(NullComponent)
準備:
# for .jsx yarn add react-helmet # for .tsx; 両方必要になります yarn add react-helmet @types/react-helmet
実用:
import React, { Component } from "react"; // for .jsx & .tsx import { Helmet } from "react-helmet"; export default class MyAwesomeMainCanvas extends Component { render() { return ( <div> <canvas id="main-canvas"></canvas> <Helmet> <script src="runner.main-canvas.something.js"></script> <title>にゃーん/title> </Helmet> </div> ); } }
3. DOM 操作でぶちこむ
いわゆるごりごり手書きした的な方法。
import React, { Component } from "react"; export default class MyAwesomeMainCanvas extends Component { render() { return ( <div> <canvas id="main-canvas"></canvas> </div> ); } componentDidMount() { const s = document.createElement( "script" ); s.src = "runner.main-canvas.something.js"; s.async = true; document.body.appendChild( s ); } }
複数箇所で使用したいとか再利用性が欲しい場合は方法(1),(2)を選択するほかか、この方法(3)も手書きオレオレコンポーネント化する方法もあります。コンポーネント化の方法はこのメモの本論ではないので省略。
4. React の Effect Hook 機能
方法(3) のごりごり手書きを若干変態気味に実装する亜種のような方法です。
カスタム・フックを作って:
import { useEffect } from 'react'; export default const myHook = (src) => { useEffect( () => { const s = document.createElement( "script" ); s.src = src; s.async = true; document.body.appendChild( s ); return () => { document.body.removeChild( s ); }; } , [src] ); };
カスタム・フックをひっかける:
// for .jsx // import myHook from 'myHook.jsx'; // for .tsx import myHook from 'myHook'; const Demo = props => { importScript("runner.main-canvas.something.js"); }
参考
React なプロジェクトを TypeScript に移行したら3つの困りが発生したけど解決できたメモ
こまったこと:
1. カスタムソースディレクトリー ./src.react
を tsc
が認識できなかった
諸事情により ./src
ではなく ./src.react
に react 用のソースを配置していたが、 tsconfig.json
の include
では "." 文字の入ったディレクトリーは認識しない仕様らしく困りました。
考えた解決方法:
- symlink を作って渡す
./src-react
に変える
もんやり感はありましたが (2) でディレクトリーの名前から "." を排除して解決しました。ちなみに React のソースファイルパスのカスタムは react-app-rewired
で eject-free に対応しています。
2. .tsx 化したモジュールで他の .tsx 化したモジュールを import
できなかった
import Hoge from "./Hoge.tsx"
← ダメ🙅♀️import Hoge from "./Hoge"
← ヨシ!🙆♀️
3. 独自定義のタグが JSX.IntrinsicElements
に無いですよエラーで困った
独自定義のタグ、例えば GUI のルック・アンド・フィール的に Xel を使っていると <x-box>
とか <x-button>
とか使います。TypeScript化していない状態では一般的なHTMLタグと同様に埋め込めましたが、 TypeScript にしたら
Property 'x-box' does not exist on type 'JSX.IntrinsicElements'. TS2339
などと怒られてビルドできなくなりました。
単純なタグとしてのみの場合、かつ独自定義のタグが少ない場合は、
declare global { namespace JSX { interface IntrinsicElements { "my-awesome-tag": React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>; } } }
↑のようにまじめに TypeScript らしさを残したハック的なコードを <my-awesome-tag>
を使いたい .tsx ファイル冒頭へ加えればよい。よいのだけど、 Xel を使いたい場合のようにたくさんのタグをぶちこみたい場合や、独自定義のタグに独自定義のプロパティーも使いたい場合は、 TypeScript にこだわって疲弊するよりは↓型は any
にして "my-awesome-tag": any;
のようにすると少し楽です。
参考
Web 向けの3D の GPU 描画コンテキストお取り扱いライブラリーの2020-07時点のメモ: ES 系, Unity, Unreal Engine, Rust 系
note: 2D 系(eg. phaser, ggez, etc. )は今回のメモでは含めていません。
Lang | Library | License | WebGPU | WebGL | native | ES & wasm |
---|---|---|---|---|---|---|
ES | babylon.js | Apache-2.0 | Ready | 2 %a | n/a | pure ES |
ES | three.js (+A-Frame) | MIT(+MIT) | (unknown†1) | 1 + (2) | n/a | pure ES |
ES | playcanvas | MIT | (unknown†2) | 2 %b | n/a | pure ES |
C++ | Unreal Engine | *1 | (unknown) | 1 %c | D3D12, Metal, OpenGL | wasm(Emscripten) |
C# | Unity | *2 | (unknown) | 2 %d | D3D12, Metal, OpenGL | wasm(Emscripten) |
Rust | unrust | Apache-2.0 | (unknown) | 2 | OpenGL | wasm |
Rust | Amethyst(≈rendy≈gfx-hal) | Apache-2.0 or MIT | (unknown; potentially maybe) | (2;potentially) | Vulkan, Metal, OpenGL | wasm(; experimental) |
Rust | harmony | Zlib | (unknown†4; potentially yes) | (2;potentially) | Vulkan, Metal | wasm(; help wanted) |
Rust | oxidator | MIT | (potentially yes) | n/a | Vulkan, D3D12 | wasm(; not yet) |
Rust | piston | MIT | (unknown) | n/a | n/a | n/a |
おまけ: 所感
- pure-ES 系で特に WebGPU を見据える場合は babylon.js で書くのが良さそう
- リッチなエンジン系は…
- Unreal Engine で HTML5 出力は現実的に考えない方がいいです(UE5移行へWebGPUと併せて期待したいけど、期待しないでおいた方がいいかも)
- 豊富なエコシステムやコミュニティーもあるので Unity で WebGL2 バックエンドにするのが現時点だけを見るなら現実的で良いかもしれません
- Web 親和性の点では playcanvas もよいかもしれません
- Rust 系は...
- unrust は現時点で Web へきっちり出力できるたぶん唯一の3D-GPU描画系です
- Amethyst は wasm+GL サポートを開発途中ですが、このメモの作成時点では全ての要素技術にかなり強い技師でも辛うじてバギーながらChromeで動くような動かないような微妙な出力に辿り着ける程度の状態です
- harmony は描画バックエンドのポテンシャル的には WebGPU 対応、 OpenGL 対応により Web 出力できますが、採用する ECS バックエンドやマルチスレッドに最適化しまくっている事から WebWorker の制限と性能の懸念などもあり今の所 Author は Web 出力にそれほど積極的ではないようです。(Webよりエンジンとしての機能実装を進めたい状況でもあります)
- oxidator は RTS ゲームエンジンに特化していますが、 Author も WebGPU での Web 出力は視野にあるようです。いまのところは WebGPU や rust と wasm 処理系の進化、ウェブブラウザーの進化待ちのようです
- piston は Web に興味無さそうです
少なくとも、 Web 向けアプリの 3D-GPU 描画系に現時点で Rust を採用するのはわりとしんどいです。3年後に WebGPU と Rust/wasm 処理系が最高の選択肢になっている可能性はわりと高いとは思いますが、いまはまだしんどいです。 Rust を採用したい場合も、恐らく 3D-GPU 描画系はうまく機能分離し、できるだけ疎結合に近い設計にしつつ、ビジネスロジック部分は Rust で書いて wasm-unknown-unknown ターゲットで wasm を wasm-bindgen で library としてフロントエンドから叩く感じで作っておき、フロントエンドとして GUI系、 3D-GPU 描画系は pure-Web 系またはそれに近い要素技術でカバーするのが現時点では合理的かもしれません。
例えばフロントエンドは React あるいは yew と HTML と ES のライブラリーで GUI を作り、 3D-GPU 描画系は babylon.js を babylon.rs で取り込み、アプリ本体の処理、状態やリソースの管理、Entity-Component-System、などなどは pure-Rust または wasm 対応が面倒ではない範囲の Rust のエコシステムを用いて作り、 wasm-bindgen で .wasm に固めるとか。
Arch Linux 環境で cargo が言うことを聞かなくなったメモ
症状
- 数日ぶり程度に使用した Arch Linux 環境の rust 処理系で
cargo
が+nightly
を受け付けてくれなくなって困った (-Z
したかった )rustup toolchain
では stable, nightly が installed 状態
原因
- rust は
rustup
を per user で導入して使っていた"はず"だったのに /sbin/cargo
が実行されていた (which cargo
で判明 )/sbin/cargo
は stable
なぜ /sbin/cargo
が環境に "いつのまにか" install されてしまったのか考えると、この数日の間に実験的に yay
で rust 製のツールをシステムへ install した事を思い出しました。 yay
での install ではビルドツールチェインも芋蔓し、途中の選択肢によってはビルドツールチェインはビルド後に remove できたりするのですが、おそらく yay
で rust 製のツールをシステム導入した際にビルドツールチェインの remove を選択ミスしたためシステムパッケージ版の rust
が導入された状態になってしまい、意図しない cargo
= /sbin/cargo
が ~/.cargo/bin/cargo
よりパスが優先される状態に陥っていたのだろうと思います。
解決
yay
またはpacman
で rust パッケージを削除 ( per user で導入しているrustup
には影響しません )
おまけ: Arch Linux ゆえ
同様の症状の状態は Ubuntu など他の GNU/Linux 環境でも発生させる事はできますが、 AUR と yay のようにユーザーの環境でソースコードからビルドするタイプのパッケージ管理システムを常用していない限り、"意図せず"に発症する事は無いと思います。Gentoo で emerge している場合は似た状況がうっかり発生する事もあるのかも。
MDN の wasm-bindgen の入門用チュートリアル "Hello, WebAssembly" の補足的なメモ
"Hello, WebAssembly"
- https://developer.mozilla.org/ja/docs/WebAssembly/Rust_to_wasm; MDN / WebAssembly / Rust から WebAssembly にコンパイルする
補足的なメモ
1. npm のアカウントとパッケージの公開は必要?
MDNの記事そのものではそこも含めたやり方の例として必要としていますが、本質的には不要です。 npm へ @mynpmusername/hello-wasm パッケージを公開せずにこのチュートリアルを進める場合は:
- 「パッケージの npm への発行」の直前までは記事の通り進めます
package.json
のdependencies.@mynpmusername/hello-wasm
をバージョン番号表記ではなく"file:./hello-wasm/pkg"
のようにローカルファイルシステムからのパッケージ参照で記述しますnpm install
またはyarn
すると他の依存パッケージ群と併せて@mynpmusername/hello-wasm
も./hello-wasm/pkg
からnode_modules
へ install されます- 続きは記事の通り進めます
2. webpack 使う必要ある?
Webサービスとしてデプロイする形態で動作確認してみたければ事実上必要です。
webpack-dev-server
すると index.js
, node_modules
の中身(hello-wasm/pkg
からnode_modules
へinstallされた@mynpmusername/hello-wasm
パッケージも含みます)を統合して開発用のhttpdをwebpack.config.js
の定義に従って起動してくれます。webpack
を使わないとnode_modules
のパッケージ群が統合されないため一般的なhttpdで単純にディレクトリーを指定して起動しても期待動作しません。
hello-wasm
を直接 Node.js のインタラクティブ・シェルで叩いて確認したいだけなら不要です。 WebAssemblyコードのロードと実行 - WebAssembly | MDN の fetch
を Node.js の require('fs').readFileSync
などに置き換えて .wasm ファイルをロードして WebAssembly.instantiate
を叩けば .wasm のより原始的な動作確認も可能です。
3. 一般的なhttpdへデプロイしたい場合はどうするの?
npm run webpack
または yarn webpack
などすると webpack-cli
パッケージの webpack
コマンドにより ./dist
へ一般的なhttpdへデプロイできるファイル群が生成されます。./dist
に生成された中身とindex.html
を同じディレクトリーで参照可能な配置を行い、httpd でホストすると期待動作します。たぶん。
例えばChromeで期待動作せず Console に↓のエラーが出ている場合はホストしている httpd が .wasm の MIME を正しく application/wasm
で出力できていません。たぶん。うまいこと MIME を吐くように設定して下さい。
localhost/:1 Uncaught (in promise) TypeError: Failed to execute 'compile' on 'WebAssembly': Incorrect response MIME type. Expected 'application/wasm'.
4. npm
より yarn
どうぞお好きな方で大丈夫です。
WebGPU 実装状況のメモ; Firefox-80.0a1(nightly), Chrome-86.0.4191.0(canary) なう
- [OK🙆♀️] Firefox-80.0a1(nightly) + gfx.webrender.all=True + dom.webgpu.enabled=True / Windows 10
- [NG🙅♀️] Firefox-78.0.1(stable) + gfx.webrender.all=True + dom.webgpu.enabled=True / Windows 10
- [OK🙆♀️] Chrome-86.0.4191.0(canary) + Unsafe WebGPU=Enabled / Windows 10
- [NG🙅♀️] Chrome-83.0.4103.116(stable) + Unsafe WebGPU=Enabled / Windows 10
note: Firefox のフラグは about:config
, Chrome のフラグは chrome:flags
から設定。ほかに Safari / macOS or iOS でも試せるらしいけどうちには環境無いので確認していません。Android, Chrome OS, GNU/Linux では Chromium, Firefox の WebGPU サポートはまだ進んでいないみたいです。実装状況の概況 -> https://github.com/gpuweb/gpuweb/wiki/Implementation-Status
動作確認は https://austineng.github.io/webgpu-samples/ より。参考 -> https://hacks.mozilla.org/2020/04/experimental-webgpu-in-firefox/
CRA=create-react-app が WSL で start できない理由と回避方法のメモ
問題
WSLでCRAしてyarn start
するとcmd.exe
を実行できずにウェブブラウザーの起動どころかサーバーも起動せず死んでしまいます。
再現方法:
- WSLで
npx create-react-app hoge
してcd hoge; yarn start
します
Starting the development server... events.js:291 throw er; // Unhandled 'error' event ^ Error: spawn cmd.exe ENOENT at Process.ChildProcess._handle.onexit (internal/child_process.js:268:19) at onErrorNT (internal/child_process.js:468:16) at processTicksAndRejections (internal/process/task_queues.js:80:21) Emitted 'error' event on ChildProcess instance at: at Process.ChildProcess._handle.onexit (internal/child_process.js:274:12) at onErrorNT (internal/child_process.js:468:16) at processTicksAndRejections (internal/process/task_queues.js:80:21) { errno: -2, code: 'ENOENT', syscall: 'spawn cmd.exe', path: 'cmd.exe', spawnargs: [ '/s', '/c', 'start', '""', '/b', 'http://localhost:3000' ] } error Command failed with exit code 1. info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
そもそもWSLで実行しているのにcmd.exe
などとわけのわからない事を言われたので「またyarn
か何かの実行バイナリーがWSL内ではなくWindowsの何かが何故か優先されているふぁっきゅーかな」と思いましたが、同じくらいふぁっきゅーなCRA側の問題でした。
回避策; 本質的な解決策は執筆時点でもまだCRAにマージされていません
1. BROWSER=none
戦術
いまのところ、わたしのおすすめはこちらの回避策です。
yarn start
をBROWSER=none yarn start
にします- または
package.json
で"start": "BROWSER=none react-scripts start"
してyarn start
します
note:
package.json
を変更する場合に、開発環境の可搬性に PowerShell, cmd など対応したい場合はcross-env
付きで仕込みます。- またはプロジェクトで採用するビルドエコシステムの方針によって
run-script-os
が有用な場合もあるかもしれません。
- またはプロジェクトで採用するビルドエコシステムの方針によって
"scripts": { "start": "run-script-os", "start:default": "cross-env BROWSER=none react-scripts start", "start:win32": "react-scripts start",
2. PATH=$PATH:/mnt/c/Windows/System32
戦術
yarn start
をPATH=$PATH:/mnt/c/Windows/System32 yarn start
にします- または
package.json
で"start": "PATH=$PATH:/mnt/c/Windows/System32 react-scripts start"
してyarn start
します - または WSL の実行環境の
PATH
をexport PATH=$PATH:/mnt/c/Windows/System32
します - または
/etc/wsl.conf
の[Interop]
セクションでappendWindowsPath = True
またはFalse
を定義しない設定へ変更します - または
cmd.exe
を実行可能な他のお膳立てをしてあげます
原因
- CRAがブラウザーを起動するために"
cmd.exe
を使える状態を前提"にのみ実装されている is-wsl
で WSL を検出して気を利かせてcmd.exe
を起動しようとしているっぽい †参考1
参考
- https://github.com/facebook/create-react-app/issues/7251; Error: spawn cmd.exe ENOENT using WSL since 9.0.0 #7251
関連おまけ
思うところメモ
BROWSER=none
回避策は PATH
回避策より一般的には良いです。WSLのユーザーのおそらくほとんどはWSLを"WindowsとGNU/Linuxが悪魔合体した何か"ではなく、Windowsから便利に利用しやすいGNU/Linux環境として利用し、WSLがWindows上で実行されながらもWindows特有の環境要因にはできるだけ依存せずGNU/Linux環境としての純粋性や可搬性を維持したWindowsとは異なる環境かつWindowsとのつながりもユーザーが求めれば構築しやすい、そんな何かであることを望んでいるのではないかな、と思います。参考の Issue #7251 でもそういう考え方の開発者さんは少なくも無さそうな雰囲気があります。
もちろん、is-wsl
からcmd.exe
によるWindowsネイティブのブラウザー呼び出しを試みようとする、その気の利いた工夫は良い事です。しかし、現状の実装は想定が甘く、WSLを悪魔合体的な環境で利用されている状況だけを前提に fallback も無い実装になっている事が悲しみを生んでしまった原因になっていると思います。開発環境の可搬性も保守性には有用な視点の1つにもなりますし、 default でも巧く解決されて多くのユーザーが意図に反したエラーに悩まされない実装になると嬉しいです。