概況
- 接觸了vue有一年多卖词,基本上已經(jīng)非常了解其中的用法熬丧,但是很多時候用歸用,涉及到其中的配置以及一些原理知識掸刊,心虛得很乃摹。所以導致很多東西只看到表面禁漓,并不知其中,關(guān)于在vue項目當中如何理解webpack配置孵睬。
- webpack 是一個現(xiàn)代 JavaScript 應用程序的靜態(tài)模塊打包器(module bundler)播歼,也就是說,webpack能夠使我們的項目代碼模塊化掰读,通過配置管理項目中的依賴包以及插件等秘狞,給我們提供打包壓縮文件等技術(shù)。今天的項目是基于webpack 3.6.0 + vue 2.5.2 來理解其中的配置蹈集。
一烁试、目錄結(jié)構(gòu)
構(gòu)建項目目錄結(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", // 項目描述
"author": "zhufengli", // 作者
"private": true, // 私有
"scripts": { // 指定執(zhí)行命令
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", // 執(zhí)行npm run dev或者npm start的時候就是執(zhí)行的build文件下面的webpack.dev.conf.js
"start": "npm run dev", // 啟動項目命令
"build": "node build/build.js" // build命令
},
"dependencies": { // 配置依賴
"vue": "^2.5.2",
"vue-router": "^3.0.1",
"vuex": "^3.5.1"
},
"devDependencies": { // dev開發(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') // 復制插件
const HtmlWebpackPlugin = require('html-webpack-plugin') // 配置生成的文件
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') // 友好錯誤提示插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin') // 清除文件插件
const portfinder = require('portfinder') // 自動獲取端口
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 })
},
// 主要是定位錯誤(用于開發(fā))
devtool: config.dev.devtool, // 'cheap-module-eval-source-map'
// 這些devServer選項應在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, // 運行npm run dev成功之后自動打開瀏覽器窗口
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(),
// 這個打包本來配了兩個文件裆蒸,發(fā)現(xiàn)沒效果
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默認值:text/javascript 默認值false
removeAttributeQuotes: true, // 是否移除屬性的引號 默認false
useShortDoctype: true, // 使用短的文檔類型僚祷,默認false
decodeEntities: true,
collapseWhitespace: true, // 刪除空白符與換行符
minifyCSS: true // 是否壓縮html里的css(使用clean-css進行的壓縮) 默認值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)
}
})
})
通過webpack.dev.conf.js merge合并文件 webpack.base.conf.js 可以知道基礎的配置文件都在這里
四佛致、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: {
// 擴展文件后綴辙谜,這樣在引入文件的時候可以忽略后綴名
// 如 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文件語法處理
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig // (下面詳解)
},
// 語法處理俺榆,會處理成瀏覽器能夠識別的ES5語法
{
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注入無用的setImmediate polyfill。
setImmediate: false,
// 阻止webpack將模擬注入到Node本機模塊
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
這個文件主要是處理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 工具文件凸郑,主要作用分為
- 配置導出路徑
- 處理各類loader相關(guān)配置
- 跨平臺通知系統(tǒng)
'use strict'
const path = require('path')
const config = require('../config')
const ExtractTextPlugin = require('extract-text-webpack-plugin') // 用來將文本從bundle中提取到一個單獨的文件中
const packageConfig = require('../package.json')
// 導出路徑
exports.assetsPath = function (_path) {
const assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory : config.dev.assetsSubDirectory
// 返回一個完整路徑的相對根路徑
return path.posix.join(assetsSubDirectory, _path)
}
// 導出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
})
})
}
// 指定該選項時提取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(), // 對應 vue-style-loader 和 css-loader
postcss: generateLoaders(), // 對應 vue-style-loader 和 css-loader
less: generateLoaders('less'), // 對應 vue-style-loader 和 less-loader
sass: generateLoaders('sass', { indentedSyntax: true }), // 對應 vue-style-loader 和 sass-loader
scss: generateLoaders('sass'), // 對應 vue-style-loader 和 sass-loader
stylus: generateLoaders('stylus'), // 對應 vue-style-loader 和 stylus-loader
styl: generateLoaders('stylus') // 對應 vue-style-loader 和 styl-loader
}
}
// 為獨立樣式文件生成加載程序(.vue之外)
exports.styleLoaders = function (options) {
const output = []
// 生成的各種css文件的loader對象
const loaders = exports.cssLoaders(options)
// 把每個文件的loader提取出來裳食,push到output數(shù)組中
for (const extension in loaders) {
const loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}
// 發(fā)送跨平臺通知系統(tǒng)
exports.createNotifierCallback = () => {
const notifier = require('node-notifier')
// 出現(xiàn)error時觸發(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, // 標題
message: severity + ': ' + error.name, // 內(nèi)容
subtitle: filename || '', // 短標題
icon: path.join(__dirname, 'logo.png') // 圖標
})
}
}
關(guān)于sass和less,在我們初始化項目的時候并沒有默認安裝芙沥,而在項目中胞谈,基本上都有用到,比如寫一個.sass文件或者寫一段代碼
<style lang="sass" scoped>
</style>
可能憨愉,就會報錯如下
這時候你就需要安裝一下node-sass烦绳、sass-loader和scss
npm install node-sass
npm install sass-loader
npm install scss
安裝完之后你以為萬事大吉了,沒想到只是從一個坑掉進另一個坑
這意思是路徑錯誤配紫?查找了一番之后原來是版本過高径密,package.json里面可以查看版本10.0.1,卸載躺孝,重新裝一個低版本的7.3.1享扔,這次真的可以了
npm uninstall sass-loader
npm install sass-loader@7.3.1
七底桂、build/build.js和build/check-versions.js
暫不深入理解,想要了解的小伙伴可以看下參考文章 vue-cli腳手架中webpack配置基礎文件詳解
講完build文件夾惧眠,還有一個config文件夾籽懦,這個文件夾主是定義一些變量exports出去給build文件夾下面的文件使用
八、config/dev.env.js
開發(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
定義一些開發(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: {},
// 各種開發(fā)服務器設置
host: 'localhost', // 可以被process.env.HOST覆蓋
port: 8080, //端口, 可以被process.env.PORT覆蓋秀存,如果正在使用端口捶码,則將確定一個空閑端口
autoOpenBrowser: false, // 運行自動打開瀏覽器
errorOverlay: true, // 錯誤提示
notifyOnErrors: true, // 跨平臺錯誤提示
poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
/**
* Source Maps 代碼調(diào)試BUG、錯誤等
https://webpack.js.org/configuration/devtool/#development
*/
devtool: 'cheap-module-eval-source-map',
// cheap-module-eval-source-map 開發(fā)環(huán)境(dev)推薦使用
// cheap-module-source-map 可以定位生產(chǎn)環(huán)境的代碼報錯
//如果在devtools中調(diào)試vue文件時遇到問題或链,
//將其設置為false-可能會有所幫助
// 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
}
}
十一澳盐、項目遇坑記
為了弄清楚一下項目中使用的插件祈纯,以及一些重要的屬性,進行的一些屬性試驗
1. 關(guān)于npm run build 打包編譯
這里有個問題是關(guān)于輸出公共存放路徑 config.build.assetsPublicPath 在我打包完之后打開dist/index.html叼耙,頁面是空白盆繁,然后報錯如下
經(jīng)過一番查找之后,得到結(jié)果是在config/index.js文件下dev和build下面的 assetsPublicPath '/' 修改成 './' 旬蟋,改完之后發(fā)現(xiàn)npm run dev 的時候頁面找不到了油昂,所以為了兼容打包和運行,打包的時候統(tǒng)一使用config.build.assetsPublicPath倾贰,而dev下面的assetsPublicPath還是改回原來的 '/'冕碟。
打包好之后的文件在根目錄下面會生成一個dist文件夾,里面的結(jié)構(gòu)如下匆浙,其中安寺,static主要存放靜態(tài)資源(css、js首尼、images挑庶、fonts等)
2. 關(guān)于devtool: 'cheap-module-eval-source-map'
為什么需要這個東西,主要的作用是幫助我們精準定位錯誤信息软能,如
我在helloWorld.vue文件調(diào)用了print文件里面的consoleLog函數(shù)迎捺,函數(shù)內(nèi)容我寫的很簡單
let consoleLog = () =>{
console.log('這是正確的打印');
cosnole.error('這是錯誤的打印')
}
export default {
consoleLog
}
如果沒有devtool: '',那么提示的錯誤信息
它雖然告訴你cosnole is not defined查排,但是并沒有告訴你在哪個文件哪一行
但是如果有devtool: 'cheap-module-eval-source-map'凳枝,他的提示信息可以非常準確定位到文件以及行位置
3.關(guān)于clean-webpack-plugin 清除文件插件
按著上面插件使用方法,直接報了一個不是構(gòu)造函數(shù)的錯誤跋核,一臉懵
后來去看官方文檔引入
使用的時候查找了很多文章有這類的使用方法
new CleanWebpackPlugin(['dist'],{
root: path.resolve(__dirname, '..'),
dry: false // 啟用刪除文件
}),
果然不行岖瑰,這不是就是參數(shù)類型傳得不對嗎
換成下面這種參數(shù)吧叛买,估計這個與版本有關(guān),我的是3.0.0
new CleanWebpackPlugin({
root: path.resolve(__dirname, '..'),
dry: false // 啟用刪除文件
}),
clean-webpack-plugin相關(guān)知識
結(jié)語
很多時候蹋订,好記性真是不如爛筆頭率挣,寫了一遍之后基本上知道理解其中得工作原理,以及相關(guān)一些配置露戒,還有一些生產(chǎn)環(huán)境得配置已經(jīng)應用還沒有真正深入理解椒功,后面有時間有精力一定補上。附上webpack官方文檔玫锋,我也只看到【開發(fā)】額蛾茉。讼呢。撩鹿。