最近、SolidJSに興味を持ちました。
JSXを使うことができるけど、React系とはアプローチが異なり、仮想DOMを使わないことで効率が良いらしい。
公式ページの情報
公式ページの情報でも、deno + SolidJS については書いてあります。
deno init --npm solid
とすれば、いろいろと開発環境を整えてくれるらしいのですが、今回これは使いません。
この形式は私の好みじゃないのです。
私は deno + preact でいろいろ作ってきたので、同じように deno + SolidJS で頑張ってみようと思いました。
……のですが、結構大変でした。その話を書き残しておこうと思います。
最初のチャレンジ
deno + SolidJSなので、最初に deno の設定です。
deno.json を次のようにします。
{
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "npm:solid-js",
"lib": [
"dom",
"deno.ns"
]
},
"imports": {
"solid-js": "npm:solid-js"
}
}次に簡単なソースとして index.tsx を作ってみます。
import { render } from "solid-js/web";
document.addEventListener("DOMContentLoaded", () => {
render(() => <div>Hello World</div>, document.body);
})renderの書き方がpreactと少し違います。
vscodeの補完機能では、
(alias) render(code: () => JSX.Element, element: MountableElement): () => void
import render
と表示されます。第1引数がpreactと違いますね。
(preactなら、 render(<div>Hello World</div>, document.body)) でよい。)
最後にJavaScriptに変換。
deno bundle index.tsx --output index.js
できた index.js を読み込むように、適当な HTML を用意して開いてみると……
Uncaught Error
at notSup (c:\Users\purple\Documents\vscode\TypeScript\TestSolidJS\index.js:1038:9)
at <anonymous> (c:\Users\purple\Documents\vscode\TypeScript\TestSolidJS\index.js:1043:3)
というエラーが出ました。
最初のチャレンジ失敗!
deno + SolidJS + esbuild
変換された index.js を調べると、次のようになっていました。
// index.tsx
document.addEventListener("DOMContentLoaded", () => {
notSup(() => /* @__PURE__ */ React.createElement("div", null, "Hello World"), document.body);
});deno.jsonの書き方(compilerOptionsとか)が悪いのかと、いろいろ試行錯誤したのですが解決しません。
deno bundle は preactには対応しているのですが、 SolidJSには対応していないので、notSup()となっているようです。
ここは deno bundle での変換をあきらめて、esbuildで変換を試みることにします。
esbuildを使うために、deno.json を変更します。
{
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "npm:solid-js",
"lib": [
"dom",
"deno.ns"
]
},
"nodeModulesDir": "auto",
"imports": {
"solid-js": "npm:solid-js",
"esbuild": "npm:esbuild"
}
}imports に esbuild を追加しました。
また、 nodeModulesDir を追加しました。
(これを設定しないと、esbuild が import を解決できずに、途中でエラー終了してしまいます。)
そして、変換用の build.ts を作ります。
import * as esbuild from "esbuild";
esbuild.build({
"entryPoints": ["./index.tsx"],
"outfile": "./index.js",
"bundle": true,
"platform": "browser",
"format": "iife",
"target": "esnext",
})次のコマンドで変換します。
deno -A build.ts
index.jsはできたものの、HTMLを開いても、やはり何も表示されません。
デバッグコンソールにエラーが出ていました。
Uncaught Error
at <anonymous> (c:\Users\purple\Documents\vscode\TypeScript\TestSolidJS\index.js:757:40)
at <anonymous> (c:\Users\purple\Documents\vscode\TypeScript\TestSolidJS\index.js:612:55)
at updateFn (c:\Users\purple\Documents\vscode\TypeScript\TestSolidJS\index.js:48:40)
at runUpdates (c:\Users\purple\Documents\vscode\TypeScript\TestSolidJS\index.js:322:19)
at createRoot (c:\Users\purple\Documents\vscode\TypeScript\TestSolidJS\index.js:52:14)
at render (c:\Users\purple\Documents\vscode\TypeScript\TestSolidJS\index.js:610:5)
at <anonymous> (c:\Users\purple\Documents\vscode\TypeScript\TestSolidJS\index.js:757:5)
またも、チャレンジ失敗!!
deno + SolidJS + esbuild + esbuild-plugin-solid
変換された index.js を調べると、次のようになっていました。
// index.tsx
document.addEventListener("DOMContentLoaded", () => {
render(() => /* @__PURE__ */ React.createElement("div", null, "Hello World"), document.body);
});notSup() はなくなりました。
でも、React.createElementとなっているのがあやしいです。
(だって、SolidJSなんだから)
自力では解決できなかったのでネット検索してみると、esbuildが SolidJS に対応できていないので、専用のプラグインを追加する必要がありました。
というわけで、esbuild-plugin-solid を deno.json の imports に追加します。
"imports": {
"solid-js": "npm:solid-js",
"esbuild": "npm:esbuild",
"esbuild-plugin-solid": "npm:esbuild-plugin-solid"
}build.ts も esbuild-plugin-solid を追加します。
import * as esbuild from "esbuild";
import { solidPlugin } from "esbuild-plugin-solid";
esbuild.build({
"plugins": [
solidPlugin(),
],
"entryPoints": ["./index.tsx"],
"outfile": "./index.js",
"bundle": true,
"platform": "browser",
"format": "iife",
"target": "esnext",
})deno -A build.ts して、HTMLを開いてみます。
すると……今回は成功!!!
HTMLを開くと「Hello World」の文字が出てくれます。
index.js を確認すると、下のようになっていました。
// index.tsx
var _tmpl$ = /* @__PURE__ */ template(`<div>Hello World`);
document.addEventListener("DOMContentLoaded", () => {
render(() => _tmpl$(), document.body);
});今までとは全然違いますね。この形が SolidJS を使ったときの JavaScript になるようです。
node_modules を削除するチャレンジ
変換して、動かすことができるようになったものの、 node_modules ができてしまうのが、私にとっては気に入りません。
できれば排除したいです。
そのためには、まず deno.json を編集します。
{
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "npm:solid-js",
"lib": [
"dom",
"deno.ns"
]
},
"imports": {
"solid-js": "npm:solid-js",
"esbuild": "npm:esbuild",
"esbuild-plugin-solid": "npm:esbuild-plugin-solid"
}
}"nodeModulesDir": "auto",を削除しました。
これで node_modules/ が自動で作成されなくなります。
既に作成されている node_modules を削除した後、 deno -A build.ts を実行すると、次のようなエラーが出ます。
✘ [ERROR] Could not resolve "solid-js/web"
index.tsx:1:39:
1 │ import { template as _$template } from "solid-js/web";
╵ ~~~~~~~~~~~~~~
You can mark the path "solid-js/web" as external to exclude it from the bundle, which will remove
this error and leave the unresolved path in the bundle.
error: Uncaught (in promise) Error: Build failed with 1 error:
index.tsx:1:39: ERROR: Could not resolve "solid-js/web"
let error = new Error(text);
index.tsx で import している "solid-js/web" が解決しないので、なんとかしろ!といっているようです。
その解決のために、"nodeModulesDir": "auto",を使っていたのです。それを削除したから当然の結果です。
まず、deno は nodeModulesDir がないときは、専用の場所に npm パッケージをダウンロードしています。
"solid-js/web"の場所は以下のコマンドを実行すればわかります。
deno eval 'console.log(import.meta.resolve("solid-js/web"))'
私の環境では、次のようになりました。
cyan:/mnt/c/Users/purple/Documents/vscode/TypeScript/TestSolidJS> deno eval 'console.log(import.meta.resolve("solid-js/web"))'
file:///home/purple/.cache/deno/npm/registry.npmjs.org/solid-js/1.9.11/web/dist/server.js
(WSL 環境のdenoなので、こんなディレクトリ名ですが、Windows版だと別のフォルダ名になるはず。)
そこで、build.ts に alias を追加して、"solid-js/web" と上の場所を関連付けます。
import * as esbuild from "esbuild";
import { solidPlugin } from "esbuild-plugin-solid";
esbuild.build({
"plugins": [
solidPlugin(),
],
"entryPoints": ["./index.tsx"],
"outfile": "./index.js",
"bundle": true,
"platform": "browser",
"format": "iife",
"target": "esnext",
"alias": {
"solid-js": "/home/purple/.cache/deno/npm/registry.npmjs.org/solid-js/1.9.11/dist/server.js",
"solid-js/web": "/home/purple/.cache/deno/npm/registry.npmjs.org/solid-js/1.9.11/web/dist/server.js",
}
})"solid-js/web" のなかで "solid-js" を import しているので、それも alias しています。
これで deno -A build.ts をしてみると……
../../../../../../../../home/purple/.cache/deno/npm/registry.npmjs.org/solid-js/1.9.11/web/dist/server.js:3:61:
3 │ import { Feature, Serializer, getCrossReferenceHeader } from 'seroval';
╵ ~~~~~~~~~
のような、新たなエラーが出てきます。
このあと、エラーが出てくるパッケージ名を同じように alias に追加してみたものの、うまくいきませんでした。
うまくいかない理由と改良
うまくいかない理由は、"/home/purple/.cache/deno/npm/registry.npmjs.org/solid-js/1.9.11/web/dist/server.js"が正しくないからでした。
この server.js は、serverモード用のパッケージらしいです。
deno eval 'console.log(import.meta.resolve("solid-js/web"))'
でパッケージの場所を探していましたが、
denoを使っている。
→ ブラウザ用ではなく、deno(のserver)用として探し出す。
→ 結果 serverモード用の server.js を見つけてしまう。
という動作になっているようです。
/home/purple/.cache/deno/npm/registry.npmjs.org/solid-js/1.9.11/packaege.json を確認すると……
"./web": {
"worker": {
"types": "./web/types/index.d.ts",
"import": "./web/dist/server.js",
"require": "./web/dist/server.cjs"
},
"browser": {
"development": {
"types": "./web/types/index.d.ts",
"import": "./web/dist/dev.js",
"require": "./web/dist/dev.cjs"
},
"types": "./web/types/index.d.ts",
"import": "./web/dist/web.js",
"require": "./web/dist/web.cjs"
},
"deno": {
"types": "./web/types/index.d.ts",
"import": "./web/dist/server.js",
"require": "./web/dist/server.cjs"
},となっています。
- deno用としては
"./web/dist/server.js" - browser用としては
"./web/dist/web.js"
にすると正しく解決できるようです。
そして、"solid-js" のファイルも同じように違っていました。
そこで、build.ts の alias を次のように修正します。
"alias": {
"solid-js": "/home/purple/.cache/deno/npm/registry.npmjs.org/solid-js/1.9.11/dist/solid.js",
"solid-js/web": "/home/purple/.cache/deno/npm/registry.npmjs.org/solid-js/1.9.11/web/dist/web.js",
}deno -A build.ts を実行すると、エラーが出なくなりました。
変換された JavaScript も node_modules があるときと同じで、HTMLを開くと「Hello World」が表示されました。
なお、上の書き方もあまりよくないようで、最終的な build.ts は次のようになりました。
import * as esbuild from "esbuild";
import { solidPlugin } from "esbuild-plugin-solid";
esbuild.build({
"plugins": [
solidPlugin(),
],
"entryPoints": ["./index.tsx"],
"outfile": "./index.js",
"bundle": true,
"platform": "browser",
"format": "iife",
"target": "esnext",
"nodePaths": [
"/home/purple/.cache/deno/npm/registry.npmjs.org/"
],
"alias": {
"solid-js": "solid-js/1.9.11/dist/solid.js",
"solid-js/web": "solid-js/1.9.11/web/dist/web.js",
}
})

コメント