パーセル-プラグインの作成







前の記事で、構成を必要とせず、インストール後すぐに戦闘の準備ができる新しいParcel bundlerについて説明しました。 しかし、資産の標準セットが十分にない場合はどうでしょうか? 答えは簡単です-独自のプラグインを作成します。







箱から次の資産を利用できることを思い出させてください。









アセットを作成するには、2つの方法を選択できます。既存の方法( JSAssetHTMLAssetなど)を使用します。この方法では、 Assetクラスをベースとして、ロジックの一部を書き換えたり追加したり、ゼロから書き込みます。







例として、 Pugプラグインがどのように書かれたかを説明します







理論のビット



最初に、Parcelがプラグインでどのように機能し、何ができるのかを理解する必要がありますか?







バンドラーが初期化されると、パッケージはparcel-plugin-



始まるpackage.jsonで検索されます。 見つかった各バンドルパッケージは、エクスポートされた関数を接続して呼び出し、そのコンテキストを渡します。 この機能では、資産を登録できます。







私たちの資産は次のメソッドを実装する必要があります







 /** *     AST */ parse(code: string): Ast /** *         */ collectDependencies(): void /** *   AST   */ generate(): Object
      
      





また、オプションのメソッドを実装することもできます。







 /** *     AST */ pretransform(): void /** *      AST */ transform(): void
      
      





Pug ASTの使用方法



ASTを操作するためのいくつかの公式パッケージがあります。









プラグイン



次のプロジェクト構造を作成します。







 parcel-plugin-pug ├── package.json ├── src │  ├── PugAsset.ts │  ├── index.ts │  └── modules.d.ts ├── tsconfig.json └── tslint.json
      
      





index.ts



ファイルは、プラグインへのエントリポイントになります。







 export = (bundler: any) => { bundler.addAssetType('pug', require.resolve('./PugAsset')); bundler.addAssetType('jade', require.resolve('./PugAsset')); };
      
      





アセットを操作するには、基本クラスAsset



が必要です。 必要なモジュールのTypeScriptバインディングを作成しましょう。







modules.d.ts
 declare module 'parcel-bundler/src/Asset' { class Asset { constructor(name: string, pkg: string, options: any); parse(code: string): any; addDependency(path: string, options: Object): any; addURLDependency(url: string): string; name: string; isAstDirty: boolean; contents: string; ast: any; options: any; dependencies: Set<Object>; } export = Asset; } declare module 'parcel-bundler/src/utils/is-url' { function isURL(url: string): boolean; export = isURL; } declare module 'pug-load' { class load { static string(str: string, options?: any): any; } export = load; } declare module 'pug-lexer' { class Lexer {} export = Lexer; } declare module 'pug-parser' { class Parser {} export = Parser; } declare module 'pug-walk' { function walkAST(ast: any, before?: (node: any, replace?: any) => void, after?: (node: any, replace?: any) => void, options?: any): void; export = walkAST; } declare module 'pug-linker' { function link(ast: any): any; export = link; } declare module 'pug-code-gen' { function generateCode(ast: any, options: any): string; export = generateCode; } declare module 'pug-runtime/wrap' { function wrap(template: string, templateName?: string): Function; export = wrap; }
      
      





PugAsset.ts



ファイルは、テンプレートファイルをHTMLに変換するための資産です。







 import Asset = require('parcel-bundler/src/Asset'); export = class PugAsset extends Asset { public type = 'html'; constructor(name: string, pkg: string, options: any) { super(name, pkg, options); } public parse(code: string) { } public collectDependencies(): void { } public generate() { } };
      
      





テンプレートテキストをASTに変換することから始めましょう。 前にも言ったように、バンドラーはファイルに出くわすと、そのアセットを見つけようとします。 見つかった場合、 parse -> pretransform -> collectDependencies -> transform -> generate



呼び出しチェーンのparse -> pretransform -> collectDependencies -> transform -> generate



が発生します。 最初のステップは、 parse



メソッドを実装parse



ことです。







 public parse(code: string) { let ast = load.string(code, { //   lex: lexer, //   parse: parser, //   ,        filename: this.name }); //    AST (    include  extends) ast = linker(ast); return ast; }
      
      





次に、構築されたツリーを調べて、リンクを含む可能性のある要素を見つける必要があります。 操作のメカニズムは非常に単純で、標準のHTMLAsset



スパイされています。 一番下の行は、リンクを含む可能性のあるHTMLノードの属性を使用して辞書をコンパイルすることです。 ツリーを検索するとき、適切なノードを見つけて、属性のコンテンツにaddURLDependency



メソッドへのリンクをaddURLDependency



必要があります。このメソッドは、ファイル拡張子に応じて必要なアセットを見つけようとします。 アセットが見つかった場合、メソッドは新しいファイル名を返し、同時にこのファイルをアセンブリツリーに追加します(これにより、他のアセットのネストされた変換が発生します)。 古いパスの代わりにこの名前を使用する必要があります。 また、すべての接続ファイル( include



およびextends



)をこのアセットの依存関係として追加する必要があるという事実を考慮する必要があります。そうしないと、接続ファイルまたはベースファイルを変更するときに、テンプレート全体を再構築する必要がなくなります。







 interface Dictionary<T> { [key: string]: T; } const ATTRS: Dictionary<string[]> = { src: [ 'script', 'img', 'audio', 'video', 'source', 'track', 'iframe', 'embed' ], href: ['link', 'a'], poster: ['video'] };
      
      





 public collectDependencies(): void { walk(this.ast, node => { // ,       ,     if (node.filename !== this.name && !this.dependencies.has(node.filename)) { //     this.addDependency(node.filename, { name: node.filename, //    includedInParent: true //        }); } // ,      if (node.attrs) { //     for (const attr of node.attrs) { const elements = ATTRS[attr.name]; //    -       if (node.type === 'Tag' && elements && elements.indexOf(node.name) > -1) { // Pug  URL  ,    let assetPath = attr.val.substring(1, attr.val.length - 1); //       assetPath = this.addURLDependency(assetPath); //       -   if (!isURL(assetPath)) { // Use url.resolve to normalize path for windows // from \path\to\res.js to /path/to/res.js assetPath = url.resolve(path.join(this.options.publicURL, assetPath), ''); } //    attr.val = `'${assetPath}'`; } } } return node; }); }
      
      





最後の仕上げは、最終的なHTMLの取得です。 これはgenerate



メソッドの責任です:







 public generate() { const result = generateCode(this.ast, { //    compileDebug: false, //      pretty: !this.options.minify }); return { html: wrap(result)() }; }
      
      





すべてをまとめると、次のようになります。







PugAsset.ts
 import url = require('url'); import path = require('path'); import Asset = require('parcel-bundler/src/Asset'); import isURL = require('parcel-bundler/src/utils/is-url'); import load = require('pug-load'); import lexer = require('pug-lexer'); import parser = require('pug-parser'); import walk = require('pug-walk'); import linker = require('pug-linker'); import generateCode = require('pug-code-gen'); import wrap = require('pug-runtime/wrap'); interface Dictionary<T> { [key: string]: T; } // A list of all attributes that should produce a dependency // Based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes const ATTRS: Dictionary<string[]> = { src: [ 'script', 'img', 'audio', 'video', 'source', 'track', 'iframe', 'embed' ], href: ['link', 'a'], poster: ['video'] }; export = class PugAsset extends Asset { public type = 'html'; constructor(name: string, pkg: string, options: any) { super(name, pkg, options); } public parse(code: string) { let ast = load.string(code, { lex: lexer, parse: parser, filename: this.name }); ast = linker(ast); return ast; } public collectDependencies(): void { walk(this.ast, node => { if (node.filename !== this.name && !this.dependencies.has(node.filename)) { this.addDependency(node.filename, { name: node.filename, includedInParent: true }); } if (node.attrs) { for (const attr of node.attrs) { const elements = ATTRS[attr.name]; if (node.type === 'Tag' && elements && elements.indexOf(node.name) > -1) { let assetPath = attr.val.substring(1, attr.val.length - 1); assetPath = this.addURLDependency(assetPath); if (!isURL(assetPath)) { // Use url.resolve to normalize path for windows // from \path\to\res.js to /path/to/res.js assetPath = url.resolve(path.join(this.options.publicURL, assetPath), ''); } attr.val = `'${assetPath}'`; } } } return node; }); } public generate() { const result = generateCode(this.ast, { compileDebug: false, pretty: !this.options.minify }); return { html: wrap(result)() }; } };
      
      





プラグインの準備ができました。 入力としてテンプレートを受け入れ、テキストをASTに変換し、すべての内部依存関係を解決し、既成のHTMLを生成し、組み込みのinclude



およびextends



コンストラクトを正しく認識しinclude



コンストラクトデータを含むテンプレート全体を再構築する方法を知っています。







マイナーな欠陥から-エラーが発生すると、テキストが複製されます。これはParcel出力の機能であり、 try catch



関数呼び出しをラップし、発信エラーを美しく出力します。







参照資料






All Articles