快速入門vue-cli配置

做爲一名使用了一段時間Vue.js的新手,相信和很多初入Vue的朋友同樣,都對Vue-cli的配置只知其一;不知其二。後來經過對webpack的學習,也算是對腳手架的配置有了必定的瞭解,因此也想把這段時間本身的成果分享給你們,但願能和你們一塊兒進步。

有兩點要說明的:javascript

  1. 閱讀本文須要瞭解一點點webpack的知識,至少要entry,output,module,plugins都是作什麼,以及一些經常使用的loader和plugins;
  2. 本文使用的是最新版的vue,配置可能會和你們的有所不一樣,不過差距不會太大,不影響閱讀;

一.起步

先放一張本身整理的簡易腦圖:css


Vue-cli有兩個文件——buildconfig:build文件包含了腳手架在開發環境和生產環境下webpack該如何配置。config文件則包含了build文件下webpack具體配置的值。換句話說,build下的webpack配置的值要引入config後才能獲取到。html

config文件夾下一共有三個文件vue

  • dev.env.js: 導出開發環境名稱;
  • prod.env.js: 導出生產環境名稱;
  • index.js: 導出不一樣環境的具體配置;


build文件夾下一共有七個文件java

  • build.js: 編譯時的入口文件,當執行npm run build時其實就是執行node build/build.js(在package.json中);
  • check-versions.js: 編譯代碼時執行的確認node和npm版本的文件,若是版本不符,則中止編譯;
  • utils.js:這個文件有兩個做用,一是做爲vue-loader的配置來使用;另外一個是用來給開發環境和生產環境配置loader;
  • vue-loader.conf.js:vue-loader的配置,用在webpack.base.conf.js中;
  • webpack.base.conf.js:vue-cli腳手架的基礎webpack配置,經過與webpack.dev.conf.js和webpack.prod.conf.js兩個配置文件的合併(合併方式我會在下一章來說)來實現「不重複原則(Don't repeat yourself - DRY),不會在不一樣的環境中配置相同的代碼」
  • webpack.dev.conf.js:開發環境下的webpack的配置;
  • webpack.prod.conf.js:生產環境下的webpack的配置;


二.config文件

1.prod.env.js:node

//導出一個對象,對象有一個當前node環境的屬性,值爲「production」(生產環境)
module.exports = {  NODE_ENV: '"production"'}複製代碼

2.dev.env.js:
webpack

//導出另外一個對象,屬性爲當前的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來進行合併,減小重複代碼。

關於更多webpack-merge請點擊www.npmjs.com/package/web…es6

3.index.js:web

index.js做爲具體的配置值,我以爲不必把代碼貼出來了,你們能夠拿上面的的腦圖或者本身項目裏的文件來結合我後面要說的代碼來看。vue-cli

三.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()和styleLoaders()這兩個方法返回的是個什麼東西,我在這裏簡單的寫一下:

  • 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裏經常使用的配置格式:

[
    {
        test: /\.css$/,
        use: [ 'style-loader', 'css-loader' ]
    },
    ...
]
複製代碼

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配置的分析基本結束了,相信瞭解webpack的朋友看起來必定很是簡單,配置主要麻煩的地方在於低耦合致使常常須要來回翻文件才能看懂配置,若是你們結合着文章開頭的腦圖看可能會相對容易些。

一個壞消息是這個文章發佈的時候webpack4.0已經上線了,Vue-cli新版也進入了Beta測試階段,因此這篇文章你們看看就好,瞭解一下思路,立刻配置又會更新的......

相關文章
相關標籤/搜索