C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

React プロジェクトの src 内のコンポーネントから諸事情によりウェブサイト外部の SCRIPT を読む方法のメモ

諸事情の例

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 )
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.reacttsc が認識できなかった

諸事情により ./src ではなく ./src.react に react 用のソースを配置していたが、 tsconfig.jsoninclude では "." 文字の入ったディレクトリーは認識しない仕様らしく困りました。

考えた解決方法:

  1. symlink を作って渡す
  2. ./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(≈rendygfx-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 EngineHTML5 出力は現実的に考えない方がいいです(UE5移行へWebGPUと併せて期待したいけど、期待しないでおいた方がいいかも)
    • 豊富なエコシステムやコミュニティーもあるので Unity で WebGL2 バックエンドにするのが現時点だけを見るなら現実的で良いかもしれません
    • Web 親和性の点では playcanvas もよいかもしれません
  • Rust 系は...
    • unrust は現時点で Web へきっちり出力できるたぶん唯一の3D-GPU描画系です
    • Amethyst は wasm+GL サポートを開発途中ですが、このメモの作成時点では全ての要素技術にかなり強い技師でも辛うじてバギーながらChromeで動くような動かないような微妙な出力に辿り着ける程度の状態です
    • harmony は描画バックエンドのポテンシャル的には WebGPU 対応、 OpenGL 対応により Web 出力できますが、採用する ECS バックエンドやマルチスレッドに最適化しまくっている事から WebWorker の制限と性能の懸念などもあり今の所 Author は Web 出力にそれほど積極的ではないようです。(Webよりエンジンとしての機能実装を進めたい状況でもあります)
    • oxidatorRTS ゲームエンジンに特化していますが、 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.jsbabylon.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"

補足的なメモ

1. npm のアカウントとパッケージの公開は必要?

MDNの記事そのものではそこも含めたやり方の例として必要としていますが、本質的には不要です。 npm へ @mynpmusername/hello-wasm パッケージを公開せずにこのチュートリアルを進める場合は:

  1. 「パッケージの npm への発行」の直前までは記事の通り進めます
  2. package.jsondependencies.@mynpmusername/hello-wasm をバージョン番号表記ではなく "file:./hello-wasm/pkg" のようにローカルファイルシステムからのパッケージ参照で記述します
  3. npm install または yarn すると他の依存パッケージ群と併せて @mynpmusername/hello-wasm./hello-wasm/pkg から node_modules へ install されます
  4. 続きは記事の通り進めます

2. webpack 使う必要ある?

Webサービスとしてデプロイする形態で動作確認してみたければ事実上必要です。

webpack-dev-serverすると index.js, node_modules の中身(hello-wasm/pkgからnode_modulesへinstallされた@mynpmusername/hello-wasmパッケージも含みます)を統合して開発用のhttpdwebpack.config.jsの定義に従って起動してくれます。webpackを使わないとnode_modulesのパッケージ群が統合されないため一般的なhttpdで単純にディレクトリーを指定して起動しても期待動作しません。

hello-wasm を直接 Node.js のインタラクティブ・シェルで叩いて確認したいだけなら不要です。 WebAssemblyコードのロードと実行 - WebAssembly | MDNfetch を 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/

f:id:USAGI-WRP:20200704073323p:plain

f:id:USAGI-WRP:20200704074331p:plain

CRA=create-react-app が WSL で start できない理由と回避方法のメモ

問題

WSLでCRAしてyarn startするとcmd.exeを実行できずにウェブブラウザーの起動どころかサーバーも起動せず死んでしまいます。

再現方法:

  1. WSLで
  2. npx create-react-app hoge して
  3. 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 startBROWSER=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 startPATH=$PATH:/mnt/c/Windows/System32 yarn start にします
  • または package.json"start": "PATH=$PATH:/mnt/c/Windows/System32 react-scripts start" して yarn start します
  • または WSL の実行環境の PATHexport PATH=$PATH:/mnt/c/Windows/System32 します
  • または /etc/wsl.conf[Interop] セクションで appendWindowsPath = True または False を定義しない設定へ変更します
  • または cmd.exe を実行可能な他のお膳立てをしてあげます

原因

  • CRAがブラウザーを起動するために"cmd.exeを使える状態を前提"にのみ実装されている
  • is-wsl で WSL を検出して気を利かせてcmd.exeを起動しようとしているっぽい †参考1

参考

関連おまけ

思うところメモ

BROWSER=none 回避策は PATH 回避策より一般的には良いです。WSLのユーザーのおそらくほとんどはWSLを"WindowsGNU/Linux悪魔合体した何か"ではなく、Windowsから便利に利用しやすいGNU/Linux環境として利用し、WSLがWindows上で実行されながらもWindows特有の環境要因にはできるだけ依存せずGNU/Linux環境としての純粋性や可搬性を維持したWindowsとは異なる環境かつWindowsとのつながりもユーザーが求めれば構築しやすい、そんな何かであることを望んでいるのではないかな、と思います。参考の Issue #7251 でもそういう考え方の開発者さんは少なくも無さそうな雰囲気があります。

もちろん、is-wslからcmd.exeによるWindowsネイティブのブラウザー呼び出しを試みようとする、その気の利いた工夫は良い事です。しかし、現状の実装は想定が甘く、WSLを悪魔合体的な環境で利用されている状況だけを前提に fallback も無い実装になっている事が悲しみを生んでしまった原因になっていると思います。開発環境の可搬性も保守性には有用な視点の1つにもなりますし、 default でも巧く解決されて多くのユーザーが意図に反したエラーに悩まされない実装になると嬉しいです。