概況
- 接觸了vue有一年多,基本上已經(jīng)非常了解其中的用法,但是很多時(shí)候用歸用,涉及到其中的配置以及一些原理知識(shí),心虛得很。所以導(dǎo)致很多東西只看到表面,并不知其中,關(guān)于在vue項(xiàng)目當(dāng)中如何理解webpack配置。
- webpack 是一個(gè)現(xiàn)代 JavaScript 應(yīng)用程序的靜態(tài)模塊打包器(module bundler),也就是說(shuō),webpack能夠使我們的項(xiàng)目代碼模塊化,通過(guò)配置管理項(xiàng)目中的依賴包以及插件等,給我們提供打包壓縮文件等技術(shù)。今天的項(xiàng)目是基于webpack 3.6.0 + vue 2.5.2 來(lái)理解其中的配置。
一、目錄結(jié)構(gòu)
構(gòu)建項(xiàng)目目錄結(jié)構(gòu)如下,不知道怎么構(gòu)建的可以網(wǎng)上查找一下,不再贅述。

其中
build文件夾下面有:build.js、check-versions.js、utils.js、vue-loader.conf.js、webpack.base.conf.js、webpack.dev.conf.js、webpack.prod.conf.js
config文件夾下面有有:dev.env.js、index.js、prod.env.js
這些文件,都是關(guān)于webpack配置的文件。
二、首先理解package.json
{
"name": "openlayer", // 模塊名稱
"version": "1.0.0", // 版本
"description": "A Vue.js project", // 項(xiàng)目描述
"author": "zhufengli", // 作者
"private": true, // 私有
"scripts": { // 指定執(zhí)行命令
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", // 執(zhí)行npm run dev或者npm start的時(shí)候就是執(zhí)行的build文件下面的webpack.dev.conf.js
"start": "npm run dev", // 啟動(dòng)項(xiàng)目命令
"build": "node build/build.js" // build命令
},
"dependencies": { // 配置依賴
"vue": "^2.5.2",
"vue-router": "^3.0.1",
"vuex": "^3.5.1"
},
"devDependencies": { // dev開(kāi)發(fā)環(huán)境配置依賴
"autoprefixer": "^7.1.2",
"babel-core": "^6.22.1",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
},
"engines": { // 指定node和npm版本
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
},
"browserslist": [ // 支持瀏覽器配置
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}
從package的執(zhí)行命令配置中可以知道執(zhí)行的是build文件下的webpack.dev.conf.js
三、build/webpack.dev.conf.js
'use strict'
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge') // 合并文件作用
const path = require('path')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin') // 復(fù)制插件
const HtmlWebpackPlugin = require('html-webpack-plugin') // 配置生成的文件
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') // 友好錯(cuò)誤提示插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin') // 清除文件插件
const portfinder = require('portfinder') // 自動(dòng)獲取端口
const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)
// 合并了build下面的webpack.base.conf.js
const devWebpackConfig = merge(baseWebpackConfig, {
module: {
// 資源管理配置,處理各種文件類型
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
},
// 主要是定位錯(cuò)誤(用于開(kāi)發(fā))
devtool: config.dev.devtool, // 'cheap-module-eval-source-map'
// 這些devServer選項(xiàng)應(yīng)在config/index.js中自定義
devServer: {
clientLogLevel: 'warning',
historyApiFallback: {
rewrites: [
{ from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
],
},
hot: true,
contentBase: false,
compress: true,
host: HOST || config.dev.host, // ip地址
port: PORT || config.dev.port, // 端口
open: config.dev.autoOpenBrowser, // 運(yùn)行npm run dev成功之后自動(dòng)打開(kāi)瀏覽器窗口
overlay: config.dev.errorOverlay
? { warnings: false, errors: true }
: false,
publicPath: config.dev.assetsPublicPath,
proxy: config.dev.proxyTable, // 代理
quiet: true,
watchOptions: {
poll: config.dev.poll,
}
},
// 插件配置
plugins: [
new webpack.DefinePlugin({
'process.env': require('../config/dev.env'),
'process.env.NODE_ENV': 'pro'
}),
new webpack.HotModuleReplacementPlugin(), // 熱更新
new webpack.NamedModulesPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
// 這個(gè)打包本來(lái)配了兩個(gè)文件,發(fā)現(xiàn)沒(méi)效果
new HtmlWebpackPlugin({
title:"openlayer",//它的title為app,在index.html的title中間中加入<%= %>
filename: 'index.html', // 輸出的文件名
template: 'index.html', // 模板文件
inject: true,
minify: {
removeComments: true, // 移除HTML中的注釋
removeScriptTypeAttributes: true, // 刪除script的類型屬性,在h5下面script的type默認(rèn)值:text/javascript 默認(rèn)值false
removeAttributeQuotes: true, // 是否移除屬性的引號(hào) 默認(rèn)false
useShortDoctype: true, // 使用短的文檔類型,默認(rèn)false
decodeEntities: true,
collapseWhitespace: true, // 刪除空白符與換行符
minifyCSS: true // 是否壓縮html里的css(使用clean-css進(jìn)行的壓縮) 默認(rèn)值false
},
hash:true,
chunks:['app']
}),
new HtmlWebpackPlugin({
title:"test",
filename: 'test.html',
template: 'test.html',
hash:true,
inject:true,
chunks:['test']
}),
new CleanWebpackPlugin({
root: path.resolve(__dirname, '..'),
dry: false // 啟用刪除文件
}),
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.dev.assetsSubDirectory,
ignore: ['.*']
}
])
]
})
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
// 將端口添加到devServer配置
devWebpackConfig.devServer.port = port
// 添加 FriendlyErrorsPlugin
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
compilationSuccessInfo: {
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
},
onErrors: config.dev.notifyOnErrors
? utils.createNotifierCallback()
: undefined
}))
resolve(devWebpackConfig)
}
})
})
通過(guò)webpack.dev.conf.js merge合并文件 webpack.base.conf.js 可以知道基礎(chǔ)的配置文件都在這里
四、build/webpack.base.conf.js
'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
context: path.resolve(__dirname, '../'),
// 入口文件配置
entry: {
app: './src/main.js', // 入口文件
test: './src/test.js'
},
// 出口文件配置
output: {
path: config.build.assetsRoot, // 輸出文件路徑
filename: '[name].js', // 輸出文件名
publicPath: process.env.NODE_ENV === 'pro'
? config.build.assetsPublicPath
: config.build.assetsPublicPath // 公共存放路徑
// 為什么用一樣的路勁config.build.assetsPublicPath,下面詳解
},
resolve: {
// 擴(kuò)展文件后綴,這樣在引入文件的時(shí)候可以忽略后綴名
// 如 import 'js/index' js/index.js
extensions: ['.js', '.vue', '.json'],
// 配置別名
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
'src': resolve('src'),
}
},
// 文件處理
module: {
rules: [
// vue文件語(yǔ)法處理
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig // (下面詳解)
},
// 語(yǔ)法處理,會(huì)處理成瀏覽器能夠識(shí)別的ES5語(yǔ)法
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
},
// 圖片處理
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
// 文件處理
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('media/[name].[hash:7].[ext]')
}
},
// 字體處理
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
},
node: {
//防止Webpack注入無(wú)用的setImmediate polyfill。
setImmediate: false,
// 阻止webpack將模擬注入到Node本機(jī)模塊
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
}
}
五、build/webpack.prod.conf.js
生產(chǎn)環(huán)境配置,暫不深入理解,想要了解的小伙伴可以看下這篇文章 webpack.prod.conf.js文件詳解
六、build/vue-loader.conf.js
這個(gè)文件主要是處理vue文件,主要是sass、less用的比較多,這里需要更加正確理解。
'use strict'
const utils = require('./utils')
const config = require('../config')
const isProduction = process.env.NODE_ENV === 'production'
const sourceMapEnabled = isProduction ? config.build.productionSourceMap : config.dev.cssSourceMap
module.exports = {
// css規(guī)則處理,包括sass、less、postcss等
loaders: utils.cssLoaders({
sourceMap: sourceMapEnabled, // 調(diào)式作用
extract: isProduction
}),
cssSourceMap: sourceMapEnabled,
cacheBusting: config.dev.cacheBusting,
// 可以將某些屬性轉(zhuǎn)成require調(diào)用
transformToRequire: {
video: ['src', 'poster'],
source: 'src',
img: 'src',
image: 'xlink:href'
}
}
七、build/utils.js
utils 工具文件,主要作用分為
- 配置導(dǎo)出路徑
- 處理各類loader相關(guān)配置
- 跨平臺(tái)通知系統(tǒng)
'use strict'
const path = require('path')
const config = require('../config')
const ExtractTextPlugin = require('extract-text-webpack-plugin') // 用來(lái)將文本從bundle中提取到一個(gè)單獨(dú)的文件中
const packageConfig = require('../package.json')
// 導(dǎo)出路徑
exports.assetsPath = function (_path) {
const assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory : config.dev.assetsSubDirectory
// 返回一個(gè)完整路徑的相對(duì)根路徑
return path.posix.join(assetsSubDirectory, _path)
}
// 導(dǎo)出cssLoaders相關(guān)配置
exports.cssLoaders = function (options) {
options = options || {}
const cssLoader = {
loader: 'css-loader',
options: {
sourceMap: options.sourceMap
}
}
const postcssLoader = {
loader: 'postcss-loader',
options: {
sourceMap: options.sourceMap
}
}
// 生成用于提取文本插件的加載程序字符串
function generateLoaders (loader, loaderOptions) {
const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
if (loader) {
loaders.push({
loader: loader + '-loader',
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
// 指定該選項(xiàng)時(shí)提取CSS(生產(chǎn)構(gòu)建)
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader'
})
} else {
return ['vue-style-loader'].concat(loaders)
}
}
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
return {
css: generateLoaders(), // 對(duì)應(yīng) vue-style-loader 和 css-loader
postcss: generateLoaders(), // 對(duì)應(yīng) vue-style-loader 和 css-loader
less: generateLoaders('less'), // 對(duì)應(yīng) vue-style-loader 和 less-loader
sass: generateLoaders('sass', { indentedSyntax: true }), // 對(duì)應(yīng) vue-style-loader 和 sass-loader
scss: generateLoaders('sass'), // 對(duì)應(yīng) vue-style-loader 和 sass-loader
stylus: generateLoaders('stylus'), // 對(duì)應(yīng) vue-style-loader 和 stylus-loader
styl: generateLoaders('stylus') // 對(duì)應(yīng) vue-style-loader 和 styl-loader
}
}
// 為獨(dú)立樣式文件生成加載程序(.vue之外)
exports.styleLoaders = function (options) {
const output = []
// 生成的各種css文件的loader對(duì)象
const loaders = exports.cssLoaders(options)
// 把每個(gè)文件的loader提取出來(lái),push到output數(shù)組中
for (const extension in loaders) {
const loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}
// 發(fā)送跨平臺(tái)通知系統(tǒng)
exports.createNotifierCallback = () => {
const notifier = require('node-notifier')
// 出現(xiàn)error時(shí)觸發(fā)
return (severity, errors) => {
if (severity !== 'error') return
const error = errors[0]
const filename = error.file && error.file.split('!').pop()
notifier.notify({
title: packageConfig.name, // 標(biāo)題
message: severity + ': ' + error.name, // 內(nèi)容
subtitle: filename || '', // 短標(biāo)題
icon: path.join(__dirname, 'logo.png') // 圖標(biāo)
})
}
}
關(guān)于sass和less,在我們初始化項(xiàng)目的時(shí)候并沒(méi)有默認(rèn)安裝,而在項(xiàng)目中,基本上都有用到,比如寫(xiě)一個(gè).sass文件或者寫(xiě)一段代碼
<style lang="sass" scoped>
</style>
可能,就會(huì)報(bào)錯(cuò)如下

這時(shí)候你就需要安裝一下node-sass、sass-loader和scss
npm install node-sass
npm install sass-loader
npm install scss
安裝完之后你以為萬(wàn)事大吉了,沒(méi)想到只是從一個(gè)坑掉進(jìn)另一個(gè)坑

這意思是路徑錯(cuò)誤?查找了一番之后原來(lái)是版本過(guò)高,package.json里面可以查看版本10.0.1,卸載,重新裝一個(gè)低版本的7.3.1,這次真的可以了

npm uninstall sass-loader
npm install sass-loader@7.3.1
七、build/build.js和build/check-versions.js
暫不深入理解,想要了解的小伙伴可以看下參考文章 vue-cli腳手架中webpack配置基礎(chǔ)文件詳解
講完build文件夾,還有一個(gè)config文件夾,這個(gè)文件夾主是定義一些變量exports出去給build文件夾下面的文件使用
八、config/dev.env.js
開(kāi)發(fā)環(huán)境配置
'use strict'
module.exports = {
NODE_ENV: '"dev"'
}
九、config/prod.env.js
生產(chǎn)環(huán)境配置
'use strict'
module.exports = {
NODE_ENV: '"pro"'
}
八、config/index.js
定義一些開(kāi)發(fā)/打包需要的變量
'use strict'
// Template version: 1.3.1
// see http://vuejs-templates.github.io/webpack for documentation.
const path = require('path')
module.exports = {
dev: {
assetsSubDirectory: 'static',
assetsPublicPath: '/', // 公共路勁
proxyTable: {},
// 各種開(kāi)發(fā)服務(wù)器設(shè)置
host: 'localhost', // 可以被process.env.HOST覆蓋
port: 8080, //端口, 可以被process.env.PORT覆蓋,如果正在使用端口,則將確定一個(gè)空閑端口
autoOpenBrowser: false, // 運(yùn)行自動(dòng)打開(kāi)瀏覽器
errorOverlay: true, // 錯(cuò)誤提示
notifyOnErrors: true, // 跨平臺(tái)錯(cuò)誤提示
poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
/**
* Source Maps 代碼調(diào)試BUG、錯(cuò)誤等
https://webpack.js.org/configuration/devtool/#development
*/
devtool: 'cheap-module-eval-source-map',
// cheap-module-eval-source-map 開(kāi)發(fā)環(huán)境(dev)推薦使用
// cheap-module-source-map 可以定位生產(chǎn)環(huán)境的代碼報(bào)錯(cuò)
//如果在devtools中調(diào)試vue文件時(shí)遇到問(wèn)題,
//將其設(shè)置為false-可能會(huì)有所幫助
// https://vue-loader.vuejs.org/en/options.html#cachebusting
cacheBusting: true, // 緩存失效
cssSourceMap: true
},
// 打包配置
build: {
index: path.resolve(__dirname, '../dist/index.html'), // 編譯后生成的文件位置
assetsRoot: path.resolve(__dirname, '../dist'), // 打包后存放代碼位置
assetsSubDirectory: 'static', // 靜態(tài)文件夾(js、css、images)
assetsPublicPath: './', // 發(fā)布根目錄
productionSourceMap: true,
devtool: '#source-map',
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
bundleAnalyzerReport: process.env.npm_config_report
}
}
十一、項(xiàng)目遇坑記
為了弄清楚一下項(xiàng)目中使用的插件,以及一些重要的屬性,進(jìn)行的一些屬性試驗(yàn)
1. 關(guān)于npm run build 打包編譯
這里有個(gè)問(wèn)題是關(guān)于輸出公共存放路徑 config.build.assetsPublicPath 在我打包完之后打開(kāi)dist/index.html,頁(yè)面是空白,然后報(bào)錯(cuò)如下

經(jīng)過(guò)一番查找之后,得到結(jié)果是在config/index.js文件下dev和build下面的 assetsPublicPath '/' 修改成 './' ,改完之后發(fā)現(xiàn)npm run dev 的時(shí)候頁(yè)面找不到了,所以為了兼容打包和運(yùn)行,打包的時(shí)候統(tǒng)一使用config.build.assetsPublicPath,而dev下面的assetsPublicPath還是改回原來(lái)的 '/'。


打包好之后的文件在根目錄下面會(huì)生成一個(gè)dist文件夾,里面的結(jié)構(gòu)如下,其中,static主要存放靜態(tài)資源(css、js、images、fonts等)

2. 關(guān)于devtool: 'cheap-module-eval-source-map'
為什么需要這個(gè)東西,主要的作用是幫助我們精準(zhǔn)定位錯(cuò)誤信息,如

我在helloWorld.vue文件調(diào)用了print文件里面的consoleLog函數(shù),函數(shù)內(nèi)容我寫(xiě)的很簡(jiǎn)單
let consoleLog = () =>{
console.log('這是正確的打印');
cosnole.error('這是錯(cuò)誤的打印')
}
export default {
consoleLog
}
如果沒(méi)有devtool: '',那么提示的錯(cuò)誤信息

它雖然告訴你cosnole is not defined,但是并沒(méi)有告訴你在哪個(gè)文件哪一行
但是如果有devtool: 'cheap-module-eval-source-map',他的提示信息可以非常準(zhǔn)確定位到文件以及行位置

3.關(guān)于clean-webpack-plugin 清除文件插件
按著上面插件使用方法,直接報(bào)了一個(gè)不是構(gòu)造函數(shù)的錯(cuò)誤,一臉懵


后來(lái)去看官方文檔引入

使用的時(shí)候查找了很多文章有這類的使用方法
new CleanWebpackPlugin(['dist'],{
root: path.resolve(__dirname, '..'),
dry: false // 啟用刪除文件
}),
果然不行,這不是就是參數(shù)類型傳得不對(duì)嗎

換成下面這種參數(shù)吧,估計(jì)這個(gè)與版本有關(guān),我的是3.0.0
new CleanWebpackPlugin({
root: path.resolve(__dirname, '..'),
dry: false // 啟用刪除文件
}),
clean-webpack-plugin相關(guān)知識(shí)
結(jié)語(yǔ)
很多時(shí)候,好記性真是不如爛筆頭,寫(xiě)了一遍之后基本上知道理解其中得工作原理,以及相關(guān)一些配置,還有一些生產(chǎn)環(huán)境得配置已經(jīng)應(yīng)用還沒(méi)有真正深入理解,后面有時(shí)間有精力一定補(bǔ)上。附上webpack官方文檔,我也只看到【開(kāi)發(fā)】額。。。