1. 程式人生 > >從 vue-cli 到 webpack多入口打包(一)

從 vue-cli 到 webpack多入口打包(一)

從三個外掛開始

1、CommonsChunkPlugin

commonsChunkPlugin 是webpack中的程式碼提取外掛,可以分析程式碼中的引用關係然後根據所需的配置進行程式碼的提取到指定的檔案中,常用的用法可以歸為四類:(1)、提取node_modules中的模組到一個檔案中;(2)、提取 webpack 的runtime程式碼到指定檔案中;(3)、提取入口檔案所引用的公共模組到指定檔案中;(4)、提取非同步載入檔案中的公共模組到指定檔案中。下面就具體說明以上四種用法。

1.1 提取node_modules中的模組

貼一段vue-cli 生成的程式碼

new webpack.optimize.CommonsChunkPlugin({
  name: 'vendor',
  minChunks(module) {
    // any required modules inside node_modules are extracted to vendor
    // 所有在node_modules中被引用的模組將被提取到vendor中
    return (
      module.resource &&
      /\.js$/.test(module.resource) &&
      module.resource.indexOf(
        path.join(__dirname, '../node_modules')
      ) === 0
    )
  }
})

通過查詢 commonsChunkPlugin 的相關文件可以知道 options 的配置項中name用於 output 中的 filename的 [name] 佔位符,也就是通過name可以指定提取出的檔案的檔名或包含name的資料夾名稱。minChunks 的取值比較多樣

minChunks: number|Infinity|function(module, count) => boolean

這裡我們用的是函式的形式,函式接受兩個引數,且返回值是boolean型別。函式的作用是遍歷所有被引用的模組(module.resource表示的是被引用模組的絕對路徑),由此我們可以通過module.resource進行判斷,滿足條件的我們返回true,反之返回false。返回值為true的模組將被提取到vender中。通過這一步我們可以完成node_modules中模組的提取。

1.2 提取webpack的runtime程式碼

通過給minChunks傳入Infinity這個引數,就可以實現提取webpack的執行時程式碼。按照官方文件的說法是:

// Passing `Infinity` just creates the commons chunk, but moves no modules into it.
// with more entries, this ensures that no other module goes into the vendor chunk

我個人理解:傳入Infinity會生成一個沒有包含任何模組的檔案,生成這個檔案的目的是為了保證vendor和其他公共模組的純潔性,貼一段vue-cli中的webpack.prod.conf.js檔案中的配置程式碼,並稍作解釋:

// extract webpack runtime and module manifest[module manifest => 模組清單] to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
  name: 'manifest',
  minChunks: Infinity
})

[]中註釋,manifest是一個模組清單,如果在沒有manifest的情況下,我們在業務程式碼中新引入了一個自定義模組(不是在node_modules中的模組),我們會發現打包的vendeor會發生變化,變化就是因為在vendor中需要指明webpack的模組清單及模組的引用關係。這樣我們就無法保證vendor的純潔性,所以這就是我們提取manifest的必要性。下面會有兩張對比圖片:

有manifest
有manifest的vendor
無manifest
無manifest的vendor

1.3 提取入口檔案所引用的公共模組

如果是在多入口的打包專案中,提取出公共檔案可以減少冗餘的打包程式碼。如果是在單入口的應用中,這一步驟可以省略,下面直接貼出程式碼:

/**
 * 該配置用於在多入口打包中,對 entry chunk中的公共程式碼進行提取
 */
new webpack.optimize.CommonsChunkPlugin({
  name: 'common', // 如果filename不存在,則使用該配置項的value進行對提取的公共程式碼進行命名
  filename: 'common/common-[hash].js', // filename 配置項可以指定index.html中的檔案引用路徑,及提取出來的程式碼放置路徑
  chunks: [...utils.bundleModules], // 需要指定entry chunk 及 menifest
  minChunks: 2 // 指定最小引用次數
})

使用情況如註釋中所言,不再贅述。

1.4 提取非同步入口中的公共模組到單獨檔案

如果我們在專案中使用了非同步載入模組的形式進行程式碼打包,非同步的檔案會作為主入口下的子入口。比較典型的例子就是vue中的非同步路由形式,每一個非同步路由頁面可以作為子入口,而這些入口中的公共程式碼我們可以將其提取出來到一個公共檔案中,從而實現程式碼的精簡。下面看一段路由程式碼:

routes: [
    {
      path: '/',
      name: 'Home',
      // 此處採用非同步路由的形式,第一個引數是路由所對應的元件路徑,最後一個引數是指定[name]佔位符
      component: resolve => require.ensure(['@/modules/moduleA/Home'], resolve, 'moduleA/js/home')
    },
    {
      path: '/add',
      name: 'Add',
      component: resolve => require.ensure(['@/components/Add'], resolve, 'moduleA/js/add')
    }
]

再貼一段webpack配置程式碼:

// 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({
  names: [...utils.bundleModules],
  async: 'vendor-async',
  children: true,
  minChunks: 2
})

以上兩段程式碼會根據commonsChunkPlugin中names所指定的入口chunk名稱進行分別提取,例如names中指定的值為['moduleA', 'moduleB'],webpack會分別找到moduleA和moduleB兩個主入口,然後分別在兩個主入口中查詢非同步模組,如果他們各自的非同步模組中有共通引用的模組,則這些公共模組會被提取到一個名為vendr-async-moduleA和vendr-async-moduleB的兩個資料夾中。

通過以上四個步驟我們基本上可以將常用的公共模組提取到指定的檔案中,並且通過commonsChunkPlugin,webpack會自動將依賴檔案注入到index.html檔案中。完成程式碼的分割操作。

2、HTMLWebpackPlugin

htmlWebpackPlugin這個外掛是用來配置主入口html檔案的,具體功能可以通過官方檔案瞭解,這裡不再贅述。此處需要說明的是在多入口中htmlWebpackPlugin的配置。

下面貼上vue-cli中的配置:

// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
    filename: config.build.index,
    template: 'index.html',
    inject: true,
    minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
        // more options:
        // https://github.com/kangax/html-minifier#options-quick-reference
    },
    // necessary to consistently work with multiple chunks via CommonsChunkPlugin
    chunksSortMode: 'dependency'
})

上面的配置中指定了template和filename,filename是打包後輸出的檔名,filename值是一個字串,所以可以配置輸出的路徑和檔名,template用於指定模板檔案,同樣可以通過字串的形式指定template的路徑和檔名。所以我們可以根據不同的入口配置多個htmlWebpackPlugin,並且指定不同的輸出路徑,這樣就可以將多入口的打包的index.html檔案進行區分了。

在webpack的配置中plugins是一個數組,此處我們可以通過迴圈的方式生成多個htmlWebpackPlugin陣列,然後將生成的陣列與plugins陣列進行合併。多入口配置如下:

// 生成htmlWebpackPlugin陣列
const htmlOutput = (function () {
    // utils.bundleModules 是打包的模組列表
    return utils.bundleModules.map(item => {
    // 指定 template 路徑
    let template = './src/modules/' + item + '/index.html'
    return new HtmlWebpackPlugin({
        // 指定每個index.html 的生成路徑,現在的規則下是生成到dist目錄下與入口chunk name相同的資料夾下
        filename: path.resolve(__dirname, '../dist/' + item + '/index.html'),
        template,
        // 此處需要指定 chunks 的值,如果不指定該值,則會預設將所有生成js檔案都注入到index.html檔案中。實際上我們只希望得到跟當前入口相關的js檔案
        chunks: ['manifest', 'common', 'vendor', item],
        // 對檔案實行壓縮混淆等操作
        minify: {
            removeComments: true,
            collapseWhitespace: true,
            removeAttributeQuotes: true
        },
        // necessary to consistently work with multiple chunks via CommonsChunkPlugin
        chunksSortMode: 'dependency'
    })
  })
}())
// ...
// 將生成的htmlOutput 陣列拼接到 plugins中
plugins: [
    // 其他外掛 ...
    ...htmlOutput
]

通過上面的配置我們就可以順利將index.html 檔案分別打包到我們指定的目錄當中。

3、ExtractTextPlugin

通過以上兩個配置我們完成了js檔案和html檔案的分離,並且將他們打包到了指定的目錄下。最終我們還剩下css檔案沒有進行處理。css檔案我們通過extractTextPlugin這個外掛進行處理,相較於vue-cli中的預設配置,我們改動較小。只需要根據入口來指定打包的位置,見程式碼:

// vue-cli預設配置,
// extract css into its own file
new ExtractTextPlugin({
    // 所有的檔案都統一打包到 dist目錄下的css資料夾下
    filename: utils.assetsPath('css/[name].[contenthash].css'),
    // Setting the following option to `false` will not extract CSS from codesplit chunks.
    // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
    // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 
    // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
    allChunks: true
})
// 多入口配置
new ExtractTextPlugin({
    // 將每個入口的css檔案提取到各自獨立的資料夾下
    // 這裡的[name]就是佔位符,表示的是 entry中的入口名稱
    filename: utils.assetsPath('[name]/css/[name].[contenthash].css'),
    // Setting the following option to `false` will not extract CSS from codesplit chunks.
    // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
    // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 
    // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
    allChunks: true
})

小結

通過以上三個外掛我們基本可以將我們需要的程式碼打包的指定的路徑下,並且完成index.html中的檔案引用正常,下面我們要做的就是將這三個外掛串起來,完成多入口專案的打包。