Html Config

This section describes some HTML configurations in Modern.js Builder.

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

  • Type: boolean
  • Default: false

Remove the folder of the HTML files. When this option is enabled, the generated HTML file path will change from [name]/index.html to [name].html.

Example

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

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

Enable the html.disableHtmlFolder config:

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

After recompiling, the directory structure of the HTML files in dist is:

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

If you want to set the 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" />

For detailed usage, please refer to Rsbuild - html.favicon.

html.faviconByEntries

  • Type: Record<string, string>
  • Default: undefined

Set different favicon for different pages.

The usage is same as favicon, and you can use the "entry name" as the key to set each page individually.

faviconByEntries will overrides the value set in favicon.

WARNING

Deprecated: This configuration is deprecated, please use the function usage of favicon instead.

Example

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

After recompiling, you will see:

  • The favicon for page foo is ./src/assets/foo.png.

  • The favicon for other pages is ./src/assets/default.png.

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>

For detailed usage, please refer to Rsbuild - html.inject.

html.injectByEntries

  • Type: Record<string, boolean | string>
  • Default: undefined

Set different script tag inject positions for different pages.

The usage is same as inject, and you can use the "entry name" as the key to set each page individually.

injectByEntries will overrides the value set in inject.

WARNING

Deprecated: This configuration is deprecated, please use the function usage of inject instead.

Example

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

After recompiling, you will see:

  • The script tag of the page foo will be injected inside the body tag.

  • The script tag of other pages will be injected inside the head tag.

html.meta

  • Type: Object | Function
  • Default: undefined

Configure the <meta> tag of the HTML.

Example

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

For detailed usage, please refer to Rsbuild - html.meta.

html.metaByEntries

  • Type: Record<string, Meta>
  • Default: undefined

Set different meta tags for different pages.

The usage is same as meta, and you can use the "entry name" as the key to set each page individually.

metaByEntries will overrides the value set in meta.

WARNING

Deprecated: This configuration is deprecated, please use the function usage of meta instead.

Example

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

After compiling, you can see that the meta of the page foo is:

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

The meta of other pages is:

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

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 Builder 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 Builder 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.tagsByEntries

  • Type: Record<string, ArrayOrNot<HtmlInjectTag | HtmlInjectTagHandler>>
  • Default: undefined

Used for multiple entry applications, injecting different tags for each entry.

The usage is the same as tags, and you can use the "entry name" as the key to set each page individually.

tagsByEntries will override the value set in tags.

WARNING

Deprecated: This configuration is deprecated, please use the function usage of tags instead.

Example

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

Compile the application and you can see a tag injected on the foo page:

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

And for any other pages:

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

html.template

  • Type: string | Function
  • Default:

Define the path to the HTML template, corresponding to the template config of html-webpack-plugin.

Example

Replace the default template with a custom HTML template file, you can add the following config:

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

For detailed usage, please refer to Rsbuild - html.template.

html.templateByEntries

  • Type: Object
  • Default: undefined

Set different template file for different pages.

The usage is same as template, and you can use the "entry name" as the key to set each page individually.

templateByEntries will overrides the value set in template.

WARNING

Deprecated: This configuration is deprecated, please use the function usage of template instead.

Example

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

html.templateParameters

  • Type: Object | Function
  • Default:
type DefaultParameters = {
  mountId: string; // corresponding to html.mountId config
  entryName: string; // entry name
  assetPrefix: string; // corresponding to output.assetPrefix config
  compilation: webpack.Compilation; // Compilation object corresponding to webpack
  webpackConfig: config; // webpack config
  // 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. You can use the config as an object or a function.

If it is an object, it will be merged with the default parameters. For example:

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

If it is a function, the default parameters will be passed in, and you can return an object to override the default parameters. For example:

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

For detailed usage, please refer to Rsbuild - html.templateParameters.

html.templateParametersByEntries

  • Type: Object
  • Default: undefined

Set different template parameters for different pages.

The usage is same as templateParameters, and you can use the "entry name" as the key to set each page individually.

templateParametersByEntries will overrides the value set in templateParameters.

WARNING

Deprecated: This configuration is deprecated, please use the function usage of templateParameters instead.

Example

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

html.title

  • Type: string | Function
  • Default: undefined

Set the title tag of the HTML page, for example:

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

For detailed usage, please refer to Rsbuild - html.title.

html.titleByEntries

  • Type: Record<string, string>
  • Default: undefined

Set different title for different pages.

The usage is same as title, and you can use the "entry name" as the key to set each page individually.

titleByEntries will overrides the value set in title.

WARNING

Deprecated: This configuration is deprecated, please use the function usage of title instead.

Example

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

After recompiling, you can see:

  • The title of the page foo is TikTok.
  • The title of other pages is ByteDance.