HtmlRspackPlugin

Rspack 獨有

rspack.HtmlRspackPlugin 是一個以 Rust 實作的高效能 HTML 外掛。您可以使用它來為 Rspack 專案產生 HTML 檔案。

new rspack.HtmlRspackPlugin(options);

比較

在使用 rspack.HtmlRspackPlugin 之前,請注意 rspack.HtmlRspackPlugin 與社群 html-webpack-plugin 之間存在一些差異。

效能

由於 rspack.HtmlRspackPlugin 是以 Rust 實作,因此其建置效能顯著優於 html-webpack-plugin,尤其是在建置許多 HTML 檔案的情況下。

功能

rspack.HtmlRspackPlugin 的功能是 html-webpack-plugin 的子集。為了確保外掛的效能,我們並未實作 html-webpack-plugin 提供的所有功能。

如果其選項不符合您的需求,您也可以直接使用社群 html-webpack-plugin

警告

rspack.HtmlRspackPlugin 不支援完整的 ejs 語法;它僅支援 ejs 語法的一個子集。如果您需要完整的 ejs 語法支援,您可以直接使用 html-webpack-plugin。為了對齊 html-webpack-plugin 的預設範本語法,Rspack 將預設的 EJS 跳脫和不跳脫改為與 html-webpack-plugin 的預設語法相同。

支援的 EJS 語法

僅支援以下基本插值表達式和一些控制語句。在這裡,插值表達式僅支援最基本的字串類型,而不支援任意 JavaScript 表達式。目前不支援其他 EJS 語法。

<%-: 跳脫輸出

跳脫插值中的內容

ejs
<p>Hello, <%- name %>.</p>
<p>Hello, <%- 'the Most Honorable ' + name %>.</p>
locals
{
  "name": "Rspack<y>"
}
html
<p>Hello, Rspack&lt;y&gt;.</p>
<p>Hello, the Most Honorable Rspack&lt;y&gt;.</p>

<%=: 不跳脫輸出

不跳脫插值中的內容

ejs
<p>Hello, <%- myHtml %>.</p>
<p>Hello, <%= myHtml %>.</p>

<p>Hello, <%- myMaliciousHtml %>.</p>
<p>Hello, <%= myMaliciousHtml %>.</p>
locals
{
  "myHtml": "<strong>Rspack</strong>",
  "myMaliciousHtml": "</p><script>document.write()</script><p>"
}
html
<p>Hello, &lt;strong&gt;Rspack&lt;/strong&gt;.</p>
<p>Hello, <strong>Rspack</strong>.</p>

<p>Hello, &lt;/p&gt;&lt;script&gt;document.write()&lt;/script&gt;&lt;p&gt;.</p>
<p>Hello,</p>
<script>
  document.write();
</script>
<p>.</p>

控制語句

使用 for in 語句實作列表遍歷,並使用 if 語句實作條件判斷

ejs
<% for tag in htmlRspackPlugin.tags.headTags { %>
  <% if tag.tagName=="script" { %>
    <%= toHtml(tag) %>
  <% } %>
<% } %>

用法

此外掛會為您產生一個 HTML 檔案,其中包含所有您在 head 中使用 <script> 標籤的 JS 輸出。

只需將此外掛新增至您的 Rspack 設定,如下所示

rspack.config.js
const rspack = require('@rspack/core');

module.exports = {
  plugins: [new rspack.HtmlRspackPlugin()],
};

這會產生一個包含以下內容的檔案 dist/index.html

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>rspack</title>
    <script src="main.js" defer></script>
  </head>
  <body></body>
</html>

如果您在 Rspack 設定中有多個進入點,它們都會以 <script> 標籤包含在產生的 HTML 中。

如果您的建置輸出中有一些 CSS 資產,它們將會以 <link> 標籤包含在 HTML head 中。

選項

您可以將一些設定選項傳遞給 rspack.HtmlRspackPlugin。允許的選項如下

  • 類型
type HtmlRspackPluginOptions = {
  title?: string;
  filename?: string | ((entry: string) => string);
  template?: string;
  templateContent?: string | ((params: Record<string, any>) => string | Promise<string>);
  templateParameters?: Record<string, string> | (oldParams: params: Record<string, any>) => Record<string, any> | Promise<Record<string, any>>;
  inject?: 'head' | 'body' | boolean;
  publicPath?: string;
  base?: string | {
    href?: string;
    target?: '_self' | '_blank' | '_parent' | '_top'
  };
  scriptLoading?: 'blocking' | 'defer' | 'module' | 'systemjs-module';
  chunks?: string[];
  excludeChunks?: string[];
  sri?: 'sha256' | 'sha384' | 'sha512';
  minify?: boolean;
  favicon?: string;
  meta?: Record<string, string | Record<string, string>>;
  hash?: boolean;
};
  • 預設值: {}
名稱類型預設值描述
title字串|未定義未定義用於產生的 HTML 文件的標題。
filename字串|未定義|((entry: 字串) => 字串)'index.html'寫入 HTML 的檔案。預設值為 index.html。您也可以在這裡指定子目錄 (例如:pages/index.html)。
template字串|未定義未定義範本檔案路徑
templateContent字串|未定義|((params: Record<字串, any>) => 字串 | Promise<字串>)未定義範本檔案內容,優先順序高於 template。當使用函式時,傳入範本參數並使用傳回的字串作為範本內容。
templateParametersRecord<字串, 字串>|(oldParams: params: Record<字串, any>) => Record<字串, any> | Promise<Record<字串, any>>{}允許覆寫範本中使用的參數。當使用函式時,傳入原始範本參數並使用傳回的物件作為最終範本參數。
inject'head' | 'body' | 布林值 | 未定義未定義template 中 script 和 link 標籤的注入位置。使用 false 不注入。如果未指定,將根據 scriptLoading 自動決定。
publicPath字串''用於 script 和 link 標籤的 publicPath。
scriptLoading'blocking'|'defer'|'module'|'systemjs-module'|未定義'defer'現代瀏覽器支援非阻塞 JavaScript 載入 ('defer'),以提升頁面啟動效能。設定為 'module' 會新增屬性 type='module'。這也表示 'defer',因為模組會自動延遲。
chunks字串[]|未定義未定義允許您僅新增一些 chunks。
excludeChunks字串[]|未定義未定義允許您跳過一些 chunks。
sri'sha256'|'sha384'|'sha512'|未定義未定義sri 雜湊演算法,預設為停用。
minify布林值false控制是否要縮小輸出。
favicon字串|未定義未定義將給定的 favicon 路徑新增至輸出 HTML。
metaRecord<字串, 字串|Record<字串, 字串>>{}允許注入 meta 標籤。
hash布林值false如果為 true,則將唯一的 rspack 編譯雜湊附加至所有包含的 script 和 CSS 檔案。這對於快取清除很有用
base字串|物件|未定義未定義注入 base 標籤

範例

自訂 HTML 範本

如果預設產生的 HTML 不符合您的需求,您可以使用自己的範本。

使用範本檔案

最簡單的方法是使用 template 選項並傳遞自訂 HTML 檔案。rspack.HtmlRspackPlugin 會自動將所有必要的 JS、CSS 和 favicon 檔案注入 HTML。

透過 template 指定 HTML 範本檔案

index.html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title><%= htmlRspackPlugin.options.title %></title>
  </head>
  <body></body>
</html>
rspack.config.js
const rspack = require('@rspack/core');

module.exports = {
  plugins: [
    new rspack.HtmlRspackPlugin({
      template: 'index.html',
    }),
  ],
};

使用範本字串

透過 templateContent 指定 HTML 範本內容

rspack.config.js
const rspack = require('@rspack/core');

module.exports = {
  plugins: [
    new rspack.HtmlRspackPlugin({
      title: "My HTML Template"
      templateContent: `
        <!DOCTYPE html>
        <html>
          <head>
            <title><%= htmlRspackPlugin.options.title %></title>
          </head>
          <body></body>
        </html>
      `,
    }),
  ],
};

使用範本函式

使用函式產生 HTML 範本內容

  • templateContent 中傳遞函式
rspack.config.js
const rspack = require('@rspack/core');

module.exports = {
  plugins: [
    new rspack.HtmlRspackPlugin({
      title: "My HTML Template"
      templateContent: ({ htmlRspackPlugin }) => `
        <!DOCTYPE html>
        <html>
          <head>
            <title>${htmlRspackPlugin.options.title}</title>
          </head>
          <body></body>
        </html>
      `,
    }),
  ],
};
  • 或者在 template 中傳遞以 .js.cjs 結尾的檔案路徑
template.js
module.exports = ({ htmlRspackPlugin }) => `
  <!DOCTYPE html>
  <html>
    <head>
      <title>${htmlRspackPlugin.options.title}</title>
    </head>
    <body></body>
  </html>
`;
rspack.config.js
const rspack = require('@rspack/core');

module.exports = {
  plugins: [
    new rspack.HtmlRspackPlugin({
      title: "My HTML Template"
      template: "template.js",
    }),
  ],
};

範本參數

HTML 範本的渲染參數可以透過 templateParameters 擴充。預設情況下可以使用以下變數

  • htmlRspackPlugin: 外掛的資料
    • htmlRspackPlugin.options: 外掛的設定物件
    • htmlRspackPlugin.tags: 範本中準備注入的標籤資訊
      • htmlRspackPlugin.tags.headTags: 要注入到 <head> 中的 <base><meta><link><script> 標籤清單
      • htmlRspackPlugin.tags.bodyTags: 要注入到 <body> 中的 <script> 標籤清單
    • htmlRspackPlugin.files: 此編譯中產生的資產檔案
      • htmlRspackPlugin.files.js: 此編譯中產生的 JS 資產路徑清單
      • htmlRspackPlugin.files.css: 此編譯中產生的 CSS 資產路徑清單
      • htmlRspackPlugin.files.favicon:如果設定了 favicon,這裡會是計算出的最終 favicon 資源路徑
      • htmlRspackPlugin.files.publicPath:資源檔案的 publicPath
  • rspackConfig:此次編譯中使用的 Rspack 設定物件
  • compilation:此次編譯的 Compilation 物件
警告

如果使用 htmlRspackPlugin.tags 在樣板渲染期間插入標籤,請將 inject 設定為 false,否則標籤將會被插入兩次。

差異

與 HtmlWebpackPlugin 存在一些差異

  • 不支援使用 ! 來為樣板檔案新增 loader 進行處理
  • rspackConfig 物件目前僅支援 modeoutput.publicPathoutput.crossOriginLoading
  • compilation 物件目前僅在使用樣板函式時支援
  • 當在樣板中渲染標籤列表(例如 htmlRspackPlugin.tags.headTags)或單個標籤(例如 htmlRspackPlugin.tags.headTags[0])時,需要使用 toHtml() 函式來生成 HTML 程式碼

過濾區塊

可以透過以下設定指定需要注入的區塊

rspack.config.js
const rspack = require('@rspack/core');

module.exports = {
  plugins: [
    new HtmlRspackPlugin({
      chunks: ['app'],
    }),
  ],
};

也可以透過以下設定排除特定的區塊

rspack.config.js
const rspack = require('@rspack/core');

module.exports = {
  plugins: [
    new HtmlRspackPlugin({
      excludeChunks: ['app'],
    }),
  ],
};

Meta 標籤

如果設定了 meta,HtmlRspackPlugin 將會注入 <meta> 標籤。

請查看這個維護良好的清單,其中列出了幾乎所有可用的 meta 標籤

透過以下設定新增鍵值對來生成 <meta> 標籤

rspack.config.js
const rspack = require('@rspack/core');

module.exports = {
  plugins: [
    new HtmlRspackPlugin({
      meta: {
        // Will generate: <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no',
        // Will generate: <meta name="theme-color" content="#4285f4">
        'theme-color': '#4285f4',
        // Will generate:  <meta http-equiv="Content-Security-Policy" content="default-src https:">
        'Content-Security-Policy': {
          'http-equiv': 'Content-Security-Policy',
          content: 'default-src https:',
        },
      },
    }),
  ],
};

Base 標籤

如果設定了 base,HtmlRspackPlugin 將會注入 <base> 標籤。

有關 <base> 標籤的更多資訊,請查看文件

可以透過以下設定生成 <base> 標籤

rspack.config.js
new HtmlWebpackPlugin({
  // Will generate: <base href="http://example.com/some/page.html">
  base: 'http://example.com/some/page.html',
});

new HtmlWebpackPlugin({
  // Will generate: <base href="http://example.com/some/page.html" target="_blank">
  base: {
    href: 'http://example.com/some/page.html',
    target: '_blank',
  },
});

生成多個 HTML 檔案

如果您有多個入口點,並且希望為每個入口點生成一個 HTML 檔案,您可以註冊多個 rspack.HtmlRspackPlugin

  • 使用 filename 來指定每個 HTML 檔案的名稱。
  • 使用 chunks 來指定每個 HTML 檔案中包含的 JS 套件。

例如,以下設定將生成 foo.html 和 bar.html,其中 foo.html 僅包含由 foo.js 生成的 JS 套件。

rspack.config.js
const rspack = require('@rspack/core');

module.exports = {
  entry: {
    foo: './foo.js',
    bar: './bar.js',
  },
  plugins: [
    new rspack.HtmlRspackPlugin({
      filename: 'foo.html',
      chunks: ['foo'],
    }),
    new rspack.HtmlRspackPlugin({
      filename: 'bar.html',
      chunks: ['bar'],
    }),
  ],
};

Hooks

HtmlRspackPlugin 提供了一些 Hook,允許您修改標籤或生成的 HTML 程式碼。Hook 物件可以透過 HtmlRspackPlugin.getCompilationHooks 取得

rspack.config.js
const HtmlModifyPlugin = {
  apply: function (compiler) {
    compiler.hooks.compilation.tap('HtmlModifyPlugin', compilation => {
      const hooks = HtmlRspackPlugin.getCompilationHooks(compilation);
      // hooks.beforeAssetTagGeneration.tapPromise()
      // hooks.alterAssetTags.tapPromise()
      // hooks.alterAssetTagGroups.tapPromise()
      // hooks.afterTemplateExecution.tapPromise()
      // hooks.beforeEmit.tapPromise()
      // hooks.afterEmit.tapPromise()
    });
  },
};

module.exports = {
  //...
  plugins: [new HtmlRspackPlugin(), HtmlModifyPlugin],
};

beforeAssetTagGeneration

此 Hook 會在從編譯中收集資源並生成載入路徑後,但在生成標籤之前被呼叫。

此處可以修改 assets 以新增自訂的 JS 和 CSS 資源檔案。

  • 類型AsyncSeriesWaterfallHook<[BeforeAssetTagGenerationData]>
  • 參數:
    type BeforeAssetTagGenerationData = {
      assets: {
        publicPath: string;
        js: Array<string>;
        css: Array<string>;
        favicon?: string;
      };
      outputName: string;
      plugin: {
        options: HtmlRspackPluginOptions;
      };
    };
警告

只有 assets.jsassets.cssassets.favicon 可以修改。修改其他項目將不會生效。

以下程式碼會新增一個額外的 extra-script.js,並在最終的 HTML 內容中生成一個 <script defer src="extra-script.js"></script> 標籤。

rspack.config.js
const AddScriptPlugin = {
  apply: function (compiler) {
    compiler.hooks.compilation.tap('AddScriptPlugin', compilation => {
      HtmlRspackPlugin.getCompilationHooks(
        compilation,
      ).beforeAssetTagGeneration.tapPromise('AddScriptPlugin', async data => {
        data.assets.js.push('extra-script.js');
      });
    });
  },
};

module.exports = {
  //...
  plugins: [new HtmlRspackPlugin(), AddScriptPlugin],
};

alterAssetTags

此 Hook 會在根據資源檔案生成資源標籤之後,但在決定標籤的插入位置之前被呼叫。

可以在此處調整標籤。

  • 類型AsyncSeriesWaterfallHook<[AlterAssetTagsData]>

  • 參數:

    type HtmlTag = {
      tagName: string;
      attributes: Record<string, string | boolean | undefined | null>;
      voidTag: boolean;
      innerHTML?: string;
      asset?: string;
    };
    
    type AlterAssetTagsData = {
      assetTags: {
        scripts: Array<HtmlTag>;
        styles: Array<HtmlTag>;
        meta: Array<HtmlTag>;
      };
      outputName: string;
      plugin: {
        options: HtmlRspackPluginOptions;
      };
    };
警告

只有 assetTags 可以修改。修改其他項目將不會生效。

  • 當將屬性值設定為 true 時,將會新增一個無值的屬性,並且會生成 <script defer specialattribute src="main.js"></script>
  • 當將屬性值設定為 string 時,將會新增一個有值的屬性,並且會生成 <script defer specialattribute="some value" src="main.js"></script>
  • 當將屬性值設定為 false 時,將會移除該屬性。

以下程式碼會將 specialAttribute 屬性新增至所有 script 類型的標籤

rspack.config.js
const AddAttributePlugin = {
  apply: function (compiler) {
    compiler.hooks.compilation.tap('AddAttributePlugin', compilation => {
      HtmlRspackPlugin.getCompilationHooks(
        compilation,
      ).alterAssetTags.tapPromise('AddAttributePlugin', async data => {
        data.assetTags.scripts = data.assetTags.scripts.map(tag => {
          if (tag.tagName === 'script') {
            tag.attributes.specialAttribute = true;
          }
          return tag;
        });
      });
    });
  },
};

module.exports = {
  //...
  plugins: [new HtmlRspackPlugin(), AddAttributePlugin],
};

alterAssetTagGroups

此 Hook 會在生成 headbody 的標籤群組後,但在函式或樣板引擎渲染樣板之前被呼叫。

可以在此處調整標籤的插入位置。

  • 類型AsyncSeriesWaterfallHook<[AlterAssetTagGroupsData]>
  • 參數:
    type AlterAssetTagGroupsData = {
      headTags: Array<HtmlTag>;
      bodyTags: Array<HtmlTag>;
      outputName: string;
      plugin: {
        options: HtmlRspackPluginOptions;
      };
    };
警告

只有 headTagsbodyTags 可以修改。修改其他項目將不會生效。

以下程式碼會將 async script 標籤從 body 移至 head

rspack.config.js
const MoveTagsPlugin = {
  apply: function (compiler) {
    compiler.hooks.compilation.tap('MoveTagsPlugin', compilation => {
      HtmlWebpackPlugin.getCompilationHooks(
        compilation,
      ).alterAssetTagGroups.tapPromise('MoveTagsPlugin', async data => {
        data.headTags.push(data.headTags.bodyTags.filter(i => i.async));
        data.bodyTags = data.bodyTags.filter(i => !i.async);
      });
    });
  },
};

module.exports = {
  //...
  plugins: [
    new HtmlRspackPlugin({
      inject: 'body',
    }),
    AllHeadTagsPlugin,
  ],
};

afterTemplateExecution

此 Hook 會在樣板渲染完成後,但在標籤被注入之前被呼叫。

可以在此處修改 HTML 內容和要注入的標籤。

  • 當使用函式 templateContent 或以 .js/.cjs 結尾的 template,並使用此函式來渲染樣板時,這裡的 html 是由該函式回傳的結果。

  • 在其他情況下,HTML 樣板將會透過內部樣板引擎編譯,而此處的 html 是編譯的結果。

  • 類型AsyncSeriesWaterfallHook<[AfterTemplateExecutionData]>

  • 參數:

    type AfterTemplateExecutionData = {
      html: string;
      headTags: Array<HtmlTag>;
      bodyTags: Array<HtmlTag>;
      outputName: string;
      plugin: {
        options: HtmlRspackPluginOptions;
      };
    };

    :::warning 警告 只有 htmlheadTagsbodyTags 可以修改。修改其他項目將不會生效。 :::

以下程式碼會在 body 的結尾新增 Injected by plugin。然後標籤將會在此文字之後注入。因此,在最終的 HTML 內容中將會是 <Injected by plugin<script defer src="main.js"></script></body>

rspack.config.js
const InjectContentPlugin = {
  apply: function (compiler) {
    compiler.hooks.compilation.tap('InjectContentPlugin', compilation => {
      HtmlWebpackPlugin.getCompilationHooks(
        compilation,
      ).afterTemplateExecution.tapPromise('InjectContentPlugin', async data => {
        data.html = data.html.replace('</body>', 'Injected by plugin</body>');
      });
    });
  },
};

module.exports = {
  //...
  plugins: [
    new HtmlRspackPlugin({
      inject: 'body',
    }),
    InjectContentPlugin,
  ],
};

beforeEmit

此 Hook 會在生成 HTML 資源檔案之前被呼叫,這是修改 HTML 內容的最後機會。

  • 類型SyncHook<[BeforeEmitData]>
  • 參數:
    type BeforeEmitData = {
      html: string;
      outputName: string;
      plugin: {
        options: HtmlRspackPluginOptions;
      };
    };
警告

只有 html 可以修改。修改其他項目將不會生效。

以下程式碼會在 body 的結尾新增 Injected by plugin。在最終的 HTML 內容中將會是 <script defer src="main.js"></script>Injected by plugin</body>

rspack.config.js
const InjectContentPlugin = {
  apply: function (compiler) {
    compiler.hooks.compilation.tap('InjectContentPlugin', compilation => {
      HtmlWebpackPlugin.getCompilationHooks(compilation).beforeEmit.tapPromise(
        'InjectContentPlugin',
        async data => {
          data.html = data.html.replace('</body>', 'Injected by plugin</body>');
        },
      );
    });
  },
};

module.exports = {
  //...
  plugins: [
    new HtmlRspackPlugin({
      inject: 'body',
    }),
    InjectContentPlugin,
  ],
};

afterEmit

此 Hook 會在生成 HTML 資源檔案後被呼叫,並且僅用於通知。

  • 類型SyncHook<[AfterEmitData]>
  • 參數:
    type AfterEmitData = {
      outputName: string;
      plugin: {
        options: HtmlRspackPluginOptions;
      };
    };