Code Splitting是Webpack的核心功能,其主要作用:
- 管理加载顺序
- 合并相同代码
- 模块管理
Code Splitting维护了两个依赖树,一个是cmd标准的同步依赖树,另外一个是amd标准的异步依赖树。
当使用同步依赖的适合,使用require(path)
,path是模块的路径
当需要使用异步依赖的适合,使用require.ensure([deps], callback)
,deps
是依赖的模块,callback
是回调,当deps里面的模块加载完毕后,执行callback
回调函数。
-
a
和b
通过CommonJs同步require。 -
c
调用require.ensure
保证下载并加载c后,执行回调函数。 -
b
和d
是通过CommonJs同步加载,其中b
的代码块,可以检测到在调用c之前已经被加载,代码块将会忽略,因为已经传输过了。
main.js
var a = require("a");
var b = require("b");
require.ensure(["c"], function(require) {
require("b").xyz();
var d = require("d");
});
对于单纯CommonJs的逻辑产生的ouput代码比较简单。
核心流程是:
- 调度entry入口函数,
return __webpack_require__(0)
。 - 执行主流程函数。
/***/ function(module, exports, __webpack_require__) {
var a = __webpack_require__(/*! a */ 1);
var b = __webpack_require__(/*! b */ 2);
/***/ },
- 按顺序通过
__webpack_require__
函数加载模块。
根据moduleId
来寻找可执行的模块,如果发现缓存区内,则返回该模块结果,如果没有,则执行该模块,并将结果返回,并将模块结果保存到内存中。
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
- 当遇到
require.ensure
的时候,如果遇到amd加载的时候,需要调用ensure.require
。
__webpack_require__.e/* nsure */(1, function(require) {
__webpack_require__(/*! b */ 2).xyz();
var d = __webpack_require__(/*! d */ 4);
});
__webpack_require__.e(chunkId, callback)
逻辑分析
判断chunkId
是否在已经加载过,如果已经加载过模块,直接返回。如果还没加载,加载script,如果加载了,但没有执行,则等待执行。
/******/ __webpack_require__.e = function requireEnsure(chunkId, callback) {
/******/ // "0" is the signal for "already loaded"
/******/ if(installedChunks[chunkId] === 0)
/******/ return callback.call(null, __webpack_require__);
/******/ // an array means "currently loading".
/******/ if(installedChunks[chunkId] !== undefined) {
/******/ installedChunks[chunkId].push(callback);
/******/ } else {
/******/ // start chunk loading
/******/ installedChunks[chunkId] = [callback];
/******/ var head = document.getElementsByTagName('head')[0];
/******/ var script = document.createElement('script');
/******/ script.type = 'text/javascript';
/******/ script.charset = 'utf-8';
/******/ script.async = true;
/******/ script.src = __webpack_require__.p + "" + chunkId + ".output.js";
/******/ head.appendChild(script);
/******/ }
/******/ };
- 通过require.ensure异步加载目标script,执行script。
webpackJsonp([1],[,,,function(n,t){},function(n,t){}]);
判断chunkIds列表里面的模块是否加载完成,如果完成,执行模块,并将模块放进去modules
里,方便后面的模块进行读取。
/******/ window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) {
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ var moduleId, chunkId, i = 0, callbacks = [];
/******/ for(;i < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(installedChunks[chunkId])
/******/ callbacks.push.apply(callbacks, installedChunks[chunkId]);
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ modules[moduleId] = moreModules[moduleId];
/******/ }
/******/ if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);
/******/ while(callbacks.length)
/******/ callbacks.shift().call(null, __webpack_require__);
/******/ };
最后,完整代码
https://github.com/webpack/webpack/tree/master/examples/code-splitting
总结
对于webpack的代码编译的核心思想不难理解,webpack会维持一堆代码块modules
,代码块0,是所有代码的开端,然后通过id 寻找需要加载的代码块,如果遇到amd加载的代码块,需要把代码块回写到modules里。