3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Nodeもわからない人がDenoの導入を検討してみた

Last updated at Posted at 2023-06-28

この記事の結論

Denoは速いが導入を検討するのも早い

正直に言えばこれタイトルに書きたかったが
それをするにはNodeの知識も何もかもたりてなかった

この記事は、DenoどころかNodeも知らない私のために書いた記事だ

Denoについて耳にしたことはあるが、ほとんど何もわからない人は
この記事が少しは参考になると思う

  • JavaScript、Vueでプロジェクト開発を経験している
  • TypeScriptの知識はない
  • 最近やっと環境構築について興味を持った
  • Nodeの知識はなく、JavaScriptを使うための呪文程度にしか考えていない
  • Denoについて全く知らないが新しい開発環境になり得るため調査をはじめた

調査内容

  • Denoとは何か
    • 動画説明:Nodeで後悔したこと
    • 動画説明:Denoに導入する仕組み
  • Denoを試してみて体感したこと
    • package.jsonがないこと
      • Denoをインストールしてみる
      • Denoのフレームワークを試す
    • NPMが使えない、互換性がない
      • DenoでAPIを呼び出してみる
  • 感想 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ではそれを行わない
      • なので前述した通り事前に許可する権限を付与する仕組みにしてある
  • 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のビルドプロセスように複雑な定義ファイルは読み込まない
    • その他

実際に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
    • Unocss
    • bootstrap
    • React/Vue/SolidJS/Yew/
  • Fresh
    • twind
    • preact

Aleph.js

インストールはGitに書いてる一行で完結

Aleph.js_install.png

サンプルのコードにはTODOリストが実装されている

alephjs_demo1.png
alephjs_demo2.png
alephjs_demo3.png

補足: 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

fresh_install.png

こちらはもとからVueのサンプルはなかったので
TypeScriptで手順通りインストール

fresh_home.png

DenoでAPIを呼び出す(TODOアプリを作ってみる)

先に開発されて人気なのはAleph.jsのようだが
freshの方がディレクトリ構造が面白そうだったので
freshフレームワークを使いつつTODOアプリを作ってみた

Fetch

~/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>
    </>
  );
}

gql.png

その他の情報

.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で開発していくことは茨の道になりそうで
二の足を踏んでしまうのが正直なところだ

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?