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 文件,该文件使用 <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 头部。

选项

您可以将一些配置选项传递给 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;
};
  • 默认值: {}
名称类型默认值描述
titlestring|undefinedundefined用于生成的 HTML 文档的标题。
filenamestring|undefined|((entry: string) => string)'index.html'要写入 HTML 的文件。默认为 index.html。您也可以在这里指定一个子目录(例如:pages/index.html)。
templatestring|undefinedundefined模板文件路径
templateContentstring|undefined|((params: Record<string, any>) => string | Promise<string>)undefined模板文件内容,优先级高于 template。使用函数时,传入模板参数,使用返回的字符串作为模板内容。
templateParametersRecord<string, string>|(oldParams: params: Record<string, any>) => Record<string, any> | Promise<Record<string, any>>{}允许覆盖模板中使用的参数。使用函数时,传入原始模板参数,使用返回的对象作为最终的模板参数。
inject'head' | 'body' | boolean | undefinedundefinedtemplate 中的脚本和链接标签注入位置。使用 false 不注入。如果没有指定,它将根据 scriptLoading 自动确定。
publicPathstring''用于脚本和链接标签的 publicPath。
scriptLoading'blocking'|'defer'|'module'|'systemjs-module'|undefined'defer'现代浏览器支持非阻塞 JavaScript 加载 ('defer') 以提高页面启动性能。设置为 'module' 将添加属性 type='module'。这也意味着 'defer',因为模块会自动延迟。
chunksstring[]|undefinedundefined允许您仅添加一些块。
excludeChunksstring[]|undefinedundefined允许您跳过一些块。
sri'sha256'|'sha384'|'sha512'|undefinedundefinedsri 哈希算法,默认情况下禁用。
minifybooleanfalse控制是否缩小输出。
faviconstring|undefinedundefined将给定的 favicon 路径添加到输出 HTML。
metaRecord<string, string|Record<string, string>>{}允许注入元标签。
hashbooleanfalse如果为 true,则将唯一的 rspack 编译哈希追加到所有包含的脚本和 CSS 文件中。这对于清除缓存很有用
basestring|object|undefinedundefined注入一个 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 有些区别

  • 不支持使用 ! 添加加载器来处理模板文件
  • rspackConfig 对象目前仅支持 modeoutput.publicPathoutput.crossOriginLoading
  • compilation 对象目前仅在 使用模板函数 时支持
  • 在模板中渲染标签列表(例如 htmlRspackPlugin.tags.headTags)或单个标签(例如 htmlRspackPlugin.tags.headTags[0])时,需要使用 toHtml() 函数生成 HTML 代码

过滤 Chunk

可以通过以下配置指定需要注入的 Chunk

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

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

也可以通过以下配置排除特定 Chunk

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

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

元标签

如果设置了 meta,HtmlRspackPlugin 将注入 <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,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'],
    }),
  ],
};

钩子

HtmlRspackPlugin 提供了一些钩子,允许您修改标签或生成的 HTML 代码。可以通过 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

此钩子将在从编译中收集资产并生成加载路径后调用,但在生成标签之前调用。

可以在这里修改 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 并生成一个 <script defer src="extra-script.js"></script> 标签,在最终的 html 内容中。

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

此钩子将在根据资产文件生成资产标签后调用,但在确定标签的插入位置之前调用。

可以在这里调整标签。

  • 类型: 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

此钩子将在生成 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

此钩子将在模板渲染完成后调用,但在标签被注入之前调用。

可以在这里修改 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

此钩子将在生成 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

此钩子将在生成 HTML 资产文件后调用,仅用于通知。

  • 类型: SyncHook<[AfterEmitData]>
  • 参数:
    type AfterEmitData = {
      outputName: string;
      plugin: {
        options: HtmlRspackPluginOptions;
      };
    };