前言
上一期中我們通過(guò)引入了插件實(shí)現(xiàn)了不少功能——樣式抽離、公共模塊提取、代碼壓縮等等;本期開(kāi)始講講可能會(huì)用到的第三方編譯器的配置和多入口的優(yōu)化。
本期重點(diǎn):postcss和.babelrc配置、多入口多頁(yè)面代碼提取優(yōu)化
往期鏈接:
從搭建vue-腳手架到掌握webpack配置(一.基礎(chǔ)配置)
從搭建vue-腳手架到掌握webpack配置(二.插件與提取)
小小題外話(huà)
本系列文章寫(xiě)到了第三篇,Jason我發(fā)現(xiàn)了這系列的文章有一個(gè)很大的缺陷,文章和前面的文章存在著耦合,可能會(huì)導(dǎo)致知識(shí)點(diǎn)存在線(xiàn)性的關(guān)聯(lián),對(duì)于有基礎(chǔ)的朋友來(lái)說(shuō),又要從第一期開(kāi)始閱讀的話(huà),比較不友好。
所以后面的文章開(kāi)始Jason會(huì)嘗試獨(dú)立知識(shí)點(diǎn),盡量回歸知識(shí)點(diǎn)的運(yùn)用上;在文章的開(kāi)頭也會(huì)說(shuō)明本期的主要內(nèi)容;當(dāng)然一些插件和loader的進(jìn)階使用還是要有基礎(chǔ)的,初學(xué)者還是建議從頭過(guò)一遍。
使用postcss
postcss介紹
postcss官方的GitHub上還有中文的介紹。
PostCSS 是一個(gè)允許使用 JS 插件轉(zhuǎn)換樣式的工具。 這些插件可以檢查(lint)你的 CSS,支持 CSS Variables 和 Mixins, 編譯尚未被瀏覽器廣泛支持的先進(jìn)的 CSS 語(yǔ)法,內(nèi)聯(lián)圖片,以及其它很多優(yōu)秀的功能。
簡(jiǎn)單來(lái)說(shuō)postcss就是一個(gè)css的轉(zhuǎn)換器,有了postcss或許你就不用再用less和sass了,通過(guò)在postcss上添加插件可以組裝出你需要的語(yǔ)法需求和功能(屬性變量,父子嵌套,版本兼容等),在postcss上通常會(huì)用的插件有cssnext、Autoprefixer、postcss-import。甚至可以在postcss上用less或sass編譯器
用法
這里就用添加Autoprefixer(自動(dòng)兼容瀏覽器)為例簡(jiǎn)單講講postcss的配置方法,有幾種配置方法
- 使用配置文件配置 postcss.config.js或.postcssrc.js
- 使用post-loader的時(shí)候通過(guò)options配置項(xiàng)配置
建議是使用配置文件進(jìn)行配置,這樣可以在所有調(diào)用到postcss-loader的地方使用同樣的配置
!值得一提的是其實(shí)vue-loader是默認(rèn)啟用了postcss-loader的,所以vue-loader的官方文檔里面有直接設(shè)置postcss配置項(xiàng)的選項(xiàng)。
所以在有vue-loader的項(xiàng)目里面其實(shí)是不用手動(dòng)安裝npm install -save-dev postcss-loader
的,不是vue項(xiàng)目就裝吧。
只要在css的loaders里面添加vue-style-loader,就會(huì)自動(dòng)啟用postcss了,如下
{
test:/\.less$/,
use:[
'vue-style-loader',
'css-loader',
'less-loader'
]
})
},
{
test:/\.vue$/,
loader:'vue-loader',
options:{
loaders:{
'css': [
'vue-style-loader',
'css-loader',
],
'less': [
'vue-style-loader',
'css-loader',
'less-loader'
]
},
//postcss:{}//vue-loader還自帶了這一選項(xiàng),但是建議用配置文件進(jìn)行配置
}
}
引入 Autoprefixer :
- 需要安裝 autoprefixer插件哦:
npm install --save-dev autoprefixer
- 新建.postcssrc.js文件在跟目錄下,內(nèi)容如下
module.exports = {
"plugins": {
//"postcss-import": {},
// to edit target browsers: use "browserslist" field in package.json
"autoprefixer": {}
}
}
autoprefixer對(duì)應(yīng)的對(duì)象{},是該插件的配置項(xiàng),有需要可以查閱文檔進(jìn)行配置;postcss-import插件是@import是可以引本地的文件,如node_modules內(nèi)的文件,英文好的去看看該插件介紹。既然vue-cli用了postcss-import我們也這樣用吧,記得安裝npm install --save-dev postcss-import
。
autoprefixer會(huì)去檢查package.json里的browserslist 配置項(xiàng)作為兼容瀏覽器版本的范圍
//在package.json上添加這一項(xiàng)
"browserslist": [
"> 5%",
"last 2 versions",
"not ie <= 8"
]
我們這里就簡(jiǎn)單的引入了autoprefixer,沒(méi)有做過(guò)多的配置,如果對(duì)postcss感興趣的話(huà)可以去看看下面文檔
- postcss的github(有常用插件的鏈接和介紹)
- postcss-loader配置說(shuō)明(無(wú)論是用文件配置還是webpack內(nèi)配置都用一樣的配置規(guī)則)
- autoprefixer配置項(xiàng)介紹
- browserslist的介紹
.babelrc配置
在使用es6語(yǔ)法編寫(xiě)js的時(shí)候,我們都會(huì)在webpack上用babel-loader轉(zhuǎn)換js文件,而.bablrc就是babel編譯js時(shí)用的的規(guī)則和插件的配置規(guī)范
像postcss一樣在根目錄下創(chuàng)建名為.babelrc
的文件(沒(méi)有.js后綴)
{
"presets": [
["env", {
"modules": false ,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"stage-3"
]
}
安裝依賴(lài):
npm install --save-dev babel-presets-env babel-presets-stage-0
babel-preset-env,本依賴(lài)插件是設(shè)置編譯環(huán)境的插件,如果直接引入不設(shè)置規(guī)則那實(shí)際它和引入babel-preset-latest
一樣,會(huì)囊括es2015、es2016、es2017。
'modules':false
:設(shè)置模塊引用規(guī)則,可以設(shè)置成"amd" | "umd" | "systemjs" | "commonjs" | false, defaults to "commonjs"
,設(shè)置了false,就是用es6以上默認(rèn)的規(guī)則。
targets.browsers
:設(shè)置兼容的瀏覽器范圍
tage-3依賴(lài),ES7不同階段語(yǔ)法提案的轉(zhuǎn)碼規(guī)則(共有4個(gè)階段),選裝一個(gè)
babel-presets-stage-0 babel-presets-stage-1
babel-presets-stage-2 babel-presets-stage-3
其他
還可以在里面添加插件實(shí)現(xiàn)更多的功能,如添加react的jsx編譯。像官方給出的示例代碼一樣
{
"presets":["env"]
"plugins": ["transform-react-jsx"],
"ignore": [
"foo.js",
"bar/**/*.js"
]
}
畢竟這不是babelrc的教程,更高級(jí)的使用可以去以下的鏈接去深入學(xué)習(xí)。
鏈接
好了,整個(gè)個(gè)系列到目前為止常常會(huì)用到的webpack配置、loader、插件、編譯器都使用過(guò)了。
后面開(kāi)始就要按照需求,改進(jìn)我們的自動(dòng)化構(gòu)建配置了。
多頁(yè)面、多入口
很多用vue或者react工程環(huán)境都會(huì)默認(rèn)是spa(單頁(yè)應(yīng)用),但是業(yè)界上也有很多項(xiàng)目?jī)A向于使用vue等框架的組件化功能,但是并不引入router模塊,而是使用傳統(tǒng)的路由鏈接;尤其是視頻網(wǎng)站、購(gòu)物網(wǎng)站等pc端的網(wǎng)頁(yè)是不會(huì)做成spa的。所以在一個(gè)工程中編輯和生成多個(gè)頁(yè)面是必須的情況。
在src目錄下新建home.js文件(內(nèi)容和main.js一樣就行),在webpack的entry項(xiàng)設(shè)置多個(gè)入口是最方便快捷的方法。
entry:{
app:'./src/main.js',
home:'./src/home.js'
},
output:{
path:path.resolve(__dirname,'./dist'),
filename:"js/[name].js",
},
entry對(duì)應(yīng)不同的頁(yè)面設(shè)置不同的入口文件,output.filename要寫(xiě)成[name]以chunk名命名的形式
看似簡(jiǎn)單,但是我們之前用到公共代碼提取(CommonsChunkPlugin)和css抽離(ExtractTextPlugin)會(huì)把整個(gè)項(xiàng)目中的代碼打包到一起會(huì)影響頁(yè)面的加載。所以我們要改進(jìn)他的邏輯
css分頁(yè)面拆分
這個(gè)很簡(jiǎn)單,只要在實(shí)例化ExtractTextPlugin插件的同時(shí)添加一個(gè)allchunks:true
參數(shù)就可以了,還有就是把輸出都指向一個(gè)css文件,用``[name]```區(qū)分chunk名
const ExtractVueCss = new ExtractTextPlugin({filename:'styles/[name]-style.css',allChunks:true});
按照我的習(xí)慣,先分出一份公共樣式(root.css),其他的按chunk的數(shù)量進(jìn)行分割,所以root.css的allchunks:false
,其他的true
。
const path = require('path')
const webpack = require('webpack')
const ExtractTextPlugin = require("extract-text-webpack-plugin")
const ExtractRootCss = new ExtractTextPlugin({filename:'styles/[name]-root.css',allChunks:false});
const ExtractVueCss = new ExtractTextPlugin({filename:'styles/[name]-style.css',allChunks:true});
module.exports = {
//other options...
module:{
rules:[
//...
{
test:/\.css$/,
//這里用的ExtractRootCss,輸出到root.css
use:ExtractRootCss.extract({
fallback:'style-loader',
use:['css-loader']
})
},
{
test:/\.less$/,
//這里用的ExtractRootCss,輸出到root.css
use:ExtractRootCss.extract({
fallback:'style-loader',
use:[
'css-loader',
'less-loader'
]
})
},
{
test:/\.vue$/,
loader:'vue-loader',
options:{
loaders:{
//這里用的ExtractVueCss
'css': ExtractVueCss.extract({
use: 'css-loader',
fallback: 'vue-style-loader'
}),
//這里用的ExtractVueCss
'less':
ExtractVueCss.extract({
use:[
'css-loader',
'less-loader'
],
fallback:'vue-style-loader'
})
},
}
},
]
},
plugins:[
//填入插件實(shí)例,記得按順序填入
ExtractRootCss,//root.css
ExtractVueCss,//vue內(nèi)的css
new webpack.HotModuleReplacementPlugin(),
]
}
多頁(yè)面公共提取
不懂commons-chunk-plugin,又不想看以前的文章的話(huà)看左邊鏈接
之前我們把node_modules內(nèi)的模塊抽取到了vender.js里面。
//抽取從node_modules引入的模塊,如vue,vue-router
new webpack.optimize.CommonsChunkPlugin({
name: 'vender',
minChunks:function(module,count){
var sPath = module.resource;
// console.log(sPath,count);
//匹配 node_modules文件目錄
return sPath &&
/\.js$/.test(sPath) &&
sPath.indexOf(
path.join(__dirname, 'node_modules')
) === 0
}
}),
上上面設(shè)置了多個(gè)入口chunk,而且output的時(shí)候也不是設(shè)置成唯一的文件名,這樣webpack打包的時(shí)候會(huì)自動(dòng)按照入口chunk的數(shù)量生成相應(yīng)數(shù)量的代碼包,按目前情況會(huì)有app.js和home.js。
但是問(wèn)題在于每個(gè)chunk都會(huì)把所有他們用到的模塊單獨(dú)打包起來(lái)。比如app和home用到一樣的header.vue模塊,webpack都會(huì)分別打包到app.js和home.js里面。多次復(fù)用的模塊最好是可以抽取出來(lái),在首頁(yè)加載過(guò)js文件之后得到了緩存,在詳情頁(yè)能馬上得到提升頁(yè)面加載速度。
為了解決以上問(wèn)題,我們?cè)偌右粋€(gè)公共代碼提取的實(shí)例
new webpack.optimize.CommonsChunkPlugin({
name:'common'
minChunks:2
}),
這樣每個(gè)頁(yè)面(入口chunk)中引入超過(guò)兩次的模塊就會(huì)打包到common.js文件下面。
minChunks
:2
代表所有chunk中復(fù)用超過(guò)2以上的模塊會(huì)被提取。寫(xiě)成2粒度最小,你可以按自己需求修改。
(5.20更新) 實(shí)際項(xiàng)目上發(fā)現(xiàn),單一入口的時(shí)候,vendor.js并沒(méi)有被抽離,原因可能是單一入口時(shí)common沒(méi)被提前導(dǎo)致的(具體原因請(qǐng)大神指教),所以我們要再做一步入口數(shù)量判斷
Object.keys(config.page).length >= 2
? new webpack.optimize.CommonsChunkPlugin({
name: 'common',
minChunks:2
}):()=>{},
抽取webpack的運(yùn)行時(shí)邏輯
參考vue-cli,我們會(huì)認(rèn)為webpack的加載調(diào)度等運(yùn)行時(shí)(runtime)邏輯是不會(huì)頻繁修改的,所以我們把這部分抽離出來(lái)方面以后的頁(yè)面都調(diào)用它。這樣的話(huà)我們?cè)诩右粋€(gè)CommonsChunkPlugin實(shí)例,用于抽取這些邏輯。
new webpack.optimize.CommonsChunkPlugin({
name:'common'
minChunks:2
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vender',
minChunks:function(module,count){
var sPath = module.resource;
return sPath &&
/\.js$/.test(sPath) &&
sPath.indexOf(
path.join(__dirname, 'node_modules')
) === 0
}
}),
//將webpack runtime 和一些復(fù)用部分抽取出來(lái)
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks:Infinity
}),
!注意!這里插件是有引入順序的,順序不對(duì)可能會(huì)導(dǎo)致操作被覆蓋。
minChunks
:Infinity
什么chunk都不抽取出來(lái),只抽取webpack的runtime等邏輯。
抽取異步公共模塊
在開(kāi)發(fā)vue或者react的時(shí)候可能會(huì)用到異步加載模塊的能力(又叫懶加載),比如import()
和webpack require.ensure功能異步加載的模塊。vue項(xiàng)目通常是vue-router懶加載組件的時(shí)候用到。
雖然做多頁(yè)開(kāi)發(fā)的時(shí)候不一定會(huì)用到vue-router,但是我們讓構(gòu)建配置更健壯那就適配到spa的情況,把異步公共模塊的提取也加進(jìn)去
// 放到上面三個(gè)CommonsChunkPlugin的最后
new webpack.optimize.CommonsChunkPlugin({
// names: ["app", "subPageA"]
// (選擇 chunks,或者忽略該項(xiàng)設(shè)置以選擇全部 chunks)
async: 'vendor-async',
children: true,
minChunks:2
}),
沒(méi)有給name的話(huà)就會(huì)默認(rèn)選擇所有入口chunk。
async
:可以使true
或者字符串,字符串的話(huà)就是生成的公共chunk的名字。(這個(gè)選項(xiàng)一直沒(méi)搞懂,直到看到了這文章Webpack 大法之 Code Splitting )
children
:選擇所有被選 chunks 的子 chunks
minChunks
:大于等于兩個(gè)chunk復(fù)用的子模塊會(huì)提取到該公共chunk
中文文檔下面的示例有介紹 link
改寫(xiě)生成的html模板
同樣,懶得看上期文章,又不懂HtmlWebpackPlugin的同學(xué)點(diǎn)這里
HtmlWebpackPlugin插件在上一期中用到了,但是我們只是簡(jiǎn)單的設(shè)置了模板文件和出口文件。插件會(huì)默認(rèn)把所有輸出的chunk包和css文件都引入到生成的模板中。我們?nèi)缦滦薷囊幌虏寮呐渲?/p>
plugins:[
new HtmlWebpackPlugin({
filename:'index.html',
title:'vue demo',
// favicon:'./src/images/logo.png',
template:'./index.html',
chunks:['app','vender','manifest','common'],
chunksSortMode: 'dependency'
}),
new HtmlWebpackPlugin({
filename:'home.html',
title:'vue home',
template:'./index.html',
chunks:['home','vender','manifest','common'],
chunksSortMode: 'dependency'
}),
]
filename
:生成的文件名,區(qū)分頁(yè)面起對(duì)應(yīng)的名字
template
:html模板來(lái)源,應(yīng)為是vue 項(xiàng)目所以用同樣的模板,你可以按自己的需要來(lái)設(shè)置
chunks
:關(guān)鍵的來(lái)了,這個(gè)就是把本模板關(guān)聯(lián)的chunks列舉出來(lái)的參數(shù),我把各種的入口chunk和提取出來(lái)的公共chunk填入了。
chunksSortMode
:chunk的引入順序,'dependency'
按依賴(lài)關(guān)系映入
build一下
運(yùn)行npm run build
,生成的dist文件目錄結(jié)構(gòu)如下
并沒(méi)有成產(chǎn)vendor-async.js異步公共模塊是因?yàn)轫?xiàng)目中還沒(méi)有用到異步加載的部分
到目前為止完整的webpack.config.js文件可以到這 下載
ps
Jason建議看別人的webpack配置時(shí)遇到不懂的插件多多查 npmjs 或者社區(qū)論壇,有時(shí)間看看去webpack官方介紹的插件或許里面有有你需要的
Jason水平有限,如果有什么地方的知識(shí)點(diǎn)有錯(cuò)誤請(qǐng)大家多多提點(diǎn),在評(píng)論中告訴我。
下期預(yù)告
上一期的webpack配置完后完全可以應(yīng)付單頁(yè)應(yīng)用的構(gòu)建,而這一期還適應(yīng)了多頁(yè)面的構(gòu)建需求,而且還學(xué)會(huì)了postcss和babelrc的基礎(chǔ)配置方法。是不是感覺(jué)越來(lái)越接近vue-cli創(chuàng)建的項(xiàng)目了呢?
引入了多頁(yè)面的構(gòu)建思想后,我們發(fā)現(xiàn)如果我們的頁(yè)面不斷的增加就要不斷的給webpack.config.js添加插件和邏輯。所以為了方便和易用性,下一期我們來(lái)嘗試寫(xiě)一些封裝邏輯把構(gòu)建配置封裝起來(lái),用一個(gè)文件整合常用的配置項(xiàng)統(tǒng)一對(duì)工程進(jìn)行配置。
下一期可能不會(huì)很快能更新,一方面因?yàn)槭稚嫌惺虑橐Γ硪环矫嬲?xiàng)目我還沒(méi)有封裝測(cè)試好(這也正是為什么一直沒(méi)有給出github的原因),所以請(qǐng)有關(guān)注本系列的同學(xué)可能要等等了,有需要的同學(xué)也可以關(guān)注我的賬號(hào)留意更新。