Skip to content
This repository was archived by the owner on Mar 22, 2019. It is now read-only.

code splitting.cn

e-cloud edited this page Jul 12, 2016 · 4 revisions

For big web apps it's not efficient to put all code into a single file, especially if some blocks of code are only required under some circumstances.

对于大型web应用来说,将所有代码都放进一个文件里面是低效的,尤其是有些代码只在某种情况下需要用到。

Webpack has a feature to split your codebase into "chunks" which are loaded on demand. Some other bundlers call them "layers", "rollups", or "fragments". This feature is called "code splitting".

webpack有个特性可用来将你的代码库分割成chunk(块)以按需加载。其他打包工具将其称为"layers", "rollups", 或者 "fragments"。webpack的这个特性叫作“代码分割”。

It's an opt-in feature. You can define split points in your code base. Webpack takes care of the dependencies, output files and runtime stuff.

这是一个可选功能。你可以在你的代码库里定义分割点。 Webpack负责处理依赖,文件输出以及运行时的事情 。

To clarify a common misunderstanding: Code Splitting is not just about extracting common code into a shared chunk. The more notable feature is that Code Splitting can be used to split code into an on demand loaded chunk. This can keep the initial download small and downloads code on demand when requested by the application.

澄清一个常见误解:代码分割不只是提取公共代码到共享块。更值得关注的特点是,代码分割可用于将代码分成一个按需加载的块。这可以保证初始下载流量小,并在应用请求时按需加载其他代码。

Defining a split point

定义分割点

AMD and CommonJs specify different methods to load code on demand. Both are supported and act as split points:

AMD和CommonJS定义了不同的方法来按需加载代码。两者都被支持,也作为分割点的存在形式:

CommonJs: require.ensure

require.ensure(dependencies, callback)

The require.ensure method ensures that every dependency in dependencies can be synchronously required when calling the callback. callback is called with the require function as parameter.

require.ensure方法确保了dependencies中每个dependency在调用callback时可以同步引用到。 callback调用时,require函数被注入作为参数。

Example:

require.ensure(["module-a", "module-b"], function(require) {
	var a = require("module-a");
	// ...
});

Note: require.ensure only loads the modules, it doesn't evaluate them.

注意:require.ensure只加载模块,它并没有执行。

AMD: require

The AMD spec defines an asynchronous require method with this definition:

AMD规范定义了一个异步的require方法:

require(dependencies, callback)

When called, all dependencies are loaded and the callback is called with the exports of the loaded dependencies.

当被调用时,所有dependencies都被加载,callback被调用时会被注入加载了的dependencies的输出值。

Example:

require(["module-a", "module-b"], function(a, b) {
	// ...
});

Note: AMD require loads and evaluate the modules. In webpack modules are evaluated left to right.

注意:AMD的require会加载并执行模块。在webpack中模块被从左到右依次执行。

Note: It's allowed to omit the callback.

注意:忽略回调函数是允许的。

ES6 Modules

ES6 模块

tl;dr: Webpack doesn't support ES6 modules; use require.ensure or require directly depending on which module format your transpiler creates.

tl;dr(太长不要看):webpack并不支持es6模块,使用require.ensurerequire中哪一个直接取决于transpiler(转译器)创建的模块格式

Webpack 1.x.x (coming in 2.0.0!) does not natively support or understand ES6 modules. However, you can get around that by using a transpiler, like Babel, to turning the ES6 import syntax into CommonJs or AMD modules. This approach is effective but has one important caveat for dynamic loading.

Webpack1.x.x2.0.0快来了!)本身并不支持或理解ES6模块。但是,你可以通过使用transpiler,像Babel,以将ES6import语法转换成到CommonJS的或AMD模块。这种方法是有效的,但是对动态加载有一点重要缺陷。

The module syntax addition (import x from 'foo') is intentionally designed to be statically analyzable, which means that you cannot do dynamic imports.

新增的模块语法(import x from 'foo')是有意设计成可静态分析的。这意味着你不能动态引入模块。

// INVALID!!!!!!!!!
['lodash', 'backbone'].forEach(name => import name )

Luckily, there is a JavaScript API "loader" specification being written to handle the dynamic use case: System.load (or System.import). This API will be the native equivalent to the above require variations. However, most transpilers do not support converting System.load calls to require.ensure so you have to do that directly if you want to make use of dynamic code splitting.

辛运的是,有个Javascript API "loader"的规范在修订中,以处理动态场景:System.load (or System.import)。这个API本质上等价于上述的require变体。但是,大部分转译器不支持将System.load 的调用转换成 require.ensure,所以如果你想使用动态代码分割,你需要自己手动转换。

//static imports
import _ from 'lodash'

// dynamic imports
require.ensure([], function(require) {
  let contacts = require('./contacts')
})

Chunk content

块的内容

All dependencies at a split point go into a new chunk. Dependencies are also recursively added.

一个分割点的所有依赖都存入一个新的块。依赖会递归地添加。

If you pass a function expression (or bound function expression) as callback to the split point, webpack automatically puts all dependencies required in this function expression into the chunk too.

如果你传入一个函数表达式(或约​​束函数表达式)作为分割点的回调函数,webpack自动将该表达式内用到的依赖都放进该块。

Chunk optimization

块优化

If two chunks contain the same modules, they are merged into one. This can cause chunks to have multiple parents.

如果两个块包含同一个模块,它们会被合成一个,这会导致块有很多父模块。

If a module is available in all parents of a chunk, it's removed from that chunk.

如果一个模块在一个块的所有父模块可用,它将被移出该块。

If a chunk contains all modules of another chunk, this is stored. It fulfills multiple chunks.

如果一个块包含另一个块的所有模块,仍会被存储。因为可能被多个块依赖。[此处翻译不确定]

Chunk loading

块的加载

Depending on the configuration option target a runtime logic for chunk loading is added to the bundle. I. e. for the web target chunks are loaded via jsonp. A chunk is only loaded once and parallel requests are merged into one. The runtime checks for loaded chunks whether they fulfill multiple chunks.

根据配置选项target,块加载的运行时逻辑会被加入到打包文件。例如,对于选项值为web时,块是通过JSONP来加载的。一个块只会被加载一次,并行的请求会被合并成一个。运行时会检查加载了的块是否满足多个块的依赖。

Chunk types

块类型

Entry chunk

入口块

An entry chunk contains the runtime plus a bunch of modules. If the chunk contains the module 0 the runtime executes it. If not, it waits for chunks that contains the module 0 and executes it (every time when there is a chunk with a module 0).

一个入口块包含了运行时再加上一堆模块。如果该块包含编号为0的模块,运行时会将其执行。如果没有,它会等待包含编号为0的模块并将其执行(每当有一个编号为0的模块)

Normal chunk

普通块

A normal chunk contains no runtime. It only contains a bunch of modules. The structure depends on the chunk loading algorithm. I. e. for jsonp the modules are wrapped in a jsonp callback function. The chunk also contains a list of chunk id that it fulfills.

一个正常的块不包含运行时。它只包含一堆模块。其结构取决于块加载算法。例如,对于JSONP的模块,会被封装进一个JSONP的回调函数内。这类块还包含一个列表,存储其它依赖于该块的块的id。

Initial chunk (non-entry)

初始块(非入口)

An initial chunk is a normal chunk. The only difference is that optimization treats it as more important because it counts toward the initial loading time (like entry chunks). That chunk type can occur in combination with the CommonsChunkPlugin.

初始块是一个普通的块。唯一的区别是,优化模块对其更为重视,因为它会计算初始加载时间(像入口块一样)。这种块的类型会出现在与CommonsChunkPlugin的结合使用。

Split app and vendor code

分离应用与库代码

To split your app into 2 files, say app.js and vendor.js, you can require the vendor files in vendor.js. Then pass this name to the CommonsChunkPlugin as shown below.

要想将你的应用分成两个文件,譬如说app.jsvendor.js,你可以在vendor.jsrequire那些库代码,然后将该文件名传给CommonsChunkPlugin(如下所示)。

var webpack = require("webpack");

module.exports = {
  entry: {
    app: "./app.js",
    vendor: ["jquery", "underscore", ...],
  },
  output: {
    filename: "bundle.js"
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin(/* chunkName= */"vendor", /* filename= */"vendor.bundle.js")
  ]
};

This will remove all modules in the vendor chunk from the app chunk. The bundle.js will now contain just your app code, without any of its dependencies. These are in vendor.bundle.js.

这会移除app块中所有在vendor块出现的模块。bundle.js现在只会包含你的应用代码,没有任何依赖库模块。依赖的库模块都在vendor.bundle.js中。

In your HTML page load vendor.bundle.js before bundle.js. 在你的HTML页面中,加载bundle.js前先加载vendor.bundle.js

<script src="vendor.bundle.js"></script>
<script src="bundle.js"></script>

Multiple entry chunks

多个入口块

It's possible to configure multiple entry points that will result in multiple entry chunks. The entry chunk contains the runtime and there must only be one runtime on a page (there are exceptions).

可以configure多个入口点来生成多个入口块。入口块包含了运行时,而且一个页面必须只有一个运行时(存在例外情况)

Running multiple entry points

运行多个入口点

With the CommonsChunkPlugin the runtime is moved to the commons chunk. The entry points are now in initial chunks. While only one initial chunk can be loaded, multiple entry chunks can be loaded. This exposes the possibility to run multiple entry points in a single page.

使用CommonsChunkPlugin时,运行时被移到公共块。然后入口点位于初始块。只会有一个初始块被加载,但可以加载多个入口块。这提供了在一个页面运行多个入口点的可能性。

Example:

var webpack = require("webpack");
module.exports = {
	entry: { a: "./a", b: "./b" },
	output: { filename: "[name].js" },
	plugins: [ new webpack.optimize.CommonsChunkPlugin("init.js") ]
}
<script src="init.js"></script>
<script src="a.js"></script>
<script src="b.js"></script>

Commons chunk

公共块

The CommonsChunkPlugin can move modules that occur in multiple entry chunks to a new entry chunk (the commons chunk). The runtime is moved to the commons chunk too. This means the old entry chunks are initial chunks now. See all options in the list of plugins.

CommonsChunkPlugin可以将在多个入口块出现的模块移到另一个入口块(公共块)。运行时也被移到公共块。这意味着旧的入口块现在是初始块了。请参见list of plugins中所有选项。

Optimization

优化

There are optimizing plugins that can merge chunks depending on specific criteria. See list of plugins.

有些优化插件可根据特定标准用来合并块。参见list of plugins

  • LimitChunkCountPlugin
  • MinChunkSizePlugin
  • AggressiveMergingPlugin

Named chunks

具名块

The require.ensure function accepts an additional 3rd parameter. This must be a string. If two split point pass the same string they use the same chunk.

require.ensure函数可额外接受第三个参数。这必须是一个字符串。如果两个分割点传递相同的字符串,它们将使用相同的块。

require.include

require.include(request)

require.include is a webpack specific function that adds a module to the current chunk, but doesn't evaluate it (The statement is removed from the bundle).

require.include是webpack的一个特定方法,用来添加一个模块到当前块,但不执行(打包文件中该调用代码被移除了)

Example:

require.ensure(["./file"], function(require) {
  require("./file2");
});

// is equal to

require.ensure([], function(require) {
  require.include("./file");
  require("./file2");
});

require.include can be useful if a module is in multiple child chunks. A require.include in the parent would include the module and the instances of the modules in the child chunks would disappear.

如果一个模块存在于多个子块中,require.include可能是有用的。父块中出现的require.include会导致包含该模块,而该模块会从子块中消失。

Examples

For a running demo see the example-app. Check Network in DevTools.

运行中的示例可参见example-app。并打开DevTools中的Network面板。

Clone this wiki locally