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

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.