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 を有効にする方法

推薦する

Linux で rc.local ファイルがない場合の完璧なソリューション

新しい Linux ディストリビューションには rc.local ファイルがなくなりました。サービス...

Win10 64ビットMySQL8.0のダウンロードとインストールのチュートリアル図

公式サイトから MySQL をダウンロードしてインストールし、クライアントにログインするにはどうすれ...

docker公式mysqlイメージのカスタム構成の詳細な説明

インストール時間を節約するために、公式の mysql docker イメージを使用して mysql ...

MySQL 8.0.12 のインストールと環境変数の設定チュートリアル (Win10 の場合)

Windows 10 プラットフォームでの MySQL のインストール、構成、起動、ログイン、環境...

K8S クラスターを構築し、Hyper-V で Docker をインストールする方法

Win10 システムをインストールしていて、k8s クラスターを構築する場合、Win10 に付属する...

Node.js のイベント モジュールに関する知識ポイントのまとめ

Node の研究と応用を通じて、NodeJS はシングルスレッド、イベント駆動型、非ブロッキング I...

MySQL ストアド プロシージャの作成と呼び出しの詳細な説明

目次序文ストアドプロシージャ: 1. ストアドプロシージャの作成と呼び出し1. ストアドプロシージャ...

Linux での syslogd および syslog.conf ファイルの解釈

1: syslog.conf の概要異なるタイプの Unix の場合、標準の UnixLog システ...

VMware仮想マシンブリッジによるインターネット相互接続を実現する方法

VMware をインストールして新しい仮想マシンを作成したら、オプション バーの [編集] - [仮...

MySQL でストアド プロシージャを作成し、ループでレコードを追加する方法

この記事では、例を使用して、MySQL でストアド プロシージャを作成し、ループでレコードを追加する...

Linux sedコマンドの使用

1. 機能紹介sed (Stream EDitor) は、コンテンツを 1 行ずつ処理するストリーム...

CentOS 7.0 (mysql-5.7.21) で複数の MySQL インスタンスを起動する方法

設定手順Linux システム: CentOS-7.0 MySQL バージョン: 5.7.21 Lin...

MySql8.0バージョンに接続するMyBatisの設定問題について

mybatis を学習しているときにエラーが発生しました。エラーの内容は次のとおりです。データベース...

Docker+gitlab+jenkins は、ゼロから自動デプロイメントを構築します

目次序文: 1. Dockerをインストールする2. DockerでJenkinsをインストールする...

MySQLとElasticsearch間のデータ非対称性問題の解決策

MySQLとElasticsearch間のデータ非対称性問題の解決策jdbc-input-plugi...