環境分析
開發環境
- 在開發環境中,我們需要具有強大的、具有實時重新載入(live reloading)、熱模組替換(hot module replacement)能力的 source map(方便開發者除錯程式碼) 和 localhost server(本地伺服器)。
大致如下:
- webpack-dev-server實時過載、熱替換
- 不壓縮程式碼
- css樣式不提取至單獨的檔案中
- 使用sourceMap配置,將原始碼對映會原始檔案(方便除錯)
- 不壓縮html
生產環境
- 在生產環境中,我們的目標則轉向於關注更小的 bundle,更輕量的 source map,以及更優化的資源,以改善載入時間。。
大致如下:
- 不需要實時過載、熱替換
- 壓縮js、css
- css樣式提取至單獨的檔案中
- sourceMap.
- 程式碼分離(optimization)
- 壓縮html
- 資源快取(NamedChunksPlugin、HashedModuleIdsPlugin)
- 清除dist目錄檔案
解決通用程式碼
在兩個環境下,需要把通用的配置合併,所以需要用到 webpack-merge
合併工具,解決程式碼重複的問題。
安裝webpack-merge
npm i webpack-merge -D
複製程式碼
最新目錄、檔案
lesson-05
|- build
|- webpack.base.conf.js // + 通用配置
|- webpack.dev.conf.js // + 開發環境配置
|- webpack.prod.conf.js // + 生產環境
|- node-modules
|- pubilc
|- package.json
|- package-lock.json
|- src
|- print.js // + 用於測試快取
|- favicon.ico // + 網頁icon
複製程式碼
安裝生產環境相關包
npm i cross-env copy-webpack-plugin mini-css-extract-plugin optimize-css-assets-webpack-plugin uglifyjs-webpack-plugin -D
複製程式碼
- cross-env:在命令列中配置環境變數(檢視package.json)
- copy-webpack-plugin:拷貝資源
- mini-css-extract-plugin:單獨提取至css檔案
- optimize-css-assets-webpack-plugin:壓縮css檔案
- uglifyjs-webpack-plugin:壓縮js檔案
通用配置
webpack.base.conf.js
const path = require('path')
const webpack = require('webpack')
const { VueLoaderPlugin } = require('vue-loader')
const resolve = (dir) => path.resolve(__dirname, dir)
const jsonToStr = (json) => JSON.stringify(json)
const isProd = process.env.NODE_ENV === 'production'
module.exports = {
// 入口配置
entry: {
app: ['@babel/polyfill', resolve('../src/main.js')]
},
// 打包輸出配置
output: {
path: resolve('../dist'),
filename: 'bundle.js' // filename是相對於path路徑生成
},
// 引入資源省略字尾、資源別名配置
resolve: {
extensions: ['.js', '.json', '.vue'],
alias: {
'@': resolve('../src')
}
},
// 定義模組規則
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
// 指定目錄去載入babel-loader,提升執行、打包速度
include: [resolve('../src'), resolve('../node_modules/webpack-dev-server/client')],
// 排除目錄,提升執行、打包速度
exclude: file => (
/node_modules/.test(file) &&
!/\.vue\.js/.test(file)
)
},
{
test: /\.(png|svg|jpg|gif)$/,
loader: 'file-loader',
options: {
// 指定生成的目錄
name: 'static/images/[name].[hash:7].[ext]',
},
},
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
// 外掛選項
plugins: [
// 定義環境變數
new webpack.DefinePlugin({
'process.env.NODE_ENV': isProd ? jsonToStr('production') : jsonToStr('development')
}),
new VueLoaderPlugin()
]
}
複製程式碼
開發環境配置
webpack.dev.conf.js
const path = require('path')
const webpack = require('webpack')
const merge = require('webpack-merge')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const baseWebpack = require('./webpack.base.conf')
const resolve = (dir) => path.resolve(__dirname, dir)
module.exports = merge(baseWebpack, {
mode: 'development',
devtool: 'cheap-source-map', // 開啟cheap-source-map模式除錯
// 開啟web伺服器、熱更新
devServer: {
open: true,
hot: true,
port: 3002,
publicPath: '/',
contentBase: resolve("../dist") // 設定dist目錄為伺服器預覽的內容
},
// 定義模組規則
module: {
rules: [
{
test: /\.(css|scss|sass)$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
importLoaders: 1
}
},
{
loader: 'sass-loader',
options: {
implementation: require('dart-sass')
}
},
{
loader: 'postcss-loader'
}
]
}
]
},
// 外掛選項
plugins: [
// html模板、以及相關配置
new HtmlWebpackPlugin({
title: 'Lesson-06',
template: resolve('../public/index.html')
}),
// 熱替換外掛
new webpack.HotModuleReplacementPlugin(),
// 在熱載入時直接返回更新檔名,而不是檔案的id。
new webpack.NamedModulesPlugin()
]
})
複製程式碼
生產環境配置
webpack.prod.conf.js
const path = require('path')
const merge = require('webpack-merge')
const webpack = require('webpack')
const webpackConfig = require('./webpack.base.conf')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const resolve = (dir) => path.resolve(__dirname, dir)
module.exports = merge(webpackConfig, {
mode: 'production',
devtool: false,
// 執行、打包輸出配置
output: {
path: resolve('../dist'),
filename: 'static/js/[name].[chunkhash:8].js'
},
// 壓縮js、css資源、分包
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs', // 打包後生成的js檔名稱
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial' // 只打包初始時依賴的第三方
},
// elementUI選項暫時未使用到(參考elementUI中的配置)
elementUI: {
name: 'chunk-elementUI', // 單獨將 elementUI 拆包
priority: 21, // 權重要大於 libs 和 app 不然會被打包進 libs 或者 app
test: /[\\/]node_modules[\\/]element-ui[\\/]/
},
// commons選項暫時未使用到(參考elementUI中的配置)
commons: {
name: 'chunk-commons',
test: resolve('../src/components'), // 可自定義擴充你的規則
minChunks: 3, // 最小公用次數
priority: 5,
reuseExistingChunk: true
}
}
},
runtimeChunk: 'single',
minimizer: [ // 壓縮js、壓縮css配置
new UglifyJsPlugin({
sourceMap: false,
cache: true,
parallel: true
}),
new OptimizeCSSAssetsPlugin()
]
},
// 定義模組規則
module: {
rules: [
{
test: /\.(scss|sass)$/,
use: [
{
loader: MiniCssExtractPlugin.loader
},
{
loader: 'css-loader',
options: {
importLoaders: 2,
sourceMap: false
}
},
{
loader: 'sass-loader',
options: {
implementation: require('dart-sass'),
sourceMap: false
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: false
}
}
]
},
]
},
// 外掛選項
plugins: [
// 清除上次構建的檔案,清除目錄是基於output出口目錄
new CleanWebpackPlugin(),
// 建立html入口,無需手動引入js、css資源
new HtmlWebpackPlugin({
template: resolve('../public/index.html'),
title: 'Lesson-06',
favicon: resolve('../favicon.ico'),
// 壓縮html
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
}
}),
// 提取至單獨css檔案
new MiniCssExtractPlugin({
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].css'
}),
// 拷貝資源
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../public'),
to: path.resolve(__dirname, '../dist')
}
]),
// 當chunk沒有名字時,保持chunk.id穩定(快取chunk)
new webpack.NamedChunksPlugin(chunk => {
if (chunk.name) {
return chunk.name
}
const modules = Array.from(chunk.modulesIterable)
if (modules.length > 1) {
const hash = require('hash-sum')
const joinedHash = hash(modules.map(m => m.id).join('_'))
let len = 4
const seen = new Set()
while (seen.has(joinedHash.substr(0, len))) len++
seen.add(joinedHash.substr(0, len))
return `chunk-${joinedHash.substr(0, len)}`
} else {
return modules[0].id
}
}),
// 當vender模組沒有變化時,保持module.id穩定(快取vender)
new webpack.HashedModuleIdsPlugin()
]
})
複製程式碼
動態CDN資源配置
在參考別人的配置過程中,發現一個可以在webpack中自定義配置CDN的方式,主要是利用 html-webpack-plugin
外掛的能力,可以新增自定義屬性,將CDN資源連結,配置至此自定義屬性中。通過在 index.html
模板中遍歷屬性來自動生成CDN資源引入。
如下:
webpack.dev.conf.js、webpack.prod.conf.js
...省略
// 外掛選項
plugins: [
// html模板、以及相關配置
new HtmlWebpackPlugin({
title: 'Lesson-06',
template: resolve('../public/index.html'),
// cdn(自定義屬性)載入的資源,不需要手動新增至index.html中,
// 順序按陣列索引載入
cdn: {
css:['https://cdn.bootcss.com/element-ui/2.8.2/theme-chalk/index.css'],
js: [
'https://cdn.bootcss.com/vue/2.6.10/vue.min.js',
'https://cdn.bootcss.com/element-ui/2.8.2/index.js'
]
}
})
]
...省略
複製程式碼
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title><%= htmlWebpackPlugin.options.title %></title>
<!-- import cdn css -->
<% if(htmlWebpackPlugin.options.cdn) {%>
<% for(var css of htmlWebpackPlugin.options.cdn.css) { %>
<link rel="stylesheet" href="<%=css%>">
<% } %>
<% } %>
</head>
<body>
<div id="box"></div>
<!-- import cdn js -->
<% if(htmlWebpackPlugin.options.cdn) {%>
<% for(var js of htmlWebpackPlugin.options.cdn.js) { %>
<script src="<%=js%>"></script>
<% } %>
<% } %>
</body>
</html>
複製程式碼
配置開發、生產環境命令
在package.json的scripts選項中新增,dev、build命令,分別是開發環境、生產環境的命令,
package.json
{
"name": "lesson-06",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"dev": "npx webpack-dev-server --config ./build/webpack.dev.conf.js",
"build": "cross-env NODE_ENV=production npx webpack --config ./build/webpack.prod.conf.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.4.4",
"@babel/polyfill": "^7.4.4",
"@babel/preset-env": "^7.4.4",
"autoprefixer": "^9.5.1",
"babel-loader": "^8.0.5",
"clean-webpack-plugin": "^2.0.1",
"copy-webpack-plugin": "^5.0.3",
"cross-env": "^5.2.0",
"css-loader": "^2.1.1",
"dart-sass": "^1.19.0",
"file-loader": "^3.0.1",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.6.0",
"optimize-css-assets-webpack-plugin": "^5.0.1",
"postcss-loader": "^3.0.0",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
"uglifyjs-webpack-plugin": "^2.1.2",
"vue-loader": "^15.7.0",
"vue-template-compiler": "^2.6.10",
"webpack": "^4.30.0",
"webpack-cli": "^3.3.0",
"webpack-dev-server": "^3.3.1",
"webpack-merge": "^4.2.1"
},
"dependencies": {
"vue": "^2.6.10"
}
}
複製程式碼
完成
配置完成後,可嘗試執行:
- 開發環境
npm run dev
複製程式碼
- 生產環境
npm run build
複製程式碼
如果沒有什麼問題,會分別開啟本地伺服器、生成生產環境的目錄、檔案(根目錄的dist)。否則可以自行前往github進行clone專案檢視原始碼,內有註釋。
專案地址
原始碼地址點選這GitHub