-
Notifications
You must be signed in to change notification settings - Fork 0
code splitting.cn
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.
澄清一个常见误解:代码分割不只是提取公共代码到共享块。更值得关注的特点是,代码分割可用于将代码分成一个按需加载的块。这可以保证初始下载流量小,并在应用请求时按需加载其他代码。
AMD and CommonJs specify different methods to load code on demand. Both are supported and act as split points:
AMD和CommonJS定义了不同的方法来按需加载代码。两者都被支持,也作为分割点的存在形式:
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
只加载模块,它并没有执行。
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.
注意:忽略回调函数是允许的。
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.ensure
或require
中哪一个直接取决于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.x
(2.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')
})
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自动将该表达式内用到的依赖都放进该块。
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.
如果一个块包含另一个块的所有模块,仍会被存储。因为可能被多个块依赖。[此处翻译不确定]
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来加载的。一个块只会被加载一次,并行的请求会被合并成一个。运行时会检查加载了的块是否满足多个块的依赖。
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
的模块)
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。
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
的结合使用。
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.js
和 vendor.js
,你可以在vendor.js
中require
那些库代码,然后将该文件名传给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>
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多个入口点来生成多个入口块。入口块包含了运行时,而且一个页面必须只有一个运行时(存在例外情况)
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>
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中所有选项。
There are optimizing plugins that can merge chunks depending on specific criteria. See list of plugins.
有些优化插件可根据特定标准用来合并块。参见list of plugins
LimitChunkCountPlugin
MinChunkSizePlugin
AggressiveMergingPlugin
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(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
会导致包含该模块,而该模块会从子块中消失。
- Simple
- with bundle-loader
- with context
- with amd and context
- with deduplication
- named-chunks
- multiple entry chunks
- multiple commons chunks
For a running demo see the example-app. Check Network in DevTools.
运行中的示例可参见example-app。并打开DevTools中的Network面板。