Html

本章节描述了 Rsbuild 中与 HTML 有关的配置。

html.appIcon

  • 类型: string
  • 默认值: undefined

设置 iOS 系统下的 apple-touch-icon 图标的文件路径,可以设置为相对于项目根目录的相对路径,也可以设置为文件的绝对路径。暂不支持设置为 CDN URL。

配置该选项后,在编译过程中会自动将图标拷贝至 dist 目录下,并在 HTML 中添加相应的 link 标签。

示例

设置为相对路径:

export default {
  html: {
    appIcon: './src/assets/icon.png',
  },
};

设置为绝对路径:

import path from 'path';

export default {
  html: {
    appIcon: path.resolve(__dirname, './src/assets/icon.png'),
  },
};

重新编译后,HTML 中自动生成了以下标签:

<link rel="apple-touch-icon" sizes="180*180" href="/static/image/icon.png" />

html.crossorigin

  • 类型: boolean | 'anonymous' | 'use-credentials'
  • 默认值: false

用于设置 <script><style> 标签的 crossorigin 属性。

  • 当传入 true 时,它会被自动设置为 crossorigin="anonymous"
  • 当传入 false 时,它不会设置 crossorigin 属性。

示例

export default {
  html: {
    crossorigin: 'anonymous',
  },
};

编译后,HTML 中的 <script> 标签变为:

<script defer src="/static/js/main.js" crossorigin="anonymous"></script>

<style> 标签变为:

<link href="/static/css/main.css" rel="stylesheet" crossorigin="anonymous" />

可选值

crossorigin 可以被设置为以下几个值:

  • anonymous:请求使用 CORS 头,并将证书标志设置为 "same-origin"。除非目标是相同的 origin,否则不会通过 cookie、客户端 SSL 证书或 HTTP 身份验证交换用户凭据。
  • use-credentials:请求使用 CORS 头,证书标志设置为 "include",并始终包含用户凭据。

html.outputStructure

  • Type: 'flat' | 'nested'
  • Default: 'flat'

用于定义 HTML 产物文件的目录结构。

示例

默认情况下,HTML 产物在 dist 目录下的结构为 flat

/dist
 └── [name].html

你可以将 html.outputStructure 配置为 nested:

export default {
  html: {
    outputStructure: 'nested',
  },
};

重新构建后,HTML 产物的目录结构如下:

/dist
 └── [name]
     └── index.html

如果你需要设置 HTML 文件在 dist 目录中的父级路径,请使用 output.distPath.html 配置。

html.favicon

  • 类型: string | Function
  • 默认值: undefined

设置页面的 favicon 图标,可以设置为:

  • URL 地址。
  • 文件的绝对路径。
  • 相对于项目根目录的相对路径。

配置该选项后,在编译过程中会自动将图标拷贝至 dist 目录下,并在 HTML 中添加相应的 link 标签。

示例

设置为相对路径:

export default {
  html: {
    favicon: './src/assets/icon.png',
  },
};

设置为绝对路径:

import path from 'path';

export default {
  html: {
    favicon: path.resolve(__dirname, './src/assets/icon.png'),
  },
};

设置为 URL:

import path from 'path';

export default {
  html: {
    favicon: 'https://foo.com/favicon.ico',
  },
};

重新编译后,HTML 中自动生成了以下标签:

<link rel="icon" href="/favicon.ico" />

函数用法

  • 类型:
type FaviconFunction = ({ value: string; entryName: string }) => string | void;

html.favicon 为 Function 类型时,函数接收一个对象作为入参,对象的值包括:

  • value:Rsbuild 的默认 favicon 配置。
  • entryName: 当前入口的名称。

在 MPA(多页面应用)场景下,你可以基于入口名称返回不同的 favicon,从而为每个页面生成不同的标签:

export default {
  html: {
    favicon({ entryName }) {
      const icons = {
        foo: 'https://example.com/foo.ico',
        bar: 'https://example.com/bar.ico',
      };
      return icons[entryName] || 'https://example.com/default.ico';
    },
  },
};

html.inject

  • 类型: 'head' | 'body' | boolean | Function
  • 默认值: 'head'

修改构建产物中 <script> 标签在 HTML 中的插入位置。

可以设置为以下值:

  • 'head': script 标签会插入在 HTML 的 head 标签内。
  • 'body': script 标签会插入在 HTML 的 body 标签尾部。
  • true: 最终表现取决于 html-webpack-plugin 的 scriptLoading 配置项。
  • false: script 标签不插入 HTML 中。

默认插入位置

script 标签默认在 head 标签内:

<html>
  <head>
    <title></title>
    <script defer src="/static/js/runtime-main.js"></script>
    <script defer src="/static/js/main.js"></script>
    <link href="/static/css/main.css" rel="stylesheet" />
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

插入至 body 标签

添加如下配置,可以将 script 插入至 body 标签:

export default {
  html: {
    inject: 'body',
  },
};

可以看到 script 标签生成在 body 标签尾部:

<html>
  <head>
    <title></title>
    <link href="/static/css/main.css" rel="stylesheet" />
  </head>
  <body>
    <div id="root"></div>
    <script defer src="/static/js/runtime-main.js"></script>
    <script defer src="/static/js/main.js"></script>
  </body>
</html>

函数用法

  • 类型:
type InjectFunction = ({ value: ScriptInject; entryName: string }) => string | void;

html.inject 为 Function 类型时,函数接收一个对象作为入参,对象的值包括:

  • value:Rsbuild 的默认 inject 配置。
  • entryName: 当前入口的名称。

在 MPA(多页面应用)场景下,你可以基于入口名称设置不同的 inject 方式:

export default {
  html: {
    inject({ entryName }) {
      return entryName === 'foo' ? 'body' : 'head';
    },
  },
};

html.meta

  • 类型: Object | Function
  • 默认值:
const defaultMeta = {
  // <meta charset="UTF-8" />
  charset: {
    charset: 'UTF-8',
  },
  // <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  viewport: 'width=device-width, initial-scale=1.0',
};

配置 HTML 页面的 <meta> 标签。

String 类型

  • 类型:
type MetaOptions = {
  [name: string]: string;
};

meta 对象的 value 为字符串时,会自动将对象的 key 映射为 namevalue 映射为 content

比如设置 description

export default {
  html: {
    meta: {
      description: 'a description of the page',
    },
  },
};

最终在 HTML 中生成的 meta 标签为:

<meta name="description" content="a description of the page" />

Object 类型

  • 类型:
type MetaOptions = {
  [name: string]:
    | string
    | false
    | {
        [attr: string]: string | boolean;
      };
};

meta 对象的 value 为对象时,会将该对象的 key: value 映射为 meta 标签的属性。

比如设置 charset

export default {
  html: {
    meta: {
      charset: {
        charset: 'UTF-8',
      },
    },
  },
};

最终在 HTML 中生成的 meta 标签为:

<meta charset="UTF-8" />

函数用法

  • 类型:
type MetaFunction = ({
  value: MetaOptions,
  entryName: string,
}) => MetaOptions | void;

html.meta 为 Function 类型时,函数接收一个对象作为入参,对象的值包括:

  • value:Rsbuild 的默认 meta 配置。
  • entryName: 当前入口的名称。

你可以直接修改配置对象不做返回,也可以返回一个对象作为最终的配置。

比如,你可以直接修改内置的 meta 配置对象:

export default {
  html: {
    meta({ value }) {
      value.description = 'this is my page';
      return value;
    },
  },
};

在 MPA(多页面应用)场景下,你可以基于入口名称返回不同的 meta 配置,从而为每个页面生成不同的 meta 标签:

export default {
  html: {
    meta({ entryName }) {
      switch (entryName) {
        case 'foo':
          return {
            description: 'this is foo page',
          };
        case 'bar':
          return {
            description: 'this is bar page',
          };
        default:
          return {
            description: 'this is other pages',
          };
      }
    },
  },
};

移除默认值

meta 对象的 value 设置为 false,则表示不生成对应的 meta 标签。

比如移除 Rsbuild 预设的 viewport

export default {
  html: {
    meta: {
      viewport: false,
    },
  },
};

html.mountId

  • 类型: string
  • 默认值: 'root'

默认情况下,HTML 模板中包含了 root 节点用于组件挂载,通过 mountId 可以修改该节点的 id。

<body>
  <div id="root"></div>
</body>

示例

修改 DOM 挂载节点 idapp

export default {
  html: {
    mountId: 'app',
  },
};

编译后:

<body>
  <div id="app"></div>
</body>

注意事项

更新相关代码

在修改 mountId 后,如果你的代码中有获取 root 根节点的逻辑,请更新对应的值:

- const domNode = document.getElementById('root');
+ const domNode = document.getElementById('app');

ReactDOM.createRoot(domNode).render(<App />);

自定义模板

如果你自定义了 HTML 模板,请确保模板中包含 <div id="<%= mountId %>"></div>,否则 mountId 配置项无法生效。

html.scriptLoading

  • 类型: 'defer' | 'blocking' | 'module'
  • 默认值: 'defer'

用于设置 <script> 标签的加载方式。

defer

默认情况下,Rsbuild 生成的 <script> 标签会自动设置 defer 属性,以避免阻塞页面的解析和渲染。

<head>
  <script defer src="/static/js/main.js"></script>
</head>
<body></body>
TIP

当浏览器遇到带有 defer 属性的 <script> 标签时,它会异步地下载脚本文件,不会阻塞页面的解析和渲染。在页面解析和渲染完成后,浏览器会按照 <script> 标签在文档中出现的顺序依次执行它们。

blocking

scriptLoading 设置为 blocking 可以移除 defer 属性,此时脚本是同步执行的,这意味着它会阻塞浏览器的解析和渲染过程,直到脚本文件被下载并执行完毕。

export default {
  html: {
    inject: 'body',
    scriptLoading: 'blocking',
  },
};

当你需要设置 blocking 时,建议把 html.inject 设置为 body,避免页面渲染被阻塞。

<head></head>
<body>
  <script src="/static/js/main.js"></script>
</body>

module

scriptLoading 设置为 module 时,可以让脚本支持 ESModule 语法,同时浏览器也会自动默认延迟执行这些脚本,效果与 defer 类似。

export default {
  html: {
    scriptLoading: 'module',
  },
};
<head>
  <script type="module" src="/static/js/main.js"></script>
</head>
<body></body>

html.tags

  • 类型: ArrayOrNot<HtmlInjectTag | HtmlInjectTagHandler>
  • 默认值: undefined

添加和修改最终注入到 HTML 页面的标签。

对象形式

export interface HtmlInjectTag {
  tag: string;
  attrs?: Record<string, string | boolean | null | undefined>;
  children?: string;
  /** @default false */
  hash?: boolean | string | ((url: string, hash: string) => string);
  /** @default true */
  publicPath?: boolean | string | ((url: string, publicPath: string) => string);
  /** @default false */
  append?: boolean;
  /**
   * 仅对于允许包含在 head 中的元素会默认启用
   * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head#see_also}
   */
  head?: boolean;
}

对象形式的配置项可以用于描述需要注入的标签,并可以通过参数控制注入的位置:

export default {
  output: {
    assetPrefix: '//example.com/',
  },
  html: {
    tags: [
      {
        tag: 'script',
        attrs: { src: 'a.js' },
        head: true,
        append: true,
        publicPath: true,
        hash: true,
      },
    ],
  },
};

这样的配置将会在 HTML 的 head 最后添加一个 script 标签:

<html>
  <head>
    <!-- some other headTags... -->
    <script src="//example.com/a.js?8327ec63"></script>
  </head>
  <body>
    <!-- some other bodyTags... -->
  </body>
</html>

标签最终的插入位置由 headappend 选项决定,两个配置相同的元素将被插入到相同区域,并且维持彼此之间的相对位置。

标签中表示外部资源文件路径的字段会受到 publicPathhash 选项的影响, 这些字段包括 script 标签的 srclink 标签的 href

启用 publicPath 会让标签中表示路径的属性被拼接上 output.assetPrefix 字段。 而 hash 字段会让文件名后多出一个哈希查询用于控制浏览器缓存,哈希字符串与 HTML 文件产物的哈希值相同。

用户也可以向这两个配置传入函数,以自行控制路径拼接的逻辑。

函数形式

export type HtmlInjectTagUtils = {
  outputName: string;
  publicPath: string;
  hash: string;
};

export type HtmlInjectTagHandler = (
  tags: HtmlInjectTag[],
  utils: HtmlInjectTagUtils,
) => HtmlInjectTag[] | void;

html.tags 也支持传入回调函数,通过在回调中编写逻辑可以任意修改标签列表,常用于修改标签列表或是在插入标签的同时确保其相对位置。

回调函数接受 tags 列表作为参数,并需要修改或直接返回新的 tags 数组:

export default {
  html: {
    tags: [
      (tags) => {
        tags.splice(0, 1);
      },
      /* ^?
       *   { tag: 'script', attrs: { src: 'b.js' } },
       *   ... some other headTags
       *   { tag: 'script', attrs: { src: 'c.js' } },
       *   ... some other bodyTags
       *   { tag: 'script', attrs: { src: 'a.js' }, head: false },
       */
      { tag: 'script', attrs: { src: 'a.js' }, head: false },
      { tag: 'script', attrs: { src: 'b.js' }, append: false },
      { tag: 'script', attrs: { src: 'c.js' } },
      (tags) => [...tags, { tag: 'script', attrs: { src: 'd.js' } }],
      /* ^?
       *   ... some other headTags
       *   { tag: 'script', attrs: { src: 'c.js' } },
       *   ... some other bodyTags
       *   { tag: 'script', attrs: { src: 'a.js' }, head: false },
       */
    ],
  },
};

函数将在 HTML 处理流程的最后被执行,即如下的例子中不管回调函数在配置项中的位置如何, 参数 tags 都会包含配置项中所有的对象形式配置。

也因此在回调中修改 append publicPath hash 等属性都不会生效,因为这些属性都已经分别应用到了标签的位置和路径属性。

于是最终产物将会类似:

<html>
  <head>
    <!-- tags with `{ head: true, append: false }` here. -->
    <!-- some other headTags... -->
    <!-- tags with `{ head: true, append: true }` here. -->
    <script src="//example.com/c.js"></script>
    <script src="//example.com/d.js"></script>
  </head>
  <body>
    <!-- tags with `{ head: false, append: false }` here. -->
    <!-- some other bodyTags... -->
    <!-- tags with `{ head: false, append: true }` here. -->
    <script src="//example.com/a.js"></script>
  </body>
</html>

限制

这个配置用于在 Rsbuild 构建完成后修改 HTML 产物的内容,并不会引入和解析新的模块。因此,它无法用于引入未编译的源码文件,也无法代替 source.preEntry 等配置。

例如对于以下项目:

web-app
├── src
│   ├── index.tsx
│   └── polyfill.ts
└── modern.config.ts
modern.config.ts
export default {
  output: {
    assetPrefix: '//example.com/',
  },
  html: {
    tags: [{ tag: 'script', attrs: { src: './src/polyfill.ts' } }],
  },
};

这里的 tag 对象将会在简单处理后直接添加到 HTML 产物中,但对应的 polyfill.ts 将不会被转译、打包,也因此应用会在处理这行脚本时出现 404 错误。

<body>
  <script src="//example.com/src/polyfill.ts"></script>
</body>

合理的使用场景包括:

  • 注入 CDN 上 路径确定 的静态资源
  • 注入需要首屏加载的内联脚本

例如以下示例的使用方式:

web-app
├── src
│   └── index.tsx
├── public
│   └── service-worker.js
└── modern.config.ts
modern.config.ts
function report() {
  fetch('https://www.example.com/report');
}

export default {
  html: {
    output: {
      assetPrefix: '//example.com/',
    },
    tags: [
      // Inject asset from the `public` directory.
      { tag: 'script', attrs: { src: 'service-worker.js' } },
      // Inject asset from other CDN url.
      {
        tag: 'script',
        publicPath: false,
        attrs: { src: 'https://cdn.example.com/foo.js' },
      },
      // Inject inline script.
      {
        tag: 'script',
        children: report.toString() + '\nreport()',
      },
    ],
  },
};

得到的产物将会类似:

<body>
  <script src="//example.com/service-worker.js"></script>
  <script src="https://cdn.example.com/foo.js"></script>
  <script>
    function report() {
      fetch('https://www.example.com/report');
    }
    report();
  </script>
</body>

html.template

  • 类型: string | Function
  • 默认值:

定义 HTML 模板的文件路径,可以是相对路径或绝对路径。

如果没有指定 template,默认会使用 Rsbuild 内置的 HTML 模板:

<!doctype html>
<html>
  <head></head>
  <body>
    <div id="<%= mountId %>"></div>
  </body>
</html>

字符串用法

例如,使用 static/index.html 文件替代默认 HTML 模板,可以添加如下设置:

export default {
  html: {
    template: './static/index.html',
  },
};

函数用法

  • 类型:
type TemplateFunction = ({ value: string; entryName: string }) => string | void;

html.template 为 Function 类型时,函数接收一个对象作为入参,对象的值包括:

  • value:Rsbuild 的默认 template 配置。
  • entryName: 当前入口的名称。

在 MPA(多页面应用)场景下,你可以基于入口名称返回不同的 template 路口,从而为每个页面设置不同的模板:

export default {
  html: {
    template({ entryName }) {
      const templates = {
        foo: './static/foo.html',
        bar: './static/bar.html',
      };
      return templates[entryName] || './static/index.html',
    },
  },
};

html.templateParameters

  • 类型: Record<string, unknown> | Function
  • 默认值:
type DefaultParameters = {
  mountId: string; // 对应 html.mountId 配置
  entryName: string; // 入口名称
  assetPrefix: string; // 对应 output.assetPrefix 配置
  compilation: Compilation; // Rspack 的 compilation 对象
  // htmlWebpackPlugin 内置的参数
  // 详见 https://github.com/jantimon/html-webpack-plugin
  htmlWebpackPlugin: {
    tags: object;
    files: object;
    options: object;
  };
};

定义 HTML 模板中的参数,对应 html-webpack-plugintemplateParameters 配置项。

对象用法

如果 templateParameters 的值是一个对象,它会和默认参数通过 Object.assign 合并。

比如,如果你需要在 HTML 模板中使用 foo 参数,可以添加如下设置:

export default {
  html: {
    templateParameters: {
      foo: 'bar',
    },
  },
};

接下来,你可以在 HTML 模板中,通过 <%= foo %> 来读取参数:

<script>
  window.foo = '<%= foo %>';
</script>

编译后的 HTML 代码如下:

<script>
  window.foo = 'bar';
</script>

函数用法

  • 类型:
type TemplateParametersFunction = (
  defaultValue: Record<string, unknown>,
  utils: { entryName: string },
) => Record<string, unknown> | void;

html.templateParameters 为 Function 类型时,函数接收两个参数:

  • value:Rsbuild 的默认 templateParameters 配置。
  • utils: 一个对象,其中包含了 entryName 字段,对应当前入口的名称。

在 MPA(多页面应用)场景下,你可以基于入口名称设置不同的 templateParameters 参数:

export default {
  html: {
    templateParameters(defaultValue, { entryName }) {
      const params = {
        foo: {
          ...defaultValue,
          type: 'Foo',
        },
        bar: {
          ...defaultValue,
          type: 'Bar',
          hello: 'world',
        },
      };
      return params[entryName] || defaultValue;
    },
  },
};

html.title

  • 类型: string | Function
  • 默认值: 'Rsbuild App'

配置 HTML 页面的 title 标签。

字符串用法

html.title 可以直接设置为一个字符串:

export default {
  html: {
    title: 'Example',
  },
};

最终在 HTML 中生成的 title 标签为:

<title>Example</title>

函数用法

  • 类型:
type TitleFunction = ({ value: string; entryName: string }) => string | void;

html.title 为 Function 类型时,函数接收一个对象作为入参,对象的值包括:

  • value:Rsbuild 的默认 title 配置。
  • entryName: 当前入口的名称。

在 MPA(多页面应用)场景下,你可以基于入口名称返回不同的 title 字符串,从而为每个页面生成不同的 title 标签:

export default {
  html: {
    title({ entryName }) {
      const titles = {
        foo: 'Foo Page',
        bar: 'Bar Page',
      };
      return titles[entryName] || 'Other Page';
    },
  },
};