Vue-cli的配置知識
Vue-cli有兩個文件——build和config:build文件包含了腳手架在開發環境和生產環境下webpack該如何配置。config文件則包含了build文件下webpack具體配置的值。換句話說,build下的webpack配置的值要引入config後才能獲取到。
一.config文件夾下一共有三個文件:
1.dev.env.js: 導出開發環境名稱;
2.prod.env.js: 導出生產環境名稱;
3.index.js: 導出不同環境的具體配置;
二.build文件夾下一共有七個文件:
1.build.js: 編譯時的入口文件,當執行npm run build時其實就是執行node build/build.js(在package.json中);
2.check-versions.js: 編譯代碼時執行的確認node和npm版本的文件,如果版本不符,則停止編譯;
3.utils.js:這個文件有兩個作用,一是作為vue-loader的配置來使用;另一個是用來給開發環境和生產環境配置loader;
4.vue-loader.conf.js:vue-loader的配置,用在webpack.base.conf.js中;
5.webpack.base.conf.js:vue-cli腳手架的基礎webpack配置,通過與webpack.dev.conf.js和webpack.prod.conf.js兩個配置文件的合並(合並方式我會在下一章來講)來實現“不重復原則(Don‘t repeat yourself - DRY),不會在不同的環境中配置相同的代碼
6.webpack.dev.conf.js:開發環境下的webpack的配置;
7.webpack.prod.conf.js:生產環境下的webpack的配置;
三.config文件
1.prod.env.js:
//導出一個對象,對象有一個當前node環境的屬性,值為“production”(生產環境)
module.exports = { NODE_ENV: ‘"production"‘}
2.dev.env.js:
//導出另一個對象,屬性為當前的node環境,值為“development”(開發環境)
const merge = require(‘webpack-merge‘)
const prodEnv = require(‘./prod.env‘)
module.exports = merge(prodEnv, { NODE_ENV: ‘"development"‘})
-
- 這裏要著重說一下webpack-merge這個包,這個包的作用是來合並兩個配置文件對象並生成一個新的配置文件,有點兒類似於es6的Object.assign()方法。如果合並的過程中遇到沖突的屬性,第二個參數的屬性值會覆蓋第一個參數的屬性值。
- 前面寫到webpack.base.conf.js與webpack.dev.conf.js和webpack.prod.conf.js的合並也用到了webpack-merge。Vue-cli將一些通用的配置抽出來放在一個文件內(webpack.base.conf.js),在對不同的環境配置不同的代碼,最後使用webpack-merge來進行合並,減少重復代碼。
3.index.js:
index.js作為具體的配置值,我覺得沒必要把代碼貼出來了,大家可以拿上面的的腦圖或者自己項目裏的文件來結合我後面要說的代碼來看。
四.build文件
1.check.versions.js:
//chalk 是一個用來在命令行輸出不同顏色文字的包,可以使用chalk.yellow("想添加顏色的文字....") //來實現改變文字顏色的; const chalk = require(‘chalk‘) //semver 的是一個語義化版本文件的npm包,其實它就是用來控制版本的; const semver = require(‘semver‘)const packageConfig = require(‘../package.json‘) //一個用來執行unix命令的包 const shell = require(‘shelljs‘) //child_process 是Node.js提供了衍生子進程功能的模塊,execSync()方法同步執行一個cmd命令, //將返回值的調用toString和trim方法 function exec (cmd) { return require(‘child_process‘).execSync(cmd).toString().trim() } const versionRequirements = [ { name: ‘node‘, //semver.clean()方法返回一個標準的版本號,切去掉兩邊的空格,比如semver.clean(" =v1.2.3 ") //返回"1.2.3",此外semver還有vaild,satisfies,gt,lt等方法, //這裏查看https://npm.taobao.org/package/semver可以看到更多關於semver方法的內容 currentVersion: semver.clean(process.version), versionRequirement: packageConfig.engines.node } ] //shell.which方法是去環境變量搜索有沒有參數這個命令 if (shell.which(‘npm‘)) { versionRequirements.push({ name: ‘npm‘, //執行"npm --version"命令 currentVersion: exec(‘npm --version‘), versionRequirement: packageConfig.engines.npm } )} //後面這部分代碼就比較好理解了 module.exports = function () { const warnings = [] for (let i = 0; i < versionRequirements.length; i++) {
const mod = versionRequirements[i] if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(mod.name + ‘: ‘ + chalk.red(mod.currentVersion) + ‘ should be ‘ + chalk.green(mod.versionRequirement) ) } } if (warnings.length) { console.log(‘‘) console.log(chalk.yellow(‘To use this template, you must update following to modules:‘)) console.log() for (let i = 0; i < warnings.length; i++) { const warning = warnings[i] console.log(‘ ‘ + warning) } console.log() process.exit(1) }}
2.utils.js:
const path = require(‘path‘)const config = require(‘../config‘)
//這個plugin的作用是將打包後生成的css文件通過link的方式引入到html中,如果不適用這個插件css代碼會 //放到head標簽的style中 const ExtractTextPlugin = require(‘extract-text-webpack-plugin‘)
const packageConfig = require(‘../package.json‘)
//process.env.NODE_ENV是一個環境變量,它是由webpack.dev/prod.conf.js這兩個文件聲明的; //這裏的意思是判斷當前是否是開發環境,如果是就把config下index.js文件中build.assetsSubDirectory或 //dev.assetsSubDirectory的值賦給assetsSubDirectory exports.assetsPath = function (_path) { const assetsSubDirectory = process.env.NODE_ENV === ‘production‘ ? config.build.assetsSubDirectory : config.dev.assetsSubDirectory //path.posix.join是path.join的一種兼容性寫法,它的作用是路徑的拼接,這裏返回的是"static/_path" return path.posix.join(assetsSubDirectory, _path )} //cssLoaders的作用是導出一個供vue-loader的options使用的一個配置; 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 }) }) } if (options.extract) { return ExtractTextPlugin.extract({ use: loaders, fallback: ‘vue-style-loader‘ }) } else { return [‘vue-style-loader‘].concat(loaders) } } return { css: generateLoaders(), postcss: generateLoaders(), less: generateLoaders(‘less‘), sass: generateLoaders(‘sass‘, { indentedSyntax: true }), scss: generateLoaders(‘sass‘), stylus: generateLoaders(‘stylus‘), styl: generateLoaders(‘stylus‘) } } // styleLoaders是用來給webpack提供所有和css相關的loader的配置,它也使用了cssLoaders()方法; exports.styleLoaders = function (options) { const output = [] const loaders = exports.cssLoaders(options) for (const extension in loaders) { const loader = loaders[extension] output.push({ test: new RegExp(‘\\.‘ + extension + ‘$‘), use: loader }) } return output } //‘node-notifier‘是一個跨平臺系統通知的頁面,當遇到錯誤時,它能用系統原生的推送方式給你推送信息 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() notifier.notify({ title: packageConfig.name, message: severity + ‘: ‘ + error.name, subtitle: filename || ‘‘, icon: path.join(__dirname, ‘logo.png‘) }) } }
- cssLoaders方法根據傳進來的參數(options)是否有extract屬性來返回不同的值,如果你看了後面的代碼你就會知道在生產模式下extract屬性為true,開發模式下為false。也就是說,在生產模式下返回的是一個類似於這樣的數組:
ExtractTextPlugin.extract({
use: ["css-loader","less-loader","sass-loader"...],
fallback: ‘vue-style-loader‘
})
這些css代碼打包以link的方式放到HTML中。當然了,use的值確切的說應該是這樣:
[ { loader: ‘css-loader‘, options: { sourceMap: true } }, { loader: ‘less-loader‘, options: { sourceMap: true } } ]
我為了方便看就簡寫了。
而在開發模式下,cssLoaders返回的是:
["vue-style-loader","css-loader","less-loader","sass-loader"...] //我還是簡寫了 |
styleLoaders方法返回的值就簡單了,它返回的就是webpack中module裏常用的配置格式:
[ |
3.vue-loader.conf.js:
const utils = require(‘./utils‘) const config = require(‘../config‘) //不同環境為isProduction 賦值: 生產環境為true,開發環境為false const isProduction = process.env.NODE_ENV === ‘production‘ //不同環境為sourceMapEnabled 賦值: 這裏都為true const sourceMapEnabled = isProduction ? config.build.productionSourceMap : config.dev.cssSourceMap //導出vue-loader的配置,這裏我們用了utils文件中的cssLoaders(); module.exports = { loaders: utils.cssLoaders({ sourceMap: sourceMapEnabled, extract: isProduction }), cssSourceMap: sourceMapEnabled, cacheBusting: config.dev.cacheBusting, //transformToRequire的作用是在模板編譯的過程中,編譯器可以將某些屬性,如src轉換為require調用; transformToRequire: { video: [‘src‘, ‘poster‘], source: ‘src‘, img: ‘src‘, image: ‘xlink:href‘ } } |
4.webpack.base.conf.js:
const path = require(‘path‘) const utils = require(‘./utils‘) const config = require(‘../config‘) const vueLoaderConfig = require(‘./vue-loader.conf‘) //resolve這個函數返回的是當前目錄下"../dir"這個文件夾,__dirname指的是當前文件所在路徑 function resolve (dir) { return path.join(__dirname, ‘..‘, dir)} module.exports = { //返回項目的根路徑 context: path.resolve(__dirname, ‘../‘), //入口文件 entry: { app: ‘./src/main.js‘ }, //出口文件 output: { path: config.build.assetsRoot, filename: ‘[name].js‘, publicPath: process.env.NODE_ENV === ‘production‘ ? config.build.assetsPublicPath : config.dev.assetsPublicPath }, resolve: { //自動解析擴展,比如引入對應的文件,js,vue,json的後綴名就可以省略了 extensions: [‘.js‘, ‘.vue‘, ‘.json‘], alias: { //精準匹配,使用vue來替代vue/dist/vue.esm.js ‘vue$‘: ‘vue/dist/vue.esm.js‘, //使用@替代src路徑,當你引入src下的文件是可以使用import XXfrom "@/xx" ‘@‘: resolve(‘src‘), } }, //一些loader配置,避免篇幅過長我省略一部分,大家可以看自己的文件 module: { rules: [ { test: /\.vue$/, loader: ‘vue-loader‘, options: vueLoaderConfig }, { test: /\.js$/, loader: ‘babel-loader‘, include: [resolve(‘src‘), resolve(‘test‘), resolve(‘node_modules/webpack-dev-server/client‘)] }, ...... ] }, //node裏的這些選項是都是Node.js全局變量和模塊,這裏主要是防止webpack註入一些Node.js的東西到vue中 node: { setImmediate: false, dgram: ‘empty‘, fs: ‘empty‘, net: ‘empty‘, tls: ‘empty‘, child_process: ‘empty‘ } } |
5.webpack.dev.conf.js:
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‘) //一個更友好的展示webpack錯誤提示的插件 const FriendlyErrorsPlugin = require(‘friendly-errors-webpack-plugin‘) //一個自動檢索端口的包 const portfinder = require(‘portfinder‘) const HOST = process.env.HOSTconst PORT = process.env.PORT && Number(process.env.PORT) const devWebpackConfig = merge(baseWebpackConfig, { module: { rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) }, devtool: config.dev.devtool, // devServer的配置大家看文檔就好了 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, port: PORT || config.dev.port, open: config.dev.autoOpenBrowser, 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‘) }), //模塊熱替換的插件,修改模塊不需要刷新頁面 new webpack.HotModuleReplacementPlugin(), //當使用HotModuleReplacementPlugin時,這個插件會顯示模塊正確的相對路徑 new webpack.NamedModulesPlugin(), //在編譯出錯時,使用NoEmitOnErrorsPlugin來跳過輸出階段,這樣可以確保輸出資源不會包含錯誤 new webpack.NoEmitOnErrorsPlugin(), new HtmlWebpackPlugin({ filename: ‘index.html‘, template: ‘index.html‘, inject: true }), // 將static文件夾和裏面的內容拷貝到開發模式下的路徑,比如static下有個img文件夾,裏面有張圖片 // 我們可以這樣訪問:localhost:8080/static/img/logo.png new CopyWebpackPlugin([ { from: path.resolve(__dirname, ‘../static‘), to: config.dev.assetsSubDirectory, ignore: [‘.*‘] } ]) ] }) //這裏主要是做端口的檢索以及npm run dev後對錯誤的處理,我們可以看這裏使用了前面引入的 //‘friendly-errors-webpack-plugin‘插件 module.exports = new Promise((resolve, reject) => { portfinder.basePort = process.env.PORT || config.dev.port portfinder.getPort((err, port) => { if (err) { reject(err) } else { // publish the new Port, necessary for e2e tests process.env.PORT = port // add port to devServer config devWebpackConfig.devServer.port = port // Add 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) } }) }) |
關於devServer有兩點要說明一下:
- contentBase是來告訴服務器在哪裏提供靜態的內容,這裏我們使用false的原因是使用了“copy-webpack-plugin”插件,不需要使用contentBase了;
- quiet開啟後(true),除了初始啟動信息之外的任何內容都不會被打印到控制臺,即使是webpack 的錯誤或警告在控制臺也不可見。不過我們用了‘friendly-errors-webpack-plugin‘插件,就可以設為true了。
6.webpack.prod.conf.js
經過前面這麽多代碼的分析,其實webpack.prod.conf.js的配置已經很簡單了,大致跟webpack.dev.conf.js的配置方式差不多,就是多了幾個plugins:
- UglifyJsPlugin是用來壓縮JS代碼
- optimize-css-assets-webpack-plugin是用來壓縮css代碼
- HashedModuleIdsPlugin會根據模塊的相對路徑生成一個四位數的hash作為模塊id
- ModuleConcatenationPlugin可以預編譯所有模塊到一個包中,加快瀏覽器的運行速度
- CommonsChunkPlugin拆分公共模塊,vue裏拆分了vendor,manifest和app三個模塊
- compression-webpack-plugin gzip壓縮
- webpack-bundle-analyzer可以查看打包的具體情況,比如打了多少個包,每個包多大等
好了,plugins的介紹到此結束,接下來就是最後一個文件,也是npm run build編譯時的入口文件——build.js了。
同樣的,build.js文件其實也沒什麽可說的了,無非就是執行webpack.prod.conf.js文件,遇到錯誤時在命令行提示。需要註意的是,build.js裏引入了“rimraf”的包,它的作用是每次編譯時清空dist文件,避免多次編譯時造成文件夾的重復和混亂。
Vue-cli的配置知識