Html

This section describes some HTML configurations in Rsbuild.

html.appIcon

  • Type: string
  • Default: undefined

Set the file path of the apple-touch-icon icon for the iOS system, can be set as a relative path relative to the project root directory, or as an absolute path to the file. Setting it as a CDN URL is not currently supported.

After config this option, the icon will be automatically copied to the dist directory during the compilation, and the corresponding link tag will be added to the HTML.

Example

Set as a relative path:

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

Set to an absolute path:

import path from 'path';

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

After recompiling, the following tags are automatically generated in the HTML:

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

html.crossorigin

  • Type: boolean | 'anonymous' | 'use-credentials'
  • Default: false

Set the crossorigin attribute of the <script> and <style> tags.

  • If true is passed, it will automatically be set to crossorigin="anonymous".
  • If false is passed, it will not set the crossorigin attr.

Example

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

After compilation, the <script> tag in HTML becomes:

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

The <style> tag becomes:

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

Optional Values

crossorigin can the set to the following values:

  • anonymous: Request uses CORS headers and credentials flag is set to 'same-origin'. There is no exchange of user credentials via cookies, client-side SSL certificates or HTTP authentication, unless destination is the same origin.
  • use-credentials: Request uses CORS headers, credentials flag is set to 'include' and user credentials are always included.

html.outputStructure

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

Define the directory structure of the HTML output files.

Example

By default, the structure of HTML files in the dist directory is flat:

/dist
 └── [name].html

You can set html.outputStructure to nested:

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

After rebuild, the directory structure of the HTML files is:

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

If you want to set the parent path of the HTML files, use the output.distPath.html config.

html.favicon

  • Type: string | Function
  • Default: undefined

Set the favicon icon path for all pages, can be set as:

  • a URL.
  • an absolute path to the file.
  • a relative path relative to the project root directory.

After config this option, the favicon will be automatically copied to the dist directory during the compilation, and the corresponding link tag will be added to the HTML.

Example

Set as a relative path:

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

Set to an absolute path:

import path from 'path';

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

Set to a URL:

import path from 'path';

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

After recompiling, the following tags are automatically generated in the HTML:

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

Function Usage

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

When html.favicon is of type Function, the function receives an object as input, with the following properties:

  • value: the default favicon configuration for Rsbuild.
  • entryName: the name of the current entry.

In the context of MPA (Multi-Page Application), you can return different favicon based on the entry name, thus generating different tags for each page:

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

  • Type: 'head' | 'body' | boolean | Function
  • Default: 'head'

Set the inject position of the <script> tag.

Can be set to the following values:

  • 'head': The script tag will be inject inside the head tag.
  • 'body': The script tag is inject at the end of the body tag.
  • true: The result depends on the scriptLoading config of html-webpack-plugin.
  • false: script tags will not be injected.

Default inject position

The script tag is inside the head tag by default:

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

Inject into body

Add the following config to inject script into the body tag:

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

You will see that the script tag is generated at the end of the body tag:

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

Function Usage

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

When html.inject is of type Function, the function receives an object as its parameter, with the following properties:

  • value: the default inject configuration of Rsbuild.
  • entryName: the name of the current entry.

In the context of MPA (Multi-Page Application), you can set different inject behaviors based on the entry name:

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

html.meta

  • Type: Object | Function
  • Default:
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',
};

Configure the <meta> tag of the HTML.

String Type

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

When the value of a meta object is a string, the key of the object is automatically mapped to name, and the value is mapped to content.

For example to set description:

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

The generated meta tag in HTML is:

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

Object Type

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

When the value of a meta object is an object, the key: value of the object is mapped to the attribute of the meta tag.

In this case, the name and content properties will not be set by default.

For example to set charset:

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

The meta tag in HTML is:

<meta charset="UTF-8" />

Function Usage

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

When html.meta is of type Function, the function receives an object as an argument with the following properties:

  • value: the default meta configuration of Rsbuild.
  • entryName: the name of the current entry.

You can directly modify the configuration object and not return anything, or you can return an object as the final configuration.

For example, you can directly modify the built-in meta configuration object:

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

In the MPA (Multi-Page Application) scenario, you can return different meta configurations based on the entry name, thus generating different meta tags for each page:

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',
          };
      }
    },
  },
};

Remove Default Value

Setting the value of the meta object to false and the meta tag will not be generated.

For example to remove the viewport:

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

html.mountId

  • Type: string
  • Default: 'root'

By default, the root element is included in the HTML template for component mounting, and the element id can be modified through mountId.

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

Example

Set the id to app:

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

After compilation:

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

Notes

Update Relevant Code

After modifying mountId, if there is logic in your code to obtain the root root node, please update the corresponding value:

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

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

Custom Templates

If you customized the HTML template, please make sure that the template contains <div id="<%= mountId %>"></div>, otherwise the mountId config will not take effect.

html.scriptLoading

  • Type: 'defer' | 'blocking' | 'module'
  • Default: 'defer'

Used to set how <script> tags are loaded.

defer

By default, the <script> tag generated by Rsbuild will automatically set the defer attribute to avoid blocking the parsing and rendering of the page.

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

When the browser encounters a <script> tag with the defer attribute, it will download the script file asynchronously without blocking the parsing and rendering of the page. After the page is parsed and rendered, the browser executes the <script> tags in the order they appear in the document.

blocking

Setting scriptLoading to blocking will remove the defer attribute, and the script is executed synchronously, which means it will block the browser's parsing and rendering process until the script file is downloaded and executed.

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

When you need to set blocking, it is recommended to set html.inject to 'body' to avoid page rendering being blocked.

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

module

When scriptLoading is set to module, the script can support ESModule syntax, and the browser will automatically delay the execution of these scripts by default, which is similar to defer.

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

html.tags

  • Type: ArrayOrNot<HtmlInjectTag | HtmlInjectTagHandler>
  • Default: undefined

Modifies the tags that are injected into the HTML page.

Tag Object

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;
  /**
   * Enable by default only for elements that are allowed to be included in the `head` tag.
   * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head#see_also}
   */
  head?: boolean;
}

A tag object can be used to describe the tag to be injected and the location of the injection can be controlled by the parameters.

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

It will add a script tag to the end of the head of the HTML:

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

The final insertion position of the tag is determined by the head and append options, and two elements with the same configuration will be inserted into the same area and hold their relative positions to each other.

Fields in the tag that indicate the path to the external assets are affected by the publicPath and hash options. These fields include src for the script tag and href for the link tag.

Enabling publicPath will splice the output.assetPrefix field before the attribute representing the path in the tag. And the hash field causes the filename to be followed by an additional hash query to control browser caching, with the same hash string as the HTML file product.

You can also pass functions to those fields to control the path joining.

Tags Handler

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

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

html.tags can also accept functions that can arbitrarily modify tags by writing logic to the callback, often used to ensure the relative position of tags while inserting them.

The callback function accepts a tag list as an argument and needs to modify or return a new tag array directly.

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 },
       */
    ],
  },
};

The function will be executed at the end of the HTML processing flow. In the example below, the 'tags' parameter will contain all tag objects that form config, regardless of the function's location in config.

Modifying the attributes append, publicPath, hash in the callback will not take effect, because they have been applied to the tag's location and path attributes, respectively.

So the end product will look like:

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

Limitation

This configuration is used to modify the content of HTML files after Rsbuild completes building, and does not resolve or parse new modules. It cannot be used to import uncompiled source code files. Also cannot replace configurations such as source.preEntry.

For example, for the following project:

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' } }],
  },
};

The tag object here will be directly added to the HTML product after simple processing, but the polyfill.ts will not be transpiled or bundled, so there will be a 404 error when processing this script in the application.

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

Reasonable use cases include:

  • Injecting static assets with determined paths on CDN.
  • Injecting inline scripts that need to be loaded on the first screen.

For example, the usage of the following example:

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()',
      },
    ],
  },
};

The result will seems like:

<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

  • Type: string | Function
  • Default:

Specifies the file path for the HTML template, which can be a relative or absolute path.

If template is not specified, the built-in HTML template of Rsbuild will be used by default:

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

String Usage

For example, to replace the default HTML template with the static/index.html file, you can add the following configuration:

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

Function Usage

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

When html.template is of type Function, the function receives an object as an argument, with the following properties:

  • value: the default template configuration of Rsbuild.
  • entryName: the name of the current entry.

In the MPA (multi-page application) scenario, you can return different template paths based on the entry name, thus setting different templates for each page:

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
  • Default:
type DefaultParameters = {
  mountId: string; // corresponding to html.mountId config
  entryName: string; // entry name
  assetPrefix: string; // corresponding to output.assetPrefix config
  compilation: Compilation; // Compilation object of Rspack
  // htmlWebpackPlugin built-in parameters
  // See https://github.com/jantimon/html-webpack-plugin for details
  htmlWebpackPlugin: {
    tags: object;
    files: object;
    options: object;
  };
};

Define the parameters in the HTML template, corresponding to the templateParameters config of html-webpack-plugin.

Object Usage

If the value of templateParameters is an object, it will be merged with the default parameters using Object.assign.

For example, if you need to use the foo parameter in an HTML template, you can add the following settings:

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

Then, you can read the parameter in the HTML template using <%= foo %>:

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

The compiled HTML code will be:

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

Function Usage

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

When html.templateParameters is of type Function, the function receives two parameters:

  • value: Default templateParameters configuration of Rsbuild.
  • utils: An object containing the entryName field, corresponding to the name of the current entry.

In the context of a multi-page application (MPA), you can set different templateParameters based on the entry name:

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

html.title

  • Type: string | Function
  • Default: 'Rsbuild App'

Set the title tag of the HTML page.

String Usage

html.title can be directly set as a string:

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

The title tag generated in HTML will be:

<title>Example</title>

Function Usage

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

When html.title is of type Function, the function receives an object as the argument, and the object's values include:

  • value: the default title configuration of Rsbuild.
  • entryName: the name of the current entry.

In the MPA (multi-page application) scenario, you can return different title strings based on the entry name, thus generating different title tags for each page:

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