引言
在前端开发的演进历程中,模块化一直是工程化实践的核心。从早期的脚本标签堆砌到现代的ES Modules,模块化技术极大地提升了代码的可维护性、复用性和协作效率。本文将深入探讨模块化的各个方面,包括模块加载器实现、规范演化、Polyfill技术,并补充构建工具、性能优化等工程化实践,全面解析模块化在现代前端开发中的应用。
一、实现简单的模块加载器
在理解复杂模块系统之前,我们先实现一个简单的模块加载器,了解其核心原理。
1.1 基础模块加载器实现
// 简单的模块注册表
const moduleRegistry = {};
const moduleCache = {};
// 模块定义函数
function define(name, dependencies, factory) {
if (!moduleRegistry[name]) {
moduleRegistry[name] = {
dependencies,
factory,
resolved: false,
exports: null
};
}
}
// 模块加载函数
function require(name) {
// 检查缓存
if (moduleCache[name]) {
return moduleCache[name];
}
const module = moduleRegistry[name];
if (!module) {
throw new Error(`Module ${name} not found`);
}
// 解析依赖
const resolvedDeps = module.dependencies.map(dep => {
if (dep === 'exports' || dep === 'module') {
return null; // 特殊处理
}
return require(dep);
});
// 执行工厂函数获取模块导出
const factoryResult = module.factory.apply(null, resolvedDeps);
// 缓存模块导出
moduleCache[name] = factoryResult || {};
module.resolved = true;
return moduleCache[name];
}
// 使用示例
define('math', [], function() {
return {
add: (a, b) => a + b,
multiply: (a, b) => a * b
};
});
define('calculator', ['math'], function(math) {
return {
calculate: (x, y) => math.multiply(math.add(x, y), 2)
};
});
// 使用模块
const calculator = require('calculator');
console.log(calculator.calculate(2, 3)); // 10
1.2 异步模块加载器
class AsyncModuleLoader {
constructor() {
this.modules = new Map();
this.loading = new Map();
}
// 定义模块
define(name, deps, factory) {
this.modules.set(name, {
deps,
factory,
exports: null,
resolved: false
});
}
// 异步加载模块
async require(name) {
if (this.modules.get(name)?.resolved) {
return this.modules.get(name).exports;
}
// 防止重复加载
if (this.loading.has(name)) {
return this.loading.get(name);
}
// 创建加载Promise
const loadPromise = this._loadModule(name);
this.loading.set(name, loadPromise);
return loadPromise;
}
async _loadModule(name) {
const module = this.modules.get(name);
if (!module) {
throw new Error(`Module ${name} not found`);
}
// 加载所有依赖
const depPromises = module.deps.map(dep => this.require(dep));
const deps = await Promise.all(depPromises);
// 执行工厂函数
const exports = module.factory.apply(null, deps);
// 更新模块状态
module.exports = exports || {};
module.resolved = true;
this.loading.delete(name);
return module.exports;
}
}
// 使用示例
const loader = new AsyncModuleLoader();
loader.define('utils', [], () => ({
format: str => str.toUpperCase()
}));
loader.define('app', ['utils'], (utils) => {
return {
run: () => console.log(utils.format('hello'))
};
});
loader.require('app').then(app => app.run()); // 输出: HELLO
二、AMD规范实现
AMD(Asynchronous Module Definition)规范是RequireJS推广的异步模块定义标准。
2.1 简化的AMD实现
(function(global) {
// 模块缓存
const modules = {};
const inProgress = {};
// 定义函数
function define(id, dependencies, factory) {
if (arguments.length === 2) {
factory = dependencies;
dependencies = [];
}
modules[id] = {
id: id,
dependencies: dependencies,
factory: factory,
exports: null,
resolved: false
};
// 尝试解析模块
resolveModule(id);
}
// 依赖解析
function resolveModule(id) {
const module = modules[id];
if (!module || module.resolved) return;
// 检查依赖是否都可用
const deps = module.dependencies;
const missingDeps = deps.filter(dep =>
!modules[dep] || !modules[dep].resolved
);
if (missingDeps.length === 0) {
// 所有依赖已就绪,执行工厂函数
executeModule(id);
} else {
// 等待依赖
missingDeps.forEach(dep => {
if (!inProgress[dep]) {
inProgress[dep] = [];
}
inProgress[dep].push(id);
});
}
}
// 执行模块
function executeModule(id) {
const module = modules[id];
if (module.resolved) return;
// 获取依赖的exports
const depExports = module.dependencies.map(dep => {
if (dep === 'exports') return {};
if (dep === 'require') return createRequire();
if (dep === 'module') return { id: module.id, exports: {} };
return modules[dep].exports;
});
// 执行工厂函数
const exports = module.factory.apply(null, depExports);
// 设置exports
module.exports = exports ||
(depExports[module.dependencies.indexOf('exports')] || {});
module.resolved = true;
// 通知等待此模块的其他模块
if (inProgress[id]) {
inProgress[id].forEach(dependentId => resolveModule(dependentId));
delete inProgress[id];
}
}
// 创建require函数
function createRequire() {
return function(ids, callback) {
if (typeof ids === 'string') ids = [ids];
Promise.all(ids.map(loadModule))
.then(modules => {
if (callback) callback.apply(null, modules);
});
};
}
// 异步加载模块
function loadModule(id) {
return new Promise((resolve, reject) => {
if (modules[id] && modules[id].resolved) {
resolve(modules[id].exports);
return;
}
// 动态加载脚本
const script = document.createElement('script');
script.src = id + '.js';
script.onload = () => {
// 等待模块解析
const checkInterval = setInterval(() => {
if (modules[id] && modules[id].resolved) {
clearInterval(checkInterval);
resolve(modules[id].exports);
}
}, 10);
};
script.onerror = reject;
document.head.appendChild(script);
});
}
// 暴露到全局
global.define = define;
global.require = createRequire();
})(typeof window !== 'undefined' ? window : global);
// 使用示例
define('math', [], function() {
return {
add: function(a, b) { return a + b; }
};
});
define('app', ['math', 'require'], function(math, require) {
return {
calculate: function() {
return math.add(1, 2);
},
loadExtra: function() {
require(['utils'], function(utils) {
console.log('Utils loaded');
});
}
};
});
require(['app'], function(app) {
console.log(app.calculate()); // 3
});
三、CMD规范实现
CMD(Common Module Definition)规范由Sea.js推广,强调就近依赖。
3.1 简化的CMD实现
(function(global) {
const modules = {};
const factories = {};
const cache = {};
// 模块状态
const STATUS = {
PENDING: 0,
LOADING: 1,
LOADED: 2,
EXECUTING: 3,
EXECUTED: 4
};
// 定义函数
function define(factory) {
// 获取当前脚本
const scripts = document.getElementsByTagName('script');
const currentScript = scripts[scripts.length - 1];
const id = currentScript.src.replace(/\.js$/, '');
factories[id] = factory;
modules[id] = {
id: id,
factory: factory,
deps: [],
exports: null,
status: STATUS.PENDING,
callbacks: []
};
// 解析依赖
parseDependencies(id);
}
// 解析依赖
function parseDependencies(id) {
const factory = factories[id];
if (!factory) return;
const source = factory.toString();
const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
const deps = [];
let match;
while ((match = requireRegex.exec(source)) !== null) {
deps.push(match[1]);
}
modules[id].deps = deps;
}
// 异步加载模块
function require(id, callback) {
const module = modules[id];
if (module && module.status === STATUS.EXECUTED) {
// 模块已执行,直接返回
if (callback) {
callback(module.exports);
}
return module.exports;
}
// 模块未加载,开始加载
if (!module || module.status === STATUS.PENDING) {
return loadModule(id, callback);
}
// 模块加载中,添加回调
if (module.status < STATUS.EXECUTED) {
module.callbacks.push(callback);
}
}
// 加载模块
function loadModule(id, callback) {
const module = modules[id] || (modules[id] = {
id: id,
deps: [],
exports: null,
status: STATUS.LOADING,
callbacks: callback ? [callback] : []
});
// 创建script标签加载
const script = document.createElement('script');
script.src = id + '.js';
script.async = true;
script.onload = function() {
module.status = STATUS.LOADED;
executeModule(id);
};
script.onerror = function() {
console.error(`Failed to load module: ${id}`);
};
document.head.appendChild(script);
return null;
}
// 执行模块
function executeModule(id) {
const module = modules[id];
if (!module || module.status >= STATUS.EXECUTING) return;
module.status = STATUS.EXECUTING;
// 收集依赖
const deps = module.deps;
const depValues = deps.map(depId => {
const depModule = modules[depId];
if (depModule && depModule.status === STATUS.EXECUTED) {
return depModule.exports;
}
// 同步加载依赖(简化实现)
return require(depId);
});
// 执行工厂函数
const factory = factories[id];
if (!factory) {
throw new Error(`Factory not found for module: ${id}`);
}
// 提供require、exports、module参数
const localRequire = function(depId) {
return require(depId);
};
const localExports = {};
const localModule = { exports: localExports };
// 执行
const result = factory.call(null, localRequire, localExports, localModule);
// 设置exports
module.exports = localModule.exports || result || localExports;
module.status = STATUS.EXECUTED;
// 执行回调
module.callbacks.forEach(cb => cb(module.exports));
module.callbacks = [];
}
// 暴露全局
global.define = define;
global.require = require;
})(typeof window !== 'undefined' ? window : global);
// 使用示例
// 文件: math.js
define(function(require, exports, module) {
module.exports = {
add: function(a, b) {
return a + b;
}
};
});
// 文件: app.js
define(function(require, exports, module) {
var math = require('math');
exports.calculate = function() {
return math.add(1, 2);
};
});
// 主文件
require('app', function(app) {
console.log(app.calculate()); // 3
});
四、ES Module的简单Polyfill
虽然现代浏览器支持ES Modules,但在某些场景下,我们仍需要Polyfill支持。
4.1 基础ESM Polyfill实现
// ES Module Polyfill
(function() {
const moduleMap = new Map();
const moduleCache = new Map();
// 拦截import语句(通过动态import实现)
window.importModule = async function(modulePath) {
// 检查缓存
if (moduleCache.has(modulePath)) {
return moduleCache.get(modulePath);
}
// 加载模块代码
const code = await fetchModule(modulePath);
// 解析依赖
const deps = extractDependencies(code);
// 加载依赖
const depPromises = deps.map(dep =>
importModule(resolvePath(modulePath, dep))
);
const dependencies = await Promise.all(depPromises);
// 执行模块
const moduleExports = {};
const module = {
exports: moduleExports
};
// 创建包装函数
const wrapper = createWrapper(code, dependencies);
wrapper(
moduleExports, // exports
module, // module
modulePath // __filename(模拟)
);
// 缓存结果
const exports = module.exports === moduleExports ?
moduleExports : module.exports;
moduleCache.set(modulePath, exports);
return exports;
};
// 提取依赖
function extractDependencies(code) {
const importRegex = /import\s+.*?\s+from\s+['"](.*?)['"]/g;
const dynamicImportRegex = /import\s*\(['"](.*?)['"]\)/g;
const deps = new Set();
let match;
while ((match = importRegex.exec(code)) !== null) {
deps.add(match[1]);
}
// 重置正则
importRegex.lastIndex = 0;
while ((match = dynamicImportRegex.exec(code)) !== null) {
deps.add(match[1]);
}
return Array.from(deps);
}
// 创建包装函数
function createWrapper(code, dependencies) {
const wrapperCode = `
(function(exports, module, __filename, __dirname) {
// 注入依赖
const [
${dependencies.map((_, i) => `__dep${i}`).join(', ')}
] = arguments[4];
${code}
// 返回默认导出
return module.exports && module.exports.default ?
module.exports.default : module.exports;
})
`;
return eval(wrapperCode);
}
// 解析路径
function resolvePath(basePath, targetPath) {
if (targetPath.startsWith('./') || targetPath.startsWith('../')) {
const baseDir = basePath.substring(0, basePath.lastIndexOf('/'));
return new URL(targetPath, baseDir + '/').pathname;
}
return targetPath;
}
// 获取模块代码
async function fetchModule(path) {
const response = await fetch(path);
if (!response.ok) {
throw new Error(`Failed to load module: ${path}`);
}
return response.text();
}
// 拦截script type="module"
interceptModuleScripts();
function interceptModuleScripts() {
const originalCreateElement = document.createElement;
document.createElement = function(tagName) {
const element = originalCreateElement.call(document, tagName);
if (tagName === 'script') {
const originalSetAttribute = element.setAttribute.bind(element);
element.setAttribute = function(name, value) {
originalSetAttribute(name, value);
if (name === 'type' && value === 'module') {
// 拦截模块脚本
const src = element.getAttribute('src');
if (src) {
element.type = 'text/javascript';
importModule(src).then(() => {
if (element.onload) element.onload();
}).catch(err => {
if (element.onerror) element.onerror(err);
});
}
}
};
}
return element;
};
}
})();
// 使用示例
// 模块文件: utils.js
export function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
export default function greet(name) {
return `Hello, ${capitalize(name)}!`;
}
// 主文件
importModule('./utils.js').then(utils => {
console.log(utils.default('world')); // Hello, World!
console.log(utils.capitalize('test')); // Test
});
4.2 支持Tree Shaking的ESM Polyfill
class ESMCompat {
constructor() {
this.modules = new Map();
this.usedExports = new Set();
}
// 注册模块
register(name, code) {
const ast = this.parse(code);
const exports = this.extractExports(ast);
this.modules.set(name, {
code,
ast,
exports,
used: new Set()
});
}
// 解析代码为AST(简化版)
parse(code) {
// 简化实现:实际应使用Babel等解析器
const exportMatches = code.match(/export\s+(const|let|var|function|class|default)\s+(\w+)/g) || [];
const imports = code.match(/import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/g) || [];
return {
exports: exportMatches.map(match => ({
type: match.split(' ')[1],
name: match.split(' ')[2]
})),
imports: imports.map(match => {
const parts = match.match(/import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/);
return {
specifiers: parts[1].split(',').map(s => s.trim()),
source: parts[2]
};
})
};
}
// 提取导出
extractExports(ast) {
return ast.exports.map(exp => exp.name);
}
// 使用模块(标记使用的导出)
use(name, ...exports) {
const module = this.modules.get(name);
if (module) {
exports.forEach(exp => {
if (module.exports.includes(exp)) {
module.used.add(exp);
}
});
}
}
// 生成优化后的代码
generateOptimized(name) {
const module = this.modules.get(name);
if (!module) return '';
let code = module.code;
// 移除未使用的导出(简化实现)
module.exports.forEach(exp => {
if (!module.used.has(exp)) {
const regex = new RegExp(`export\\s+.*?\\b${exp}\\b[^;]*;`, 'g');
code = code.replace(regex, '');
}
});
return code;
}
}
// 使用示例
const compat = new ESMCompat();
compat.register('math', `
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export function multiply(a, b) { return a * b; }
export function unusedFunction() { return 'unused'; }
`);
// 标记使用的导出
compat.use('math', 'PI', 'add');
// 生成优化代码
console.log(compat.generateOptimized('math'));
// 输出将只包含PI和add的导出
五、模块化构建工具集成
现代开发中,我们使用构建工具处理模块化。以下展示如何集成Webpack-like的简单打包器。
5.1 简易模块打包器
const fs = require('fs');
const path = require('path');
const { parse } = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const t = require('@babel/types');
class SimpleBundler {
constructor(entry) {
this.entry = entry;
this.modules = new Map();
this.moduleId = 0;
}
// 构建
build(outputPath) {
const entryModule = this.collectDependencies(this.entry);
const bundleCode = this.generateBundle(entryModule);
fs.writeFileSync(outputPath, bundleCode);
console.log(`Bundle generated: ${outputPath}`);
}
// 收集依赖
collectDependencies(filePath) {
const fileContent = fs.readFileSync(filePath, 'utf-8');
const ast = parse(fileContent, {
sourceType: 'module',
plugins: ['jsx']
});
const dependencies = [];
const dirname = path.dirname(filePath);
// 遍历AST收集import语句
traverse(ast, {
ImportDeclaration: ({ node }) => {
const importPath = node.source.value;
const absolutePath = this.resolvePath(importPath, dirname);
dependencies.push(absolutePath);
},
CallExpression: ({ node }) => {
if (node.callee.type === 'Import') {
const importPath = node.arguments[0].value;
const absolutePath = this.resolvePath(importPath, dirname);
dependencies.push(absolutePath);
}
}
});
const moduleId = this.moduleId++;
const module = {
id: moduleId,
filePath,
code: fileContent,
dependencies,
mapping: {}
};
this.modules.set(filePath, module);
// 递归收集依赖
dependencies.forEach(dep => {
if (!this.modules.has(dep)) {
this.collectDependencies(dep);
}
});
return module;
}
// 解析路径
resolvePath(importPath, baseDir) {
if (importPath.startsWith('.')) {
return path.resolve(baseDir, importPath);
}
// 处理node_modules(简化)
const nodeModulePath = path.resolve(process.cwd(), 'node_modules', importPath);
if (fs.existsSync(nodeModulePath)) {
return nodeModulePath;
}
return importPath;
}
// 生成打包代码
generateBundle(entryModule) {
const modules = [];
// 创建模块映射
this.modules.forEach(module => {
const transformedCode = this.transformModule(module);
modules.push(`
${module.id}: {
factory: function(require, module, exports) {
${transformedCode}
},
mapping: ${JSON.stringify(module.mapping)}
}
`);
});
// 生成运行时
return `
(function(modules) {
const moduleCache = {};
function require(id) {
if (moduleCache[id]) {
return moduleCache[id].exports;
}
const mod = modules[id];
const localRequire = function(modulePath) {
return require(mod.mapping[modulePath]);
};
const module = { exports: {} };
mod.factory(localRequire, module, module.exports);
moduleCache[id] = module;
return module.exports;
}
// 启动入口模块
require(0);
})({
${modules.join(',\n')}
});
`;
}
// 转换模块代码
transformModule(module) {
const ast = parse(module.code, {
sourceType: 'module'
});
// 构建路径映射
let importIndex = 0;
traverse(ast, {
ImportDeclaration: ({ node }) => {
const importPath = node.source.value;
const depModule = this.modules.get(
this.resolvePath(importPath, path.dirname(module.filePath))
);
if (depModule) {
const importName = `__import_${importIndex++}`;
module.mapping[importPath] = depModule.id;
// 替换import语句
const specifiers = node.specifiers.map(spec => {
if (t.isImportDefaultSpecifier(spec)) {
return t.variableDeclarator(
spec.local,
t.memberExpression(
t.identifier(importName),
t.identifier('default')
)
);
} else {
return t.variableDeclarator(
spec.local,
t.memberExpression(
t.identifier(importName),
spec.imported || spec.local
)
);
}
});
return t.variableDeclaration('const', specifiers);
}
}
});
// 移除export语句
traverse(ast, {
ExportNamedDeclaration: ({ node, remove }) => {
if (node.declaration) {
return node.declaration;
}
remove();
},
ExportDefaultDeclaration: ({ node }) => {
return t.expressionStatement(
t.assignmentExpression(
'=',
t.memberExpression(
t.identifier('module'),
t.identifier('exports')
),
t.objectExpression([
t.objectProperty(
t.identifier('default'),
node.declaration
)
])
)
);
}
});
const { code } = generate(ast);
return code;
}
}
// 使用示例
const bundler = new SimpleBundler('./src/index.js');
bundler.build('./dist/bundle.js');
六、模块联邦与微前端架构
模块联邦(Module Federation)是Webpack 5引入的重要特性,支持跨应用共享模块。
6.1 简易模块联邦实现
// 模块联邦管理器
class ModuleFederation {
constructor(config) {
this.config = config;
this.remotes = new Map();
this.exposes = new Map();
this.shared = new Map();
this.init();
}
init() {
// 初始化共享模块
if (this.config.shared) {
Object.entries(this.config.shared).forEach(([name, config]) => {
this.shared.set(name, {
module: require(name),
version: config.version,
singleton: config.singleton || false
});
});
}
// 初始化暴露模块
if (this.config.exposes) {
Object.entries(this.config.exposes).forEach(([name, modulePath]) => {
this.exposes.set(name, require(modulePath));
});
}
}
// 注册远程应用
async registerRemote(name, url) {
try {
const remoteManifest = await this.fetchRemoteManifest(url);
this.remotes.set(name, {
url,
manifest: remoteManifest
});
console.log(`Remote ${name} registered`);
} catch (error) {
console.error(`Failed to register remote ${name}:`, error);
}
}
// 获取远程清单
async fetchRemoteManifest(url) {
const response = await fetch(`${url}/federation-manifest.json`);
return response.json();
}
// 获取模块
async getModule(remoteName, moduleName) {
// 检查共享模块
if (this.shared.has(moduleName)) {
return this.shared.get(moduleName).module;
}
// 检查本地暴露
if (this.exposes.has(moduleName)) {
return this.exposes.get(moduleName);
}
// 检查远程模块
const remote = this.remotes.get(remoteName);
if (remote) {
return this.loadRemoteModule(remote, moduleName);
}
throw new Error(`Module ${moduleName} not found`);
}
// 加载远程模块
async loadRemoteModule(remote, moduleName) {
const moduleUrl = `${remote.url}/${moduleName}.js`;
// 动态加载脚本
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = moduleUrl;
script.onload = () => {
// 假设远程模块会暴露到全局
const module = window[`${remote.name}_${moduleName}`];
if (module) {
resolve(module);
} else {
reject(new Error(`Module ${moduleName} not found in remote`));
}
};
script.onerror = reject;
document.head.appendChild(script);
});
}
// 暴露模块
expose(name, module) {
this.exposes.set(name, module);
// 暴露到全局(供远程访问)
window[`${this.config.name}_${name}`] = module;
}
}
// 使用示例
// App 1 配置
const federation1 = new ModuleFederation({
name: 'app1',
exposes: {
'./Button': './src/components/Button.js'
},
shared: {
react: { singleton: true, version: '17.0.0' },
'react-dom': { singleton: true, version: '17.0.0' }
}
});
// App 2 配置
const federation2 = new ModuleFederation({
name: 'app2',
remotes: {
app1: 'http://localhost:3001'
},
shared: {
react: { singleton: true, version: '17.0.0' }
}
});
// App2中使用App1的模块
federation2.getModule('app1', 'Button').then(Button => {
// 使用远程Button组件
console.log('Remote Button loaded:', Button);
});
七、模块化性能优化
7.1 代码分割与懒加载
class CodeSplitter {
constructor() {
this.chunks = new Map();
this.loadedChunks = new Set();
}
// 定义代码分割点
defineChunk(name, getChunk) {
this.chunks.set(name, getChunk);
}
// 懒加载代码块
async loadChunk(name) {
if (this.loadedChunks.has(name)) {
return;
}
const getChunk = this.chunks.get(name);
if (!getChunk) {
throw new Error(`Chunk ${name} not found`);
}
// 标记为加载中
this.loadedChunks.add(name);
try {
await getChunk();
console.log(`Chunk ${name} loaded`);
} catch (error) {
this.loadedChunks.delete(name);
throw error;
}
}
// 预加载代码块
preloadChunk(name) {
if (this.loadedChunks.has(name)) return;
const link = document.createElement('link');
link.rel = 'preload';
link.as = 'script';
const getChunk = this.chunks.get(name);
if (getChunk && getChunk.chunkPath) {
link.href = getChunk.chunkPath;
document.head.appendChild(link);
}
}
}
// Webpack动态导入兼容
function dynamicImport(modulePath) {
if (typeof __webpack_require__ !== 'undefined') {
// Webpack环境
return import(/* webpackChunkName: "[request]" */ modulePath);
} else {
// 原生环境
return import(modulePath);
}
}
// 使用示例
const splitter = new CodeSplitter();
// 定义代码块
splitter.defineChunk('dashboard', () =>
dynamicImport('./Dashboard.js')
);
splitter.defineChunk('analytics', () =>
dynamicImport('./Analytics.js')
);
// 路由懒加载
async function loadRoute(routeName) {
switch (routeName) {
case 'dashboard':
await splitter.loadChunk('dashboard');
break;
case 'analytics':
await splitter.loadChunk('analytics');
break;
}
}
// 预加载
window.addEventListener('mouseover', (e) => {
if (e.target.href && e.target.href.includes('dashboard')) {
splitter.preloadChunk('dashboard');
}
});
7.2 模块缓存策略
class ModuleCache {
constructor() {
this.cache = new Map();
this.ttl = 5 * 60 * 1000; // 5分钟
this.maxSize = 100; // 最大缓存模块数
}
// 获取模块
async get(key, fetchModule) {
const cached = this.cache.get(key);
// 检查缓存是否有效
if (cached && Date.now() - cached.timestamp < this.ttl) {
console.log(`Cache hit: ${key}`);
return cached.module;
}
// 缓存失效或不存在,重新获取
console.log(`Cache miss: ${key}`);
const module = await fetchModule();
// 更新缓存
this.set(key, module);
return module;
}
// 设置缓存
set(key, module) {
// 清理过期缓存
this.cleanup();
this.cache.set(key, {
module,
timestamp: Date.now()
});
}
// 清理缓存
cleanup() {
const now = Date.now();
// 清理过期
for (const [key, value] of this.cache) {
if (now - value.timestamp > this.ttl) {
this.cache.delete(key);
}
}
// 清理超出大小限制的(LRU策略)
if (this.cache.size > this.maxSize) {
const entries = Array.from(this.cache.entries());
entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
for (let i = 0; i < entries.length - this.maxSize; i++) {
this.cache.delete(entries[i][0]);
}
}
}
// 清空缓存
clear() {
this.cache.clear();
}
}
// 使用示例
const moduleCache = new ModuleCache();
async function loadModuleWithCache(modulePath) {
return moduleCache.get(modulePath, async () => {
const response = await fetch(modulePath);
return response.text();
});
}
八、模块化最佳实践与工程化
8.1 模块设计原则
// 1. 单一职责原则
// 不好的例子
class UserManager {
// 混合了用户管理、验证、通知等多个职责
}
// 好的例子
class UserRepository {
// 只负责数据访问
}
class UserValidator {
// 只负责验证
}
class UserNotifier {
// 只负责通知
}
// 2. 依赖注入
class UserService {
constructor(userRepository, validator, notifier) {
this.userRepository = userRepository;
this.validator = validator;
this.notifier = notifier;
}
async register(user) {
if (!this.validator.validate(user)) {
throw new Error('Invalid user');
}
await this.userRepository.save(user);
await this.notifier.sendWelcome(user.email);
}
}
// 3. 接口抽象
// 定义接口
class IStorage {
async save(key, value) {}
async get(key) {}
async delete(key) {}
}
// 具体实现
class LocalStorage extends IStorage {
async save(key, value) {
localStorage.setItem(key, JSON.stringify(value));
}
async get(key) {
return JSON.parse(localStorage.getItem(key));
}
async delete(key) {
localStorage.removeItem(key);
}
}
class APIService {
constructor(storage) {
if (!(storage instanceof IStorage)) {
throw new Error('Invalid storage implementation');
}
this.storage = storage;
}
}
8.2 模块版本管理与升级
class ModuleVersionManager {
constructor() {
this.versions = new Map();
this.deprecations = new Map();
}
// 注册模块版本
register(moduleName, version, module) {
if (!this.versions.has(moduleName)) {
this.versions.set(moduleName, new Map());
}
this.versions.get(moduleName).set(version, module);
}
// 获取模块(支持语义化版本)
get(moduleName, versionRange = 'latest') {
const moduleVersions = this.versions.get(moduleName);
if (!moduleVersions) {
throw new Error(`Module ${moduleName} not found`);
}
if (versionRange === 'latest') {
const latestVersion = Array.from(moduleVersions.keys())
.sort(this.compareVersions)
.pop();
return moduleVersions.get(latestVersion);
}
// 简化的版本范围解析
const availableVersions = Array.from(moduleVersions.keys())
.filter(v => this.satisfiesVersion(v, versionRange))
.sort(this.compareVersions);
if (availableVersions.length === 0) {
throw new Error(`No version of ${moduleName} satisfies ${versionRange}`);
}
return moduleVersions.get(availableVersions.pop());
}
// 比较版本
compareVersions(v1, v2) {
const parts1 = v1.split('.').map(Number);
const parts2 = v2.split('.').map(Number);
for (let i = 0; i < 3; i++) {
if (parts1[i] !== parts2[i]) {
return parts1[i] - parts2[i];
}
}
return 0;
}
// 检查版本是否满足范围
satisfiesVersion(version, range) {
// 简化实现,实际应使用semver库
if (range === '*') return true;
const [op, versionRange] = range.match(/^([>=<~^]*)(\d+\.\d+\.\d+)$/).slice(1);
const vParts = version.split('.').map(Number);
const rParts = versionRange.split('.').map(Number);
switch (op) {
case '^': // 兼容版本
return vParts[0] === rParts[0] && vParts[1] >= rParts[1];
case '~': // 近似版本
return vParts[0] === rParts[0] &&
vParts[1] === rParts[1] &&
vParts[2] >= rParts[2];
case '>=':
return this.compareVersions(version, versionRange) >= 0;
case '>':
return this.compareVersions(version, versionRange) > 0;
case '<=':
return this.compareVersions(version, versionRange) <= 0;
case '<':
return this.compareVersions(version, versionRange) < 0;
default:
return version === versionRange;
}
}
// 弃用通知
deprecate(moduleName, version, message) {
if (!this.deprecations.has(moduleName)) {
this.deprecations.set(moduleName, new Map());
}
this.deprecations.get(moduleName).set(version, {
message,
deprecatedAt: new Date()
});
// 添加控制台警告
console.warn(`Module ${moduleName}@${version} is deprecated: ${message}`);
}
}
// 使用示例
const versionManager = new ModuleVersionManager();
// 注册不同版本
versionManager.register('utils', '1.0.0', {
oldMethod: () => 'old'
});
versionManager.register('utils', '1.1.0', {
oldMethod: () => 'old',
newMethod: () => 'new'
});
versionManager.register('utils', '2.0.0', {
newMethod: () => 'new',
betterMethod: () => 'better'
});
// 标记弃用
versionManager.deprecate('utils', '1.0.0', '请升级到1.1.0+版本');
// 获取模块
const utilsV1 = versionManager.get('utils', '^1.0.0');
console.log(utilsV1); // 1.1.0版本
const utilsLatest = versionManager.get('utils');
console.log(utilsLatest); // 2.0.0版本
总结
模块化是现代前端工程化的基石,从前端的脚本标签到ES Modules,再到模块联邦等高级模式,模块化技术不断演进。本文从简单模块加载器实现开始,逐步深入AMD、CMD规范,探讨ES Module的Polyfill技术,并补充了构建工具集成、模块联邦、性能优化等工程化实践。
关键要点总结:
-
模块加载器核心原理: 依赖管理、缓存、异步加载
-
规范演进: 从AMD/CMD到ES Modules的统一
-
工程化实践: 代码分割、懒加载、版本管理、依赖注入
-
未来趋势: 模块联邦、微前端架构、Web Assembly模块化
模块化不仅仅是技术选择,更是一种设计哲学。良好的模块化设计能够提升代码的可维护性、可测试性和团队协作效率。在实际项目中,应根据团队规模、项目复杂度和技术栈选择合适的模块化方案,并不断优化模块边界和依赖关系。
随着前端技术的不断发展,模块化将继续演进,但核心原则——关注点分离、接口抽象、依赖管理——将始终保持不变。掌握模块化的核心原理和实践,能够帮助开发者构建更健壮、可维护的前端应用。