Appearance
webpack
webpack 介绍
- webpack 是一个 JavaScript 应用程序的静态模块打包工具
入口(entry)
- 入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的
输出(output)
- output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件
loader
- webpack 只能理解 JavaScript 和 JSON 文件
- loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中
- 多个 loader,回从右往左执行
插件(plugin)
- loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量
commonjs 模块 简易实现
js
(() => {
let modules = {
"./src/title.js": (module) => {
// commonjs模块
module.exports = "title";
},
};
let cache = {};
function require(moduleId) {
let cacheModule = cache[moduleId];
if (cacheModule) {
return cacheModule.exports;
}
let module = (cache[moduleId] = {
exports: {},
});
modules[moduleId](module, module.exports, require);
return module.exports;
}
(() => {
let title = require("./src/title.js");
console.log(title);
})();
})();
common.js 加载 common.js 模块
js
(() => {
let modules = {
"./src/title.js": (module, exports) => {
// commonjs模块
exports.name = "title_hl";
exports.age = "title_18";
},
};
let cache = {};
function require(moduleId) {
let cacheModule = cache[moduleId];
if (cacheModule) {
return cacheModule.exports;
}
let module = (cache[moduleId] = {
exports: {},
});
modules[moduleId](module, module.exports, require);
return module.exports;
}
(() => {
let title = require("./src/title.js");
console.log(title);
})();
})();
common.js 加载 es6 模块
js
(() => {
let modules = {
"./src/title.js": (module, exports) => {
// es6 模块导出
require.r(exports);
require.d(exports, {
default: () => DEFAULT_EXPORTS, //值是一个getter
age: () => age,
});
// 默认导出
const DEFAULT_EXPORTS = "title";
// 命名导出
const age = 18;
},
};
let cache = {};
function require(moduleId) {
let cacheModule = cache[moduleId];
if (cacheModule) {
return cacheModule.exports;
}
let module = (cache[moduleId] = {
exports: {},
});
modules[moduleId](module, module.exports);
return module.exports;
}
// 判断对象是否具有某个属性
require.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
// 给exports添加属性
require.r = (exports) => {
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
Object.defineProperty(exports, "__esModule", { value: true });
};
// 绑定属性
require.d = (exports, definition) => {
for (let key in definition) {
// 如果definition有,exports没有,则绑定
if (require.o(definition, key) && !require.o(exports, key)) {
// getter
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
});
}
}
};
(() => {
let title = require("./src/title.js");
console.log(title);
})();
})();
es6 模块加载 es6 模块
js
(() => {
let modules = {
"./src/title.js": (module, exports) => {
// es6模块的导出
require.r(exports);
require.d(exports, {
default: () => DEFAULT_EXPORTS, //值是一个getter
age: () => age,
});
// 默认导出
const DEFAULT_EXPORTS = "title";
// 命名导出
let age = 18;
// es module 获取的是最终的值
// 导出的值的引用
setTimeout(() => {
age = 20;
}, 1000);
},
};
let cache = {};
function require(moduleId) {
let cacheModule = cache[moduleId];
if (cacheModule) {
return cacheModule.exports;
}
let module = (cache[moduleId] = {
exports: {},
});
modules[moduleId](module, module.exports);
return module.exports;
}
// 判断对象是否具有某个属性
require.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
// 给exports添加属性
require.r = (exports) => {
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
Object.defineProperty(exports, "__esModule", { value: true });
};
// 绑定属性
require.d = (exports, definition) => {
for (let key in definition) {
// 如果definition有,exports没有,则绑定
if (require.o(definition, key) && !require.o(exports, key)) {
// getter
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
});
}
}
};
let exports = {};
(() => {
//只要打包前的模块是一个es module,那么就会调用require.r方法进行处理
require.r(exports);
let title = require("./src/title.js");
console.log(title);
})();
})();
es6 模块加载 common.js 模块
js
(() => {
let modules = {
"./src/title.js": (module, exports) => {
// commonjs模块
module.exports = {
name: "title_name",
age: "title_age",
};
},
};
let cache = {};
function require(moduleId) {
let cacheModule = cache[moduleId];
if (cacheModule) {
return cacheModule.exports;
}
let module = (cache[moduleId] = {
exports: {},
});
modules[moduleId](module, module.exports);
return module.exports;
}
// 判断对象是否具有某个属性
require.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
// 给exports添加属性
require.r = (exports) => {
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
Object.defineProperty(exports, "__esModule", { value: true });
};
// 绑定属性
require.d = (exports, definition) => {
for (let key in definition) {
// 如果definition有,exports没有,则绑定
if (require.o(definition, key) && !require.o(exports, key)) {
// getter
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
});
}
}
};
require.n = (module) => {
var getter =
module && module.__esModule ? () => module["default"] : () => module;
return getter;
};
let exports = {};
(() => {
//只要打包前的模块是一个es module,那么就会调用require.r方法进行处理
require.r(exports);
let title = require("./src/title.js");
var title_default = require.n(title);
console.log(title_default());
console.log(title.age);
})();
})();
模块异步加载
- 1、通过 chunkId 去查找对应的代码块文件其实就是 hello.main.js
- 2、通过动态创建
script
标签加载 chunk 文件(JSONP 方式) - 3、加载完成后,会调用 webpackJsonpCallback 函数,将 chunk 文件中的模块添加到 modules 中,并且通过 installedChunks 记录 chunk 文件加载状态
js
[动态import()] → [生成chunkId] → [查找chunk文件] → [JSONP加载]
↑ ↓
[Promise返回] ← [触发回调] ← [执行webpackJsonpCallback] ← [chunk执行]
js
let modules = {};
let cache = {};
function require(moduleId) {
let cacheModule = cache[moduleId];
if (cacheModule) {
return cacheModule.exports;
}
let module = (cache[moduleId] = {
exports: {},
});
modules[moduleId](module, module.exports);
return module.exports;
}
// 判断对象是否具有某个属性
require.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
// 给exports添加属性
require.r = (exports) => {
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
Object.defineProperty(exports, "__esModule", { value: true });
};
// 绑定属性
require.d = (exports, definition) => {
for (let key in definition) {
// 如果definition有,exports没有,则绑定
if (require.o(definition, key) && !require.o(exports, key)) {
// getter
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
});
}
}
};
require.f = {};
//返回此文件对应的访问路径
require.p = "";
//返回此代码块对应的文件名
require.u = function (chunkId) {
return chunkId + ".main.js";
};
//存放加载的代码块的状态
//key是代码块的名字
//0表示已经加载完成了
var installedChunks = {
main: 0,
//'hello': [resolve, reject, promise]
};
require.e = (chunkId) => {
let promises = [];
require.f.j(chunkId, promises);
return Promise.all(promises);
};
/**
* 通过JSONP异步加载一个chunkId对应的代码块文件,其实就是hello.main.js
* 会返回一个Promise
* @param {*} chunkId 代码块ID
* @param {*} promises promise数组
*/
require.f.j = function (chunkId, promises) {
//当前的代码块的数据
let installedChunkData;
//创建一个promise
const promise = new Promise((resolve, reject) => {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
installedChunkData[2] = promise;
promises.push(promise);
const url = require.p + require.u(chunkId);
require.l(url);
};
// 加载远程的模块
require.l = function (url) {
let script = document.createElement("script");
script.src = url;
document.head.appendChild(script);
};
var chunkLoadingGlobal = (window["webpack5"] = []);
chunkLoadingGlobal.push = webpackJsonpCallback;
/**
*
* @param {*} chunkIds 代码块ID数组
* @param {*} moreModules 额外的模块定义
*/
function webpackJsonpCallback([chunkIds, moreModules]) {
const resolves = [];
for (let i = 0; i < chunkIds.length; i++) {
const chunkId = chunkIds[i];
resolves.push(installedChunks[chunkId][0]);
installedChunks[chunkId] = 0; //表示此代码块已经下载完毕
}
//合并模块定义到modules去
for (const moduleId in moreModules) {
modules[moduleId] = moreModules[moduleId];
}
//依次取出resolve方法并执行 => promise resolve
while (resolves.length) {
resolves.shift()();
}
}
/**
* require.e异步加载hello代码块文件 hello.main.js
* promise成功后会把 hello.main.js里面的代码定义合并到require.m对象上,也就是modules上
* 调用require方法加载./src/hello.js模块,获取 模块的导出对象,进行打印
*/
require
.e("hello")
.then(require.bind(require, "./src/hello.js"))
.then((result) => {
console.log(result);
});
webpack 编译流程
- 1、初始化参数:从配置文件和 Shell 语句中读取参数并且和 webpack
参数进行合并
,得出最终的配置对象 - 2、用上一步得到的参数
初始化 Compiler
对象 - 3、加载所有的
plugin
配置的插件,调用插件的apply
函数并且传递 Compiler 对象 - 4、执行 Compiler 对象的 run 方法开始执行编译
- 5、执行 Complication 对象的 build, 根据配置中的 entry 找出入口文件
- 6、从入口文件出发,调用所有配置的
Loader
对模块进行编译 - 7、再找出该模块依赖的模块,再
递归
直到所有入口依赖的文件都经过了本步骤的处理 - 8、根据入口和
模块之间的依赖关系
,组装成一个个包含多个模块的 Chunk
- 9、再把每个
Chunk 转换成一个单独的文件
加入到输出列表 - 10、在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
module、chunk、bundlle
- module:各个源码文件,webpack 中一切皆模块
- chunk:多模块合并成的
- bundlle:最终的输出文件