做爲一名使用了一段時間Vue.js的新手,相信和很多初入Vue的朋友同樣,都對Vue-cli的配置只知其一;不知其二。後來經過對webpack的學習,也算是對腳手架的配置有了必定的瞭解,因此也想把這段時間本身的成果分享給你們,但願能和你們一塊兒進步。css
有兩點要說明的:html
先放一張本身整理的簡易腦圖:vue
Vue-cli有兩個文件——build和config:build文件包含了腳手架在開發環境和生產環境下webpack該如何配置。config文件則包含了build文件下webpack具體配置的值。換句話說,build下的webpack配置的值要引入config後才能獲取到。node
config文件夾下一共有三個文件:webpack
build文件夾下一共有七個文件:es6
1.prod.env.js:web
//導出一個對象,對象有一個當前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"'})
3.index.js:vue-cli
index.js做爲具體的配置值,我以爲不必把代碼貼出來了,你們能夠拿上面的的腦圖或者本身項目裏的文件來結合我後面要說的代碼來看。shell
1.check.versions.js:npm
//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()和styleLoaders()這兩個方法返回的是個什麼東西,我在這裏簡單的寫一下:
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"...] //我仍是簡寫了
[ { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }, ... ]
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有兩點要說明一下:
6.webpack.prod.conf.js
通過前面這麼多代碼的分析,其實webpack.prod.conf.js的配置已經很簡單了,大體跟webpack.dev.conf.js的配置方式差很少,就是多了幾個plugins:
好了,plugins的介紹到此結束,接下來就是最後一個文件,也是npm run build編譯時的入口文件——build.js了。
一樣的,build.js文件其實也沒什麼可說的了,無非就是執行webpack.prod.conf.js文件,遇到錯誤時在命令行提示。須要注意的是,build.js裏引入了「rimraf」的包,它的做用是每次編譯時清空dist文件,避免屢次編譯時形成文件夾的重複和混亂。
到這裏其實關於Vue-cli配置的分析基本結束了,相信瞭解webpack的朋友看起來必定很是簡單,配置主要麻煩的地方在於低耦合致使常常須要來回翻文件才能看懂配置,若是你們結合着文章開頭的腦圖看可能會相對容易些。
一個壞消息是這個文章發佈的時候webpack4.0已經上線了,Vue-cli新版也進入了Beta測試階段,因此這篇文章你們看看就好,瞭解一下思路,立刻配置又會更新的......