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>