Webpack从2015年9月第一个版本横空初始至今已逾2载。它的出现,颠覆了一大批主流构建如Ant、Grunt和Gulp等等。腾讯NOW直播IVWEB团队以前一直采用Fis构建,本篇文章主要介绍从Fis迁移到webpack遇到的问题和背后的黑科技,内容包括inline-resource、多页面构建、资源压缩、文件hash、文件目录规则等等。javascript
有两个层面的缘由:css
"scripts": { "dev": "cross-env NODE_ENV=dev nodemon --watch webpack.config.js --exec \"webpack-dev-server --config webpack.config.js --env development\" --progress --colors", "build": "webpack --config webpack.config.js --env production --progress --colors", ... },
经过在package.json中注入环境变量的方式,注入NODE_ENV=dev表明开发环境,默认为生产环境。这里使用cross-env的缘由是:windows下 在package.json中直接使用 NODE_ENV=dev 不生效,需写成 set NODE_ENV=dev,cross-env的写法兼容各个操做系统。html
inline-resource的好处是能够减小css,js等的请求数,同时html加载的时候便可同时加载了这些内联的css、js等静态资源,能够有效的减小白屏或者页面闪动的问题。前端
这里的内联分为2种,一种是静态的html片断,css,js等,这些资源一开始就存在项目的某个目录下;另外一种是构建过程当中动态生成的css,js文件。html5
对于html的内联,写法以下:java
${require('raw-loader!../src/assets/inline/meta.html')}
对于js的内联,须要增长babel-loader将ES6的语法进行转换,避免浏览器直接解析致使报错。写法以下:node
<script>${require('raw-loader!babel-loader!../src/node_modules/@tencent/report-whitelist/lib/index.js')}</script>
说明:不能将html-loader和html-webpack-plugin同时使用,html-loader会致使默认的ejs模板引擎语法解析实效,形成 ${} 和 <% = %>等语法不生效linux
上面讲述了如何内联静态的资源文件,那么如何内联构建过程当中动态生成的资源文件呢?这里须要借助html-webpack-inline-source-plugin来加强html-webpack-plugin的功能。好比:将构建过程当中生成的css文件inline到html模板里面去。webpack
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin'); new HtmlWebpackPlugin({ inlineSource: isDev ? undefined : '\\.css$', template: __dirname + '/template/index.tmpl.html', filename: 'activity.html', inject: true, }), new HtmlWebpackInlineSourcePlugin(), ...
上面这段代码,html-webpack-plugin自己并不具有inlineSource的属性。引入了html-webpack-inline-source-plugin以后,就能够经过inlineSource属性来匹配哪些文件须要动态的内联进html模板文件中了。git
多页面构建,或者称为通配(wildcards)构建。即须要构建的页面数量是不肯定的,可能A业务有3张页面,B业务有5张页面。所以,咱们不能把entry写死了:
entry: { activity: './src/pages/activity/init.js', // 深海寻宝活动首页 my-reward: './src/pages/my-reward/init.js', // 个人奖励 exchange: './src/pages/exchange/init.js' // 线下兑换奖品 },
为何上面的写法不可取呢?很明显:上面的写法把entry写死了,这并不通用。后面若是产品需求发生改变,须要新增一张页面,就须要手动修改构建脚本。咱们须要的entry是:'./src/pages/**/init.js',它可以像一些linux的命令,具有匹配某个规则的全部结果的能力。这里的思路是借助glob,达到动态entry的目的。
entry: glob.sync('./src/pages/**/init.js'),
在webpack构建中,一个页面须要一个与之对应的HtmlWebpackPlugin实例,N个页面须要N个与之对应的HtmlWebpackPlugin。此处须要动态的设置HtmlWebpackPlugin的实例个数。
const newEntry = {}; Object.keys(config.entry).map((index) => { const entry = config.entry[index]; const match = entry.match(/\/pages\/(.*)\/init.js/); const pageName = match && match[1]; newEntry[pageName] = entry; config.plugins.push( new HtmlWebpackPlugin({ inlineSource: isDev ? undefined: '\\.css$', template: __dirname + '/template/index.tmpl.html', filename: `${pageName}.html`, chunks: [pageName], inject: true }) ); }); config.entry = newEntry;
对于html文件里面的内容压缩能够经过给html-webpack-plugin设置minify参数,html-webpack-plugin的压缩配置实际上是直接集成了 html-minifier,所以minify的参数设置能够直接参考html-minifier的文档。
config.plugins.push( new HtmlWebpackPlugin({ inlineSource: isDev ? undefined: '\\.css$', template: __dirname + '/template/index.tmpl.html', filename: `${pageName}.html`, chunks: [pageName], inject: true, minify: { minifyJS: true, // 仅压缩内联在html里面的js minifyCSS: true, // 仅压缩内联在html里面的css html5: true, // 以html5的文档格式解析html的模板文件 removeComments: false, // 不删除Html文件里面的注释 collapseWhitespace: true, // 删除空格 preserveLineBreaks: false // 删除换行 } }) );
设置了上面的minify参数后,看到生成的html文件的内容所有在1行上,须要注意的是:minifyJS和minifyCSS只会压缩内联在这个html文件的css和js内容,对于单独的css文件和js文件并不会压缩。 那么打包出来的css和js文件如何压缩呢?
对于css文件压缩,直接开启css-loader的压缩参数参数minimize为true便可:
{ test: /\.scss$/, use: ExtractTextPlugin.extract({ fallback: 'style-loader', use: [ { loader: "css-loader", options: { // 设置css-loader的minimize参数为true minimize: true } }, { loader: "sass-loader" } ] }) },
css-loader开启压缩可能会报错 Module build failed: BrowserlistError: unkonwn version 61 and _chr,解决办法:
$ npm i caniuse-db —save #更新caniuse-db到最新版本
对于js文件的压缩,能够经过引入 webpack 内置的 UglifyJsPlugin:
const webpack = require('webpack'); plugins: [ ... new webpack.optimize.UglifyJsPlugin(), ... ],
每次功能发布上线,都须要从新构建一次源代码,生成一个新的文件版本列表。此处文件Hash的方式就是一种版本管理的方式,发布时替换有变化的版本的文件,达到增量更新的效果。此处Hash策略是:根据文件内容进行hash,取8位。
JS文件:
output: { filename: '[name]_[chunkhash:8].js', // 进行js脚本hash path: path.resolve(__dirname, 'public/'), publicPath: isDev ? '/' : cdnUrl + '/', },
Css文件:
plugins: [ new CleanWebpackPlugin(['./public']), new ExtractTextPlugin('[name]_[contenthash:8].css'), // css文件hash new webpack.optimize.UglifyJsPlugin(), ... ]
Img文件:
rules: [ { test: /\.(png|svg|jpg|gif)$/, use: { loader: 'file-loader', options: { name: '[name]_[hash:8].[ext]', // img文件hash } } }, ... ]
开发过程当中,不一样分辨率的浏览器适配是个让前端开发者头疼的问题。手淘的rem方案完美解决了这个问题,它的核心思想是页面加载时动态设置body的font-size值和rem和px转换的单位。
为了避免改变编程习惯,开发过程当中仍然使用px单位来做为基础布局长度单位,以750px宽度的视觉稿做为基准,设置rem和px的转换单位为1rem=75px。那么px2rem如何集成进webpack中呢?首先增长css的解析px2rem-loader,而后在html头部引入lib-flexible文件。
{ test: /\.scss$/, use: ExtractTextPlugin.extract({ fallback: 'style-loader', use: [ { loader: "css-loader" }, { loader: "px2rem-loader", // 增长px2rem-loader,而且设置rem单位为75px options: { remUnit: 75 } }, { loader: "sass-loader" } ] }) },
"scripts": { "dev": "cross-env NODE_ENV=dev nodemon --watch webpack.config.js --exec \"webpack-dev-server --config webpack.config.js --env development\" --progress --colors" ... },
因为篇幅缘由,关于webpack的打包优化将会用另一篇文章介绍,敬请期待~