likes
comments
collection
share

html-plugin-webpack模板内容高度定制化

作者站长头像
站长
· 阅读数 34

使用html-plugin-webpack时一般都会设置template属性配置html模板。 但这存在缺点: 在团队拥有多个项目时, html格式的模板内容无法做到通用功能的统一和针对特定项目的扩展。所以如果将模板内容的配置为js内容,获取js的完全编码能力, 既可以封装团队中项目通用模板内容, 有保证较高的定制能力.

本文中使用的是html-plugin-webpack版本是v3版本。v4版本支持更多的选项支持本文的内容,而不是通过插件实现。

下文就以html模板中常用改用为js代码怎么配置.

页面标题

如果不配置html-webpack-plugin的template选项, 配置title选项时, 生成的html文件中将会有标题:

module.exports = {
  // ...
  plugins: [
    new HtmlWebpackPlugin({
      title: '测试',
    }),
  ],
};

生成的文件中:

  <head>
    <meta charset="UTF-8">
    <title>测试</title>
  </head>

图标

设置favicon选项, html-plugin-webpack会自动将图片文件复制到output目录下, 然后使用, 会自动添加上publicPath.

module.exports = {
  // ...
  plugins: [
    new HtmlWebpackPlugin({
      favicon: './build/favicon.ico',
    }),
  ],
};

生成的html代码中:

<link rel="shortcut icon" href="https://you.com/path/to/favicon.ico">
对于移动端应用, 图标配置推荐使用favicons-webpack-plugin.

设置meta信息

html文件中通常需要设置许多meta信息, 通过html-webpack-html的meta选项, 可以生成这些meta信息, 如:

module.exports = {
  // ...
  plugins: [
    new HtmlWebpackPlugin({
      meta: {
        author: 'xxx前端团队',
      },
    }),
  ],
};

如果需要配置http-equiv相关, 需要以下语法:

{ 
    meta: {
        pragma: { 'http-equiv': 'pragma', 'content': 'no-cache' },
    },
}

如果需要对同一个http-equiv属性设置多次值, 需要为:

{ 
    meta: {
        cacheControl1: { 'http-equiv': 'cache-control', content: 'no-cache' },
        cacheControl2: { 'http-equiv': 'cache-control', content: 'no-siteapp' },
        cacheControl3: { 'http-equiv': 'cache-control', content: 'no-transform' },
    },
}

一个较完整的配置:

module.exports = {
  // ...
  plugins: [
    new HtmlWebpackPlugin({
      meta: {
        author: 'xxx前端团队',
        cacheControl1: { 'http-equiv': 'cache-control', content: 'no-cache' },
        cacheControl2: { 'http-equiv': 'cache-control', content: 'no-siteapp' },
        cacheControl3: { 'http-equiv': 'cache-control', content: 'no-transform' },
        expires: { 'http-equiv': 'expires', content: '0' },
        compatible: { 'http-equiv': 'X-UA-Compatible', content: 'IE=edge,chrome=1' },
      },
    }),
  ],
};

生成的html代码:

<meta name="author" content="xxx前端团队">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="cache-control" content="no-siteapp">
<meta http-equiv="cache-control" content="no-transform">
<meta http-equiv="expires" content="0">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">

额外的资源

html中通常需要一些额外的资源, 如一些库(jquery, vue)全局提供, 一些polyfill资源等,推荐使用html-webpack-tags-plugin)来处理。

html-webpack-tags-plugin只是在模板中添加一些额外的资源标签, 不会对目标资源文件做任何其他操作,需要配合copy-webpack-plugin一起使用, copy-webpack-plugin能够把额外的资源文件(可能在node_modules中)复制到webpack配置中的output目录下.

这里以导入的谷歌分析文件为例, 假如在项目根目录下有谷歌分析单页面应用插件autotrack:

|--assets
  |--autotrack.min.js
  |--autotrack.min.js.map

配置:

module.exports = {
  // ...
  output: {
    // ...
    path: path.resolve(__dirname, 'dist')
  },

  plugins: [
    // 需要开启HtmlWebpackPlugin插件
    new HtmlWebpackPlugin({
      title: '测试',
    }),
   new CopyWebpackPlugin([{
      from: './assets',
      to: './dist',
      toType: 'dir',
    }]),
    new HtmlWebpackTagsPlugin({
      tags: [{
        path: 'autotrack/autotrack.js',
        attributes: {
          async: 'true',
        },
      }],
      scripts: [{
        path: 'https://www.google-analytics.com/analytics.js',
        attributes: {
          type: 'text/javascript',
          async: 'true',
        },
        append: false,
      }],
    }),
  ],
};

生成的dist目录:

|--dist
  |--autotrack
    |--autotrack.min.js
    |--autotrack.min.js.map
  |--bundle.js
  |--index.html

html代码内容:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>测试</title>
  </head>
  <body>
  <script type="text/javascript" src="https://www.google-analytics.com/analytics.js" async="true"></script><script type="text/javascript" src="/bundle.js"></script><script type="text/javascript" src="/autotrack/autotrack.js" async="true"></script></body>
</html>
另外也可以使用html-webpack-tags-plugin的增强库html-webpack-deploy-plugin

内联js代码或者其他直接在html中引用的js代码

实际开发过程中通常还需要在html文件中包含一些内联的js代码或者引用第三方代码, 如配置谷歌分析调用的代码。

可以使用webpack-concat-plugin进行额外的js资源的导入。

webpack-concat-plugin比html-webpack-tags-plugin的好处是支持对导入的js文件进行压缩.

webpack-concat-plugin对于目标js资源, 会自动复制到当前webpack配置output目录下, 所以不需要配合copy-webpack-plugin使用.

假如需要在单页面应用的index.html导入google-analytics-starter.js内容: 

;((function(){
      window.ga = window.ga || function () { (ga.q = ga.q || []).push(arguments) }; ga.l = +new Date;
      ga('create', 'UA-63187412-4', 'auto');
      ga('require', 'eventTracker');
      ga('require', 'outboundLinkTracker');
      ga('require', 'urlChangeTracker');
      ga('send', 'pageview');
})());

此时需要在webpack配置:

module.exports = {
  // ...
  output: {
    // ...
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: '测试',
    }),

    new WebpackConcatPlugin({
      uglify: false,
      sourceMap: false,
      name: 'google-analytics-starter',
      outputPath: 'static',
      fileName: '[name].[hash:8].js',
      filesToConcat: [path.resolve(__dirname, './google-analytics-starter.js')],
    }),
  ],
};

生成的html:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>测试</title>
  </head>
  <body>
  <script type="text/javascript" src="/static/google-analytics-starter.8f8f0b03.js"></script><script type="text/javascript" src="/bundle.js"></script></body>
</html>

对于内联代码, 建议抽取为一个一个js文件, 通过webpack-concat-plugin引用外链.对于可能造成的资源请求过多, webpack-concat-plugin可以配置filesToConcat为一个目录(多个目录也行), 会整体打成一个包.

其他逻辑追加

在html模板中, 通常还需要设置一些其他的内容, 比如供渲染框架挂载的dom元素.

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

这种框架暂时还未在社区找到, 不过可以编写一个简单的html-plugin-webpack插件实现:

const _ = require('lodash');

function resetOptionToFn(option) {
  if (_.isArray(option)) {
    return (tags) => {
      tags.push(...option);
    };
  }
  if (_.isPlainObject(option)) {
    if (option.before || option.after) {
      return (tags) => {
        tags.unshift(...[].concat(option.before || []));
        tags.push(...[].concat(option.after || []));
      };
    }
    return (tags) => {
      tags.push(option);
    };
  }
  return () => {};
}

module.exports = class HtmlCodePlugin {
  constructor({
    body,
    head,
  } = {}) {
    this.body = resetOptionToFn(body);
    this.head = resetOptionToFn(head);
  }

  apply(_compiler) {
    const compiler = _compiler;
    compiler.hooks.compilation.tap(this.constructor.name, (compilation) => {
      compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(this.constructor.name, async (data, cb) => {
        try {
          this.body(data.body);
          this.head(data.head);
          cb(null, data);
        } catch (error) {
          cb(error, null);
        }
      });
    });
  }
};

然后就可以在webpack中配置:

module.exports = {
  // ...
  plugins: [
    new HtmlWebpackPlugin({
      title: '测试',
    }),
    new MyPlugin({
      body: {
        tagName: 'div',
        selfClosingTag: false,
        voidTag: false,
        attributes: {
          id: 'app',
        },
      },
    }),
  ],
};

生成的html代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>测试</title>
  </head>
  <body>
  <script type="text/javascript" src="/bundle.js"></script><div id="app"></div></body>
</html>

对于其他的如注释代码, HTML中IE判断语句if !IE都可以实现类似html-webpack-plugin的插件。

案例代码中已经发布了一个可以使用的npm库: webpack-plugin-html-append-tag

写在最后

上文中列举了许多html模板中的内容怎么用js去配置, 这对使用统一构建工具构建多个项目的团队非常有用. 利用js的编程能力, 可以把团队通用的模板配置内容封装在构建工具内, 并提供对项目中对模板内容定制提供api, 提供封装和扩展能力.

由于当时在开发时,html-plugin-webpack@3还不支持更多的内容定制,只能通过插件去实现。但随着v4版本的到来,很多内容已经通过配置属性即可支持,无需像本文一样使用一些插件来支持。