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 のバックアップとリカバリの設計アイデア

背景まず、背景を説明します。ある制約により、当社の現在のバックアップ戦略では、1 日おきにフル バッ...

JavaScript での実行コンテキストと実行スタックの例の説明

JavaScript - 原則シリーズ日常の開発では、既存のプロジェクトを引き継ぐときは常に、まず他...

CentOS 7.4 64 ビット版に MySQL 8.0 をインストールして設定するための詳細な手順

ステップ1: MySQL YUMソースを取得するMySQLの公式サイトにアクセスして、RPMパッケー...

JavaScript のクロージャの詳細な説明

導入クロージャは JavaScript の非常に強力な機能です。いわゆるクロージャは関数内の関数です...

ウェブページ作成時に標準 HTML コードを使用する際のポイント

<br />多くのウェブサイト デザイナーが犯す最も一般的な間違いは、ウェブページが I...

CSS を使用してテクスチャ付きグラデーション背景画像を記述するためのサンプル コード

プロジェクト内のページの長さはおよそ2000px以上あり、背景画像にはテクスチャやグラデーションがあ...

MySQLストアドプロシージャにおけるカーソル(DECLARE)の原理と使い方の詳細な説明

この記事では、例を使用して、MySQL ストアド プロシージャにおけるカーソル (DECLARE) ...

MySql8.0.19 インストールピットレコードを共有する

前回の記事ではMySql8.0.19のインストール手順を紹介しました。必要な方はクリックしてご覧くだ...

HTML テーブル マークアップ チュートリアル (30): セルの暗い境界線の色属性 BORDERCOLORDARK

セルでは、暗い境界線の色を個別に定義できます。基本的な構文<TD ボーダーコロダーク=colo...

モバイルレイアウトにvw+remを使用する方法

まだ rem フレキシブルレイアウトを使用していますか?圧縮された js コードの大きなセクションを...

Centos7 での nginx のインストールと設定に関する詳細なチュートリアル

注: ソフトウェアのインストールの基本ディレクトリ パスは /usr/local です。ソフトウェア...

MySQL データベースの必須条件クエリ ステートメント

目次1. 基本的な文法2. 条件式によるフィルタリング3. 論理式によるフィルタリング4. あいまい...

docker compose を使ってワンクリックで分散構成センター Apollo を展開するプロセスの詳細な説明

導入分散について話すときは、分散構成センター、分散ログ、分散リンク トラッキングなどについて考える必...

Windows 10 で MySQL の解凍バージョンをインストールする方法の詳細なグラフィック チュートリアル

MySQL のインストールは、インストール バージョンと解凍バージョンに分かれています。インストール...

MySql 8.0.11 のインストール プロセスと Navicat とのリンク時に発生する問題の概要

私のシステムとソフトウェアのバージョンは次のとおりです。システム環境: win7、64ビットMySQ...