webpack实战——(3)自动生成项目中的html页面

1. 引言

在上一篇当中我们讲了如何通过webpack打包带hash的(或者说是版本号)的文件。这里,引申出来一个问题:文件名是不固定的,那岂不是每次打包后要去修改引用?

这么麻烦,当然不可能这么做。下文就来讲讲如何借用webpack的插件来解决这个问题。

2. 生成html页面

(1) 安装插件

在Terminal中安装html-webpack-plugin插件。

npm install html-webpack-plugin --save-dev

(2) 引用插件

在webpack.config.js中require该插件,并加上plugins这一项。

const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');

module.exports={
  entry: {
    main: './src/script/main.js',
    a: './src/script/a.js'
  },
  output:{
    path: path.resolve(__dirname, 'dist/js'),
    filename: "[name]-[chunkhash].bundle.js"    
  },
  plugins:[
    new htmlWebpackPlugin()
  ]
}

npm run webpack后可以看到dist文件夹中生成打包后的html,如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Webpack App</title>
  </head>
  <body>
  <script type="text/javascript" src="main-d02880881ec2455f025c.bundle.js"></script><script type="text/javascript" src="a-3af0762ff7b4e6ff9f7c.bundle.js"></script></body>
</html>

js的引用的确为带有hash的文件了。但是,哪里不对?

再看一下src中的index.html的源文件,如下,title标签中的文字是不一样的。

<title>webpack demo</title>

这说明了打包后的index.html和源文件的index.html没有任何关系。这显然是不满足需求的。

(3) 给插件添加模板

[1] 指定模板

如何解决上述问题呢?需要给插件给插件指定一个模板。

plugins:[
  new htmlWebpackPlugin({
    template: 'index.html'
  })
]

打包后,title发生了改变,与源文件中一致了。这样,两者就绑定起来了。

但是,还是有个问题。所有打包的内容都在dist->js中,而index.html我们并不希望放到这个文件夹了,因为不属于js,也不符合规范。

[2] 修改输出路径

这里,我们需要修改output,将输出的目标位置改为dist,同时,给js需要输出的位置增加相对路径。

【修改前】

output:{
  path: path.resolve(__dirname, 'dist/js'),
  filename: "[name]-[chunkhash].bundle.js"    
},

【修改后】

output:{
  path: path.resolve(__dirname, 'dist'),
  filename: "js/[name]-[chunkhash].bundle.js"    
},

(4) html-webpack-plugin参数

[1] filename

此时打包后的index.html不带hash。通过html-webpack-plugin提供的参数可以解决这个问题。如下,增加了filename的属性。

plugins:[
  new htmlWebpackPlugin({
    filename:'index-[hash].html',
    template: 'index.html'
  })
]

[2] inject

使用inject可以设置js的引用是放在body中还是放在head中(不设置,默认放在body中),如下。

plugins:[
  new htmlWebpackPlugin({
    filename:'index-[hash].html',
    template: 'index.html',
    inject: 'head'
  })
]

3. 传参生成html页面

在这一小节,将介绍如何将config中设置的参数引用到模板页中。

(1) 参数传递

html-webpack-plugin支持js模板文件,EJS的语法。以下为一个实例。
在webpack.config.js中设置一个title属性:

//新增title属性
plugins:[
  new htmlWebpackPlugin({
    filename:'index-[hash].html',
    template: 'index.html',
    inject: 'head',
    title: 'webpack is good'
  })
]

在index.html中的title标签中引用这个属性。以下就是EJS取值的书写方式。通过options才能活着plugin上面的属性值。

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

(2) 参数遍历

在body标签中对htmlWebpackPlugin进行遍历如下:

<body>
  <% for (var key in htmlWebpackPlugin) { %>
    <%= key %>
  <% } %>
</body>

重新打包后,输出了两个key值 files和options。再遍历这两个key可以看到对象中完整的内容。
打印出key和value。由于遍历出的值有可能是对象或者数组,又因为是js模板文件,可以直接使用js的方法,所以使用了JSON.stringify将内容字符串化。

<% for (var key in htmlWebpackPlugin.files) { %>
     <%= key %>: <%= JSON.stringify(htmlWebpackPlugin.files[key]) %>
<% } %>

<% for (var key in htmlWebpackPlugin.options) { %>
    <%= key %>: <%= JSON.stringify(htmlWebpackPlugin.options[key]) %>
<% } %>

打包后的结果为:

publicPath: ""

chunks: {"main":{"size":43,"entry":"js/main-a4ae71e3f02a70b934a0.bundle.js","hash":"a4ae71e3f02a70b934a0","css":[]},"a":{"size":14,"entry":"js/a-3612423bc713708b1f9e.bundle.js","hash":"3612423bc713708b1f9e","css":[]}}

js: ["js/main-a4ae71e3f02a70b934a0.bundle.js","js/a-3612423bc713708b1f9e.bundle.js"]

css: []

manifest: 

 template: "/Projects/webpack-demo/node_modules/html-webpack-plugin/lib/loader.js!/Projects/webpack-demo/index.html" filename: "index-[hash].html" hash: false inject: "head" compile: true favicon: false minify: false cache: true showErrors: true chunks: "all" excludeChunks: [] title: "webpack is good" xhtml: false

可以在npm的官网上看到这些参数的详细解释:
https://www.npmjs.com/package/html-webpack-plugin#configuration

(3) 自定义js引用

在实际的开发中,可能会遇到一部分js在head中引用,而另一部分则需要放到body中。这样的需求是无法在配置文件中完成的,那么需要修改模板文件。

首先需要修改config中对引用的配置:

plugins:[
  new htmlWebpackPlugin({
    filename:'index-[hash].html',
    template: 'index.html',
    inject: false, //修改为false
    title: 'webpack is good'
  })
]

按照上述需求,修改模板文件

<!DOCTYPE HTML>
<html>

  <head>
    <meta charset="uft-8">
    <title><%= htmlWebpackPlugin.options.title %></title>

    <script type="text/javascript" src="<%=
    htmlWebpackPlugin.files.chunks.main.entry %>" >
    </script>
  </head>

  <body>
    <script type="text/javascript" src="<%=
    htmlWebpackPlugin.files.chunks.a.entry %>" >
    </script>
  </body>

</html>

打包后生成的html文件如下:

<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="uft-8">
    <title>webpack is good</title>
    <script type="text/javascript" src="js/main-a4ae71e3f02a70b934a0.bundle.js" >
    </script>
  </head>
  <body>
    <script type="text/javascript" src="js/a-3612423bc713708b1f9e.bundle.js" >
    </script>
  </body>
</html>

(4) 打包上线路径设置

前面讲的都是本地的情况,如果要打包上线,那么路径肯定和本地的是不一致的。如何解决呢?这里需要借助的是output中的publicPath属性。

output:{
  path: path.resolve(__dirname, 'dist'),
  filename: "js/[name]-[chunkhash].bundle.js",
  publicPath: 'http://test.com/'
},

这里会将原来本地的绝对路径的地址改为publicPath设置的绝对路径为开头。打包后的结果为:

<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="uft-8">
    <title>webpack is good</title>
    <script type="text/javascript" src="http://test.com/js/main-917206156823645a8946.bundle.js" >
    </script>
  </head>
  <body>
    <script type="text/javascript" src="http://test.com/js/a-f35f2090dfab299129c5.bundle.js" >
    </script>
  </body>
</html>

(5) html文件压缩

通过minify属性对生成的html进行压缩。

minify有很多参数。在我们上文中提到的npm官网的html-webpack-plugin的configuration中有一个 html-minifier的链接。

下面,对一些简单的参数进行尝试:

plugins:[
    new htmlWebpackPlugin({
      filename:'index-[hash].html',
      template: 'index.html',
      inject: false,
      title: 'webpack is good',
      minify: {
        removeComments: true, //删除注释
        collapseWhitespace: true //删除空格
      }
    })
  ]

打包后的结构

<!DOCTYPE HTML><html><head><meta charset="uft-8"><title>webpack is good</title><script type="text/javascript" src="http://test.com/js/main-917206156823645a8946.bundle.js"></script></head><body><script type="text/javascript" src="http://test.com/js/a-f35f2090dfab299129c5.bundle.js"></script></body></html>

4. 多页面应用

下面用一个例子说明,有a页、b页、c页三个页面的工程该如何处理。

首先在src->script文件夹中新建a.js、b.js、c.js文件。

在webpack.config.js中,将plugin设置为长度为3的数组。

plugins:[
  new htmlWebpackPlugin({
    filename:'a-[hash].html',
    template: 'index.html',
    inject: false,
    title: 'this is a',
  }),
  new htmlWebpackPlugin({
    filename:'b-[hash].html',
    template: 'index.html',
    inject: false,
    title: 'this is b',
  }),
  new htmlWebpackPlugin({
    filename:'c-[hash].html',
    template: 'index.html',
    inject: false,
    title: 'this is c',
  }),
]

打包后发现一个问题,三个html在js的加载中,都加载的是a.js。

如何解决?

(1) chunks

在插件官网中有这样一个属性chunks

chunks: Allows you to add only some chunks (e.g. only the unit-test chunk)

修改配置文件:

const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');

module.exports={
  entry: {
    main: './src/script/main.js',
    a: './src/script/a.js',
    b: './src/script/b.js',
    c: './src/script/c.js'
  },
  output:{
    path: path.resolve(__dirname, 'dist'),
    filename: "js/[name]-[chunkhash].bundle.js",
    publicPath: 'http://test.com/'
  },
  plugins:[
    new htmlWebpackPlugin({
      filename:'a-[hash].html',
      template: 'index.html',
      inject: 'body', //改为自动插入body
      title: 'this is a',
      chunks: ['main','a'] //chunk名称需要和entry中的属性保持一致
    }),
    new htmlWebpackPlugin({
      filename:'b-[hash].html',
      template: 'index.html',
      inject: 'body',
      title: 'this is b',
      chunks: ['main','b']
    }),
    new htmlWebpackPlugin({
      filename:'c-[hash].html',
      template: 'index.html',
      inject: 'body',
      title: 'this is c',
      chunks: ['c']
    }),
  ]
}

再删掉模板文件中先前的手动引入文件

<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="uft-8">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
  </body>
</html>

(2) excludeChunks

上例是个简单结构,在实际的开发中,可能会遇到很多页面都用到了entry中的绝大多数的chunk,一个一个去设置,比较麻烦。这个插件也提供了解决方案,属性excludeChunks

excludeChunks: Allows you to skip some chunks (e.g. don’t add the unit-test chunk)

chunk一共为4个 main、a、b、c。以下两种是等价的:

chunks: ['main','a'] <=> excludeChunks: ['b','c']

5. 以inline方式引入js

为了提高脚本的运行速度,减少http请求,可以把一部分初始的js代码直接插入html中,而不是以src的方式引入进来。该如何处理呢?

插件在github上的examples -> line中提供了解决方案:
https://github.com/jantimon/html-webpack-plugin/tree/master/examples/inline

开始实战:

这一部分比较复杂,先进行拆解:

在模板文件index.html中写入:

<script type="text/javascript">
  // substr是为了获得相对路径,不带publicPath部分
  <%= htmlWebpackPlugin.files.chunks.main.entry.substr(htmlWebpackPlugin.files.publicPath.length) %>
</script>  

这部分得到的是js/main-917206156823645a8946.bundle.js

使用compilation.assets[].source(),取到其中的内容。方括号中的一大段就是上文中的那部分。

<%= compilation.assets[htmlWebpackPlugin.files.chunks.main.entry.substr(htmlWebpackPlugin.files.publicPath.length)].source() %>

打包后,成功以inline的方式插入了。但是,又引入了一遍。于是,还需要进行修改。

将配置文件中的injects属性都改为false,再手动的引入其他js文件。

如下,body标签中遍历chucks,除了main是inline方式引入了,其他的chuck,根据配置文件中的chucks属性进行外联引入。

<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="uft-8">
    <title><%= htmlWebpackPlugin.options.title %></title>
    <script type="text/javascript">
      <%= compilation.assets[htmlWebpackPlugin.files.chunks.main.entry.substr(htmlWebpackPlugin.files.publicPath.length)].source() %>
    </script>  
  </head>

  <body>
    <!--遍历插入外联js-->
    <% for(var k in htmlWebpackPlugin.files.chunks){%>
      <% if(k !== 'main') {%>
        <script type="text/javascript" src="<%=
        htmlWebpackPlugin.files.chunks[k].entry %>"></script>
      <% } %>
    <% } %>
  </body>
</html>
  • 0
    点赞
  • 0
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Joyce的前端日常

喜欢,有用,就鼓励我一下~

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值