Photo by Shahadat Rahman / Unsplash

vscode-loader 解析(浏览器环境)

快应用开发工具 Nov 4, 2021

vscode-loader 的使用示例

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 的生成逻辑基本一致,不同点如下:

  1. 依赖不同:模块 3 依赖于 test 模块,而 test 模块由于没有依赖项,define 函数设置了默认依赖模块 ['require', 'exports', 'module']。
  1. 解析依赖项的逻辑不同:模块 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 的模块加载:

  1. 在 vscode-loader 中,requiredefine 函数,一般会调用 moduleManager.defineModule 定义异步模块。
  2. defineModule 中,通过 new Module() 生成异步模块,通过 this._resolve(m) 解析模块。
  3. _resolve 中,
    • 如果模块依赖于其他模块,会通过 _loadModule 加载依赖模块。
      • _loadModule 中,会调用 this._scriptLoader.load 加载模块。对于浏览器环境,就是生成 <script> 标签,并设置 async,异步加载模块(其他环境的加载方式有异)。
      • 在加载模块后,会调用 this._onload 的回调,主要是处理匿名模块。
    • 如果模块不依赖于其他模块,会添加默认的依赖 ['require', 'exports', 'module'],并通过 _onModuleComplete 完成模块解析。
      • _onModuleComplete 中,会通过 module.complete 执行模块的工厂函数,并传入 dependenciesValues 作为参数。
      • 完成当前模块解析后,会遍历反向依赖模块数组;如果反向模块的依赖已经都解析完成,则对反向模块也调用 _onModuleComplete,执行反向模块的工厂函数,并传入 dependenciesValues 作为参数。
      • dependenciesValues, 对应的是默认依赖的处理值,或者依赖模块的工厂函数返回值。

通过上面分析,可以知道,在 _loadModule 中会逐级加载依赖模块;而在 _onModuleComplete 中,是先执行最底层的依赖模块的解析,再执行其反向模块的解析,直到所有模块解析完成。

Tags