webpack打包vue-cli項目之webpack.prod.conf.js文件分析

        這個文件是打包過程彙總的核心文件,文件中聲明的對象webpackConfig也是整個打包過程中的核心對象。

1.導入相關對象

const path = require('path')
// 下面是utils工具配置文件,主要用來處理css類文件的loader
const utils = require('./utils')
// 下面引入webpack,來使用webpack內置插件s
const webpack = require('webpack')
// 下面是config目錄下的index.js配置文件,主要用來定義了生產和開發環境的相關基礎配置
const config = require('../config')
  // webpack-merge是一個可以合併數組和對象的插件
const merge = require('webpack-merge') 
 //webpack 基本配置文件
const baseWebpackConfig = require('./webpack.base.conf') 
// copy-webpack-plugin,用於將static中的靜態文件複製到產品文件夾dist
const CopyWebpackPlugin = require('copy-webpack-plugin')
// html-webpack-plugin用於將webpack編譯打包後的產品文件注入到html模板中
// 即在index.html裏面加上<link>和<script>標籤引用webpack打包後的文件
const HtmlWebpackPlugin = require('html-webpack-plugin')
// extract-text-webpack-plugin這個插件是用來將bundle中的css等文件產出單獨的bundle文件的,之前也詳細講過
const ExtractTextPlugin = require('extract-text-webpack-plugin')
// optimize-css-assets-webpack-plugin插件的作用是壓縮css代碼的
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
// 醜化壓縮JS代碼
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
// 導入環境變量
const env = require('../config/prod.env')

2.創建webpackConfig對象

        本文件中定義了一個webpackConfig對象,這個對象來源於base文件中封裝好的baseWebpackConfig對象和在本文件中封裝的另一個配置對象。webpackConfig對象定義了打包後生成的文件的命名方式、文件的存放路徑、打包代碼時需要用到的一些加載器以及一些插件。

2.1 webpack-merge

        build配置文件夾中的文件中除了編譯(dev文件)和打包(prod文件)用的配置文件以外,還有一個兩者共同會用到的base文件。因此在打包的時候需要先整個base文件和prod文件,這裏使用webpack-merge來整合這兩個文件中的配置對象。,返回webpackConfig對象。將webpackConfig對象打印到控制檯中,截取了部分內容如下所示。可以看出來,output和module.rules部分,是將base文件和pros文件整合的結果。
在這裏插入圖片描述

2.2 prodWebpackConfig對象

        在源碼中,並沒有這個對象,這裏只是爲了描述時的便利,依據baseWebpackConfig起的名字。

2.2.1 module屬性

        module也是一個對象,只有一個rules屬性,rules屬性的值是一個樣式加載器的集合,用於加載各種樣式(不同類型的樣式文件如css/sass/scss,需要使用不同的樣式加載器)。uitls.styleLoaders是一個方法,實參是一個對象,這個對象裏有三個布爾類型的屬性,這些屬性將會作爲條件,選擇性地創建所需的類加載器,並以集合的形式存儲這些樣式加載器。

 module: {
    //sourceMap:代碼轉換前後的對應關係
    /**
     *  根據options對象屬性的布爾值作爲條件,生成一個樣式文件加載器的集合。
     * */
    rules: utils.styleLoaders({
      sourceMap: config.build.productionSourceMap,
      // 此項是自定義項,設置爲true表示,生成獨立的文件
      extract: true,
      usePostCSS: true
    })
  },

        關於utils.styleLoaders的介紹在這裏。

2.2.2 devtool屬性
  devtool: config.build.productionSourceMap ? config.build.devtool : false,
2.2.3 output屬性

        定義文件的輸出路徑及命名格式。

  output: {
    // webpack輸出路徑和命名規則
    path: config.build.assetsRoot,
    // 文件名稱使用 static/js/[name].[chunkhash].js, chunkhash就是模塊的hash值,用於瀏覽器緩存的,
    //當文件中的hash值與模塊中的hash值不一致時,就會重新加載該模塊
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    // chunkFilename是非入口模塊文件,也就是說filename文件中引用了chunckFilename
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
  },
2.2.4 plugins屬性

        這一部分定義了一些會使用到的插件。

2.2.4.1 DefinePlugin插件

        這個插件用於定義當前的運行環境,來區分編譯操作和打包操作。env 是從別的文件中導入的環境變量,這裏的值表示的是打包操作。

    /** 定義代碼的運行環境 */
    new webpack.DefinePlugin({
       // const env = require('../config/prod.env')
       /*
			module.exports = {
			  NODE_ENV: '"production"'
			}
		*/
      'process.env': env
    }),
2.2.4.2 UglifyJsPlugin插件

        這個插件的功能是壓縮JS代碼,自動混淆。詳見UglifyJsPlugin的官方API

    // 壓縮JS代碼
    new UglifyJsPlugin({
      uglifyOptions: {
        compress: {
          // 不顯示警告信息
          warnings: false
        }
      },
      // 壓縮後生成map文件(壓縮前後的代碼對應信息)
      sourceMap: config.build.productionSourceMap,
      // 並行提高效率,並行數量與CPU的數量(默認爲os.cpus().length - 1,也可以手動指定數量)相關。
      parallel: true
    }),
2.2.4.2 ExtractTextPlugin插件

        這個插件的功能是將css文件提取到單獨的文件中。ExtractTextPlugin插件會將所有的*.css模塊從entry chunk移到一個單獨的css文件中。因此你的樣式將不會再包含在js代碼塊中,而是在一個單獨的css文件中(style.css)中。由於加載css代碼塊和加載js代碼塊是並行操作的,因此這樣做的效率更高,尤其是當你的樣式表很大的時候。官網原文

    new ExtractTextPlugin({
     /**
      * 輸出的樣式文件的路徑及默認格式,使用佔位符。
      * */
      filename: utils.assetsPath('css/[name].[contenthash].css'),
       // 當使用 `CommonsChunkPlugin` 並且在公共 chunk 中有提取的 chunk(來自`ExtractTextPlugin.extract`)時,
       // `allChunks` **必須設置爲 `true`。
     allChunks: true,
    }),
2.2.4.3 OptimizeCSSPlugin插件

        使用這個插件來優化、壓縮CSS代碼,解決使用extract-text-plugin可能會造成css重複的問題。解釋原文

    new OptimizeCSSPlugin({
      cssProcessorOptions: config.build.productionSourceMap
        ? { safe: true, map: { inline: false } }
        : { safe: true }
    }),
2.2.4.4 HtmlWebpackPlugin插件
 // 將產品文件的引用注入到index.html
    /** https://www.jianshu.com/p/08a60756ffda */
    new HtmlWebpackPlugin({
      //定義生成的html文件的名字,這裏在另外一個配置文件裏面指定了
      //  index: path.resolve(__dirname, '../dist/index2.html'),
      filename: config.build.index,
      /** 模板文件,就是根目錄裏的那個文件。如果找不到與模板名相同的文件,則會報錯 */
      template: 'index.html',
      // 將js文件放到body標籤的結尾(默認值),也可以選擇插入到head中或者不插入
      inject: true,
      /** https://www.jianshu.com/p/08a60756ffda
       *minify 的作用是對 html 文件進行壓縮,minify 的屬性值是一個壓縮選項或者 false 。
       * 默認值爲false, 不對生成的 html 文件進行壓縮。
       * */
      minify: {
        // 刪除index.html中的註釋
        removeComments: true,
        // 刪除index.html中的空格
        collapseWhitespace: true,
        // 刪除各種html標籤屬性值的雙引號
        removeAttributeQuotes: true
        // more options:
        // https://github.com/kangax/html-minifier#options-quick-reference
      },
      // necessary to consistently work with multiple chunks via CommonsChunkPlugin
      /** 確定script插入的順序,dependency表示按照文件的依賴順序進行處理 */
      chunksSortMode: 'dependency'
    }),
   
2.2.4.5 HashedModuleIdsPlugin插件
 new webpack.HashedModuleIdsPlugin(),

        瀏覽器的緩存機制會自動存儲部分js代碼,例如將第三方插件拆分成一個獨立的chunk代碼(vendor.js)。由於一般我們不會修改第三方的代碼,所以將第三方代碼緩存起來會更高效。但是chunk代碼的名字中除了vendor還有一個哈希值,所以即使我們並沒有修改第三方庫中的代碼,vendor chunk的名字還是會發生變化。一旦發生變化,瀏覽器就會重新加載vendor chunk,這樣的話,瀏覽器的緩存機制就形同虛設。因此使用這個插件來解決這個。
        做了個實驗,對於修改了一個程序,然後前後打包的對比結果如下圖所示,可以看出來雖然代碼變化了,但是因爲加了這個,保證了vendor命名沒有變化。

在這裏插入圖片描述

2.2.4.3 ModuleConcatenationPlugin插件
new webpack.optimize.ModuleConcatenationPlugin(),

        預編譯提升代碼在瀏覽器中的執行速度。

2.2.4.6 CommonsChunkPlugin插件

        CommonsChunkPlugin主要是用來提取第三方庫和公共模塊,避免首屏加載的bundle文件或者按需加載的bundle文件體積過大,從而導致加載時間過長,着實是優化的一把利器。例如,就是兩個js都引用了第三個js文件,不做提取的話,這個js文件就會被重複打包。案例

        創建這個對象的時候,傳入了一個options對象,在這個文件中,用到的屬性如下所示。尤其要注意這裏的filename屬性和chunks屬性,雖然沒有實名進行定義,但是卻有約定值。

{

    name: string, // or
    names: string[],
    // chunk的名字,如果這個chunk已經在entry中定義,該chunk會被直接提取;
    // 如果沒有定義,則生成一個空的chunk來其他所有chunk的公共代碼

    filename: string,
    // 可以指定提取出的公共代碼的文件名稱,可以使用output配置項中文件名的佔位符。未定義時使用name作爲文件名。

    minChunks: number|Infinity|function(module, count) => boolean,
    // 在一個模塊被提取到公共chunk之前,它必須被最少minChunks個chunk所包含。值可以是數字、關鍵字或者是函數
    //該數字必須不小於2或者不大於chunks的個數。默認值等於chunks的個數。
    //如果指定了Infinity,則創建一個公共chunk,但是不包含任何模塊,內部是一些webpack生成的runtime代碼和chunk自身包含的模塊(如果chunk存在的話)。
    //用戶也可以定製自己的邏輯去生成代碼。

    chunks: string[],
    // 指定從某個(些)chunk中抽取子chunk。如果不定義,則默認從entry chunk中抽取公共代碼。

    children: boolean,
    // If `true` all children of the commons chunk are selected

    deepChildren: boolean,
    // If `true` all descendants of the commons chunk are selected

    async: boolean|string,
    // If `true` a new async commons chunk is created as child of `options.name` and sibling of `options.chunks`.
    // It is loaded in parallel with `options.chunks`.
    // Instead of using `option.filename`, it is possible to change the name of the output file by providing
    // the desired string here instead of `true`.

    minSize: number,
    // Minimum size of all common module before a commons chunk is created.
}

        在源碼中,如果註釋掉所有的CommonsChunkPlugin拆件,則只會生成一個entry chunk,如下圖所示。在這裏,chunk的名字“app”即是入口文件的名字。
在這裏插入圖片描述         app這個值是在base文件裏指定的。
在這裏插入圖片描述

        如下代碼所示,這個插件首先被用來提取第三方庫。將所有從將所有從node_modules中引入的js提取到vendor.js,同時便於瀏覽器緩存,提高程序的運行速度。實參裏沒有指定chunks因此是從app.js(entry chunk)裏抽取代碼,同時沒有指定filename,因此文件名取name中的vendor,下同。

// 
 
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      // minChunks 爲函數,則根據返回值的邏輯來確定是否提取爲 commons chunk。
      minChunks (module) {
        // any required modules inside node_modules are extracted to vendor
        //"有正在處理文件" + "這個文件是 .js 後綴" + "這個文件是在 node_modules 中"
        return (
          module.resource &&
          /\.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, '../node_modules')
          ) === 0
        )
      }
    }),

        上面的代碼其實就是生成一個叫做vendor的commons chunk,那麼有哪些模塊會被加入到vendor中呢?就對入口文件及其依賴的模塊進行遍歷,如果該模塊是js文件並且在node_modules中,就會加入到vendor當中,其實這也是一種讓vendor只保留第三方庫的辦法。
在這裏插入圖片描述

    // prevent vendor hash from being updated whenever app bundle is updated
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      minChunks: Infinity
    }),

        對於上面的代碼,上面說是防止從app代碼塊變動時導致vendor哈希值變動的問題。但是這裏的Infinity的條件是什麼衆說紛紜,所以我不太清楚。

 // This instance extracts shared chunks from code splitted chunks and bundles them
 // in a separate chunk, similar to the vendor chunk
 // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
 new webpack.optimize.CommonsChunkPlugin({
   name: 'app',
   async: 'vendor-async',
   children: true,
   // minChunks爲數字,則當模塊被3個chunks公共引用,纔會被抽取出來爲common chunk。
   minChunks: 3
 }),

        當一個模塊被三個chunk公共引用的時候,會被抽取出一個公共塊,但是這裏的name不知道爲什麼是這個。

2.2.4.7 CopyWebpackPlugin插件
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.build.assetsSubDirectory,
        ignore: ['.*']
      }
    ])

        這個人說,這個插件的功能是過濾掉打包過程中產生的以.開頭的文件。

3.總結

        對於後臺碼農來說,這個還是有點棘手。以上是我瀏覽了衆多中英文資料的結果。之後,還會不斷補充。敬請各位大佬不吝賜教。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章