vue-cli中的webpack配置

lybenson發表於2017-03-21

編輯模式下顯示正常,開啟的時候不知道為啥排版有問題。
segementfalut連結在這裡

版本號

vue-cli 2.8.1 (終端通過vue -V 可檢視)

vue 2.2.2

webpack 2.2.1

目錄結構

├── README.md
├── build
│   ├── build.js
│   ├── check-versions.js
│   ├── dev-client.js
│   ├── dev-server.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
├── index.html
├── package.json
├── src
│   ├── App.vue
│   ├── assets
│   │   └── logo.png
│   ├── components
│   │   └── Hello.vue
│   └── main.js
└── static複製程式碼

webpack配置

主要對build目錄下的webpack配置做詳細分析

webpack.base.conf.js

入口檔案entry
entry: {
  app: '.src/main.js'
}複製程式碼
輸出檔案output

config的配置在config/index.js檔案中

output: {
  path: config.build.assetsRoot, //匯出目錄的絕對路徑
  filename: '[name].js', //匯出檔案的檔名
  publicPath: process.env.NODE_ENV === 'production'? config.build.assetsPublicPath : config.dev.assetsPublicPath //生產模式或開發模式下html、js等檔案內部引用的公共路徑
}複製程式碼
檔案解析resolve

主要設定模組如何被解析。

resolve: {
  extensions: ['.js', '.vue', '.json'], //自動解析確定的擴充名,使匯入模組時不帶擴充名
  alias: {   // 建立import或require的別名
    'vue$': 'vue/dist/vue.esm.js', 
    '@': resolve('src')
  }
}複製程式碼
模組解析module

如何處理專案不同型別的模組。

module: {
  rules: [
    {
      test: /\.vue$/, // vue檔案字尾
      loader: 'vue-loader', //使用vue-loader處理
      options: vueLoaderConfig //options是對vue-loader做的額外選項配置
    },
    {
      test: /\.js$/, // js檔案字尾
      loader: 'babel-loader', //使用babel-loader處理
      include: [resolve('src'), resolve('test')] //必須處理包含src和test資料夾
    },
    {
      test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, //圖片字尾
      loader: 'url-loader', //使用url-loader處理
      query: {  // query是對loader做額外的選項配置
        limit: 10000, //圖片小於10000位元組時以base64的方式引用
        name: utils.assetsPath('img/[name].[hash:7].[ext]') //檔名為name.7位hash值.擴充名
      }
    },
    {
      test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, //字型檔案
      loader: 'url-loader', //使用url-loader處理
      query: {
        limit: 10000,  //字型檔案小於1000位元組的時候處理方式
        name: utils.assetsPath('fonts/[name].[hash:7].[ext]') //檔名為name.7位hash值.擴充名
      }
    }
  ]
}複製程式碼

注: 關於query 僅由於相容性原因而存在。請使用 options 代替。

webpack.dev.conf.js

開發環境下的webpack配置,通過merge方法合併webpack.base.conf.js基礎配置

var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
module.exports = merge(baseWebpackConfig, {})複製程式碼
模組配置
module: {
  //通過傳入一些配置來獲取rules配置,此處傳入了sourceMap: false,表示不生成sourceMap
  rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 
}複製程式碼

util.styleLoaders中的配置如下

exports.styleLoaders = function (options) {
  var output = [] //定義返回的陣列,陣列中儲存的是針對各型別的樣式檔案的處理方式
  var loaders = exports.cssLoaders(options) // 呼叫cssLoaders方法返回各型別的樣式物件(css: loader)
  for (var extension in loaders) {  //迴圈遍歷loaders
    var loader = loaders[extension] //根據遍歷獲得的key(extension)來得到value(loader)
    output.push({     //
      test: new RegExp('\\.' + extension + '$'), // 處理的檔案型別
      use: loader  //用loader來處理,loader來自loaders[extension]
    })
  }
  return output
}複製程式碼

上面的程式碼中呼叫了exports.cssLoaders(options),用來返回針對各型別的樣式檔案的處理方式,具體實現如下

exports.cssLoaders = function (options) {
  options = options || {}

  var cssLoader = { 
    loader: 'css-loader',
    options: {  //options是loader的選項配置 
      minimize: process.env.NODE_ENV === 'production', //生成環境下壓縮檔案
      sourceMap: options.sourceMap  //根據引數是否生成sourceMap檔案
    }
  }
  function generateLoaders (loader, loaderOptions) {  //生成loader
    var loaders = [cssLoader] // 預設是css-loader
    if (loader) { // 如果引數loader存在
      loaders.push({
        loader: loader + '-loader',
        options: Object.assign({}, loaderOptions, { //將loaderOptions和sourceMap組成一個物件
          sourceMap: options.sourceMap
        })
      })
    }
    if (options.extract) { // 如果傳入的options存在extract且為true
      return ExtractTextPlugin.extract({  //ExtractTextPlugin分離js中引入的css檔案
        use: loaders,  //處理的loader
        fallback: 'vue-style-loader' //沒有被提取分離時使用的loader
      })
    } else {
      return ['vue-style-loader'].concat(loaders)
    }
  }
  return {  //返回css型別對應的loader組成的物件 generateLoaders()來生成loader
    css: generateLoaders(),
    postcss: generateLoaders(),
    less: generateLoaders('less'),
    sass: generateLoaders('sass', { indentedSyntax: true }),
    scss: generateLoaders('sass'),
    stylus: generateLoaders('stylus'),
    styl: generateLoaders('stylus')
  }
}複製程式碼
外掛配置
plugins: [
  new webpack.DefinePlugin({ // 編譯時配置的全域性變數
    'process.env': config.dev.env //當前環境為開發環境
  }),
  new webpack.HotModuleReplacementPlugin(), //熱更新外掛
  new webpack.NoEmitOnErrorPlugin(), //不觸發錯誤,即編譯後執行的包正常執行
  new HtmlWebpackPlugin({  //自動生成html檔案,比如編譯後檔案的引入
    filename: 'index.html', //生成的檔名
    template: 'index.html', //模板
    inject: true
  }),
  new FriendlyErrorsPlugin() //友好的錯誤提示
]複製程式碼

webpack.prod.conf.js

生產環境下的webpack配置,通過merge方法合併webpack.base.conf.js基礎配置

module的處理,主要是針對css的處理

同樣的此處呼叫了utils.styleLoaders

module: {
  rules: utils.styleLoaders({
    sourceMap: config.build.productionSourceMap,
    extract: true
  }) 
}複製程式碼
輸出檔案output
output: {
  //匯出檔案目錄
  path: config.build.assetsRoot, 
  //匯出的檔名
  filename: utils.assetsPath('js/[name].[chunkhash].js'), 
  //非入口檔案的檔名,而又需要被打包出來的檔案命名配置,如按需載入的模組
  chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
}複製程式碼
外掛plugins
var path = require('path')
var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var CopyWebpackPlugin = require('copy-webpack-plugin')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
var env = config.build.env
plugins: [
  new webpack.DefinePlugin({
    'process.env': env //配置全域性環境為生產環境
  }),
  new webpack.optimize.UglifyJsPlugin({ //js檔案壓縮外掛
    compress: {  //壓縮配置
      warnings: false  // 不顯示警告
    },
    sourceMap: true //生成sourceMap檔案
  }),
  new ExtractTextPlugin({ //將js中引入的css分離的外掛
    filename: utils.assetsPath('css/[name].[contenthash].css') //分離出的css檔名
  }),
  //壓縮提取出的css,並解決ExtractTextPlugin分離出的js重複問題(多個檔案引入同一css檔案)
  new OptimizeCSSPlugin(), 
  //生成html的外掛,引入css檔案和js檔案
  new HtmlWebpackPlugin({
    filename: config.build.index, //生成的html的檔名
    template: 'index.html', //依據的模板
    inject: true, //注入的js檔案將會被放在body標籤中,當值為'head'時,將被放在head標籤中
    minify: {  //壓縮配置
      removeComments: true, //刪除html中的註釋程式碼
      collapseWhitespace: true,  //刪除html中的空白符
      removeAttributeQuotes: true  //刪除html元素中屬性的引號
    },
    chunksSortMode: 'dependency' //按dependency的順序引入
  }),
  //分離公共js到vendor中
  new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor',  //檔名
    minChunks: functions(module, count) { // 宣告公共的模組來自node_modules資料夾
      return (module.resource && /\.js$/.test(module.resource) && module,resource.indexOf(path.join(__dirname, '../node_modules')) === 0)
    }
  }),
  //上面雖然已經分離了第三方庫,每次修改編譯都會改變vendor的hash值,導致瀏覽器快取失效。原因是vendor包含了webpack在打包過程中會產生一些執行時程式碼,執行時程式碼中實際上儲存了打包後的檔名。當修改業務程式碼時,業務程式碼的js檔案的hash值必然會改變。一旦改變必然會導致vendor變化。vendor變化會導致其hash值變化。
  //下面主要是將執行時程式碼提取到單獨的manifest檔案中,防止其影響vendor.js
  new webpack.optimize.CommonsChunkPlugin({
    name: 'mainifest',
    chunks: ['vendor']
  }),
  // 複製靜態資源,將static檔案內的內容複製到指定資料夾
  new CopyWebpackPlugin([{
    from: path.resolve(__dirname, '../static'),
    to: config.build.assetsSubDirectory,
    ignore: ['.*']  //忽視.*檔案
  }])
]複製程式碼
額外配置
if (config.build.productionGzip) { //配置檔案開啟了gzip壓縮

  //引入壓縮檔案的元件,該外掛會對生成的檔案進行壓縮,生成一個.gz檔案
  var CompressionWebpackPlugin = require('compression-webpack-plugin') 

  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]', //目標檔名
      algorithm: 'gzip', //使用gzip壓縮
      test: new RegExp( //滿足正規表示式的檔案會被壓縮
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240, //資原始檔大於10240B=10kB時會被壓縮
      minRatio: 0.8 //最小壓縮比達到0.8時才會被壓縮
    })
  )
}複製程式碼

npm run dev

有了上面的配置之後,下面看看執行命令npm run dev發生了什麼

package.json檔案中定義了dev執行的指令碼

"scripts": {
   "dev": "node build/dev-server.js",
   "build": "node build/build.js"
},複製程式碼

當執行npm run dev命令時,實際上會執行dev-server.js檔案

該檔案以express作為後端框架

// nodejs環境配置
var config = require('../config')
if (!process.env.NODE_ENV) {
  process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
}
var opn = require('opn') //強制開啟瀏覽器
var path = require('path')
var express = require('express')
var webpack = require('webpack')
var proxyMiddleware = require('http-proxy-middleware') //使用代理的中介軟體
var webpackConfig = require('./webpack.dev.conf') //webpack的配置

var port = process.env.PORT || config.dev.port //埠號
var autoOpenBrowser = !!config.dev.autoOpenBrowser //是否自動開啟瀏覽器
var proxyTable = config.dev.proxyTable //http的代理url

var app = express() //啟動express
var compiler = webpack(webpackConfig) //webpack編譯

//webpack-dev-middleware的作用
//1.將編譯後的生成的靜態檔案放在記憶體中,所以在npm run dev後磁碟上不會生成檔案
//2.當檔案改變時,會自動編譯。
//3.當在編譯過程中請求某個資源時,webpack-dev-server不會讓這個請求失敗,而是會一直阻塞它,直到webpack編譯完畢
var devMiddleware = require('webpack-dev-middleware')(compiler, {
  publicPath: webpackConfig.output.publicPath,
  quiet: true
})

//webpack-hot-middleware的作用就是實現瀏覽器的無重新整理更新
var hotMiddleware = require('webpack-hot-middleware')(compiler, {
  log: () => {}
})
//宣告hotMiddleware無重新整理更新的時機:html-webpack-plugin 的template更改之後
compiler.plugin('compilation', function (compilation) {
  compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
    hotMiddleware.publish({ action: 'reload' })
    cb()
  })
})

//將代理請求的配置應用到express服務上
Object.keys(proxyTable).forEach(function (context) {
  var options = proxyTable[context]
  if (typeof options === 'string') {
    options = { target: options }
  }
  app.use(proxyMiddleware(options.filter || context, options))
})

//使用connect-history-api-fallback匹配資源
//如果不匹配就可以重定向到指定地址
app.use(require('connect-history-api-fallback')())

// 應用devMiddleware中介軟體
app.use(devMiddleware)
// 應用hotMiddleware中介軟體
app.use(hotMiddleware)

// 配置express靜態資源目錄
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
app.use(staticPath, express.static('./static'))

var uri = 'http://localhost:' + port

//編譯成功後列印uri
devMiddleware.waitUntilValid(function () {
  console.log('> Listening at ' + uri + '\n')
})
//啟動express服務
module.exports = app.listen(port, function (err) {
  if (err) {
    console.log(err)
    return
  }
  // 滿足條件則自動開啟瀏覽器
  if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
    opn(uri)
  }
})複製程式碼

npm run build

由於package.json中的配置,執行此命令後會執行build.js檔案

process.env.NODE_ENV = 'production' //設定當前環境為production
var ora = require('ora') //終端顯示的轉輪loading
var rm = require('rimraf')  //node環境下rm -rf的命令庫
var path = require('path')  //檔案路徑處理庫
var chalk = require('chalk')  //終端顯示帶顏色的文字
var webpack = require('webpack') 
var config = require('../config') 
var webpackConfig = require('./webpack.prod.conf') //生產環境下的webpack配置

// 在終端顯示ora庫的loading效果
var spinner = ora('building for production...')
spinner.start()

// 刪除已編譯檔案
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
  if (err) throw err
  //在刪除完成的回撥函式中開始編譯
  webpack(webpackConfig, function (err, stats) {
    spinner.stop() //停止loading
    if (err) throw err

    // 在編譯完成的回撥函式中,在終端輸出編譯的檔案
    process.stdout.write(stats.toString({
      colors: true,
      modules: false,
      children: false,
      chunks: false,
      chunkModules: false
    }) + '\n\n')
  })
})複製程式碼

相關文章