vscode-loader 的使用示例
VsCode 源码使用 vscode-loader 加载模块(异步模块定义 (AMD) 加载器的一种实现);vscode-loader 支持在浏览器以及 node 中使用,在两种环境的使用方式基本一致。 本文是对其在浏览器环境运行进行分析。
html:加载 loader.js,再调用 require 方法,加载 test 依赖。
<script type="text/javascript" src="./src/loader.js"></script>
<script>
require(['test'], function (test) {
console.log(test.compare(7, 5));
});
</script>
定义模块:
// test.js
define('test', function() {
return {
compare: function(a, b) {
return a > b;
}
}
});
define 方法:define(id?, dependencies?, factory);
,第一个参数是模块名,第二个参数是依赖,第三个参数是模块的工厂函数,返回定义的模块。
可以看到,和 requirejs 不同点在于,vscode-loader 不通过 data-main 指定入口文件,而是直接加载模块。
vscode-loader 的代码结构
不同于 requirejs,vscode-loader 的源码是用 ts 编写的,再通过 yarn compile
打包成 loader.js。所以,我们先看 vscode-loader/src/core 的代码结构。
<!-- todo -->
├── core # 核心代码
│ ├── main.ts # 入口文件
│ ├── env.ts # 环境,用于判断当前环境:_isWindows,_isNode、_isElectronRenderer(浏览器环境?)、_isWebWorker
│ ├── configuration.ts # 配置,类似于 requirejs 中的 context.configure,多了模块 id 转为 path 等方法。
│ ├── moduleManager.ts # 模块管理器,类似于 requirejs 中的 context.Module (模块加载器),这部分是核心代码,实现了 define、require 等方法。
│ ├── scriptLoader.ts # 脚本加载器,类似于 requirejs 中的 context.load,实现了不同环境下加载脚本的方法。
│ ├── loaderEvent.ts # 事件记录,是 vscode-loader 增加的功能,用于记录 BeginLoadingScript、EndLoadingScriptOK 等事件及其时间戳。
│ └── util.ts # 公共方法,比如 uri 转 path、生成异步模块的 id 等。
│
└── loader.js # 打包产物
main.ts
我们先看 vscode-loader 的入口文件,mian.ts
// 限制:为了加载 jquery,始终 require 'jquery' 并在加载器配置中添加 'jquery' 的路径
declare var doNotInitLoader;
var define;
namespace AMDLoader {
// 1:新建环境对象
const env = new Environment();
// 2:声明模块管理器
let moduleManager: ModuleManager = null!;
// 3:define 方法,最终调用 moduleManager.defineModule 或 moduleManager.enqueueDefineAnonymousModule
const DefineFunc: IDefineFunc = <any>function (id: any, dependencies: any, callback: any): void {
...
};
DefineFunc.amd = {
jQuery: true
};
// 4:config 方法,调用 moduleManager.configure
const _requireFunc_config = function (params: IConfigurationOptions, shouldOverwrite: boolean = false): void {
moduleManager.configure(params, shouldOverwrite);
};
// 5:require 方法,调用 moduleManager.configure 或 moduleManager.synchronousRequire 或 moduleManager.defineModule
const RequireFunc: IRequireFunc = <any>function () {
...
};
// require 增加方法,config、getConfig、reset、getBuildInfo、getStats、define,最终都是调用 moduleManager 的方法
RequireFunc.config = _requireFunc_config;
RequireFunc.getConfig = function (): IConfigurationOptions {
return moduleManager.getConfig().getOptionsLiteral();
};
RequireFunc.reset = function (): void {
moduleManager = moduleManager.reset();
};
RequireFunc.getBuildInfo = function (): IBuildModuleInfo[] | null {
return moduleManager.getBuildInfo();
};
RequireFunc.getStats = function (): LoaderEvent[] {
return moduleManager.getLoaderEvents();
};
RequireFunc.define = function () {
return DefineFunc.apply(null, arguments);
}
// 6:(重点)初始化,如果还未初始化,新建模块管理器,并初始化
export function init(): void {
// 如果有 require 方法(即 node 环境 / electron 环境),保存到 nodeRequire 和 __$__nodeRequire 中
if (typeof global.require !== 'undefined' || typeof require !== 'undefined') {
const _nodeRequire = (global.require || require);
if (typeof _nodeRequire === 'function' && typeof _nodeRequire.resolve === 'function') {
// 重新暴露 node 的 require 函数
const nodeRequire = ensureRecordedNodeRequire(moduleManager.getRecorder(), _nodeRequire);
global.nodeRequire = nodeRequire;
(<any>RequireFunc).nodeRequire = nodeRequire;
(<any>RequireFunc).__$__nodeRequire = nodeRequire;
}
}
// 根据环境,设置 module.exports、require、define 等方法
if (env.isNode && !env.isElectronRenderer) {
module.exports = RequireFunc;
require = <any>RequireFunc;
} else {
if (!env.isElectronRenderer) {
global.define = DefineFunc;
}
global.require = RequireFunc;
}
}
if (typeof global.define !== 'function' || !global.define.amd) {
// 第一步:新建模块管理器
moduleManager = new ModuleManager(env, createScriptLoader(env), DefineFunc, RequireFunc, Utilities.getHighPerformanceTimestamp());
// 第二步:如果有 global.require,且非函数,则更新 config
if (typeof global.require !== 'undefined' && typeof global.require !== 'function') {
RequireFunc.config(global.require);
}
// 第三步:定义 define 方法
define = function () {
return DefineFunc.apply(null, arguments);
};
define.amd = DefineFunc.amd;
// 第四步:如果还未初始化,调用 init
if (typeof doNotInitLoader === 'undefined') {
init();
}
}
}
可以看到,main.ts 中,主要是定义了 define、config、require 等方法,然后新建 moduleManager,再调用 init 方法进行初始化。
init 方法,主要是根据不同的环境设置方法。比如 node 环境,保存 node 原本的 require 方法到 nodeRequire,再设置 require = RequireFunc。浏览器环境,设置 global.require = RequireFunc,即 window.require = RequireFunc。
require 加载模块
浏览器环境,通过 require 方法,加载模块。
<script>
require(['test'], function (test) {
console.log(test.compare(7, 5));
});
</script>
前面已经提到 require 对应 RequireFunc,我们继续查看代码逻辑:
// main.ts
const RequireFunc: IRequireFunc = <any>function () {
// 只传一个参数:如果是对象,调用 moduleManager.configure 进行配置;如果是字符串,调用 moduleManager.synchronousRequire 同步加载模块
if (arguments.length === 1) {
if ((arguments[0] instanceof Object) && !Array.isArray(arguments[0])) {
_requireFunc_config(arguments[0]);
return;
}
if (typeof arguments[0] === 'string') {
return moduleManager.synchronousRequire(arguments[0]);
}
}
// 传 2-3 个参数:调用 moduleManager.defineModule 生成异步模块
// 这里传入 2 个参数,['test'], f(test)
if (arguments.length === 2 || arguments.length === 3) {
if (Array.isArray(arguments[0])) {
moduleManager.defineModule(Utilities.generateAnonymousModule(), arguments[0], arguments[1], arguments[2], null);
return;
}
}
throw new Error('Unrecognized require call');
};
// utils.ts
export class Utilities {
private static NEXT_ANONYMOUS_ID = 1;
// 生成异步模块的字符串id
// 返回 '===anonymous1==='
public static generateAnonymousModule(): string {
return '===anonymous' + (Utilities.NEXT_ANONYMOUS_ID++) + '===';
}
}
// moduleManager.ts
export class ModuleManager {
/**
* 创建模块并将其存储在 _modules 中。管理器将立即开始解析其依赖关系。
* @param strModuleId 模块的唯一且绝对的 id。这不能与另一个模块的 id 冲突
* @param dependencies 具有模块依赖项的数组。特殊键是:“require”、“exports”和“module”
* @param callback 如果 callback 是一个函数,它将使用解析的依赖项调用。如果 callback 是一个对象,它将被视为模块的导出。
*/
// 参数:'===anonymous1===', ['test'], f(test)
public defineModule(strModuleId: string, dependencies: string[], callback: any, errorback: ((err: AnnotatedError) => void) | null | undefined, stack: string | null, moduleIdResolver: ModuleIdResolver = new ModuleIdResolver(strModuleId)): void {
// define-1:strModuleId -> moduleId
let moduleId = this._moduleIdProvider.getModuleId(strModuleId);
// 如果已加载,提示重复定义模块
if (this._modules2[moduleId]) {
if (!this._config.isDuplicateMessageIgnoredFor(strModuleId)) {
console.warn('Duplicate definition of module \'' + strModuleId + '\'');
}
return;
}
// define-2:新建模块并保存到 this._modules2
let m = new Module(moduleId, strModuleId, this._normalizeDependencies(dependencies, moduleIdResolver), callback, errorback, moduleIdResolver);
this._modules2[moduleId] = m;
// 如果已经构建,收集构建信息
if (this._config.isBuild()) {
this._buildInfoDefineStack[moduleId] = stack;
this._buildInfoDependencies[moduleId] = (m.dependencies || []).map(dep => this._moduleIdProvider.getStrModuleId(dep.id));
}
// define-3:立即解析依赖项(不是 timeout 中执行)。如果需要支持无序的模块,为了完成文件的处理,则在 timeout 中执行。
this._resolve(m);
}
}
可以看到,这里是调用了 moduleManager.defineModule
生成异步模块。defineModule
每一步的具体逻辑如下。
第一步(define-1),strModuleId 转为 moduleId:
...
// define-1:strModuleId -> moduleId(递增的数字),返回 3
let moduleId = this._moduleIdProvider.getModuleId(strModuleId);
...
class ModuleIdProvider {
private _nextId: number;
private _strModuleIdToIntModuleId: Map<string, ModuleId>;
private _intModuleIdToStrModuleId: string[];
constructor() {
this._nextId = 0;
this._strModuleIdToIntModuleId = new Map<string, ModuleId>();
this._intModuleIdToStrModuleId = [];
// 'exports', 'modules', 'require' 对应 0,1,2,其他模块从 3 开始。
this.getModuleId('exports');
this.getModuleId('module');
this.getModuleId('require');
}
public getMaxModuleId(): number {
return this._nextId;
}
// 获取模块 id(递增的数字),并保存 strModuleId 和 id 的关系。
public getModuleId(strModuleId: string): ModuleId {
let id = this._strModuleIdToIntModuleId.get(strModuleId);
if (typeof id === 'undefined') {
id = this._nextId++;
this._strModuleIdToIntModuleId.set(strModuleId, id);
this._intModuleIdToStrModuleId[id] = strModuleId;
}
return id;
}
public getStrModuleId(moduleId: ModuleId): string {
return this._intModuleIdToStrModuleId[moduleId];
}
}
可以看出,getModuleId 是生成递增的数字, 0-2 是默认的模块,用户定义的模块从 3 开始计算。
第二步(define-2),规划化依赖,并新建模块:
...
// define-2:新建模块
let m = new Module(moduleId, strModuleId, this._normalizeDependencies(dependencies, moduleIdResolver), callback, errorback, moduleIdResolver);
...
// define-2.1:规范化依赖
// 参数:['test'], moduleIdResolver
// 返回:依赖 [{id: 4}]
export class ModuleManager {
private _normalizeDependencies(dependencies: string[], moduleIdResolver: ModuleIdResolver): Dependency[] {
let result: Dependency[] = [], resultLen = 0;
for (let i = 0, len = dependencies.length; i < len; i++) {
result[resultLen++] = this._normalizeDependency(dependencies[i], moduleIdResolver);
}
return result;
}
// 生成 dependency 对象,{id: number}
private _normalizeDependency(dependency: string, moduleIdResolver: ModuleIdResolver): Dependency {
// 默认依赖
if (dependency === 'exports') {
return RegularDependency.EXPORTS;
}
if (dependency === 'module') {
return RegularDependency.MODULE;
}
if (dependency === 'require') {
return RegularDependency.REQUIRE;
}
let bangIndex = dependency.indexOf('!');
// 插件依赖
if (bangIndex >= 0) {
...
return new PluginDependency(dependencyId, pluginId, pluginParam);
}
// 常规依赖
// getModuleId(前面已知,模块 id 是递增的数字),返回 4
return new RegularDependency(this._moduleIdProvider.getModuleId(moduleIdResolver.resolveModule(dependency)));
}
}
// 常规依赖,包含 exports,module, require
export class RegularDependency {
public static EXPORTS = new RegularDependency(ModuleId.EXPORTS);
public static MODULE = new RegularDependency(ModuleId.MODULE);
public static REQUIRE = new RegularDependency(ModuleId.REQUIRE);
public readonly id: ModuleId;
constructor(id: ModuleId) {
this.id = id;
}
}
// define-2.2:新建模块
// 参数:id: 3,strId: '===anonymous1===', dependencies: [{id: 4}], callback: f(test),errorback: undefined,moduleIdResolver
export class Module {
...
constructor(
id: ModuleId,
strId: string,
dependencies: Dependency[],
callback: any,
errorback: ((err: AnnotatedError) => void) | null | undefined,
moduleIdResolver: ModuleIdResolver | null,
) {
this.id = id;
this.strId = strId;
this.dependencies = dependencies;
this._callback = callback;
this._errorback = errorback;
this.moduleIdResolver = moduleIdResolver;
this.exports = {};
this.error = null;
this.exportsPassedIn = false;
this.unresolvedDependenciesCount = this.dependencies.length;
this._isComplete = false;
}
}
可以看到 require(['test'], function(test){...})
,对应的模块 id 为 3。依赖项是 'test','test' 对应的模块 id 为 4。
第三步(define-3),解析依赖项:
...
// define-3:立即解析依赖项,参数 m 是上一步新建的模块 3
this._resolve(m);
...
export class ModuleManager{
private _resolve(module: Module): void {
// 遍历依赖数组,根据不同类型进行处理
// 'test' 模块是普通依赖,在这里执行的是 resolve-6 和 resolve-8 的逻辑
let dependencies = module.dependencies;
if (dependencies) {
for (let i = 0, len = dependencies.length; i < len; i++) {
let dependency = dependencies[i];
// resolve-1:exports
if (dependency === RegularDependency.EXPORTS) {
module.exportsPassedIn = true;
module.unresolvedDependenciesCount--;
continue;
}
// resolve-2:module
if (dependency === RegularDependency.MODULE) {
module.unresolvedDependenciesCount--;
continue;
}
// resolve-3:require
if (dependency === RegularDependency.REQUIRE) {
module.unresolvedDependenciesCount--;
continue;
}
// resolve-4:如果已经加载,跳过
let dependencyModule = this._modules2[dependency.id];
if (dependencyModule && dependencyModule.isComplete()) {
if (dependencyModule.error) {
module.onDependencyError(dependencyModule.error);
return;
}
module.unresolvedDependenciesCount--;
continue;
}
// resolve-5:如果有循环引用,跳过
if (this._hasDependencyPath(dependency.id, module.id)) {
this._hasDependencyCycle = true;
console.warn('There is a dependency cycle between \'' + this._moduleIdProvider.getStrModuleId(dependency.id) + '\' and \'' + this._moduleIdProvider.getStrModuleId(module.id) + '\'. The cyclic path follows:');
let cyclePath = this._findCyclePath(dependency.id, module.id, 0) || [];
cyclePath.reverse();
cyclePath.push(dependency.id);
console.warn(cyclePath.map(id => this._moduleIdProvider.getStrModuleId(id)).join(' => \n'));
module.unresolvedDependenciesCount--;
continue;
}
// resolve-6:记录反向依赖关系
// 比如模块 3 依赖模块 4,即 this._inverseDependencies2[4] = [3]
this._inverseDependencies2[dependency.id] = this._inverseDependencies2[dependency.id] || [];
this._inverseDependencies2[dependency.id]!.push(module.id);
// resolve-7:插件依赖
if (dependency instanceof PluginDependency) {
...
this._loadModule(dependency.pluginId);
continue;
}
// resolve-8:普通依赖
// 参数: 4
this._loadModule(dependency.id);
}
}
// 如果依赖都已经解析,调用 _onModuleComplete
// 这里 module.unresolvedDependenciesCount: 1,不调用 _onModuleComplete
if (module.unresolvedDependenciesCount === 0) {
this._onModuleComplete(module);
}
}
// 加载模块,重点:this._scriptLoader.load、this._onLoad
private _loadModule(moduleId: ModuleId): void {
if (this._modules2[moduleId] || this._knownModules2[moduleId]) {
return;
}
this._knownModules2[moduleId] = true;
// loadModule-1:获取模块路径,moduleId -> strModuleId -> paths
// 4 -> 'test' -> ['test.js']
let strModuleId = this._moduleIdProvider.getStrModuleId(moduleId);
let paths = this._config.moduleIdToPaths(strModuleId);
let scopedPackageRegex = /^@[^\/]+\/[^\/]+$/ // matches @scope/package-name
if (this._env.isNode && (strModuleId.indexOf('/') === -1 || scopedPackageRegex.test(strModuleId))) {
paths.push('node|' + strModuleId);
}
let lastPathIndex = -1;
let loadNextPath = (err: any) => {
lastPathIndex++;
if (lastPathIndex >= paths.length) { // lastPathIndex: 0, paths.length: 1, 走 else 的逻辑
this._onLoadError(moduleId, err);
} else {
let currentPath = paths[lastPathIndex]; // "test.js"
let recorder = this.getRecorder(); // 事件记录器,用于记录 BeginLoadingScript、EndLoadingScriptOK 等事件及其时间戳。
if (this._config.isBuild() && currentPath === 'empty:') { // 如果满足条件,直接调用 this._onLoad,这里不满足条件,跳过
this._buildInfoPath[moduleId] = currentPath;
this.defineModule(this._moduleIdProvider.getStrModuleId(moduleId), [], null, null, null);
this._onLoad(moduleId);
return;
}
recorder.record(LoaderEventType.BeginLoadingScript, currentPath); // 记录事件 BeginLoadingScript
// loadModule-2.1:调用 this._scriptLoader.load 加载模块
this._scriptLoader.load(this, currentPath, () => {
if (this._config.isBuild()) {
this._buildInfoPath[moduleId] = currentPath;
}
recorder.record(LoaderEventType.EndLoadingScriptOK, currentPath); // 记录事件 EndLoadingScriptOK
// loadModule-2.2:在脚本加载回调中,调用 this._onLoad
// moduleId: 4,对应 test 模块
this._onLoad(moduleId);
}, (err) => {
recorder.record(LoaderEventType.EndLoadingScriptError, currentPath);
loadNextPath(err);
});
}
};
// loadModule-2: 调用 loadNextPath
loadNextPath(null);
}
}
可以看到,在第三步(define-3)解析依赖,最终通过 this._scriptLoader.load
加载 test 模块。我们继续看这部分代码:
// main.ts
// 在入口文件,新建模块管理器时,新建了脚本加载器
moduleManager = new ModuleManager(env, createScriptLoader(env), DefineFunc, RequireFunc, Utilities.getHighPerformanceTimestamp());
// scriptLoader.ts
export function createScriptLoader(env: Environment): IScriptLoader {
return new OnlyOnceScriptLoader(env);
}
class OnlyOnceScriptLoader implements IScriptLoader {
...
constructor(env: Environment) {
this._env = env;
this._scriptLoader = null;
this._callbackMap = {};
}
public load(moduleManager: IModuleManager, scriptSrc: string, callback: () => void, errorback: (err: any) => void): void {
// load-1: 根据环境,新建脚本加载器
if (!this._scriptLoader) {
if (this._env.isWebWorker) {
this._scriptLoader = new WorkerScriptLoader();
} else if (this._env.isElectronRenderer) {
const { preferScriptTags } = moduleManager.getConfig().getOptionsLiteral();
if (preferScriptTags) {
this._scriptLoader = new BrowserScriptLoader();
} else {
this._scriptLoader = new NodeScriptLoader(this._env);
}
} else if (this._env.isNode) {
this._scriptLoader = new NodeScriptLoader(this._env);
} else {
// 以浏览器环境为例,新建浏览器的脚本加载器
this._scriptLoader = new BrowserScriptLoader();
}
}
let scriptCallbacks: IScriptCallbacks = {
callback: callback,
errorback: errorback
};
// load-2: 将脚本的回调函数,保存到 _callbackMap 中
if (this._callbackMap.hasOwnProperty(scriptSrc)) {
this._callbackMap[scriptSrc].push(scriptCallbacks);
return;
}
this._callbackMap[scriptSrc] = [scriptCallbacks];
// load-3: 加载脚本,参数 callback: () => this.triggerCallback(scriptSrc)
this._scriptLoader.load(moduleManager, scriptSrc, () => this.triggerCallback(scriptSrc), (err: any) => this.triggerErrorback(scriptSrc, err));
}
}
// 浏览器环境的脚本加载器
class BrowserScriptLoader implements IScriptLoader {
public load(moduleManager: IModuleManager, scriptSrc: string, callback: () => void, errorback: (err: any) => void): void {
if (/^node\|/.test(scriptSrc)) {
// 'node|' 开头的,是 node 模块,用 nodeRequire 加载,直接调用 callback
let opts = moduleManager.getConfig().getOptionsLiteral();
let nodeRequire = ensureRecordedNodeRequire(moduleManager.getRecorder(), (opts.nodeRequire || AMDLoader.global.nodeRequire));
let pieces = scriptSrc.split('|');
let moduleExports = null;
try {
moduleExports = nodeRequire(pieces[1]);
} catch (err) {
errorback(err);
return;
}
moduleManager.enqueueDefineAnonymousModule([], () => moduleExports);
callback();
} else {
// 浏览器环境,新建 <script> 标签,设置为异步加载
let script = document.createElement('script');
script.setAttribute('async', 'async');
script.setAttribute('type', 'text/javascript');
// browser-1: 添加 load 和 error 的事件监听器
this.attachListeners(script, callback, errorback);
// 设置 src
// 比如对于 test 模块,就是 test.js
const { trustedTypesPolicy } = moduleManager.getConfig().getOptionsLiteral();
if (trustedTypesPolicy) {
scriptSrc = trustedTypesPolicy.createScriptURL(scriptSrc);
}
script.setAttribute('src', scriptSrc);
// 安全策略,将 CSP nonce 传给 <script> 标签
const { cspNonce } = moduleManager.getConfig().getOptionsLiteral();
if (cspNonce) {
script.setAttribute('nonce', cspNonce);
}
// <script> 标签添加到 head,加载脚本
document.getElementsByTagName('head')[0].appendChild(script);
}
}
/**
* 添加 load 和 error 的事件监听器,并在其中一个事件触发时移除这两个监听器
*/
private attachListeners(script: HTMLScriptElement, callback: () => void, errorback: (err: any) => void): void {
let unbind = () => {
script.removeEventListener('load', loadEventListener);
script.removeEventListener('error', errorEventListener);
};
let loadEventListener = (e: any) => {
unbind();
// browser-2: 在脚本加载后,执行 callback
callback();
};
let errorEventListener = (e: any) => {
unbind();
errorback(e);
};
script.addEventListener('load', loadEventListener);
script.addEventListener('error', errorEventListener);
}
}
可以看到,这里和 requirejs 浏览器环境加载模块的逻辑基本一致,都是新建一个 <script>
标签,设置异步加载。在脚本加载后(即 test.js 运行结束后),执行回调函数。
回调函数的传入过程比较绕,整理如下:
export class ModuleManager{
private _loadModule(moduleId: ModuleId): void {
...
// 第一步:加载脚本,参数 callback: `() => { ... this._onLoad(moduleId); }`
this._scriptLoader.load(this, currentPath, () => { ... this._onLoad(moduleId); }, (err) => {...});
}
}
class OnlyOnceScriptLoader implements IScriptLoader {
public load(moduleManager: IModuleManager, scriptSrc: string, callback: () => void, errorback: (err: any) => void): void {
...
let scriptCallbacks: IScriptCallbacks = {
// callback: 对应第一步传入的 () => { ... this._onLoad(moduleId); }
callback: callback,
errorback: errorback
};
if (this._callbackMap.hasOwnProperty(scriptSrc)) {
this._callbackMap[scriptSrc].push(scriptCallbacks);
return;
}
// 第二步:浏览器环境加载脚本,参数 callback: () => this.triggerCallback(scriptSrc)
this._scriptLoader.load(moduleManager, scriptSrc, () => this.triggerCallback(scriptSrc), (err: any) => this.triggerErrorback(scriptSrc, err));
}
private triggerCallback(scriptSrc: string): void {
let scriptCallbacks = this._callbackMap[scriptSrc];
delete this._callbackMap[scriptSrc];
for (let i = 0; i < scriptCallbacks.length; i++) {
// triggerCallback: 触发回调,scriptCallbacks[i].callback 对应第一步传入的 () => { ... this._onLoad(moduleId); }
scriptCallbacks[i].callback();
}
}
}
class BrowserScriptLoader implements IScriptLoader {
public load(moduleManager: IModuleManager, scriptSrc: string, callback: () => void, errorback: (err: any) => void): void {
...
// 第三步:添加脚本事件监听器,参数 callback: () => this.triggerCallback(scriptSrc)
this.attachListeners(script, callback, errorback);
...
}
private attachListeners(script: HTMLScriptElement, callback: () => void, errorback: (err: any) => void): void {
...
let loadEventListener = (e: any) => {
unbind();
// 第四步:监听脚本加载时间,执行 callback: () => this.triggerCallback(scriptSrc)
callback();
};
...
}
}
可以看出,脚本加载后,调用 () => this.triggerCallback(scriptSrc)
,triggerCallback
中又调用() => { ... this._onLoad(moduleId); }
。
逻辑如下,对于 test 模块,没有进行什么操作。
export class ModuleManager{
// 参数 moduleId: 4
private _onLoad(moduleId: ModuleId): void {
// 只对匿名模块,进行 defineModule
// test 模块不是匿名模块,所以什么都没有做
if (this._currentAnnonymousDefineCall !== null) {
let defineCall = this._currentAnnonymousDefineCall;
this._currentAnnonymousDefineCall = null;
this.defineModule(this._moduleIdProvider.getStrModuleId(moduleId), defineCall.dependencies, defineCall.callback, null, defineCall.stack);
}
}
}
define 定义模块
前面 require
通过添加 <script src="test.js" async="async"></script>
标签加载 test.js,而 test.js 中又调用 define
函数,定义了 test 模块。
// test.js
define('test', function() {
return {
compare: function(a, b) {
return a > b;
}
}
});
下面看一下 define
函数的实现:
// main.ts
...
define = function () {
return DefineFunc.apply(null, arguments);
};
const DefineFunc: IDefineFunc = <any>function (id: any, dependencies: any, callback: any): void {
// 第一步:没有传入 id,调整参数
if (typeof id !== 'string') {
callback = dependencies;
dependencies = id;
id = null;
}
// 第二步:没有传入 dependencies,调整参数
// 对于 test 模块,调整后,id = 'test', callback = f (), dependencies = ['require', 'exports', 'module']
if (typeof dependencies !== 'object' || !Array.isArray(dependencies)) {
callback = dependencies;
dependencies = null;
}
if (!dependencies) {
dependencies = ['require', 'exports', 'module'];
}
// 第三步:定义模块(可以为匿名模块),最终是调用 moduleManager 中的方法
// 对于 test 模块,调用 moduleManager.defineModule
if (id) {
moduleManager.defineModule(id, dependencies, callback, null, null);
} else {
moduleManager.enqueueDefineAnonymousModule(dependencies, callback);
}
};
前面 require([test], f(test))
调用 moduleManager.defineModule
生成模块 3,这里 define('test', f())
也是调用 moduleManager.defineModule
生成 test 模块。
// moduleManager.ts
export class ModuleManager {
// 参数:'test', ['require', 'exports', 'module'], f()
public defineModule(strModuleId: string, dependencies: string[], callback: any, errorback: ((err: AnnotatedError) => void) | null | undefined, stack: string | null, moduleIdResolver: ModuleIdResolver = new ModuleIdResolver(strModuleId)): void {
// define-1:strModuleId -> moduleId
// 'test' -> 4。在模块 3 解析依赖时,'test' 模块已经生成 moduleId 4。
let moduleId = this._moduleIdProvider.getModuleId(strModuleId);
...
// define-2:新建模块并保存到 this._modules2
// 新建 test 模块
let m = new Module(moduleId, strModuleId, this._normalizeDependencies(dependencies, moduleIdResolver), callback, errorback, moduleIdResolver);
this._modules2[moduleId] = m;
...
// define-3:立即解析依赖项
// m:对应 test 模块
this._resolve(m);
}
}
这里和模块 3 的生成逻辑基本一致,不同点如下:
- 依赖不同:模块 3 依赖于 test 模块,而 test 模块由于没有依赖项,
define
函数设置了默认依赖模块 ['require', 'exports', 'module']。
<!-- 在 define-2 中,通过 _normalizeDependencies
生成 3 个 dependency 对象,export: {id: 0}
, module: {id: 1}
, require: {id: 2}
。前面已经分析过,dependency 对象就是带唯一 id 的对象,比如 test 模块是 {id: 4}
。 -->
- 解析依赖项的逻辑不同:模块 3 解析依赖 test 模块,而 test 模块解析默认模块 ['require', 'exports', 'module']。
解析默认模块的逻辑如下:
export class ModuleManager{
// 参数:test 模块
private _resolve(module: Module): void {
// ['require', 'exports', 'module'] 是默认依赖模块,在这里执行的是 resolve-1、resolve-2、resolve-3 的逻辑
let dependencies = module.dependencies;
if (dependencies) {
for (let i = 0, len = dependencies.length; i < len; i++) {
let dependency = dependencies[i];
// resolve-1:exports
if (dependency === RegularDependency.EXPORTS) {
module.exportsPassedIn = true;
module.unresolvedDependenciesCount--;
continue;
}
// resolve-2:module
if (dependency === RegularDependency.MODULE) {
module.unresolvedDependenciesCount--;
continue;
}
// resolve-3:require
if (dependency === RegularDependency.REQUIRE) {
module.unresolvedDependenciesCount--;
continue;
}
...
}
}
// 前面遍历依赖数组,每次都 module.unresolvedDependenciesCount--,所以这里 module.unresolvedDependenciesCount: 0,调用 _onModuleComplete
if (module.unresolvedDependenciesCount === 0) {
this._onModuleComplete(module);
}
}
前面模块 3 解析依赖,最终调用 _loadModule(4) 加载模块 4。而这里解析依赖后,调用 _onModuleComplete 完成模块 4 的解析。具体如下:
export class ModuleManager{
private _onModuleComplete(module: Module): void {
let recorder = this.getRecorder(); // 事件记录器,用于记录 BeginLoadingScript、EndLoadingScriptOK 等事件及其时间戳。
if (module.isComplete()) {
return;
}
// 第一步:遍历依赖数组,['require', 'exports', 'module'],生成 dependenciesValues
let dependencies = module.dependencies;
let dependenciesValues: any[] = [];
if (dependencies) {
for (let i = 0, len = dependencies.length; i < len; i++) {
let dependency = dependencies[i];
// 'exports': module.exports
if (dependency === RegularDependency.EXPORTS) {
dependenciesValues[i] = module.exports;
continue;
}
// 'module': {id: 'test', config: () => { return this._config.getConfigForModule('test') }}
if (dependency === RegularDependency.MODULE) {
dependenciesValues[i] = {
id: module.strId,
config: () => {
return this._config.getConfigForModule(module.strId);
}
};
continue;
}
// 'require': require 函数,包含 node 原生的 require,require.__$__nodeRequire
if (dependency === RegularDependency.REQUIRE) {
dependenciesValues[i] = this._createRequire(module.moduleIdResolver!);
continue;
}
...
}
}
// 第二步:调用模块的 complete 方法,执行模块的工厂函数
module.complete(recorder, this._config, dependenciesValues);
// 第三步:遍历模块的反向依赖模块,如果反向模块的依赖都已经解析完成,就对反向模块执行 _onModuleComplete
// 模块 4 的反向依赖模块是 [3]
let inverseDeps = this._inverseDependencies2[module.id];
this._inverseDependencies2[module.id] = null;
if (inverseDeps) {
for (let i = 0, len = inverseDeps.length; i < len; i++) {
let inverseDependencyId = inverseDeps[i];
let inverseDependency = this._modules2[inverseDependencyId];
// 比如模块 3 依赖于模块 4,现在模块 4 已经解析完成,所以模块 3 的未解析依赖数量 - 1
inverseDependency.unresolvedDependenciesCount--;
// 对模块 3 执行 _onModuleComplete
if (inverseDependency.unresolvedDependenciesCount === 0) {
this._onModuleComplete(inverseDependency);
}
}
}
// 第四步:解析反向插件依赖
let inversePluginDeps = this._inversePluginDependencies2.get(module.id);
if (inversePluginDeps) {
this._inversePluginDependencies2.delete(module.id);
for (let i = 0, len = inversePluginDeps.length; i < len; i++) {
this._loadPluginDependency(module.exports, inversePluginDeps[i]);
}
}
}
}
第二步,调用 module.complete
方法,完成模块 4 的解析:
// 完成模块解析
export class Module {
public complete(recorder: ILoaderEventRecorder, config: Configuration, dependenciesValues: any[]): void {
// 标记模块已解析完成
this._isComplete = true;
let producedError: any = null;
// 调用工厂函数,设置 this.exports
if (this._callback) {
if (typeof this._callback === 'function') {
recorder.record(LoaderEventType.BeginInvokeFactory, this.strId); // 记录 BeginInvokeFactory 事件
// complete-1:调用 _invokeFactory
let r = Module._invokeFactory(config, this.strId, this._callback, dependenciesValues);
producedError = r.producedError;
recorder.record(LoaderEventType.EndInvokeFactory, this.strId); // 记录 EndInvokeFactory 事件
if (!producedError && typeof r.returnedValue !== 'undefined' && (!this.exportsPassedIn || Utilities.isEmpty(this.exports))) {
this.exports = r.returnedValue; // 如果工厂函数有返回值,设置 this.exports = r.returnedValue
}
} else {
this.exports = this._callback; // 如果没有工厂函数,设置 this.exports = this._callback
}
}
// 错误处理和重置
...
}
private static _invokeFactory(config: Configuration, strModuleId: string, callback: Function, dependenciesValues: any[]): { returnedValue: any; producedError: any; } {
if (config.isBuild() && !Utilities.isAnonymousModule(strModuleId)) {
return {
returnedValue: null,
producedError: null
};
}
if (config.shouldCatchError()) {
return this._safeInvokeFunction(callback, dependenciesValues); // 和 complete-2 类似,只是增加了 try-catch
}
// complete-2: 调用 callback,并将结果返回。
// 对于模块 4,是调用 define('test', f()) 中的函数,并返回 compare: function(a, b)
return {
returnedValue: callback.apply(global, dependenciesValues),
producedError: null
};
}
}
第二步解析完模块 4,调用 define('test', f())
中的函数,返回函数执行结果 compare: function(a, b)
,并赋值给模块 4 的 exports
。
第三步,由于模块 4 已经完成解析,模块 3 也可以完成解析(模块 3 只依赖模块 4),所以也对模块 3 执行 _onModuleComplete
。
export class ModuleManager{
private _onModuleComplete(module: Module): void {
let recorder = this.getRecorder(); // 事件记录器,用于记录 BeginLoadingScript、EndLoadingScriptOK 等事件及其时间戳。
if (module.isComplete()) {
return;
}
// 第一步:遍历依赖数组,[{id: 4}],生成 dependenciesValues: [{ compare: ƒ (a, b) }]
let dependencies = module.dependencies;
let dependenciesValues: any[] = [];
if (dependencies) {
for (let i = 0, len = dependencies.length; i < len; i++) {
let dependency = dependencies[i];
...
let dependencyModule = this._modules2[dependency.id];
if (dependencyModule) {
// 保存模块 4 的 exports,即 test 模块的工厂函数返回值:compare: function(a, b)
dependenciesValues[i] = dependencyModule.exports;
continue;
}
dependenciesValues[i] = null;
}
}
// 第二步:调用模块的 complete 方法,执行模块的工厂函数
module.complete(recorder, this._config, dependenciesValues);
// 模块 3 没有反向依赖,第三步、第四步略
...
}
}
export class Module {
public complete(recorder: ILoaderEventRecorder, config: Configuration, dependenciesValues: any[]): void {
...
// 调用 _invokeFactory,dependenciesValues 对应 test 模块的返回值
let r = Module._invokeFactory(config, this.strId, this._callback, dependenciesValues);
...
// 模块 3 的工厂函数没有返回值,不设置
if (!producedError && typeof r.returnedValue !== 'undefined' && (!this.exportsPassedIn || Utilities.isEmpty(this.exports))) {
this.exports = r.returnedValue;
}
...
}
private static _invokeFactory(config: Configuration, strModuleId: string, callback: Function, dependenciesValues: any[]): { returnedValue: any; producedError: any; } {
...
// 对于模块 3,是调用 require(['test'], f(test)) 中的函数,传入参数 dependenciesValues 是 test 模块的返回值
// 返回 {returnValue: null, producedError: null}
return {
returnedValue: callback.apply(global, dependenciesValues),
producedError: null
};
}
}
可以看到,_onModuleComplete
执行了模块 3 的工厂函数,至此所有模块加载完成,模块的工厂函数也都执行完毕。
总结
本文以浏览器环境为例,分析了 vscode-loader 的模块加载:
- 在 vscode-loader 中,
require
或 define
函数,一般会调用 moduleManager.defineModule
定义异步模块。
- 在
defineModule
中,通过 new Module()
生成异步模块,通过 this._resolve(m)
解析模块。
- 在
_resolve
中,
- 如果模块依赖于其他模块,会通过
_loadModule
加载依赖模块。
- 在
_loadModule
中,会调用 this._scriptLoader.load
加载模块。对于浏览器环境,就是生成 <script>
标签,并设置 async,异步加载模块(其他环境的加载方式有异)。
- 在加载模块后,会调用
this._onload
的回调,主要是处理匿名模块。
- 如果模块不依赖于其他模块,会添加默认的依赖 ['require', 'exports', 'module'],并通过
_onModuleComplete
完成模块解析。
- 在
_onModuleComplete
中,会通过 module.complete
执行模块的工厂函数,并传入 dependenciesValues
作为参数。
- 完成当前模块解析后,会遍历反向依赖模块数组;如果反向模块的依赖已经都解析完成,则对反向模块也调用
_onModuleComplete
,执行反向模块的工厂函数,并传入 dependenciesValues
作为参数。
dependenciesValues
, 对应的是默认依赖的处理值,或者依赖模块的工厂函数返回值。
通过上面分析,可以知道,在 _loadModule
中会逐级加载依赖模块;而在 _onModuleComplete
中,是先执行最底层的依赖模块的解析,再执行其反向模块的解析,直到所有模块解析完成。
猜您可能感兴趣的文章