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 での SQL モードの表示と設定の詳細な説明

MySQL での SQL モードの表示と設定MySQL はさまざまなモードで実行でき、さまざまなシナ...

MySQL 制約の超詳細な説明

目次MySQL 制約操作1. 非ヌル制約2. ユニーク制約3. 主キー制約4. 外部キー制約5. カ...

Promise カプセル化 wx.request メソッド

前回の記事では、Promise を使用して小さなプログラム wx.request をカプセル化する実...

CSS 表示テーブルの適応的な高さと幅の問題の解決策

定義と使用法display プロパティは、要素が生成するボックスのタイプを指定します。例示するこの属...

Vue がルート変更を監視するときに watch メソッドが複数回実行される理由と解決策

目次要件の説明:要件分析:ニーズの解決問題解決私はフロントエンドの新人ですが、バックエンドのバグの中...

CSS で隠し要素を実現する 7 つの興味深い方法

序文非表示要素の 3 つの属性である表示、可視性、不透明度の類似点と相違点は、フロントエンドの就職面...

MySQL-8.0.26 構成グラフィックチュートリアル

はじめに: 最近、会社のプロジェクトでデータベースのバージョンが変更されました。ここでは、MySQL...

Windows システムに MySQL を素早くインストールして展開する方法 (グリーンの無料インストール バージョン)

まずは緑色の無料インストール版のMySQLをダウンロードします。任意のフォルダに入れて構いません。今...

ネイティブ Js で実装されたシンプルなシームレス スクロール カルーセルのサンプル コード

シンプルなシームレススクロールカルーセルには多くの抜け穴があり、後から画像を追加するのは非常に不便で...

Vue+js はビデオのフェードインとフェードアウト効果を実現します

Vue+jsはビデオのフェードインとフェードアウトを実現します。参考までに、具体的な内容は次のとおり...

Mysql でサーバーの UUID を変更する方法

問題の原因:スレーブサーバーがクローンマスターサーバーである場合、server-uuidの値は同じで...

Ubuntu16.04はphp5.6ウェブサーバー環境を構築します

Ubuntu 16.04 はデフォルトで PHP7.0 環境をインストールしますが、PHP7 は一部...

Nginx ストリーム構成プロキシ (Nginx TCP/UDP ロード バランシング)

序章nginx が優れたリバース プロキシ サービスであることは誰もが知っています。nginx を使...

JS オブジェクト コンストラクター Object.freeze

目次概要例1) オブジェクトをフリーズする2) 配列をフリーズする3) 浅い凍結4) ディープフリー...

今日、今週、今月、先月のMySQLクエリデータ

今日 テーブル名から * を選択します。ここで、to_days(時間フィールド名) = to_day...