在上篇中搭建了
vue的基本腳手架,為了加快腳手架打包項(xiàng)目的構(gòu)建速度和減少打包代碼的體積,下面在上一篇的基礎(chǔ)上添磚加瓦,完成腳手架的優(yōu)化。
腳手架目錄
腳手架是在原有的基礎(chǔ)上構(gòu)建的,所以基本的目錄結(jié)構(gòu)相類似。不過加上了對JS壓縮,CSS代碼壓縮(tree shake),引入了dllPlugin打包不常變動的類庫生成動態(tài)鏈接庫,加入happypack開啟多進(jìn)程打包,加快構(gòu)建的速度,下面也會說到運(yùn)用npm script,通過sftp把打包的文件上傳到部署的目錄,避免手動上傳。
腳手架地址:https://github.com/Harhao/webpack/tree/dev
|-- .gitignore
|-- build
| |-- webpack.base.conf.js
| |-- webpack.dev.conf.js (開發(fā)環(huán)境打包配置)
| |-- webpack.dll.conf.js (生成dll動態(tài)鏈接庫)
| `-- webpack.prod.conf.js(生產(chǎn)環(huán)境打包配置)
|-- config
| `-- index.js (打包配置參數(shù))
|-- dist (生產(chǎn)環(huán)境打包目錄)
| |-- index.html
| |-- precache-manifest.2df00ef5798fdac7218a2cecb8233a17.js(緩存清單)
| |-- service-worker.js(service-worker文件)
| `-- static
| |-- css
| | `-- main_de7bb505.css
| `-- js
| |-- framework_2f05dfbce1b06dd8.js
| |-- framework_2f05dfbce1b06dd8.js.gz (開啟gz壓縮打包結(jié)構(gòu))
| |-- main_2f05dfbce1b06dd8.js
| `-- vendors~main_2f05dfbce1b06dd8.js
|-- dll(開啟dllplugin打包的鏈接庫)
| |-- axios.dll.js
| |-- axios.manifest.json
| |-- framework.dll.js
| `-- framework.manifest.json
|-- package.json
|-- postcss.config.js
|-- public
| `-- index.html
`-- src(開發(fā)主目錄)
|-- App.vue
|-- main.js
|-- router
| `-- index.js
`-- views
|-- admin
| `-- index.vue
`-- login
`-- index.vue
開發(fā)環(huán)境配置
在webpack.dev.conf.js開發(fā)環(huán)境配置文件中,增加了happypack、Dllplugin動態(tài)鏈接。happypack主要是開啟多進(jìn)程打包文件,加快打包構(gòu)建速度。而Dllplugin主要是對一些不常變動的類庫提取打包,在再次編譯打包過程,可以免除再次的打包。
開發(fā)環(huán)境配置文件
下面是webpack.dev.conf.js開發(fā)環(huán)境全瞰,主要增加了DllReferencePlugin、happypack、add-asset-html-webpack-plugin插件。下面對各自的作用詳細(xì)說明。
const path = require("path")
const HappyPack = require("happypack")
const VueLoaderPlugin = require("vue-loader/lib/plugin")
const HtmlWebpackPlugin = require("html-webpack-plugin")
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin')
const AddAssetHtmlWebpackPlugin = require("add-asset-html-webpack-plugin")
const HotModuleReplacementPlugin = require("webpack/lib/HotModuleReplacementPlugin")
const config = require("../config/index")
module.exports = {
mode: "development",
devtool: "cheap-module-eval-source-map",
entry: path.resolve(__dirname, "../src/main.js"),
output: {
filename: "[name]_[hash:16].js",
path: path.resolve(__dirname, "../dist"),
publicPath: '/'
},
resolve: {
modules: ["node_modules"],
alias: {
"@": path.resolve(__dirname, '../src')
},
extensions: [".js", ".json", ".vue", ".scss"]
},
module: {
rules: [{
test: /\.js$/,
use: ["happypack/loader?id=babel"],
exclude: path.resolve(__dirname, '../node_modules')
},
{
test: /\.scss$/,
use: ["style-loader", "css-loader", "postcss-loader", "sass-loader"]
}, {
test: /\.vue$/,
use: ["vue-loader"]
}, {
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: ["happypack/loader?id=url"]
}]
},
plugins: [
new DllReferencePlugin({
manifest: require('../dll/framework.manifest.json')
}),
new DllReferencePlugin({
manifest: require('../dll/axios.manifest.json')
}),
new HappyPack({
id: 'babel',
loaders: ["babel-loader?cacheDirectory"]
}),
new HappyPack({
id: 'url',
loaders: ["url-loader"]
}),
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html"),
filename: 'index.html',
inject: 'body'
}),
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, '../dll/framework.dll.js') // 對應(yīng)的 dll 文件路徑
}),
new AddAssetHtmlWebpackPlugin({
filepath: path.resolve(__dirname, '../dll/axios.dll.js') // 對應(yīng)的 dll 文件路徑
}),
new HotModuleReplacementPlugin()
],
devServer: {
...config.dev.server
},
/**
* 開啟webpack對文件變動的監(jiān)聽
*/
watch: true,
watchOptions: {
ignored: /node_modules/,
aggregateTimeout: 300,
poll: 1000
}
}
DllPlugin動態(tài)鏈接庫
.dll 為后綴的文件,這些文件稱為動態(tài)鏈接庫。提供為其他模塊使用的函數(shù)和數(shù)據(jù)。在一些不常變動的類庫vuejs、vue-router、vuex,我們可以打包成動態(tài)鏈接庫,動態(tài)鏈接庫只需要編譯一次,在之后的構(gòu)建過程中被動態(tài)鏈接庫包含的模塊將不會在重新編譯。加快我們構(gòu)建的速度。配置dll動態(tài)鏈接庫,需要另外一個配置文件webpack.dll.conf.js。
Dllplugin 插件專門用于單獨(dú)的webpack配置中,以創(chuàng)建僅限dll的捆綁包。它創(chuàng)建了一個manifest.json文件,用于[DllReferencePlugin]映射依賴項(xiàng)。下面webpack.dll.conf.js通過配置生成dll動態(tài)鏈接庫。
通過配置npm script生成動態(tài)鏈接庫文件,在package.json定義了npm run build:dll打包生成dll動態(tài)鏈接庫。
生成下面的dll動態(tài)鏈接庫,在運(yùn)行開發(fā)環(huán)境前,需要先打包生成dll動態(tài)鏈接庫,這樣開發(fā)環(huán)境打包時會自動引用動態(tài)鏈接庫。其中framework和axios都是我們自定義命名,menifest是類庫的映射文件。
有動態(tài)鏈接庫,還需要在webpack.dev.conf.js文件中對鏈接庫的引入。DllReferencePlugin 會去 manifest.json 文件讀取 name字段的值, 把值的內(nèi)容作為在從全局變量中獲取動態(tài)鏈接庫中內(nèi)容時的全局變量名。而AddAssetHtmlWebpackPlugin是將JavaScript或CSS資源添加到生成的HTML中,這里使用該插件將dll動態(tài)鏈接庫添加到index.html中。
生成index.html源碼已經(jīng)加入framework.dll.js和axios.dll.js,源碼結(jié)構(gòu)如下所示:
happypack多進(jìn)程打包
HappyPack通過并行轉(zhuǎn)換文件使得初始webpack構(gòu)建更快。happypack通過開啟多個子進(jìn)程并行打包文件,使文件構(gòu)建速度明顯加快。在使用happypack過程中,發(fā)現(xiàn)happypack對scss支持度不高。所以在webpack.dev.conf.js沒有對scss文件進(jìn)行處理。在happypack的官網(wǎng)案例中,happypack對less的支持度比較高。happypack使用比較簡單,如下所示,下面對以js為后綴的文件用happypack進(jìn)行打包。happypack默認(rèn)開啟的進(jìn)程是3個,可以自定義配置,詳細(xì)參數(shù)可以參照官方文檔說明。
開啟happypack成功打包的構(gòu)建流程圖
生產(chǎn)環(huán)境配置
在webpack.prod.conf.js生產(chǎn)配置文件中,添加了workbox-webpack-plugin漸進(jìn)行PWA的資源緩存;optimize-css-assets-webpack-plugin對css代碼壓縮處理;compression-webpack-plugin對大文件進(jìn)行gz壓縮;UglifyJsPlugin對js文件壓縮處理,UglifyJsPlugin在上篇已做說明。
生產(chǎn)環(huán)境配置文件
webpack.prod.conf.js生產(chǎn)配置環(huán)境文件,沒有對不常更改的類庫(vuejs)生成動態(tài)鏈接庫。利用webpack4內(nèi)部提取公共代碼,進(jìn)行了處理。總瞰如下所示:
const path = require("path")
const VueLoaderPlugin = require("vue-loader/lib/plugin")
const HtmlWebpackPlugin = require("html-webpack-plugin")
const { CleanWebpackPlugin } = require("clean-webpack-plugin")
const UglifyJsPlugin = require("uglifyjs-webpack-plugin")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const WorkboxPlugin = require('workbox-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const CompressionWebpackPlugin = require('compression-webpack-plugin')
module.exports = {
mode: "production",
entry: {
main: path.resolve(__dirname, "../src/main.js"),
framework: ["vue", "vue-router", "vuex"]
},
output: {
filename: "static/js/[name]_[hash:16].js",
path: path.resolve(__dirname, "../dist"),
publicPath: './'
},
resolve: {
modules: ["node_modules"],
alias: {
"@": path.resolve(__dirname, '../src')
},
extensions: [".js", ".json", ".vue", ".scss"]
},
module: {
rules: [{
test: /\.js$/,
use: [{
loader: "babel-loader",
options: {
presets: ['@babel/preset-env']
}
}],
exclude: /node_modules/
},
{
test: /\.scss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
"css-loader",
"postcss-loader",
"sass-loader"
],
exclude: /node_modules/
},
{
test: /\.vue$/,
use: ["vue-loader"],
exclude: /node_modules/
}]
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
title: '自搭建webpack腳手架',
template: path.resolve(__dirname, "../public/index.html"),
filename: 'index.html',
inject: 'body',
minify: {
removeComments: true,//移除注釋
collapseWhitespace: true,//移除空白字符串
removeAttributeQuotes: true //移除雙引號
}
}),
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: 'static/css/[name]_[chunkhash:8].css',
chunkFilename: '[id].css',
}),
new WorkboxPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true
}),
new CompressionWebpackPlugin({
filename: '[path].gz[query]',
algorithm: 'gzip',
test: /(\.js$|\.css$)/,
threshold: 10240,
minRatio: 0.8
})
],
optimization: {
usedExports:true,
minimizer: [
new UglifyJsPlugin({
test: /\.js(\?.*)?$/i
}),
new OptimizeCSSAssetsPlugin({
// cssProcessorOptions: true? {map: { inline: false }}:{}
})
],
splitChunks: {
chunks: "all",
minChunks: 1,
minSize: 0,
cacheGroups: {
framework: {
test: "framework",
name: "framework",
enforce: true
}
}
}
}
}
workbox-webpack-plugin
漸進(jìn)式Web應(yīng)用程序(或PWA)使我們開發(fā)的應(yīng)用在離線后,依然可以訪問。主要依靠了Service Workers的Web技術(shù)實(shí)現(xiàn)。在webpack社區(qū)提供了workbox-webpack-plugin方便實(shí)現(xiàn)PWA功能。
在wenbpack.prod.conf.js配置好插件workbox-webpack-plugin后,需要在項(xiàng)目的入口文件里注冊serviceWorker。這里展示的vuejs的入口文件main.js
運(yùn)行生產(chǎn)環(huán)境打包腳本命令,有2個額外的文件正在生成; service-worker.js和precache-manifest.18d731d6b692ffdc910ac5548db2f8c0.js。service-worker.js是Service Worker文件,precache-manifest.18d731d6b692ffdc910ac5548db2f8c0.js是一個service-worker.js需要運(yùn)行的文件。您自己生成的文件可能會有所不同; 但你應(yīng)該有一個service-worker.js文件。
接下來要離線環(huán)境進(jìn)行測試,查看service-worker離線緩存的效果。webpack官網(wǎng)提供了一個http-server對service-worker測試。
service worker已經(jīng)生效,斷開http-server服務(wù),刷新網(wǎng)頁,依舊可以正確顯示到需要的資源。
optimize-css-assets-webpack-plugin
optimize-css-assets-webpack-plugin主要是對打包的css文件進(jìn)行壓縮處理。在webpack4版本中,設(shè)置mode模式為production,會自動對js代碼進(jìn)行壓縮處理,其實(shí)底下是用uglifyjs-webpack-plugin進(jìn)行壓縮。但是css文件還是需要手動進(jìn)行壓縮。而optimize-css-assets-webpack-plugin起的就是壓縮css作用。
打包壓縮的文件如下所示,可見css樣式是壓縮狀態(tài)的。
.entry[data-v-7ba5bd90]{color:red;-webkit-transform:scale(.8);-ms-transform:scale(.8);transform:scale(.8)}.admin[data-v-2ba5fe30]{color:#333}
optimize-css-assets-webpack-plugin在webpack.prod.conf.js配置使用:
postcss
postcss是一個用JavaScript 工具和插件轉(zhuǎn)換 CSS代碼的工具。postcss加上autoprefixer幫助我們在新的css3屬性自動添加廠商前綴。
在package.json文件添加以下字段:
"browserslist": [
"last 5 version",
">1%",
"ie>=8"
]
在項(xiàng)目的根目錄下,創(chuàng)建postcss.config.js,內(nèi)容如下:
module.exports = {
plugins: {
autoprefixer: {
}
}
};
JS tree shake
在webpack4中使用tree shake剔除沒有使用的JS代碼比較簡單,直接在optimization中開啟usedExports,不過JS tree shake只對ES6語法有效,所以要度量使用。關(guān)于對于css的tree shake,有朋友建議是purecss對沒有使用的css代碼剔除,不過實(shí)驗(yàn)了一下,感覺沒有效果,就沒有做推薦。
splitChunks 提取公用代碼
在webpack.dev.conf.js開發(fā)環(huán)境中使用了dllPlugin的動態(tài)鏈接庫方法提取公用的類庫,加快構(gòu)建速度。在生產(chǎn)環(huán)境中,采用的是webpack4內(nèi)置功能提取公用代碼。在webpack3中采取CommonChunkPlugin提取公共模塊和第三方庫。在webpack4已經(jīng)不建議使用該插件,而是使用內(nèi)置的方法。
compression-webpack-plugin
在上面使用splitChunks提取公共模塊framework,但是打包出來的模塊還是比較大,在瀏覽器下載包模塊時候時間會比較長,所以在對打包出來,體積比較大的模塊使用compression-webpack-plugin打包成gz包。配合后端開啟gz支持,這樣可以加快資源下載的速度。
總結(jié)
對常見的優(yōu)化方案做了一次測試和實(shí)踐,如果有其他好的優(yōu)化方法,可以留言分享。