Html Config

本章节描述了 Builder 中与 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.disableHtmlFolder

;

  • 类型: boolean
  • 默认值: false

移除 HTML 产物对应的文件夹。开启该选项后,生成的 HTML 文件目录会从 [name]/index.html 变为 [name].html

示例

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

/dist
└── html
    └── main
        └── index.html

开启 html.disableHtmlFolder 配置:

export default {
  html: {
    disableHtmlFolder: true,
  },
};

重新编译后,HTML 产物在 dist 中的目录结构如下:

/dist
└── html
    └── main.html

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

html.favicon

;

  • 类型: string
  • 默认值: 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" />

html.faviconByEntries

;

  • 类型: Record<string, string>
  • 默认值: undefined

用于在多页面的场景下,为不同的页面设置不同的 favicon。

整体用法与 favicon 一致,并且可以使用「入口名称」作为 key ,对各个页面进行单独设置。

faviconByEntries 的优先级高于 favicon,因此会覆盖 favicon 中设置的值。

示例

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

重新编译后,可以看到:

  • 页面 foo 的 favicon 为 ./src/assets/foo.png

  • 其他页面的 favicon 为 ./src/assets/default.png

html.inject

;

  • 类型: 'head' | 'body'| 'true' | false
  • 默认值: '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>

html.injectByEntries

;

  • 类型: Record<string, boolean | string>
  • 默认值: undefined

用于在多页面的场景下,为不同的页面设置不同的 script 标签插入位置。

整体用法与 inject 一致,并且可以使用「入口名称」作为 key ,对各个页面进行单独设置。

injectByEntries 的优先级高于 inject,因此会覆盖 inject 中设置的值。

示例

export default {
  html: {
    inject: 'head',
    injectByEntries: {
      foo: 'body',
    },
  },
};

重新编译后,可以看到:

  • 页面 foo 的 script 标签会插入到 body 标签内。

  • 其他页面的 script 标签会插入到 head 标签内。

html.meta

;

  • 类型: Record<string, false | string | Record<string, string | boolean>>
  • 默认值: undefined

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

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

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

这种情况下默认不会设置 namecontent 属性。

比如设置 http-equiv

export default {
  html: {
    meta: {
      'http-equiv': {
        'http-equiv': 'x-ua-compatible',
        content: 'ie=edge',
      },
    },
  },
};

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

<meta http-equiv="x-ua-compatible" content="ie=edge" />

移除默认值

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

比如移除框架预设的 imagemode

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

html.metaByEntries

;

  • 类型: Record<string, Meta>
  • 默认值: undefined

用于在多页面的场景下,为不同的页面设置不同的 meta 标签。

整体用法与 meta 一致,并且可以使用「入口名称」作为 key ,对各个页面进行单独设置。

metaByEntries 的优先级高于 meta,因此会覆盖 meta 中设置的值。

示例

export default {
  html: {
    meta: {
      description: 'ByteDance',
    },
    metaByEntries: {
      foo: {
        description: 'TikTok',
      },
    },
  },
};

编译后,可以看到页面 foo 的 meta 为:

<meta name="description" content="TikTok" />

其他页面的 meta 为:

<meta name="description" content="ByteDance" />

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

默认情况下,Builder 生成的 <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>

限制

这个配置用于在 Builder 构建完成后修改 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.tagsByEntries

;

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

用于在多页面的场景下,为不同的页面注入不同的标签。

整体用法与 tags 一致,并且可以使用「入口名称」作为 key ,对各个页面进行单独设置。

tagsByEntries 的优先级高于 tags,因此会覆盖 tags 中设置的值。

示例

export default {
  html: {
    tags: [{ tag: 'script', attrs: { src: 'a.js' } }],
    tagsByEntries: {
      foo: [{ tag: 'script', attrs: { src: 'b.js' } }],
    },
  },
};

编译后,可以看到页面 foo 注入标签:

<script src="b.js"></script>

其他页面则注入了:

<script src="a.js"></script>

html.template

;

  • 类型: string
  • 默认值:

定义 HTML 模板的文件路径,对应 html-webpack-plugintemplate 配置项。

示例

使用自定义的 HTML 模板文件替代默认模板,可以添加如下设置:

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

html.templateByEntries

;

  • 类型: Object
  • 默认值: undefined

用于在多页面的场景下,为不同的页面设置不同的 HTML 模板。

整体用法与 template 一致,并且可以使用「入口名称」作为 key ,对各个页面进行单独设置。

templateByEntries 的优先级高于 template,因此会覆盖 template 设置的值。

示例

export default {
  html: {
    template: './static/index.html',
    templateByEntries: {
      foo: './src/pages/foo/index.html',
      bar: './src/pages/bar/index.html',
    },
  },
};

html.templateParameters

;

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

定义 HTML 模板中的参数,对应 html-webpack-plugintemplateParameters 配置项。你可以使用配置为对象或者函数。

如果是对象,会和默认参数合并。比如:

export default {
  html: {
    templateParameters: {
      title: 'My App',
    },
  },
};

如果是函数,会传入默认参数,你可以返回一个对象,用于覆盖默认参数。比如:

export default {
  html: {
    templateParameters: defaultParameters => {
      console.log(defaultParameters.compilation);
      console.log(defaultParameters.title);
      return {
        title: 'My App',
      };
    },
  },
};

示例

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

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

或者使用函数配置:

export default {
  html: {
    templateParameters: defaultParameters => {
      return {
        foo: 'bar',
      };
    },
  },
};

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

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

经过编译后的最终 HTML 代码如下:

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

html.templateParametersByEntries

;

  • 类型: Object
  • 默认值: undefined

用于在多页面的场景下,为不同的页面设置不同的模板参数。

整体用法与 templateParameters 一致,并且可以使用「入口名称」作为 key ,对各个页面进行单独设置。

templateParametersByEntries 的优先级高于 templateParameters,因此会覆盖 templateParameters 中设置的值。

示例

export default {
  html: {
    templateParametersByEntries: {
      foo: {
        type: 'a',
      },
      bar: {
        type: 'b',
      },
    },
  },
};

html.title

;

  • 类型: string
  • 默认值: undefined

配置 HTML 页面的 title 标签,例如:

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

html.titleByEntries

;

  • 类型: Record<string, string>
  • 默认值: undefined

用于在多页面的场景下,为不同的页面设置不同的 title

整体用法与 title 一致,并且可以使用「入口名称」作为 key ,对各个页面进行单独设置。

titleByEntries 的优先级高于 title,因此会覆盖 title 中设置的值。

示例

export default {
  html: {
    title: 'ByteDance',
    titleByEntries: {
      foo: 'TikTok',
    },
  },
};

重新编译后,可以看到:

  • 页面 foo 的 title 为 TikTok
  • 其他页面的 title 为 ByteDance