由 CommonsChunk 到 SplitChunk

webpack3中的CommonsChunkPlugin

先上一套代码:


 











//fuck.js
export const fuck = item => item + 2;

//main1.js
import {fuck} from './fuck';
import $ from 'jquery';
console.log($,`first  ${fuck}`);

//main2.js
import {fuck} from './fuck';
import $ from 'jquery';
console.log($,`second ${fuck}`);

webpack3 中有一个插件CommonsChunkPlugin,打包时可以把多处 import 的模块提取到公共文件,避免重复下载。是用来提取第三方库和公共模块,避免首屏加载的 bundle 文件或者按需加载的 bundle 文件体积过大,从而导致加载时间过长,优化必备。

过程不多说,直接上配置。

new webpack.optimize.CommonsChunkPlugin({
  name: 'vendor',
  minChunks (module) {
    return (        
        module.resource &&
        /\.js$/.test(module.resource) &&
        module.resource.indexOf(
        path.join(__dirname, '../node_modules')
        ) === 0
    )
    }
}),
new webpack.optimize.CommonsChunkPlugin({
    name: 'manifest',
    minChunks: Infinity
}),
new webpack.optimize.CommonsChunkPlugin({
    name: 'app',
    async: 'vendor-async',
    children: true,
    minChunks: 3
}),

有一点需要注意,[hash]适用于开发环境,每次编译都会改变,而[chunkhash]适用于生产环境,为了迎合浏览器的缓存机制。所以在生产环境,output 的: chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')

  • 第一个配置,minChunks的规则是: minChunks: number|Infinity|function(module, count) => boolean 使用上述函数的写法保证了vendor中,只保留第三方库。而不会把runtime webpack运行时的代码包括进去,因为不把runtime剥离出来,本地的代码一旦改变,无论引用的第三方库改不改变,vendor都会变,那样的话生产环境浏览器会再次加载。
  • 再说第二个配置mainfest: 用来引导所有模块的交互。manifest文件包含了加载和处理模块的逻辑,有了它,runtime会根据manifest文件来处理和加载模块
  • 再说第三个配置asyncchildren的使用方法: 包含require.ensure,AMD,import()

#1:[]中是依赖,callback中写入require的东西,最后一个参数是output的chunkFilename
require.ensure([], function(require){
    require('./first.js');
},'name');
#2:返回一个promise
import('./async.js).then((data)=> ... )
  • Lazy Loading,懒加载或者按需加载,属于code split的一部分。当chilren为true的时候,source chunk通过entry chunks进行code split,children 可以用来把 entry chunk 创建的 children chunks 的共用模块合并到自身,但这会导致初始加载时间较长.async就解决了children:true时合并到entry chunks自身时初始加载时间过长的问题,async设为true时,commons chunk 将不会合并到自身,而是使用一个新的异步的commons chunk。当这个children chunk 被下载时,自动并行下载该commons chunk。具体的分析可以看这里
  • chunk分为async和sync

Async chunks are loaded on demand (lazy loaded) and you would see network request for async chunk files in developers tool.

webpack4中的SplitChunkPlugin

webpack文档中有这么一句话

Originally, chunks (and modules imported inside them) were connected by a parent-child relationship in the internal webpack graph. The CommonsChunkPlugin was used to avoid duplicated dependencies across them, but further optimizations were not possible.

每次切割之后,生成一种父子关系,更深一步的优化还是要靠SplitChunkPlugin

借用老外的一篇文章把chunk分为了三种:

  • Vendor Chunk Create separate files for vendor code (3rd party code coming from node_modules). A single vendor.js file will suffice. Any vendor code used inside index.js (import statements of npm modules) will be break from it and form vendor.js which will be loaded synchronously with main.js.

  • Async Chunks Create separate files for code which can be lazy loaded. Like a file for every Route of React router which can be lazy loaded when route is changed. Webpack inject some code into main.js which takes care of lazy loading async chunks and stops from loading same chunks again and again. When route changes, React router calls a Webpack function to load a chunk file and Webpack after done loading runs it, which chunk would internally ask React to do something.

  • Common Chunk Create common file from code which is shared between different chunks. For example, if 10 routes create 10 different async chunks and these chunks have a common import statement, then code associated with that import statement will get injected into respective chunk files separately. That would load same piece of code every single time we change the route, which is not a very good for UX. Instead, we could take out common code shared between different chunks and create common.js file which will be loaded synchronously with main.js beforehand.

webpack's import

webpack自带import(module)来处理异步,返回一个promise,这个module可以是npm也可以是本地的模块

//async.js
export default 'fuck';

//index.js
import('./async.js').then( data => {
    console.log(data)
})

这一步会报错,因为它和es6的import冲突了,需要安装babel-plugin-syntax-dynamic-import; 然后运行,看时间线,会发现0.main.js是异步加载的,这里就包含了async.js中所有export出的value

An image

接下来说import()和vue-router

<BrowserRouter>
    <div>
        <div className="menu">
            <Link exact to="/" activeClassName="active">Home</Link>
            <Link to="/about" activeClassName="active">About</Link>
            <Link to="/contact" activeClassName="active">Contact</Link>
        </div>
        
        <Switch>
            <Route exact path="/" component={ HomeComponent } />
            <Route path="/about" component={ AboutComponent } />
            <Route path="/contact" component={ AsyncContactComponent } />
        </Switch>
    </div>
</BrowserRouter>

代码所示,contact对应的路由,prop传递一个异步组件,我们现在来模拟一下异步加载。

// yarn add react-loadable -S 满足异步组件之前可以显示loading

import loadable from 'react-loadable';

const LoadingComponent = () => <h3>please wait...</h3>;

const ContactComponentPromise = () => import('./ContactComponent');

const AsyncContactComponent = loadable({
    loader: ContactComponentPromise,
    loading: LoadingComponent
})

然后观察时间线,或者页面的表现,会发现1.main.js是异步加载的

下面说SplitChunksPlugin的配置

splitChunks: {
    chunks: "async",//有all, async, and initial 还可以写函数retrun
    // chunks(chunk){
    //     return chunk.name !== 'my-excluded-chunk'
    // }
    minSize: 30000,
    minChunks: 1,//共享该module的最小chunk数
    maxAsyncRequests: 5,
    maxInitialRequests: 3,
    automaticNameDelimiter: '~',
    name: true,
    cacheGroups: {
        vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10
        },
    	default: {
            minChunks: 2,//共享该module的最小chunk数
            priority: -20,//优先级
            reuseExistingChunk: true
        }
    }
}

参考文献1 参考文献2

上次更新: 9/1/2018, 3:31:09 PM