呦吼各位,時隔了很久的我終于開始補(bǔ)全配置webpack的教程了,前兩篇講到了幾個常用的插件和loader,那么大家對webpack也有了一個最基本的認(rèn)識,知道了它是如何配置并正常工作的,那么這篇我將通過vue來具體的想大家說明如何根據(jù)環(huán)境進(jìn)行對應(yīng)的操作。(其實(shí)是我不大想使用vue3.0腳手架生成,但是我又想使用webpack4.0....所以就仿照vue2.x腳手架生成的webpack配置,完成webpack4.0的配置)
- 首先我們需要一些額外的
plugin和loader現(xiàn)在我貼出我的package.json文件;你可以直接復(fù)制下來然后放到package.json文件中也是可以使用的。
{
"name": "learnWebpackOfVue",
"version": "1.0.0",
"description": "這是一個webpack4.0的vue配置",
"scripts": {
"dev": "webpack-dev-server --inline --progress --config webpack/webpack.dev.js",
"start": "npm run dev",
"build": "node webpack/build.js"
},
"author": "Sky <syl1998@hotmail.com>",
"license": "ISC",
"dependencies": {
"vue": "^2.6.10",
"vue-router": "^3.0.7"
},
"devDependencies": {
"@babel/cli": "^7.5.0",
"@babel/core": "^7.5.4",
"@babel/plugin-transform-runtime": "^7.5.0",
"@babel/preset-env": "^7.5.4",
"@babel/runtime": "^7.5.4",
"autoprefixer": "^9.6.1",
"babel-loader": "^8.0.6",
"css-loader":"3.2.0",
"clean-webpack-plugin": "^3.0.0",
"friendly-errors-webpack-plugin": "^1.7.0",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.8.0",
"node-notifier": "^5.4.1",
"node-sass": "^4.12.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"ora": "^3.4.0",
"portfinder": "^1.0.21",
"sass-loader": "^7.1.0",
"terser-webpack-plugin": "^1.3.0",
"url-loader": "^2.0.1",
"vue-loader": "^15.7.0",
"vue-template-compiler": "^2.6.10",
"webpack": "^4.39.3",
"webpack-cli": "^3.3.6",
"webpack-dev-server": "^3.5.1",
"webpack-merge": "^4.2.2"
}
}
- 然后便是文件目錄的結(jié)構(gòu)介紹,既然我們需要根據(jù)環(huán)境進(jìn)行代碼分離,那么我們勢必需要很多的配置文件,現(xiàn)在我貼上我自己的目錄結(jié)構(gòu):
├─config
│ dev.env.js
│ index.js
│ prod.env.js
│
├─dist
│
├─src
│ │ App.vue
│ │ index.html
│ │ main.js
│ │
│ ├─router
│ │ index.js
│ │
│ └─views
│ Helloworld.vue
│
├─webpack
│ build.js
│ utils.js
│ webpack.base.js
│ webpack.dev.js
│ webpack.prod.js
├─.babelrc
├─.postcssrc.js
└─package.json
現(xiàn)在我來介紹下這幾個文件夾,首先是
config文件夾,該文件夾中存儲著抽離出來需要隨時變更的webpack配置信息,總不能我們老是去變更webpack的配置文件,那樣不僅僅會很麻煩而且很容易改錯,那么我們就將需要經(jīng)常變更的變量抽離出來,放到這里,這里分index,dev,prod三個文件,其中index文件中包含著這些配置信息,我們來看一看都寫了什么:
'use strict'
const path = require('path')
module.exports = {
// 對應(yīng)dev環(huán)境的快捷設(shè)置
dev: {
assetsSubDirectory: 'static',
assetsPublicPath: '/',
// 使用webpack進(jìn)行端口代理,一般是用于跨域
proxyTable: {},
// 使用什么devtools
devtool: 'cheap-module-eval-source-map',
// 這是設(shè)置的是局域網(wǎng)和本地都可以訪問
host: '0.0.0.0',
// 端口號
port: 8080,
// 是否自動打開瀏覽器
autoOpenBrowser: false,
errorOverlay: true,
// 是否使用系統(tǒng)提示彈出錯誤簡略信息
notifyOnErrors: true,
poll: false,
},
// 對應(yīng)build環(huán)境的快捷設(shè)置
build: {
// 模板index
index: path.resolve(__dirname, '../dist/index.html'),
// 此處決定打包的文件夾
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
// 針對vue,如果你想通過雙擊index打開你的頁面的話
// 你就需要更改為'./'即可
assetsPublicPath: '/',
/**
* 打包時是否啟用map
*/
productionSourceMap: false,
devtool: '#source-map',
// 此處配置是是否啟動webpack打包檢測你可以通過使用以下命令進(jìn)行啟動
// `npm run build --report`
// 或者你也可以直接設(shè)置true或者false來直接進(jìn)行控制
bundleAnalyzerReport: process.env.npm_config_report,
}
}
而dev和prod文件則則是這樣的,dev.env.js:
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
// 設(shè)置運(yùn)行環(huán)境
NODE_ENV: '"development"',
server_api:'"這里存放著開發(fā)環(huán)境服務(wù)器地址"'
})
prod.env.js:
'use strict'
module.exports = {
NODE_ENV: '"production"',
server_api:'"這里存放著生產(chǎn)環(huán)境服務(wù)器地址"'
}
在這里我們可以直接使用不同的開發(fā)環(huán)境進(jìn)行關(guān)鍵詞設(shè)置,然后這里同樣還能存放服務(wù)器地址,這樣的話就可以不同的環(huán)境連接不同的服務(wù)器,就不需要手動去更改了。但是需要在webpack的插件中新增一個webpack的一個插件,稍后會提到。dist文件夾,就不多提了,打包之后的文件都在這里。src文件夾,我們的源碼存放地點(diǎn),因?yàn)槭褂玫氖莢ue,我就仿照vue2.x的腳手架的目錄結(jié)構(gòu)了,這里大家看著做個參考就好。那么重點(diǎn)就是我們的這個webpack文件夾了,這里,存放了webpack的配置信息,讓我們挨個探索~
// 首先是我們的webpack.base文件
const path = require('path');
const utils = require('./utils')
const config = require('../config')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
function resolve(dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
context: path.resolve(__dirname, '../'),
entry: {
app: './src/main.js'
},
output: {
filename: utils.assetsPath('js/[name].[chunkhash:8].js'),
chunkFilename: utils.assetsPath('js/[name].[chunkhash:8].bundle.js'),
path: config.build.assetsRoot,
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'@': resolve('src'),
}
},
module: {
rules: [{
test: /\.vue$/,
use: 'vue-loader'
},
{
test: /\.js$/,
// ?cacheDirectory=true這個參數(shù),是使用緩存
// 這樣的好處是當(dāng)你的項(xiàng)目非常大的時候,這將提升至少2倍的構(gòu)建速度
loader: 'babel-loader?cacheDirectory=true',
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
},
{
test: /\.(gif|png|jpe?g|svg)$/,
use: [{
loader: 'url-loader',
options: {
limit: 8192,
name: utils.assetsPath('image/[name]-[hash:8].[ext]')
}
}]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [{
loader: 'url-loader',
options: {
limit: 8192,
name: utils.assetsPath('fonts/[name]-[hash:8].[ext]')
}
}]
}
]
},
plugins: [
new VueLoaderPlugin(),
],
};
- 在這里我們將公共部分的loader提取出來進(jìn)行,在這里我們也將字體和js以及圖片文件分離開歸類各個文件夾,而
VueLoaderPlugin這個插件由于vue2.5以后均需要載入所以我就放在這里了。我們再看看剩下的兩個webpack文件。
// webpack.dev.js
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const merge = require('webpack-merge');
const common = require('./webpack.base.js');
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const config = require("../config")
const portfinder = require('portfinder')
const utils = require('./utils')
const dev = require('../config/dev.env')
// 這些配置信息是在config文件夾的index中設(shè)置的
const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)
const options = merge(common, {
devtool: config.dev.devtool,
module: {
rules: [
// 由于這里我可能會用到scss,所以就直接添加了sass-loader用來處理
// 如果你們需要less,stylus預(yù)處理器,就請自行添加吧~規(guī)則在前面有說哦。
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader']
},
{
test: /\.scss/,
use: ['vue-style-loader', 'css-loader', 'sass-loader']
},
]
},
output: {
filename: '[name].js',
},
devServer: {
// 日志等級
clientLogLevel: 'warning',
// 當(dāng)使用history出現(xiàn)404時則自動調(diào)回index頁
historyApiFallback: true,
contentBase: path.join(__dirname, "../dist"),
// 熱加載模式
hot: true,
// 啟用gzip
compress: false,
// 設(shè)置webpackdevServer地址
host: HOST || config.dev.host,
// 設(shè)置webpackdevServer端口
port: PORT || config.dev.port,
// 設(shè)置是否自動打開瀏覽器
open: config.dev.autoOpenBrowser,
// 當(dāng)編譯器出現(xiàn)錯誤時,在全屏覆蓋顯示錯誤位置
overlay: config.dev.errorOverlay
? { warnings: false, errors: true }
: false,
// 從config文件中讀取端口代理設(shè)置
proxy: config.dev.proxyTable,
// 啟用簡潔報錯
quiet: true,
// 啟用監(jiān)聽文件變化
watchOptions: {
poll: config.dev.poll,
}
},
plugins: [
new webpack.DefinePlugin({
'process.env': dev
}),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'src/index.html',
chunksSortMode: 'none',
inject: 'body',
hash: true
}),
new webpack.HotModuleReplacementPlugin()
]
});
// 使用portfinder來檢查端口占用問題,如果發(fā)現(xiàn)端口出現(xiàn)被占用的情況
// 則端口號+1,直到找到可以使用的端口為止,默認(rèn)情況是8000到25565
// 當(dāng)然你還可以自己設(shè)置搜索范圍,詳情請查看:https://www.npmjs.com/package/portfinder
// 這里還使用了FriendlyErrorsPlugin對控制臺輸出信息進(jìn)行簡略友好化輸出。
// 詳情使用請查看:https://www.npmjs.com/package/friendly-errors-webpack-plugin
// 當(dāng)一切準(zhǔn)備就緒就通過Promise.resolve方法拋出處理好的webpack配置信息
// 出現(xiàn)錯誤則是Promise.reject方法拋出錯誤信息供開發(fā)者調(diào)試修改
module.exports = new Promise((resolve, reject) => {
portfinder.basePort = process.env.PORT || config.dev.port
portfinder.getPort((err, port) => {
if (err) {
reject(err)
} else {
process.env.PORT = port
options.devServer.port = port
options.plugins.push(new FriendlyErrorsPlugin({
compilationSuccessInfo: {
messages: [`您的項(xiàng)目已成功啟動`],
notes: [`本機(jī)訪問請在vscode的終端中按住左ctrl鍵點(diǎn)擊: http://127.0.0.1:${port} \n `, `局域網(wǎng)訪問地址: http://${utils.getNetworkIp()}:${port}`],
},
onErrors: config.dev.notifyOnErrors
? utils.createNotifierCallback()
: undefined
}))
resolve(options)
}
})
})
// webpack.prod.js
const path = require('path');
const webpack = require('webpack');
const common = require('./webpack.base.js');
const merge = require('webpack-merge');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const config = require('../config')
const utils = require('./utils')
const env = require('../config/prod.env')
const options = merge(common, {
mode: "production",
devtool: config.build.productionSourceMap ? config.build.devtool : false,
output: {
path: config.build.assetsRoot,
},
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssPlugin.loader,
{
loader: 'css-loader'
},
{
loader: 'postcss-loader',
options: {
// 是否開啟css映射,根據(jù)config文件設(shè)置決定
sourceMap: config.build.productionSourceMap
}
}]
},
{
test: /\.scss$/,
use: [
MiniCssPlugin.loader,
{
loader: 'css-loader'
},
{
loader: 'postcss-loader',
options: {
sourceMap: config.build.productionSourceMap
}
},
'sass-loader']
}
]
},
optimization: {
splitChunks: {
chunks: "async",
cacheGroups: {
vendor: {
// 將第三方模塊提取出來
minSize: 30000,
minChunks: 1,
test: /node_modules/,
chunks: 'initial',
name: 'vendor',
priority: 1
},
commons: {
test: /[\\/]src[\\/]common[\\/]/,
name: 'commons',
minSize: 30000,
minChunks: 3,
chunks: 'initial',
priority: -1,
// 這個配置允許我們使用已經(jīng)存在的代碼塊
reuseExistingChunk: true
}
}
},
runtimeChunk: { name: 'runtime' },
minimizer: [
new TerserPlugin({
test: /\.js(\?.*)?$/i,
// 是否開啟多線程
parallel: true,
// 是否開啟映射
sourceMap: config.build.productionSourceMap,
terserOptions: {
warnings: false,
// 去除打印
compress: {
warnings: false,
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log']
},
// 去除注釋,當(dāng)設(shè)置為true時,會保留注釋
// 當(dāng)然這個默認(rèn)是false
output: {
comments: false,
},
}
}),
new OptimizeCssAssetsPlugin({
assetNameRegExp: /\.css$/g,
cssProcessorOptions: {
safe: true,
autoprefixer: { disable: true },
mergeLonghand: false,
discardComments: {
// 移除css中的注釋
removeAll: true
}
},
canPrint: true
})
]
},
plugins: [
new webpack.DefinePlugin({
'process.env': env
}),
new CleanWebpackPlugin(),
new MiniCssPlugin({
filename: utils.assetsPath('css/[name].css'),
chunkFilename: utils.assetsPath('css/[name].[contenthash:8].css')
}),
new webpack.HashedModuleIdsPlugin(),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'src/index.html',
inject: 'body',
chunksSortMode: 'none',
minify: {
collapseWhitespace: true,
removeComments: true,
minifyJS: true,
minifyCSS: true
}
}),
],
stats: {
// 顯示所有模塊
maxModules: Infinity,
// 顯示模塊為何被引入
reasons: true,
}
});
// 當(dāng)config中對應(yīng)項(xiàng)為true時,啟用打包分析
if (config.build.bundleAnalyzerReport) {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
prodWebpackConf.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = options;
- 那么在這兩個文件中你們都看到了utils文件,而在package.json中打包命令執(zhí)行的卻是一個build.js而不是webpack.prod.js現(xiàn)在,我就來隆重的介紹這兩個工具文件,他們是美化日志輸出的小幫手,即使是敲代碼,我們也要干干凈凈的看log,控制臺要整潔對于錯誤和警告都要一目了然~話不多說,上代碼
// utils.js
'use strict'
const path = require('path')
const packageConfig = require('../package.json')
const config = require('../config')
// 將資源文件合并到config文件中的配置項(xiàng)中
exports.assetsPath = function (_path) {
const assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory
return path.posix.join(assetsSubDirectory, _path)
}
// 當(dāng)出現(xiàn)報錯時,使用node-notifier彈出系統(tǒng)消息彈窗告知出現(xiàn)錯誤
exports.createNotifierCallback = () => {
const notifier = require('node-notifier')
return (severity, errors) => {
if (severity !== 'error') return
const error = errors[0]
const filename = error.file && error.file.split('!').pop()
// 這里是設(shè)置當(dāng)出現(xiàn)錯誤時,給你的,標(biāo)題,錯誤信息,錯誤文件地址,以及圖標(biāo)
notifier.notify({
title: packageConfig.name,
message: severity + ': ' + error.name,
subtitle: filename || '',
icon: path.join(__dirname, 'logo.png')
})
}
}
// 獲取本地局域網(wǎng)ip
// 這里也就是為何我在config文件中設(shè)置了host為0.0.0.0
// 這樣的話除了本地開發(fā)可預(yù)覽,和開發(fā)機(jī)在同一個局域網(wǎng)訪問
// 該計算機(jī)ip和端口一樣可以訪問,靈感來自react的控制臺。
exports.getNetworkIp = () => {
const os = require('os');
let needHost = ''; // 打開的host
try {
// 獲得網(wǎng)絡(luò)接口列表
let network = os.networkInterfaces();
for (let dev in network) {
let iface = network[dev];
for (let i = 0; i < iface.length; i++) {
let alias = iface[i];
if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
needHost = alias.address;
}
}
}
} catch (e) {
needHost = 'localhost';
}
return needHost;
}
// build.js
'use strict'
process.env.NODE_ENV = 'production'
const ora = require('ora')
const rm = require('rimraf')
const path = require('path')
const chalk = require('chalk')
const webpack = require('webpack')
const config = require('../config')
const webpackConfig = require('./webpack.prod')
// 設(shè)置進(jìn)度條文字
const spinner = ora('正在打包...')
// 開啟進(jìn)度條
spinner.start()
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
if (err) throw err
webpack(webpackConfig, (err, stats) => {
// webpack執(zhí)行打包操作之后關(guān)閉進(jìn)度條
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
// 開啟控制臺顏色輸出
colors: true,
// 是否顯示打包了源碼文件
modules: false,
// 如果你是typescript,開啟此項(xiàng)則會導(dǎo)致報錯
children: false,
// 后面這兩個個人感覺和開啟了modules沒太大區(qū)別,只是顯示更加簡略一些
// 是否顯示生成的文件塊
chunks: false,
// 顯示打包的代碼塊來源
chunkModules: false
}) + '\n\n')
// 當(dāng)打包失敗時
if (stats.hasErrors()) {
console.log(chalk.red(' 打包失敗\n'))
process.exit(1)
}
// 當(dāng)打包成功時
console.log(chalk.cyan(' 打包成功\n'))
})
})
- 那么大家看明白了嗎?以上代碼很多都是來自vue2.x腳手架,因?yàn)槲矣X得這些東西比腳手架3.0的控制臺更加直觀明了,但是我還是非常非常的喜歡V3的ui功能,相比控制臺更加的美觀,但是卻不夠方便,現(xiàn)在我們還差兩樣?xùn)|西,在配置文件中我們配置了babel和postcss看過前兩個文章的小伙伴一定清楚,這兩個小家伙同樣也需要配置信息,那么廢話不多說,上代碼!
// .babelrc
{
"presets": [
["@babel/preset-env", {
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}]
],
"plugins": ["@babel/transform-runtime"]
}
此處你們會發(fā)現(xiàn)和上兩個文章均不同,這里要注意啦?。ㄇ煤诎澹┰谶@里我直接升級了babel的版本使用的是版本7,那么以前的那一套就全部被廢棄,現(xiàn)在的env就需要使用上面的配置而無需
babel-preset-stage-x這個插件了,而runtime則也是換成了對應(yīng)的版本。
// .postcssrc.js
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
"plugins": {
"postcss-import": {},
"postcss-url": {},
// to edit target browsers: use "browserslist" field in package.json
"autoprefixer": {}
}
}
postcss則沒有任何變化還是和之前一樣
至此,針對webpack的配置就告一段落了,稍后我會放上我這個示例的最后完成文件,當(dāng)然是針對vue的,因?yàn)樽罱恢币苍趯W(xué)習(xí)vue啦~~~ 如果文檔有落后的地方我會及時更新的。嘻嘻,那么我們webpack5.0見~
附上工程: 提取碼:v50a