前言
上一期從零構(gòu)建了一個基礎(chǔ)版的vue-cli項目,主要介紹了loader的安裝和一些配置項的用法,還給項目添加了less預(yù)處理器
上一期的鏈接-從搭建vue-腳手架到掌握webpack配置(一.基礎(chǔ)配置)
本期開始引入常用的插件實現(xiàn)開發(fā)環(huán)境和生成環(huán)境會用到的一些功能,比如熱插拔、css樣式提取、公共模塊提、取代碼壓縮等等
區(qū)分開發(fā)與生產(chǎn)環(huán)境
很多插件功能是在開發(fā)環(huán)境(development)用到的但是在s生產(chǎn)環(huán)境(production)用不到的,反之亦然。比如
-development用到的
- 熱插拔調(diào)試
- 生成html模板
-production用到的
- 生成html模板
- css樣式提取
- 公共模塊提取
- JavaScript壓縮
- ......
引用官方的說法 ,區(qū)分生產(chǎn)和開發(fā)環(huán)境有兩種方法,如下圖
第二種方法涉及到二次封裝,就像官方vue-cli構(gòu)建的項目一樣,分成了三個配置文件,對目前的我們來說比較復(fù)雜,我們使用第一種方法,設(shè)置環(huán)境變量來區(qū)分部署環(huán)境。
參考vue-cli生成的簡單版工程(webpack-simple),我們發(fā)現(xiàn)npm script寫得有點奇怪
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
}
在運行webpack命令之前運行了 cross-env NODE_ENV=develpoment和production,這就是給環(huán)境變量賦值的過程,但是單單這樣寫是無法執(zhí)行的,我們需要安裝一個插件——cross-env
npm install --save cross-env
這樣我們就可以在之后運行在node環(huán)境的js 文件中訪問到這些環(huán)境變量,通過process.env對象還能拿到package.json里面的配置信息,這就涉及到node的知識了,不多說。
const env = process.env.NODE_ENV
//獲取工程的版本號
const version = process.env.npm_package_version
簡單點寫,把環(huán)境變量的判斷直接放到webpack.config.js文件的最下面
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry:{
app:'./src/main.js'
},
//...
}
/**
* 生成生產(chǎn)代碼的時候才觸發(fā)
*/
if (process.env.NODE_ENV === 'production') {
// http://vue-loader.vuejs.org/en/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
])
}
如果以后額外的配置項越來越多的話,像上面這樣寫是不太好合并配置項的,到最后還是要抽離出另一個js文件裝載新增或重寫的配置項,用webpack-merge中間件合并配置對象。
webpack.DefinePlugin插件是設(shè)置全局常量的插件,要記住!賦值的時候記得寫成'"production"', 官方對DefinePlugin插件 是這么說的
注意,因為這個插件直接執(zhí)行文本替換,給定的值必須包含字符串本身內(nèi)的實際引號。通常,有兩種方式來達到這個效果,使用 '"production"', 或者使用 JSON.stringify('production')。
生成html模板
之前根目錄下index.html要我們自己引入js資源地址,有新的資源都要手動引入,很麻煩,這時候就會用到HtmlWebpackPlugin 插件,按照index.html作為模板在dist目錄下生成帶上所有資源的html 文件。
npm install --save-dev html-webpack-plugin
先通過require引入插件,然后在輸出對象里面添加plugins屬性,數(shù)據(jù)值類型是數(shù)組,數(shù)組成員new [插件]()添加插件就行。每個插件都有自己的配置項和規(guī)范,可以查 npmjs 或者 他們的官方文檔
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')
module.exports = {
entry:{
app:'./src/main.js'
},
output:{
path:path.resolve(__dirname,'./dist'),
filename:"js/[name].js",
},
module:{
rules:[
//...
]
},
plugins:[
new HtmlWebpackPlugin({
filename:'index.html',
title:'vue demo',
template:'./index.html',
})
],
externals:{
'jquery':'window.jQuery'
}
}
說明
-
filename生成的html的文件名,不填就默認是原文件名 -
titletitle標簽的內(nèi)容 -
templatehtml模板地址,這里我們用我上一期建在跟目錄的index.html
這里有前輩對HtmlWebpackPlugin的詳細說明文章
index.html的內(nèi)容要改一改了,因為webpack打包完之后自動添加資源地址到html文件里,所以我們要刪掉原本寫上去的script標簽
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue demo</title>
</head>
<body>
<div id="app">
</div>
<script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.js"></script>
</body>
</html>
有人可能會奇怪,這里為什么加了一個cdn的jQuery,因為我要在這里帶過一個知識點:有時候我們會有用到cdn加速的庫資源,但是不知道怎么在工程中使用。
很簡單我們在html模板中直接引入,然后在webpack.config.js配置中加一項“外部引入”(externals)
// webpack.config.js
externals:{
'jquery':'window.jQuery'
}
//app.vue中引入
import $ from 'jquery'
熱替換
web服務(wù)器
使用熱替換之前當然要先有一個web服務(wù)器環(huán)境啦,安裝webpack-dev-server
npm install --save-dev webpack-dev-server
webpack-dev-server其實是一個獨立的插件,但是webpack內(nèi)置了它的配置項,屬性devServer對應(yīng)的就是它的配置項。
module.exports = {
entry:{
app:'./src/main.js'
},
output:{
path:path.resolve(__dirname,'./dist'),
filename:"js/[name].js",
},
devServer:{
contentBase:"./dist"
}
}
端口地址什么的都默認 http://localhost:8080/ ,就設(shè)置了跟資源目錄地址contentBase。
想更深入的去配置可以看官方文檔 dev-server。我還真沒認真看過,嘻嘻。
熱替換插件
熱替換就是開發(fā)的過程中修改文件內(nèi)容之后不用頻繁刷新頁面,修改會自動同步到瀏覽器中,webpack內(nèi)部已經(jīng)有這份插件了,不用安裝直接都用就可以。在plugins添加一項 new webpack.HotModuleReplacementPlugin()就ok了
plugins:[
new HtmlWebpackPlugin({
filename:'index.html',
title:'vue demo',
template:'./index.html',
}),
new webpack.HotModuleReplacementPlugin()
]
改一下npm scripts
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
}
運行 npm run dev,熱部署搞定
以上是開發(fā)環(huán)境要用到的插件,下面就是生成環(huán)境用到的插件了
css文件和vue內(nèi)樣式提取
如果不提取css樣式,所有的.css文件和vue內(nèi)的style都會以style標簽的形式被添加到頁面的head里面,不利于資源的緩存而且降低了頁面的加載速度。
好的,就用extract-text-webpack-plugin插件吧,老規(guī)矩安裝一下
npm install extract-text-webpack-plugin --save-dev
簡單使用
在使用css相關(guān)loader之前先用本插件過濾一遍
var ExtractTextPlugin = require("extract-text-webpack-plugin")
module.exports = {
// other options...
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
css: ExtractTextPlugin.extract({
use: 'css-loader',
fallback: 'vue-style-loader' // <- 這是vue-loader的依賴
}),
//用了less或者sass的地方都要用上哦
'less': ExtractTextPlugin.extract({
use:[
'css-loader',
'less-loader'
],
fallback:'vue-style-loader'
})
}
}
}
]
},
plugins: [
new ExtractTextPlugin("styles/style.css")
]
}
vue內(nèi)部的style需要先抽取出來,所以要在fallback屬性上添加預(yù)先的加載器 'vue-style-loader','vue-style-loader'是vue-loader自帶的哦,如果運行時報錯的話那就手動install一下他吧。
生成多文件
我一般的習(xí)慣是把外部引入的css文件認為是可以復(fù)用的,而vue內(nèi)的style是每個頁面都不一樣的要另外生成的,所以我建了兩ExtractTextPlugin實例分別抽取樣式到兩個文件里。
const path = require('path')
const webpack = require('webpack')
const ExtractTextPlugin = require("extract-text-webpack-plugin")
const ExtractRootCss = new ExtractTextPlugin({filename:'styles/root.css',allChunks:false});
const ExtractVueCss = new ExtractTextPlugin({filename:'styles/[name]/style.css',allChunks:true});
module.exports = {
//other options...
module:{
rules:[
//...
{
test:/\.css$/,
//這里用的ExtractRootCss
use:ExtractRootCss.extract({
fallback:'style-loader',
use:['css-loader']
})
},
{
test:/\.less$/,
//這里用的ExtractRootCss
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' // <- 這是vue-loader的依賴,所以如果使用npm3,則不需要顯式安裝
}),
//這里用的ExtractVueCss
'less':
ExtractVueCss.extract({
use:[
'css-loader',
'less-loader'
],
fallback:'vue-style-loader'
})
},
}
},
]
},
plugins:[
new HtmlWebpackPlugin({
filename:'index.html',
title:'vue demo',
template:'./index.html',
}),
ExtractRootCss,//填入插件實例,復(fù)用的css
ExtractVueCss,//記得按順序填入,vue內(nèi)的css
new webpack.HotModuleReplacementPlugin(),
]
}
這就是ExtractTextPlugin插件生成多個文件的方法。你也可以按照自己的習(xí)慣去配置。
公共代碼提取
在多頁面或者多入口的時候(entry設(shè)了不只一個),不同的模塊(chunks)會多次引入一樣的資源模塊(module,也就是import引入的js文件),還有vue等庫的代碼,以上這些復(fù)用的代碼最好是可以獨立出來,一方面方便緩存,一方面減少包的體積。
CommonsChunkPlugin插件就是解決這一問題的,它從屬于webpack.optimize對象所以也是不用安裝的。具體使用如下
new webpack.optimize.CommonsChunkPlugin({
name: 'vender',
minChunks:2
})
minChunks參數(shù)可以是number類型,填2 就是說有2個chunk以上用到的公共塊就會被打包的vender.js里面。minChunks也可以傳一個方法,返回值是boolean類型.
(chunk可以簡單理解為entry屬性設(shè)置的入口而生成的整條關(guān)系樹,所以到目前為止本項目也只有一個chunk,就是'app',當然插件生成的vender也是一個chunk。對初學(xué)者來說就這樣理解吧,用多了自然會有概念)
既然只有一個chunk 那就先抽取公用庫中的代碼吧,如vue包中的代碼。把代碼放到生產(chǎn)環(huán)境判斷里面哦~
/*生成生產(chǎn)代碼的時候才觸發(fā)*/
if (process.env.NODE_ENV === 'production') {
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
//抽取從node_modules引入的模塊,如vue
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
}
})
])
}
這是中文文檔上的介紹 commons-chunk-plugin
這是一個好心人總結(jié)的各種配置情況下打包的結(jié)果 https://segmentfault.com/a/1190000006808865
其他插件
源碼映射
因為重構(gòu)和壓縮后的代碼不利于debug,所以我們先要開啟source map功能,在webpack配置里面添加一項devtool,如下
module.exports = {
//entry: ...
devtool: '#eval-source-map'
}
if (process.env.NODE_ENV === 'production') {
module.exports.devtool = '#source-map'
}
eval-source-map是開發(fā)環(huán)境用的源碼映射,source-map是生成環(huán)境用的源碼映射
官方對 devtool的介紹在這里
阮一峰老師對 source map 的介紹在這里
js代碼壓縮
css文件在 build(抽取和裝載)的同時已經(jīng)進行了簡單的壓縮,所以下面主要是對js代碼的壓縮,也就是常常的UglifyJs(丑化js),webpack自帶了UglifyJsPlugin插件,在plugins上啟用就行。
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,//開啟源碼映射
compress: {
warnings: false//去到警告
}
}),
但是以上的用法是webpack1.0遺留下來的,用的舊版的UglifyJs,他的使用說明也在wepack1.0的文檔里。你可以有手動安裝uglifyjs-webpack-plugin,引入最新的UglifyJs
/* npm install -save-dev uglifyjs-webpack-plugin */
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false
}
},
sourceMap: true
})
webpack3.0中文文檔對該插件的說明 在這里
官方文檔的介紹 在這里
webpack1.0遷移插件
loader-options-plugin 和其他插件不同。它的用途是幫助人們從 webpack 1 遷移至 webpack 2。官方說明
new webpack.LoaderOptionsPlugin({
minimize: true
}),
運行構(gòu)建試一下
好了到目前為止大部分會用到的插件都引入到了webpack配置里面,構(gòu)建一下試試。
完整webpack.config.js的代碼在這里 https://pan.baidu.com/s/1jKnDSYa
npm run dev
npm run build
發(fā)現(xiàn)uglifyJs報錯,是因為我們沒有配置babel的翻譯器和編譯規(guī)則,篇幅有限babel的配置說明放到下一期。
解決方法:在根目錄下創(chuàng)建文件.babelrc,內(nèi)容如下
{
"presets": [
["env", {
"modules": false
}]
]
}
安裝babel-preset-env,npm install --save-dev babel-preset-env
然后再build,沒問題了
打包后的目錄結(jié)構(gòu)如下
嘮叨幾句
想要深入了解每個插件的具體用法,定制自己的需求一定要多點去參考文檔和資料。為了方便大家我已經(jīng)在教程中每一個插件的下面給了大量的鏈接,可以說省去了大家百度的時間,突然感覺自己好細心。
官方文檔也不需要全部都看,用到什么看什么,要什么功能配置就重點看那部分就好,等到有時間再簡要的過一遍文檔。
下期預(yù)告
到目前為止,整個工程可以說完全可用了。樣式抽離,公共提取,壓縮都用到了,對比一下
vue init webpack-simple project-name構(gòu)建的簡單工程,會發(fā)現(xiàn)我們比它的功能還完整,有沒有一點成就感呢?
很可惜,沒想到講插件用了這么長的篇幅,還是沒有提到postcss和babel的配置,下一期開始簡要提一下這些,然后我們繼續(xù)優(yōu)化構(gòu)建過程,讓他可以適應(yīng)多入口多頁面的開發(fā)。想要了解以后的內(nèi)容可以關(guān)注哦~~
?。?!文章首發(fā)地址