Webpack從2015年9月第一個版本橫空初始至今已逾2載泛豪。它的出現(xiàn)寄雀,顛覆了一大批主流構(gòu)建如Ant莱坎、Grunt和Gulp等等玫氢。騰訊NOW直播IVWEB團隊之前一直采用Fis構(gòu)建倔撞,本篇文章主要介紹從Fis遷移到webpack遇到的問題和背后的黑科技讲仰,內(nèi)容包括inline-resource、多頁面構(gòu)建痪蝇、資源壓縮鄙陡、文件hash、文件目錄規(guī)則等等躏啰。
為什么要遷移至webpack?
有兩個層面的原因:
- 首先webpack的社區(qū)生態(tài)火爆趁矾,插件齊全并且維護更新的很頻繁,遇到了問題给僵,比較容易解決毫捣。
- webpack里面有happypack多實例構(gòu)建方案、code spliting按需加載文件等方案, 可以有效的進行打包構(gòu)建持續(xù)優(yōu)化, 這些在Fis里面是缺少的帝际。
區(qū)分構(gòu)建的開發(fā)or生產(chǎn)環(huán)境蔓同?
"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中注入環(huán)境變量的方式,注入NODE_ENV=dev代表開發(fā)環(huán)境蹲诀,默認(rèn)為生產(chǎn)環(huán)境斑粱。這里使用cross-env的原因是:windows下 在package.json中直接使用 NODE_ENV=dev 不生效,需寫成 set NODE_ENV=dev脯爪,cross-env的寫法兼容各個操作系統(tǒng)则北。
資源內(nèi)聯(lián) (inline-resource)
inline-resource的好處是可以減少css,js等的請求數(shù)蹋宦,同時html加載的時候即可同時加載了這些內(nèi)聯(lián)的css、js等靜態(tài)資源咒锻,可以有效的減少白屏或者頁面閃動的問題冷冗。
這里的內(nèi)聯(lián)分為2種,一種是靜態(tài)的html片段,css,js等惑艇,這些資源一開始就存在項目的某個目錄下蒿辙;另一種是構(gòu)建過程中動態(tài)生成的css,js文件。
對于html的內(nèi)聯(lián)滨巴,寫法如下:
${require('raw-loader!../src/assets/inline/meta.html')}
對于js的內(nèi)聯(lián)思灌,需要增加babel-loader將ES6的語法進行轉(zhuǎn)換,避免瀏覽器直接解析導(dǎo)致報錯恭取。寫法如下:
<script>${require('raw-loader!babel-loader!../src/node_modules/@tencent/report-whitelist/lib/index.js')}</script>
說明:不能將html-loader和html-webpack-plugin同時使用泰偿,html-loader會導(dǎo)致默認(rèn)的ejs模板引擎語法解析實效,造成 ${} 和 <% = %>等語法不生效
上面講述了如何內(nèi)聯(lián)靜態(tài)的資源文件蜈垮,那么如何內(nèi)聯(lián)構(gòu)建過程中動態(tài)生成的資源文件呢耗跛?這里需要借助html-webpack-inline-source-plugin來增強html-webpack-plugin的功能。比如:將構(gòu)建過程中生成的css文件inline到html模板里面去攒发。
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屬性來匹配哪些文件需要動態(tài)的內(nèi)聯(lián)進html模板文件中了羔砾。
多頁面構(gòu)建
多頁面構(gòu)建,或者稱為通配(wildcards)構(gòu)建偶妖。即需要構(gòu)建的頁面數(shù)量是不確定的姜凄,可能A業(yè)務(wù)有3張頁面,B業(yè)務(wù)有5張頁面趾访。因此态秧,我們不能把entry寫死了:
entry: {
activity: './src/pages/activity/init.js', // 深海尋寶活動首頁
my-reward: './src/pages/my-reward/init.js', // 我的獎勵
exchange: './src/pages/exchange/init.js' // 線下兌換獎品
},
為什么上面的寫法不可取呢?很明顯:上面的寫法把entry寫死了腹缩,這并不通用屿聋。后面如果產(chǎn)品需求發(fā)生改變,需要新增一張頁面藏鹊,就需要手動修改構(gòu)建腳本。我們需要的entry是:'./src/pages/**/init.js'转锈,它能夠像一些linux的命令盘寡,具備匹配某個規(guī)則的所有結(jié)果的能力。這里的思路是借助glob撮慨,達到動態(tài)entry的目的竿痰。
entry: glob.sync('./src/pages/**/init.js'),
在webpack構(gòu)建中脆粥,一個頁面需要一個與之對應(yīng)的HtmlWebpackPlugin實例,N個頁面需要N個與之對應(yīng)的HtmlWebpackPlugin影涉。此處需要動態(tài)的設(shè)置HtmlWebpackPlugin的實例個數(shù)变隔。
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、css和js壓縮
對于html文件里面的內(nèi)容壓縮可以通過給html-webpack-plugin設(shè)置minify參數(shù)蟹倾,html-webpack-plugin的壓縮配置其實是直接集成了 html-minifier匣缘,因此minify的參數(shù)設(shè)置可以直接參考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, // 僅壓縮內(nèi)聯(lián)在html里面的js
minifyCSS: true, // 僅壓縮內(nèi)聯(lián)在html里面的css
html5: true, // 以html5的文檔格式解析html的模板文件
removeComments: false, // 不刪除Html文件里面的注釋
collapseWhitespace: true, // 刪除空格
preserveLineBreaks: false // 刪除換行
}
})
);
設(shè)置了上面的minify參數(shù)后鲜棠,看到生成的html文件的內(nèi)容全部在1行上肌厨,需要注意的是:minifyJS和minifyCSS只會壓縮內(nèi)聯(lián)在這個html文件的css和js內(nèi)容,對于單獨的css文件和js文件并不會壓縮豁陆。 那么打包出來的css和js文件如何壓縮呢柑爸?
對于css文件壓縮,直接開啟css-loader的壓縮參數(shù)參數(shù)minimize為true即可:
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: "css-loader",
options: { // 設(shè)置css-loader的minimize參數(shù)為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 內(nèi)置的 UglifyJsPlugin:
const webpack = require('webpack');
plugins: [
...
new webpack.optimize.UglifyJsPlugin(),
...
],
文件Hash
每次功能發(fā)布上線,都需要重新構(gòu)建一次源代碼祥诽,生成一個新的文件版本列表进胯。此處文件Hash的方式就是一種版本管理的方式,發(fā)布時替換有變化的版本的文件原押,達到增量更新的效果胁镐。此處Hash策略是:根據(jù)文件內(nèi)容進行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
}
}
},
...
]
多終端適配
開發(fā)過程中盯漂,不同分辨率的瀏覽器適配是個讓前端開發(fā)者頭疼的問題。手淘的rem方案完美解決了這個問題笨农,它的核心思想是頁面加載時動態(tài)設(shè)置body的font-size值和rem和px轉(zhuǎn)換的單位就缆。
為了不改變編程習(xí)慣,開發(fā)過程中仍然使用px單位來作為基礎(chǔ)布局長度單位谒亦,以750px寬度的視覺稿作為基準(zhǔn)竭宰,設(shè)置rem和px的轉(zhuǎn)換單位為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锁摔,并且設(shè)置rem單位為75px
options: {
remUnit: 75
}
},
{
loader: "sass-loader"
}
]
})
},
其它feature
- 開發(fā)環(huán)境支持WDS: webpack3.x版本自帶webpack-dev-server廓旬,開發(fā)環(huán)境中開啟WDS。這樣依賴的文件發(fā)生變化后谐腰,會自動增量構(gòu)建并且刷新瀏覽器
- 支持HMR: webpack.config.js文件內(nèi)容變化后孕豹,會觸發(fā)熱更新邏輯涩盾,此處通過nodemon來守護webpack的構(gòu)建進程,eg:
"scripts": { "dev": "cross-env NODE_ENV=dev nodemon --watch webpack.config.js --exec \"webpack-dev-server --config webpack.config.js --env development\" --progress --colors" ... },
由于篇幅原因励背,關(guān)于webpack的打包優(yōu)化將會用另外一篇文章介紹春霍,敬請期待~
參考文檔
- webpack 官方文檔
- 一本介紹webpack比較全面的教程
- html-webpack-plugin文檔
- Wildcards in entry points
- BrowserslistError: Unknown version 55 of and_chr
打個廣告
- 騰訊NOW直播工程化解決方案feflow正式開源啦~ 集項目初始化、開發(fā)構(gòu)建叶眉、代碼規(guī)范址儒、代碼發(fā)布 于一身的工具。