Christian-freedom / StudyNotes2017

学习笔记
0 stars 0 forks source link

CommonsChunkPlugin 插件学习 #5

Open Christian-freedom opened 7 years ago

Christian-freedom commented 7 years ago

CommonsChunkPlugin

new webpack.optimize.CommonsChunkPlugin(options) CommonsChunkPlugin 插件,是一个可选的用于建立一个独立文件(又称作 chunk)的功能,这个文件包括多个入口 chunk 的公共模块。通过将公共模块拆出来,最终合成的文件能够在最开始的时候加载一次,便存起来到缓存中供后续使用。这个带来速度上的提升,因为浏览器会迅速将公共的代码从缓存中取出来,而不是每次访问一个新页面时,再去加载一个更大的文件。

配置

{
  name: string, // or
  names: string[],
  // 这是 common chunk 的名称。已经存在的 chunk 可以通过传入一个已存在的 chunk 名称而被选择。
  // 如果一个字符串数组被传入,这相当于插件针对每个 chunk 名被多次调用
  // 如果该选项被忽略,同时 `options.async` 或者 `options.children` 被设置,所有的 chunk 都会被使用,否则 `options.filename` 会用于作为 chunk 名。

  filename: string,
  // common chunk 的文件名模板。可以包含与 `output.filename` 相同的占位符。
  // 如果被忽略,原本的文件名不会被修改(通常是 `output.filename` 或者 `output.chunkFilename`)

  minChunks: number|Infinity|function(module, count) -> boolean,
  // 在传入  公共chunk(commons chunk) 之前所需要包含的最少数量的 chunks 。
  // 数量必须大于等于2,或者少于等于 chunks的数量
  // 传入 `Infinity` 会马上生成 公共chunk,但里面没有模块。
  // 你可以传入一个 `function` ,以添加定制的逻辑(默认是 chunk 的数量)

  chunks: string[],
  // 通过 chunk name 去选择 chunks 的来源。chunk 必须是  公共chunk 的子模块。
  // 如果被忽略,所有的,所有的 入口chunk (entry chunk) 都会被选择。

  children: boolean,
  // 如果设置为 `true`,所有  公共chunk 的子模块都会被选择

  async: boolean|string,
  // 如果设置为 `true`,一个异步的  公共chunk 会作为 `options.name` 的子模块,和 `options.chunks` 的兄弟模块被创建。
  // 它会与 `options.chunks` 并行被加载。可以通过提供想要的字符串,而不是 `true` 来对输出的文件进行更换名称。

  minSize: number,
  // 在 公共chunk 被创建立之前,所有 公共模块 (common module) 的最少大小。
}

webpack1 构造函数new webpack.optimize.CommonsChunkPlugin(options, filenameTemplate, selectedChunks, minChunks)不再被支持。请使用相应的选项对象。

例子

公共chunk 用于 入口chunk (entry chunk)

生成一个额外的 chunk 包含入口chunk 的公共模块。

new webpack.optimize.CommonsChunkPlugin({
  name: "commons",
  // ( 公共chunk(commnons chunk) 的名称)

  filename: "commons.js",
  // ( 公共chunk 的文件名)

  // minChunks: 3,
  // (模块必须被3个 入口chunk 共享)

  // chunks: ["pageA", "pageB"],
  // (只使用这些 入口chunk)
})

你必须在 入口chunk 之前加载生成的这个 公共chunk:

<script src="commons.js" charset="utf-8"></script>
<script src="entry.bundle.js" charset="utf-8"></script>

明确第三方库 chunk

将你的代码拆分成公共代码和应用代码。

entry: {
  vendor: ["jquery", "other-lib"],
  app: "./entry"
}
new webpack.optimize.CommonsChunkPlugin({
  name: "vendor",

  // filename: "vendor.js"
  // (给 chunk 一个不同的名字)

  minChunks: Infinity,
  // 随着 入口chunk 越来越多,这个配置保证没其它的模块会打包进 公共chunk
})
<script src="vendor.js" charset="utf-8"></script>
<script src="app.js" charset="utf-8"></script>

提示:结合长期缓存,你可能需要使用这个插件去避免 公共chunk 改变。 你也需要使用 records 去保持稳定的模块 id。

将公共模块打包进父 chunk

使用代码拆分功能,一个 chunk 的多个子 chunk 会有公共的依赖。为了防止重复,可以将这些公共模块移入父 chunk。这会减少总体的大小,但会对首次加载时间产生不良影响。如果预期到用户需要下载许多兄弟 chunks(例如,入口 trunk 的子 chunk),那这对改善加载时间将非常有用。

new webpack.optimize.CommonsChunkPlugin({
  // names: ["app", "subPageA"]
  // (选择 chunks,或者忽略该项设置以选择全部 chunks)

  children: true,
  // (选择所有被选 chunks 的子 chunks)

  // minChunks: 3,
  // (在提取之前需要至少三个子 chunk 共享这个模块)
})

额外的异步 公共chunk

与上面的类似,但是并非将公共模块移动到父 chunk(增加初始加载时间),而是使用新的异步加载的额外公共chunk。当下载额外的 chunk 时,它将自动并行下载。

new webpack.optimize.CommonsChunkPlugin({
  // names: ["app", "subPageA"]
 // (选择 chunks,或者忽略该项设置以选择全部 chunks)

  children: true,
  // (选择所有被选 chunks 的子 chunks)

  async: true,
  // (创建一个异步 公共chunk)

  // minChunks: 3,
  // (在提取之前需要至少三个子 chunk 共享这个模块)
})

给 minChunks 配置传入函数

你也可以给 minChunks 传入一个函数。这个函数会被 CommonsChunkPlugin 插件回调,并且调用函数时会传入 module 和 count 参数。

module 参数代表每个 chunks 里的模块,这些 chunks 是你通过 name/names 参数传入的。 module has the shape of a NormalModule, which has two particularly useful properties for this use case:

module.context: The directory that stores the file. For example: '/my_project/node_modules/example-d·ependency'
module.resource: The name of the file being processed. For example: '/my_project/node_modules/example-dependency/index.js'

count 参数表示 module 被使用的 chunk 数量。

当你想要对 CommonsChunk 如何决定模块被打包到哪里的算法有更为细致的控制, 这个配置就会非常有用。

new webpack.optimize.CommonsChunkPlugin({
  name: "my-single-lib-chunk",
  filename: "my-single-lib-chunk.js",
  minChunks: function(module, count) {
    // 如果模块是一个路径,而且在路径中有 "somelib" 这个名字出现,
    // 而且它还被三个不同的 chunks/入口chunk 所使用,那请将它拆分到
    // 另一个分开的 chunk 中,chunk 的 keyname 是 "my-single-lib-chunk",而文件名是 "my-single-lib-chunk.js"
    return module.resource && (/somelib/).test(module.resource) && count === 3;
  }
});

正如上面看到的,这个例子允许你只将其中一个库移到一个分开的文件当中,当而仅当函数中的所有条件都被满足了。

This concept may be used to obtain implicit common vendor chunks:

new webpack.optimize.CommonsChunkPlugin({
  name: "vendor",
  minChunks: function (module) {
    // this assumes your vendor imports exist in the node_modules directory
    return module.context && module.context.indexOf("node_modules") !== -1;
  }
})

In order to obtain a single CSS file containing your application and vendor CSS, use the following minChunks function together with ExtractTextPlugin:

new webpack.optimize.CommonsChunkPlugin({
  name: "vendor",
  minChunks: function (module) {
    // This prevents stylesheet resources with the .css or .scss extension
    // from being moved from their original chunk to the vendor chunk
    if(module.resource && (/^.*\.(css|scss)$/).test(module.resource)) {
      return false;
    }
    return module.context && module.context.indexOf("node_modules") !== -1;
  }
})

Manifest file

To extract the webpack bootstrap logic into a separate file, use the CommonsChunkPlugin on a name which is not defined as entry. Commonly the name manifest is used. See the code splitting libraries guide for details.

new webpack.optimize.CommonsChunkPlugin({
  name: "manifest",
  minChunks: Infinity
})

Combining implicit common vendor chunks and manifest file

Since the vendor and manifest chunk use a different definition for minChunks, you need to invoke the plugin twice:

[
  new webpack.optimize.CommonsChunkPlugin({
    name: "vendor",
    minChunks: function(module){
      return module.context && module.context.indexOf("node_modules") !== -1;
    }
  }),
  new webpack.optimize.CommonsChunkPlugin({
    name: "manifest",
    minChunks: Infinity
  }),
]

More Examples

Common and Vendor Chunks Multiple Common Chunks Multiple Entry Points with Commons Chunk

Christian-freedom commented 7 years ago

例子1:http://blog.csdn.net/github_26672553/article/details/52280655

var HtmlWebpackPlugin = require('html-webpack-plugin');
var webpack = require('webpack');

var extractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
    // entry是入口文件,可以多个,代表要编译那些js
    //entry:['./src/main.js','./src/login.js','./src/reg.js'],

    entry:
    {
        'main':'./src/main.js',
        'user':['./src/login.js','./src/reg.js'],
        'index':['./src/index.js']
    },

    externals:{
        'jquery':'jQuery'
    },

    module:{
        loaders:[
            // {test:/\.css$/,loader:'style-loader!css-loader'},
            {test:/\.css$/,loader:extractTextPlugin.extract('style','css')}
        ],
    },

    output:{
        path: __dirname+'/build/js', // 输出到那个目录下(__dirname当前项目目录)
        filename:'[name].js' //最终打包生产的文件名
    },

    plugins:[
        new HtmlWebpackPlugin({
            filename: __dirname+'/build/html/login-build.html',
            template:__dirname+'/src/tpl/login.html',
            inject:'body',
            hash:true,
            chunks:['main','user','common.js']   // 这个模板对应上面那个节点
        }),

        new HtmlWebpackPlugin({
            filename: __dirname+'/build/html/index-build.html',
            template:__dirname+'/src/tpl/index.html',
            inject:'body',
            hash:true,
            chunks:['index','common.js']   // 这个模板对应上面那个节点
        }),

        // css抽取
        new extractTextPlugin("[name].css"),

        // 提供公共代码
        new webpack.optimize.CommonsChunkPlugin('common.js'), // 默认会把所有入口节点的公共代码提取出来,生成一个common.js
    ]
};

CommonsChunkPlugin的使用如下:

new webpack.optimize.CommonsChunkPlugin({
//这段代码的意思是,从上面的entry中的三个入口文件,生成的最终的三个bundles文件,main.js和
user.js和index.js三个文件中,提取出一个在三个文件中都存在的代码,作为一个公共块common.js
            name:'common', // 注意不要.js后缀
            chunks:['main','user','index']
        }),

这段代码的意思是,从上面的entry中的三个入口文件,生成的最终的三个bundles文件,main.js和 user.js和index.js三个文件中,提取出一个在三个文件中都存在的代码,作为一个公共块common.js

通过CommonsChunkPlugin,我们把公共代码专门抽取到一个common.js,这样业务代码只在index.js,main.js,user.js。

Christian-freedom commented 7 years ago

这两个关于CommonsChunkPlugin的解释更加详细一点:
https://segmentfault.com/q/1010000009070061 http://www.jianshu.com/p/2b81262898a4

//////////////////////////第一:[name]的含义///////////////////////////////////////

这是一个我们需要使用的公共组件foo.js

//foo.js
export let foo = 123;

---------------------------情况A:只有一个入口文件----------------------------

//入口文件
  entry: {
    page1: './src/js/entry1.js'
  }

//提取公共chuck的name

  new CommonsChunkPlugin({ name: 'vendor' })

然后我们引入foo.js

//entry1.js
import { foo } from './components/foo.js';
console.log(`${foo}-entry1`);

结果:foo.js会和entry1被打包到一起变成page1,webpack的运行文件会被抽离出来成为vendor.js

理解=>{
这个结果的意思是:把webpack运行时(Runtime)代码单独的提取出来,创建了一个 vendor.js文件 }
---------------------------情况B:两个入口文件----------------------------

//入口文件
  entry: {
    page1: './src/js/entry1.js',
    page2: './src/js/entry2.js'
  }
//提取公共chuck的name
  new CommonsChunkPlugin({ name: 'vendor' })

然后entry1和entry2都引入foo.js

//entry1.js
import { foo } from './components/foo.js';
console.log(`${foo}-entry1`);
//entry2.js
import同上
console.log(`${foo}-entry2`);

结果:entry1和entry2会分别被打包成page1、page2,webpack运行文件和foo.js会被抽取成为vendor
理解=>{
这个结果的意思是:因为entry1和entry2两个文件都引用了foo.js这个文件,所以正常情况下, page1中会有entry1和foo.js的代码,而page2中会有entry2和foo.js的代码。而webpack的运行时代码, 肯定在page1和page2中都有,所以如果要提取出共同的东西也就是vendor。那么最终vendor就会把page1和page2中的共同的东西都提取出来,也就是foo.js和两个文件中存在的webpack运行时代码,这样 生成了一个vendor.js
}
---------------------------情况C:有的入口没引用foo.js----------------------------

//入口文件
  entry: {
    page1: './src/js/entry1.js',//引用foo.js
    page2: './src/js/entry2.js',//引用foo.js
    page3: './src/js/entry3.js',//新增入口,不引用foo.js
  }
//提取公共chuck的name
  new CommonsChunkPlugin({ name: 'vendor' })

结果:会生成page123三个js文件,并且foo.js会被包含在page1和page2中,并没有被抽离出来。
vendor.js里则只有webpack运行文件,等于公共模块功能未实现.

此时就体现出在入口中指定公共模块的重要性了,就是下面的情况D
---------------------------情况D:指定公共文件入口----------------------------

//入口文件
  entry: {
    page1: './src/js/entry1.js',//不引用foo.js
    page2: './src/js/entry2.js',//引用foo.js
    page3: './src/js/entry3.js',//引用foo.js
    'vendor': ['./src/js/components/foo.js']
  }
//提取公共chuck的name
  new CommonsChunkPlugin({ name: 'vendor' })

此时我们指定了新入口vendor,并且它指向foo.js
三个入口有的引用了foo,有的则没有

结果:不管entry1/2/3是否引用foo,foo.js和webpack运行文件都被抽取出来成为vendor.
所以此方式可以用来抽取第三方类库和框架,这样每一个入口文件无论是否调用它们,都不会将它们重复打包进去,例如:

'vendor': ['react','jquery','others']

---------------------------情况E:name指定的chunk不存在----------------------------
条件:

1、入口文件里没写vendor:[...]
2、entry1和entry2都没引用任何文件
结果:那么这样会发现生成的vendor.js里是webpack运行文件加一个([])空chunk;

综上: name的意思就是,提取出来的东西,公共的东西就是name.js //////////////////////////第二:[filename]的含义///////////////////////////////////////

这个很简单,filename就是打包出来的公共模块的实际名称

1、当filename未定义,就默认使用name的名称作为打包后文件名2、当filename已指定,就用它指定的名称了
//////////////////////////第三:[minchunks]////////////////////////////////////

minchunks表示模块抽取的最小被调用次数,也就是说minchunks:10,那么这个chunk最少需要被调用10次,webpack才会把它当做一个公共模块抽取出来。
//////////////////////////*第三:[chunks]////////////////////////////////////
前面的都是把公共模块打包成了一整块vendor.js,那么如果需要从这个vendor.js中再抽取就需要用到chunks选项
意思就是:已经从自己写的模块中抽取出了公共的模块,这个公共的模块叫做vendor.js,然后我还想从这个公共的模块中再次抽取东西,比如我想把webpack的运行时代码抽取出来,那么就使用chunks这个参数,那么比如:

//首先从所有的自己定制的模块中提取出公共的模块
 new CommonsChunkPlugin({ name: 'vendor' }),
 //从上面生成的公共的模块vendor.js中,再次提取公共的东西,这里提取的就是webpack运行时
代码,因为这个东西每次编译的时候都会改变,所以把它单独的提取出来。
所以下面的东西的意思是:从vendor.js这个块中提出公共的运行时代码到manifest.js文件中
 new CommonsChunkPlugin({ name: 'manifest', chunks: ['vendor'] }),
//入口文件
  entry: {
    page1: './src/js/entry1.js',
    page2: './src/js/entry2.js',
    'vendor': ['./src/js/components/foo.js']
  }
//公共模块
    new CommonsChunkPlugin({ name: 'vendor' }),
    new CommonsChunkPlugin({ name: 'manifest', chunks: ['vendor'] }),

结果:foo.js和webpack的运行文件会先被生成vendor.js,然后webpack的运行文件会被从vendor中再次抽出,生成一个manifest.js文件
上面的写法也等于:

        new webpack.optimize.CommonsChunkPlugin({
            names: ['vendor', 'manifest'],
        }),
Christian-freedom commented 7 years ago

解决webpack运行时代码的问题: webpack打包第三方类库的正确姿势:https://segmentfault.com/a/1190000005828579


================没看====================
webpack代码分割技巧:http://www.tuicool.com/articles/UZRF3qM 看完这篇就看懂了很多webpack脚手架:https://segmentfault.com/a/1190000007972133
================没看====================

Christian-freedom commented 7 years ago
 /**
       * Plugin: CommonsChunkPlugin
       * Description: Shares common code between the pages.
       * It identifies common modules and put them into a commons chunk.
       *
       * See: https://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin
       * See: https://github.com/webpack/docs/wiki/optimization#multi-page-app
       */
      new CommonsChunkPlugin({
        name: 'polyfills',
        chunks: ['polyfills']
      }),
      /**
       * This enables tree shaking of the vendor modules
       */
      // new CommonsChunkPlugin({
      //   name: 'vendor',
      //   chunks: ['main'],
      //   minChunks: module => /node_modules/.test(module.resource)
      // }),
      /**
       * Specify the correct order the scripts will be injected in
       */
      // new CommonsChunkPlugin({
      //   name: ['polyfills', 'vendor'].reverse()
      // }),
      // new CommonsChunkPlugin({
      //   name: ['manifest'],
      //   minChunks: Infinity,
      // }),

这是angular starter中的webpack.common.js中的代码。原来是全部放开的,但是今天看到的版本是,作者注释掉了其他的,只是保留了:

new CommonsChunkPlugin({
        name: 'polyfills',
        chunks: ['polyfills']
      }),

这段代码的意思是:从polyfills.js中提取出一个叫做polyfills.js的公共模块。 而入口的配置是:

entry: {

      'polyfills': './src/polyfills.browser.ts',
      'main':      AOT ? './src/main.browser.aot.ts' :
                  './src/main.browser.ts'

    },