webpack 区分环境使用CDN以及HtmlWebpackPlugin插件的编写
webpack 区分环境使用CDN以及HtmlWebpackPlugin插件的编写
最近项目由1.0版开发到了1.1版,内容越来越多,引入的库也越来越多,每次打包的时候webpack都会提示,包的大小太大什么的,影响性能,作为一个有追求的前端当然不会放任不管,于是,Google一番,引入cdn,在index.html中写入
<script src="https://unpkg.com/vue@2.5.21/dist/vue.min.js"></script>
<script src="https://unpkg.com/vuex@3.0.1/dist/vuex.min.js"></script>
<script src="https://unpkg.com/element-ui@2.4.11/lib/index.js"></script>
2
3
在webpack.base.conf.js中添加要忽略的包
externals: {
vue: 'Vue',
vuex: 'Vuex',
'element-ui': 'ELEMENT'
}
2
3
4
5
一切都看起来很不错,but,当我背着电脑回家,打开项目,npm run dev,输入localhost:8080,页面出不来,额,忘了没有联网。。。但是,没有网就不能开发吗,显然不能,于是注释掉上面这些代码,页面终于出来了,这么注释来注释去,不够优雅,我只想在生产环境下使用CDN,在开发环境下还是想用本地的,有了问题我们来解决
# 区分环境使用CDN
HtmlWebpackPlugin插件是我们开发时基本上必用的一个webpack插件,网上的介绍也很多,它主要是完成对index.html的js,css等资源的注入,同时它也能定义一些变量,template即我们的模板文件如果没有使用loader的时候,默认以ejs格式处理,这样,我们就可以在index.html方便的处理了
首先,去掉index.html中我们添加的CDN引用
去掉webpack.base.conf.js中的externals配置,将其放到webpack.prod.conf.js中
在webpack.prod.conf.js中的HtmlWebpackPlugin中添加一个变量cdn,最终修改为如下所示:
new HtmlWebpackPlugin({ filename: config.build.index, template: 'index.html', inject: true, cdn:[ 'https://unpkg.com/vue@2.5.21/dist/vue.min.js', 'https://unpkg.com/vuex@3.0.1/dist/vuex.min.js', 'https://unpkg.com/element-ui@2.4.11/lib/index.js' ], minify: { removeComments: true, collapseWhitespace: true, removeAttributeQuotes: true }, chunksSortMode: 'dependency' })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16将index.html中添加如下内容:
<% if(htmlWebpackPlugin.options.cdn){ %> <% for(var i in htmlWebpackPlugin.options.cdn){ %> <script src="<%= htmlWebpackPlugin.options.cdn[i] %>"></script> <% } %> <% } %>
1
2
3使用ejs语法将定义在HtmlWebpackPlugin中的cdn写入,这样就解决了只在生产环境使用CDN,在开发环境下使用本地的。但是,作为一个完美主义者,修改HtmlWebpackPlugin插件的配置以及在index.html中写入一串类似js的代码总觉得不够优雅,难道不能直接在HtmlWebpackPlugin注入的阶段插入CDN吗,于是一顿操作,最终发现HtmlWebpackPlugin提供了事件接口,我们可以在它的基础上开发自己的插件,bingo,那么就来开发一个吧。
# HtmlWebpackPlugin插件的编写
HtmlWebpackPlugin插件在3.x和4.x版本上的写法是不一样的,我们的开发环境使用的webpack3.x的,因此下面我基于3.x进行开发。
HtmlWebpackPlugin提供了5个异步事件和1个同步事件,这些在官方GitHub和其他网友的博客中都有写到,我就不再叙述了,我主要使用了html-webpack-plugin-before-html-processing事件,在HTML处理之前将js注入进去,官网给了开发插件的例子:
function MyPlugin(options) { // Configure your plugin with options... } MyPlugin.prototype.apply = function(compiler) { // ... compiler.plugin('compilation', function(compilation) { console.log('The compiler is starting a new compilation...'); compilation.plugin('html-webpack-plugin-before-html-processing', function(htmlPluginData, callback) { htmlPluginData.html += 'The magic footer'; callback(null, htmlPluginData); }); }); }; module.exports = MyPlugin;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18htmlPluginData我们打印出来一看,属性一目了然:
{ html:''//HTML模板内容, assets:{ chunks:[],//入口文件的内容 js:[],//要插入的js文件的路径 ---就是我们要找的 css:[],//要插入的css文件的路径 manifest:'' }, .... }
1
2
3
4
5
6
7
8
9
10assets.js就是我们要找的,编写插件其实只要一句话
htmlPluginData.assets.js = this.options.js.concat(htmlPluginData.assets.js)
1so easy,下面是插件的完整代码:
class InjectCDN { constructor (options) { const defaultOpt = { js: [] } this.options = Object.assign(defaultOpt, options) } // 处理方法 apply (compiler) { // webpack3.x compiler.plugin('compilation', compilation => { compilation.plugin( 'html-webpack-plugin-before-html-processing', (htmlPluginData, callback) => { // 将js添加进去 htmlPluginData.assets.js = this.options.js.concat(htmlPluginData.assets.js) if (callback) { return callback(null, htmlPluginData) } else { return Promise.resolve(htmlPluginData) } } ) }) } } module.exports = InjectCDN
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28在webpack.prod.conf.js中这样使用:
new HtmlWebpackPlugin({}), new InjectCDN({ js: [ 'https://unpkg.com/vue@2.5.21/dist/vue.min.js', 'https://unpkg.com/vuex@3.0.1/dist/vuex.min.js', 'https://unpkg.com/element-ui@2.4.11/lib/index.js' ] })
1
2
3
4
5
6
7
8new InjectCDN需要放到new HtmlWebpackPlugin后面。
这样,就完成了在发布的时候使用cdn,在开发的时候使用本地文件。同时,这个插件也可以实现不同环境下不同CDN的切换,方便调试和发布。