この記事の結論
Denoは速いが導入を検討するのも早い
正直に言えばこれタイトルに書きたかったが
それをするにはNodeの知識も何もかもたりてなかった
この記事は、DenoどころかNodeも知らない私のために書いた記事だ
Denoについて耳にしたことはあるが、ほとんど何もわからない人は
この記事が少しは参考になると思う
- JavaScript、Vueでプロジェクト開発を経験している
- TypeScriptの知識はない
- 最近やっと環境構築について興味を持った
- Nodeの知識はなく、JavaScriptを使うための呪文程度にしか考えていない
- Denoについて全く知らないが新しい開発環境になり得るため調査をはじめた
調査内容
- Denoとは何か
- 動画説明:Nodeで後悔したこと
- 動画説明:Denoに導入する仕組み
- Denoを試してみて体感したこと
- package.jsonがないこと
- Denoをインストールしてみる
- Denoのフレームワークを試す
- NPMが使えない、互換性がない
- DenoでAPIを呼び出してみる
- package.jsonがないこと
- 感想 Denoをプロジェクトに導入できるか
Denoとは何か
Denoとは、Node.jsの開発者Ryan Dahl(ライアン・ダール氏)が
Nodeの設計について後悔している10の問題を解決するため
2018年に新しく作り出したランタイム環境
Nodeで後悔したこと
彼の述べたNode.jsに関する10の反省点は以下の通り
またなぜそれを後悔しているのか、動画を見ながら可能な限り解釈してまとめた
英語:動画内スレッド文章そのまま
日本語: 筆者の補足情報
- Not sticking whis Promises
- Promiseを使い続けなかったことによるasync/awaitのエコシステムの開発が遅れたこと
- Security
- Nodeが全てのシステムコールにアクセスできてしまうことによるセキュリティの低下
- デフォルトで全てのシステムにアクセス権限がある状態
- The Build System(GYP)
- jipで複雑になってしまった依存関係とビルドシステム
- package.json
- 直接URLで呼び出せば良いのに、モジュールという概念を生み出してしまったpackage.json
- node_modules
- ローカルに落とすことで各プロジェクトが大きくなり圧迫するnode_modules
- requite("module") without the extentions ".js"
- requiteモジュールに拡張機能を含めなかったためあまりにも非明示的になり、ファイルシステム調査時に非常に手間がかかるようになったこと
- index.js
- ブラウザでも
/
で/index.html
が読み込めるのに、nodeでrequire("./")
でrequire("./index.js")
を読み込むようにしたことでloaderを複雑にしてしまったこと
- ブラウザでも
Denoに導入する仕組み
- Security
- Utillze the fact that JavaScript is a secure sandbox
- デフォルトでアクセス権はなく事前に許可を設定することができる
- 例: deno task --allow-net
- Do not allow arbitrary native function tobe bound into V8
- NodeではV8のネイティブ関数を直接呼び出すが、Denoではそれを行わない
- なので前述した通り事前に許可する権限を付与する仕組みにしてある
- Utillze the fact that JavaScript is a secure sandbox
- Deno Goals
- Simplify the module system
- ローカルへの相対パスか絶対パス、あるいはURLでしかモジュールを読み込めない
- そのため既存のモジュールとの親和性は低くなる
- URLは一度実行すればグローバルにキャッシュされる
node_modulesのように各ローカルへのインストールではない - --reloadで再度実行できる
- TypeScript compiler build into the executable
- Ship only a single executable with minimal linkage
- Nodeのビルドプロセスように複雑な定義ファイルは読み込まない
- その他
- 未処理のpromiseは即座にシャットダウンする
- top-level awaitをサポートする
- ブラウザと機能が被ればDenoはブラウザの機能を使用できるようにする(fetchとかWindowsとか)
- Simplify the module system
実際にDenoを試してみて体感したこと
5年前の動画の内容を読み解くのに時間をかけてしまったが
ここから先は実際に触れてみての感想となる
現在は2023年のため、2018年の動画で話してる内容と
現在のDenoは変化しているかもしれないが
触れてみてすぐに体感できたことは以下の通りだ
- package.jsonがないこと
- URL指定で使いたいライブラリが指定できるのでわかりやすい
- deno.jsonに書き込めば各ファイルでは定義名で読み込める
- NPMのライブラリが全て使えないわけではない
- もちろん動画の中でも全て捨てたと言ってるのでほぼ使えないが、一部Denoで使えるようにしてくれている
- 改めて新しいライブラリを探したり自分で実装するのは大変なコストがかりそう
package.jsonがないこと
Nodeとの違いは概ね理解できたので
実際にインストールして現状を確認してみる
Denoをインストールする(MAC OS BigSur)
$ brew install deno
バージョンの確認をして終わり
ものすごくはやい
$ deno --version
deno 1.32.5 (release, aarch64-apple-darwin)
v8 11.2.214.9
typescript 5.0.3
Denoではpackage.jsonの代わりに
設定ファイルとしてdeno.jsonができていた
deno.jsonには「imports」という項目がある
必要なものはURLで直接指定する
moduleごとのパッケージがないからごちゃごちゃしない
各ファイルではimportsに書いた名前で呼び出せるため
全てのファイルでURLを書く必要はない
例:aleohjsフレームワークを試した時のもの
{
"imports": {
"~/": "./",
"std/": "https://deno.land/std@0.180.0/",
"aleph/": "https://deno.land/x/aleph@1.0.0-beta.43/",
"aleph/server": "https://deno.land/x/aleph@1.0.0-beta.43/server/mod.ts",
"aleph/dev": "https://deno.land/x/aleph@1.0.0-beta.43/server/dev.ts",
"aleph/react": "https://deno.land/x/aleph@1.0.0-beta.43/framework/react/mod.ts",
"aleph/plugins/react": "https://deno.land/x/aleph@1.0.0-beta.43/framework/react/plugin.ts",
"react": "https://esm.sh/v117/react@18.2.0",
"react-dom": "https://esm.sh/v117/react-dom@18.2.0",
"react-dom/": "https://esm.sh/v117/react-dom@18.2.0/"
},
"scopes": {}
}
各ファイルでの呼び出し方例
import type { FormEvent } from "react";
import { Head, useData } from "aleph/react";
NPMのライブラリが全て使えないわけではない
動画では過去のNPMライブラリとの互換性は無くなるとの話をしていた
それはすごく爽快なことだろうとは思うけれど
全部使えないとなると一から開発しなおす必要があるし
プロジェクトにすぐに導入を検討することができない
なので、現在すでにNPMのライブラリを読み込むための仕組みが
導入されいてる様子だった
nom:
とつけて実行すると
グローバルにNPMライブラリがインストールされて
以降はそこから使用されるようになる
とはいえ、もしそのライブラリがnode-modulesから利用されることを想定している場合は
個別にnode-modulesディレクトリを作ることもできる様子
Denoのフレームワーク
試してみたのは以下二つ
インストールの方法はどちらもとても簡単
Aleph.js
インストールはGitに書いてる一行で完結
サンプルのコードにはTODOリストが実装されている
補足: Vueのサンプルはダウンロードできなかった(2023/04/26)
公式をみるとvue templateもインストールできるかのような記述があるが
2023/04/26現在は以下のような警告が出てダウンロードできなかった
公式サイトの情報はGitの更新に追いついてなくて古いかもしれない
! Invalid template name vue, must be one of [react,react-mdx,yew,leptos,api]
fresh
こちらはもとからVueのサンプルはなかったので
TypeScriptで手順通りインストール
DenoでAPIを呼び出す(TODOアプリを作ってみる)
先に開発されて人気なのはAleph.jsのようだが
freshの方がディレクトリ構造が面白そうだったので
freshフレームワークを使いつつTODOアプリを作ってみた
Fetch
- DenoではFetchがdefaultで利用できる
- fetchで直接GQLquery(GET)を送る
- Freshではislandsディレクトリで定義したイベントのみがクレイアント側で実行できる
- これに気づかないままずっと ルート直下の index.tsx で実行して右往左往していた
- https://github.com/denoland/fresh/discussions/716
- https://fresh.deno.dev/docs/getting-started/adding-interactivity
~/islands/GqlTable.tsx
import { useState } from "preact/hooks";
import { Button } from "../components/Button.tsx";
const CONTENT_API_URL = "あなたのContent API Endpoint URL";
const AUTH_TOKENS = "あなたのトークン";
// クエリはご自身で作ったテーブルにあわせて作成してください
const params = new URLSearchParams({
query: `{
tasks(orderBy: id_ASC) {
id
name
description
createdAt
}
}`,
});
export default function GqlTable() {
const [data, setData] = useState({data: {tasks: []}});
const [isLoading, setIsLoading] = useState(false);
const [err, setErr] = useState('');
const handleClick = async () => {
setIsLoading(true);
try {
const response = await fetch(`${CONTENT_API_URL}?${params}`, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${AUTH_TOKENS}`,
},
});
if (!response.ok) {
throw new Error(`Error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err) {
setErr(err.message);
} finally {
setIsLoading(false);
}
};
return (
<div>
<div class="flex gap-2 w-full">
<Button onClick={handleClick}>Fetch data</Button>
</div>
<div>
{err && <h2>{err}</h2>}
<div class="relative overflow-x-auto my-8">
<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400">
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th scope="col" class="px-6 py-3">name</th>
<th scope="col" class="px-6 py-3">description</th>
<th scope="col" class="px-6 py-3">created_at</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-slate-800">
{!isLoading && data.data.tasks.length==0 && <tr class="text-center bg-white border-b dark:bg-gray-800 dark:border-gray-700">
<td colspan="3" class="py-2">Not data</td></tr>}
{isLoading && <tr class="text-center bg-white border-b dark:bg-gray-800 dark:border-gray-700">
<td colspan="3" class="py-2">Loading...</td></tr>}
{data.data.tasks.map(person => {
return (
<tr key={person.id} class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
<td class="py-2">{person.name}</td>
<td class="py-2">{person.description}</td>
<td class="py-2">{person.createdAt}</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
</div>
);
}
~/routes/index.tsx
import { Head } from "$fresh/runtime.ts";
import GqlTable from "../islands/GqlTable.tsx";
export default function Home() {
return (
<>
<Head>
<title>Fresh App</title>
</Head>
<div class="p-4 mx-auto max-w-screen-md">
<img
src="/logo.svg"
class="w-32 h-32"
alt="the fresh logo: a sliced lemon dripping with juice"
/>
<p class="my-6">
Welcome to `fresh`. Try updating this message in the
./routes/index.tsx file, and refresh.
</p>
<GqlTable />
</div>
</>
);
}
その他の情報
.envはブラウザ側で?使えない
環境変数を定義するのに.envを使いたかったのだが
NUXTのような動的インポートをサポートしていないらしく
サーバー側でしか使えなかった
なので、deno実行時に直接環境変数を追加するしかない
GQLのライブラリはまだ使えない
サンプル通りに実行してもエラーがでるので
こちらのライブラリの利用はやめた
まだ解決はしてない様子(2023/06/20)
Error: Expected { __validationErrors: undefined, description: undefined, extensions: {}, astNode: { kind: "SchemaDefinition", operationTypes: [Array] }, extensionASTNodes: [], _queryType: Query, _mutationType: undefined, _subscriptionType: undefined, _directives: [@include, @skip, @deprecated, @specifiedBy], _typeMap: { Query: Query, String: String, Boolean: Boolean, __Schema: __Schema, __Type: __Type, __TypeKind: __TypeKind, __Field: __Field, __InputValue: __InputValue, __EnumValue: __EnumValue, __Directive: __Directive, __DirectiveLocation: __DirectiveLocation }, _subTypeMap: {}, _implementationsMap: {} } to be a GraphQL schema.
at Q (https://esm.sh/v117/graphql@16.6.0/deno/type/schema.js:2:843)
at W (https://esm.sh/v117/graphql@16.6.0/deno/type/validate.js:2:891)
at a (https://esm.sh/v117/graphql@16.6.0/deno/graphql.js:2:863)
at https://esm.sh/v117/graphql@16.6.0/deno/graphql.js:2:483
at new Promise (<anonymous>)
at R (https://esm.sh/v117/graphql@16.6.0/deno/graphql.js:2:466)
at runHttpQuery (https://deno.land/x/gql@1.2.3/common.ts:28:16)
at eventLoopTick (ext:core/01_core.js:165:11)
at async https://deno.land/x/gql@1.2.3/http.ts:91:22
at async handleGraphQL (~/edge/supabase/functions/graphql/index.ts:37:15)
所感
既存プロジェクトをDenoに移行できるか
しようと思えばできるかもしれないが
Nodeのプロジェクトで使っているプラグインが全て使えるわけではないし
その代変えとなるプラグインを探したり実装するのにかなり時間がかかる
Denoは基本がTypeScriptということも踏まえると
フロントエンド的にも学習コストが高め
プライベートで色々開発してテストする分には楽しい
仕事で導入を検討するとなるとフロントエンドだけではなく
チーム全体で学習しなければいけないコストもあり
Denoがとても速いので導入て開発をやってみたいけれど
そこまでの恩恵を受けれる仕組みのプロジェクトはあるのか
と言われると難しい
やはりNodeの情報量が勝る現在
Denoで開発していくことは茨の道になりそうで
二の足を踏んでしまうのが正直なところだ