JavaScriptでマクロを使用する方法

JavaScriptでマクロを使用する方法

言語では、DSL を実装するためにマクロがよく使用されます。マクロを使用すると、開発者は JSX 構文の実装など、一部の言語の形式をカスタマイズできます。 WASM が実装されたので、他の言語で Web ページを書くことは不可能ではありません。たとえば、Rust 言語には強力なマクロ機能があるため、Rust ベースの Yew フレームワークでは Babel のようなものを実装する必要はなく、言語自体に依存することで JSX のような構文を実装できます。 JSX のような構文をサポートする Yew コンポーネントの例。

MyComponent のコンポーネントを実装します {
    // ...

    fn view(&self) -> HTML {
        onclick を self.link.callback(|_| Msg::Click); とします。
        html! {
            <button onclick=onclick>{ self.props.button_text }</button>
        }
    }
}

JavaScript マクロの制限

Rust とは異なり、JavaScript 自体はマクロをサポートしていないため、ツール チェーン全体でマクロは考慮されません。したがって、カスタム構文を認識するマクロを記述することはできますが、最も一般的な VSCode や Typescript などのサポートツールチェーンがそれをサポートしていないため、構文エラーが発生します。同様に、Babel 自体が使用するパーサーは、別の Babel をフォークしない限り、拡張構文をサポートしません。したがって、babel-plugin-macros はカスタム構文をサポートしていません。 ただし、テンプレート文字列関数の助けを借りれば、迂回して少なくとも構文ツリーを部分的にカスタマイズできるようになります。 JavaScript で直接 GraphQL を記述することをサポートする GraphQL の例。

'graphql.macro' から {gql} をインポートします。

定数クエリ = gql`
  クエリユーザー{
    ユーザー(ID: 5) {
      苗字
      ...ユーザーエントリ1
    }
  }
`;

// コンパイル時に ↓ ↓ ↓ ↓ ↓ ↓ に変換されます

定数クエリ = {
  "種類": "ドキュメント",
  「定義」: [{
    ...

Babel プラグインの代わりにマクロを使用する理由は何ですか?

Babel プラグインの機能はマクロよりもはるかに優れているため、場合によってはプラグインを実際に使用する必要があります。マクロが Babel プラグインよりも優れている点の 1 つは、マクロの考え方がそのまま使用できることです。 React を使用する開発者は、さまざまな基礎的な詳細をカプセル化して開発者がコードの記述に集中できるようにする有名な Create-React-App について聞いたことがあるはずです。しかし、CRA の問題は、カプセル化が厳しすぎることです。Babel プラグインをカスタマイズする必要がある場合は、基本的に yarn react-script eject を実行して、基礎となる詳細をすべて公開する必要があります。 マクロに関しては、プロジェクトの Babel 構成に babel-plugin-macros プラグインを追加するだけで、プラグインなどのさまざまなプラグインをダウンロードする必要がなく、カスタム Babel マクロを完全にサポートできます。 CRA には babel-plugin-macros が組み込まれているため、CRA プロジェクトで任意の Babel マクロを使用できます。

マクロの書き方は?

導入

マクロは Babel プラグインに非常によく似ているため、事前に Babel プラグインの書き方を知っておくと非常に役立ちます。Babel には、Babel プラグインを最初から書き込む方法に関する公式マニュアルがあります。 Babel プラグインの書き方がわかったので、まずマクロを使用する例を使用して、Babel がファイル内のマクロを識別する方法を説明しましょう。これは特殊な構文なのでしょうか、それとも単に $ 記号の使い方が間違っているのでしょうか?

'preval.macro' から preval をインポートします

const one = preval `module.exports = 1 + 2 - 1 - 1`

これは非常に一般的なマクロです。その機能は、コンパイル時に文字列内の JavaScript コードを実行し、実行結果を対応する場所に置き換えることです。上記のコードは次のように展開されます。

'preval.macro' から preval をインポートします

定数 1 = 1

使用の観点から、マクロの識別に関連する唯一のものは *.macro 文字であり、これはまさに Babel がマクロを識別する方法です。実際、*.macro 形式だけでなく、Babel は名前が正規表現 /[./]macro(\.c?js)?$/ に一致するライブラリを Babel マクロと見なします。これらの一致表現の例をいくつか示します。

'私のマクロ'
'my.macro.js'
'my.macro.cjs'
「私の/マクロ」
'my/macro.js'
'my/macro.cjs'

書く

次に、URL を介していくつかのライブラリをインポートし、コンパイル中にこれらのライブラリのコードを事前に取得して処理し、ファイルにインポートするために使用される importURL マクロを記述します。いくつかの Webpack プラグインがすでに URL からのライブラリのインポートをサポートしていることは知っていますが、これは楽しみのためにマクロの書き方を学ぶのにもよい例です。そして、NodeJS で同期リクエストを作成する方法! :)

準備する

まず、importURL という名前のフォルダーを作成し、npm init -y を実行してプロジェクトをすばやく作成します。プロジェクトでマクロを使用する人は、babel-plugin-macros をインストールする必要があります。同様に、マクロを作成する人もこのプラグインをインストールする必要があります。作成する前に、マクロの作成を支援する他のライブラリも事前にインストールする必要があります。開発の前に、次のことを行う必要があります。

  • package.json 内の名前を import-url.macro に変更します。これは、Babel がマクロを認識する形式に準拠しています。
  • マクロを作成するには、Babel が提供するヘルパー メソッドを使用する必要があります。 yarn add babel-plugin-macrosを実行します。
  • yarn は、Nodefs モジュールを置き換えるのに使いやすいライブラリである fs-extra を追加します。
  • yarn add find-root、マクロを書く過程で、処理されたファイルのパスに従って作業ディレクトリを見つけ、それをキャッシュに書き込む必要があります。これはパッケージ化されたライブラリです。

私たちの目標は、次のコードを

'importurl.macros' から importURL をインポートします。

React を importURL としてインポートします。

// コンパイルして importURL を 'importurl.macros' からインポートします。

React を実装するには、次のコードを実行します。

コードの importURL 関数の最初のパラメータをリモート ライブラリのアドレスとして解析し、コンパイル中に Get リクエストを通じてコード コンテンツを同期的に取得します。次に、それをプロジェクトの最上位フォルダーの .chache に書き込み、対応する importURL ステートメントを require(...) ステートメントに置き換えます。path... は、importURL ファイルの .cache ファイル内の相対パスを使用するため、webpack は最終的にパッケージ化されたときに対応するコードを見つけることができます。

始める

まずは最終的なコードがどのようなものか見てみましょう

'child_process' から execSync をインポートします。
'find-root' から findRoot をインポートします。
'path' からパスをインポートします。
'fs-extra' から fse をインポートします。

'babel-plugin-macros' から {createMacro} をインポートします。

const syncGet = (url) => {
  const data = execSync(`curl -L ${url}`).toString();
  if (データ === '') {
    新しいエラーをスローします('空のデータ');
  }
  データを返します。
}

count = 0 とします。
エクスポート const genUniqueName = () => `pkg${++count}.js`;

module.exports = createMacro((ctx) => {
  定数{
    参照、// ファイル内のマクロへのすべての参照 babel: {
      タイプ: t、
    }
  } = ctx;
  // Babelは現在処理中のファイルパスをctx.state.filenameに設定します
  定数ワークスペースパス = findRoot(ctx.state.filename);
  // キャッシュ フォルダーを計算します const cacheDirPath = path.join(workspacePath, '.cache');
  //
  const calls = references.default.map(path => path.findParent(path => path.node.type === 'CallExpression' ));
  呼び出し.forEach(nodePath => {
    // astNode の型を判定する if (nodePath.node.type === 'CallExpression') {
      // 関数の最初の引数が純粋な文字列であることを確認する if (nodePath.node.arguments[0]?.type === 'StringLiteral') {
        // リモートライブラリのアドレスとしてパラメータを取得します。const url = nodePath.node.arguments[0].value;
        // URL に従ってコードを取得します。const codes = syncGet(url);
        // 競合を防ぐために一意のパッケージ名を生成します。const pkgName = genUniqueName();
        // 書き込まれる最終的なファイル パスを決定します。const cahceFilename = path.join(cacheDirPath, pkgName);
        //fse ライブラリを通じてコン​​テンツを書き込むと、outputFileSync は存在しないフォルダーを自動的に作成します fse.outputFileSync(cahceFilename, codes);
        // 相対パスを計算します const relativeFilename = path.relative(ctx.state.filename, cahceFilename);
        // importURL ステートメントを置き換えるための最終計算 nodePath.replaceWith(t.stringLiteral(`require('${relativeFilename}')`))
      }
    }
  });
});

マクロを作成する

createMacro 関数を使用してマクロを作成します。createMacro はマクロを生成するためのパラメータとして記述した関数を受け入れますが、createMacro の戻り値が何であるかは実際には気にしません。なぜなら、コードは最終的にそれ自体に置き換えられ、実行時には実行されないからです。 私たちが書いた関数の最初の引数は、Babel が渡した何らかの状態であり、その型が何であるかを簡単に確認できます。

関数 createMacro(ハンドラ: MacroHandler、オプション?: Options): any;
インターフェース MacroParams {
      参照: { デフォルト: Babel.NodePath[] } & References;
      状態: Babel.PluginPass;
      babel: Babel の type;
      config?: { [キー: 文字列]: 任意 };
  }
エクスポートインターフェースPluginPass {
    ファイル: BabelFile;
    キー: 文字列;
    オプション: プラグインオプション;
    cwd: 文字列;
    ファイル名: 文字列;
    [キー: 文字列]: 不明;
}

AST の視覚化

これから処理するコードの構文木を見るには、astexplorerを使います。次のコードの場合

'importurl.macros' から importURL をインポートします。

React を importURL としてインポートします。

次の構文木が生成されます

赤でマークされた構文ツリー ノードは、Babel が ctx.references を通じて渡すものなので、arguments プロパティの下のパラメーターを取得し、リモート ライブラリの URL アドレスを取得するには、.findParent() メソッドを使用して親ノード CallExpresstion を見つける必要があります。

同期リクエスト

ここでの難しさの 1 つは、Babel が非同期変換をサポートしていないことです。すべての変換操作は同期的であるため、リクエストも同期リクエストである必要があります。これは簡単に実行でき、Node は sync: true のようなオプションを提供するだろうと考えていました。しかし、Nodeは、以下の奇妙な方法を選択しない限り、同期リクエストをサポートしません。

const syncGet = (url) => {
  const data = execSync(`curl -L ${url}`).toString();
  if (データ === '') {
    新しいエラーをスローします('空のデータ');
  }
  データを返します。
}

エンディング

コードを取得したら、最初に計算したファイルパスにコードを書き込みます。ここで fs-extra を使用する目的は、fs-extra が書き込み時に存在しないフォルダーに遭遇した場合、fs のように直接エラーをスローするのではなく、対応するファイルを自動的に作成することです。書き込みが完了したら、Babel が提供する補助メソッド stringLiteral を使用して文字列ノードを作成し、importURL(...) を置き換えて、変換プロセス全体が完了します。

やっと

このマクロにはいくつか欠陥があり、興味のある学生は引き続き改善することができます。

同じ URL を識別して再利用するためのライブラリはありませんが、マクロの書き方という目的であればこれらで十分だと思います。

genUniqueNameはファイル間で重複するパッケージ名を計算します。正しいアルゴリズムは、URLに基​​づいてハッシュ値を一意のパッケージ名として計算することです。

JavaScript でマクロを使用する方法についての記事はこれで終わりです。JavaScript でマクロを使用する方法についての詳細は、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • JS イベントループの仕組み イベントループ マクロタスク マイクロタスク 原理分析
  • JS 非同期マクロキューとマイクロキューの原則の違いの詳細な説明
  • JavaScript イベント ループとマクロタスクおよびマイクロタスクの原則の分析
  • JavaScript イベント ループ マイクロタスクとマクロタスク キューの原理に関する簡単な説明
  • JS非同期マクロキューとマイクロキューの原理の詳細な説明

<<:  MySQL 5.7.20 zip インストール チュートリアル

>>:  Windows Server 2016 で Flash を有効にする方法

推薦する

MySQL データベース 8 - データベース内の関数の適用の詳細な説明

データベースの組み込み関数の使用この記事では、主に日付関数、文字列関数、数学関数など、データベースの...

Nodejs と Socket.IO を組み合わせて Websocket の即時通信を実現

目次WebSocketを使用する理由ソケット.ioオープンソースプロジェクト効果プレビューアプリイン...

UbuntuはCUDAの複数のバージョンをインストールし、いつでも切り替えることができます

CUDA とは何かを紹介するのではなく、複数の CUDA バージョンの共存とリアルタイム切り替えをど...

Linux でプロセスを効果的に管理するための 8 つのコマンド

序文プロセス管理の役割:サーバーの健全性状態を判定する: プロセスの状態 (メモリ、CPU 占有率な...

JavaScript のマイクロタスクとマクロタスクの説明

序文: js はシングルスレッド言語なので、非同期にすることは不可能です。しかし、js のホスト環境...

HTML テーブルの空白セル補完を実装する方法

私が初めて Web 開発を独学で学んだ頃は、いわゆる DIV/CSS レイアウトはなく、テーブル レ...

Dockerはelasticsearchイメージを起動し、ディレクトリをマウントした後にエラーを解決します

docker hub から es イメージ (バージョン 6.4.2) をダウンロードしました。詳細...

Linux でハイパースレッディング技術を動的に有効/無効にする方法の詳細な説明

序文Intel のハイパースレッディング テクノロジーにより、1 つの物理コア上で 2 つのスレッド...

ネイティブJSが様々なスポーツの均一な動きを実現

この記事では、ネイティブ JS で実装された均一なモーションを紹介します。その効果は次のとおりです。...

MySQLでページングクエリを実装する方法

SQL ページング クエリ:背景会社のシステムには、構成管理用のプラットフォーム、いわゆる CRUD...

MySQL 8.0.16 圧縮パッケージのインストールと設定方法のグラフィックチュートリアル

この記事では、MySQL 8.0.16圧縮パッケージのインストールと設定方法を参考までに紹介します。...

CSSで記事の区切り線のスタイルを実装するさまざまな方法のまとめ

この記事では、CSS で記事の区切り線を実装するさまざまな方法をまとめています。区切り線はページを美...

JavaScript で文字列を数値に変換する方法

目次1.parseInt(文字列、基数) 2. 数値() 3.parseFloat()主なメソッドは...

lastInfdexOf 関数の MySQL 実装例

MySQL では lastIndexOf に似た関数を使用する必要がある場合もありますが、すぐに使用...

div要素に丸い境界線を追加する方法

以下のように表示されます。 CSSコードコンテンツをクリップボードにコピー分割{境界線: 2px 固...