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的打包優化將會用另一篇文章介紹,敬請期待~