配置HTML模版

在构建的过程中,EMP 会基于 HTML 模板文件和模板参数进行编译,生成一个类似于 SPA 的构建产物。

EMP 提供了一些配置项来对 HTML 模板进行设置。通过本章节你可以了解到这些配置项的基本用法。

设置模板文件

EMP 默认使用自带的 HTML 模板文件,当然, EMP 允许您指定一个 HTML 文件作为模板文件代替默认的 HTML 模板文件进行产物构建运行,您可以使用 html.template 配置项来设置它:

emp.config.js
export default defineConfig(store => {
  return {
    html: {
      template: './index.html',
    },
  }
})

上述示例代码会查找当前 emp.config.js 同一级目录下的 index.html 作为模板文件。

⚠️ 注意

配置项 html.template 的值只能是相对路径,请勿使用绝对路径,默认值为 src/index.html

模板文件示例

为了您的正常使用,您应该将HTML模板设置完整,一个最小的模板文件应该是这样的:

min.html
<!DOCTYPE html>
<html lang="<%= htmlWebpackPlugin.options.lang %>">

<head>
  <title>
    <%= htmlWebpackPlugin.options.title %>
  </title>
</head>

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

</html>

当然,您也可以进行在此基础上进行优化,进行兼容性等相关处理,完整的示例如下:

index.html
<!DOCTYPE html>
<!--[if lt IE 7 ]> <html lang="en" class="ie6" <% if(htmlWebpackPlugin.files.manifest) { %> manifest="<%= htmlWebpackPlugin.files.manifest %>"<% } %>> <![endif]-->
<!--[if IE 7 ]>    <html lang="en" class="ie7" <% if(htmlWebpackPlugin.files.manifest) { %> manifest="<%= htmlWebpackPlugin.files.manifest %>"<% } %>> <![endif]-->
<!--[if IE 8 ]>    <html lang="en" class="ie8" <% if(htmlWebpackPlugin.files.manifest) { %> manifest="<%= htmlWebpackPlugin.files.manifest %>"<% } %>> <![endif]-->
<!--[if IE 9 ]>    <html lang="en" class="ie9" <% if(htmlWebpackPlugin.files.manifest) { %> manifest="<%= htmlWebpackPlugin.files.manifest %>"<% } %>> <![endif]-->
<!--[if (gt IE 9)|!(IE)]><!--> <html lang="en" class="" <% if(htmlWebpackPlugin.files.manifest) { %> manifest="<%= htmlWebpackPlugin.files.manifest %>"<% } %>> <!--<![endif]-->
<head>
  <meta charset="utf-8">
  <title><%= htmlWebpackPlugin.options.title || 'Webpack App'%></title>

  <% if (htmlWebpackPlugin.files.favicon) { %>
  <link rel="shortcut icon" href="<%= htmlWebpackPlugin.files.favicon%>">
  <% } %>
  <% if (htmlWebpackPlugin.options.mobile) { %>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <% } %>

  <% for (var css in htmlWebpackPlugin.files.css) { %>
  <link href="<%= htmlWebpackPlugin.files.css[css] %>" rel="stylesheet">
  <% } %>
</head>
<body>
<% if (htmlWebpackPlugin.options.unsupportedBrowser) { %>
<style>.unsupported-browser { display: none; }</style>
<div class="unsupported-browser">
  Sorry, your browser is not supported.  Please upgrade to
  the latest version or switch your browser to use this site.
  See <a href="http://outdatedbrowser.com/">outdatedbrowser.com</a>
  for options.
</div>
<% } %>

<% if (htmlWebpackPlugin.options.appMountId) { %>
<div id="<%= htmlWebpackPlugin.options.appMountId%>"></div>
<% } %>

<% if (htmlWebpackPlugin.options.appMountIds && htmlWebpackPlugin.options.appMountIds.length > 0) { %>
<% for (var index in htmlWebpackPlugin.options.appMountIds) { %>
<div id="<%= htmlWebpackPlugin.options.appMountIds[index]%>"></div>
<% } %>
<% } %>

<% if (htmlWebpackPlugin.options.window) { %>
<script>
  <% for (var varName in htmlWebpackPlugin.options.window) { %>
    window['<%=varName%>'] = <%= JSON.stringify(htmlWebpackPlugin.options.window[varName]) %>;
  <% } %>
</script>
<% } %>

<% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
<script src="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>"></script>
<% } %>

<% if (htmlWebpackPlugin.options.devServer) { %>
<script src="<%= htmlWebpackPlugin.options.devServer%>/webpack-dev-server.js"></script>
<% } %>

<% if (htmlWebpackPlugin.options.googleAnalytics) { %>
<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
      (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');


  <% if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
    ga('create', '<%= htmlWebpackPlugin.options.googleAnalytics.trackingId%>', 'auto');
    <% } else { throw new Error("html-webpack-template requires googleAnalytics.trackingId config"); }%>

  <% if (htmlWebpackPlugin.options.googleAnalytics.pageViewOnLoad) { %>
    ga('send', 'pageview');
  <% } %>
</script>
<% } %>
</body>
</html>

设置页面标题

当我们只需要改动页面标题时,我们可以直接通过 html.title 配置项来设置它:

emp.config.js
export default defineConfig(store => {
  return {
    html: {
      title: 'EMP3 666',
    },
  }
})

上述示例代码会将 HTML 模板文件中的<title> 标签设置为 EMP3 666

设置页面语言

当我们只需要改动页面语言时,我们可以直接通过 html.lang 配置项来设置它:

emp.config.js
export default defineConfig(store => {
  return {
    html: {
      lang: 'en-US',
    },
  }
})

上述示例代码会将 HTML 模板文件中的<lang> 标签设置为 en-US

设置页面meta标签

当我们只需要设置meta标签时,我们可以直接通过 html.meta 配置项来设置它:

emp.config.js
export default defineConfig(store => {
  return {
    html: {
      meta: {
        viewport: 'width=device-width, initial-scale=1.0',
      },
    },
  }
})

上述示例代码会将 HTML 模板文件中的<meta> 标签设置相关视口属性。

设置页面图标

当我们只需要设置页面图标时,我们可以直接通过 html.favicon 配置项来设置它:

emp.config.js
export default defineConfig(store => {
  return {
    html: {
      favicon: './favicon.ico',
    },
  }
})

上述示例代码会查找当前 emp.config.js 同一级目录下的 favicon.ico 作为页面图标。

⚠️ 注意

配置项 html.favicon 的值只能是相对路径,请勿使用绝对路径,默认值为 src/favicon.ico

设置模板参数

⚠️ 注意

使用模板参数需要您在项目中正确嵌入了模板参数,或者使用了默认的模板文件,并且使用的模板渲染引擎为 Lodash Template

在 HTML 模板中,你可以使用丰富的模板参数用于HTML模板中,EMP默认注入的参数有:

type DefaultParameters = {
  mountId: string; // 对应 html.mountId 配置
  entryName: string; // 入口名称
  assetPrefix: string; // 对应 dev.assetPrefix 和 output.assetPrefix 配置
  compilation: Compilation; // 对应 Rspack 的 compilation 对象
  // htmlWebpackPlugin 内置的参数
  // 详见 https://github.com/jantimon/html-webpack-plugin
  htmlWebpackPlugin: {
    tags: object;
    files: object;
    options: object;
  };
};

您也可以通过 html.templateParameters 配置项来传入自定义的模板参数:

emp.config.js
export default defineConfig(store => {
  return {
    html: {
      templateParameters: {
        title: 'EMP3 wow',
      },
    },
  }
})

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

<div><%= text %>!</div>

最终经过 EMP 编译后,它的编译结果如下:

<div>EMP3 wow!</div>

模板引擎

EMP 内置了三套模板引擎,可用于动态化生成HTML内容,默认使用最基础的 Lodash Template 作为模板引擎。

Lodash Template

当模板文件的后缀为 .html 时,EMP 会使用 Lodash Template 对模板进行编译。

例如,在模板中定义一个 text 参数,值为 'EMP3 wow',在构建时会自动将 <%= text %> 替换为对应的值。

<!-- 输入 -->
<div><%= text %>!</div>

<!-- 输出 -->
<div>EMP3 wow!</div>
💡 TIP

您可以阅读 Lodash Template 来了解完整用法。

EJS文件

当模板文件的后缀为 .ejs 时,EMP 会使用 EJS 模板引擎对模板进行编译。EJS 是一套简单的模板语言,支持直接在标签内书写简单、直白的 JavaScript 代码,并通过 JavaScript 输出最终所需的 HTML。

例如,你可以先通过 html.template 配置项来引用一个 .ejs 模板文件:

emp.config.js
export default defineConfig(store => {
  return {
    html: {
      template: './index.ejs',
    },
  }
})

接着在模板中定义一个 user 参数,值为 { name: 'Youli' }。在构建时,会自动将 <%= user.name %> 替换为对应的值。

<!-- 输入  -->
<% if (user) { %>
<h2><%= user.name %></h2>
<% } %>

<!-- 输出 -->
<h2>Youli</h2>
💡 TIP

您可以阅读 EJS 来了解完整用法。

Pug

EMP 通过 Pug 插件来支持 Pug 模板引擎,请阅读 Pug 插件文档 来了解用法。

注入标签

通过配置 html.tags 选项可以在最终生成的 HTML 产物中插入任意标签。

🛠️ 使用场景

前端应用的产物最终都会直接或间接地被 HTML 入口引用,但大多数时候直接向 HTML 注入标签都并非首选。

模版文件中可以通过 htmlWebpackPlugin.tags 变量来访问需要最终注入到 HTML 的所有标签:

<html>
  <head>
    <%= htmlWebpackPlugin.tags.headTags %>
  </head>
  <body>
    <%= htmlWebpackPlugin.tags.bodyTags %>
  </body>
</html>

html.tags 的作用就是调整这些模板变量进而修改 HTML,配置的具体定义参考 API References

对象用法

emp.config.js
export default defineConfig(store => {
  return {
    html: {
      tags: {
        headTags: [
          {tag: 'script', attrs: {src: 'a.js'}},
          {tag: 'script', attrs: {src: 'b.js'}},
          {tag: 'link', attrs: {href: 'style.css', rel: 'stylesheet'}},
          {tag: 'link', attrs: {href: 'page.css', rel: 'stylesheet'}, publicPath: false},
          {tag: 'meta', attrs: {name: 'referrer', content: 'origin'}},
        ],
        bodyTags: [{tag: 'script', attrs: {src: 'c.js'}}],
      },
    },
  }
})

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

标签默认会启用 publicPath 配置,即会将 output.assetPrefix 的值拼接到 script 标签的 src 等表示路径的属性上。

所以以上配置构建出的 HTML 产物将会类似:

<html>
  <head>
    <script src="https://example.com/b.js"></script>
    <link href="https://example.com/style.css" rel="stylesheet" />
    <link href="page.css" rel="stylesheet" />
    <!-- some other headTags... -->
    <script src="https://example.com/a.js"></script>
    <meta name="referrer" content="origin" />
  </head>
  <body>
    <!-- some other bodyTags... -->
    <script src="https://example.com/c.js"></script>
  </body>
</html>

函数用法

html.tags 也支持传入回调函数,常用于修改标签列表或是在插入标签的同时确保其相对位置。

emp.config.js
export default defineConfig(store => {
  return {
    html: {
      tags: {
        headTags: [
          (tags) => {
            tags.splice(0, 1);
          },
          { tag: 'script', attrs: { src: 'a.js' }},
          { tag: 'script', attrs: { src: 'b.js' }},
          { tag: 'script', attrs: { src: 'c.js' } },
          (tags) => [...tags, { tag: 'script', attrs: { src: 'd.js' } }],
        ],
        bodyTags: [],
      },
    },
  }
})

最终产物将会类似:

<html>
  <head>
    <!-- some other headTags... -->
    <script src="https://example.com/c.js"></script>
    <script src="https://example.com/d.js"></script>
    <script src="https://example.com/a.js"></script>
  </head>
  <body>
  </body>
</html>