Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

JavaScript 模块化开发指南

欢迎来到 JavaScript 模块化开发的世界!

关于本书

本书是一本全面介绍 JavaScript 模块化开发的技术指南,涵盖了从基础概念到现代工具链的完整知识体系。无论你是前端开发的新手,还是希望深入理解模块化机制的资深开发者,这本书都能为你提供有价值的内容。

内容概览

📚 基础篇

  • 模块化发展历史: 从全局变量到现代模块系统的演进历程
  • 核心概念: 理解模块化的本质和设计思想
  • 为什么需要模块化: 解决传统开发模式的痛点

🔧 技术篇

  • ES模块系统 (ESM): JavaScript 原生模块系统的深入解析
  • CommonJS: Node.js 生态的模块标准
  • AMD/UMD: 异步模块定义和通用模块格式

🛠️ 工具篇

  • 现代打包工具: Webpack、Rollup、Vite 等工具的模块处理机制
  • 转译工具: Babel、TypeScript 的模块转换原理
  • 运行环境: 浏览器、Node.js、Deno 中的模块差异

💡 实践篇

  • 最佳实践: 模块设计原则和性能优化策略
  • 高级主题: 微前端、Web Workers、WASM 集成
  • 实战案例: 真实项目中的模块化应用

学习路径

🚀 快速入门

如果你是模块化开发的新手,建议按以下顺序阅读:

  1. 为什么需要模块化
  2. 模块化的核心概念
  3. ES模块基础
  4. 导入与导出

🎯 深入理解

如果你已有一定基础,想要深入理解:

  1. 模块解析机制
  2. 循环依赖处理
  3. 与ES模块的互操作
  4. 现代工具链

🏆 高级应用

如果你希望掌握高级技巧:

  1. 性能优化
  2. 微前端模块化
  3. 大型项目模块组织

特色内容

  • 理论与实践并重: 既有深入的原理解析,也有丰富的代码示例
  • 覆盖主流技术: 包含所有主要的模块系统和工具链
  • 前瞻性内容: 涵盖最新的技术趋势和发展方向
  • 中文友好: 针对中文开发者的阅读习惯优化

如何使用本书

  • 按章节顺序阅读: 适合系统学习模块化知识
  • 作为参考手册: 遇到具体问题时快速查阅相关章节
  • 结合实践: 建议边阅读边在实际项目中应用

反馈与贡献

如果你在阅读过程中发现问题或有改进建议,欢迎通过以下方式反馈:

  • 在代码仓库中提交 Issue
  • 发送邮件至作者
  • 参与社区讨论

让我们一起构建更好的 JavaScript 模块化生态!


开始阅读: 模块化发展历史

模块化发展历史

早期的全局变量时代

在 JavaScript 诞生之初,所有的代码都运行在全局作用域中。

// 早期的代码组织方式
var userName = 'John';
var userAge = 25;

function getUserInfo() {
    return userName + ' is ' + userAge + ' years old';
}

存在的问题

  • 全局命名空间污染
  • 变量名冲突
  • 依赖关系不明确
  • 代码难以维护和复用

命名空间模式

为了解决全局变量冲突问题,开发者开始使用命名空间模式:

// 命名空间模式
var MyApp = {
    models: {},
    views: {},
    controllers: {}
};

MyApp.models.User = function(name) {
    this.name = name;
};

IIFE 模式

立即执行函数表达式(IIFE)提供了更好的封装:

// IIFE 模式
var MyModule = (function() {
    var privateVar = 'hidden';
    
    return {
        publicMethod: function() {
            return privateVar;
        }
    };
})();

模块化规范的诞生

CommonJS (2009)

  • 主要用于服务器端(Node.js)
  • 同步加载模块
  • 简单易用的 requiremodule.exports

AMD (2011)

  • 异步模块定义
  • 主要用于浏览器端
  • RequireJS 是主要实现

UMD (2011)

  • 通用模块定义
  • 兼容 CommonJS 和 AMD
  • 可在多种环境下运行

ES6 模块系统 (2015)

2015年,ECMAScript 6 引入了原生的模块系统:

// ES6 模块
import { userName } from './user.js';
export default class User {
    constructor(name) {
        this.name = name;
    }
}

主要特性

  • 静态结构
  • 编译时确定依赖
  • 支持树摇(Tree Shaking):自动移除未使用的代码,减小打包体积
  • 原生浏览器支持

现代模块化工具

打包工具

  • Webpack (2012): 强大的模块打包器,丰富的插件生态
  • Rollup (2015): 专注于库打包,优秀的Tree Shaking支持
  • Vite (2020): 基于原生 ESM 的构建工具,开发体验极佳
  • Bun (2021): 极速的JavaScript运行时和打包器
  • Rolldown (2024): Rollup的Rust实现,性能大幅提升

转译工具

  • Babel: 将现代 JavaScript 转换为兼容版本
  • TypeScript: 添加类型系统的 JavaScript 超集

时间线总结

年份里程碑说明
1995JavaScript 诞生全局变量时代开始
2009CommonJS 规范服务器端模块化标准
2011AMD/UMD 规范浏览器端异步模块化
2012Webpack 发布模块打包工具革命
2015ES6 模块系统JavaScript 原生模块支持
2020Vite 发布原生 ESM 开发体验

发展趋势

现代 JavaScript 模块化正朝着以下方向发展:

  1. 原生 ESM 优先: 越来越多工具支持原生 ES 模块
  2. 性能优化: 更快的构建速度和更小的包体积
  3. 开发体验: 更好的开发工具和调试支持
  4. 标准化: 各种环境间的模块化差异逐渐缩小

下一章: 为什么需要模块化

为什么需要模块化

在现代 JavaScript 开发中,模块化已经成为不可或缺的开发模式。本章将详细探讨模块化解决的核心问题。

传统开发模式的问题

1. 全局命名空间污染

// script1.js
var name = 'John';
var age = 25;

// script2.js  
var name = 'Jane'; // 覆盖了 script1.js 中的 name
var address = 'New York';

在传统开发模式下,所有变量都在全局作用域中,容易造成命名冲突。

2. 依赖关系混乱

<!-- HTML 中的脚本加载顺序 -->
<script src="utils.js"></script>
<script src="config.js"></script> 
<script src="main.js"></script>
<script src="app.js"></script>
  • 必须手动管理脚本加载顺序
  • 依赖关系不明确
  • 容易出现依赖缺失或循环依赖

3. 代码复用困难

// 每次使用都需要复制粘贴代码
function formatDate(date) {
    // 100 行格式化代码...
}

// 在多个文件中重复定义相同功能

4. 难以进行单元测试

// 全局函数难以独立测试
var userData = {}; // 全局状态

function processUser(id) {
    // 依赖全局状态,难以测试
    userData[id] = fetchUserData(id);
}

模块化带来的好处

1. 作用域隔离

// user.js
export class User {
    private name: string; // 私有属性
    
    constructor(name: string) {
        this.name = name;
    }
}

// main.js
import { User } from './user.js';
const user = new User('John'); // 不会污染全局作用域

2. 明确的依赖关系

// math.js
export function add(a, b) {
    return a + b;
}

export function multiply(a, b) {
    return a * b;
}

// calculator.js
import { add, multiply } from './math.js'; // 明确依赖

export function calculate(x, y) {
    return multiply(add(x, y), 2);
}

3. 代码复用和维护性

// utils/date.js - 可复用的日期工具
export function formatDate(date, format = 'YYYY-MM-DD') {
    // 实现日期格式化
}

export function parseDate(dateString) {
    // 实现日期解析
}

// 在多个项目中复用
import { formatDate } from '@utils/date';

4. 更好的测试支持

// user-service.js
export class UserService {
    constructor(apiClient) {
        this.apiClient = apiClient;
    }
    
    async getUser(id) {
        return this.apiClient.get(`/users/${id}`);
    }
}

// user-service.test.js
import { UserService } from './user-service.js';

// 可以轻松进行单元测试
const mockApiClient = { get: jest.fn() };
const userService = new UserService(mockApiClient);

模块化的核心价值

1. 关注点分离

每个模块专注于特定的功能:

// 数据层
export class UserRepository {
    async findById(id) { /* ... */ }
}

// 业务逻辑层  
export class UserService {
    constructor(userRepo) {
        this.userRepo = userRepo;
    }
}

// 表现层
export class UserController {
    constructor(userService) {
        this.userService = userService;
    }
}

2. 可扩展性

// 基础模块
export class BaseValidator {
    validate(data) {
        // 基础验证逻辑
    }
}

// 扩展模块
import { BaseValidator } from './base-validator.js';

export class EmailValidator extends BaseValidator {
    validate(email) {
        super.validate(email);
        // 邮箱特定验证逻辑
    }
}

3. 团队协作

// 团队成员 A 负责用户模块
// user/
//   ├── user.model.js
//   ├── user.service.js
//   └── user.controller.js

// 团队成员 B 负责订单模块  
// order/
//   ├── order.model.js
//   ├── order.service.js
//   └── order.controller.js

4. 性能优化

// 按需加载
const loadUserModule = () => import('./user/user.module.js');
const loadOrderModule = () => import('./order/order.module.js');

// 只加载当前页面需要的模块
if (currentPage === 'user') {
    const userModule = await loadUserModule();
}

现实场景对比

传统方式开发大型应用

// 一个巨大的 app.js 文件
var users = [];
var orders = [];
var products = [];

function addUser() { /* ... */ }
function removeUser() { /* ... */ }
function addOrder() { /* ... */ }
function removeOrder() { /* ... */ }
// ... 几千行代码

问题

  • 文件过大,难以维护
  • 功能耦合严重
  • 团队协作困难
  • 性能问题(一次性加载所有代码)

模块化方式开发

// 清晰的模块结构
src/
├── modules/
│   ├── user/
│   │   ├── user.service.js
│   │   ├── user.model.js
│   │   └── user.controller.js
│   ├── order/
│   │   ├── order.service.js
│   │   ├── order.model.js
│   │   └── order.controller.js
│   └── product/
│       ├── product.service.js
│       ├── product.model.js
│       └── product.controller.js
└── app.js

优势

  • 代码组织清晰
  • 功能独立,易于测试
  • 团队可以并行开发
  • 支持按需加载

总结

模块化不仅仅是一种编程技巧,更是现代软件开发的基础设施。它解决了传统开发模式的核心痛点:

  • 解决命名冲突:每个模块有独立的作用域
  • 明确依赖关系:import/export 明确声明依赖
  • 提高代码复用:模块可以在多个项目中使用
  • 改善可测试性:独立的模块易于单元测试
  • 支持团队协作:不同模块可以并行开发
  • 提升性能:支持按需加载和树摇优化

在下一章中,我们将深入了解模块化的核心概念和设计原则。


下一章: 模块化的核心概念

模块化的核心概念

本章将介绍 JavaScript 模块化的基础概念,帮助你建立对模块系统的整体认知。

什么是模块

模块(Module) 是一个独立的、可复用的代码单元,它封装了特定的功能,并可以与其他模块进行交互。

模块的基本特征

  1. 封装性: 模块内部实现细节对外部不可见
  2. 独立性: 模块具有独立的作用域
  3. 可复用性: 可以在多个地方使用同一个模块
  4. 接口明确: 通过明确的接口进行交互
// 一个简单的模块示例
// math.js
const PI = 3.14159;

function add(a, b) {
    return a + b;
}

function multiply(a, b) {
    return a * b;
}

// 对外提供的接口
export { add, multiply, PI };

模块系统的核心组成

1. 模块定义(Module Definition)

模块定义规定了如何创建和组织模块:

// ES6 模块定义
// user.js
class User {
    constructor(name, email) {
        this.name = name;
        this.email = email;
    }
    
    getInfo() {
        return `${this.name} <${this.email}>`;
    }
}

// 导出模块
export default User;
export { User as UserClass };

2. 模块导入(Module Import)

定义如何使用其他模块的功能:

// app.js
import User from './user.js';              // 导入默认导出
import { UserClass } from './user.js';     // 导入命名导出
import * as UserModule from './user.js';   // 导入所有导出

const user = new User('John', 'john@example.com');

3. 模块解析(Module Resolution)

规定如何查找和加载模块:

// 不同的模块路径解析
import utils from './utils.js';           // 相对路径
import lodash from 'lodash';              // npm 包
import config from '../config/app.js';   // 相对路径(上级目录)
import api from '/src/api/index.js';     // 绝对路径

4. 模块加载(Module Loading)

模块系统决定何时以及如何加载模块:

// 静态导入(编译时确定)
import { calculateTax } from './tax-utils.js';

// 动态导入(运行时确定)
const loadTaxUtils = async () => {
    const module = await import('./tax-utils.js');
    return module.calculateTax;
};

模块的作用域(Module Scope)

作用域隔离

每个模块都有自己的作用域,不会污染全局作用域:

// module1.js
const message = 'Hello from module1';
let counter = 0;

export function increment() {
    counter++;
    console.log(`Module1 counter: ${counter}`);
}

// module2.js  
const message = 'Hello from module2'; // 不会与 module1 冲突
let counter = 100;

export function increment() {
    counter++;
    console.log(`Module2 counter: ${counter}`);
}

私有和公开成员

// user-service.js
// 私有成员(不导出)
const API_KEY = 'secret-key';
const cache = new Map();

function validateUser(user) {
    return user && user.name && user.email;
}

// 公开成员(导出)
export class UserService {
    async getUser(id) {
        if (cache.has(id)) {
            return cache.get(id);
        }
        
        const user = await fetch(`/api/users/${id}`, {
            headers: { 'Authorization': API_KEY }
        }).then(r => r.json());
        
        if (validateUser(user)) {
            cache.set(id, user);
            return user;
        }
        
        throw new Error('Invalid user data');
    }
}

模块依赖关系

依赖图(Dependency Graph)

模块之间的依赖关系构成了一个有向图:

// 依赖关系示例
// app.js → user-controller.js → user-service.js → api-client.js

// api-client.js
export class ApiClient {
    async get(url) { /* ... */ }
    async post(url, data) { /* ... */ }
}

// user-service.js
import { ApiClient } from './api-client.js';

export class UserService {
    constructor() {
        this.apiClient = new ApiClient();
    }
}

// user-controller.js
import { UserService } from './user-service.js';

export class UserController {
    constructor() {
        this.userService = new UserService();
    }
}

// app.js
import { UserController } from './user-controller.js';

const userController = new UserController();

循环依赖问题

循环依赖是模块化中需要避免的问题:

// 问题示例:循环依赖
// moduleA.js
import { functionB } from './moduleB.js';

export function functionA() {
    return functionB() + 1;
}

// moduleB.js
import { functionA } from './moduleA.js'; // 循环依赖!

export function functionB() {
    return functionA() + 1; // 将导致错误
}

解决方案

// 方案 1:提取公共依赖
// common.js
export function baseFunction() {
    return 42;
}

// moduleA.js
import { baseFunction } from './common.js';

export function functionA() {
    return baseFunction() + 1;
}

// moduleB.js
import { baseFunction } from './common.js';

export function functionB() {
    return baseFunction() + 2;
}

模块的生命周期

1. 模块加载阶段

// 模块被首次导入时执行
console.log('Module initializing...');

const config = {
    apiUrl: process.env.API_URL || 'http://localhost:3000'
};

export { config };

2. 模块缓存

模块只会被加载和执行一次,后续导入会使用缓存:

// counter.js
let count = 0;

export function increment() {
    return ++count;
}

export function getCount() {
    return count;
}

// main.js
import { increment, getCount } from './counter.js';
import { increment as inc2 } from './counter.js'; // 同一个模块实例

console.log(increment()); // 1
console.log(inc2());      // 2 (共享状态)
console.log(getCount());  // 2

模块化设计原则

1. 单一职责原则(SRP)

每个模块应该只有一个明确的职责:

// 好的示例:单一职责
// email-validator.js - 只负责邮箱验证
export function validateEmail(email) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
}

// password-validator.js - 只负责密码验证
export function validatePassword(password) {
    return password.length >= 8 && /[A-Z]/.test(password);
}

2. 接口隔离原则(ISP)

模块应该提供精细化的接口:

// user-operations.js
export { createUser } from './user-create.js';
export { updateUser } from './user-update.js';
export { deleteUser } from './user-delete.js';
export { findUser } from './user-query.js';

// 使用者可以按需导入
import { createUser, findUser } from './user-operations.js';

3. 依赖倒置原则(DIP)

高层模块不应该依赖于低层模块,都应该依赖于抽象:

// 抽象接口
// storage-interface.js
export class StorageInterface {
    async save(key, value) {
        throw new Error('Method must be implemented');
    }
    
    async load(key) {
        throw new Error('Method must be implemented');
    }
}

// 具体实现
// local-storage.js
import { StorageInterface } from './storage-interface.js';

export class LocalStorage extends StorageInterface {
    async save(key, value) {
        localStorage.setItem(key, JSON.stringify(value));
    }
    
    async load(key) {
        const item = localStorage.getItem(key);
        return item ? JSON.parse(item) : null;
    }
}

// 高层模块
// user-service.js
export class UserService {
    constructor(storage) { // 依赖抽象,而非具体实现
        this.storage = storage;
    }
    
    async saveUser(user) {
        await this.storage.save(`user_${user.id}`, user);
    }
}

主流模块系统对比

在了解了模块化的核心概念后,让我们来对比一下 JavaScript 生态系统中的主要模块系统:

基本特性对比

特性ES Modules (ESM)CommonJS (CJS)AMDUMDRequireJS
标准状态✅ ECMA标准❌ Node.js事实标准❌ 社区标准❌ 社区标准❌ 库实现
首次发布2015 (ES6)2009201120132010
加载方式静态 + 动态同步异步通用异步
运行环境浏览器 + Node.jsNode.js 主要浏览器主要通用浏览器主要
语法复杂度简单简单中等复杂中等

语法对比

模块定义语法

// ===== ES Modules (ESM) =====
// 命名导出
export const PI = 3.14159;
export function add(a, b) { return a + b; }

// 默认导出
export default class Calculator {
  multiply(a, b) { return a * b; }
}

// ===== CommonJS (CJS) =====
// 导出
const PI = 3.14159;
function add(a, b) { return a + b; }

module.exports = { PI, add };
// 或者
exports.PI = PI;
exports.add = add;

// 默认导出
module.exports = class Calculator {
  multiply(a, b) { return a * b; }
};

// ===== AMD =====
define(['dependency1', 'dependency2'], function(dep1, dep2) {
  const PI = 3.14159;
  
  function add(a, b) { return a + b; }
  
  class Calculator {
    multiply(a, b) { return a * b; }
  }
  
  return {
    PI,
    add,
    Calculator
  };
});

// ===== UMD =====
(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD
    define(['dependency'], factory);
  } else if (typeof module === 'object' && module.exports) {
    // CommonJS
    module.exports = factory(require('dependency'));
  } else {
    // Browser globals
    root.MyModule = factory(root.Dependency);
  }
}(typeof self !== 'undefined' ? self : this, function (dependency) {
  const PI = 3.14159;
  
  function add(a, b) { return a + b; }
  
  return { PI, add };
}));

// ===== RequireJS =====
// 定义模块
define('math-utils', ['jquery'], function($) {
  const PI = 3.14159;
  
  function add(a, b) { return a + b; }
  
  return {
    PI: PI,
    add: add
  };
});

模块导入语法

// ===== ES Modules (ESM) =====
import Calculator, { PI, add } from './math.js';
import * as MathUtils from './math.js';

// 动态导入
const module = await import('./math.js');

// ===== CommonJS (CJS) =====
const { PI, add } = require('./math');
const Calculator = require('./math');
const MathUtils = require('./math');

// ===== AMD =====
require(['./math'], function(MathUtils) {
  console.log(MathUtils.PI);
});

// ===== RequireJS =====
requirejs.config({
  paths: {
    'math': './math'
  }
});

requirejs(['math'], function(MathUtils) {
  console.log(MathUtils.PI);
});

技术特性对比

特性ESMCommonJSAMDUMDRequireJS
Tree Shaking✅ 原生支持❌ 需工具支持❌ 有限支持❌ 不支持❌ 不支持
静态分析✅ 编译时确定❌ 运行时确定❌ 运行时确定❌ 运行时确定❌ 运行时确定
循环依赖✅ 原生处理⚠️ 部分支持⚠️ 部分支持⚠️ 部分支持⚠️ 部分支持
代码分割✅ 动态导入❌ 不支持✅ 异步加载❌ 取决于环境✅ 异步加载
浏览器支持✅ 现代浏览器❌ 需要打包✅ 直接支持✅ 通用支持✅ 直接支持
Node.js支持✅ v12+✅ 原生支持❌ 需要库✅ 通过包装❌ 需要库
打包工具支持✅ 广泛支持✅ 广泛支持⚠️ 部分支持✅ 广泛支持⚠️ 特定工具

性能对比

指标ESMCommonJSAMDUMDRequireJS
加载性能⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
解析速度⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
运行时开销⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
打包体积⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
启动时间⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

适用场景

模块系统最佳适用场景不适用场景
ES Modules• 现代Web应用
• Node.js新项目
• 需要Tree Shaking
• 库开发
• 旧浏览器支持
• 旧版Node.js项目
CommonJS• Node.js服务端
• 现有Node.js项目
• npm包开发
• 浏览器直接使用
• 需要异步加载
AMD• 浏览器应用
• 需要异步加载
• 复杂依赖关系
• Node.js环境
• 简单项目
UMD• 跨平台库
• 向后兼容
• 第三方库发布
• 现代应用开发
• 性能敏感应用
RequireJS• 旧版浏览器应用
• 渐进式升级
• 教学演示
• 新项目开发
• 现代工具链

生态系统支持

方面ESMCommonJSAMDUMDRequireJS
框架支持React, Vue, Angular等Express, Koa等部分旧框架通用支持旧版库
工具链Webpack, Vite, RollupWebpack, Browserifyr.js, Webpack所有打包工具r.js优化器
TypeScript✅ 完全支持✅ 完全支持⚠️ 配置复杂⚠️ 类型推导难⚠️ 配置复杂
测试工具Jest, Vitest等Jest, Mocha等特定配置需要适配特定配置
CDN支持Skypack, esm.sh需要打包jsDelivr等广泛支持jsDelivr等

迁移路径

// 从 CommonJS 迁移到 ESM
// Before (CommonJS)
const express = require('express');
const { createUser } = require('./user-service');

module.exports = {
  startServer: () => {
    const app = express();
    // ...
  }
};

// After (ESM)
import express from 'express';
import { createUser } from './user-service.js';

export function startServer() {
  const app = express();
  // ...
}

// 从 AMD 迁移到 ESM
// Before (AMD)
define(['lodash', './utils'], function(_, utils) {
  return {
    processData: function(data) {
      return _.map(data, utils.transform);
    }
  };
});

// After (ESM)
import _ from 'lodash';
import { transform } from './utils.js';

export function processData(data) {
  return _.map(data, transform);
}

未来趋势

趋势说明影响
ESM 标准化成为唯一标准其他系统逐步淘汰
原生支持增强浏览器和Node.js完全支持减少工具依赖
Import Maps标准化模块映射简化依赖管理
动态导入普及代码分割标准化提升应用性能
工具链整合围绕ESM构建开发体验提升

总结

模块化的核心概念包括:

  • 模块定义: 如何创建和组织模块
  • 导入导出: 模块间的接口交互
  • 作用域隔离: 保证模块独立性
  • 依赖管理: 构建清晰的依赖关系
  • 设计原则: 遵循良好的模块化实践

选择建议

  • 新项目: 优先选择 ES Modules
  • Node.js服务: CommonJS 仍然是主流,但可考虑 ESM
  • 库开发: ESM + UMD 兼容发布
  • 旧项目维护: 渐进式迁移到 ESM
  • 浏览器兼容: 使用构建工具将 ESM 转译为合适格式

理解这些核心概念和各系统的特点是掌握任何模块系统的基础。在接下来的章节中,我们将深入探讨具体的模块系统实现。


下一章: ES模块基础

ES模块基础

ES模块(ECMAScript Modules,简称ESM)是JavaScript的官方模块系统,在ES6(ES2015)中正式引入。它提供了一种标准化的方式来组织和重用JavaScript代码。

什么是ES模块

ES模块是JavaScript语言层面的模块系统,具有以下特点:

  • 静态结构: 模块的导入导出关系在编译时确定
  • 严格模式: 模块代码自动运行在严格模式下
  • 顶层作用域: 每个模块都有自己的顶层作用域
  • 异步加载: 支持异步模块加载
  • 树摇友好: 支持静态分析和死代码消除

基本语法

导出(Export)

ES模块提供了多种导出方式:

1. 命名导出(Named Exports)

// math.js

// 导出变量
export const PI = 3.14159;
export let counter = 0;

// 导出函数
export function add(a, b) {
    return a + b;
}

export function multiply(a, b) {
    return a * b;
}

// 导出类
export class Calculator {
    add(a, b) {
        return a + b;
    }
}

// 批量导出
const subtract = (a, b) => a - b;
const divide = (a, b) => a / b;

export { subtract, divide };

2. 默认导出(Default Export)

// user.js

// 默认导出类
export default class User {
    constructor(name, email) {
        this.name = name;
        this.email = email;
    }
    
    getInfo() {
        return `${this.name} <${this.email}>`;
    }
}

// 或者导出函数
// export default function createUser(name, email) {
//     return new User(name, email);
// }

// 或者导出值
// export default {
//     apiUrl: 'https://api.example.com',
//     timeout: 5000
// };

3. 混合导出

// api.js

// 默认导出
class ApiClient {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
    }
    
    async get(endpoint) {
        const response = await fetch(`${this.baseUrl}${endpoint}`);
        return response.json();
    }
}

export default ApiClient;

// 同时提供命名导出
export const DEFAULT_TIMEOUT = 5000;
export const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE'];

export function createApiClient(baseUrl) {
    return new ApiClient(baseUrl);
}

导入(Import)

1. 导入命名导出

// main.js

// 导入特定的命名导出
import { add, multiply, PI } from './math.js';

console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20
console.log(PI); // 3.14159

// 导入时重命名
import { add as sum, multiply as product } from './math.js';

console.log(sum(2, 3)); // 5
console.log(product(4, 5)); // 20

// 导入所有命名导出
import * as MathUtils from './math.js';

console.log(MathUtils.add(2, 3)); // 5
console.log(MathUtils.PI); // 3.14159

2. 导入默认导出

// app.js

// 导入默认导出
import User from './user.js';

const user = new User('John', 'john@example.com');
console.log(user.getInfo());

// 默认导出可以用任意名称
import MyUser from './user.js'; // 同样有效

3. 混合导入

// client.js

// 同时导入默认导出和命名导出
import ApiClient, { DEFAULT_TIMEOUT, createApiClient } from './api.js';

const client = new ApiClient('https://api.example.com');
const anotherClient = createApiClient('https://another-api.com');

console.log(`Default timeout: ${DEFAULT_TIMEOUT}ms`);

4. 仅导入模块(无绑定)

// 仅执行模块代码,不导入任何绑定
import './polyfills.js';
import './init-global-config.js';

模块的执行特性

1. 严格模式

ES模块代码自动运行在严格模式下,这带来了更严格的语法检查和更安全的执行环境:

严格模式的主要特征:

  • 禁止使用未声明的变量
  • 禁止删除不可删除的属性
  • 函数参数名必须唯一
  • 禁止八进制字面量
  • this在函数中不会自动指向全局对象
// module.js
// 模块代码自动运行在严格模式下

// 以下代码在模块中会报错
// undeclaredVariable = 'value'; // ReferenceError
// delete Object.prototype; // TypeError

console.log(this); // undefined(非浏览器环境)

2. 顶层作用域

// module1.js
var globalVar = 'module1';
let moduleVar = 'private to module1';

export { moduleVar };

// module2.js
var globalVar = 'module2'; // 不会与module1冲突
let moduleVar = 'private to module2';

export { moduleVar };

// main.js
import { moduleVar as var1 } from './module1.js';
import { moduleVar as var2 } from './module2.js';

console.log(var1); // 'private to module1'
console.log(var2); // 'private to module2'

3. 模块单例

// counter.js
let count = 0;

export function increment() {
    return ++count;
}

export function getCount() {
    return count;
}

// module1.js
import { increment } from './counter.js';
console.log(increment()); // 1

// module2.js
import { increment, getCount } from './counter.js';
console.log(increment()); // 2
console.log(getCount()); // 2

在HTML中使用ES模块

1. 基本用法

<!DOCTYPE html>
<html>
<head>
    <title>ES Modules Demo</title>
</head>
<body>
    <!-- 使用type="module"标识ES模块 -->
    <script type="module" src="./main.js"></script>
    
    <!-- 内联模块脚本 -->
    <script type="module">
        import { greet } from './utils.js';
        greet('World');
    </script>
</body>
</html>

2. 模块脚本的特性

<!-- ES模块脚本的特点 -->
<script type="module">
    // 1. 自动延迟执行(相当于defer)
    console.log('Module script executed');
    
    // 2. 严格模式
    console.log(this); // undefined
    
    // 3. 支持顶层await(现代浏览器)
    const data = await fetch('/api/data').then(r => r.json());
    console.log(data);
</script>

<!-- 传统脚本(用于对比) -->
<script>
    console.log('Regular script executed first');
</script>

3. 浏览器兼容性处理

<!-- 现代浏览器使用ES模块 -->
<script type="module" src="./modern-app.js"></script>

<!-- 旧浏览器降级方案 -->
<script nomodule src="./legacy-app.js"></script>

Node.js中的ES模块

1. 启用ES模块

方式1:使用.mjs扩展名

// app.mjs
import { readFile } from 'fs/promises';

const content = await readFile('./package.json', 'utf-8');
console.log(content);

方式2:在package.json中设置"type": "module"

{
  "type": "module",
  "main": "app.js"
}
// app.js (现在作为ES模块运行)
import { readFile } from 'fs/promises';

const content = await readFile('./package.json', 'utf-8');
console.log(content);

2. 内置模块的导入

// Node.js内置模块
import { readFile, writeFile } from 'fs/promises';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';

// 获取当前文件路径(在ES模块中__dirname不可用)
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const filePath = join(__dirname, 'data.txt');
const data = await readFile(filePath, 'utf-8');

ES模块的优势

1. 静态分析

// 静态结构使工具能够分析依赖关系
import { usedFunction } from './utils.js';
// import { unusedFunction } from './utils.js'; // 可以被工具检测并移除

// 动态导入无法进行静态分析
// const moduleName = getModuleName();
// import(moduleName); // 运行时确定

2. 树摇(Tree Shaking)

// utils.js
export function add(a, b) { return a + b; }
export function subtract(a, b) { return a - b; }
export function multiply(a, b) { return a * b; }
export function divide(a, b) { return a / b; }

// main.js
import { add, multiply } from './utils.js';
// 打包工具可以自动移除unused的subtract和divide函数

console.log(add(2, 3));
console.log(multiply(4, 5));

3. 循环依赖处理

// a.js
import { b } from './b.js';
export const a = 'a';
console.log('a.js:', b);

// b.js
import { a } from './a.js';
export const b = 'b';
console.log('b.js:', a); // undefined(但不会报错)

// ES模块能够优雅处理循环依赖

ES模块循环依赖处理机制:

  1. 模块记录创建: 在解析阶段,所有模块都会创建模块记录,但不立即执行
  2. 深度优先遍历: 按照依赖图进行深度优先遍历,确定执行顺序
  3. 实时绑定: 通过live binding机制,即使在循环依赖中也能获取到最终的导出值
  4. 延迟访问: 在模块完全初始化之前访问导出可能得到undefined,但不会抛出错误

这种机制使得ES模块能够安全地处理循环依赖,详细内容请参阅循环依赖章节

常见模式和最佳实践

1. 重新导出(Re-exports)

// components/index.js - 统一导出文件
export { Button } from './Button.js';
export { Input } from './Input.js';
export { Modal } from './Modal.js';

// 或者使用通配符重新导出
export * from './Button.js';
export * from './Input.js';

// 重新导出并重命名
export { default as MyButton } from './Button.js';

2. 条件导出

// config.js
const isDevelopment = process.env.NODE_ENV === 'development';

if (isDevelopment) {
    // 动态导入开发工具
    const devTools = await import('./dev-tools.js');
    devTools.setupDevMode();
}

export const config = {
    apiUrl: isDevelopment ? 'http://localhost:3000' : 'https://api.prod.com'
};

3. 工厂函数模式

// database.js
export function createDatabase(config) {
    return {
        connect() {
            console.log(`Connecting to ${config.host}:${config.port}`);
        },
        
        query(sql) {
            console.log(`Executing: ${sql}`);
        }
    };
}

// main.js
import { createDatabase } from './database.js';

const db = createDatabase({
    host: 'localhost',
    port: 5432
});

db.connect();

Live Binding(实时绑定)

Live Binding是ES模块的一个核心特性,它使得导入的绑定与导出的绑定保持实时同步。这与CommonJS的值复制完全不同。

什么是Live Binding

Live Binding意味着导入的变量是对导出变量的实时引用,而不是值的拷贝。当导出模块中的值发生变化时,导入模块中的对应变量也会自动更新。

Live Binding vs 值复制

CommonJS(值复制)

// counter.js (CommonJS)
let count = 0;

function increment() {
    count++;
}

function getCount() {
    return count;
}

// 导出的是值的副本
module.exports = { count, increment, getCount };
// main.js (CommonJS)
const { count, increment, getCount } = require('./counter');

console.log(count); // 0
increment();
console.log(count); // 0 ← 还是0,因为是值复制
console.log(getCount()); // 1 ← 通过函数才能获取最新值

ES模块(Live Binding)

// counter.mjs (ES模块)
export let count = 0;

export function increment() {
    count++;
}

export function decrement() {
    count--;
}

export function reset() {
    count = 0;
}
// main.mjs (ES模块)
import { count, increment, decrement, reset } from './counter.mjs';

console.log(count); // 0

increment();
console.log(count); // 1 ← 自动更新!

increment();
increment();
console.log(count); // 3 ← 继续同步!

decrement();
console.log(count); // 2 ← 实时反映变化

reset();
console.log(count); // 0 ← 重置后也同步

Live Binding的工作原理

// 演示Live Binding的内部机制

// module-a.mjs
export let sharedValue = 'initial';
export let counter = 0;

export function updateValue(newValue) {
    sharedValue = newValue;
    counter++;
    console.log(`模块A: 值更新为 ${sharedValue}, 计数器: ${counter}`);
}

export function getTimestamp() {
    return Date.now();
}
// module-b.mjs  
import { sharedValue, counter, updateValue } from './module-a.mjs';

export function showValues() {
    console.log(`模块B看到: ${sharedValue}, 计数器: ${counter}`);
}

export function triggerUpdate() {
    updateValue('从模块B更新');
}
// main.mjs
import { sharedValue, counter, updateValue } from './module-a.mjs';
import { showValues, triggerUpdate } from './module-b.mjs';

console.log('=== Live Binding 演示 ===');

console.log(`主模块初始值: ${sharedValue}, 计数器: ${counter}`);

// 从主模块更新
updateValue('从主模块更新');
console.log(`主模块更新后: ${sharedValue}, 计数器: ${counter}`);

// 从模块B更新
triggerUpdate();
console.log(`B模块更新后: ${sharedValue}, 计数器: ${counter}`);

// 显示所有模块都看到相同的值
showValues();

// 执行结果:
// 主模块初始值: initial, 计数器: 0
// 模块A: 值更新为 从主模块更新, 计数器: 1
// 主模块更新后: 从主模块更新, 计数器: 1
// 模块A: 值更新为 从模块B更新, 计数器: 2
// B模块更新后: 从模块B更新, 计数器: 2
// 模块B看到: 从模块B更新, 计数器: 2

Live Binding的重要特征

1. 只读性

// readonly-demo.mjs
export let value = 'original';

export function setValue(newValue) {
    value = newValue;
}
// main.mjs
import { value, setValue } from './readonly-demo.mjs';

console.log(value); // 'original'

// 以下操作会报错!
// value = 'modified'; // TypeError: Assignment to constant variable.

// 只能通过导出模块的函数来修改
setValue('modified');
console.log(value); // 'modified' ← Live Binding生效

2. 时间敏感性

// timing-demo.mjs
export let asyncValue = 'loading...';

// 模拟异步操作
setTimeout(() => {
    asyncValue = 'loaded!';
    console.log('异步操作完成,值已更新');
}, 1000);

export function getCurrentValue() {
    return asyncValue;
}
// main.mjs
import { asyncValue, getCurrentValue } from './timing-demo.mjs';

console.log('立即读取:', asyncValue); // 'loading...'

setTimeout(() => {
    console.log('1秒后读取:', asyncValue); // 'loaded!' ← Live Binding自动更新
}, 1500);

// 也可以通过函数获取
setTimeout(() => {
    console.log('通过函数:', getCurrentValue()); // 'loaded!'
}, 1500);

3. 循环依赖中的Live Binding

// circular-a.mjs
import { bValue, setBValue } from './circular-b.mjs';

export let aValue = 'from-a';

export function setAValue(newValue) {
    aValue = newValue;
}

export function showBValue() {
    console.log('A模块看到B的值:', bValue);
}

// 初始化时调用B模块的函数
setBValue('a-modified-b');
// circular-b.mjs  
import { aValue, setAValue } from './circular-a.mjs';

export let bValue = 'from-b';

export function setBValue(newValue) {
    bValue = newValue;
}

export function showAValue() {
    console.log('B模块看到A的值:', aValue);
}

// 初始化时调用A模块的函数
setAValue('b-modified-a');
// main.mjs
import { aValue, showBValue } from './circular-a.mjs';
import { bValue, showAValue } from './circular-b.mjs';

console.log('主模块看到:');
console.log('A值:', aValue); // 'b-modified-a'
console.log('B值:', bValue); // 'a-modified-b'

showAValue(); // B模块看到A的值: b-modified-a
showBValue(); // A模块看到B的值: a-modified-b

Live Binding的优势

1. 状态同步

// state-manager.mjs
export let appState = {
    user: null,
    theme: 'light',
    language: 'en'
};

export function login(user) {
    appState.user = user;
    console.log('用户已登录:', user.name);
}

export function setTheme(theme) {
    appState.theme = theme;
    console.log('主题已切换:', theme);
}

export function setLanguage(language) {
    appState.language = language;
    console.log('语言已切换:', language);
}
// ui-components.mjs
import { appState } from './state-manager.mjs';

export function renderHeader() {
    const { user, theme } = appState;
    console.log(`渲染头部: 用户=${user?.name || '未登录'}, 主题=${theme}`);
}

export function renderSidebar() {
    const { language, theme } = appState;
    console.log(`渲染侧边栏: 语言=${language}, 主题=${theme}`);
}
// main.mjs
import { login, setTheme, setLanguage } from './state-manager.mjs';
import { renderHeader, renderSidebar } from './ui-components.mjs';

// 初始渲染
renderHeader(); // 渲染头部: 用户=未登录, 主题=light
renderSidebar(); // 渲染侧边栏: 语言=en, 主题=light

// 状态变化
login({ name: 'Alice', id: 1 });
renderHeader(); // 渲染头部: 用户=Alice, 主题=light ← 自动更新

setTheme('dark');
renderHeader(); // 渲染头部: 用户=Alice, 主题=dark ← 继续同步
renderSidebar(); // 渲染侧边栏: 语言=en, 主题=dark ← 同时更新

2. 热重载支持

// hot-reload-demo.mjs
export let moduleVersion = '1.0.0';
export let featureFlags = {
    newUI: false,
    experimentalAPI: true
};

// 模拟热重载更新
if (import.meta.hot) {
    import.meta.hot.accept(() => {
        moduleVersion = '1.0.1';
        featureFlags.newUI = true;
        console.log('模块已热重载更新');
    });
}

Live Binding的注意事项

1. 性能考虑

// 避免在热点路径中频繁访问Live Binding
import { heavyComputedValue } from './expensive-module.mjs';

// ❌ 不好的做法
for (let i = 0; i < 1000000; i++) {
    if (heavyComputedValue > threshold) {
        // 每次循环都访问Live Binding
    }
}

// ✅ 更好的做法
const cachedValue = heavyComputedValue;
for (let i = 0; i < 1000000; i++) {
    if (cachedValue > threshold) {
        // 使用缓存的值
    }
}

2. 调试技巧

// debug-utils.mjs
export let debugMode = false;
export let logLevel = 'info';

export function enableDebug() {
    debugMode = true;
    logLevel = 'debug';
}

export function log(message, level = 'info') {
    if (debugMode && shouldLog(level)) {
        console.log(`[${level.toUpperCase()}] ${message}`);
    }
}

function shouldLog(level) {
    const levels = { debug: 0, info: 1, warn: 2, error: 3 };
    return levels[level] >= levels[logLevel];
}

总结

ES模块是现代JavaScript开发的基础设施,提供了:

  • 标准化语法: 官方标准,广泛支持
  • 静态结构: 编译时优化,支持树摇
  • 严格模式: 更安全的代码执行环境
  • 作用域隔离: 避免全局变量污染
  • 异步加载: 更好的性能和用户体验
  • 工具友好: 丰富的开发工具生态

掌握ES模块的基础语法和特性是现代JavaScript开发的必备技能。在下一章中,我们将深入探讨导入导出的高级用法。


下一章: 导入与导出

导入与导出

ES模块的导入和导出是模块系统的核心功能。本章将深入探讨各种导入导出模式、高级用法以及最佳实践。

导出的详细语法

1. 声明时导出

// declarations.js

// 导出变量声明
export const API_VERSION = 'v1';
export let currentUser = null;
export var debugMode = false;

// 导出函数声明
export function getUserById(id) {
    return fetch(`/api/users/${id}`);
}

// 导出异步函数
export async function fetchUserData(id) {
    const response = await getUserById(id);
    return response.json();
}

// 导出生成器函数
export function* numberGenerator() {
    let i = 0;
    while (true) {
        yield i++;
    }
}

// 导出类声明
export class UserManager {
    constructor() {
        this.users = new Map();
    }
    
    addUser(user) {
        this.users.set(user.id, user);
    }
    
    getUser(id) {
        return this.users.get(id);
    }
}

2. 先声明后导出

// deferred-exports.js

// 先声明
const CONFIG = {
    apiUrl: 'https://api.example.com',
    timeout: 5000,
    retryAttempts: 3
};

function validateConfig(config) {
    return config.apiUrl && config.timeout > 0;
}

class Logger {
    constructor(level = 'info') {
        this.level = level;
    }
    
    log(message) {
        console.log(`[${this.level.toUpperCase()}] ${message}`);
    }
}

// 后导出
export { CONFIG, validateConfig, Logger };

// 导出时重命名
export { CONFIG as defaultConfig };
export { Logger as EventLogger };

3. 默认导出的多种形式

// default-exports.js

// 方式1:导出类
export default class Calculator {
    add(a, b) { return a + b; }
    subtract(a, b) { return a - b; }
}

// 方式2:导出函数
// export default function calculate(operation, a, b) {
//     switch (operation) {
//         case 'add': return a + b;
//         case 'subtract': return a - b;
//         default: throw new Error('Unknown operation');
//     }
// }

// 方式3:导出箭头函数
// export default (x, y) => x + y;

// 方式4:导出对象
// export default {
//     version: '1.0.0',
//     author: 'John Doe',
//     calculate: (a, b) => a + b
// };

// 方式5:导出表达式
// const multiplier = 2;
// export default multiplier * 10;

// 方式6:先声明后导出
// class AdvancedCalculator {
//     // ... implementation
// }
// export default AdvancedCalculator;

4. 聚合导出(Re-exports)

// utils/index.js - 模块聚合文件

// 重新导出所有命名导出
export * from './string-utils.js';
export * from './number-utils.js';
export * from './date-utils.js';

// 重新导出特定的命名导出
export { debounce, throttle } from './function-utils.js';
export { formatCurrency } from './number-utils.js';

// 重新导出并重命名
export { 
    parseDate as parseDateString,
    formatDate as formatDateString 
} from './date-utils.js';

// 重新导出默认导出
export { default as StringValidator } from './string-validator.js';
export { default as NumberValidator } from './number-validator.js';

// 混合导出:本地定义 + 重新导出
export const UTILS_VERSION = '2.1.0';

export function createUtilsBundle() {
    return {
        strings: './string-utils.js',
        numbers: './number-utils.js',
        dates: './date-utils.js'
    };
}

导入的详细语法

1. 命名导入的各种形式

// named-imports.js

// 基本命名导入
import { API_VERSION, currentUser } from './declarations.js';

// 导入时重命名
import { 
    getUserById as fetchUser,
    UserManager as UserService 
} from './declarations.js';

// 批量导入多个命名导出
import { 
    API_VERSION,
    currentUser,
    getUserById,
    UserManager 
} from './declarations.js';

// 导入所有命名导出到命名空间对象
import * as UserAPI from './declarations.js';
console.log(UserAPI.API_VERSION);
const manager = new UserAPI.UserManager();

// 混合导入:默认导出 + 命名导出
import Calculator, { 
    API_VERSION, 
    getUserById 
} from './mixed-exports.js';

2. 默认导入

// default-imports.js

// 导入默认导出(可以使用任意名称)
import Calculator from './default-exports.js';
import MyCalculator from './default-exports.js'; // 同样有效
import Calc from './default-exports.js'; // 也可以

// 使用导入的默认导出
const calc = new Calculator();
console.log(calc.add(2, 3));

// 如果默认导出是函数
import calculate from './function-export.js';
const result = calculate('add', 5, 3);

// 如果默认导出是对象
import config from './config-export.js';
console.log(config.version);

3. 副作用导入

// side-effect-imports.js

// 仅执行模块,不导入任何绑定
import './polyfills.js';          // 执行polyfill代码
import './global-styles.css';     // 加载CSS(通过构建工具处理)
import './init-app.js';           // 执行初始化代码

// 这些导入会执行目标模块的代码,但不创建任何绑定
console.log('All side effects have been applied');

4. 条件导入

// conditional-imports.js

// 在条件块中的静态导入(会被提升)
if (process.env.NODE_ENV === 'development') {
    // 这个导入会被提升到模块顶部执行
    import './dev-tools.js';
}

// 正确的条件导入方式:使用动态导入
async function loadDevTools() {
    if (process.env.NODE_ENV === 'development') {
        const devTools = await import('./dev-tools.js');
        devTools.setup();
    }
}

// 或者使用顶层await(现代环境)
if (process.env.NODE_ENV === 'development') {
    const { setup } = await import('./dev-tools.js');
    setup();
}

模块导入解析规则

1. 相对路径导入

// 相对路径导入示例
// 文件结构:
// src/
//   ├── components/
//   │   ├── Button.js
//   │   └── Input.js
//   ├── utils/
//   │   └── helpers.js
//   └── app.js

// 在 src/app.js 中
import { Button } from './components/Button.js';    // 相对路径
import { helpers } from './utils/helpers.js';       // 相对路径

// 在 src/components/Button.js 中
import { helpers } from '../utils/helpers.js';      // 上级目录
import { Input } from './Input.js';                 // 同级文件

2. 绝对路径和包导入

// absolute-imports.js

// Node.js 内置模块
import { readFile } from 'fs/promises';
import { join } from 'path';

// npm 包导入
import lodash from 'lodash';
import { debounce } from 'lodash';
import React from 'react';
import { useState } from 'react';

// 作用域包
import { parse } from '@babel/parser';
import { transform } from '@babel/core';

// 子路径导入
import { format } from 'date-fns/format';
import { isValid } from 'date-fns/isValid';

3. 带扩展名和不带扩展名

// extensions.js

// 明确指定扩展名(推荐)
import { utils } from './utils.js';
import config from './config.json';    // JSON文件
import styles from './styles.css';     // CSS文件(需构建工具支持)

// 不指定扩展名(依赖解析器配置)
// import { utils } from './utils';     // 可能解析为utils.js或utils/index.js
// import config from './config';       // 可能解析为config.js或config.json

导入导出的高级模式

1. 条件导出(package.json)

{
  "name": "my-package",
  "exports": {
    ".": {
      "import": "./dist/esm/index.js",
      "require": "./dist/cjs/index.js",
      "types": "./dist/types/index.d.ts"
    },
    "./utils": {
      "import": "./dist/esm/utils.js",
      "require": "./dist/cjs/utils.js"
    },
    "./package.json": "./package.json"
  }
}

2. 桶文件模式(Barrel Exports)

// components/index.js - 桶文件
export { default as Button } from './Button.js';
export { default as Input } from './Input.js';
export { default as Modal } from './Modal.js';
export { default as Form } from './Form.js';

// 也可以重新导出类型(TypeScript)
export type { ButtonProps } from './Button.js';
export type { InputProps } from './Input.js';

// 使用桶文件
// 在其他文件中可以从单一入口导入多个组件
import { Button, Input, Modal } from './components';

3. 命名空间模式

// math/index.js - 命名空间模式
import * as BasicMath from './basic.js';
import * as AdvancedMath from './advanced.js';
import * as Statistics from './statistics.js';

export const Math = {
    Basic: BasicMath,
    Advanced: AdvancedMath,
    Statistics
};

// 也导出具体函数以便直接使用
export { add, subtract, multiply, divide } from './basic.js';
export { sin, cos, tan, log } from './advanced.js';
export { mean, median, mode } from './statistics.js';

// 使用方式
// import { Math } from './math';
// Math.Basic.add(1, 2);
// 
// 或者
// import { add, sin, mean } from './math';
// add(1, 2);

4. 插件系统模式

// plugin-system.js

// 插件注册表
const plugins = new Map();

export function registerPlugin(name, plugin) {
    plugins.set(name, plugin);
}

export function getPlugin(name) {
    return plugins.get(name);
}

export function loadPlugin(name) {
    return import(`./plugins/${name}.js`).then(module => {
        registerPlugin(name, module.default);
        return module.default;
    });
}

// 插件接口
export class PluginBase {
    constructor(options = {}) {
        this.options = options;
    }
    
    init() {
        throw new Error('Plugin must implement init method');
    }
    
    destroy() {
        // 默认清理逻辑
    }
}

// 使用示例
// const authPlugin = await loadPlugin('auth');
// authPlugin.init({ apiKey: 'xxx' });

导入导出的最佳实践

1. 导出优先级

// good-exports.js

// 1. 优先使用命名导出
export function createUser(data) { /* ... */ }
export function updateUser(id, data) { /* ... */ }
export function deleteUser(id) { /* ... */ }

// 2. 主要功能使用默认导出
export default class UserService {
    constructor() {
        this.cache = new Map();
    }
    
    async getUser(id) {
        if (this.cache.has(id)) {
            return this.cache.get(id);
        }
        // ... fetch logic
    }
}

// 3. 常量和配置使用命名导出
export const DEFAULT_TIMEOUT = 5000;
export const USER_ROLES = ['admin', 'user', 'guest'];

2. 导入组织

// organized-imports.js

// 1. Node.js 内置模块
import { readFile } from 'fs/promises';
import { join, dirname } from 'path';

// 2. 第三方依赖
import React, { useState, useEffect } from 'react';
import { format } from 'date-fns';
import axios from 'axios';

// 3. 内部模块(按层级分组)
import { config } from '../config/app.js';
import { UserService } from '../services/user.js';
import { validateInput } from '../utils/validation.js';

// 4. 相对导入
import { Button } from './Button.js';
import { Modal } from './Modal.js';
import styles from './UserForm.module.css';

3. 避免循环依赖

// 反模式:循环依赖
// user.js
import { Order } from './order.js';
export class User {
    getOrders() {
        return Order.findByUserId(this.id);
    }
}

// order.js
import { User } from './user.js';  // 循环依赖!
export class Order {
    getUser() {
        return User.findById(this.userId);
    }
}

// 解决方案1:依赖注入
// user.js
export class User {
    getOrders(orderService) {
        return orderService.findByUserId(this.id);
    }
}

// order.js
export class Order {
    getUser(userService) {
        return userService.findById(this.userId);
    }
}

// 解决方案2:提取到服务层
// user-order-service.js
import { User } from './user.js';
import { Order } from './order.js';

export class UserOrderService {
    getUserOrders(userId) {
        return Order.findByUserId(userId);
    }
    
    getOrderUser(orderId) {
        const order = Order.findById(orderId);
        return User.findById(order.userId);
    }
}

4. 模块重新导出策略

// api/index.js - API 模块聚合

// 按功能模块重新导出
export * as Users from './users.js';
export * as Orders from './orders.js';
export * as Products from './products.js';

// 导出常用的API函数
export { 
    createUser, 
    getUserById 
} from './users.js';

export { 
    createOrder, 
    getOrdersByUser 
} from './orders.js';

// 导出配置和常量
export { API_BASE_URL, DEFAULT_HEADERS } from './config.js';

// 导出默认客户端
export { default as ApiClient } from './client.js';

// 使用示例
// 方式1:使用命名空间
// import { Users, Orders } from './api';
// const user = await Users.getUserById(1);
// const orders = await Orders.getOrdersByUser(1);

// 方式2:直接导入常用函数
// import { createUser, getOrdersByUser } from './api';

常见陷阱和注意事项

1. 导入提升

// import-hoisting.js

console.log('This runs first');

// 这个导入会被提升到模块顶部执行
import { someFunction } from './utils.js';

console.log('This runs second');

// 相当于:
// import { someFunction } from './utils.js';  // <- 实际执行位置
// console.log('This runs first');
// console.log('This runs second');

2. 默认导出的陷阱

// default-export-pitfalls.js

// 陷阱1:默认导出的重命名容易出错
// math.js
export default function add(a, b) { return a + b; }

// main.js
import subtract from './math.js';  // 错误:以为导入的是subtract函数
console.log(subtract(5, 3));       // 实际上是add函数,结果是8而不是2

// 解决方案:使用命名导出
// math.js
export function add(a, b) { return a + b; }
export function subtract(a, b) { return a - b; }

// main.js
import { add, subtract } from './math.js';  // 明确的函数名

3. 导入绑定的活性

// live-bindings.js

// counter.js
export let count = 0;

export function increment() {
    count++;
}

export function getCount() {
    return count;
}

// main.js
import { count, increment } from './counter.js';

console.log(count);    // 0
increment();
console.log(count);    // 1 (绑定是活的,会更新)

// 但是不能直接修改导入的绑定
// count = 10;         // TypeError: Assignment to constant variable

总结

ES模块的导入导出系统提供了强大而灵活的代码组织方式:

  • 多样的导出方式: 命名导出、默认导出、重新导出
  • 灵活的导入语法: 支持重命名、命名空间、条件导入
  • 静态结构: 便于工具分析和优化
  • 活绑定: 导入的绑定能反映导出模块的状态变化
  • 良好的重用性: 支持模块聚合和重新导出模式

掌握这些导入导出的技巧和最佳实践,能够帮助你构建更清晰、更可维护的模块化代码。在下一章中,我们将探讨ES模块的动态导入功能。


下一章: 动态导入

动态导入

动态导入(Dynamic Import)是ES2020引入的功能,允许在运行时按需加载模块。与静态导入不同,动态导入提供了更大的灵活性,支持条件加载、懒加载和代码分割等高级用法。

基本语法

import() 函数

// 基本动态导入语法
import('./module.js')
    .then(module => {
        // 使用导入的模块
        console.log(module.default);
        console.log(module.namedExport);
    })
    .catch(err => {
        console.error('Failed to load module:', err);
    });

// 使用 async/await
async function loadModule() {
    try {
        const module = await import('./module.js');
        return module;
    } catch (error) {
        console.error('Module loading failed:', error);
        throw error;
    }
}

顶层 await 与动态导入

// top-level-await.js
// 在支持顶层await的环境中使用

// 条件性加载模块
const isDevelopment = process.env.NODE_ENV === 'development';

if (isDevelopment) {
    const devTools = await import('./dev-tools.js');
    devTools.setup();
    console.log('Development tools loaded');
}

// 动态选择实现
const preferredImplementation = getPreferredImplementation();
const implementation = await import(`./implementations/${preferredImplementation}.js`);

export default implementation.default;

动态导入的应用场景

1. 条件加载

// conditional-loading.js

// 根据用户权限加载不同的模块
async function loadUserInterface(userRole) {
    switch (userRole) {
        case 'admin':
            const adminUI = await import('./admin-ui.js');
            return adminUI.default;
            
        case 'moderator':
            const modUI = await import('./moderator-ui.js');
            return modUI.default;
            
        case 'user':
        default:
            const userUI = await import('./user-ui.js');
            return userUI.default;
    }
}

// 根据功能特性加载polyfill
async function loadPolyfills() {
    const promises = [];
    
    if (!('IntersectionObserver' in window)) {
        promises.push(import('./polyfills/intersection-observer.js'));
    }
    
    if (!('fetch' in window)) {
        promises.push(import('./polyfills/fetch.js'));
    }
    
    if (!('Promise' in window)) {
        promises.push(import('./polyfills/promise.js'));
    }
    
    return Promise.all(promises);
}

// 使用示例
const user = getCurrentUser();
const ui = await loadUserInterface(user.role);
ui.render(document.body);

2. 懒加载和代码分割

// lazy-loading.js

// 路由懒加载
const routes = {
    '/': () => import('./pages/Home.js'),
    '/about': () => import('./pages/About.js'),
    '/contact': () => import('./pages/Contact.js'),
    '/dashboard': () => import('./pages/Dashboard.js'),
    '/settings': () => import('./pages/Settings.js')
};

async function navigateTo(path) {
    const loadPage = routes[path];
    
    if (!loadPage) {
        throw new Error(`No route found for ${path}`);
    }
    
    try {
        const pageModule = await loadPage();
        const page = new pageModule.default();
        
        // 清理当前页面
        clearCurrentPage();
        
        // 渲染新页面
        page.render(document.getElementById('app'));
        
        // 更新浏览器历史
        history.pushState({}, '', path);
    } catch (error) {
        console.error(`Failed to load page ${path}:`, error);
        showErrorPage();
    }
}

// 组件懒加载
class ComponentLoader {
    constructor() {
        this.cache = new Map();
    }
    
    async loadComponent(name) {
        // 检查缓存
        if (this.cache.has(name)) {
            return this.cache.get(name);
        }
        
        try {
            const module = await import(`./components/${name}.js`);
            const component = module.default;
            
            // 缓存组件
            this.cache.set(name, component);
            
            return component;
        } catch (error) {
            console.error(`Failed to load component ${name}:`, error);
            return null;
        }
    }
}

const loader = new ComponentLoader();

// 使用示例
document.addEventListener('click', async (event) => {
    if (event.target.dataset.component) {
        const componentName = event.target.dataset.component;
        const Component = await loader.loadComponent(componentName);
        
        if (Component) {
            const instance = new Component();
            instance.mount(event.target.parentElement);
        }
    }
});

3. 插件系统

// plugin-system.js

class PluginManager {
    constructor() {
        this.plugins = new Map();
        this.hooks = new Map();
    }
    
    // 动态加载插件
    async loadPlugin(pluginName, config = {}) {
        try {
            const pluginModule = await import(`./plugins/${pluginName}/index.js`);
            const PluginClass = pluginModule.default;
            
            const plugin = new PluginClass(config);
            
            // 初始化插件
            if (typeof plugin.init === 'function') {
                await plugin.init();
            }
            
            // 注册插件
            this.plugins.set(pluginName, plugin);
            
            // 注册插件的钩子
            if (plugin.hooks) {
                Object.entries(plugin.hooks).forEach(([hookName, handler]) => {
                    this.registerHook(hookName, handler);
                });
            }
            
            console.log(`Plugin ${pluginName} loaded successfully`);
            return plugin;
        } catch (error) {
            console.error(`Failed to load plugin ${pluginName}:`, error);
            throw error;
        }
    }
    
    // 卸载插件
    async unloadPlugin(pluginName) {
        const plugin = this.plugins.get(pluginName);
        
        if (!plugin) {
            return false;
        }
        
        // 执行清理
        if (typeof plugin.destroy === 'function') {
            await plugin.destroy();
        }
        
        // 移除钩子
        if (plugin.hooks) {
            Object.keys(plugin.hooks).forEach(hookName => {
                this.unregisterHook(hookName, plugin.hooks[hookName]);
            });
        }
        
        this.plugins.delete(pluginName);
        console.log(`Plugin ${pluginName} unloaded`);
        return true;
    }
    
    registerHook(hookName, handler) {
        if (!this.hooks.has(hookName)) {
            this.hooks.set(hookName, []);
        }
        this.hooks.get(hookName).push(handler);
    }
    
    unregisterHook(hookName, handler) {
        const handlers = this.hooks.get(hookName);
        if (handlers) {
            const index = handlers.indexOf(handler);
            if (index > -1) {
                handlers.splice(index, 1);
            }
        }
    }
    
    // 触发钩子
    async triggerHook(hookName, ...args) {
        const handlers = this.hooks.get(hookName) || [];
        const results = await Promise.all(
            handlers.map(handler => handler(...args))
        );
        return results;
    }
}

// 使用示例
const pluginManager = new PluginManager();

// 加载插件
await pluginManager.loadPlugin('analytics', {
    trackingId: 'GA-XXXXXX-X'
});

await pluginManager.loadPlugin('chat-widget', {
    apiKey: 'chat-api-key',
    position: 'bottom-right'
});

// 触发钩子
await pluginManager.triggerHook('user-login', { userId: 123 });

4. 国际化和本地化

// i18n.js

class I18nManager {
    constructor(defaultLocale = 'en') {
        this.currentLocale = defaultLocale;
        this.translations = new Map();
        this.fallbacks = new Map();
    }
    
    // 动态加载语言包
    async loadLocale(locale) {
        if (this.translations.has(locale)) {
            return this.translations.get(locale);
        }
        
        try {
            // 尝试加载完整语言包
            const fullModule = await import(`./locales/${locale}.js`);
            this.translations.set(locale, fullModule.default);
            return fullModule.default;
        } catch (error) {
            // 如果失败,尝试加载语言的基础版本
            const baseLang = locale.split('-')[0];
            if (baseLang !== locale) {
                try {
                    const baseModule = await import(`./locales/${baseLang}.js`);
                    this.fallbacks.set(locale, baseModule.default);
                    return baseModule.default;
                } catch (baseError) {
                    console.error(`Failed to load locale ${locale} and ${baseLang}:`, error, baseError);
                }
            }
            throw error;
        }
    }
    
    // 设置当前语言
    async setLocale(locale) {
        await this.loadLocale(locale);
        this.currentLocale = locale;
        
        // 触发语言变更事件
        document.dispatchEvent(new CustomEvent('locale-changed', {
            detail: { locale, translations: this.translations.get(locale) }
        }));
    }
    
    // 获取翻译
    t(key, params = {}) {
        const translations = this.translations.get(this.currentLocale) ||
                           this.fallbacks.get(this.currentLocale) ||
                           this.translations.get('en');
        
        if (!translations) {
            return key;
        }
        
        let text = this.getNestedValue(translations, key) || key;
        
        // 替换参数
        Object.entries(params).forEach(([param, value]) => {
            text = text.replace(new RegExp(`\\{\\{${param}\\}\\}`, 'g'), value);
        });
        
        return text;
    }
    
    getNestedValue(obj, path) {
        return path.split('.').reduce((current, key) => {
            return current && current[key] !== undefined ? current[key] : null;
        }, obj);
    }
}

// 使用示例
const i18n = new I18nManager('en');

// 检测用户语言并加载
const userLocale = navigator.language || navigator.userLanguage || 'en';
await i18n.setLocale(userLocale);

// 在应用中使用
console.log(i18n.t('welcome.message', { name: 'John' }));
console.log(i18n.t('navigation.home'));

// 语言切换器
async function switchLanguage(locale) {
    try {
        await i18n.setLocale(locale);
        updateUI();
    } catch (error) {
        console.error('Failed to switch language:', error);
    }
}

动态导入的高级用法

1. 并行加载多个模块

// parallel-loading.js

// 并行加载多个相关模块
async function loadDashboardModules() {
    const [
        chartsModule,
        tablesModule,
        filtersModule,
        exportModule
    ] = await Promise.all([
        import('./charts.js'),
        import('./tables.js'),
        import('./filters.js'),
        import('./export.js')
    ]);
    
    return {
        Charts: chartsModule.default,
        Tables: tablesModule.default,
        Filters: filtersModule.default,
        Export: exportModule.default
    };
}

// 使用Promise.allSettled处理部分失败
async function loadOptionalModules() {
    const results = await Promise.allSettled([
        import('./analytics.js'),
        import('./chat.js'),
        import('./notifications.js'),
        import('./help.js')
    ]);
    
    const loadedModules = {};
    
    results.forEach((result, index) => {
        const moduleNames = ['analytics', 'chat', 'notifications', 'help'];
        const moduleName = moduleNames[index];
        
        if (result.status === 'fulfilled') {
            loadedModules[moduleName] = result.value.default;
            console.log(`${moduleName} module loaded successfully`);
        } else {
            console.warn(`${moduleName} module failed to load:`, result.reason);
        }
    });
    
    return loadedModules;
}

2. 模块预加载

// preloading.js

class ModulePreloader {
    constructor() {
        this.preloadCache = new Map();
        this.loadingPromises = new Map();
    }
    
    // 预加载模块(但不执行)
    preload(modulePath) {
        if (this.preloadCache.has(modulePath)) {
            return this.preloadCache.get(modulePath);
        }
        
        // 创建link标签进行预加载
        const link = document.createElement('link');
        link.rel = 'modulepreload';
        link.href = modulePath;
        document.head.appendChild(link);
        
        const promise = import(modulePath).then(module => {
            this.preloadCache.set(modulePath, module);
            return module;
        });
        
        this.loadingPromises.set(modulePath, promise);
        return promise;
    }
    
    // 立即获取预加载的模块
    async getPreloaded(modulePath) {
        if (this.preloadCache.has(modulePath)) {
            return this.preloadCache.get(modulePath);
        }
        
        if (this.loadingPromises.has(modulePath)) {
            return this.loadingPromises.get(modulePath);
        }
        
        return import(modulePath);
    }
    
    // 批量预加载
    preloadBatch(modulePaths) {
        return Promise.all(modulePaths.map(path => this.preload(path)));
    }
    
    // 智能预加载:根据用户行为预测
    intelligentPreload(userBehavior) {
        const predictions = this.predictNextModules(userBehavior);
        return this.preloadBatch(predictions);
    }
    
    predictNextModules(behavior) {
        // 简单的预测逻辑
        const moduleMap = {
            'viewing-products': ['./cart.js', './checkout.js'],
            'in-cart': ['./payment.js', './shipping.js'],
            'profile-page': ['./settings.js', './orders.js']
        };
        
        return moduleMap[behavior] || [];
    }
}

const preloader = new ModulePreloader();

// 在应用启动时预加载关键模块
preloader.preloadBatch([
    './router.js',
    './auth.js',
    './api-client.js'
]);

// 根据用户行为智能预加载
document.addEventListener('mouseover', (event) => {
    if (event.target.dataset.preload) {
        preloader.preload(event.target.dataset.preload);
    }
});

3. 容错和重试机制

// error-handling.js

class RobustModuleLoader {
    constructor(options = {}) {
        this.maxRetries = options.maxRetries || 3;
        this.retryDelay = options.retryDelay || 1000;
        this.fallbacks = options.fallbacks || {};
    }
    
    async loadWithRetry(modulePath, retryCount = 0) {
        try {
            return await import(modulePath);
        } catch (error) {
            console.warn(`Failed to load ${modulePath} (attempt ${retryCount + 1}):`, error);
            
            if (retryCount < this.maxRetries) {
                // 指数退避
                const delay = this.retryDelay * Math.pow(2, retryCount);
                await this.sleep(delay);
                return this.loadWithRetry(modulePath, retryCount + 1);
            }
            
            // 尝试使用fallback
            const fallbackPath = this.fallbacks[modulePath];
            if (fallbackPath && fallbackPath !== modulePath) {
                console.log(`Trying fallback for ${modulePath}: ${fallbackPath}`);
                return this.loadWithRetry(fallbackPath);
            }
            
            throw new Error(`Failed to load module ${modulePath} after ${this.maxRetries} retries`);
        }
    }
    
    async loadWithFallback(primaryPath, fallbackPath) {
        try {
            return await import(primaryPath);
        } catch (error) {
            console.warn(`Primary module ${primaryPath} failed, using fallback:`, error);
            return import(fallbackPath);
        }
    }
    
    sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
}

// 使用示例
const loader = new RobustModuleLoader({
    maxRetries: 3,
    retryDelay: 1000,
    fallbacks: {
        './advanced-charts.js': './basic-charts.js',
        './high-res-images.js': './low-res-images.js'
    }
});

try {
    const chartsModule = await loader.loadWithRetry('./advanced-charts.js');
    console.log('Advanced charts loaded successfully');
} catch (error) {
    console.error('All chart loading attempts failed:', error);
}

性能考虑

1. 模块大小优化

// module-optimization.js

// 分割大模块为小块
// 代替一个大的utils模块:
// import * as utils from './large-utils.js';

// 使用按需导入:
const stringUtils = () => import('./utils/string.js');
const arrayUtils = () => import('./utils/array.js');
const dateUtils = () => import('./utils/date.js');

// 只在需要时加载具体功能
async function formatUserData(userData) {
    const { format } = await import('./utils/string.js');
    const { sortBy } = await import('./utils/array.js');
    const { formatDate } = await import('./utils/date.js');
    
    return {
        name: format(userData.name),
        dates: userData.dates.map(formatDate),
        sorted: sortBy(userData.items, 'priority')
    };
}

2. 缓存策略

// caching.js

class ModuleCache {
    constructor(options = {}) {
        this.cache = new Map();
        this.maxAge = options.maxAge || 300000; // 5分钟
        this.maxSize = options.maxSize || 50;
    }
    
    async loadModule(modulePath) {
        const cacheKey = modulePath;
        const cached = this.cache.get(cacheKey);
        
        if (cached && this.isValid(cached)) {
            return cached.module;
        }
        
        const module = await import(modulePath);
        
        this.cache.set(cacheKey, {
            module,
            timestamp: Date.now()
        });
        
        this.cleanup();
        return module;
    }
    
    isValid(cached) {
        return Date.now() - cached.timestamp < this.maxAge;
    }
    
    cleanup() {
        if (this.cache.size <= this.maxSize) {
            return;
        }
        
        // 移除最旧的条目
        const entries = Array.from(this.cache.entries());
        entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
        
        const toRemove = entries.slice(0, this.cache.size - this.maxSize);
        toRemove.forEach(([key]) => this.cache.delete(key));
    }
    
    clear() {
        this.cache.clear();
    }
}

const moduleCache = new ModuleCache({ maxAge: 600000, maxSize: 100 });

动态导入的局限性

1. 静态分析限制

// limitations.js

// ❌ 这些用法会使打包工具无法进行静态分析
const moduleName = getUserPreference();
import(moduleName); // 完全动态的路径

const modules = ['a', 'b', 'c'];
modules.forEach(name => import(name)); // 循环中的动态导入

// ✅ 更好的做法:使用部分静态路径
const theme = getUserTheme();
import(`./themes/${theme}.js`); // 部分静态路径

// ✅ 或使用显式的模块映射
const moduleMap = {
    theme1: () => import('./themes/theme1.js'),
    theme2: () => import('./themes/theme2.js'),
    theme3: () => import('./themes/theme3.js')
};

const themeLoader = moduleMap[theme];
if (themeLoader) {
    themeLoader().then(module => {
        // 使用主题模块
    });
}

2. 错误处理的重要性

// error-handling-best-practices.js

// ❌ 没有错误处理
import('./module.js').then(module => {
    module.default(); // 如果模块加载失败,这里会出错
});

// ✅ 完善的错误处理
async function loadAndUseModule() {
    try {
        const module = await import('./module.js');
        
        if (!module || !module.default) {
            throw new Error('Module does not have expected exports');
        }
        
        return module.default();
    } catch (error) {
        console.error('Module loading or execution failed:', error);
        
        // 提供fallback逻辑
        return useDefaultBehavior();
    }
}

function useDefaultBehavior() {
    // 默认行为实现
    console.log('Using default behavior due to module loading failure');
}

总结

动态导入为JavaScript模块系统带来了强大的运行时灵活性:

  • 按需加载: 减少初始包大小,提升应用启动速度
  • 条件加载: 根据用户环境、权限等条件加载不同模块
  • 代码分割: 自动将代码分割为更小的块
  • 插件系统: 支持运行时扩展应用功能
  • 懒加载: 延迟加载非关键功能
  • 国际化: 动态加载语言包和地区化内容

动态导入是现代Web应用性能优化和架构设计的重要工具,合理使用能够显著提升用户体验和应用的可维护性。


下一章: 模块解析机制

模块解析机制

模块解析(Module Resolution)是ES模块系统中的核心机制,决定了当你写下import './module.js'时,JavaScript引擎如何找到并加载正确的模块文件。本章将深入探讨各种解析规则、配置方式和最佳实践。

ES模块执行的四个阶段

ES模块系统的执行过程严格按照以下四个阶段进行,这是ES模块规范的核心:

1. 解析阶段(Parse Phase)

目标: 静态分析模块代码,识别所有的导入和导出声明

// 在这个阶段,引擎会分析:
import { utils, config } from './utils.js';     // ← 导入声明
import defaultExport from './helper.js';        // ← 默认导入
import * as api from './api.js';               // ← 命名空间导入

export const myVar = 'value';                  // ← 导出声明
export { helperFunc };                         // ← 重新导出
export default class MyClass {};               // ← 默认导出

// 在解析阶段,以下代码不会执行:
console.log('这行代码在解析阶段不会执行');

关键特征

  • 静态分析,不执行代码
  • 构建模块依赖图
  • 识别所有import/export声明
  • 验证语法正确性

2. 加载阶段(Load Phase)

目标: 根据模块标识符获取所有依赖模块的源代码

// 假设模块依赖关系:
// main.js → utils.js → config.js
// main.js → api.js → shared.js

// 加载阶段会递归获取所有模块的源码:
/*
加载顺序(深度优先):
1. 开始加载 main.js
2. 发现依赖 utils.js,开始加载
3. 发现 utils.js 依赖 config.js,开始加载
4. config.js 无依赖,加载完成
5. 回到 utils.js,加载完成
6. 发现依赖 api.js,开始加载
7. 发现 api.js 依赖 shared.js,开始加载
8. shared.js 无依赖,加载完成
9. 回到 api.js,加载完成
10. 回到 main.js,加载完成
*/

关键特征

  • 递归加载所有依赖
  • 网络请求或文件系统读取
  • 深度优先遍历依赖图
  • 处理循环依赖检测

目标: 创建模块记录,建立导入导出绑定,验证模块完整性

// utils.js
export let count = 0;
export function increment() { count++; }

// main.js  
import { count, increment } from './utils.js';

// 在链接阶段:
// 1. 为每个模块创建模块记录(Module Record)
// 2. 验证 main.js 中的 { count, increment } 在 utils.js 中确实存在
// 3. 建立实时绑定(Live Binding)
// 4. 检查是否有未解析的导入

链接过程详细步骤

// 1. 创建模块环境记录
ModuleRecord {
  environment: ModuleEnvironmentRecord,
  namespace: ModuleNamespace,
  status: 'linking'
}

// 2. 验证导入导出匹配
main.js imports: ['count', 'increment']
utils.js exports: ['count', 'increment'] ✓

// 3. 建立绑定关系
main.count ──→ utils.count (live binding)
main.increment ──→ utils.increment (live binding)

关键特征

  • 创建模块环境记录
  • 验证导入导出一致性
  • 建立实时绑定关系
  • 检测未解析的引用

4. 求值阶段(Evaluate Phase)

目标: 按拓扑顺序执行模块代码,初始化导出值

// 执行顺序遵循依赖关系,无依赖的模块先执行

// config.js (无依赖,最先执行)
console.log('1. config.js 执行');
export const API_URL = 'https://api.example.com';

// utils.js (依赖 config.js)
import { API_URL } from './config.js';
console.log('2. utils.js 执行');
export const client = createClient(API_URL);

// main.js (依赖 utils.js)
import { client } from './utils.js';
console.log('3. main.js 执行');
client.connect();

求值特征

// 拓扑排序确定执行顺序
依赖图: main.js → utils.js → config.js
执行顺序: config.js → utils.js → main.js

// 循环依赖处理
// 如果 A ←→ B 循环依赖,按照遇到的顺序执行
// 未初始化的绑定在求值完成前可能是 undefined

关键特征

  • 按拓扑顺序执行代码
  • 初始化导出值
  • 处理循环依赖
  • 建立实时绑定关系

阶段间的关系

graph TD
    A[Parse Phase<br/>静态分析] --> B[Load Phase<br/>获取源码]
    B --> C[Link Phase<br/>建立绑定]
    C --> D[Evaluate Phase<br/>执行代码]
    
    A --> A1[构建依赖图]
    B --> B1[递归加载依赖]
    C --> C1[验证导入导出]
    D --> D1[按顺序执行]

实际示例演示

// 创建测试文件来观察各个阶段
// main.js
console.log('=== main.js 开始执行 ===');
import { helper } from './helper.js';
console.log('helper imported:', helper);
export const mainValue = 'from-main';

// helper.js  
console.log('=== helper.js 开始执行 ===');
import { config } from './config.js';
console.log('config imported:', config);
export const helper = 'helper-value';

// config.js
console.log('=== config.js 开始执行 ===');
export const config = 'config-value';

// 执行结果将显示求值阶段的顺序:
// === config.js 开始执行 ===
// === helper.js 开始执行 ===  
// config imported: config-value
// === main.js 开始执行 ===
// helper imported: helper-value

这四个阶段的严格执行保证了ES模块的静态分析能力、依赖管理和循环依赖处理的可靠性。

与ECMAScript规范的对应关系

上述四阶段模型完全符合ECMAScript规范(ES2015+ 15.2.1节)中定义的模块处理流程:

规范中的核心操作

  1. ParseModule(sourceText)解析阶段

    • 解析源码为抽象语法树
    • 提取导入导出声明
    • 创建模块记录(Source Text Module Record)
  2. HostResolveImportedModule()加载阶段

    • 解析模块标识符
    • 递归加载依赖模块
    • 实现定义的具体加载机制
  3. ModuleDeclarationInstantiation()链接阶段

    • 创建模块环境记录
    • 建立导入导出绑定
    • 验证所有依赖的可解析性
  4. ModuleEvaluation()求值阶段

    • 按依赖顺序递归求值
    • 执行模块代码
    • 初始化绑定值

规范保证的特性

// ECMAScript规范确保的行为特征:

// 1. 幂等性 - 同一模块多次加载返回相同实例
const mod1 = await import('./module.js');
const mod2 = await import('./module.js');
console.log(mod1 === mod2); // true

// 2. 循环依赖检测 - ResolveExport算法防止无限递归
// 规范中的伪代码:
// If module and r.[[module]] are the same Module Record 
// and SameValue(exportName, r.[[exportName]]) is true, then
//   Assert: this is a circular import request.
//   Return null.

// 3. 静态结构 - 所有绑定在链接阶段确定
// 动态导入在运行时进行,但仍遵循四阶段流程

这种规范化的处理流程是ES模块相对于其他模块系统的核心优势。

三类模块标识符

1. 相对路径解析

// 文件结构:
// src/
//   ├── components/
//   │   ├── Button.js
//   │   ├── Input.js
//   │   └── shared/
//   │       └── utils.js
//   ├── pages/
//   │   ├── Home.js
//   │   └── About.js
//   └── app.js

// 在 src/app.js 中
import { Button } from './components/Button.js';        // 相对路径:同级子目录
import { Home } from './pages/Home.js';               // 相对路径:同级子目录

// 在 src/components/Button.js 中
import { Input } from './Input.js';                   // 相对路径:同级文件
import { utils } from './shared/utils.js';           // 相对路径:子目录
import { config } from '../config.js';               // 相对路径:父目录

// 在 src/components/shared/utils.js 中
import { Button } from '../Button.js';               // 相对路径:父目录中的文件
import { Home } from '../../pages/Home.js';          // 相对路径:祖父目录

2. 绝对路径和包导入

// 绝对路径(从项目根目录开始)
import { utils } from '/src/utils/helpers.js';

// Node.js 内置模块
import { readFile } from 'fs/promises';
import { join } from 'path';
import { EventEmitter } from 'events';

// npm 包导入
import React from 'react';                    // 包的主入口
import { useState } from 'react';             // 包的命名导出
import lodash from 'lodash';                  // 整个包
import { debounce } from 'lodash';            // 包的部分导入

// 作用域包(Scoped Packages)
import { parse } from '@babel/parser';
import { transform } from '@babel/core';
import { Button } from '@company/ui-components';

// 子路径导入
import { format } from 'date-fns/format';     // 包的子模块
import { isValid } from 'date-fns/isValid';

3. 裸模块标识符 (Bare Specifiers)

裸模块标识符是不以 ./..// 开头的模块标识符,主要用于导入 npm 包或内置模块:

// 内置模块
import { readFile } from 'fs/promises';
import { join } from 'path';
import { EventEmitter } from 'events';

// npm 包导入
import React from 'react';                    // 包的默认导出
import { useState, useEffect } from 'react';  // 包的命名导出
import lodash from 'lodash';                  // 整个包
import { debounce } from 'lodash/debounce';   // 包的子模块

// 作用域包 (Scoped Packages)
import { parse } from '@babel/parser';
import { transform } from '@babel/core';
import { Button } from '@company/ui-components';

// 子路径导入
import { format } from 'date-fns/format';     // 包的子模块
import { isValid } from 'date-fns/isValid';
import utils from 'my-package/utils';         // 自定义子路径

裸模块标识符的解析特点

// 1. 优先级顺序
// Node.js 环境中的解析顺序:
// 1) 核心模块(如 'fs', 'path', 'http')
// 2) node_modules 中的包
// 3) 全局安装的包(较少使用)

// 2. 包入口解析
// 按以下顺序查找包的入口:
// - package.json 中的 "exports" 字段(现代方式)
// - package.json 中的 "module" 字段(ES模块入口)
// - package.json 中的 "main" 字段(传统入口)
// - index.js 文件(默认约定)

// 3. 子路径解析
import { helper } from 'my-package/utils';    // 解析为 node_modules/my-package/utils.js
import config from 'my-package/config.json';  // 解析为 node_modules/my-package/config.json

// 4. 作用域包解析
import { component } from '@company/ui';      // 解析为 node_modules/@company/ui/

不同环境中的裸模块标识符

// Node.js 环境
import fs from 'fs';                    // ✅ 内置模块
import express from 'express';          // ✅ npm包

// 浏览器环境(原生)
import fs from 'fs';                    // ❌ 浏览器不支持
import express from 'express';          // ❌ 需要Import Maps或构建工具

// 浏览器环境(使用Import Maps)
// <script type="importmap">
// {
//   "imports": {
//     "lodash": "https://cdn.skypack.dev/lodash",
//     "react": "https://esm.sh/react@18"
//   }
// }
// </script>
import lodash from 'lodash';            // ✅ 通过Import Maps解析
import React from 'react';             // ✅ 通过Import Maps解析

// 构建工具环境(Webpack/Vite等)
import lodash from 'lodash';            // ✅ 构建时解析
import utils from '@/utils';           // ✅ 通过别名配置

Node.js 中的模块解析

1. 解析算法

Node.js 使用复杂的解析算法来查找模块:

// Node.js 模块解析步骤示例
// 当导入 'my-package' 时

// 1. 检查内置模块
import { readFile } from 'fs/promises';  // fs 是内置模块,直接返回

// 2. 如果不是内置模块,查找 node_modules
// 按以下顺序查找:
// ./node_modules/my-package/
// ../node_modules/my-package/
// ../../node_modules/my-package/
// ... 一直到文件系统根目录

// 3. 在包目录中解析主入口
// 按优先级查找:
// - package.json 中的 "exports" 字段
// - package.json 中的 "main" 字段
// - index.js
// - index.json
// - index.node

2. package.json 的 exports 字段

{
  "name": "my-library",
  "exports": {
    ".": {
      "import": "./dist/esm/index.js",
      "require": "./dist/cjs/index.js",
      "types": "./dist/types/index.d.ts"
    },
    "./utils": {
      "import": "./dist/esm/utils.js",
      "require": "./dist/cjs/utils.js"
    },
    "./components/*": {
      "import": "./dist/esm/components/*.js",
      "require": "./dist/cjs/components/*.js"
    },
    "./package.json": "./package.json"
  }
}
// 使用上述配置的导入示例
import MyLibrary from 'my-library';           // 解析到 ./dist/esm/index.js
import { helper } from 'my-library/utils';    // 解析到 ./dist/esm/utils.js
import { Button } from 'my-library/components/Button';  // 解析到 ./dist/esm/components/Button.js

3. 条件导出

{
  "exports": {
    ".": {
      "node": "./dist/node/index.js",
      "browser": "./dist/browser/index.js",
      "import": "./dist/esm/index.js",
      "require": "./dist/cjs/index.js",
      "development": "./src/index.js",
      "production": "./dist/prod/index.js",
      "default": "./dist/esm/index.js"
    }
  }
}

浏览器中的模块解析

1. 基本规则

<!DOCTYPE html>
<html>
<head>
    <title>Browser Module Resolution</title>
</head>
<body>
    <script type="module">
        // 相对路径必须明确指定扩展名
        import { utils } from './utils.js';           // ✅ 正确
        // import { utils } from './utils';           // ❌ 在浏览器中会失败
        
        // 绝对路径
        import { config } from '/js/config.js';       // ✅ 从网站根目录
        
        // 完整URL
        import { library } from 'https://cdn.skypack.dev/lodash';  // ✅ CDN导入
    </script>
</body>
</html>

2. Import Maps

Import Maps 允许在浏览器中配置模块解析:

<script type="importmap">
{
  "imports": {
    "react": "https://esm.sh/react@18",
    "react-dom": "https://esm.sh/react-dom@18",
    "lodash": "https://cdn.skypack.dev/lodash",
    "lodash/": "https://cdn.skypack.dev/lodash/",
    "@company/": "/js/packages/company/",
    "utils/": "/js/utils/"
  },
  "scopes": {
    "/js/legacy/": {
      "react": "https://esm.sh/react@17"
    }
  }
}
</script>

<script type="module">
    // 现在可以使用裸模块说明符
    import React from 'react';                    // 解析到 https://esm.sh/react@18
    import { debounce } from 'lodash';           // 解析到 https://cdn.skypack.dev/lodash
    import { merge } from 'lodash/merge';        // 解析到 https://cdn.skypack.dev/lodash/merge
    import { Button } from '@company/ui';        // 解析到 /js/packages/company/ui
    import { helper } from 'utils/helper.js';    // 解析到 /js/utils/helper.js
</script>

3. 动态 Import Maps

// dynamic-import-maps.js

function createImportMap(dependencies) {
    const importMap = {
        imports: {}
    };
    
    Object.entries(dependencies).forEach(([name, url]) => {
        importMap.imports[name] = url;
    });
    
    const script = document.createElement('script');
    script.type = 'importmap';
    script.textContent = JSON.stringify(importMap);
    document.head.appendChild(script);
}

// 根据环境动态配置
const isDevelopment = location.hostname === 'localhost';
const dependencies = isDevelopment ? {
    'react': '/node_modules/react/index.js',
    'lodash': '/node_modules/lodash/lodash.js'
} : {
    'react': 'https://cdn.skypack.dev/react',
    'lodash': 'https://cdn.skypack.dev/lodash'
};

createImportMap(dependencies);

动态 Import Maps 的重要限制

// 关键原则:Import Maps 只影响未来的导入,不影响已加载的模块

// 1. 模块缓存机制
console.log('=== 演示模块缓存和 Import Maps 的交互 ===');

// 首先加载一个模块
import('https://cdn.skypack.dev/lodash').then(lodash1 => {
    console.log('第一次加载 lodash:', lodash1.default.VERSION);
    
    // 然后添加 Import Map(对已加载的模块无效)
    const importMap = {
        imports: {
            'lodash': 'https://esm.sh/lodash@4.17.20'  // 不同的URL
        }
    };
    
    const script = document.createElement('script');
    script.type = 'importmap';
    script.textContent = JSON.stringify(importMap);
    document.head.appendChild(script);
    
    // 再次使用相同URL导入 - 返回缓存的模块
    import('https://cdn.skypack.dev/lodash').then(lodash2 => {
        console.log('相同URL再次导入:', lodash2.default.VERSION);
        console.log('是同一个对象:', lodash1 === lodash2); // true
    });
    
    // 使用裸模块标识符导入 - 使用新的 Import Map
    import('lodash').then(lodash3 => {
        console.log('通过Import Map导入:', lodash3.default.VERSION);
        console.log('与第一次不同:', lodash1 !== lodash3); // 可能是true
    });
});

// 2. 安全的动态配置模式
class SafeImportMapManager {
    constructor() {
        this.loadedModules = new Set();
        this.importMapInstalled = false;
    }
    
    // 检查是否可以安全添加 Import Map
    canAddImportMap() {
        // Import Maps 必须在任何模块导入之前定义
        return !this.importMapInstalled && this.loadedModules.size === 0;
    }
    
    // 安全添加 Import Map
    addImportMap(dependencies) {
        if (!this.canAddImportMap()) {
            console.warn('Cannot add Import Map: modules already loaded or Import Map already exists');
            return false;
        }
        
        const importMap = { imports: dependencies };
        const script = document.createElement('script');
        script.type = 'importmap';
        script.textContent = JSON.stringify(importMap);
        document.head.appendChild(script);
        
        this.importMapInstalled = true;
        return true;
    }
    
    // 跟踪模块加载
    async importModule(specifier) {
        const module = await import(specifier);
        this.loadedModules.add(specifier);
        return module;
    }
}

// 使用示例
const importManager = new SafeImportMapManager();

// 在应用启动时配置
if (importManager.canAddImportMap()) {
    importManager.addImportMap({
        'react': 'https://esm.sh/react@18',
        'lodash': 'https://cdn.skypack.dev/lodash'
    });
}

// 3. 模块热更新的替代方案
class ModuleVersionManager {
    constructor() {
        this.moduleCache = new Map();
        this.versionCounter = 0;
    }
    
    // 通过版本化URL绕过模块缓存
    async loadFreshModule(baseUrl) {
        this.versionCounter++;
        const versionedUrl = `${baseUrl}?v=${this.versionCounter}&t=${Date.now()}`;
        
        try {
            const module = await import(versionedUrl);
            this.moduleCache.set(baseUrl, { module, url: versionedUrl, timestamp: Date.now() });
            return module;
        } catch (error) {
            console.error(`Failed to load fresh module from ${baseUrl}:`, error);
            throw error;
        }
    }
    
    // 获取缓存的模块信息
    getCachedModule(baseUrl) {
        return this.moduleCache.get(baseUrl);
    }
    
    // 清理过期缓存
    clearExpiredCache(maxAge = 300000) { // 5分钟
        const now = Date.now();
        for (const [url, info] of this.moduleCache.entries()) {
            if (now - info.timestamp > maxAge) {
                this.moduleCache.delete(url);
            }
        }
    }
}

// 4. 实际应用场景
// 场景1:开发环境 vs 生产环境
if (typeof window !== 'undefined') {
    const isDev = window.location.hostname === 'localhost';
    
    // 只在页面加载最开始配置Import Map
    if (!document.querySelector('script[type="importmap"]')) {
        const dependencies = isDev ? {
            'react': '/node_modules/react/index.js',
            'react-dom': '/node_modules/react-dom/index.js'
        } : {
            'react': 'https://esm.sh/react@18.2.0',
            'react-dom': 'https://esm.sh/react-dom@18.2.0'
        };
        
        const script = document.createElement('script');
        script.type = 'importmap';
        script.textContent = JSON.stringify({ imports: dependencies });
        document.head.appendChild(script);
    }
}

核心要点

  1. 模块缓存优先级最高: 已经通过特定URL加载的模块会被永久缓存,Import Maps无法改变这些模块的解析结果

  2. Import Maps只影响新的解析: 只有尚未解析过的模块标识符才会应用Import Maps规则

  3. 时机关键: Import Maps必须在任何使用相关模块标识符的import语句执行之前定义

  4. 绕过缓存的方法:

    • 使用版本化URL (module.js?v=1.0.1)
    • 添加时间戳 (module.js?t=${Date.now()})
    • 使用动态import的module reload技术

浏览器 Module Map API

简短回答:浏览器目前没有提供直接访问 Module Map 的标准API。

// ❌ 不存在的API
// console.log(window.moduleMap);           // undefined
// console.log(document.moduleCache);       // undefined
// console.log(navigator.loadedModules);    // undefined

// ❌ 不存在的方法
// window.clearModuleCache();               // TypeError
// document.reloadModule('lodash');         // TypeError

// Module Map 是浏览器内部的实现细节,不暴露给开发者

Module Map 的内部机制

// 浏览器内部类似这样的结构(简化示例,实际更复杂)
/*
InternalModuleMap = {
  'https://cdn.skypack.dev/lodash': {
    status: 'evaluated',
    module: ModuleRecord { ... },
    namespace: { default: lodash, ... },
    timestamp: 1640995200000
  },
  'https://esm.sh/react@18': {
    status: 'evaluated', 
    module: ModuleRecord { ... },
    namespace: { default: React, ... },
    timestamp: 1640995201000
  }
}
*/

// 开发者无法直接访问这个内部结构

间接观察 Module Map 的方法

// 1. 通过性能和行为推断
class ModuleMapObserver {
    constructor() {
        this.loadTimes = new Map();
        this.loadedModules = new Set();
    }
    
    async observeModuleLoad(specifier) {
        const startTime = performance.now();
        
        try {
            const module = await import(specifier);
            const endTime = performance.now();
            const loadTime = endTime - startTime;
            
            // 第一次加载通常较慢(网络请求)
            // 后续加载很快(缓存命中)
            if (loadTime < 1) { // 小于1ms通常是缓存命中
                console.log(`📦 ${specifier} - 缓存命中 (${loadTime.toFixed(2)}ms)`);
                this.loadedModules.add(specifier);
            } else {
                console.log(`🌐 ${specifier} - 网络加载 (${loadTime.toFixed(2)}ms)`);
            }
            
            this.loadTimes.set(specifier, loadTime);
            return module;
        } catch (error) {
            console.error(`❌ ${specifier} - 加载失败:`, error);
            throw error;
        }
    }
    
    // 检查模块是否可能已缓存
    isProbablyCached(specifier) {
        const loadTime = this.loadTimes.get(specifier);
        return loadTime !== undefined && loadTime < 1;
    }
    
    // 获取加载统计
    getLoadStats() {
        const cached = Array.from(this.loadTimes.entries())
            .filter(([_, time]) => time < 1).length;
        const total = this.loadTimes.size;
        
        return {
            total,
            cached,
            networkLoaded: total - cached,
            cacheHitRate: total > 0 ? (cached / total * 100).toFixed(2) + '%' : '0%'
        };
    }
}

// 使用示例
const observer = new ModuleMapObserver();

// 第一次加载
await observer.observeModuleLoad('https://cdn.skypack.dev/lodash');
// 输出: 🌐 https://cdn.skypack.dev/lodash - 网络加载 (245.67ms)

// 第二次加载
await observer.observeModuleLoad('https://cdn.skypack.dev/lodash'); 
// 输出: 📦 https://cdn.skypack.dev/lodash - 缓存命中 (0.23ms)

console.log(observer.getLoadStats());
// 输出: { total: 2, cached: 1, networkLoaded: 1, cacheHitRate: '50.00%' }

Module Map 调试技巧

// 2. Chrome DevTools 中的模块调试
// 在 Chrome DevTools 中可以通过以下方式观察模块:

// Sources → Page → (no domain) → 查看已加载的模块
// Network → 过滤 "JS" 查看模块网络请求
// Application → Frames → 查看模块依赖图

// 3. 自定义模块加载跟踪
class ModuleLoadTracker {
    constructor() {
        this.modules = new Map();
        this.originalImport = window.eval('import'); // 保存原始import
        this.setupInterception();
    }
    
    setupInterception() {
        // 注意:这种方法在实际中不可行,因为import是语法关键字
        // 这里只是演示概念
        
        // 实际中可以通过重写动态import
        const originalDynamicImport = window.__dynamicImportHandler__;
        if (originalDynamicImport) {
            window.__dynamicImportHandler__ = async (specifier) => {
                console.log(`🔍 尝试导入: ${specifier}`);
                const result = await originalDynamicImport(specifier);
                this.modules.set(specifier, {
                    timestamp: Date.now(),
                    exports: Object.keys(result)
                });
                return result;
            };
        }
    }
    
    getLoadedModules() {
        return Array.from(this.modules.keys());
    }
    
    getModuleInfo(specifier) {
        return this.modules.get(specifier);
    }
}

// 4. 使用 Performance Observer 监控模块加载
if ('PerformanceObserver' in window) {
    const moduleObserver = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
            if (entry.entryType === 'navigation' || entry.entryType === 'resource') {
                if (entry.name.includes('.js') || entry.name.includes('.mjs')) {
                    console.log(`📊 模块资源: ${entry.name}`);
                    console.log(`   - 开始时间: ${entry.startTime}ms`);
                    console.log(`   - 持续时间: ${entry.duration}ms`);
                    console.log(`   - 传输大小: ${entry.transferSize} bytes`);
                }
            }
        }
    });
    
    moduleObserver.observe({ 
        entryTypes: ['navigation', 'resource'] 
    });
}

为什么浏览器不暴露 Module Map API?

// 1. 安全考虑
// 暴露Module Map可能导致安全问题:
// - 恶意脚本可能清除关键模块
// - 可能绕过同源策略检查
// - 敏感信息泄露

// 2. 性能考虑  
// - Module Map操作可能很昂贵
// - 暴露内部结构可能影响引擎优化
// - 避免开发者意外破坏模块系统

// 3. 标准化复杂性
// - 不同浏览器实现差异
// - API设计的复杂性
// - 向后兼容性问题

// 4. 替代方案存在
// 开发者可以通过其他方式实现类似功能:
class UserLandModuleRegistry {
    constructor() {
        this.registry = new Map();
        this.importWrapper = this.createImportWrapper();
    }
    
    createImportWrapper() {
        return async (specifier) => {
            if (this.registry.has(specifier)) {
                console.log(`📦 从用户注册表获取: ${specifier}`);
                return this.registry.get(specifier);
            }
            
            console.log(`🌐 动态导入: ${specifier}`);
            const module = await import(specifier);
            this.registry.set(specifier, module);
            return module;
        };
    }
    
    // 用户可控的模块管理
    register(specifier, module) {
        this.registry.set(specifier, module);
    }
    
    unregister(specifier) {
        return this.registry.delete(specifier);
    }
    
    has(specifier) {
        return this.registry.has(specifier);
    }
    
    clear() {
        this.registry.clear();
    }
    
    list() {
        return Array.from(this.registry.keys());
    }
}

// 使用用户层模块注册表
const moduleRegistry = new UserLandModuleRegistry();
const dynamicImport = moduleRegistry.importWrapper;

// 这样可以实现类似Module Map的功能
await dynamicImport('https://cdn.skypack.dev/lodash');
console.log('已注册模块:', moduleRegistry.list());

未来可能的发展

// 虽然目前没有标准API,但可能的未来方向:

// 1. Module Reflection API (提案阶段)
// if ('moduleReflection' in window) {
//     const loadedModules = window.moduleReflection.getLoadedModules();
//     const moduleInfo = window.moduleReflection.getModuleInfo(specifier);
//     const canClear = window.moduleReflection.canClearModule(specifier);
// }

// 2. Performance API 扩展
// if ('getEntriesByType' in performance) {
//     const moduleEntries = performance.getEntriesByType('module');
//     // 获取模块性能指标
// }

// 3. 开发者工具集成
// 更好的DevTools集成,提供模块依赖可视化

总结

  • 没有直接API: 浏览器不提供访问Module Map的标准API
  • 🔍 间接观察: 可通过性能监控、加载时间等方式推断
  • 🛠️ 替代方案: 开发者可以构建用户层的模块管理系统
  • 🔒 安全设计: 这是有意的设计选择,出于安全和性能考虑
  • 🚀 未来发展: 可能会有专门的Module Reflection API

构建工具中的模块解析

1. Webpack 解析配置

// webpack.config.js
module.exports = {
    resolve: {
        // 模块查找目录
        modules: ['node_modules', 'src'],
        
        // 文件扩展名
        extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
        
        // 别名配置
        alias: {
            '@': path.resolve(__dirname, 'src'),
            '@components': path.resolve(__dirname, 'src/components'),
            '@utils': path.resolve(__dirname, 'src/utils'),
            '@assets': path.resolve(__dirname, 'src/assets')
        },
        
        // 主字段
        mainFields: ['browser', 'module', 'main'],
        
        // 主文件名
        mainFiles: ['index', 'main'],
        
        // 条件导出
        conditionNames: ['import', 'module', 'browser', 'default']
    }
};

// 使用别名的导入示例
// 代替: import { Button } from '../../../components/Button.js'
import { Button } from '@components/Button.js';

// 代替: import { helper } from '../../../utils/helper.js'
import { helper } from '@utils/helper.js';

2. Vite 解析配置

// vite.config.js
import { defineConfig } from 'vite';
import path from 'path';

export default defineConfig({
    resolve: {
        alias: {
            '@': path.resolve(__dirname, 'src'),
            '@components': path.resolve(__dirname, 'src/components'),
            '@utils': path.resolve(__dirname, 'src/utils')
        },
        extensions: ['.js', '.ts', '.jsx', '.tsx', '.json'],
        conditions: ['import', 'module', 'browser', 'default'],
        mainFields: ['module', 'jsnext:main', 'jsnext']
    }
});

3. TypeScript 解析配置

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"],
      "@utils/*": ["src/utils/*"],
      "@types/*": ["src/types/*"]
    },
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "resolveJsonModule": true
  }
}

模块解析的高级用法

1. 条件解析

// platform-specific.js

// 根据平台加载不同的实现
const platform = process.platform;

let fileSystem;
switch (platform) {
    case 'win32':
        fileSystem = await import('./fs/windows.js');
        break;
    case 'darwin':
        fileSystem = await import('./fs/macos.js');
        break;
    case 'linux':
        fileSystem = await import('./fs/linux.js');
        break;
    default:
        fileSystem = await import('./fs/generic.js');
}

export default fileSystem.default;

2. 版本化模块

// versioned-modules.js

class ModuleVersionManager {
    constructor() {
        this.versions = new Map();
    }
    
    async loadVersion(moduleName, version) {
        const versionKey = `${moduleName}@${version}`;
        
        if (this.versions.has(versionKey)) {
            return this.versions.get(versionKey);
        }
        
        try {
            // 尝试加载指定版本
            const module = await import(`./modules/${moduleName}/v${version}/index.js`);
            this.versions.set(versionKey, module);
            return module;
        } catch (error) {
            console.warn(`Failed to load ${versionKey}, trying latest`);
            return this.loadLatest(moduleName);
        }
    }
    
    async loadLatest(moduleName) {
        const latestKey = `${moduleName}@latest`;
        
        if (this.versions.has(latestKey)) {
            return this.versions.get(latestKey);
        }
        
        const module = await import(`./modules/${moduleName}/latest/index.js`);
        this.versions.set(latestKey, module);
        return module;
    }
    
    async loadCompatible(moduleName, semverRange) {
        // 简化的语义版本兼容性检查
        const availableVersions = await this.getAvailableVersions(moduleName);
        const compatibleVersion = this.findCompatibleVersion(availableVersions, semverRange);
        
        if (compatibleVersion) {
            return this.loadVersion(moduleName, compatibleVersion);
        }
        
        throw new Error(`No compatible version found for ${moduleName}@${semverRange}`);
    }
    
    async getAvailableVersions(moduleName) {
        // 实际实现中可能需要查询API或文件系统
        return ['1.0.0', '1.1.0', '1.2.0', '2.0.0'];
    }
    
    findCompatibleVersion(versions, range) {
        // 简化的语义版本匹配逻辑
        return versions.find(version => this.satisfies(version, range));
    }
    
    satisfies(version, range) {
        // 实际实现中应使用专业的semver库
        return version.startsWith(range.replace('^', '').split('.')[0]);
    }
}

// 使用示例
const versionManager = new ModuleVersionManager();

// 加载特定版本
const moduleV1 = await versionManager.loadVersion('my-library', '1.0.0');

// 加载兼容版本
const moduleCompat = await versionManager.loadCompatible('my-library', '^1.0.0');

3. 模块解析中间件

// resolution-middleware.js

class ModuleResolutionMiddleware {
    constructor() {
        this.middlewares = [];
    }
    
    use(middleware) {
        this.middlewares.push(middleware);
    }
    
    async resolve(specifier, context = {}) {
        let result = { specifier, context };
        
        for (const middleware of this.middlewares) {
            result = await middleware(result.specifier, result.context) || result;
        }
        
        return result.specifier;
    }
}

// 中间件示例
const aliasMiddleware = (aliases) => (specifier, context) => {
    for (const [alias, target] of Object.entries(aliases)) {
        if (specifier.startsWith(alias)) {
            return {
                specifier: specifier.replace(alias, target),
                context
            };
        }
    }
};

const environmentMiddleware = (specifier, context) => {
    if (specifier.includes('{{env}}')) {
        return {
            specifier: specifier.replace('{{env}}', process.env.NODE_ENV || 'development'),
            context
        };
    }
};

const loggingMiddleware = (specifier, context) => {
    console.log(`Resolving: ${specifier}`);
    // 不修改specifier,只是记录日志
};

// 使用中间件
const resolver = new ModuleResolutionMiddleware();

resolver.use(aliasMiddleware({
    '@/': './src/',
    '@components/': './src/components/',
    '@utils/': './src/utils/'
}));

resolver.use(environmentMiddleware);
resolver.use(loggingMiddleware);

// 解析模块
const resolvedPath = await resolver.resolve('@components/Button');
// 输出: Resolving: ./src/components/Button

解析性能优化

1. 缓存机制

// resolution-cache.js

class ResolutionCache {
    constructor(options = {}) {
        this.cache = new Map();
        this.maxSize = options.maxSize || 1000;
        this.ttl = options.ttl || 300000; // 5分钟
    }
    
    get(specifier) {
        const entry = this.cache.get(specifier);
        
        if (!entry) {
            return null;
        }
        
        if (Date.now() - entry.timestamp > this.ttl) {
            this.cache.delete(specifier);
            return null;
        }
        
        return entry.resolved;
    }
    
    set(specifier, resolved) {
        if (this.cache.size >= this.maxSize) {
            // 删除最旧的条目
            const oldestKey = this.cache.keys().next().value;
            this.cache.delete(oldestKey);
        }
        
        this.cache.set(specifier, {
            resolved,
            timestamp: Date.now()
        });
    }
    
    clear() {
        this.cache.clear();
    }
    
    size() {
        return this.cache.size;
    }
}

const resolutionCache = new ResolutionCache({ maxSize: 500, ttl: 600000 });

async function resolveWithCache(specifier) {
    // 检查缓存
    const cached = resolutionCache.get(specifier);
    if (cached) {
        return cached;
    }
    
    // 执行实际解析
    const resolved = await actualResolve(specifier);
    
    // 缓存结果
    resolutionCache.set(specifier, resolved);
    
    return resolved;
}

2. 预解析

// pre-resolution.js

class ModulePreResolver {
    constructor() {
        this.preresolved = new Map();
    }
    
    // 预解析常用模块
    async preresolveCommonModules(modules) {
        const promises = modules.map(async (specifier) => {
            try {
                const resolved = await this.resolve(specifier);
                this.preresolved.set(specifier, resolved);
            } catch (error) {
                console.warn(`Failed to preresolve ${specifier}:`, error);
            }
        });
        
        await Promise.all(promises);
    }
    
    async resolve(specifier) {
        // 检查预解析结果
        if (this.preresolved.has(specifier)) {
            return this.preresolved.get(specifier);
        }
        
        // 执行实际解析
        return this.actualResolve(specifier);
    }
    
    async actualResolve(specifier) {
        // 实际的解析逻辑
        return new Promise((resolve) => {
            setTimeout(() => resolve(`resolved:${specifier}`), 10);
        });
    }
}

// 在应用启动时预解析常用模块
const preresolver = new ModulePreResolver();

await preresolver.preresolveCommonModules([
    'react',
    'lodash',
    '@company/ui-components',
    './utils/helpers.js'
]);

常见问题和解决方案

1. 路径解析问题

// 问题:深层嵌套的相对路径
// ❌ 难以维护
import { utils } from '../../../utils/helpers.js';
import { Button } from '../../../../components/Button.js';

// 解决方案1:使用绝对路径(如果工具支持)
import { utils } from '/src/utils/helpers.js';
import { Button } from '/src/components/Button.js';

// 解决方案2:使用路径别名
import { utils } from '@utils/helpers.js';
import { Button } from '@components/Button.js';

// 解决方案3:创建桶文件
// src/index.js
export * from './utils/helpers.js';
export * from './components/Button.js';

// 在其他文件中
import { utils, Button } from '@/index.js';

2. 循环依赖解析

// 检测循环依赖的工具
class CircularDependencyDetector {
    constructor() {
        this.visiting = new Set();
        this.visited = new Set();
        this.dependencies = new Map();
    }
    
    addDependency(from, to) {
        if (!this.dependencies.has(from)) {
            this.dependencies.set(from, new Set());
        }
        this.dependencies.get(from).add(to);
    }
    
    detectCycles() {
        const cycles = [];
        
        for (const module of this.dependencies.keys()) {
            if (!this.visited.has(module)) {
                const cycle = this.dfs(module, []);
                if (cycle) {
                    cycles.push(cycle);
                }
            }
        }
        
        return cycles;
    }
    
    dfs(module, path) {
        if (this.visiting.has(module)) {
            // 找到循环
            const cycleStart = path.indexOf(module);
            return path.slice(cycleStart).concat(module);
        }
        
        if (this.visited.has(module)) {
            return null;
        }
        
        this.visiting.add(module);
        path.push(module);
        
        const deps = this.dependencies.get(module) || new Set();
        for (const dep of deps) {
            const cycle = this.dfs(dep, [...path]);
            if (cycle) {
                return cycle;
            }
        }
        
        this.visiting.delete(module);
        this.visited.add(module);
        
        return null;
    }
}

// 使用示例
const detector = new CircularDependencyDetector();
detector.addDependency('A', 'B');
detector.addDependency('B', 'C');
detector.addDependency('C', 'A'); // 循环依赖

const cycles = detector.detectCycles();
console.log('Detected cycles:', cycles); // [['A', 'B', 'C', 'A']]

总结

模块解析机制是ES模块系统的重要组成部分,理解其工作原理有助于:

  • 正确组织项目结构: 合理的路径设计和别名配置
  • 优化构建配置: 配置适当的解析规则提升构建性能
  • 调试导入问题: 快速定位和解决模块找不到的问题
  • 设计可重用模块: 创建易于导入和使用的模块接口
  • 性能优化: 通过缓存和预解析提升解析性能

掌握模块解析机制,能够帮助你构建更加健壮和高效的模块化应用。


下一章: 热模块重载

热模块重载 (Hot Module Reload)

热模块重载(HMR)是现代前端开发中的重要特性,允许在不刷新整个页面的情况下更新模块。它通过巧妙地绕过浏览器的模块缓存机制来实现实时开发体验。

HMR基础概念

核心价值

HMR解决了传统开发中的几个关键痛点:

// 传统开发流程的问题
// 1. 修改代码 → 2. 保存文件 → 3. 手动刷新浏览器 → 4. 重新填写表单/导航到测试页面
// 结果:开发效率低下,测试状态丢失

// HMR开发流程
// 1. 修改代码 → 2. 保存文件 → 3. 模块自动更新,状态保持
// 结果:即时反馈,开发体验显著提升

浏览器模块缓存机制

// 浏览器模块缓存的工作原理
const moduleCache = new Map();

// 首次导入
import('./utils.js').then(module => {
    moduleCache.set('./utils.js', module); // 缓存模块
});

// 后续导入直接从缓存返回
import('./utils.js').then(module => {
    // 返回缓存的模块,即使文件已改变
});

// HMR的核心:绕过缓存
const versionedURL = `./utils.js?hmr_version=${Date.now()}`;
import(versionedURL).then(newModule => {
    // 强制获取新版本模块
});

HMR的技术实现

版本化URL和WebSocket通信

class HMRManager {
    constructor() {
        this.moduleVersions = new Map();
        this.moduleSubscribers = new Map();
        this.websocket = this.createWebSocket();
    }
    
    // 生成版本化的模块URL
    createVersionedURL(originalURL) {
        const version = this.moduleVersions.get(originalURL) || 0;
        const newVersion = version + 1;
        this.moduleVersions.set(originalURL, newVersion);
        
        // 添加版本参数绕过浏览器缓存
        const url = new URL(originalURL, window.location.origin);
        url.searchParams.set('hmr_version', newVersion.toString());
        url.searchParams.set('timestamp', Date.now().toString());
        
        return url.toString();
    }
    
    // 热重载指定模块
    async hotReload(modulePath) {
        console.log(`🔥 热重载模块: ${modulePath}`);
        
        try {
            const versionedURL = this.createVersionedURL(modulePath);
            const newModule = await import(versionedURL);
            
            // 通知所有订阅者
            const subscribers = this.moduleSubscribers.get(modulePath) || [];
            subscribers.forEach(callback => {
                try {
                    callback(newModule, modulePath);
                } catch (error) {
                    console.error(`HMR callback failed for ${modulePath}:`, error);
                }
            });
            
            return newModule;
        } catch (error) {
            console.error(`热重载失败 ${modulePath}:`, error);
            throw error;
        }
    }
    
    createWebSocket() {
        const ws = new WebSocket('ws://localhost:3000/hmr');
        
        ws.onmessage = (event) => {
            const message = JSON.parse(event.data);
            if (message.type === 'file-changed') {
                this.hotReload(message.path);
            }
        };
        
        ws.onopen = () => console.log('🔗 HMR WebSocket连接已建立');
        ws.onerror = (error) => console.error('❌ HMR WebSocket错误:', error);
        
        return ws;
    }
}

Live Binding问题与Proxy解决方案

Live Binding在HMR中的核心问题

graph TD
    A[Module A<br/>静态导入] --> B[Module B<br/>旧版本]
    C[HMR更新] --> D[Module B'<br/>新版本]
    
    A -.->|❌ 仍然引用| B
    D -.->|✅ 需要重新绑定| A
    
    style B fill:#ff9999
    style D fill:#99ff99
    style A fill:#ffcc99
// 问题:静态导入的live binding在HMR中失效
import { calculateTotal, formatCurrency } from './utils.js';

class ProblematicShoppingCart {
    render() {
        // ❌ 这些函数引用在HMR后仍然指向旧版本!
        const total = calculateTotal(this.items);      // 旧版本
        const formattedTotal = formatCurrency(total);  // 旧版本
    }
}

// HMR更新后,即使utils.js文件内容改变了,
// calculateTotal和formatCurrency仍然是初始导入时的旧版本函数引用

基于Proxy的Live Binding解决方案

// 核心:模块代理类
class ModuleProxy {
    constructor(modulePath) {
        this.modulePath = modulePath;
        this.currentModule = null;
        this.isLoading = false;
        this.loadInitialModule();
    }
    
    async loadInitialModule() {
        this.currentModule = await import(this.modulePath);
    }
    
    // 创建代理对象,动态转发到当前模块
    createProxy() {
        return new Proxy(this, {
            get(target, prop) {
                // 处理特殊属性
                if (prop === Symbol.toPrimitive || prop === 'valueOf' || prop === 'toString') {
                    return () => `[ModuleProxy: ${target.modulePath}]`;
                }
                
                if (target.currentModule && prop in target.currentModule) {
                    const value = target.currentModule[prop];
                    
                    // 如果是函数,绑定正确的this并保持上下文
                    if (typeof value === 'function') {
                        return function(...args) {
                            return value.apply(target.currentModule, args);
                        };
                    }
                    
                    // 返回其他类型的值(变量、对象等)
                    return value;
                }
                
                return undefined;
            },
            
            // 支持 'prop' in proxy 检查
            has(target, prop) {
                return target.currentModule && prop in target.currentModule;
            },
            
            // 支持 Object.keys(proxy) 等操作
            ownKeys(target) {
                return target.currentModule ? Object.keys(target.currentModule) : [];
            },
            
            getOwnPropertyDescriptor(target, prop) {
                if (target.currentModule && prop in target.currentModule) {
                    return Object.getOwnPropertyDescriptor(target.currentModule, prop);
                }
                return undefined;
            }
        });
    }
    
    // HMR更新时调用
    async updateModule() {
        if (this.isLoading) return;
        
        this.isLoading = true;
        try {
            const versionedURL = `${this.modulePath}?hmr_version=${Date.now()}`;
            const newModule = await import(versionedURL);
            
            // 保存旧模块用于对比
            const oldModule = this.currentModule;
            this.currentModule = newModule;
            
            console.log('✅ 模块代理已更新,live binding已恢复');
            
            // 可选:输出变更信息
            this.logChanges(oldModule, newModule);
            
        } catch (error) {
            console.error('❌ 模块代理更新失败:', error);
            throw error;
        } finally {
            this.isLoading = false;
        }
    }
    
    // 对比并输出模块变更
    logChanges(oldModule, newModule) {
        if (!oldModule) return;
        
        const oldKeys = Object.keys(oldModule);
        const newKeys = Object.keys(newModule);
        
        const added = newKeys.filter(key => !oldKeys.includes(key));
        const removed = oldKeys.filter(key => !newKeys.includes(key));
        const changed = oldKeys.filter(key => {
            return newKeys.includes(key) && oldModule[key] !== newModule[key];
        });
        
        if (added.length > 0) console.log('➕ 新增导出:', added);
        if (removed.length > 0) console.log('➖ 移除导出:', removed);
        if (changed.length > 0) console.log('🔄 变更导出:', changed);
    }
}

实际应用示例

// Module B: utils.js (被依赖的模块)
export function calculateTotal(items) {
    return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}

export function formatCurrency(amount) {
    return new Intl.NumberFormat('zh-CN', {
        style: 'currency',
        currency: 'CNY'
    }).format(amount);
}

// HMR处理
if (import.meta.hot) {
    import.meta.hot.accept((newModule) => {
        console.log('🔄 utils.js 已更新');
    });
}
// Module A: index.js - 使用Proxy方案
// 创建模块代理
const utilsProxy = new ModuleProxy('./utils.js');
const utils = utilsProxy.createProxy();

class SmartShoppingCart {
    constructor() {
        this.items = [
            { id: 1, name: '商品A', price: 100, quantity: 2 },
            { id: 2, name: '商品B', price: 50, quantity: 1 }
        ];
        this.render();
    }
    
    render() {
        // ✅ 通过代理访问,始终获取最新版本的函数
        const total = utils.calculateTotal(this.items);
        const formattedTotal = utils.formatCurrency(total);
        
        document.getElementById('cart').innerHTML = `
            <div>购物车总计: ${formattedTotal}</div>
            <div>商品数量: ${this.items.length}</div>
        `;
    }
}

// 全局实例管理
if (!window.__SMART_CART_INSTANCES__) {
    window.__SMART_CART_INSTANCES__ = [];
}

export function createShoppingCart() {
    const cart = new SmartShoppingCart();
    window.__SMART_CART_INSTANCES__.push(cart);
    return cart;
}

// HMR处理 - 使用Proxy方案
if (import.meta.hot) {
    import.meta.hot.accept(['./utils.js'], async () => {
        console.log('📦 utils.js 已更新,更新模块代理...');
        
        // 更新代理指向的模块
        await utilsProxy.updateModule();
        
        // 重新渲染所有实例,现在会使用新版本的函数
        window.__SMART_CART_INSTANCES__.forEach(cart => {
            cart.render();
        });
    });
}

增强版Proxy工厂

// 高级模块代理工厂
class HMRModuleFactory {
    constructor() {
        this.proxies = new Map();
        this.hmrManager = new HMRManager();
    }
    
    // 创建或获取模块代理
    create(modulePath) {
        if (this.proxies.has(modulePath)) {
            return this.proxies.get(modulePath);
        }
        
        const moduleProxy = new ModuleProxy(modulePath);
        const proxy = moduleProxy.createProxy();
        
        // 注册HMR回调
        this.hmrManager.moduleSubscribers.set(modulePath, [
            async () => {
                await moduleProxy.updateModule();
                this.notifyUpdate(modulePath);
            }
        ]);
        
        this.proxies.set(modulePath, { proxy, moduleProxy });
        return proxy;
    }
    
    // 通知模块更新
    notifyUpdate(modulePath) {
        const event = new CustomEvent('hmr-module-updated', {
            detail: { modulePath, timestamp: Date.now() }
        });
        window.dispatchEvent(event);
    }
    
    // 获取所有代理的状态
    getProxyStatus() {
        const status = {};
        for (const [path, { moduleProxy }] of this.proxies) {
            status[path] = {
                isLoaded: !!moduleProxy.currentModule,
                isLoading: moduleProxy.isLoading,
                exports: moduleProxy.currentModule ? Object.keys(moduleProxy.currentModule) : []
            };
        }
        return status;
    }
}

// 全局工厂实例
const moduleFactory = new HMRModuleFactory();

// 便捷的模块导入函数
function hmrImport(modulePath) {
    return moduleFactory.create(modulePath);
}

// 使用示例
const utils = hmrImport('./utils.js');
const config = hmrImport('./config.js');
const helpers = hmrImport('./helpers.js');

// 监听模块更新事件
window.addEventListener('hmr-module-updated', (event) => {
    console.log(`📦 模块 ${event.detail.modulePath} 已通过HMR更新`);
});

HMR的适用场景

技术性变更 (推荐使用HMR)

// ✅ 性能优化 - API保持不变
// 优化前
export function calculateTotal(items) {
    let total = 0;
    for (let i = 0; i < items.length; i++) {
        total += items[i].price * items[i].quantity;
    }
    return total;
}

// 优化后 - 使用更高效的reduce
export function calculateTotal(items) {
    return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}

不适用的场景 (推荐完整刷新)

// ❌ API签名变更
// 变更前
export function calculateTotal(items) {
    return items.reduce((sum, item) => sum + item.price, 0);
}

// 变更后 - 签名改变,返回值类型改变
export function calculateTotal(items, options = {}) {
    const { tax = 0, discount = 0 } = options;
    const subtotal = items.reduce((sum, item) => sum + item.price, 0);
    return {
        subtotal,
        tax: subtotal * tax,
        total: subtotal * (1 + tax - discount)
    };
}

HMR决策管理器

// FIXME replace with markdown table

class HMRDecisionManager {
    shouldUseHMR(changeType, moduleInfo) {
        const reasons = {
            // 技术性变更 - 适合HMR
            'performance_optimization': { useHMR: true, reason: '性能优化,API不变' },
            'bug_fix': { useHMR: true, reason: 'Bug修复,逻辑一致' },
            'code_refactoring': { useHMR: true, reason: '代码重构,接口稳定' },
            'style_update': { useHMR: true, reason: '样式更新,无副作用' },
            
            // 业务变更 - 需要完整刷新
            'api_signature_change': { useHMR: false, reason: 'API变更,避免类型错误' },
            'business_logic_change': { useHMR: false, reason: '业务逻辑变更,确保一致性' },
            'data_structure_change': { useHMR: false, reason: '数据结构变更,避免状态冲突' }
        };
        
        const decision = reasons[changeType] || { 
            useHMR: true, 
            reason: '默认尝试HMR,失败时降级' 
        };
        
        console.log(`📋 变更类型: ${changeType}`);
        console.log(`🎯 决策: ${decision.useHMR ? '使用HMR' : '完整刷新'}`);
        console.log(`💡 原因: ${decision.reason}`);
        
        return decision;
    }
}

总结

热模块重载通过以下核心机制工作:

  1. 🔄 版本化URL: 绕过浏览器模块缓存,确保获取最新模块
  2. 📡 WebSocket通信: 实时文件变更通知,保持客户端与服务器同步
  3. 🎯 Proxy代理: 解决ES模块live binding问题,确保引用始终指向最新版本
  4. 💾 状态保持: 维持应用运行时状态,避免开发时数据丢失

关键优势

  • ⚡ 快速反馈: 代码变更即时可见
  • 🎯 精确更新: 只更新修改的模块
  • 💾 状态保持: 保持应用状态不丢失
  • 🔧 Live Binding: 通过Proxy确保始终访问最新模块

使用建议

  1. Proxy方案: 对于复杂应用,使用基于Proxy的模块代理解决live binding问题
  2. 适用场景: 优先用于技术性变更(性能优化、bug修复、代码重构)
  3. 谨慎使用: 避免在业务逻辑变更和API签名变化时使用HMR
  4. 错误处理: 实现完善的降级策略和错误恢复机制

HMR是现代前端开发不可或缺的工具,而基于Proxy的live binding解决方案使其在复杂应用中也能稳定可靠地工作。


上一章: ← 模块解析机制

下一章: 循环依赖处理

循环依赖处理

循环依赖是模块化开发中常见但复杂的问题。当两个或多个模块相互依赖时,就形成了循环依赖。ES模块系统具有处理循环依赖的能力,但理解其机制并采用正确的设计模式对于构建健壮的应用至关重要。

什么是循环依赖

基本概念

// 简单的循环依赖示例

// moduleA.js
import { functionB } from './moduleB.js';

export function functionA() {
    console.log('Function A called');
    return functionB();
}

// moduleB.js  
import { functionA } from './moduleA.js';  // 循环依赖!

export function functionB() {
    console.log('Function B called');
    return 'Result from B';
}

// main.js
import { functionA } from './moduleA.js';
functionA(); // 这可能会导致问题

循环依赖的类型

// 1. 直接循环依赖(A → B → A)
// a.js
import { b } from './b.js';
export const a = 'a';

// b.js
import { a } from './a.js';
export const b = 'b';

// 2. 间接循环依赖(A → B → C → A)
// a.js
import { b } from './b.js';
export const a = 'a';

// b.js
import { c } from './c.js';
export const b = 'b';

// c.js
import { a } from './a.js';
export const c = 'c';

// 3. 复杂循环依赖(多个模块形成环)
// user.js
import { Order } from './order.js';
import { Product } from './product.js';

// order.js
import { User } from './user.js';
import { Product } from './product.js';

// product.js
import { User } from './user.js';
import { Order } from './order.js';

ES模块中循环依赖的行为

1. 模块加载顺序

// 演示ES模块如何处理循环依赖

// a.js
console.log('a.js start');
import { b } from './b.js';
console.log('a.js - b imported:', b);
export const a = 'value-a';
console.log('a.js end');

// b.js
console.log('b.js start');
import { a } from './a.js';
// 注意:在现代Node.js中,直接访问a会抛出ReferenceError
// console.log('b.js - a imported:', a); // ReferenceError: Cannot access 'a' before initialization

// 使用函数延迟访问来避免TDZ错误
export function getA() {
    return a; // 这里可以安全访问,因为调用时a已经初始化
}

export const b = 'value-b';
console.log('b.js end');

// main.js
import { a } from './a.js';
import { getA } from './b.js';
console.log('main.js - a:', a);
console.log('main.js - getA():', getA());

// 执行结果:
// b.js start
// b.js end
// a.js start
// a.js - b imported: value-b
// a.js end
// main.js - a: value-a
// main.js - getA(): value-a

重要提示:

  • 在现代JavaScript引擎(如Node.js v14+)中,直接访问未初始化的绑定会抛出ReferenceError,这是由于Temporal Dead Zone (TDZ)的保护机制
  • 早期的ES模块实现可能返回undefined,但现代实现更加严格
  • 推荐使用函数延迟访问或重构代码来避免循环依赖问题

执行顺序解析

你可能会好奇:为什么执行顺序是 b.js starta.js startmain.js

这是ES模块深度优先加载策略的结果:

  1. main.js 开始执行,遇到 import { a } from './a.js'
  2. 引擎暂停 main.js,开始加载 a.js
  3. a.js 执行,遇到 import { b } from './b.js'
  4. 引擎暂停 a.js,开始加载 b.js
  5. b.js 执行,遇到 import { a } from './a.js'
  6. 引擎检测到循环依赖(a.js 已在加载中),创建未初始化绑定
  7. b.js 继续执行完成 → 输出 “b.js start” 和 “b.js end”
  8. 返回 a.js 继续执行 → 输出 “a.js start” 和 “a.js end”
  9. 返回 main.js 继续执行 → 输出最终结果

调用栈演示:

时间线    调用栈状态
T1:      [main.js]
T2:      [main.js, a.js]
T3:      [main.js, a.js, b.js]
T4:      [main.js, a.js, b.js] ← 检测循环依赖
T5:      [main.js, a.js] ← b.js 完成
T6:      [main.js] ← a.js 完成
T7:      [] ← main.js 完成

这种后进先出的执行顺序是ES模块系统的核心特征之一。

2. 绑定的活性(Live Bindings)

// 展示ES模块活绑定如何帮助处理循环依赖

// counter.js
console.log('counter.js loading');
import { increment } from './utils.js';

export let count = 0;

export function getCount() {
    return count;
}

export function setCount(value) {
    count = value;
}

// 初始化时调用increment
increment();

// utils.js
console.log('utils.js loading');
import { count, setCount } from './counter.js';

export function increment() {
    console.log('increment called, current count:', count); // 初始时可能是0
    setCount(count + 1);
}

// main.js
import { getCount } from './counter.js';
import { increment } from './utils.js';

console.log('Initial count:', getCount()); // 1
increment();
console.log('After increment:', getCount()); // 2

检测循环依赖

1. 静态分析工具

// dependency-analyzer.js

class DependencyAnalyzer {
    constructor() {
        this.dependencies = new Map();
        this.visited = new Set();
        this.visiting = new Set();
    }
    
    addDependency(from, to) {
        if (!this.dependencies.has(from)) {
            this.dependencies.set(from, new Set());
        }
        this.dependencies.get(from).add(to);
    }
    
    findCircularDependencies() {
        const cycles = [];
        
        for (const module of this.dependencies.keys()) {
            if (!this.visited.has(module)) {
                const path = [];
                const cycle = this.dfs(module, path);
                if (cycle) {
                    cycles.push(cycle);
                }
            }
        }
        
        return cycles;
    }
    
    dfs(module, path) {
        if (this.visiting.has(module)) {
            // 找到循环依赖
            const cycleStart = path.indexOf(module);
            return path.slice(cycleStart).concat([module]);
        }
        
        if (this.visited.has(module)) {
            return null;
        }
        
        this.visiting.add(module);
        path.push(module);
        
        const dependencies = this.dependencies.get(module) || new Set();
        for (const dep of dependencies) {
            const cycle = this.dfs(dep, [...path]);
            if (cycle) {
                return cycle;
            }
        }
        
        this.visiting.delete(module);
        this.visited.add(module);
        path.pop();
        
        return null;
    }
    
    generateReport() {
        const cycles = this.findCircularDependencies();
        
        if (cycles.length === 0) {
            return 'No circular dependencies found.';
        }
        
        let report = `Found ${cycles.length} circular dependency(ies):\n\n`;
        
        cycles.forEach((cycle, index) => {
            report += `${index + 1}. ${cycle.join(' → ')}\n`;
        });
        
        return report;
    }
}

// 使用示例
const analyzer = new DependencyAnalyzer();

// 添加依赖关系
analyzer.addDependency('user.js', 'order.js');
analyzer.addDependency('order.js', 'product.js');
analyzer.addDependency('product.js', 'user.js');

console.log(analyzer.generateReport());
// 输出: Found 1 circular dependency(ies):
//       1. user.js → order.js → product.js → user.js

2. 运行时检测

// runtime-cycle-detector.js

class RuntimeCycleDetector {
    constructor() {
        this.importStack = [];
        this.importGraph = new Map();
    }
    
    beforeImport(modulePath, currentModule) {
        // 检查是否形成循环
        if (this.importStack.includes(modulePath)) {
            const cycleStart = this.importStack.indexOf(modulePath);
            const cycle = this.importStack.slice(cycleStart).concat([modulePath]);
            
            console.warn('🔄 Circular dependency detected:', cycle.join(' → '));
            
            // 记录到图中
            this.recordCycle(cycle);
            
            return true; // 检测到循环
        }
        
        this.importStack.push(modulePath);
        return false; // 未检测到循环
    }
    
    afterImport(modulePath) {
        const index = this.importStack.indexOf(modulePath);
        if (index !== -1) {
            this.importStack.splice(index, 1);
        }
    }
    
    recordCycle(cycle) {
        const cycleKey = cycle.slice().sort().join('|');
        if (!this.importGraph.has(cycleKey)) {
            this.importGraph.set(cycleKey, {
                cycle: cycle,
                count: 1,
                firstDetected: new Date()
            });
        } else {
            this.importGraph.get(cycleKey).count++;
        }
    }
    
    getReport() {
        const cycles = Array.from(this.importGraph.values());
        
        if (cycles.length === 0) {
            return 'No circular dependencies detected during runtime.';
        }
        
        let report = 'Runtime Circular Dependencies Report:\n\n';
        
        cycles.forEach((info, index) => {
            report += `${index + 1}. ${info.cycle.join(' → ')}\n`;
            report += `   Detected ${info.count} time(s)\n`;
            report += `   First detected: ${info.firstDetected.toISOString()}\n\n`;
        });
        
        return report;
    }
}

// 创建全局检测器实例
const cycleDetector = new RuntimeCycleDetector();

// 模拟导入钩子(实际实现可能需要构建工具支持)
function importWithDetection(modulePath, currentModule) {
    const hasCycle = cycleDetector.beforeImport(modulePath, currentModule);
    
    if (hasCycle) {
        console.log(`⚠️  Proceeding with import despite circular dependency: ${modulePath}`);
    }
    
    // 执行实际导入
    const result = import(modulePath);
    
    cycleDetector.afterImport(modulePath);
    
    return result;
}

循环依赖的解决方案

1. 重构消除循环依赖

// 问题:用户和订单模块相互依赖

// 原始设计(有循环依赖)
// user.js
import { Order } from './order.js';

export class User {
    constructor(id, name) {
        this.id = id;
        this.name = name;
    }
    
    getOrders() {
        return Order.findByUserId(this.id);
    }
}

// order.js
import { User } from './user.js';

export class Order {
    constructor(id, userId, amount) {
        this.id = id;
        this.userId = userId;
        this.amount = amount;
    }
    
    getUser() {
        return User.findById(this.userId);
    }
    
    static findByUserId(userId) {
        // 查找逻辑
        return [];
    }
}

// 解决方案1:提取到服务层
// user.js
export class User {
    constructor(id, name) {
        this.id = id;
        this.name = name;
    }
}

// order.js
export class Order {
    constructor(id, userId, amount) {
        this.id = id;
        this.userId = userId;
        this.amount = amount;
    }
}

// user-service.js
import { User } from './user.js';
import { Order } from './order.js';

export class UserService {
    static async getUserWithOrders(userId) {
        const user = await User.findById(userId);
        const orders = await Order.findByUserId(userId);
        
        return {
            user,
            orders
        };
    }
    
    static async getOrderWithUser(orderId) {
        const order = await Order.findById(orderId);
        const user = await User.findById(order.userId);
        
        return {
            order,
            user
        };
    }
}

2. 依赖注入模式

// dependency-injection.js

// 用户仓库
export class UserRepository {
    async findById(id) {
        // 数据库查询逻辑
        return { id, name: `User ${id}` };
    }
    
    async findByIds(ids) {
        return ids.map(id => ({ id, name: `User ${id}` }));
    }
}

// 订单仓库
export class OrderRepository {
    async findById(id) {
        return { id, userId: 1, amount: 100 };
    }
    
    async findByUserId(userId) {
        return [
            { id: 1, userId, amount: 100 },
            { id: 2, userId, amount: 200 }
        ];
    }
}

// 用户服务(注入订单仓库)
export class UserService {
    constructor(orderRepository) {
        this.orderRepository = orderRepository;
    }
    
    async getUserOrders(userId) {
        return this.orderRepository.findByUserId(userId);
    }
}

// 订单服务(注入用户仓库)
export class OrderService {
    constructor(userRepository) {
        this.userRepository = userRepository;
    }
    
    async getOrderUser(orderId) {
        const order = await this.findById(orderId);
        return this.userRepository.findById(order.userId);
    }
}

// 应用组装器(无循环依赖)
// app.js
import { UserRepository } from './user-repository.js';
import { OrderRepository } from './order-repository.js';
import { UserService } from './user-service.js';
import { OrderService } from './order-service.js';

export function createServices() {
    const userRepository = new UserRepository();
    const orderRepository = new OrderRepository();
    
    const userService = new UserService(orderRepository);
    const orderService = new OrderService(userRepository);
    
    return {
        userService,
        orderService,
        userRepository,
        orderRepository
    };
}

3. 事件驱动模式

// event-driven.js

// 事件总线
class EventBus {
    constructor() {
        this.listeners = new Map();
    }
    
    on(event, callback) {
        if (!this.listeners.has(event)) {
            this.listeners.set(event, []);
        }
        this.listeners.get(event).push(callback);
    }
    
    off(event, callback) {
        const callbacks = this.listeners.get(event);
        if (callbacks) {
            const index = callbacks.indexOf(callback);
            if (index > -1) {
                callbacks.splice(index, 1);
            }
        }
    }
    
    emit(event, data) {
        const callbacks = this.listeners.get(event) || [];
        callbacks.forEach(callback => callback(data));
    }
}

export const eventBus = new EventBus();

// 用户模块(不直接依赖订单)
import { eventBus } from './event-bus.js';

export class User {
    constructor(id, name) {
        this.id = id;
        this.name = name;
    }
    
    static create(userData) {
        const user = new User(userData.id, userData.name);
        
        // 发布用户创建事件
        eventBus.emit('user:created', user);
        
        return user;
    }
    
    delete() {
        // 发布用户删除事件
        eventBus.emit('user:deleted', { userId: this.id });
    }
}

// 订单模块(通过事件响应用户变化)
import { eventBus } from './event-bus.js';

export class Order {
    constructor(id, userId, amount) {
        this.id = id;
        this.userId = userId;
        this.amount = amount;
    }
    
    static init() {
        // 监听用户删除事件
        eventBus.on('user:deleted', Order.handleUserDeleted);
    }
    
    static handleUserDeleted(data) {
        console.log(`Handling deletion of orders for user ${data.userId}`);
        // 处理用户删除后的订单清理逻辑
    }
}

// 初始化
Order.init();

4. 延迟导入模式

// lazy-import.js

// 用户模块
export class User {
    constructor(id, name) {
        this.id = id;
        this.name = name;
    }
    
    async getOrders() {
        // 延迟导入订单模块
        const { OrderService } = await import('./order-service.js');
        return OrderService.findByUserId(this.id);
    }
}

// 订单模块
export class Order {
    constructor(id, userId, amount) {
        this.id = id;
        this.userId = userId;
        this.amount = amount;
    }
    
    async getUser() {
        // 延迟导入用户模块
        const { UserService } = await import('./user-service.js');
        return UserService.findById(this.userId);
    }
}

// 订单服务
export class OrderService {
    static findByUserId(userId) {
        // 查找逻辑
        return Promise.resolve([
            new Order(1, userId, 100),
            new Order(2, userId, 200)
        ]);
    }
}

// 用户服务
export class UserService {
    static findById(userId) {
        // 查找逻辑
        return Promise.resolve(new User(userId, `User ${userId}`));
    }
}

5. 中介者模式

// mediator-pattern.js

// 中介者
export class AppMediator {
    constructor() {
        this.userService = null;
        this.orderService = null;
    }
    
    setUserService(userService) {
        this.userService = userService;
    }
    
    setOrderService(orderService) {
        this.orderService = orderService;
    }
    
    async getUserOrders(userId) {
        return this.orderService.findByUserId(userId);
    }
    
    async getOrderUser(orderId) {
        const order = await this.orderService.findById(orderId);
        return this.userService.findById(order.userId);
    }
    
    async getUserWithOrders(userId) {
        const user = await this.userService.findById(userId);
        const orders = await this.orderService.findByUserId(userId);
        
        return { user, orders };
    }
}

// 创建全局中介者实例
export const mediator = new AppMediator();

// 用户服务
import { mediator } from './mediator.js';

export class UserService {
    constructor() {
        mediator.setUserService(this);
    }
    
    async findById(id) {
        return { id, name: `User ${id}` };
    }
    
    async getUserOrders(userId) {
        return mediator.getUserOrders(userId);
    }
}

// 订单服务
import { mediator } from './mediator.js';

export class OrderService {
    constructor() {
        mediator.setOrderService(this);
    }
    
    async findById(id) {
        return { id, userId: 1, amount: 100 };
    }
    
    async findByUserId(userId) {
        return [
            { id: 1, userId, amount: 100 },
            { id: 2, userId, amount: 200 }
        ];
    }
    
    async getOrderUser(orderId) {
        return mediator.getOrderUser(orderId);
    }
}

最佳实践

1. 设计原则

// design-principles.js

// 原则1:单向依赖
// ✅ 好的设计
// domain/user.js
export class User {
    constructor(id, name) {
        this.id = id;
        this.name = name;
    }
}

// domain/order.js
import { User } from './user.js';  // 只依赖User,User不依赖Order

export class Order {
    constructor(id, user, amount) {
        this.id = id;
        this.user = user;  // 组合关系
        this.amount = amount;
    }
}

// 原则2:依赖于抽象而非具体实现
// services/user-service.js
export class UserService {
    constructor(userRepository) {  // 依赖抽象接口
        this.userRepository = userRepository;
    }
    
    async createUser(userData) {
        return this.userRepository.save(userData);
    }
}

// 原则3:分层架构
// 表现层 → 应用层 → 领域层 → 基础设施层
// 上层可以依赖下层,下层不应依赖上层

2. 模块组织策略

// module-organization.js

// 策略1:按特性分组(Feature-based)
// features/
//   ├── user/
//   │   ├── user.model.js
//   │   ├── user.service.js
//   │   └── user.controller.js
//   ├── order/
//   │   ├── order.model.js
//   │   ├── order.service.js
//   │   └── order.controller.js
//   └── shared/
//       ├── event-bus.js
//       └── database.js

// 策略2:按层分组(Layer-based)
// src/
//   ├── models/
//   │   ├── user.js
//   │   └── order.js
//   ├── services/
//   │   ├── user-service.js
//   │   └── order-service.js
//   ├── controllers/
//   │   ├── user-controller.js
//   │   └── order-controller.js
//   └── shared/
//       └── interfaces.js

// 策略3:混合方式
// src/
//   ├── core/              # 核心业务逻辑
//   │   ├── user/
//   │   └── order/
//   ├── infrastructure/    # 基础设施
//   │   ├── database/
//   │   └── external-apis/
//   ├── application/       # 应用服务
//   │   └── use-cases/
//   └── presentation/      # 表现层
//       └── controllers/

3. 工具和配置

// tools-config.js

// ESLint配置检测循环导入
// .eslintrc.js
module.exports = {
    plugins: ['import'],
    rules: {
        'import/no-cycle': ['error', { 
            maxDepth: 10,
            ignoreExternal: true 
        }]
    }
};

// Webpack配置显示循环依赖警告
// webpack.config.js
const CircularDependencyPlugin = require('circular-dependency-plugin');

module.exports = {
    plugins: [
        new CircularDependencyPlugin({
            exclude: /node_modules/,
            failOnError: true,
            allowAsyncCycles: false,
            cwd: process.cwd(),
        })
    ]
};

// 自定义检测脚本
// scripts/check-cycles.js
const madge = require('madge');

madge('./src')
    .then((res) => {
        const circular = res.circular();
        if (circular.length > 0) {
            console.error('Circular dependencies found:');
            circular.forEach((cycle) => {
                console.error('  ', cycle.join(' → '));
            });
            process.exit(1);
        } else {
            console.log('✅ No circular dependencies found');
        }
    })
    .catch((err) => {
        console.error('Error analyzing dependencies:', err);
        process.exit(1);
    });

总结

循环依赖虽然在ES模块中不会直接导致错误,但会增加代码的复杂性和维护难度:

  • 理解ES模块行为: 掌握模块加载顺序和活绑定机制
  • 及早检测: 使用工具在开发阶段发现循环依赖
  • 重构设计: 通过分层、依赖注入等模式消除循环依赖
  • 采用最佳实践: 遵循单向依赖和分层架构原则
  • 选择合适策略: 根据项目特点选择适当的解决方案

良好的模块设计应该避免循环依赖,这不仅提高了代码的可测试性和可维护性,也使应用架构更加清晰和稳定。


下一章: CommonJS基础

CommonJS基础

CommonJS是Node.js采用的模块系统,也是JavaScript模块化历史上最重要的规范之一。虽然ES模块是现代标准,但理解CommonJS仍然至关重要,因为大量的Node.js代码和npm包仍在使用这种模块系统。

什么是CommonJS

背景和历史

CommonJS规范于2009年诞生,目标是为JavaScript提供一个服务器端的模块系统。在ES6模块出现之前,CommonJS是JavaScript模块化的事实标准。

// CommonJS的核心理念
// 1. 每个文件都是一个模块
// 2. 模块内的变量和函数默认是私有的
// 3. 通过module.exports导出
// 4. 通过require()导入
// 5. 同步加载模块

核心特性

  • 同步加载: 模块在require时同步加载和执行
  • 缓存机制: 模块只执行一次,后续require返回缓存结果
  • 动态加载: 可以在运行时动态require模块
  • 简单易用: 语法简洁,学习成本低
  • Node.js原生支持: Node.js内置支持,无需额外配置

基本语法

1. 模块导出

// math.js - 基本导出示例

// 方式1: 直接给exports添加属性
exports.add = function(a, b) {
    return a + b;
};

exports.subtract = function(a, b) {
    return a - b;
};

exports.PI = 3.14159;

// 方式2: 使用module.exports
module.exports.multiply = function(a, b) {
    return a * b;
};

// 方式3: 整体替换module.exports
module.exports = {
    divide: function(a, b) {
        if (b === 0) {
            throw new Error('Division by zero');
        }
        return a / b;
    },
    power: function(base, exponent) {
        return Math.pow(base, exponent);
    }
};

// 注意: 一旦整体替换module.exports,之前的exports.*都会失效

2. 模块导入

// app.js - 基本导入示例

// 导入整个模块
const math = require('./math');
console.log(math.add(2, 3)); // 5

// 解构导入特定函数
const { add, subtract, PI } = require('./math');
console.log(add(5, 3)); // 8
console.log(PI); // 3.14159

// 导入并重命名
const { add: sum, subtract: diff } = require('./math');
console.log(sum(10, 5)); // 15
console.log(diff(10, 5)); // 5

// 导入内置模块
const fs = require('fs');
const path = require('path');
const http = require('http');

// 导入npm包
const lodash = require('lodash');
const express = require('express');

3. 不同的导出模式

// user.js - 类导出示例
class User {
    constructor(name, email) {
        this.name = name;
        this.email = email;
    }
    
    getInfo() {
        return `${this.name} <${this.email}>`;
    }
}

// 导出类
module.exports = User;

// config.js - 对象导出示例
const config = {
    database: {
        host: 'localhost',
        port: 5432,
        name: 'myapp'
    },
    server: {
        port: 3000,
        env: process.env.NODE_ENV || 'development'
    },
    secrets: {
        jwtSecret: process.env.JWT_SECRET || 'default-secret'
    }
};

module.exports = config;

// utils.js - 函数集合导出
function formatDate(date) {
    return date.toISOString().split('T')[0];
}

function isValidEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

function generateId() {
    return Math.random().toString(36).substr(2, 9);
}

// 批量导出
module.exports = {
    formatDate,
    isValidEmail,
    generateId
};

// logger.js - 单函数导出
function createLogger(level = 'info') {
    return {
        info: (message) => console.log(`[INFO] ${message}`),
        warn: (message) => console.warn(`[WARN] ${message}`),
        error: (message) => console.error(`[ERROR] ${message}`)
    };
}

module.exports = createLogger;

exports vs module.exports

理解两者的关系

// 理解exports和module.exports的关系

// Node.js内部的模块包装器大致如下:
function wrapModule(moduleCode) {
    return `
    (function(exports, require, module, __filename, __dirname) {
        ${moduleCode}
        return module.exports;
    });
    `;
}

// 初始状态下:exports === module.exports
console.log(exports === module.exports); // true

// exports是module.exports的引用
// 所以下面两种写法是等价的:
exports.hello = 'world';
module.exports.hello = 'world';

常见陷阱

// trap.js - 常见陷阱示例

// ❌ 错误用法1: 直接给exports赋值
exports = {
    name: 'John',
    age: 30
};
// 这样做只是改变了exports的指向,不会影响module.exports

// ❌ 错误用法2: 混用exports和module.exports
exports.method1 = function() { return 'method1'; };
module.exports = {
    method2: function() { return 'method2'; }
};
// module.exports会覆盖exports的设置

// ✅ 正确用法1: 始终使用exports添加属性
exports.method1 = function() { return 'method1'; };
exports.method2 = function() { return 'method2'; };

// ✅ 正确用法2: 始终使用module.exports
module.exports = {
    method1: function() { return 'method1'; },
    method2: function() { return 'method2'; }
};

// ✅ 正确用法3: 先设置module.exports,再使用exports
module.exports = {};
exports.method1 = function() { return 'method1'; };
exports.method2 = function() { return 'method2'; };

模块加载机制

1. 同步加载

// sync-loading.js

console.log('Before require');

// require是同步的,会阻塞代码执行
const largeModule = require('./large-module');

console.log('After require');

// large-module.js
console.log('Large module loading...');
// 模拟大量计算
for (let i = 0; i < 1000000; i++) {
    // 大量计算
}
console.log('Large module loaded');

module.exports = {
    data: 'some data'
};

// 输出顺序:
// Before require
// Large module loading...
// Large module loaded
// After require

2. 模块缓存

// cache-demo.js

// 第一次require会执行模块代码
const module1 = require('./counter');
console.log('First require:', module1.getCount()); // 0

module1.increment();
console.log('After increment:', module1.getCount()); // 1

// 第二次require返回缓存的模块
const module2 = require('./counter');
console.log('Second require:', module2.getCount()); // 1 (不是0!)

console.log(module1 === module2); // true (同一个对象)

// counter.js
console.log('Counter module executing...');

let count = 0;

module.exports = {
    increment() {
        count++;
    },
    getCount() {
        return count;
    }
};

3. 清除模块缓存

// cache-management.js

// 查看模块缓存
console.log('Cached modules:', Object.keys(require.cache));

// 加载模块
const myModule = require('./my-module');

// 清除特定模块缓存
delete require.cache[require.resolve('./my-module')];

// 重新加载模块(会重新执行模块代码)
const reloadedModule = require('./my-module');

// 清除所有缓存(不建议在生产环境使用)
function clearAllCache() {
    Object.keys(require.cache).forEach(key => {
        delete require.cache[key];
    });
}

// 安全的模块重载函数
function safeReload(modulePath) {
    try {
        // 解析模块路径
        const resolvedPath = require.resolve(modulePath);
        
        // 删除缓存
        delete require.cache[resolvedPath];
        
        // 重新加载
        return require(modulePath);
    } catch (error) {
        console.error('Failed to reload module:', error);
        return null;
    }
}

模块解析算法

1. 解析规则

// Node.js模块解析算法示例

// 1. 核心模块(优先级最高)
const fs = require('fs');          // 直接加载Node.js内置模块
const path = require('path');      // 内置模块不需要路径

// 2. 相对路径模块
const myModule = require('./my-module');     // ./my-module.js
const subModule = require('./sub/module');   // ./sub/module.js
const parentModule = require('../parent');   // ../parent.js

// 3. 绝对路径模块
const absoluteModule = require('/home/user/app/module');

// 4. node_modules中的模块
const lodash = require('lodash');           // node_modules/lodash
const express = require('express');         // node_modules/express

// Node.js查找node_modules的顺序:
// ./node_modules/module-name
// ../node_modules/module-name
// ../../node_modules/module-name
// ... 一直到文件系统根目录

2. 文件扩展名解析

// extension-resolution.js

// Node.js会按以下顺序尝试扩展名:

// require('./module') 会依次尝试:
// 1. ./module.js
// 2. ./module.json
// 3. ./module.node

// 如果上述都不存在,会尝试目录:
// 4. ./module/package.json (查找main字段)
// 5. ./module/index.js
// 6. ./module/index.json
// 7. ./module/index.node

// 演示不同类型的模块加载
const jsModule = require('./utils');        // utils.js
const jsonConfig = require('./config');     // config.json
const packageModule = require('./my-package'); // my-package/index.js

// config.json
{
    "name": "my-app",
    "version": "1.0.0",
    "database": {
        "host": "localhost",
        "port": 5432
    }
}

// my-package/package.json
{
    "name": "my-package",
    "main": "lib/index.js"
}

3. package.json的作用

// package-resolution.js

// package.json示例
{
    "name": "my-library",
    "version": "1.0.0",
    "main": "dist/index.js",        // CommonJS入口
    "module": "dist/index.esm.js",  // ES模块入口
    "exports": {                     // 新的导出字段
        ".": {
            "require": "./dist/index.js",
            "import": "./dist/index.esm.js"
        },
        "./utils": "./dist/utils.js"
    },
    "files": ["dist/"]
}

// 当require('my-library')时,Node.js会:
// 1. 查找node_modules/my-library/package.json
// 2. 读取main字段值:dist/index.js
// 3. 加载node_modules/my-library/dist/index.js

CommonJS的高级特性

1. 动态require

// dynamic-require.js

// 基于条件的动态加载
const environment = process.env.NODE_ENV || 'development';

let config;
if (environment === 'production') {
    config = require('./config/production');
} else if (environment === 'test') {
    config = require('./config/test');
} else {
    config = require('./config/development');
}

// 基于用户输入的动态加载
function loadPlugin(pluginName) {
    try {
        const plugin = require(`./plugins/${pluginName}`);
        return plugin;
    } catch (error) {
        console.error(`Failed to load plugin: ${pluginName}`, error);
        return null;
    }
}

// 批量加载模块
function loadModules(moduleNames) {
    const modules = {};
    
    moduleNames.forEach(name => {
        try {
            modules[name] = require(`./modules/${name}`);
        } catch (error) {
            console.warn(`Failed to load module: ${name}`);
        }
    });
    
    return modules;
}

// 使用示例
const plugins = ['auth', 'logger', 'database'];
const loadedModules = loadModules(plugins);

2. 条件导出

// conditional-exports.js

// 基于环境的条件导出
if (process.env.NODE_ENV === 'development') {
    // 开发环境:导出详细的调试版本
    module.exports = {
        log: console.log,
        debug: console.debug,
        warn: console.warn,
        error: console.error,
        trace: console.trace
    };
} else {
    // 生产环境:导出简化版本
    module.exports = {
        log: () => {}, // 空操作
        debug: () => {},
        warn: console.warn,
        error: console.error,
        trace: () => {}
    };
}

// 基于功能检测的条件导出
const hasFileSystem = (() => {
    try {
        require('fs');
        return true;
    } catch {
        return false;
    }
})();

if (hasFileSystem) {
    module.exports = require('./file-storage');
} else {
    module.exports = require('./memory-storage');
}

3. 模块工厂模式

// factory-pattern.js

// 模块工厂:根据参数创建不同的实例
function createLogger(options = {}) {
    const {
        level = 'info',
        prefix = '',
        timestamp = true
    } = options;
    
    const levels = {
        debug: 0,
        info: 1,
        warn: 2,
        error: 3
    };
    
    const currentLevel = levels[level] || 1;
    
    function log(targetLevel, message) {
        if (levels[targetLevel] >= currentLevel) {
            const time = timestamp ? new Date().toISOString() : '';
            const fullMessage = `${time} ${prefix} [${targetLevel.toUpperCase()}] ${message}`;
            console.log(fullMessage);
        }
    }
    
    return {
        debug: (msg) => log('debug', msg),
        info: (msg) => log('info', msg),
        warn: (msg) => log('warn', msg),
        error: (msg) => log('error', msg)
    };
}

module.exports = createLogger;

// 使用工厂模式
const logger = require('./logger-factory')({
    level: 'debug',
    prefix: '[MyApp]',
    timestamp: true
});

logger.info('Application started');

4. 模块单例模式

// singleton-pattern.js

// 单例数据库连接
class Database {
    constructor() {
        if (Database.instance) {
            return Database.instance;
        }
        
        this.connection = null;
        this.connected = false;
        Database.instance = this;
    }
    
    connect(connectionString) {
        if (!this.connected) {
            console.log('Connecting to database...');
            this.connection = { connectionString };
            this.connected = true;
        }
        return this.connection;
    }
    
    query(sql) {
        if (!this.connected) {
            throw new Error('Database not connected');
        }
        console.log(`Executing query: ${sql}`);
        return Promise.resolve([]);
    }
}

// 导出单例实例
module.exports = new Database();

// 在任何地方使用都是同一个实例
const db1 = require('./database');
const db2 = require('./database');
console.log(db1 === db2); // true

Node.js特有功能

1. 全局变量

// globals-demo.js

console.log('Module information:');
console.log('__filename:', __filename);    // 当前文件的绝对路径
console.log('__dirname:', __dirname);      // 当前目录的绝对路径
console.log('module.id:', module.id);      // 模块标识符
console.log('module.filename:', module.filename); // 同__filename
console.log('module.loaded:', module.loaded);     // 模块是否已加载完成
console.log('module.parent:', module.parent);     // 父模块
console.log('module.children:', module.children); // 子模块列表

// require对象的属性
console.log('require.main:', require.main);       // 主模块
console.log('require.cache keys:', Object.keys(require.cache)); // 模块缓存
console.log('require.resolve("./utils"):', require.resolve('./utils')); // 解析模块路径

// 实用的路径操作
const path = require('path');

// 获取当前模块所在目录的其他文件
const configPath = path.join(__dirname, 'config.json');
const utilsPath = path.join(__dirname, 'utils', 'helpers.js');

console.log('Config path:', configPath);
console.log('Utils path:', utilsPath);

2. require.resolve

// resolve-demo.js

// require.resolve返回模块的绝对路径,但不加载模块
const modulePath = require.resolve('./my-module');
console.log('Module path:', modulePath);

// 检查模块是否存在
function moduleExists(moduleName) {
    try {
        require.resolve(moduleName);
        return true;
    } catch (error) {
        return false;
    }
}

console.log('lodash exists:', moduleExists('lodash'));
console.log('non-existent exists:', moduleExists('non-existent-module'));

// 解析npm包的子模块
const lodashMapPath = require.resolve('lodash/map');
console.log('Lodash map path:', lodashMapPath);

// 动态加载可选依赖
function loadOptionalModule(moduleName) {
    try {
        const modulePath = require.resolve(moduleName);
        return require(modulePath);
    } catch (error) {
        console.warn(`Optional module ${moduleName} not found`);
        return null;
    }
}

const optionalModule = loadOptionalModule('optional-dependency');

3. 模块热重载

// hot-reload.js

const fs = require('fs');
const path = require('path');

class ModuleHotReloader {
    constructor() {
        this.watchers = new Map();
    }
    
    watch(modulePath, callback) {
        const absolutePath = require.resolve(modulePath);
        
        if (this.watchers.has(absolutePath)) {
            return;
        }
        
        const watcher = fs.watchFile(absolutePath, (curr, prev) => {
            if (curr.mtime > prev.mtime) {
                console.log(`Module ${modulePath} changed, reloading...`);
                
                // 清除缓存
                delete require.cache[absolutePath];
                
                // 重新加载
                try {
                    const reloadedModule = require(modulePath);
                    callback(reloadedModule);
                } catch (error) {
                    console.error('Failed to reload module:', error);
                }
            }
        });
        
        this.watchers.set(absolutePath, watcher);
    }
    
    unwatch(modulePath) {
        const absolutePath = require.resolve(modulePath);
        const watcher = this.watchers.get(absolutePath);
        
        if (watcher) {
            fs.unwatchFile(absolutePath);
            this.watchers.delete(absolutePath);
        }
    }
    
    unwatchAll() {
        this.watchers.forEach((watcher, path) => {
            fs.unwatchFile(path);
        });
        this.watchers.clear();
    }
}

// 使用示例
const reloader = new ModuleHotReloader();

reloader.watch('./config', (newConfig) => {
    console.log('Config reloaded:', newConfig);
    // 更新应用配置
});

性能优化

1. 延迟加载

// lazy-loading.js

// 延迟加载大型模块
class LazyLoader {
    constructor() {
        this._cache = new Map();
    }
    
    lazy(modulePath) {
        return new Proxy({}, {
            get: (target, prop) => {
                if (!this._cache.has(modulePath)) {
                    console.log(`Lazy loading: ${modulePath}`);
                    this._cache.set(modulePath, require(modulePath));
                }
                
                const module = this._cache.get(modulePath);
                return module[prop];
            }
        });
    }
}

const loader = new LazyLoader();

// 只有在第一次访问时才会加载模块
const heavyModule = loader.lazy('./heavy-computation');

// 这行代码才会触发模块加载
console.log(heavyModule.calculate(100));

2. 模块预加载

// preload.js

class ModulePreloader {
    constructor() {
        this.preloaded = new Map();
    }
    
    preload(modules) {
        modules.forEach(modulePath => {
            setImmediate(() => {
                try {
                    this.preloaded.set(modulePath, require(modulePath));
                    console.log(`Preloaded: ${modulePath}`);
                } catch (error) {
                    console.warn(`Failed to preload: ${modulePath}`, error);
                }
            });
        });
    }
    
    get(modulePath) {
        if (this.preloaded.has(modulePath)) {
            return this.preloaded.get(modulePath);
        }
        return require(modulePath);
    }
}

// 应用启动时预加载常用模块
const preloader = new ModulePreloader();
preloader.preload([
    'lodash',
    'moment',
    './utils/helpers',
    './config/database'
]);

// 后续使用预加载的模块
setTimeout(() => {
    const lodash = preloader.get('lodash');
    const helpers = preloader.get('./utils/helpers');
}, 1000);

最佳实践

1. 模块导出策略

// export-strategies.js

// ✅ 推荐:明确的导出
module.exports = {
    // 明确列出所有导出
    createUser,
    updateUser,
    deleteUser,
    UserValidationError
};

// ✅ 推荐:单一职责导出
module.exports = class UserService {
    constructor(database) {
        this.db = database;
    }
    
    async createUser(userData) {
        // 实现
    }
};

// ❌ 避免:混用exports和module.exports
exports.method1 = () => {};
module.exports.method2 = () => {}; // 不一致

// ❌ 避免:导出过多内容
module.exports = {
    // 导出了太多不相关的内容
    UserService,
    ProductService,
    OrderService,
    DatabaseConnection,
    Logger,
    Config,
    Utils
};

2. 错误处理

// error-handling.js

// ✅ 推荐:优雅的错误处理
function safeRequire(modulePath, fallback = null) {
    try {
        return require(modulePath);
    } catch (error) {
        if (error.code === 'MODULE_NOT_FOUND') {
            console.warn(`Module not found: ${modulePath}`);
            return fallback;
        }
        throw error; // 重新抛出其他类型的错误
    }
}

// 使用示例
const optionalConfig = safeRequire('./optional-config', {});
const logger = safeRequire('./logger', console);

// ✅ 推荐:模块初始化错误处理
function createDatabaseModule() {
    let connection = null;
    
    function connect() {
        if (!connection) {
            try {
                connection = require('./database-connection')();
            } catch (error) {
                console.error('Failed to initialize database:', error);
                throw new Error('Database module initialization failed');
            }
        }
        return connection;
    }
    
    return {
        connect,
        query: (sql) => connect().query(sql)
    };
}

module.exports = createDatabaseModule();

3. 模块测试

// testable-module.js

// ✅ 可测试的模块设计
class UserService {
    constructor(dependencies = {}) {
        // 依赖注入,便于测试
        this.database = dependencies.database || require('./database');
        this.logger = dependencies.logger || require('./logger');
        this.emailService = dependencies.emailService || require('./email-service');
    }
    
    async createUser(userData) {
        try {
            // 验证数据
            this.validateUserData(userData);
            
            // 创建用户
            const user = await this.database.users.create(userData);
            
            // 发送欢迎邮件
            await this.emailService.sendWelcomeEmail(user.email);
            
            this.logger.info(`User created: ${user.id}`);
            return user;
        } catch (error) {
            this.logger.error('Failed to create user:', error);
            throw error;
        }
    }
    
    validateUserData(userData) {
        if (!userData.email) {
            throw new Error('Email is required');
        }
        // 更多验证逻辑
    }
}

module.exports = UserService;

// user-service.test.js
const UserService = require('./user-service');

describe('UserService', () => {
    it('should create user successfully', async () => {
        // 模拟依赖
        const mockDatabase = {
            users: {
                create: jest.fn().mockResolvedValue({ id: 1, email: 'test@example.com' })
            }
        };
        
        const mockLogger = {
            info: jest.fn(),
            error: jest.fn()
        };
        
        const mockEmailService = {
            sendWelcomeEmail: jest.fn().mockResolvedValue(true)
        };
        
        // 注入模拟依赖
        const userService = new UserService({
            database: mockDatabase,
            logger: mockLogger,
            emailService: mockEmailService
        });
        
        const userData = { email: 'test@example.com', name: 'Test User' };
        const result = await userService.createUser(userData);
        
        expect(result.id).toBe(1);
        expect(mockDatabase.users.create).toHaveBeenCalledWith(userData);
        expect(mockEmailService.sendWelcomeEmail).toHaveBeenCalledWith('test@example.com');
    });
});

总结

CommonJS作为Node.js的原生模块系统,具有以下特点:

  • 简单易用: 语法简洁,学习成本低
  • 同步加载: 适合服务器端开发,模块加载可预期
  • 动态特性: 支持运行时动态加载模块
  • 成熟生态: 大量npm包基于CommonJS构建
  • 工具支持: 丰富的开发工具和调试支持

理解CommonJS对于Node.js开发和理解JavaScript模块化演进历程都非常重要。虽然ES模块是未来趋势,但CommonJS仍在现代开发中扮演重要角色。


下一章: require与module.exports

require与module.exports

本章深入探讨CommonJS模块系统的两个核心机制:require()函数和module.exports对象。理解它们的工作原理和最佳实践对于编写高质量的Node.js代码至关重要。

require()函数深入解析

require()的工作流程

// require()的内部工作流程示例

// 1. 路径解析
const resolvedPath = require.resolve('./my-module');

// 2. 检查缓存
if (require.cache[resolvedPath]) {
    return require.cache[resolvedPath].exports;
}

// 3. 创建新模块对象
const module = {
    id: resolvedPath,
    filename: resolvedPath,
    loaded: false,
    parent: currentModule,
    children: [],
    exports: {}
};

// 4. 加载并执行模块
const moduleWrapper = NativeModule.wrap(moduleContent);
const compiledWrapper = vm.runInThisContext(moduleWrapper);
compiledWrapper.call(module.exports, module.exports, require, module, __filename, __dirname);

// 5. 标记模块已加载
module.loaded = true;

// 6. 缓存模块
require.cache[resolvedPath] = module;

// 7. 返回exports
return module.exports;

require()的不同用法

// require-usage.js

// 1. 基本用法
const fs = require('fs');
const path = require('path');
const myModule = require('./my-module');

// 2. 条件require
let config;
if (process.env.NODE_ENV === 'production') {
    config = require('./config/production.json');
} else {
    config = require('./config/development.json');
}

// 3. 动态require
function loadModule(moduleName) {
    try {
        return require(moduleName);
    } catch (error) {
        console.log(`Module ${moduleName} not found`);
        return null;
    }
}

// 4. require表达式计算
const moduleName = './modules/' + process.argv[2];
const dynamicModule = require(moduleName);

// 5. 解构require
const { readFile, writeFile } = require('fs/promises');
const { join, dirname } = require('path');

// 6. require JSON文件
const packageInfo = require('./package.json');
const config = require('./config.json');

// 7. require.resolve - 仅解析路径不加载
const modulePath = require.resolve('lodash');
console.log('Lodash path:', modulePath);

// 8. 检查模块是否存在
function moduleExists(name) {
    try {
        require.resolve(name);
        return true;
    } catch (e) {
        return false;
    }
}

require缓存机制

// require-cache.js

// 演示模块缓存
console.log('=== 模块缓存演示 ===');

// 第一次require - 执行模块代码
console.log('First require:');
const module1 = require('./counter-module');
console.log('Counter value:', module1.getValue()); // 0

// 增加计数
module1.increment();
module1.increment();
console.log('After increment:', module1.getValue()); // 2

// 第二次require - 返回缓存
console.log('Second require:');
const module2 = require('./counter-module');
console.log('Counter value:', module2.getValue()); // 2 (不是0!)

// 验证是同一个对象
console.log('Same object?', module1 === module2); // true

// counter-module.js
console.log('Counter module executing...');

let counter = 0;

module.exports = {
    increment() {
        counter++;
    },
    getValue() {
        return counter;
    }
};

// 缓存操作
console.log('\n=== 缓存操作 ===');

// 查看缓存的模块
console.log('Cached modules:');
Object.keys(require.cache).forEach(key => {
    console.log(key);
});

// 删除缓存并重新加载
const modulePath = require.resolve('./counter-module');
delete require.cache[modulePath];

console.log('After clearing cache:');
const module3 = require('./counter-module');
console.log('Counter value:', module3.getValue()); // 0 (重新执行了模块)

require()的错误处理

// require-error-handling.js

// 基本错误处理
function safeRequire(modulePath, defaultValue = null) {
    try {
        return require(modulePath);
    } catch (error) {
        console.log(`Failed to require ${modulePath}:`, error.message);
        return defaultValue;
    }
}

// 区分不同类型的错误
function detailedRequire(modulePath) {
    try {
        return require(modulePath);
    } catch (error) {
        switch (error.code) {
            case 'MODULE_NOT_FOUND':
                console.log(`Module not found: ${modulePath}`);
                break;
            case 'ENOENT':
                console.log(`File not found: ${modulePath}`);
                break;
            default:
                console.log(`Error loading module: ${error.message}`);
                throw error; // 重新抛出未知错误
        }
        return null;
    }
}

// 可选依赖加载
function loadOptionalDependencies(dependencies) {
    const loaded = {};
    
    dependencies.forEach(dep => {
        try {
            loaded[dep] = require(dep);
            console.log(`✅ Loaded: ${dep}`);
        } catch (error) {
            console.log(`⚠️  Optional dependency not found: ${dep}`);
            loaded[dep] = null;
        }
    });
    
    return loaded;
}

// 使用示例
const optionalDeps = loadOptionalDependencies([
    'colors',
    'moment',
    'non-existent-package'
]);

// 带重试的require
function requireWithRetry(modulePath, maxRetries = 3) {
    for (let i = 0; i < maxRetries; i++) {
        try {
            return require(modulePath);
        } catch (error) {
            if (i === maxRetries - 1) {
                throw error;
            }
            console.log(`Retry ${i + 1} for ${modulePath}`);
        }
    }
}

module.exports详解

module.exports vs exports

// exports-comparison.js

// 理解module.exports和exports的关系
console.log('初始状态:');
console.log('exports === module.exports:', exports === module.exports); // true

// exports是module.exports的引用
exports.method1 = function() {
    return 'method1 from exports';
};

module.exports.method2 = function() {
    return 'method2 from module.exports';
};

console.log('添加方法后:');
console.log('exports === module.exports:', exports === module.exports); // 仍然是true

// 危险操作:重新赋值exports
exports = {
    method3: function() {
        return 'method3';
    }
};

console.log('重新赋值exports后:');
console.log('exports === module.exports:', exports === module.exports); // false!

// 此时module.exports仍然包含method1和method2
// 但exports指向了新对象,包含method3

// 正确的重新赋值方式
module.exports = {
    method4: function() {
        return 'method4';
    }
};

// 现在module.exports只包含method4了

不同的导出模式

// export-patterns.js

// 1. 对象导出模式
module.exports = {
    name: 'MyModule',
    version: '1.0.0',
    
    init() {
        console.log(`${this.name} v${this.version} initialized`);
    },
    
    process(data) {
        return data.toUpperCase();
    }
};

// 2. 类导出模式
class Calculator {
    add(a, b) { return a + b; }
    subtract(a, b) { return a - b; }
    multiply(a, b) { return a * b; }
    divide(a, b) { 
        if (b === 0) throw new Error('Division by zero');
        return a / b; 
    }
}

module.exports = Calculator;

// 3. 函数导出模式
function createLogger(level = 'info') {
    const levels = ['debug', 'info', 'warn', 'error'];
    const currentLevel = levels.indexOf(level);
    
    return {
        log(message, msgLevel = 'info') {
            const msgIndex = levels.indexOf(msgLevel);
            if (msgIndex >= currentLevel) {
                console.log(`[${msgLevel.toUpperCase()}] ${message}`);
            }
        }
    };
}

module.exports = createLogger;

// 4. 混合导出模式
function mainFunction() {
    return 'Main functionality';
}

// 将函数本身作为默认导出
module.exports = mainFunction;

// 添加额外的属性和方法
module.exports.helper = function() {
    return 'Helper functionality';
};

module.exports.constant = 42;

module.exports.SubClass = class {
    constructor() {
        this.name = 'SubClass';
    }
};

// 5. 条件导出模式
if (process.env.NODE_ENV === 'development') {
    module.exports = {
        log: console.log,
        warn: console.warn,
        error: console.error,
        debug: console.debug
    };
} else {
    module.exports = {
        log() {}, // 生产环境静默
        warn: console.warn,
        error: console.error,
        debug() {}
    };
}

渐进式导出

// progressive-exports.js

// 可以逐步构建exports对象

// 先创建基础结构
module.exports = {};

// 添加常量
module.exports.VERSION = '2.1.0';
module.exports.DEFAULT_CONFIG = {
    timeout: 5000,
    retries: 3
};

// 添加工具函数
module.exports.utils = {};

module.exports.utils.formatDate = function(date) {
    return date.toISOString().split('T')[0];
};

module.exports.utils.generateId = function() {
    return Math.random().toString(36).substr(2, 9);
};

// 添加主要功能
module.exports.createClient = function(config = {}) {
    const finalConfig = { ...module.exports.DEFAULT_CONFIG, ...config };
    
    return {
        config: finalConfig,
        
        async request(url) {
            console.log(`Requesting ${url} with timeout ${finalConfig.timeout}ms`);
            // 模拟请求
            return { status: 200, data: 'response data' };
        }
    };
};

// 添加错误类
class ClientError extends Error {
    constructor(message, code) {
        super(message);
        this.name = 'ClientError';
        this.code = code;
    }
}

module.exports.ClientError = ClientError;

// 添加验证函数
module.exports.validate = {
    url(url) {
        try {
            new URL(url);
            return true;
        } catch {
            return false;
        }
    },
    
    config(config) {
        if (typeof config !== 'object') return false;
        if (config.timeout && typeof config.timeout !== 'number') return false;
        if (config.retries && typeof config.retries !== 'number') return false;
        return true;
    }
};

高级模式和技巧

1. 模块工厂模式

// module-factory.js

// 工厂函数创建配置化的模块
function createDatabase(options = {}) {
    const {
        host = 'localhost',
        port = 5432,
        database = 'myapp',
        pool = { min: 2, max: 10 }
    } = options;
    
    let connection = null;
    
    return {
        async connect() {
            if (!connection) {
                console.log(`Connecting to ${host}:${port}/${database}`);
                connection = { 
                    host, 
                    port, 
                    database, 
                    connected: true 
                };
            }
            return connection;
        },
        
        async query(sql, params = []) {
            const conn = await this.connect();
            console.log(`Executing: ${sql}`, params);
            return { rows: [], rowCount: 0 };
        },
        
        async close() {
            if (connection) {
                console.log('Closing database connection');
                connection.connected = false;
                connection = null;
            }
        },
        
        getConfig() {
            return { host, port, database, pool };
        }
    };
}

module.exports = createDatabase;

// 使用工厂模式
// const db = require('./database-factory')({
//     host: 'prod-server',
//     port: 5432,
//     database: 'production_db'
// });

2. 单例模式

// singleton-pattern.js

// 单例配置管理器
let instance = null;

class ConfigManager {
    constructor() {
        if (instance) {
            return instance;
        }
        
        this.config = {};
        this.loaded = false;
        instance = this;
    }
    
    load(configPath) {
        if (!this.loaded) {
            try {
                this.config = require(configPath);
                this.loaded = true;
                console.log('Configuration loaded');
            } catch (error) {
                console.error('Failed to load configuration:', error);
                this.config = {};
            }
        }
        return this;
    }
    
    get(key, defaultValue = null) {
        return this.getNestedValue(this.config, key, defaultValue);
    }
    
    set(key, value) {
        this.setNestedValue(this.config, key, value);
    }
    
    getNestedValue(obj, path, defaultValue) {
        const keys = path.split('.');
        let current = obj;
        
        for (const key of keys) {
            if (current === null || current === undefined || !(key in current)) {
                return defaultValue;
            }
            current = current[key];
        }
        
        return current;
    }
    
    setNestedValue(obj, path, value) {
        const keys = path.split('.');
        const lastKey = keys.pop();
        let current = obj;
        
        for (const key of keys) {
            if (!(key in current)) {
                current[key] = {};
            }
            current = current[key];
        }
        
        current[lastKey] = value;
    }
}

// 导出单例实例
module.exports = new ConfigManager();

// 无论在哪里require,都是同一个实例
// const config1 = require('./config-manager');
// const config2 = require('./config-manager');
// console.log(config1 === config2); // true

3. 插件系统

// plugin-system.js

class PluginManager {
    constructor() {
        this.plugins = new Map();
        this.hooks = new Map();
    }
    
    register(name, plugin) {
        if (typeof plugin !== 'object') {
            throw new Error('Plugin must be an object');
        }
        
        if (typeof plugin.init !== 'function') {
            throw new Error('Plugin must have an init method');
        }
        
        this.plugins.set(name, plugin);
        console.log(`Plugin registered: ${name}`);
        
        // 初始化插件
        plugin.init(this);
        
        // 注册插件的钩子
        if (plugin.hooks) {
            Object.entries(plugin.hooks).forEach(([hookName, handler]) => {
                this.addHook(hookName, handler);
            });
        }
    }
    
    addHook(name, handler) {
        if (!this.hooks.has(name)) {
            this.hooks.set(name, []);
        }
        this.hooks.get(name).push(handler);
    }
    
    async executeHook(name, ...args) {
        const handlers = this.hooks.get(name) || [];
        const results = [];
        
        for (const handler of handlers) {
            try {
                const result = await handler(...args);
                results.push(result);
            } catch (error) {
                console.error(`Hook ${name} error:`, error);
            }
        }
        
        return results;
    }
    
    getPlugin(name) {
        return this.plugins.get(name);
    }
    
    unregister(name) {
        const plugin = this.plugins.get(name);
        if (plugin && typeof plugin.destroy === 'function') {
            plugin.destroy();
        }
        this.plugins.delete(name);
        console.log(`Plugin unregistered: ${name}`);
    }
}

// 导出插件管理器
const pluginManager = new PluginManager();

// 提供便捷的插件加载函数
function loadPlugin(pluginPath) {
    try {
        const plugin = require(pluginPath);
        const name = plugin.name || require('path').basename(pluginPath);
        pluginManager.register(name, plugin);
        return plugin;
    } catch (error) {
        console.error(`Failed to load plugin ${pluginPath}:`, error);
        return null;
    }
}

module.exports = {
    pluginManager,
    loadPlugin
};

4. 延迟初始化

// lazy-initialization.js

// 延迟初始化的数据库连接
let _connection = null;
let _connecting = false;
let _connectionPromise = null;

async function getConnection() {
    if (_connection) {
        return _connection;
    }
    
    if (_connecting) {
        return _connectionPromise;
    }
    
    _connecting = true;
    _connectionPromise = initializeConnection();
    
    try {
        _connection = await _connectionPromise;
        return _connection;
    } finally {
        _connecting = false;
        _connectionPromise = null;
    }
}

async function initializeConnection() {
    console.log('Initializing database connection...');
    
    // 模拟异步连接过程
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    const connection = {
        id: Math.random().toString(36),
        connected: true,
        
        async query(sql) {
            if (!this.connected) {
                throw new Error('Connection closed');
            }
            console.log(`Executing: ${sql}`);
            return { rows: [], count: 0 };
        },
        
        async close() {
            this.connected = false;
            _connection = null;
            console.log('Connection closed');
        }
    };
    
    console.log('Database connection established');
    return connection;
}

// 导出接口
module.exports = {
    async query(sql) {
        const conn = await getConnection();
        return conn.query(sql);
    },
    
    async close() {
        if (_connection) {
            await _connection.close();
        }
    },
    
    isConnected() {
        return _connection && _connection.connected;
    }
};

性能优化技巧

1. 条件require

// conditional-require.js

// 避免不必要的require
let heavyModule = null;

function getHeavyModule() {
    if (!heavyModule) {
        console.log('Loading heavy module...');
        heavyModule = require('./heavy-computation');
    }
    return heavyModule;
}

// 只有在需要时才加载
function performHeavyOperation(data) {
    const heavy = getHeavyModule();
    return heavy.process(data);
}

// 基于功能检测的条件require
let cryptoModule = null;

function getCrypto() {
    if (!cryptoModule) {
        try {
            cryptoModule = require('crypto');
        } catch (error) {
            // 回退到其他实现
            cryptoModule = require('./crypto-fallback');
        }
    }
    return cryptoModule;
}

// 基于环境的条件require
const debugMode = process.env.NODE_ENV === 'development';

let debugUtils = null;
if (debugMode) {
    debugUtils = require('./debug-utils');
}

function debug(message) {
    if (debugMode && debugUtils) {
        debugUtils.log(message);
    }
}

module.exports = {
    performHeavyOperation,
    getCrypto,
    debug
};

2. require缓存优化

// cache-optimization.js

// 手动管理require缓存
class RequireCache {
    static clear(modulePath) {
        const resolvedPath = require.resolve(modulePath);
        delete require.cache[resolvedPath];
    }
    
    static clearPattern(pattern) {
        const regex = new RegExp(pattern);
        Object.keys(require.cache).forEach(key => {
            if (regex.test(key)) {
                delete require.cache[key];
            }
        });
    }
    
    static preload(modules) {
        modules.forEach(modulePath => {
            try {
                require(modulePath);
                console.log(`Preloaded: ${modulePath}`);
            } catch (error) {
                console.warn(`Failed to preload: ${modulePath}`);
            }
        });
    }
    
    static getStats() {
        const cached = Object.keys(require.cache);
        return {
            count: cached.length,
            modules: cached.map(path => ({
                path,
                loaded: require.cache[path].loaded
            }))
        };
    }
}

// 智能require封装
function smartRequire(modulePath, options = {}) {
    const { cache = true, reload = false } = options;
    
    if (reload) {
        RequireCache.clear(modulePath);
    }
    
    const module = require(modulePath);
    
    if (!cache) {
        // 立即清除缓存
        setImmediate(() => {
            RequireCache.clear(modulePath);
        });
    }
    
    return module;
}

module.exports = {
    RequireCache,
    smartRequire
};

调试和监控

1. require跟踪

// require-tracer.js

// 跟踪require调用
const originalRequire = module.constructor.prototype.require;

module.constructor.prototype.require = function(id) {
    const start = Date.now();
    console.log(`📦 Requiring: ${id}`);
    
    try {
        const result = originalRequire.call(this, id);
        const duration = Date.now() - start;
        console.log(`✅ Loaded: ${id} (${duration}ms)`);
        return result;
    } catch (error) {
        const duration = Date.now() - start;
        console.log(`❌ Failed: ${id} (${duration}ms) - ${error.message}`);
        throw error;
    }
};

// 使用示例
const fs = require('fs');        // 📦 Requiring: fs / ✅ Loaded: fs (1ms)
const path = require('path');    // 📦 Requiring: path / ✅ Loaded: path (0ms)

// 恢复原始require
function restoreRequire() {
    module.constructor.prototype.require = originalRequire;
}

module.exports = { restoreRequire };

2. 模块依赖分析

// dependency-analyzer.js

class DependencyAnalyzer {
    constructor() {
        this.dependencies = new Map();
        this.loadTimes = new Map();
    }
    
    analyze() {
        Object.entries(require.cache).forEach(([path, moduleObj]) => {
            const deps = moduleObj.children.map(child => child.filename);
            this.dependencies.set(path, deps);
        });
        
        return this.generateReport();
    }
    
    generateReport() {
        const report = {
            totalModules: this.dependencies.size,
            dependencies: {},
            circularDependencies: this.findCircularDependencies(),
            heaviestModules: this.findHeaviestModules()
        };
        
        this.dependencies.forEach((deps, module) => {
            report.dependencies[module] = {
                dependsOn: deps,
                dependentCount: deps.length
            };
        });
        
        return report;
    }
    
    findCircularDependencies() {
        // 简化的循环依赖检测
        const visited = new Set();
        const visiting = new Set();
        const cycles = [];
        
        const dfs = (module, path = []) => {
            if (visiting.has(module)) {
                const cycleStart = path.indexOf(module);
                cycles.push(path.slice(cycleStart).concat(module));
                return;
            }
            
            if (visited.has(module)) return;
            
            visiting.add(module);
            path.push(module);
            
            const deps = this.dependencies.get(module) || [];
            deps.forEach(dep => dfs(dep, [...path]));
            
            visiting.delete(module);
            visited.add(module);
        };
        
        this.dependencies.forEach((_, module) => {
            if (!visited.has(module)) {
                dfs(module);
            }
        });
        
        return cycles;
    }
    
    findHeaviestModules(top = 10) {
        const modules = Array.from(this.dependencies.entries())
            .map(([module, deps]) => ({
                module,
                dependencyCount: deps.length
            }))
            .sort((a, b) => b.dependencyCount - a.dependencyCount)
            .slice(0, top);
        
        return modules;
    }
}

module.exports = DependencyAnalyzer;

总结

require()module.exports是CommonJS模块系统的核心:

  • require(): 同步加载、缓存机制、路径解析
  • module.exports: 灵活的导出方式、与exports的关系
  • 高级模式: 工厂模式、单例模式、插件系统
  • 性能优化: 条件加载、缓存管理、延迟初始化
  • 调试工具: 依赖跟踪、性能监控、循环依赖检测

理解这些机制有助于编写更高效、更可维护的Node.js应用。


下一章: 模块缓存机制

CommonJS模块缓存机制

CommonJS模块系统的一个重要特性是模块缓存(Module Caching)。理解缓存机制对于优化应用性能、避免重复执行代码以及管理模块状态至关重要。

模块缓存原理

1. 缓存机制概述

// math.js
console.log('math.js被执行');

let counter = 0;

function add(a, b) {
    counter++;
    return a + b;
}

function getCounter() {
    return counter;
}

module.exports = { add, getCounter };
// app.js
const math1 = require('./math'); // 输出: math.js被执行
const math2 = require('./math'); // 不会再次输出

console.log(math1 === math2); // true - 同一个对象
console.log(math1.add(2, 3)); // 5
console.log(math1.getCounter()); // 1
console.log(math2.getCounter()); // 1 - 共享状态

2. require.cache对象

Node.js将所有加载的模块存储在require.cache对象中:

// cache-demo.js
console.log('加载前的缓存:', Object.keys(require.cache));

const fs = require('fs');
const path = require('path');
const myModule = require('./my-module');

console.log('加载后的缓存:', Object.keys(require.cache));

// 查看特定模块的缓存信息
const modulePath = require.resolve('./my-module');
console.log('模块路径:', modulePath);
console.log('缓存对象:', require.cache[modulePath]);

3. 缓存键规则

缓存的键是模块的绝对路径

// 相同模块的不同引用方式
const mod1 = require('./utils');
const mod2 = require('./utils.js');
const mod3 = require(path.resolve(__dirname, 'utils.js'));

// 这些都指向同一个缓存条目(如果解析为相同的绝对路径)
console.log(mod1 === mod2); // true
console.log(mod2 === mod3); // true

缓存操作

1. 查看缓存

// cache-inspector.js
function showCache() {
    console.log('当前缓存的模块:');
    Object.keys(require.cache).forEach(path => {
        console.log(`  ${path}`);
    });
}

function showModuleInfo(modulePath) {
    const resolvedPath = require.resolve(modulePath);
    const cachedModule = require.cache[resolvedPath];
    
    if (cachedModule) {
        console.log(`模块信息: ${modulePath}`);
        console.log(`  绝对路径: ${resolvedPath}`);
        console.log(`  是否已加载: true`);
        console.log(`  导出对象:`, cachedModule.exports);
        console.log(`  子模块:`, cachedModule.children.map(child => child.id));
        console.log(`  父模块:`, cachedModule.parent ? cachedModule.parent.id : 'none');
    }
}

showCache();
require('./utils');
showModuleInfo('./utils');

2. 清除缓存

// cache-cleaner.js
function clearModuleCache(modulePath) {
    const resolvedPath = require.resolve(modulePath);
    
    // 删除缓存条目
    delete require.cache[resolvedPath];
    
    console.log(`已清除模块缓存: ${resolvedPath}`);
}

function clearAllCache() {
    Object.keys(require.cache).forEach(path => {
        delete require.cache[path];
    });
    console.log('已清除所有模块缓存');
}

// 使用示例
const math1 = require('./math'); // 首次加载
clearModuleCache('./math');
const math2 = require('./math'); // 重新加载

console.log(math1 === math2); // false - 不同的对象实例

3. 重新加载模块

// hot-reload.js
function reloadModule(modulePath) {
    // 清除缓存
    const resolvedPath = require.resolve(modulePath);
    delete require.cache[resolvedPath];
    
    // 重新加载
    return require(modulePath);
}

// 使用示例
let config = require('./config');
console.log('原始配置:', config);

// 模拟配置文件更改后重新加载
setTimeout(() => {
    config = reloadModule('./config');
    console.log('重新加载的配置:', config);
}, 1000);

缓存模式和最佳实践

1. 单例模式

// database.js - 数据库连接单例
let dbConnection = null;

function createConnection() {
    console.log('创建新的数据库连接');
    return {
        query: (sql) => console.log(`执行SQL: ${sql}`),
        close: () => console.log('关闭数据库连接')
    };
}

function getConnection() {
    if (!dbConnection) {
        dbConnection = createConnection();
    }
    return dbConnection;
}

module.exports = { getConnection };
// app.js
const db1 = require('./database').getConnection();
const db2 = require('./database').getConnection();

console.log(db1 === db2); // true - 同一个连接实例

2. 工厂模式

// logger-factory.js
const loggers = new Map();

function createLogger(name) {
    return {
        name,
        log: (message) => console.log(`[${name}] ${message}`),
        error: (message) => console.error(`[${name}] ERROR: ${message}`)
    };
}

function getLogger(name) {
    if (!loggers.has(name)) {
        loggers.set(name, createLogger(name));
    }
    return loggers.get(name);
}

module.exports = { getLogger };

3. 配置模块

// config.js
const config = {
    database: {
        host: process.env.DB_HOST || 'localhost',
        port: process.env.DB_PORT || 5432
    },
    api: {
        port: process.env.PORT || 3000,
        timeout: 30000
    }
};

// 提供更新配置的方法
function updateConfig(updates) {
    Object.assign(config, updates);
}

module.exports = {
    ...config,
    updateConfig
};

缓存相关问题和解决方案

1. 循环依赖中的缓存

// a.js
console.log('a.js开始执行');
exports.name = 'module-a';

const b = require('./b');
console.log('a.js中b的值:', b);

exports.getValue = () => `a-${b.name}`;
console.log('a.js执行完成');
// b.js
console.log('b.js开始执行');
exports.name = 'module-b';

const a = require('./a');
console.log('b.js中a的值:', a); // 此时a可能是部分导出的对象

exports.getValue = () => `b-${a.name || 'undefined'}`;
console.log('b.js执行完成');

2. 开发环境热重载

// dev-hot-reload.js
const fs = require('fs');
const path = require('path');

class ModuleHotReloader {
    constructor() {
        this.watchers = new Map();
    }
    
    watch(modulePath, callback) {
        const resolvedPath = require.resolve(modulePath);
        
        if (this.watchers.has(resolvedPath)) {
            return; // 已经在监听
        }
        
        const watcher = fs.watch(resolvedPath, (eventType) => {
            if (eventType === 'change') {
                console.log(`检测到文件变化: ${resolvedPath}`);
                
                // 清除缓存
                delete require.cache[resolvedPath];
                
                // 执行回调
                if (callback) {
                    try {
                        const newModule = require(modulePath);
                        callback(newModule, null);
                    } catch (error) {
                        callback(null, error);
                    }
                }
            }
        });
        
        this.watchers.set(resolvedPath, watcher);
    }
    
    stopWatching(modulePath) {
        const resolvedPath = require.resolve(modulePath);
        const watcher = this.watchers.get(resolvedPath);
        
        if (watcher) {
            watcher.close();
            this.watchers.delete(resolvedPath);
        }
    }
    
    stopAll() {
        this.watchers.forEach(watcher => watcher.close());
        this.watchers.clear();
    }
}

// 使用示例
const hotReloader = new ModuleHotReloader();

hotReloader.watch('./config', (newConfig, error) => {
    if (error) {
        console.error('重新加载配置失败:', error);
    } else {
        console.log('配置已更新:', newConfig);
    }
});

3. 内存泄漏预防

// memory-management.js
class ModuleCacheManager {
    constructor() {
        this.moduleUsage = new Map();
    }
    
    trackModule(modulePath) {
        const resolvedPath = require.resolve(modulePath);
        const usage = this.moduleUsage.get(resolvedPath) || 0;
        this.moduleUsage.set(resolvedPath, usage + 1);
    }
    
    cleanup() {
        // 清理很少使用的模块
        this.moduleUsage.forEach((usage, path) => {
            if (usage < 2 && require.cache[path]) {
                delete require.cache[path];
                this.moduleUsage.delete(path);
                console.log(`清理低使用率模块: ${path}`);
            }
        });
    }
    
    getStats() {
        return {
            totalCached: Object.keys(require.cache).length,
            tracked: this.moduleUsage.size,
            usage: Array.from(this.moduleUsage.entries())
        };
    }
}

const cacheManager = new ModuleCacheManager();

// 定期清理
setInterval(() => {
    cacheManager.cleanup();
}, 60000); // 每分钟清理一次

高级缓存技巧

1. 条件缓存

// conditional-cache.js
function requireWithCondition(modulePath, condition) {
    if (!condition) {
        // 如果条件不满足,临时清除缓存
        const resolvedPath = require.resolve(modulePath);
        const cached = require.cache[resolvedPath];
        delete require.cache[resolvedPath];
        
        try {
            return require(modulePath);
        } finally {
            // 恢复缓存(如果之前存在)
            if (cached) {
                require.cache[resolvedPath] = cached;
            }
        }
    }
    
    return require(modulePath);
}

// 使用示例
const isDevelopment = process.env.NODE_ENV === 'development';
const config = requireWithCondition('./config', !isDevelopment);

2. 缓存代理

// cache-proxy.js
function createCacheProxy(require) {
    const originalRequire = require;
    const loadTimes = new Map();
    
    return function proxiedRequire(modulePath) {
        const startTime = Date.now();
        const module = originalRequire(modulePath);
        const loadTime = Date.now() - startTime;
        
        const resolvedPath = originalRequire.resolve(modulePath);
        const times = loadTimes.get(resolvedPath) || [];
        times.push(loadTime);
        loadTimes.set(resolvedPath, times);
        
        // 添加性能统计方法
        if (!module.__performance) {
            Object.defineProperty(module, '__performance', {
                value: {
                    getLoadTimes: () => loadTimes.get(resolvedPath) || [],
                    getAverageLoadTime: () => {
                        const times = loadTimes.get(resolvedPath) || [];
                        return times.reduce((sum, time) => sum + time, 0) / times.length;
                    }
                },
                enumerable: false
            });
        }
        
        return module;
    };
}

// 使用代理
const proxiedRequire = createCacheProxy(require);
const utils = proxiedRequire('./utils');
console.log('平均加载时间:', utils.__performance.getAverageLoadTime(), 'ms');

测试中的缓存管理

1. 测试隔离

// test-helper.js
class TestCacheManager {
    constructor() {
        this.originalCache = null;
    }
    
    // 备份当前缓存
    backup() {
        this.originalCache = { ...require.cache };
    }
    
    // 恢复备份的缓存
    restore() {
        if (this.originalCache) {
            // 清除当前缓存
            Object.keys(require.cache).forEach(key => {
                delete require.cache[key];
            });
            
            // 恢复原始缓存
            Object.assign(require.cache, this.originalCache);
        }
    }
    
    // 清除特定模块的缓存
    clearModule(modulePath) {
        const resolvedPath = require.resolve(modulePath);
        delete require.cache[resolvedPath];
    }
    
    // 模拟新鲜环境
    isolate(callback) {
        this.backup();
        try {
            // 清除所有用户模块缓存(保留Node.js内置模块)
            Object.keys(require.cache).forEach(path => {
                if (!path.includes('node_modules') && !path.includes('internal')) {
                    delete require.cache[path];
                }
            });
            
            return callback();
        } finally {
            this.restore();
        }
    }
}

// 在测试中使用
const testCache = new TestCacheManager();

describe('Module tests', () => {
    beforeEach(() => {
        testCache.backup();
    });
    
    afterEach(() => {
        testCache.restore();
    });
    
    it('should load module fresh', () => {
        testCache.clearModule('./my-module');
        const module = require('./my-module');
        // 测试逻辑
    });
});

性能优化

1. 缓存预热

// cache-warmup.js
function warmupCache(modules) {
    console.log('开始缓存预热...');
    const startTime = Date.now();
    
    modules.forEach(modulePath => {
        try {
            require(modulePath);
            console.log(`✓ 预热完成: ${modulePath}`);
        } catch (error) {
            console.error(`✗ 预热失败: ${modulePath}`, error.message);
        }
    });
    
    const warmupTime = Date.now() - startTime;
    console.log(`缓存预热完成,耗时: ${warmupTime}ms`);
}

// 应用启动时预热
const criticalModules = [
    './database',
    './cache',
    './logger',
    './config',
    './utils'
];

warmupCache(criticalModules);

2. 智能缓存清理

// smart-cache-cleaner.js
class SmartCacheManager {
    constructor(options = {}) {
        this.maxCacheSize = options.maxCacheSize || 100;
        this.maxAge = options.maxAge || 3600000; // 1小时
        this.accessTimes = new Map();
        this.loadTimes = new Map();
    }
    
    trackAccess(modulePath) {
        const resolvedPath = require.resolve(modulePath);
        this.accessTimes.set(resolvedPath, Date.now());
    }
    
    shouldEvict(modulePath) {
        const resolvedPath = require.resolve(modulePath);
        const lastAccess = this.accessTimes.get(resolvedPath);
        
        if (!lastAccess) return true;
        
        // 超过最大年龄
        if (Date.now() - lastAccess > this.maxAge) {
            return true;
        }
        
        return false;
    }
    
    cleanup() {
        const cachedModules = Object.keys(require.cache);
        
        // 如果缓存数量超过限制
        if (cachedModules.length > this.maxCacheSize) {
            const candidates = cachedModules
                .filter(path => this.shouldEvict(path))
                .sort((a, b) => {
                    const aTime = this.accessTimes.get(a) || 0;
                    const bTime = this.accessTimes.get(b) || 0;
                    return aTime - bTime; // 最少最近使用
                });
            
            // 清理最老的模块
            const toRemove = candidates.slice(0, Math.floor(this.maxCacheSize * 0.1));
            toRemove.forEach(path => {
                delete require.cache[path];
                this.accessTimes.delete(path);
                console.log(`智能清理模块: ${path}`);
            });
        }
    }
    
    getStats() {
        return {
            cacheSize: Object.keys(require.cache).length,
            trackedAccess: this.accessTimes.size,
            maxCacheSize: this.maxCacheSize,
            oldestAccess: Math.min(...this.accessTimes.values()),
            newestAccess: Math.max(...this.accessTimes.values())
        };
    }
}

总结

CommonJS模块缓存机制是Node.js性能优化的重要组成部分:

  • 自动缓存: 模块首次加载后自动缓存,提高性能
  • 状态共享: 支持模块间状态共享和单例模式
  • 灵活控制: 可以手动清除和重新加载模块
  • 开发友好: 支持热重载和测试隔离
  • ⚠️ 内存管理: 需要注意内存泄漏和缓存清理
  • ⚠️ 循环依赖: 在循环依赖中需要特别小心缓存时机

理解和善用缓存机制能够显著提升Node.js应用的性能和开发体验。


下一章: 模块互操作性

模块互操作性

在现代JavaScript开发中,经常需要在同一个项目中使用不同的模块系统。本章将深入探讨CommonJS与ES模块的互操作性,以及如何在实际项目中处理模块系统的混合使用。

CommonJS与ES模块的差异

1. 语法差异

// CommonJS
const fs = require('fs');
const { readFile } = require('fs');
module.exports = { utility };
exports.helper = function() {};

// ES模块
import fs from 'fs';
import { readFile } from 'fs';
export { utility };
export const helper = function() {};
export default utility;

2. 执行时机差异

// CommonJS - 同步加载
console.log('开始');
const utils = require('./utils'); // 同步执行
console.log('结束');

// ES模块 - 静态分析
console.log('开始');
import utils from './utils.js'; // 编译时确定
console.log('结束');

3. 值复制 vs 实时绑定

// CommonJS - 值复制
// counter.js (CommonJS)
let count = 0;
function increment() {
    count++;
}
function getCount() {
    return count;
}
module.exports = { count, increment, getCount };

// main.js
const { count, increment, getCount } = require('./counter');
console.log(count); // 0
increment();
console.log(count); // 0 (值复制,不会更新)
console.log(getCount()); // 1 (通过函数获取最新值)
// ES模块 - 实时绑定
// counter.mjs (ES模块)
let count = 0;
export function increment() {
    count++;
}
export { count };

// main.mjs
import { count, increment } from './counter.mjs';
console.log(count); // 0
increment();
console.log(count); // 1 (实时绑定,会更新)

Node.js中的互操作性

1. ES模块导入CommonJS

// utils.js (CommonJS)
function add(a, b) {
    return a + b;
}

function multiply(a, b) {
    return a * b;
}

module.exports = {
    add,
    multiply,
    default: { add, multiply } // 可选:显式默认导出
};
// main.mjs (ES模块)

// 方式1:默认导入
import utils from './utils.js';
console.log(utils.add(2, 3)); // 5

// 方式2:命名导入(如果CommonJS模块支持)
import { add, multiply } from './utils.js';
console.log(add(2, 3)); // 5

// 方式3:命名空间导入
import * as utils from './utils.js';
console.log(utils.add(2, 3)); // 5

2. CommonJS导入ES模块

CommonJS不能直接使用import,需要使用动态导入:

// math.mjs (ES模块)
export function add(a, b) {
    return a + b;
}

export function subtract(a, b) {
    return a - b;
}

export default {
    version: '1.0.0'
};
// app.js (CommonJS)

// 方式1:使用动态import
async function main() {
    const math = await import('./math.mjs');
    console.log(math.add(2, 3)); // 5
    console.log(math.default.version); // '1.0.0'
    
    // 解构导入
    const { add, subtract } = await import('./math.mjs');
    console.log(add(5, 3)); // 8
}

main();

// 方式2:使用import()表达式
import('./math.mjs').then(math => {
    console.log(math.add(2, 3));
});

// 方式3:在函数中使用
function loadMath() {
    return import('./math.mjs');
}

3. 混合使用示例

// config.js (CommonJS)
const defaults = {
    port: 3000,
    host: 'localhost'
};

function createConfig(overrides = {}) {
    return { ...defaults, ...overrides };
}

module.exports = { defaults, createConfig };
// server.mjs (ES模块)
import express from 'express';
import configModule from './config.js';

const { createConfig } = configModule;

const config = createConfig({
    port: process.env.PORT || 8080
});

const app = express();

app.listen(config.port, () => {
    console.log(`Server running on port ${config.port}`);
});

打包工具中的互操作性

1. Webpack配置

// webpack.config.js
module.exports = {
    entry: './src/index.js',
    module: {
        rules: [
            {
                test: /\.m?js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: [
                            ['@babel/preset-env', {
                                modules: false // 保持ES模块用于tree shaking
                            }]
                        ]
                    }
                }
            }
        ]
    },
    resolve: {
        extensions: ['.js', '.mjs', '.cjs']
    }
};

2. Rollup配置

// rollup.config.js
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';

export default {
    input: 'src/index.js',
    output: {
        file: 'dist/bundle.js',
        format: 'esm'
    },
    plugins: [
        nodeResolve({
            preferBuiltins: false
        }),
        commonjs({
            // 将CommonJS模块转换为ES模块
            transformMixedEsModules: true
        })
    ]
};

3. Vite配置

// vite.config.js
import { defineConfig } from 'vite';

export default defineConfig({
    build: {
        rollupOptions: {
            output: {
                format: 'esm'
            }
        }
    },
    optimizeDeps: {
        include: ['legacy-commonjs-package']
    }
});

实际互操作模式

1. 渐进式迁移

// 步骤1:创建兼容层
// compat.js
const legacyUtils = require('./legacy-utils');

// 包装CommonJS模块为ES模块友好的格式
export const { helper1, helper2 } = legacyUtils;
export default legacyUtils;
// 步骤2:新代码使用ES模块
// new-feature.mjs
import { helper1 } from './compat.js';
import modernUtil from './modern-util.mjs';

export function newFeature() {
    return helper1() + modernUtil();
}

2. 双模式包

创建同时支持CommonJS和ES模块的包:

// package.json
{
  "name": "my-dual-package",
  "version": "1.0.0",
  "type": "module",
  "main": "./dist/index.cjs",
  "module": "./dist/index.mjs",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs"
    }
  }
}
// src/index.js (源码)
export function utility() {
    return 'Hello from utility';
}

export default {
    utility
};
// build/build-cjs.js (构建CommonJS版本)
const fs = require('fs');

const esmCode = `
export function utility() {
    return 'Hello from utility';
}

export default {
    utility
};
`;

const cjsCode = `
function utility() {
    return 'Hello from utility';
}

module.exports = {
    utility,
    default: { utility }
};
`;

fs.writeFileSync('./dist/index.cjs', cjsCode);
fs.writeFileSync('./dist/index.mjs', esmCode);

3. 条件导入

// dynamic-loader.js
async function loadModule(modulePath) {
    try {
        // 尝试ES模块导入
        return await import(modulePath);
    } catch (error) {
        // 降级到CommonJS
        if (error.code === 'ERR_REQUIRE_ESM') {
            return require(modulePath);
        }
        throw error;
    }
}

// 使用示例
async function main() {
    const module = await loadModule('./some-module');
    console.log(module.default || module);
}

常见问题和解决方案

1. __dirname和__filename在ES模块中的替代

// utils.mjs
import { fileURLToPath } from 'url';
import { dirname } from 'path';

// ES模块中获取当前文件路径
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

export { __filename, __dirname };
// 兼容函数
// path-utils.mjs
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';

export function getCurrentDir(importMetaUrl) {
    return dirname(fileURLToPath(importMetaUrl));
}

export function resolvePath(importMetaUrl, ...paths) {
    const currentDir = getCurrentDir(importMetaUrl);
    return join(currentDir, ...paths);
}

// 使用
import { resolvePath } from './path-utils.mjs';
const configPath = resolvePath(import.meta.url, 'config.json');

2. require.resolve在ES模块中的替代

// resolve-utils.mjs
import { createRequire } from 'module';

// 创建require函数
const require = createRequire(import.meta.url);

export function resolveModule(modulePath) {
    return require.resolve(modulePath);
}

export function importModule(modulePath) {
    return require(modulePath);
}

// 使用示例
const modulePath = resolveModule('lodash');
console.log(modulePath); // 绝对路径

3. 动态require在ES模块中的实现

// dynamic-require.mjs
import { createRequire } from 'module';

const require = createRequire(import.meta.url);

export function dynamicRequire(modulePath) {
    return require(modulePath);
}

export async function universalImport(modulePath) {
    try {
        // 首先尝试ES模块导入
        return await import(modulePath);
    } catch (esError) {
        try {
            // 降级到CommonJS
            return { default: require(modulePath) };
        } catch (cjsError) {
            throw new Error(`无法加载模块 ${modulePath}: ${esError.message}`);
        }
    }
}

4. 模块类型检测

// module-detector.mjs
import { readFileSync } from 'fs';
import { resolve } from 'path';

export function isESModule(packagePath) {
    try {
        const packageJsonPath = resolve(packagePath, 'package.json');
        const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
        return packageJson.type === 'module';
    } catch {
        return false;
    }
}

export function getModuleType(filePath) {
    if (filePath.endsWith('.mjs')) return 'esm';
    if (filePath.endsWith('.cjs')) return 'commonjs';
    
    // 检查package.json
    const packageDir = findPackageDir(filePath);
    return isESModule(packageDir) ? 'esm' : 'commonjs';
}

function findPackageDir(startPath) {
    let currentPath = resolve(startPath);
    while (currentPath !== resolve(currentPath, '..')) {
        try {
            readFileSync(resolve(currentPath, 'package.json'));
            return currentPath;
        } catch {
            currentPath = resolve(currentPath, '..');
        }
    }
    return null;
}

最佳实践

1. 新项目建议

// 推荐的项目结构
project/
├── package.json          // type: "module"
├── src/
│   ├── index.mjs         // ES模块入口
│   ├── utils/
│   │   ├── modern.mjs    // 新代码使用ES模块
│   │   └── legacy.cjs    // 旧代码保持CommonJS
│   └── compat/
│       └── require-wrapper.mjs // CommonJS兼容层
└── build/
    ├── build-dual.js     // 构建双模式包
    └── test-compat.js    // 兼容性测试

2. 迁移策略

// 阶段1:准备阶段
// package.json
{
  "type": "module",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs"
    }
  }
}

// 阶段2:渐进迁移
// 保持CommonJS接口不变,内部逐步迁移
// 阶段3:完全切换
// 移除CommonJS兼容层

3. 库开发建议

// lib/index.js (源码使用ES模块)
export function utility() {
    return 'utility function';
}

export class Helper {
    constructor(options) {
        this.options = options;
    }
    
    process() {
        return 'processed';
    }
}

export default {
    utility,
    Helper
};
// scripts/build-dual.js
import { rollup } from 'rollup';

async function buildDual() {
    // 构建ES模块版本
    const esmBundle = await rollup({
        input: 'lib/index.js',
        external: ['fs', 'path']
    });
    
    await esmBundle.write({
        file: 'dist/index.mjs',
        format: 'esm'
    });
    
    // 构建CommonJS版本
    const cjsBundle = await rollup({
        input: 'lib/index.js',
        external: ['fs', 'path']
    });
    
    await cjsBundle.write({
        file: 'dist/index.cjs',
        format: 'cjs'
    });
}

buildDual();

总结

模块互操作性是现代JavaScript开发的重要话题:

  • 渐进迁移: 可以逐步从CommonJS迁移到ES模块

  • 工具支持: 现代构建工具提供了良好的互操作支持

  • 双模式包: 可以创建同时支持两种模块系统的包

  • 动态导入: 提供了运行时模块加载的灵活性

  • ⚠️ 性能考虑: 频繁的动态导入可能影响性能

  • ⚠️ 复杂性: 混合使用增加了项目的复杂性

  • ⚠️ 调试困难: 互操作问题可能难以调试

理解和掌握模块互操作性对于维护现有项目和开发新项目都至关重要。随着生态系统的发展,ES模块正在成为主流,但CommonJS仍将在相当长的时间内存在。


下一章: AMD模块系统

AMD模块系统基础

AMD(Asynchronous Module Definition,异步模块定义)是一种为浏览器环境设计的JavaScript模块格式。它支持异步加载、依赖管理和模块化开发,是早期解决浏览器端模块化问题的重要方案。

什么是AMD

AMD是一个用于定义模块的JavaScript API,具有以下特点:

  • 异步加载: 支持模块的异步加载,不阻塞页面渲染
  • 依赖管理: 明确声明模块依赖,自动处理加载顺序
  • 浏览器友好: 专为浏览器环境设计,无需构建工具
  • 插件系统: 支持加载各种类型的资源
  • 配置灵活: 提供丰富的配置选项

AMD API

1. define函数

define是AMD的核心函数,用于定义模块:

// 基本语法
define(id?, dependencies?, factory);

无依赖模块

// math.js - 简单的数学工具模块
define(function() {
    function add(a, b) {
        return a + b;
    }
    
    function multiply(a, b) {
        return a * b;
    }
    
    // 返回模块的公共接口
    return {
        add: add,
        multiply: multiply,
        PI: 3.14159
    };
});

有依赖的模块

// calculator.js - 依赖math模块的计算器
define(['math'], function(math) {
    function calculate(operation, a, b) {
        switch(operation) {
            case 'add':
                return math.add(a, b);
            case 'multiply':
                return math.multiply(a, b);
            case 'circle-area':
                return math.PI * a * a;
            default:
                throw new Error('Unknown operation: ' + operation);
        }
    }
    
    return {
        calculate: calculate
    };
});

命名模块

// 指定模块ID
define('utils/string', [], function() {
    function capitalize(str) {
        return str.charAt(0).toUpperCase() + str.slice(1);
    }
    
    function trim(str) {
        return str.replace(/^\s+|\s+$/g, '');
    }
    
    return {
        capitalize: capitalize,
        trim: trim
    };
});

2. require函数

require用于动态加载和使用模块:

// 异步加载模块
require(['calculator', 'utils/string'], function(calc, stringUtils) {
    var result = calc.calculate('add', 5, 3);
    var message = stringUtils.capitalize('result is: ' + result);
    console.log(message); // "Result is: 8"
});

错误处理

require(['module1', 'module2'], 
    function(mod1, mod2) {
        // 成功回调
        console.log('Modules loaded successfully');
    },
    function(error) {
        // 错误回调
        console.error('Failed to load modules:', error);
    }
);

3. require.config配置

require.config({
    // 基础路径
    baseUrl: 'js/lib',
    
    // 路径映射
    paths: {
        'jquery': 'jquery-3.6.0.min',
        'underscore': 'underscore-1.13.1.min',
        'backbone': 'backbone-1.4.0.min',
        'utils': '../utils',
        'components': '../components'
    },
    
    // 模块依赖配置(用于非AMD库)
    shim: {
        'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone'
        },
        'underscore': {
            exports: '_'
        }
    },
    
    // 超时设置
    waitSeconds: 15,
    
    // URL参数
    urlArgs: 'bust=' + (new Date()).getTime()
});

常见模式

1. 简单工厂模式

// logger.js
define(function() {
    function createLogger(name) {
        return {
            name: name,
            log: function(message) {
                console.log('[' + this.name + '] ' + message);
            },
            error: function(message) {
                console.error('[' + this.name + '] ERROR: ' + message);
            }
        };
    }
    
    return {
        create: createLogger
    };
});

2. 单例模式

// config.js
define(function() {
    var instance = null;
    
    function Config() {
        if (instance) {
            return instance;
        }
        
        this.settings = {
            apiUrl: '/api',
            timeout: 5000,
            debug: false
        };
        
        instance = this;
        return this;
    }
    
    Config.prototype.get = function(key) {
        return this.settings[key];
    };
    
    Config.prototype.set = function(key, value) {
        this.settings[key] = value;
    };
    
    return new Config();
});

3. 模块继承

// base-view.js
define(function() {
    function BaseView(element) {
        this.element = element;
        this.initialize();
    }
    
    BaseView.prototype.initialize = function() {
        // 基础初始化逻辑
    };
    
    BaseView.prototype.render = function() {
        throw new Error('render method must be implemented');
    };
    
    BaseView.prototype.destroy = function() {
        this.element = null;
    };
    
    return BaseView;
});

// user-view.js
define(['base-view'], function(BaseView) {
    function UserView(element, user) {
        BaseView.call(this, element);
        this.user = user;
    }
    
    // 继承BaseView
    UserView.prototype = Object.create(BaseView.prototype);
    UserView.prototype.constructor = UserView;
    
    UserView.prototype.render = function() {
        this.element.innerHTML = '<h1>' + this.user.name + '</h1>';
        return this;
    };
    
    return UserView;
});

4. 插件模式

// text插件 - 加载文本文件
define('text', function() {
    return {
        load: function(name, req, onload, config) {
            var xhr = new XMLHttpRequest();
            xhr.open('GET', name, true);
            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        onload(xhr.responseText);
                    } else {
                        onload.error(xhr);
                    }
                }
            };
            xhr.send();
        }
    };
});

// 使用text插件
define(['text!templates/user.html'], function(userTemplate) {
    return {
        render: function(user) {
            return userTemplate.replace('{{name}}', user.name);
        }
    };
});

实际应用示例

1. 模块化应用结构

// app.js - 应用入口
require.config({
    baseUrl: 'js',
    paths: {
        'jquery': 'lib/jquery-3.6.0.min',
        'router': 'app/router',
        'views': 'app/views',
        'models': 'app/models',
        'utils': 'app/utils'
    }
});

require(['jquery', 'router'], function($, Router) {
    $(document).ready(function() {
        var router = new Router();
        router.start();
    });
});
// app/router.js
define(['jquery', 'views/home', 'views/user'], function($, HomeView, UserView) {
    function Router() {
        this.routes = {
            '': 'home',
            'user/:id': 'user'
        };
        this.views = {
            home: HomeView,
            user: UserView
        };
    }
    
    Router.prototype.start = function() {
        this.bindEvents();
        this.navigate(window.location.hash);
    };
    
    Router.prototype.bindEvents = function() {
        var self = this;
        $(window).on('hashchange', function() {
            self.navigate(window.location.hash);
        });
    };
    
    Router.prototype.navigate = function(hash) {
        var route = hash.replace('#', '');
        var viewName = this.matchRoute(route);
        if (viewName && this.views[viewName]) {
            var View = this.views[viewName];
            new View().render();
        }
    };
    
    Router.prototype.matchRoute = function(route) {
        // 简化的路由匹配
        for (var pattern in this.routes) {
            if (pattern === route || pattern === '') {
                return this.routes[pattern];
            }
        }
        return null;
    };
    
    return Router;
});

2. 组件化开发

// components/modal.js
define(['jquery'], function($) {
    function Modal(options) {
        this.options = $.extend({
            title: '',
            content: '',
            closable: true,
            width: 400,
            height: 300
        }, options);
        
        this.element = null;
        this.isVisible = false;
    }
    
    Modal.prototype.create = function() {
        if (this.element) {
            return this;
        }
        
        this.element = $('<div class="modal">')
            .css({
                position: 'fixed',
                top: '50%',
                left: '50%',
                width: this.options.width,
                height: this.options.height,
                marginTop: -this.options.height / 2,
                marginLeft: -this.options.width / 2,
                backgroundColor: 'white',
                border: '1px solid #ccc',
                boxShadow: '0 4px 8px rgba(0,0,0,0.1)',
                zIndex: 1000,
                display: 'none'
            });
        
        var header = $('<div class="modal-header">')
            .css({
                padding: '10px',
                borderBottom: '1px solid #eee',
                fontWeight: 'bold'
            })
            .text(this.options.title);
        
        var content = $('<div class="modal-content">')
            .css({
                padding: '10px',
                height: this.options.height - 80
            })
            .html(this.options.content);
        
        this.element.append(header, content);
        
        if (this.options.closable) {
            var closeBtn = $('<button>×</button>')
                .css({
                    position: 'absolute',
                    top: '5px',
                    right: '10px',
                    border: 'none',
                    background: 'none',
                    fontSize: '18px',
                    cursor: 'pointer'
                })
                .click(this.hide.bind(this));
            header.append(closeBtn);
        }
        
        $('body').append(this.element);
        return this;
    };
    
    Modal.prototype.show = function() {
        if (!this.element) {
            this.create();
        }
        this.element.fadeIn();
        this.isVisible = true;
        return this;
    };
    
    Modal.prototype.hide = function() {
        if (this.element) {
            this.element.fadeOut();
        }
        this.isVisible = false;
        return this;
    };
    
    Modal.prototype.destroy = function() {
        if (this.element) {
            this.element.remove();
            this.element = null;
        }
        this.isVisible = false;
        return this;
    };
    
    return Modal;
});

// 使用Modal组件
require(['components/modal'], function(Modal) {
    var modal = new Modal({
        title: 'Welcome',
        content: '<p>Welcome to our application!</p>',
        width: 500,
        height: 200
    });
    
    $('#open-modal').click(function() {
        modal.show();
    });
});

3. 数据模型

// models/user.js
define(['utils/ajax'], function(ajax) {
    function User(data) {
        this.id = data.id || null;
        this.name = data.name || '';
        this.email = data.email || '';
        this.createdAt = data.createdAt || null;
    }
    
    User.prototype.save = function() {
        var url = this.id ? '/api/users/' + this.id : '/api/users';
        var method = this.id ? 'PUT' : 'POST';
        
        return ajax.request({
            url: url,
            method: method,
            data: this.toJSON()
        }).then(function(response) {
            this.id = response.id;
            return this;
        }.bind(this));
    };
    
    User.prototype.destroy = function() {
        if (!this.id) {
            return Promise.reject(new Error('Cannot delete user without ID'));
        }
        
        return ajax.request({
            url: '/api/users/' + this.id,
            method: 'DELETE'
        });
    };
    
    User.prototype.toJSON = function() {
        return {
            id: this.id,
            name: this.name,
            email: this.email,
            createdAt: this.createdAt
        };
    };
    
    // 静态方法
    User.findById = function(id) {
        return ajax.request({
            url: '/api/users/' + id,
            method: 'GET'
        }).then(function(data) {
            return new User(data);
        });
    };
    
    User.findAll = function() {
        return ajax.request({
            url: '/api/users',
            method: 'GET'
        }).then(function(data) {
            return data.map(function(userData) {
                return new User(userData);
            });
        });
    };
    
    return User;
});

AMD vs 其他模块系统

1. AMD vs CommonJS

// CommonJS (同步)
var math = require('./math');
var result = math.add(2, 3);

// AMD (异步)
define(['math'], function(math) {
    var result = math.add(2, 3);
});

2. AMD vs ES模块

// ES模块
import { add } from './math.js';
const result = add(2, 3);

// AMD
define(['math'], function(math) {
    var result = math.add(2, 3);
});

优缺点分析

优点

  • 异步加载: 不阻塞页面渲染,提升用户体验
  • 依赖管理: 自动处理模块依赖顺序
  • 浏览器原生: 无需预编译,直接在浏览器中运行
  • 插件系统: 支持加载各种资源类型
  • 配置灵活: 丰富的配置选项满足不同需求

缺点

  • 语法复杂: 相比ES模块语法较为繁琐
  • 回调嵌套: 复杂依赖可能导致回调地狱
  • 性能开销: 运行时依赖解析有一定性能成本
  • 调试困难: 异步加载增加调试复杂度
  • 生态衰落: 现代开发更倾向于ES模块

总结

AMD作为早期的模块化解决方案,为浏览器端JavaScript模块化开发做出了重要贡献:

  • 🎯 适用场景: 需要浏览器端异步加载的传统项目
  • 🎯 学习价值: 理解模块化发展历程和设计思想
  • 🎯 现状: 逐渐被ES模块和现代构建工具取代

虽然AMD在现代开发中使用较少,但理解其设计原理对于掌握JavaScript模块化发展历程仍然很有价值。


下一章: RequireJS详解

RequireJS详解

RequireJS是AMD(Asynchronous Module Definition)规范最流行的实现,也是早期浏览器端模块化开发的重要工具。本章将深入探讨RequireJS的使用方法、配置选项、最佳实践和实际应用。

RequireJS简介

RequireJS是一个JavaScript文件和模块加载器,具有以下特点:

  • 异步加载: 异步加载JavaScript文件,提升页面性能
  • 依赖管理: 自动解析和加载模块依赖
  • 跨浏览器: 支持所有现代浏览器
  • 插件系统: 丰富的插件生态,支持加载各种资源
  • 优化工具: 提供r.js优化器进行代码合并和压缩

安装和基本使用

1. 引入RequireJS

<!DOCTYPE html>
<html>
<head>
    <title>RequireJS Demo</title>
</head>
<body>
    <div id="app"></div>
    
    <!-- 引入RequireJS -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js"></script>
    
    <!-- 或者从本地引入 -->
    <!-- <script src="js/lib/require.js"></script> -->
    
    <!-- 指定主模块 -->
    <script src="js/lib/require.js" data-main="js/app"></script>
</body>
</html>

2. 基本模块定义

// js/utils/math.js
define(function() {
    'use strict';
    
    return {
        add: function(a, b) {
            return a + b;
        },
        
        subtract: function(a, b) {
            return a - b;
        },
        
        multiply: function(a, b) {
            return a * b;
        },
        
        divide: function(a, b) {
            if (b === 0) {
                throw new Error('Division by zero');
            }
            return a / b;
        }
    };
});

3. 使用模块

// js/app.js - 主应用文件
require(['utils/math'], function(math) {
    console.log('2 + 3 =', math.add(2, 3));
    console.log('10 - 4 =', math.subtract(10, 4));
    console.log('5 * 6 =', math.multiply(5, 6));
    console.log('15 / 3 =', math.divide(15, 3));
});

配置系统

1. 基础配置

// js/config.js
require.config({
    // 设置基础路径
    baseUrl: 'js',
    
    // 路径映射
    paths: {
        // 第三方库
        'jquery': 'lib/jquery-3.6.0.min',
        'underscore': 'lib/underscore-1.13.1.min',
        'backbone': 'lib/backbone-1.4.0.min',
        
        // 应用模块
        'app': 'app',
        'models': 'app/models',
        'views': 'app/views',
        'collections': 'app/collections',
        'templates': '../templates'
    },
    
    // 超时设置(秒)
    waitSeconds: 30,
    
    // URL查询参数(用于缓存破坏)
    urlArgs: 'v=' + (new Date()).getTime()
});

// 启动应用
require(['app/main'], function(main) {
    main.init();
});

2. Shim配置(兼容非AMD库)

require.config({
    baseUrl: 'js',
    
    paths: {
        'jquery': 'lib/jquery-3.6.0.min',
        'underscore': 'lib/underscore-1.13.1.min',
        'backbone': 'lib/backbone-1.4.0.min',
        'bootstrap': 'lib/bootstrap-4.6.0.min',
        'd3': 'lib/d3-7.3.0.min'
    },
    
    // 为非AMD库配置依赖和导出
    shim: {
        'underscore': {
            exports: '_'
        },
        'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone'
        },
        'bootstrap': {
            deps: ['jquery']
        },
        'd3': {
            exports: 'd3'
        }
    }
});

3. 高级配置选项

require.config({
    baseUrl: 'js',
    
    // 映射配置 - 为不同上下文使用不同版本
    map: {
        '*': {
            'jquery': 'jquery-3.6.0'
        },
        'legacy-module': {
            'jquery': 'jquery-1.12.4'
        }
    },
    
    // 包配置
    packages: [
        {
            name: 'dojo',
            location: 'lib/dojo',
            main: 'main'
        }
    ],
    
    // 配置文件
    config: {
        'app/config': {
            apiUrl: 'https://api.example.com',
            debug: true
        }
    },
    
    // 强制定义
    enforceDefine: true,
    
    // Node.js适配
    nodeIdCompat: true
});

插件系统

1. text插件 - 加载文本文件

// 安装:下载text.js到js/lib/目录

// 配置
require.config({
    paths: {
        'text': 'lib/text'
    }
});

// 使用text插件加载HTML模板
define(['text!templates/user.html'], function(userTemplate) {
    return {
        render: function(user) {
            var html = userTemplate
                .replace('{{name}}', user.name)
                .replace('{{email}}', user.email);
            return html;
        }
    };
});

2. domReady插件 - DOM就绪

// 使用domReady插件
require(['domReady!'], function() {
    // DOM已经准备就绪
    console.log('DOM is ready');
    
    // 可以安全地操作DOM
    document.getElementById('app').innerHTML = 'Hello, World!';
});

// 或者在模块中使用
define(['domReady!', 'jquery'], function(doc, $) {
    // DOM和jQuery都已准备就绪
    return {
        init: function() {
            $('#app').text('Application initialized');
        }
    };
});

3. i18n插件 - 国际化

// 配置i18n
require.config({
    config: {
        i18n: {
            locale: 'zh-cn' // 设置默认语言
        }
    }
});

// 创建语言文件
// nls/messages.js
define({
    root: {
        greeting: 'Hello',
        goodbye: 'Goodbye'
    },
    'zh-cn': true,
    'en-us': true
});

// nls/zh-cn/messages.js
define({
    greeting: '你好',
    goodbye: '再见'
});

// 使用i18n
define(['i18n!nls/messages'], function(messages) {
    return {
        greet: function() {
            return messages.greeting;
        }
    };
});

4. css插件 - 加载CSS

// 使用css插件
define(['css!styles/main.css'], function() {
    // CSS已经加载
    return {
        init: function() {
            console.log('Styles loaded');
        }
    };
});

5. 自定义插件

// plugins/json.js - 加载JSON文件的插件
define(function() {
    return {
        load: function(name, req, onload, config) {
            if (config.isBuild) {
                // 构建时的处理
                onload(null);
                return;
            }
            
            var xhr = new XMLHttpRequest();
            xhr.open('GET', name + '.json', true);
            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        try {
                            var data = JSON.parse(xhr.responseText);
                            onload(data);
                        } catch (e) {
                            onload.error(e);
                        }
                    } else {
                        onload.error(new Error('HTTP ' + xhr.status));
                    }
                }
            };
            xhr.send();
        }
    };
});

// 使用自定义插件
define(['json!data/config'], function(config) {
    console.log('Loaded config:', config);
});

实际项目应用

1. 大型单页应用架构

// js/app/main.js - 应用入口
define([
    'jquery',
    'backbone',
    'app/router',
    'app/models/user',
    'app/views/layout'
], function($, Backbone, Router, User, LayoutView) {
    
    var App = {
        Models: {},
        Views: {},
        Collections: {},
        Router: null,
        
        init: function() {
            // 初始化布局
            this.layout = new LayoutView({
                el: '#app'
            });
            
            // 初始化路由
            this.router = new Router();
            
            // 启动应用
            this.start();
        },
        
        start: function() {
            // 渲染布局
            this.layout.render();
            
            // 启动Backbone路由
            Backbone.history.start({
                pushState: true,
                root: '/'
            });
            
            console.log('Application started');
        }
    };
    
    return App;
});
// js/app/router.js - 路由器
define([
    'backbone',
    'app/views/home',
    'app/views/users',
    'app/views/profile'
], function(Backbone, HomeView, UsersView, ProfileView) {
    
    var Router = Backbone.Router.extend({
        routes: {
            '': 'home',
            'users': 'users',
            'users/:id': 'profile',
            '*notfound': 'notFound'
        },
        
        home: function() {
            var view = new HomeView();
            this.showView(view);
        },
        
        users: function() {
            var view = new UsersView();
            this.showView(view);
        },
        
        profile: function(id) {
            var view = new ProfileView({ userId: id });
            this.showView(view);
        },
        
        notFound: function() {
            console.log('404 - Page not found');
        },
        
        showView: function(view) {
            // 清理之前的视图
            if (this.currentView) {
                this.currentView.remove();
            }
            
            // 显示新视图
            this.currentView = view;
            view.render();
        }
    });
    
    return Router;
});

2. 模块化组件系统

// js/components/base-component.js - 基础组件
define(['jquery'], function($) {
    
    function BaseComponent(options) {
        this.options = $.extend(true, {}, this.defaults, options);
        this.element = null;
        this.initialized = false;
        
        this.init();
    }
    
    BaseComponent.prototype = {
        defaults: {},
        
        init: function() {
            this.createElement();
            this.bindEvents();
            this.initialized = true;
        },
        
        createElement: function() {
            throw new Error('createElement must be implemented');
        },
        
        bindEvents: function() {
            // 子类可以重写
        },
        
        render: function() {
            throw new Error('render must be implemented');
        },
        
        destroy: function() {
            if (this.element) {
                this.element.remove();
            }
            this.initialized = false;
        }
    };
    
    return BaseComponent;
});

// js/components/data-table.js - 数据表组件
define([
    'components/base-component',
    'text!templates/data-table.html'
], function(BaseComponent, tableTemplate) {
    
    function DataTable(options) {
        BaseComponent.call(this, options);
    }
    
    // 继承BaseComponent
    DataTable.prototype = Object.create(BaseComponent.prototype);
    DataTable.prototype.constructor = DataTable;
    
    DataTable.prototype.defaults = {
        columns: [],
        data: [],
        sortable: true,
        pageable: true,
        pageSize: 10
    };
    
    DataTable.prototype.createElement = function() {
        this.element = $(tableTemplate);
    };
    
    DataTable.prototype.bindEvents = function() {
        var self = this;
        
        // 排序事件
        if (this.options.sortable) {
            this.element.on('click', 'th[data-sortable]', function() {
                var column = $(this).data('column');
                self.sort(column);
            });
        }
        
        // 分页事件
        if (this.options.pageable) {
            this.element.on('click', '.pagination a', function(e) {
                e.preventDefault();
                var page = $(this).data('page');
                self.goToPage(page);
            });
        }
    };
    
    DataTable.prototype.render = function() {
        this.renderHeader();
        this.renderBody();
        this.renderPagination();
        return this;
    };
    
    DataTable.prototype.renderHeader = function() {
        var headerHtml = '<tr>';
        this.options.columns.forEach(function(column) {
            var sortable = column.sortable !== false ? 'data-sortable data-column="' + column.field + '"' : '';
            headerHtml += '<th ' + sortable + '>' + column.title + '</th>';
        });
        headerHtml += '</tr>';
        
        this.element.find('thead').html(headerHtml);
    };
    
    DataTable.prototype.renderBody = function() {
        var self = this;
        var bodyHtml = '';
        
        var data = this.getCurrentPageData();
        data.forEach(function(row) {
            bodyHtml += '<tr>';
            self.options.columns.forEach(function(column) {
                var value = row[column.field];
                if (column.formatter) {
                    value = column.formatter(value, row);
                }
                bodyHtml += '<td>' + value + '</td>';
            });
            bodyHtml += '</tr>';
        });
        
        this.element.find('tbody').html(bodyHtml);
    };
    
    DataTable.prototype.getCurrentPageData = function() {
        if (!this.options.pageable) {
            return this.options.data;
        }
        
        var start = (this.currentPage - 1) * this.options.pageSize;
        var end = start + this.options.pageSize;
        return this.options.data.slice(start, end);
    };
    
    DataTable.prototype.sort = function(column) {
        // 排序逻辑
        console.log('Sorting by:', column);
    };
    
    DataTable.prototype.goToPage = function(page) {
        this.currentPage = page;
        this.render();
    };
    
    return DataTable;
});

3. API集成

// js/services/api.js - API服务
define(['jquery'], function($) {
    
    var Api = {
        baseUrl: '/api/v1',
        
        request: function(options) {
            var defaults = {
                url: '',
                method: 'GET',
                data: null,
                headers: {
                    'Content-Type': 'application/json'
                },
                timeout: 30000
            };
            
            var config = $.extend(true, {}, defaults, options);
            config.url = this.baseUrl + config.url;
            
            // 添加认证头
            var token = this.getAuthToken();
            if (token) {
                config.headers['Authorization'] = 'Bearer ' + token;
            }
            
            return $.ajax(config).fail(function(xhr, status, error) {
                console.error('API request failed:', error);
                
                // 处理认证失败
                if (xhr.status === 401) {
                    Api.handleAuthError();
                }
            });
        },
        
        get: function(url, params) {
            return this.request({
                url: url,
                method: 'GET',
                data: params
            });
        },
        
        post: function(url, data) {
            return this.request({
                url: url,
                method: 'POST',
                data: JSON.stringify(data)
            });
        },
        
        put: function(url, data) {
            return this.request({
                url: url,
                method: 'PUT',
                data: JSON.stringify(data)
            });
        },
        
        delete: function(url) {
            return this.request({
                url: url,
                method: 'DELETE'
            });
        },
        
        getAuthToken: function() {
            return localStorage.getItem('auth_token');
        },
        
        setAuthToken: function(token) {
            localStorage.setItem('auth_token', token);
        },
        
        clearAuthToken: function() {
            localStorage.removeItem('auth_token');
        },
        
        handleAuthError: function() {
            this.clearAuthToken();
            window.location.href = '/login';
        }
    };
    
    return Api;
});

// js/models/user.js - 用户模型
define(['services/api'], function(Api) {
    
    function User(data) {
        this.id = data.id || null;
        this.name = data.name || '';
        this.email = data.email || '';
        this.role = data.role || 'user';
        this.createdAt = data.createdAt || null;
    }
    
    User.prototype.save = function() {
        var url = this.id ? '/users/' + this.id : '/users';
        var method = this.id ? 'put' : 'post';
        
        return Api[method](url, this.toJSON()).then(function(response) {
            if (!this.id) {
                this.id = response.id;
            }
            return this;
        }.bind(this));
    };
    
    User.prototype.destroy = function() {
        if (!this.id) {
            return Promise.reject(new Error('Cannot delete user without ID'));
        }
        
        return Api.delete('/users/' + this.id);
    };
    
    User.prototype.toJSON = function() {
        return {
            id: this.id,
            name: this.name,
            email: this.email,
            role: this.role
        };
    };
    
    // 静态方法
    User.findById = function(id) {
        return Api.get('/users/' + id).then(function(data) {
            return new User(data);
        });
    };
    
    User.findAll = function(params) {
        return Api.get('/users', params).then(function(data) {
            return data.map(function(userData) {
                return new User(userData);
            });
        });
    };
    
    return User;
});

性能优化

1. r.js构建优化

// build.js - r.js构建配置
({
    appDir: '../src',
    baseUrl: 'js',
    dir: '../dist',
    
    // 模块配置
    modules: [
        {
            name: 'app/main',
            include: [
                'app/router',
                'app/models/user',
                'app/views/layout'
            ]
        }
    ],
    
    // 路径配置
    paths: {
        'jquery': 'lib/jquery-3.6.0.min',
        'underscore': 'lib/underscore-1.13.1.min',
        'backbone': 'lib/backbone-1.4.0.min'
    },
    
    // 文件排除
    fileExclusionRegExp: /^(r|build)\.js$/,
    
    // 优化选项
    optimize: 'uglify2',
    preserveLicenseComments: false,
    generateSourceMaps: true,
    
    // 移除console.log
    uglify2: {
        output: {
            beautify: false
        },
        compress: {
            drop_console: true
        }
    }
})

2. 运行时优化

// 懒加载策略
define(['jquery'], function($) {
    
    var LazyLoader = {
        cache: {},
        
        loadModule: function(moduleName) {
            if (this.cache[moduleName]) {
                return Promise.resolve(this.cache[moduleName]);
            }
            
            return new Promise(function(resolve, reject) {
                require([moduleName], function(module) {
                    this.cache[moduleName] = module;
                    resolve(module);
                }.bind(this), reject);
            }.bind(this));
        },
        
        preloadModules: function(moduleNames) {
            var promises = moduleNames.map(function(name) {
                return this.loadModule(name);
            }.bind(this));
            
            return Promise.all(promises);
        }
    };
    
    return LazyLoader;
});

// 代码分割
require.config({
    bundles: {
        'bundle-admin': ['app/admin/dashboard', 'app/admin/users'],
        'bundle-public': ['app/public/home', 'app/public/about']
    }
});

调试和测试

1. 开发环境配置

// js/config-dev.js
require.config({
    baseUrl: 'js',
    
    paths: {
        'jquery': 'lib/jquery-3.6.0',  // 使用未压缩版本
        'underscore': 'lib/underscore-1.13.1',
        'backbone': 'lib/backbone-1.4.0'
    },
    
    // 禁用缓存
    urlArgs: 'bust=' + (new Date()).getTime(),
    
    // 启用详细错误
    enforceDefine: true,
    
    // 缩短超时以快速发现问题
    waitSeconds: 7
});

// 全局错误处理
requirejs.onError = function(err) {
    console.error('RequireJS Error:', err);
    console.error('Error type:', err.requireType);
    console.error('Error modules:', err.requireModules);
    
    if (err.requireType === 'timeout') {
        console.error('Modules that timed out:', err.requireModules);
    }
};

2. 单元测试

// test/test-main.js - 测试配置
require.config({
    baseUrl: '../js',
    
    paths: {
        'jasmine': '../test/lib/jasmine-2.9.1/jasmine',
        'jasmine-html': '../test/lib/jasmine-2.9.1/jasmine-html',
        'jasmine-boot': '../test/lib/jasmine-2.9.1/boot',
        
        // 应用模块
        'utils': 'utils',
        'models': 'app/models'
    },
    
    shim: {
        'jasmine-html': ['jasmine'],
        'jasmine-boot': ['jasmine', 'jasmine-html']
    }
});

// 加载测试
require(['jasmine-boot'], function() {
    require([
        'test/utils/math-test',
        'test/models/user-test'
    ], function() {
        // 启动测试
        window.onload();
    });
});

// test/utils/math-test.js
define(['utils/math'], function(math) {
    describe('Math Utils', function() {
        it('should add two numbers', function() {
            expect(math.add(2, 3)).toBe(5);
        });
        
        it('should subtract two numbers', function() {
            expect(math.subtract(5, 3)).toBe(2);
        });
        
        it('should throw error on division by zero', function() {
            expect(function() {
                math.divide(10, 0);
            }).toThrow();
        });
    });
});

总结

RequireJS作为AMD规范的主要实现,提供了完整的模块化解决方案:

  • 成熟稳定: 经过大量项目验证的可靠工具

  • 功能丰富: 完整的配置系统和插件生态

  • 工具完善: 提供构建优化和调试支持

  • 文档详细: 有详细的文档和社区支持

  • 语法复杂: 相比现代模块系统语法较为复杂

  • 体积较大: 运行时库增加了页面负担

  • 生态衰落: 现代开发更倾向于ES模块和构建工具

虽然RequireJS在现代开发中逐渐被新技术取代,但它为JavaScript模块化发展做出了重要贡献,理解其原理和使用方法对于掌握前端发展历程很有价值。


下一章: UMD通用模块

UMD通用模块定义

UMD(Universal Module Definition,通用模块定义)是一种JavaScript模块模式,旨在创建可以在多种环境中工作的模块。它结合了CommonJS、AMD和全局变量模式的特点,使模块能够在Node.js、浏览器和AMD加载器中都能正常工作。

什么是UMD

UMD是一种模块包装器模式,具有以下特点:

  • 跨平台兼容: 同一个模块可以在不同环境中使用
  • 自动检测: 自动检测当前环境并选择合适的模块系统
  • 向后兼容: 支持旧版本浏览器和旧的模块系统
  • 无依赖: 不需要额外的加载器或构建工具
  • 灵活性: 可以根据需要定制检测逻辑

UMD基本模式

1. 标准UMD模式

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD环境
        define(['dependency1', 'dependency2'], factory);
    } else if (typeof module === 'object' && module.exports) {
        // Node.js/CommonJS环境
        module.exports = factory(require('dependency1'), require('dependency2'));
    } else {
        // 浏览器全局变量环境
        root.MyModule = factory(root.Dependency1, root.Dependency2);
    }
}(typeof self !== 'undefined' ? self : this, function (dependency1, dependency2) {
    'use strict';
    
    // 模块实现
    function MyModule() {
        // 构造函数
    }
    
    MyModule.prototype.method1 = function() {
        return 'method1 result';
    };
    
    MyModule.prototype.method2 = function() {
        return dependency1.someFunction() + dependency2.someOtherFunction();
    };
    
    // 返回模块
    return MyModule;
}));

2. 简化版UMD

(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
    (global = global || self, global.MyModule = factory());
}(this, function () {
    'use strict';
    
    // 模块实现
    var MyModule = {
        version: '1.0.0',
        
        init: function() {
            console.log('MyModule initialized');
        },
        
        destroy: function() {
            console.log('MyModule destroyed');
        }
    };
    
    return MyModule;
}));

3. 无依赖UMD模式

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define([], factory);
    } else if (typeof exports === 'object') {
        // Node.js
        module.exports = factory();
    } else {
        // 浏览器全局变量
        root.Utils = factory();
    }
}(typeof self !== 'undefined' ? self : this, function () {
    
    var Utils = {
        // 类型检查工具
        isArray: Array.isArray || function(obj) {
            return Object.prototype.toString.call(obj) === '[object Array]';
        },
        
        isObject: function(obj) {
            return obj !== null && typeof obj === 'object' && !this.isArray(obj);
        },
        
        isFunction: function(obj) {
            return typeof obj === 'function';
        },
        
        // 对象工具
        extend: function(target) {
            var sources = Array.prototype.slice.call(arguments, 1);
            sources.forEach(function(source) {
                for (var key in source) {
                    if (source.hasOwnProperty(key)) {
                        target[key] = source[key];
                    }
                }
            });
            return target;
        },
        
        clone: function(obj) {
            if (!this.isObject(obj)) return obj;
            var cloned = {};
            for (var key in obj) {
                if (obj.hasOwnProperty(key)) {
                    cloned[key] = this.isObject(obj[key]) ? this.clone(obj[key]) : obj[key];
                }
            }
            return cloned;
        },
        
        // 数组工具
        unique: function(array) {
            var result = [];
            for (var i = 0; i < array.length; i++) {
                if (result.indexOf(array[i]) === -1) {
                    result.push(array[i]);
                }
            }
            return result;
        },
        
        // 字符串工具
        camelCase: function(str) {
            return str.replace(/-([a-z])/g, function(match, letter) {
                return letter.toUpperCase();
            });
        },
        
        kebabCase: function(str) {
            return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
        }
    };
    
    return Utils;
}));

实际应用示例

1. 数学工具库

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory();
    } else {
        root.MathUtils = factory();
    }
}(typeof self !== 'undefined' ? self : this, function () {
    'use strict';
    
    var MathUtils = {
        // 常量
        PI: Math.PI,
        E: Math.E,
        
        // 基础运算
        add: function(a, b) {
            return a + b;
        },
        
        subtract: function(a, b) {
            return a - b;
        },
        
        multiply: function(a, b) {
            return a * b;
        },
        
        divide: function(a, b) {
            if (b === 0) throw new Error('Division by zero');
            return a / b;
        },
        
        // 高级运算
        power: function(base, exponent) {
            return Math.pow(base, exponent);
        },
        
        sqrt: function(n) {
            if (n < 0) throw new Error('Cannot calculate square root of negative number');
            return Math.sqrt(n);
        },
        
        factorial: function(n) {
            if (n < 0) throw new Error('Cannot calculate factorial of negative number');
            if (n === 0 || n === 1) return 1;
            var result = 1;
            for (var i = 2; i <= n; i++) {
                result *= i;
            }
            return result;
        },
        
        // 几何计算
        circleArea: function(radius) {
            return this.PI * radius * radius;
        },
        
        circleCircumference: function(radius) {
            return 2 * this.PI * radius;
        },
        
        rectangleArea: function(width, height) {
            return width * height;
        },
        
        triangleArea: function(base, height) {
            return 0.5 * base * height;
        },
        
        // 统计函数
        average: function(numbers) {
            if (!Array.isArray(numbers) || numbers.length === 0) {
                throw new Error('Input must be a non-empty array');
            }
            var sum = numbers.reduce(function(acc, num) {
                return acc + num;
            }, 0);
            return sum / numbers.length;
        },
        
        median: function(numbers) {
            if (!Array.isArray(numbers) || numbers.length === 0) {
                throw new Error('Input must be a non-empty array');
            }
            var sorted = numbers.slice().sort(function(a, b) { return a - b; });
            var middle = Math.floor(sorted.length / 2);
            
            if (sorted.length % 2 === 0) {
                return (sorted[middle - 1] + sorted[middle]) / 2;
            } else {
                return sorted[middle];
            }
        },
        
        max: function(numbers) {
            if (!Array.isArray(numbers) || numbers.length === 0) {
                throw new Error('Input must be a non-empty array');
            }
            return Math.max.apply(Math, numbers);
        },
        
        min: function(numbers) {
            if (!Array.isArray(numbers) || numbers.length === 0) {
                throw new Error('Input must be a non-empty array');
            }
            return Math.min.apply(Math, numbers);
        }
    };
    
    return MathUtils;
}));

2. 事件发射器

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory();
    } else {
        root.EventEmitter = factory();
    }
}(typeof self !== 'undefined' ? self : this, function () {
    'use strict';
    
    function EventEmitter() {
        this._events = {};
        this._maxListeners = 10;
    }
    
    EventEmitter.prototype = {
        constructor: EventEmitter,
        
        // 添加事件监听器
        on: function(event, listener) {
            if (typeof listener !== 'function') {
                throw new TypeError('Listener must be a function');
            }
            
            if (!this._events[event]) {
                this._events[event] = [];
            }
            
            this._events[event].push(listener);
            
            // 检查监听器数量
            if (this._events[event].length > this._maxListeners) {
                console.warn('MaxListenersExceededWarning: Possible memory leak detected. ' +
                    this._events[event].length + ' ' + event + ' listeners added.');
            }
            
            return this;
        },
        
        // 添加一次性事件监听器
        once: function(event, listener) {
            if (typeof listener !== 'function') {
                throw new TypeError('Listener must be a function');
            }
            
            var self = this;
            function onceWrapper() {
                listener.apply(this, arguments);
                self.removeListener(event, onceWrapper);
            }
            
            onceWrapper.listener = listener;
            return this.on(event, onceWrapper);
        },
        
        // 移除事件监听器
        removeListener: function(event, listener) {
            if (typeof listener !== 'function') {
                throw new TypeError('Listener must be a function');
            }
            
            if (!this._events[event]) {
                return this;
            }
            
            var listeners = this._events[event];
            for (var i = listeners.length - 1; i >= 0; i--) {
                if (listeners[i] === listener || listeners[i].listener === listener) {
                    listeners.splice(i, 1);
                    break;
                }
            }
            
            if (listeners.length === 0) {
                delete this._events[event];
            }
            
            return this;
        },
        
        // 移除所有监听器
        removeAllListeners: function(event) {
            if (event) {
                delete this._events[event];
            } else {
                this._events = {};
            }
            return this;
        },
        
        // 触发事件
        emit: function(event) {
            if (!this._events[event]) {
                return false;
            }
            
            var listeners = this._events[event].slice();
            var args = Array.prototype.slice.call(arguments, 1);
            
            for (var i = 0; i < listeners.length; i++) {
                try {
                    listeners[i].apply(this, args);
                } catch (error) {
                    console.error('Error in event listener:', error);
                }
            }
            
            return true;
        },
        
        // 获取监听器列表
        listeners: function(event) {
            return this._events[event] ? this._events[event].slice() : [];
        },
        
        // 获取监听器数量
        listenerCount: function(event) {
            return this._events[event] ? this._events[event].length : 0;
        },
        
        // 设置最大监听器数量
        setMaxListeners: function(n) {
            if (typeof n !== 'number' || n < 0) {
                throw new TypeError('n must be a non-negative number');
            }
            this._maxListeners = n;
            return this;
        },
        
        // 获取最大监听器数量
        getMaxListeners: function() {
            return this._maxListeners;
        }
    };
    
    // 静态方法
    EventEmitter.listenerCount = function(emitter, event) {
        return emitter.listenerCount(event);
    };
    
    return EventEmitter;
}));

3. HTTP客户端

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory();
    } else {
        root.HttpClient = factory();
    }
}(typeof self !== 'undefined' ? self : this, function () {
    'use strict';
    
    // 检测环境
    var isNode = typeof module === 'object' && module.exports;
    var XMLHttpRequest = isNode ? null : (
        typeof XMLHttpRequest !== 'undefined' ? XMLHttpRequest :
        typeof ActiveXObject !== 'undefined' ? function() {
            return new ActiveXObject('Microsoft.XMLHTTP');
        } : null
    );
    
    function HttpClient(baseURL, defaultOptions) {
        this.baseURL = baseURL || '';
        this.defaultOptions = defaultOptions || {};
        this.interceptors = {
            request: [],
            response: []
        };
    }
    
    HttpClient.prototype = {
        constructor: HttpClient,
        
        // 请求方法
        request: function(options) {
            var self = this;
            
            // 合并配置
            var config = this._mergeConfig(options);
            
            // 应用请求拦截器
            config = this._applyRequestInterceptors(config);
            
            if (isNode) {
                return this._nodeRequest(config);
            } else {
                return this._browserRequest(config);
            }
        },
        
        // GET请求
        get: function(url, options) {
            return this.request(this._extend({ method: 'GET', url: url }, options));
        },
        
        // POST请求
        post: function(url, data, options) {
            return this.request(this._extend({ method: 'POST', url: url, data: data }, options));
        },
        
        // PUT请求
        put: function(url, data, options) {
            return this.request(this._extend({ method: 'PUT', url: url, data: data }, options));
        },
        
        // DELETE请求
        delete: function(url, options) {
            return this.request(this._extend({ method: 'DELETE', url: url }, options));
        },
        
        // 浏览器环境请求
        _browserRequest: function(config) {
            var self = this;
            
            return new Promise(function(resolve, reject) {
                var xhr = new XMLHttpRequest();
                
                xhr.open(config.method, self._buildURL(config.url), true);
                
                // 设置请求头
                if (config.headers) {
                    for (var header in config.headers) {
                        xhr.setRequestHeader(header, config.headers[header]);
                    }
                }
                
                // 设置超时
                if (config.timeout) {
                    xhr.timeout = config.timeout;
                }
                
                xhr.onreadystatechange = function() {
                    if (xhr.readyState === 4) {
                        var response = {
                            data: self._parseResponse(xhr.responseText, xhr.getResponseHeader('Content-Type')),
                            status: xhr.status,
                            statusText: xhr.statusText,
                            headers: self._parseHeaders(xhr.getAllResponseHeaders()),
                            config: config
                        };
                        
                        response = self._applyResponseInterceptors(response);
                        
                        if (xhr.status >= 200 && xhr.status < 300) {
                            resolve(response);
                        } else {
                            reject(new Error('Request failed with status ' + xhr.status));
                        }
                    }
                };
                
                xhr.onerror = function() {
                    reject(new Error('Network Error'));
                };
                
                xhr.ontimeout = function() {
                    reject(new Error('Request Timeout'));
                };
                
                // 发送请求
                var data = config.data ? self._serializeData(config.data) : null;
                xhr.send(data);
            });
        },
        
        // Node.js环境请求
        _nodeRequest: function(config) {
            var self = this;
            
            return new Promise(function(resolve, reject) {
                var http = require('http');
                var https = require('https');
                var url = require('url');
                
                var parsedUrl = url.parse(self._buildURL(config.url));
                var isSecure = parsedUrl.protocol === 'https:';
                var client = isSecure ? https : http;
                
                var requestOptions = {
                    hostname: parsedUrl.hostname,
                    port: parsedUrl.port || (isSecure ? 443 : 80),
                    path: parsedUrl.path,
                    method: config.method,
                    headers: config.headers || {}
                };
                
                var req = client.request(requestOptions, function(res) {
                    var data = '';
                    
                    res.on('data', function(chunk) {
                        data += chunk;
                    });
                    
                    res.on('end', function() {
                        var response = {
                            data: self._parseResponse(data, res.headers['content-type']),
                            status: res.statusCode,
                            statusText: res.statusMessage,
                            headers: res.headers,
                            config: config
                        };
                        
                        response = self._applyResponseInterceptors(response);
                        
                        if (res.statusCode >= 200 && res.statusCode < 300) {
                            resolve(response);
                        } else {
                            reject(new Error('Request failed with status ' + res.statusCode));
                        }
                    });
                });
                
                req.on('error', function(error) {
                    reject(error);
                });
                
                if (config.timeout) {
                    req.setTimeout(config.timeout, function() {
                        req.abort();
                        reject(new Error('Request Timeout'));
                    });
                }
                
                // 发送数据
                if (config.data) {
                    req.write(self._serializeData(config.data));
                }
                
                req.end();
            });
        },
        
        // 添加拦截器
        addRequestInterceptor: function(interceptor) {
            this.interceptors.request.push(interceptor);
        },
        
        addResponseInterceptor: function(interceptor) {
            this.interceptors.response.push(interceptor);
        },
        
        // 辅助方法
        _mergeConfig: function(options) {
            return this._extend({}, this.defaultOptions, options);
        },
        
        _buildURL: function(url) {
            if (url.indexOf('http') === 0) {
                return url;
            }
            return this.baseURL + url;
        },
        
        _extend: function(target) {
            var sources = Array.prototype.slice.call(arguments, 1);
            sources.forEach(function(source) {
                for (var key in source) {
                    if (source.hasOwnProperty(key)) {
                        target[key] = source[key];
                    }
                }
            });
            return target;
        },
        
        _serializeData: function(data) {
            if (typeof data === 'string') return data;
            return JSON.stringify(data);
        },
        
        _parseResponse: function(data, contentType) {
            if (contentType && contentType.indexOf('application/json') !== -1) {
                try {
                    return JSON.parse(data);
                } catch (e) {
                    return data;
                }
            }
            return data;
        },
        
        _parseHeaders: function(headerStr) {
            var headers = {};
            if (!headerStr) return headers;
            
            headerStr.split('\r\n').forEach(function(line) {
                var parts = line.split(': ');
                if (parts.length === 2) {
                    headers[parts[0].toLowerCase()] = parts[1];
                }
            });
            
            return headers;
        },
        
        _applyRequestInterceptors: function(config) {
            var result = config;
            this.interceptors.request.forEach(function(interceptor) {
                result = interceptor(result);
            });
            return result;
        },
        
        _applyResponseInterceptors: function(response) {
            var result = response;
            this.interceptors.response.forEach(function(interceptor) {
                result = interceptor(result);
            });
            return result;
        }
    };
    
    // 静态方法
    HttpClient.create = function(baseURL, defaultOptions) {
        return new HttpClient(baseURL, defaultOptions);
    };
    
    return HttpClient;
}));

UMD变体和优化

1. 返回工厂函数的UMD

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(['jquery'], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory(require('jquery'));
    } else {
        root.MyPlugin = factory(root.jQuery);
    }
}(typeof self !== 'undefined' ? self : this, function ($) {
    'use strict';
    
    // 返回工厂函数而不是构造函数
    return function(options) {
        var defaults = {
            width: 300,
            height: 200,
            animation: true
        };
        
        var settings = $.extend({}, defaults, options);
        
        return {
            init: function() {
                console.log('Plugin initialized with settings:', settings);
            },
            
            destroy: function() {
                console.log('Plugin destroyed');
            },
            
            getSettings: function() {
                return settings;
            }
        };
    };
}));

2. 支持ES6模块的UMD

(function (global, factory) {
    if (typeof exports === 'object' && typeof module !== 'undefined') {
        // CommonJS
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        // AMD
        define(factory);
    } else {
        // 浏览器全局变量
        (global = global || self).MyModule = factory();
    }
}(this, function () {
    'use strict';
    
    class MyClass {
        constructor(options = {}) {
            this.options = Object.assign({
                debug: false,
                timeout: 5000
            }, options);
        }
        
        async fetchData(url) {
            try {
                const response = await fetch(url);
                return await response.json();
            } catch (error) {
                if (this.options.debug) {
                    console.error('Fetch error:', error);
                }
                throw error;
            }
        }
        
        processData(data) {
            return data.map(item => ({
                ...item,
                processed: true,
                timestamp: Date.now()
            }));
        }
    }
    
    // 同时支持类和工厂函数
    MyClass.create = function(options) {
        return new MyClass(options);
    };
    
    return MyClass;
}));

3. 懒加载UMD

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(['exports'], factory);
    } else if (typeof exports === 'object' && typeof module !== 'undefined') {
        factory(exports);
    } else {
        factory((root.LazyModule = {}));
    }
}(typeof self !== 'undefined' ? self : this, function (exports) {
    'use strict';
    
    var modules = {};
    var loading = {};
    
    function loadModule(name, factory) {
        if (!modules[name]) {
            modules[name] = {
                loaded: false,
                factory: factory,
                exports: null
            };
        }
    }
    
    function getModule(name) {
        var module = modules[name];
        if (!module) {
            throw new Error('Module not found: ' + name);
        }
        
        if (!module.loaded) {
            module.exports = module.factory();
            module.loaded = true;
        }
        
        return module.exports;
    }
    
    async function getModuleAsync(name) {
        if (loading[name]) {
            return loading[name];
        }
        
        if (modules[name] && modules[name].loaded) {
            return modules[name].exports;
        }
        
        loading[name] = new Promise(function(resolve) {
            setTimeout(function() {
                resolve(getModule(name));
                delete loading[name];
            }, 0);
        });
        
        return loading[name];
    }
    
    // 预定义一些模块
    loadModule('utils', function() {
        return {
            formatDate: function(date) {
                return date.toISOString().split('T')[0];
            },
            generateId: function() {
                return Math.random().toString(36).substr(2, 9);
            }
        };
    });
    
    loadModule('validator', function() {
        return {
            isEmail: function(email) {
                return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
            },
            isUrl: function(url) {
                try {
                    new URL(url);
                    return true;
                } catch {
                    return false;
                }
            }
        };
    });
    
    // 导出API
    exports.load = loadModule;
    exports.get = getModule;
    exports.getAsync = getModuleAsync;
    exports.list = function() {
        return Object.keys(modules);
    };
}));

构建工具支持

1. Webpack UMD输出

// webpack.config.js
module.exports = {
    entry: './src/index.js',
    output: {
        path: __dirname + '/dist',
        filename: 'my-library.js',
        library: 'MyLibrary',
        libraryTarget: 'umd',
        globalObject: 'typeof self !== "undefined" ? self : this'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                }
            }
        ]
    }
};

2. Rollup UMD输出

// rollup.config.js
import babel from '@rollup/plugin-babel';
import { terser } from 'rollup-plugin-terser';

export default {
    input: 'src/index.js',
    output: {
        file: 'dist/my-library.umd.js',
        format: 'umd',
        name: 'MyLibrary',
        globals: {
            'lodash': '_',
            'jquery': '$'
        }
    },
    external: ['lodash', 'jquery'],
    plugins: [
        babel({
            exclude: 'node_modules/**',
            babelHelpers: 'bundled'
        }),
        terser()
    ]
};

测试UMD模块

1. 多环境测试

// test/umd-test.js
(function() {
    'use strict';
    
    // 测试不同环境
    function testEnvironments() {
        var results = {
            amd: false,
            commonjs: false,
            global: false
        };
        
        // 模拟AMD环境
        if (typeof define === 'undefined') {
            global.define = function(deps, factory) {
                results.amd = typeof factory() === 'object';
            };
            global.define.amd = true;
            
            // 重新加载模块
            // 这里需要重新执行UMD模块代码
            
            delete global.define;
        }
        
        // 模拟CommonJS环境
        if (typeof module === 'undefined') {
            global.module = { exports: {} };
            global.exports = global.module.exports;
            
            // 重新加载模块
            // 这里需要重新执行UMD模块代码
            
            results.commonjs = typeof global.module.exports === 'object';
            
            delete global.module;
            delete global.exports;
        }
        
        // 测试全局变量环境
        // 重新加载模块
        // 这里需要重新执行UMD模块代码
        results.global = typeof global.MyModule === 'object';
        
        return results;
    }
    
    // 功能测试
    function testFunctionality(module) {
        var tests = {
            instantiation: false,
            methods: false,
            properties: false
        };
        
        try {
            var instance = new module();
            tests.instantiation = true;
            
            if (typeof instance.method1 === 'function') {
                tests.methods = true;
            }
            
            if (instance.hasOwnProperty('someProperty')) {
                tests.properties = true;
            }
        } catch (error) {
            console.error('Test error:', error);
        }
        
        return tests;
    }
    
    console.log('UMD Environment Tests:', testEnvironments());
    // console.log('UMD Functionality Tests:', testFunctionality(MyModule));
})();

优缺点分析

优点

  • 跨平台兼容: 同一代码可在多种环境中使用
  • 无构建依赖: 不需要额外的构建步骤
  • 向后兼容: 支持旧版本浏览器和模块系统
  • 灵活部署: 可以选择最适合的分发方式
  • 简单易用: 使用标准JavaScript语法

缺点

  • 代码冗余: 包装代码增加了文件大小
  • 复杂性: 模块包装逻辑较为复杂
  • 性能开销: 运行时环境检测有微小开销
  • 调试困难: 包装器可能影响调试体验
  • 维护负担: 需要考虑多种环境的兼容性

现代替代方案

1. 构建时转换

// 源码使用ES模块
export class MyClass {
    constructor() {
        // ...
    }
}

export default MyClass;

// 构建工具自动生成UMD版本

2. 条件导出

{
  "name": "my-package",
  "main": "./dist/index.cjs",
  "module": "./dist/index.mjs",
  "browser": "./dist/index.umd.js",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs",
      "browser": "./dist/index.umd.js"
    }
  }
}

总结

UMD作为过渡期的解决方案,解决了模块系统碎片化的问题:

  • 🎯 历史价值: 在模块标准化过程中发挥了重要作用
  • 🎯 实用性: 对于需要广泛兼容的库仍然有用
  • 🎯 现状: 逐渐被ES模块和现代构建工具取代

虽然UMD在现代开发中使用频率降低,但理解其设计原理对于构建跨平台JavaScript库和理解模块化发展历程仍然很有价值。随着ES模块的普及和构建工具的完善,UMD正在从“必需品“转向“兼容性选项“。


下一章: 模块化工具与实践

打包工具

打包工具是现代前端开发的核心基础设施,它们将开发时分散的模块文件整合成浏览器可以高效执行的代码包。随着JavaScript应用复杂度的增长,打包工具不仅要解决基本的文件合并问题,还要提供代码优化、资源管理、开发体验等全方位支持。

打包工具的核心作用

解决的基本问题

模块化开发与浏览器兼容性的矛盾

  • 开发环境:需要模块化组织代码,便于维护和协作
  • 生产环境:需要优化的代码包,减少网络请求和加载时间
  • 兼容性:支持不同的模块格式和运行环境

网络性能优化

  • 减少HTTP请求数量,合并多个小文件
  • 压缩代码体积,移除不必要的代码
  • 实现资源缓存策略,提高重复访问性能

主流工具特点

工具主要特点适用场景
Webpack配置灵活、生态丰富、功能全面大型复杂应用
Rollup专注ES模块、树摇优秀、输出干净库开发
Vite开发快速、原生ESM、零配置现代Web应用
Parcel零配置、开箱即用、自动化快速原型
esbuild极速构建、Go编写、性能优先大型项目构建

打包工作流程

标准构建流程

打包工具的工作可以分为以下几个关键阶段:

1. 入口分析 (Entry Resolution)

  • 确定构建的起始点,通常是一个或多个入口文件
  • 解析入口文件的绝对路径和基本信息
  • 建立入口文件与后续处理流程的关联

2. 依赖解析 (Dependency Resolution)

  • 分析每个模块的导入语句(importrequire等)
  • 递归解析所有依赖模块,构建完整的依赖关系图
  • 处理模块路径解析,包括相对路径、绝对路径、npm包等

3. 资源加载 (Asset Loading)

  • 读取各种类型的文件内容(JS、CSS、图片等)
  • 通过Loader系统处理非JavaScript资源
  • 将所有资源转换为模块系统可以理解的格式

4. 代码转换 (Code Transformation)

  • 使用Babel等转译器处理新语法特性
  • 应用各种代码转换插件
  • 处理模块格式转换(ES Module → CommonJS等)

5. 优化处理 (Optimization)

  • Tree Shaking:移除未使用的代码
  • 代码压缩和混淆
  • 资源压缩和优化

6. 代码生成 (Code Generation)

  • 将模块组织成最终的代码包
  • 生成模块运行时代码
  • 处理代码分割和动态导入

7. 资源输出 (Asset Emission)

  • 将生成的代码包写入文件系统
  • 生成Source Map文件
  • 输出构建报告和统计信息

打包工具核心特性

1. 代码合并 (Code Bundling)

概念:将多个分散的模块文件合并成少量的代码包,减少浏览器网络请求。

核心价值

  • 减少HTTP请求数量,提升加载性能
  • 解决模块间的依赖关系
  • 优化资源加载顺序

实现方式

  • 单文件打包:将所有代码合并为一个bundle.js
  • 多入口打包:针对不同页面生成对应的代码包
  • 分层打包:将应用代码和第三方库分别打包

2. Tree Shaking

概念:分析代码的导入导出关系,移除未被使用的代码(“死代码消除”)。

工作原理

  • 静态分析ES模块的import/export语句
  • 标记被使用的函数和变量
  • 移除未被标记的代码

优势

  • 显著减小bundle体积
  • 特别适用于工具库的按需引入
  • 对ES模块支持最佳

限制

  • 主要支持ES模块格式
  • 需要代码无副作用才能安全移除
  • 动态导入难以进行静态分析

3. 代码分割 (Code Splitting)

概念:将代码包拆分成多个较小的chunk,实现按需加载和并行下载。

分割策略

  • 入口分割:每个入口点生成独立的bundle
  • 动态分割:基于动态import()语句创建分割点
  • 公共代码分割:提取多个chunk间的共同依赖

收益

  • 减少初始加载时间
  • 提高缓存命中率
  • 支持懒加载优化

4. 外部模块处理 (External Dependencies)

概念:将某些依赖标记为外部模块,不打包到bundle中,而是在运行时从外部获取。

应用场景

  • CDN加载的库(如React、Vue)
  • 微前端架构中的共享依赖
  • 大型库的按需加载

配置方式

// webpack示例
externals: {
  'react': 'React',
  'react-dom': 'ReactDOM'
}

5. 模块格式兼容性

处理的格式

  • ES Modules (ESM):import/export语法
  • CommonJS (CJS):require/module.exports
  • AMD:异步模块定义
  • UMD:通用模块定义
  • SystemJS:动态模块加载器

转换策略

  • ES模块 → CommonJS:最常见的转换
  • CommonJS → ES模块:需要特殊处理
  • 混合格式:智能检测和适配

性能优化策略

1. 构建时优化

缓存机制

  • 文件级缓存:缓存未变更的模块
  • 增量构建:只重建发生变化的部分
  • 持久化缓存:跨构建保存缓存数据

并行处理

  • 模块解析并行化
  • 代码转换并行执行
  • 多核心构建优化

预编译优化

  • 依赖预构建(如Vite的预构建)
  • 第三方库预编译
  • 公共代码提前处理

2. 运行时优化

懒加载

  • 路由级代码分割
  • 组件级懒加载
  • 功能模块按需加载

缓存策略

  • 长效缓存:为稳定文件设置长期缓存
  • 内容哈希:基于内容生成文件名
  • 缓存失效控制:精确控制缓存更新

资源优化

  • 代码压缩和混淆
  • 图片压缩和格式优化
  • 字体文件优化

3. 开发体验优化

热模块替换 (HMR)

  • 保持应用状态的代码更新
  • 快速的开发反馈循环
  • CSS样式实时更新

开发服务器

  • 快速的冷启动
  • 内存文件系统
  • 自动重新加载

打包工具特性对比

根据在前面章节中介绍的各个工具,我们来进行全面的特性对比:

官方文档链接

新一代工具

工具官方网站状态说明
Rolldownrolldown.rs开发中Rust版Rollup,Vite团队开发
Turbopackturbo.build开发中Webpack继任者,Vercel开发
Biomebiomejs.dev活跃开发一体化工具链
Farmfarm-fe.github.io活跃开发Rust构建工具

综合能力对比

特性WebpackRollupViteParcelesbuildSWCBun
主要语言JavaScriptJavaScriptJavaScript + GoJavaScriptGoRustZig
首次发布2012201520202017202020192022
Node.js要求18.12.0+18.0.0+20.19+/22.12+16.0.0+无要求16.0.0+1.0.0+
构建速度中等非常快极快极快极快
开发服务器支持插件内置优秀内置优秀内置内置
HMR支持良好插件支持优秀优秀良好
Tree Shaking良好优秀良好(Rollup)良好基础基础良好
代码分割优秀优秀优秀良好支持基础良好
TypeScript配置插件内置内置内置内置内置
JSX支持配置插件内置内置内置内置内置
CSS处理配置插件内置内置插件基础
生产优化优秀优秀优秀良好良好良好良好
插件生态非常丰富丰富快速增长中等有限有限新兴
配置复杂度中等零配置
学习曲线陡峭适中平缓简单简单简单简单
社区支持优秀良好快速增长中等活跃活跃快速增长

详细功能特性对比

核心打包功能

功能特性WebpackRollupViteParcelesbuildSWCBun
Module Federation✅ 内置🔌 插件
Bundle Splitting✅ 优秀✅ 优秀✅ 优秀✅ 良好✅ 基础✅ 良好
Dynamic Imports
Tree Shaking✅ 良好✅ 优秀✅ 优秀✅ 良好✅ 基础✅ 基础✅ 良好
Dead Code Elimination
Scope Hoisting
Asset Processing✅ 强大🔌 插件✅ 内置✅ 内置🔌 插件✅ 基础

开发工具特性

功能特性WebpackRollupViteParcelesbuildSWCBun
Source Maps✅ 全支持✅ 全支持✅ 全支持✅ 全支持✅ 全支持✅ 全支持✅ 全支持
Watch Mode
Dev Server✅ 内置🔌 插件✅ 优秀✅ 优秀✅ 内置✅ 内置
Hot Reload🔌✅ 优秀✅ 优秀
Error Overlay🔌
Progress Reporting🔌

语言与框架支持

功能特性WebpackRollupViteParcelesbuildSWCBun
TypeScript🔌 ts-loader🔌 @rollup/plugin-typescript✅ 内置✅ 内置✅ 内置✅ 内置✅ 内置
JSX/TSX🔌 babel🔌 插件✅ 内置✅ 内置✅ 内置✅ 内置✅ 内置
Vue SFC🔌 vue-loader🔌 插件✅ 官方插件✅ 内置🔌🔌
Svelte🔌🔌🔌 官方🔌🔌
React Fast Refresh🔌

CSS 与样式处理

功能特性WebpackRollupViteParcelesbuildSWCBun
CSS Modules🔌🔌
PostCSS🔌🔌基础
Sass/SCSS🔌 sass-loader🔌🔌基础
Less🔌 less-loader🔌🔌基础
Stylus🔌🔌
CSS-in-JS🔌 多种🔌🔌🔌🔌🔌
Tailwind CSS🔌🔌

高级特性

功能特性WebpackRollupViteParcelesbuildSWCBun
Macros🔌 babel-macros🔌🔌✅ 内置
Custom Loaders✅ 强大✅ 插件✅ 插件✅ 变换器✅ 插件✅ 插件
Virtual Modules
External Dependencies
Banner/Footer
Alias Resolution
Conditional Exports

性能与优化

功能特性WebpackRollupViteParcelesbuildSWCBun
Minification✅ 多种选择✅ terser✅ esbuild✅ 内置✅ 内置✅ 内置✅ 内置
Gzip/Brotli🔌🔌🔌🔌🔌
Image Optimization🔌🔌🔌✅ 内置🔌基础
Bundle Analysis🔌🔌基础
Caching✅ 持久化基础✅ 依赖预构建✅ 强大✅ 基础✅ 基础
Parallel Processing🔌✅ esbuild✅ worker✅ 内置✅ 内置✅ 内置

部署与输出

功能特性WebpackRollupViteParcelesbuildSWCBun
Multiple Targets
Library Mode✅ 优秀
UMD Output✅ IIFE
ES Module Output✅ 优秀
CommonJS Output
File Hashing🔌

独特功能特性

工具特有功能描述使用场景示例
Webpack Asset Modules原生资源处理(Webpack 5)替代各种loadertype: 'asset/resource' 处理图片、字体等资源
Webpack Module Federation微前端运行时模块共享大型分布式应用在运行时动态加载其他应用的模块
Webpack DLL Plugin预编译第三方库加速开发构建将React、Vue等库预打包
Rollup Pure Annotations纯函数标记优化库开发Tree Shaking/*#__PURE__*/ 注释优化
Vite 依赖预构建CommonJS→ESM转换预构建开发时性能自动预构建node_modules依赖
Vite 原生ESM开发时无需打包极速开发体验直接在浏览器中运行ES模块
Parcel 自动依赖安装检测并自动安装缺失依赖快速原型开发引用新包时自动npm install
Parcel Differential Bundling现代/传统浏览器差异化打包性能优化为新浏览器提供更小的bundle
esbuild Go并发Go协程并行处理极速构建同时处理数千个文件
SWC Rust性能Rust编写的编译器编译速度比Babel快20倍的转译
Bun Macros构建时宏展开代码生成和优化import {sql} from "./query.ts" with {type: "macro"}
Bun 内置运行时打包器+运行时一体全栈开发同时处理前端打包和后端运行

高级配置特性

特性WebpackRollupViteParcelesbuildSWCBun
条件编译✅ DefinePlugin✅ @rollup/plugin-replace✅ define✅ 环境变量✅ define✅ define
代码注入✅ BannerPlugin✅ banner/footer✅ build.rollupOptions✅ banner
自定义解析✅ resolve配置✅ 插件✅ resolve配置✅ 自动解析✅ 解析器✅ 解析器
环境变量注入✅ 多种方式🔌 插件✅ 内置✅ 内置✅ define✅ 内置
多入口配置✅ 复杂配置✅ 对象/数组✅ build.rollupOptions✅ 自动检测✅ 数组✅ 数组

社区生态对比

生态指标WebpackRollupViteParcelesbuildSWCBun
官方插件20+30+15+内置丰富基础基础新兴
第三方插件2000+400+300+150+80+50+30+
框架官方支持Vue, React, AngularVue, ReactVue官方, React全支持多数框架Next.js等快速增长
企业案例Netflix, AirbnbReact, VueVue, Element+AtlassianDiscord, FigmaVercel, Next.jsOven, Vercel
学习资源极丰富丰富快速增长中等充足增长中新兴

图例说明

  • ✅ 原生支持或支持良好
  • 🔌 需要插件支持
  • ❌ 不支持或支持有限
  • 基础 = 基本功能可用但功能有限
  • 良好 = 功能完整且表现良好
  • 优秀 = 功能强大且性能出色

各工具核心优势总结

🔧 Webpack - 企业级全能选手

核心优势

  • Module Federation: 业界唯一的运行时模块联邦解决方案
  • 极度灵活的配置: 几乎可以定制一切打包行为
  • 最成熟的生态: 2000+插件,解决各种边缘需求
  • 强大的代码分析: 内置Bundle Analyzer,详细的构建统计

独特功能

1. Asset Modules (Webpack 5)

// 原生资源处理,替代file-loader/url-loader
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',  // 输出单独文件
      },
      {
        test: /\.svg$/i,
        type: 'asset/inline',    // 内联为data URL
      },
      {
        test: /\.txt$/i,
        type: 'asset/source',    // 导出为字符串
      },
      {
        test: /\.png$/i,
        type: 'asset',          // 自动选择inline/resource
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024   // 8kb阈值
          }
        }
      }
    ]
  }
}

2. Module Federation示例

// 微前端运行时模块共享
const ModuleFederationPlugin = require('@module-federation/webpack');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'shell',
      remotes: {
        'remote-app': 'remoteApp@http://localhost:3001/remoteEntry.js'
      }
    })
  ]
}

3. 其他特性

  • Persistent Caching: 文件系统级持久化缓存
  • Top Level Await: 顶层await支持
  • DLL插件: 预编译优化
  • 完整的开发服务器: 内置HMR系统
  • 支持所有主流前端框架

🌲 Rollup - 库开发专家

核心优势

  • 极致的Tree Shaking: ES模块静态分析领域的佼佼者
  • 干净的输出代码: 接近手写代码的可读性
  • 优秀的库模式: 支持多种输出格式(UMD、ESM、CJS)
  • 纯函数标记: /*#__PURE__*/注释优化支持

独特功能

  • 最佳的ES模块处理
  • 插件API设计优雅
  • 支持复杂的条件导出
  • 专为库开发优化

Vite - 现代开发体验之王

核心优势

  • 原生ESM开发: 无需打包,直接在浏览器运行
  • 依赖预构建: 自动将CommonJS转为ESM并缓存
  • 毫秒级HMR: 基于ES模块的精确更新
  • 零配置体验: 开箱即用的现代前端工具链

独特功能

1. 依赖预构建机制

// Vite自动检测并预构建依赖
// 将 node_modules 中的 CommonJS/UMD 转换为 ESM

// 预构建后的文件结构
.vite/
└── deps/
    ├── react.js          // 预构建的React
    ├── react-dom.js      // 预构建的ReactDOM
    ├── lodash.js         // 预构建的工具库
    └── _metadata.json    // 依赖元数据

// 手动配置预构建
export default {
  optimizeDeps: {
    include: ['linked-dep'],        // 强制预构建
    exclude: ['your-lib'],          // 排除预构建
    force: true,                    // 强制重新预构建
    esbuildOptions: {
      target: 'es2020'              // 预构建目标
    }
  }
}

2. 开发与生产架构差异

  • 开发时: 原生ESM + esbuild转译 + 依赖预构建
  • 生产时: Rollup打包 + Tree Shaking + 代码分割
  • HMR实现: 基于ES模块边界的精确更新

3. 浏览器兼容性

  • 开发: 现代浏览器 (esnext target)
  • 生产: Baseline Widely Available (2.5年前浏览器)
  • Legacy支持: @vitejs/plugin-legacy插件

4. 其他特性

  • 自动CSS代码分割
  • 内置TypeScript、JSX支持
  • 官方Vue支持,React生态完善
  • Multi-page应用支持

📦 Parcel - 零配置自动化专家

核心优势

  • 零配置哲学: 检测项目需求自动配置
  • 自动依赖安装: 检测到新依赖自动安装
  • 差异化打包: 为不同浏览器生成不同的bundle
  • 内置优化: 自动图片压缩、代码分割等

独特功能

  • 多核并行处理
  • 自动检测和转换各种资源类型
  • 内置开发服务器和HMR
  • 支持多种前端框架无需配置

🚀 esbuild - 极速构建引擎

核心优势

  • Go并发处理: 利用Go协程实现真正的并行处理
  • 极速构建: 比传统工具快10-100倍
  • 内置转译: 内置TypeScript、JSX处理
  • 简洁API: 简单易用的配置接口

独特功能

1. 开发服务器支持

// 开发服务器模式
const ctx = await esbuild.context({
  entryPoints: ['src/app.js'],
  bundle: true,
  outdir: 'dist',
})

// 启动开发服务器
const { host, port } = await ctx.serve({
  servedir: 'dist',
  port: 8000,
})
console.log(`Server: http://${host}:${port}/`)

// Watch模式
await ctx.watch()

// 手动重新构建
await ctx.rebuild()

2. Glob-style动态导入

// 支持模式匹配的动态导入
const json1 = require('./data/' + kind + '.json')
const json2 = require(`./data/${kind}.json`)

// esbuild会:
// 1. 扫描匹配的文件
// 2. 生成文件映射表
// 3. 替换为查表逻辑

3. 浏览器WebAssembly支持

// 浏览器中使用esbuild
import * as esbuild from 'esbuild-wasm'

await esbuild.initialize({
  wasmURL: './node_modules/esbuild-wasm/esbuild.wasm',
})

const result = await esbuild.transform(code, { loader: 'tsx' })

4. 其他特性

  • 零依赖安装
  • 支持多种输出格式(ESM/CJS/IIFE)
  • 内置代码压缩和混淆
  • 插件系统简单高效
  • 增量构建API

🦀 SWC - Rust编译器

核心优势

  • Rust性能: 比Babel快20倍的转译速度
  • 现代语法支持: 完整的ES2022+语法支持
  • 框架集成: Next.js官方编译器
  • 插件生态: 支持自定义插件开发

独特功能

  • 实验性特性支持
  • 高度可配置的转译规则
  • 支持WebAssembly输出
  • 与各种构建工具集成

🥟 Bun - 全栈一体化解决方案

核心优势

  • Macros系统: 构建时代码生成和优化
  • 一体化工具链: 包管理器+打包器+运行时
  • 极速安装: 比npm/yarn快数倍的包安装
  • 内置测试: 无需Jest等额外测试框架

独特功能

1. Macros系例

// Bun Macros示例
import {sql} from "./database.ts" with {type: "macro"};

// 构建时展开为优化的SQL查询代码
const users = await sql`SELECT * FROM users WHERE id = ${userId}`;

2. 环境变量处理

// 内联所有环境变量
await Bun.build({
  env: "inline"  // process.env.FOO → "actual_value"
});

// 选择性内联(推荐用于客户端)
await Bun.build({
  env: "PUBLIC_*"  // 只内联PUBLIC_开头的变量
});

// 禁用环境变量注入
await Bun.build({
  env: "disable"
});

3. 目标环境配置

await Bun.build({
  target: "browser",  // 浏览器环境(默认)
  target: "bun",      // Bun运行时,添加// @bun pragma
  target: "node",     // Node.js兼容输出
  format: "esm",      // ES模块(默认)
  format: "cjs",      // CommonJS
  format: "iife",     // 自执行函数
});

4. 全栈应用特性

  • 运行时+构建时一体化
  • 同时处理前端和后端代码打包
  • 内置SQLite支持
  • 原生WebSocket和HTTP服务器
  • 兼容Node.js生态系统

性能基准对比

基于各工具官方提供的基准测试数据和社区测试结果:

测试项目WebpackRollupViteParcelesbuildSWCBun
冷启动时间10-30s5-15s1-3s3-8s0.5-2sN/A0.5-2s
热更新速度1-5sN/A<100ms<500msN/AN/A<500ms
大型项目构建2-10min1-5min30s-2min1-3min10s-1min10s-1min15s-1min
增量构建5-30s2-15s1-5s2-10s1-5s1-5s1-8s
Bundle大小优化优秀优秀优秀良好良好良好良好
内存使用中等中等中等中等

生态系统成熟度

方面WebpackRollupViteParcelesbuildSWCBun
官方插件数量100+50+30+20+10+5+10+
社区插件1000+300+200+100+50+30+50+
框架集成全支持React/VueVue/React全支持多数支持多数支持快速增长
企业采用率极高快速增长中等增长中增长中新兴
Stack Overflow问题50K+10K+5K+3K+1K+500+200+
GitHub Stars65K+25K+65K+43K+38K+31K+74K+

注意:性能数据会因项目规模、配置和硬件环境而有所差异。建议在实际项目中进行测试以获得准确数据。

详细特性分析

1. 构建性能 🚀

速度排名

  1. esbuild/SWC/Bun - 使用系统级语言,构建速度极快
  2. Vite - 开发时使用原生ESM,生产时使用Rollup
  3. Rollup - 专注打包,相对简单快速
  4. Parcel - 多核并行处理,速度较快
  5. Webpack - 功能全面但相对较重

2. 开发体验 👨‍💻

最佳开发体验

  • Vite:毫秒级HMR + 原生ESM
  • Parcel:零配置 + 自动化一切
  • Bun:全栈工具链集成

配置友好性

  • 零配置:Parcel
  • 约定优于配置:Vite
  • 高度灵活:Webpack

3. 适用场景 🎯

场景推荐工具理由
大型企业应用Webpack配置灵活、生态成熟、功能全面
库/框架开发RollupTree shaking优秀、输出干净
现代Web应用Vite开发快速、构建优化、体验优秀
快速原型Parcel零配置、开箱即用
性能敏感项目esbuild/SWC构建速度极快
全栈应用Bun运行时+打包器一体化

4. 生态系统成熟度 🌍

生态系统排名

  1. Webpack - 插件丰富、社区庞大、文档完善
  2. Rollup - 插件质量高、专业化工具
  3. Vite - 快速增长、Vue生态支持强
  4. Parcel - 插件较少但核心功能强
  5. 新一代工具 - 生态正在建设中

技术架构差异

传统架构 vs 现代架构

传统工具 (Webpack/Rollup)

  • JavaScript编写,单线程为主
  • 配置驱动,插件系统复杂
  • 成熟稳定,功能全面

现代工具 (esbuild/SWC/Bun)

  • 系统级语言编写(Go/Rust)
  • 并行处理,性能优先
  • 简化配置,开箱即用

混合架构 (Vite)

  • 开发时使用原生ESM + esbuild
  • 生产时使用Rollup优化
  • 兼顾速度和功能

总结

现代打包工具已经从简单的文件合并发展为复杂的构建系统,具备以下核心能力:

🔧 核心功能

  • 模块解析: 支持多种模块格式
  • 依赖分析: 构建完整依赖图
  • 代码转换: Loader/Plugin生态
  • 代码优化: Tree Shaking、压缩等
  • 代码分割: 智能chunk分割策略

⚡ 性能优化

  • 增量构建: 只重建变更部分
  • 并行处理: 多核心并行编译
  • 缓存机制: 持久化缓存加速
  • 流式处理: 边处理边输出

🎯 发展趋势

  • 原生语言重写: Rust/Go提升性能
  • 零配置: 开箱即用的开发体验
  • 原生ESM: 拥抱现代Web标准
  • 开发体验: 更快的HMR和更好的错误提示

选择合适的打包工具需要考虑项目规模、团队技能栈、性能要求和生态系统等因素。


下一章: Webpack模块处理

转译工具

转译工具是现代JavaScript开发中不可或缺的组成部分,它们将使用新语法、新特性或其他语言编写的代码转换为浏览器和运行环境能够理解的JavaScript代码。本章将深入探讨主要的转译工具及其在模块化开发中的作用。

转译的核心概念

什么是转译

转译(Transpilation)是源到源的编译过程,将一种语言的代码转换为另一种语言的等价代码:

// 输入:ES2020+特性
const data = await fetch('/api/data').then(res => res.json());
const filtered = data?.items?.filter(item => item.active) ?? [];

// 输出:ES5兼容代码
var data = fetch('/api/data').then(function(res) { return res.json(); });
var _data$items;
var filtered = (_data$items = data === null || data === void 0 ? void 0 : data.items) === null || _data$items === void 0 ? void 0 : _data$items.filter(function(item) {
  return item.active;
});
if (filtered === null || filtered === void 0) {
  filtered = [];
}

转译器的作用域

现代转译器处理多种转换任务:

// 语法转换示例
const transpilerTasks = {
  // 1. 语法降级
  syntaxDowngrade: {
    input: 'const { a, ...rest } = obj;',
    output: 'var a = obj.a; var rest = _objectWithoutProperties(obj, ["a"]);'
  },
  
  // 2. 模块格式转换
  moduleFormat: {
    input: 'import { utils } from "./utils";',
    output: 'var utils = require("./utils").utils;'
  },
  
  // 3. JSX转换
  jsxTransform: {
    input: '<div className="container">{content}</div>',
    output: 'React.createElement("div", { className: "container" }, content)'
  },
  
  // 4. TypeScript类型擦除
  typeErasure: {
    input: 'function add(a: number, b: number): number { return a + b; }',
    output: 'function add(a, b) { return a + b; }'
  }
};

抽象语法树(AST)处理

AST基础概念

转译器通过AST进行代码分析和转换:

// 简化的AST结构示例
const astExample = {
  type: 'Program',
  body: [
    {
      type: 'ImportDeclaration',
      specifiers: [
        {
          type: 'ImportDefaultSpecifier',
          local: { type: 'Identifier', name: 'React' }
        }
      ],
      source: { type: 'Literal', value: 'react' }
    },
    {
      type: 'FunctionDeclaration',
      id: { type: 'Identifier', name: 'Component' },
      params: [],
      body: {
        type: 'BlockStatement',
        body: [
          {
            type: 'ReturnStatement',
            argument: {
              type: 'JSXElement',
              openingElement: {
                type: 'JSXOpeningElement',
                name: { type: 'JSXIdentifier', name: 'div' }
              }
            }
          }
        ]
      }
    }
  ]
};

AST遍历和转换

// AST访问者模式实现
class ASTVisitor {
  constructor() {
    this.visitors = {};
  }
  
  // 注册访问者
  register(nodeType, visitor) {
    this.visitors[nodeType] = visitor;
  }
  
  // 遍历AST
  traverse(node, parent = null) {
    // 进入节点
    if (this.visitors[node.type]?.enter) {
      this.visitors[node.type].enter(node, parent);
    }
    
    // 递归访问子节点
    for (const key in node) {
      const child = node[key];
      if (Array.isArray(child)) {
        child.forEach(item => {
          if (this.isNode(item)) {
            this.traverse(item, node);
          }
        });
      } else if (this.isNode(child)) {
        this.traverse(child, node);
      }
    }
    
    // 退出节点
    if (this.visitors[node.type]?.exit) {
      this.visitors[node.type].exit(node, parent);
    }
  }
  
  isNode(obj) {
    return obj && typeof obj === 'object' && obj.type;
  }
}

// 使用示例:将箭头函数转换为普通函数
const arrowFunctionTransformer = new ASTVisitor();

arrowFunctionTransformer.register('ArrowFunctionExpression', {
  enter(node, parent) {
    // 转换箭头函数为普通函数
    node.type = 'FunctionExpression';
    
    // 处理隐式返回
    if (node.body.type !== 'BlockStatement') {
      node.body = {
        type: 'BlockStatement',
        body: [
          {
            type: 'ReturnStatement',
            argument: node.body
          }
        ]
      };
    }
  }
});

插件和预设系统

插件架构

现代转译器采用插件化架构,便于扩展和定制:

// 简化的插件系统实现
class TranspilerCore {
  constructor() {
    this.plugins = [];
    this.presets = [];
  }
  
  use(plugin, options = {}) {
    if (typeof plugin === 'function') {
      this.plugins.push(plugin(options));
    } else {
      this.plugins.push(plugin);
    }
    return this;
  }
  
  preset(preset) {
    this.presets.push(preset);
    return this;
  }
  
  transform(code, filename) {
    // 1. 解析代码为AST
    let ast = this.parse(code, filename);
    
    // 2. 应用预设
    this.presets.forEach(preset => {
      preset.plugins.forEach(plugin => {
        ast = plugin.transform(ast);
      });
    });
    
    // 3. 应用插件
    this.plugins.forEach(plugin => {
      ast = plugin.transform(ast);
    });
    
    // 4. 生成代码
    return this.generate(ast);
  }
}

// 插件示例:模块导入转换
function importTransformPlugin(options = {}) {
  return {
    name: 'import-transform',
    transform(ast) {
      const visitor = new ASTVisitor();
      
      visitor.register('ImportDeclaration', {
        enter(node) {
          if (options.format === 'cjs') {
            // 转换为CommonJS require
            this.convertToRequire(node);
          }
        }
      });
      
      visitor.traverse(ast);
      return ast;
    }
  };
}

预设配置

预设是插件的集合,提供了开箱即用的配置:

// 环境预设示例
const envPreset = {
  name: '@transpiler/preset-env',
  plugins: [
    ['@transpiler/plugin-arrow-functions'],
    ['@transpiler/plugin-destructuring'],
    ['@transpiler/plugin-async-to-generator'],
    ['@transpiler/plugin-optional-chaining']
  ],
  
  // 根据目标环境动态调整
  getPlugins(targets) {
    const plugins = [];
    
    if (!this.supportsArrowFunctions(targets)) {
      plugins.push('@transpiler/plugin-arrow-functions');
    }
    
    if (!this.supportsOptionalChaining(targets)) {
      plugins.push('@transpiler/plugin-optional-chaining');
    }
    
    return plugins;
  }
};

// React预设示例
const reactPreset = {
  name: '@transpiler/preset-react',
  plugins: [
    '@transpiler/plugin-jsx',
    '@transpiler/plugin-react-display-name',
    '@transpiler/plugin-react-pure-annotations'
  ],
  
  options: {
    runtime: 'automatic', // 或 'classic'
    development: process.env.NODE_ENV === 'development'
  }
};

模块格式转换

ES模块到CommonJS

// ES模块转换插件实现
function esModuleToCjsPlugin() {
  return {
    name: 'esm-to-cjs',
    
    ImportDeclaration(path) {
      const { node } = path;
      const source = node.source.value;
      
      if (node.specifiers.length === 0) {
        // import './side-effect'
        path.replaceWith(
          t.expressionStatement(
            t.callExpression(t.identifier('require'), [t.stringLiteral(source)])
          )
        );
      } else {
        // import { a, b } from 'module'
        const declarations = [];
        
        node.specifiers.forEach(spec => {
          if (t.isImportDefaultSpecifier(spec)) {
            // import defaultExport from 'module'
            declarations.push(
              t.variableDeclarator(
                spec.local,
                t.memberExpression(
                  t.callExpression(t.identifier('require'), [t.stringLiteral(source)]),
                  t.identifier('default')
                )
              )
            );
          } else if (t.isImportSpecifier(spec)) {
            // import { namedExport } from 'module'
            declarations.push(
              t.variableDeclarator(
                spec.local,
                t.memberExpression(
                  t.callExpression(t.identifier('require'), [t.stringLiteral(source)]),
                  spec.imported
                )
              )
            );
          }
        });
        
        path.replaceWith(t.variableDeclaration('const', declarations));
      }
    },
    
    ExportDeclaration(path) {
      const { node } = path;
      
      if (t.isExportDefaultDeclaration(node)) {
        // export default value
        path.replaceWith(
          t.expressionStatement(
            t.assignmentExpression(
              '=',
              t.memberExpression(t.identifier('module'), t.identifier('exports')),
              node.declaration
            )
          )
        );
      } else if (t.isExportNamedDeclaration(node)) {
        // export { a, b }
        const assignments = [];
        
        node.specifiers.forEach(spec => {
          assignments.push(
            t.expressionStatement(
              t.assignmentExpression(
                '=',
                t.memberExpression(
                  t.memberExpression(t.identifier('module'), t.identifier('exports')),
                  spec.exported
                ),
                spec.local
              )
            )
          );
        });
        
        path.replaceWithMultiple(assignments);
      }
    }
  };
}

动态导入转换

// 动态导入转换
function dynamicImportPlugin() {
  return {
    name: 'dynamic-import',
    
    CallExpression(path) {
      if (path.node.callee.type === 'Import') {
        // import() -> Promise.resolve(require())
        const requireCall = t.callExpression(
          t.identifier('require'),
          path.node.arguments
        );
        
        path.replaceWith(
          t.callExpression(
            t.memberExpression(t.identifier('Promise'), t.identifier('resolve')),
            [requireCall]
          )
        );
      }
    }
  };
}

代码生成和源映射

源映射生成

// 源映射生成器
class SourceMapGenerator {
  constructor(filename) {
    this.filename = filename;
    this.mappings = [];
    this.sources = [filename];
  }
  
  addMapping(generated, original, source = 0) {
    this.mappings.push({
      generated: { line: generated.line, column: generated.column },
      original: { line: original.line, column: original.column },
      source
    });
  }
  
  generate() {
    return {
      version: 3,
      file: this.filename,
      sources: this.sources,
      mappings: this.encodeMappings(),
      sourcesContent: this.getSourcesContent()
    };
  }
  
  encodeMappings() {
    // VLQ编码实现
    return vlqEncode(this.mappings);
  }
}

// 代码生成器
class CodeGenerator {
  constructor(ast, options = {}) {
    this.ast = ast;
    this.options = options;
    this.sourceMap = options.sourceMaps ? new SourceMapGenerator(options.filename) : null;
  }
  
  generate() {
    const result = {
      code: '',
      map: null
    };
    
    // 遍历AST生成代码
    this.traverse(this.ast, (code, node) => {
      result.code += code;
      
      // 记录源映射
      if (this.sourceMap && node.loc) {
        this.sourceMap.addMapping(
          this.getCurrentPosition(),
          node.loc.start
        );
      }
    });
    
    if (this.sourceMap) {
      result.map = this.sourceMap.generate();
    }
    
    return result;
  }
}

性能优化策略

缓存机制

// 转译缓存实现
class TranspilerCache {
  constructor() {
    this.cache = new Map();
    this.dependencyGraph = new Map();
  }
  
  getCacheKey(filename, code, options) {
    const optionsHash = this.hashObject(options);
    const codeHash = this.hashString(code);
    return `${filename}:${codeHash}:${optionsHash}`;
  }
  
  get(filename, code, options) {
    const key = this.getCacheKey(filename, code, options);
    const cached = this.cache.get(key);
    
    if (cached && this.isCacheValid(filename, cached.timestamp)) {
      return cached.result;
    }
    
    return null;
  }
  
  set(filename, code, options, result) {
    const key = this.getCacheKey(filename, code, options);
    this.cache.set(key, {
      result,
      timestamp: Date.now(),
      dependencies: this.extractDependencies(result.ast)
    });
  }
  
  isCacheValid(filename, timestamp) {
    // 检查文件和依赖是否有变更
    const stats = fs.statSync(filename);
    if (stats.mtime.getTime() > timestamp) {
      return false;
    }
    
    // 检查依赖文件
    const deps = this.dependencyGraph.get(filename) || [];
    return deps.every(dep => {
      const depStats = fs.statSync(dep);
      return depStats.mtime.getTime() <= timestamp;
    });
  }
}

增量编译

// 增量编译管理器
class IncrementalCompiler {
  constructor() {
    this.fileGraph = new Map();
    this.lastBuildTime = 0;
  }
  
  compile(files, options) {
    const changedFiles = this.getChangedFiles(files);
    const affectedFiles = this.getAffectedFiles(changedFiles);
    
    // 只编译受影响的文件
    const results = new Map();
    
    affectedFiles.forEach(file => {
      const result = this.transpiler.transform(file, options);
      results.set(file, result);
      
      // 更新依赖图
      this.updateDependencyGraph(file, result.dependencies);
    });
    
    this.lastBuildTime = Date.now();
    return results;
  }
  
  getChangedFiles(files) {
    return files.filter(file => {
      const stats = fs.statSync(file);
      return stats.mtime.getTime() > this.lastBuildTime;
    });
  }
  
  getAffectedFiles(changedFiles) {
    const affected = new Set(changedFiles);
    
    // 递归找出所有受影响的文件
    function addDependents(file) {
      const dependents = this.fileGraph.get(file)?.dependents || [];
      dependents.forEach(dependent => {
        if (!affected.has(dependent)) {
          affected.add(dependent);
          addDependents(dependent);
        }
      });
    }
    
    changedFiles.forEach(file => addDependents(file));
    return Array.from(affected);
  }
}

最佳实践

配置优化

// 生产环境配置
const productionConfig = {
  presets: [
    ['@transpiler/preset-env', {
      targets: '> 1%, not dead',
      modules: false,
      useBuiltIns: 'usage',
      corejs: 3
    }]
  ],
  plugins: [
    ['@transpiler/plugin-transform-runtime', {
      regenerator: false,
      useESModules: true
    }]
  ],
  
  // 优化选项
  compact: true,
  minified: true,
  sourceMaps: false
};

// 开发环境配置
const developmentConfig = {
  presets: [
    ['@transpiler/preset-env', {
      targets: { node: 'current' },
      modules: 'auto'
    }],
    '@transpiler/preset-react'
  ],
  
  // 开发优化
  sourceMaps: 'inline',
  retainLines: true,
  
  // 快速转换
  compact: false,
  comments: true
};

项目集成

// 构建工具集成
module.exports = {
  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'transpiler-loader',
          options: {
            configFile: './transpiler.config.js',
            cacheDirectory: true,
            cacheCompression: false
          }
        }
      }
    ]
  }
};

转译工具作为现代JavaScript开发的基础设施,不仅解决了浏览器兼容性问题,还为开发者提供了使用最新语言特性的能力。理解转译工具的工作原理和最佳实践,对于构建高效的开发工作流至关重要。


下一章: Babel模块转换

Babel模块转换

Babel是JavaScript生态系统中最重要的转译工具之一,它使开发者能够使用最新的JavaScript特性,同时保持与旧版浏览器的兼容性。在模块化开发中,Babel扮演着将现代模块语法转换为各种目标格式的关键角色。

Babel核心架构

转换流程

Babel的转换过程包含三个主要阶段:

// Babel转换流程示例
const babel = require('@babel/core');

// 1. 解析阶段 (Parse)
const ast = babel.parseSync(code, {
  sourceType: 'module',
  plugins: ['jsx', 'typescript']
});

// 2. 转换阶段 (Transform)  
const transformedAst = babel.transformFromAstSync(ast, code, {
  plugins: [
    '@babel/plugin-transform-arrow-functions',
    '@babel/plugin-transform-modules-commonjs'
  ]
});

// 3. 生成阶段 (Generate)
const result = babel.generateSync(transformedAst.ast, {
  sourceMaps: true,
  compact: false
});

console.log(result.code);

插件系统架构

Babel采用访问者模式实现插件系统:

// 简化的Babel插件结构
function babelPlugin() {
  return {
    name: 'my-babel-plugin',
    
    // 插件初始化
    pre() {
      this.imports = new Set();
    },
    
    // AST访问者
    visitor: {
      // 处理导入声明
      ImportDeclaration(path, state) {
        const source = path.node.source.value;
        this.imports.add(source);
        
        // 记录模块依赖
        state.dependencies = state.dependencies || [];
        state.dependencies.push(source);
      },
      
      // 处理导出声明
      ExportDeclaration(path) {
        if (path.isExportDefaultDeclaration()) {
          // 处理默认导出
          this.handleDefaultExport(path);
        } else if (path.isExportNamedDeclaration()) {
          // 处理命名导出
          this.handleNamedExport(path);
        }
      },
      
      // 处理函数调用
      CallExpression(path) {
        if (this.isDynamicImport(path)) {
          // 转换动态导入
          this.transformDynamicImport(path);
        }
      }
    },
    
    // 插件清理
    post() {
      console.log('处理的模块数量:', this.imports.size);
    }
  };
}

模块转换插件

ES模块到CommonJS

@babel/plugin-transform-modules-commonjs是最常用的模块转换插件:

// 插件配置
module.exports = {
  plugins: [
    ['@babel/plugin-transform-modules-commonjs', {
      // 严格模式
      strict: true,
      
      // 懒加载
      lazy: false,
      
      // 不允许顶层this
      noInterop: false,
      
      // 保留import.meta
      importInterop: 'babel',
      
      // 自定义模块ID
      getModuleId: (moduleName) => moduleName
    }]
  ]
};

// 转换示例
// 输入:ES模块
import defaultExport, { namedExport } from 'module';
import * as namespace from 'module';
export { localVar as exportName };
export default function() {}

// 输出:CommonJS
var _module = require('module');
var _module2 = _interopRequireDefault(_module);
var namespace = _interopRequireWildcard(_module);

function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : { default: obj };
}

function _interopRequireWildcard(obj) {
  if (obj && obj.__esModule) return obj;
  var newObj = {};
  if (obj != null) {
    for (var key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        newObj[key] = obj[key];
      }
    }
  }
  newObj.default = obj;
  return newObj;
}

exports.exportName = localVar;
exports.default = function() {};

动态导入转换

处理import()语法的转换:

// 动态导入插件实现
function dynamicImportPlugin() {
  return {
    name: 'dynamic-import-transform',
    visitor: {
      CallExpression(path) {
        if (path.node.callee.type === 'Import') {
          const modulePath = path.node.arguments[0];
          
          // 转换为Promise.resolve(require())
          path.replaceWith(
            t.callExpression(
              t.memberExpression(
                t.identifier('Promise'),
                t.identifier('resolve')
              ),
              [
                t.callExpression(
                  t.identifier('require'),
                  [modulePath]
                )
              ]
            )
          );
        }
      }
    }
  };
}

// 使用示例
// 输入
const module = await import('./module.js');

// 输出
const module = await Promise.resolve(require('./module.js'));

模块路径转换

自定义模块路径解析:

// 路径转换插件
function modulePathTransform(options = {}) {
  const { alias = {}, baseUrl = '' } = options;
  
  return {
    name: 'module-path-transform',
    visitor: {
      ImportDeclaration(path) {
        this.transformPath(path.node.source);
      },
      
      CallExpression(path) {
        if (this.isDynamicImport(path)) {
          this.transformPath(path.node.arguments[0]);
        } else if (this.isRequireCall(path)) {
          this.transformPath(path.node.arguments[0]);
        }
      }
    },
    
    transformPath(sourceNode) {
      const originalPath = sourceNode.value;
      
      // 处理别名
      for (const [aliasKey, aliasValue] of Object.entries(alias)) {
        if (originalPath.startsWith(aliasKey)) {
          sourceNode.value = originalPath.replace(aliasKey, aliasValue);
          return;
        }
      }
      
      // 处理相对路径
      if (originalPath.startsWith('.')) {
        sourceNode.value = path.resolve(baseUrl, originalPath);
      }
    }
  };
}

// 配置示例
{
  plugins: [
    ['module-path-transform', {
      alias: {
        '@': './src',
        'utils': './src/utils'
      },
      baseUrl: process.cwd()
    }]
  ]
}

预设配置

@babel/preset-env

环境预设是最重要的预设之一:

// 详细的preset-env配置
module.exports = {
  presets: [
    ['@babel/preset-env', {
      // 目标环境
      targets: {
        browsers: ['> 1%', 'last 2 versions', 'not dead'],
        node: '14'
      },
      
      // 模块格式
      modules: 'auto', // false, 'amd', 'umd', 'systemjs', 'commonjs', 'cjs'
      
      // polyfill策略
      useBuiltIns: 'usage', // false, 'entry', 'usage'
      corejs: { version: 3, proposals: true },
      
      // 包含/排除特定转换
      include: ['@babel/plugin-proposal-class-properties'],
      exclude: ['@babel/plugin-transform-typeof-symbol'],
      
      // 调试模式
      debug: false,
      
      // 强制所有转换
      forceAllTransforms: false,
      
      // 配置文件路径
      configPath: process.cwd(),
      
      // 忽略浏览器配置
      ignoreBrowserslistConfig: false,
      
      // 运行时优化
      shippedProposals: false,
      
      // 规范合规性
      spec: false,
      loose: false,
      
      // 模块转换选项
      bugfixes: true
    }]
  ]
};

自定义预设

创建项目特定的预设:

// custom-preset.js
module.exports = function(api, options = {}) {
  const { isDevelopment = false, isTest = false } = options;
  
  // 根据环境调整配置
  const plugins = [
    '@babel/plugin-proposal-class-properties',
    '@babel/plugin-proposal-object-rest-spread'
  ];
  
  // 开发环境特有插件
  if (isDevelopment) {
    plugins.push('@babel/plugin-transform-react-jsx-self');
    plugins.push('@babel/plugin-transform-react-jsx-source');
  }
  
  // 测试环境配置
  if (isTest) {
    plugins.push('babel-plugin-dynamic-import-node');
  }
  
  return {
    presets: [
      ['@babel/preset-env', {
        targets: isDevelopment ? { node: 'current' } : '> 0.25%, not dead',
        modules: isTest ? 'commonjs' : false,
        useBuiltIns: 'usage',
        corejs: 3
      }],
      ['@babel/preset-react', {
        development: isDevelopment,
        runtime: 'automatic'
      }],
      ['@babel/preset-typescript', {
        allowNamespaces: true,
        allowDeclareFields: true
      }]
    ],
    plugins
  };
};

// 使用自定义预设
module.exports = {
  presets: [
    ['./custom-preset', {
      isDevelopment: process.env.NODE_ENV === 'development',
      isTest: process.env.NODE_ENV === 'test'
    }]
  ]
};

高级转换技术

代码分割支持

为代码分割生成辅助代码:

// 代码分割插件
function codeSplittingPlugin() {
  return {
    name: 'code-splitting',
    visitor: {
      CallExpression(path) {
        if (this.isDynamicImport(path)) {
          const modulePath = path.node.arguments[0].value;
          
          // 添加webpack magic comments
          if (modulePath.includes('/components/')) {
            path.node.arguments[0] = t.stringLiteral(
              `${modulePath}/* webpackChunkName: "component-[request]" */`
            );
          }
          
          // 添加预加载提示
          this.addPreloadHint(path, modulePath);
        }
      }
    },
    
    addPreloadHint(path, modulePath) {
      // 生成预加载代码
      const preloadCode = t.expressionStatement(
        t.callExpression(
          t.memberExpression(
            t.identifier('document'),
            t.identifier('createElement')
          ),
          [t.stringLiteral('link')]
        )
      );
      
      path.insertBefore(preloadCode);
    }
  };
}

模块联邦支持

为Module Federation生成适配代码:

// Module Federation适配插件
function moduleFederationPlugin(options = {}) {
  const { remotes = {}, exposes = {} } = options;
  
  return {
    name: 'module-federation-adapter',
    visitor: {
      Program: {
        enter(path) {
          // 添加远程模块加载器
          this.addRemoteLoader(path);
        },
        
        exit(path) {
          // 添加模块导出适配器
          this.addExportAdapter(path);
        }
      },
      
      ImportDeclaration(path) {
        const source = path.node.source.value;
        
        // 检查是否为远程模块
        if (remotes[source]) {
          this.transformRemoteImport(path, remotes[source]);
        }
      }
    },
    
    addRemoteLoader(path) {
      const loaderCode = `
        const __webpack_require__ = {
          loadRemote: async (url, scope, module) => {
            await __webpack_init_sharing__('default');
            const container = window[scope];
            await container.init(__webpack_share_scopes__.default);
            const factory = await container.get(module);
            return factory();
          }
        };
      `;
      
      path.unshiftContainer('body', babel.parse(loaderCode).program.body);
    },
    
    transformRemoteImport(path, remoteConfig) {
      const { url, scope, module } = remoteConfig;
      
      // 转换为动态加载
      const dynamicImport = t.awaitExpression(
        t.callExpression(
          t.memberExpression(
            t.identifier('__webpack_require__'),
            t.identifier('loadRemote')
          ),
          [
            t.stringLiteral(url),
            t.stringLiteral(scope),
            t.stringLiteral(module)
          ]
        )
      );
      
      path.replaceWith(
        t.variableDeclaration('const', [
          t.variableDeclarator(
            t.objectPattern(path.node.specifiers.map(spec => 
              t.objectProperty(spec.imported, spec.local)
            )),
            dynamicImport
          )
        ])
      );
    }
  };
}

性能优化

编译缓存

Babel的缓存机制配置:

// babel.config.js
module.exports = {
  // 启用缓存
  cacheDirectory: '.babel-cache',
  cacheCompression: false,
  
  // 缓存标识符
  cacheIdentifier: JSON.stringify({
    babelVersion: require('@babel/core/package.json').version,
    nodeVersion: process.version,
    env: process.env.NODE_ENV
  }),
  
  presets: [
    ['@babel/preset-env', {
      targets: '> 0.25%, not dead'
    }]
  ]
};

// 程序化API缓存
const babel = require('@babel/core');
const fs = require('fs');
const crypto = require('crypto');

class BabelCache {
  constructor(cacheDir = '.babel-cache') {
    this.cacheDir = cacheDir;
    this.ensureCacheDir();
  }
  
  getCacheKey(filename, source, options) {
    const content = JSON.stringify({ filename, source, options });
    return crypto.createHash('md5').update(content).digest('hex');
  }
  
  get(filename, source, options) {
    const key = this.getCacheKey(filename, source, options);
    const cachePath = path.join(this.cacheDir, `${key}.json`);
    
    if (fs.existsSync(cachePath)) {
      const cached = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
      
      // 检查源文件是否有变更
      const stats = fs.statSync(filename);
      if (stats.mtime.getTime() <= cached.timestamp) {
        return cached.result;
      }
    }
    
    return null;
  }
  
  set(filename, source, options, result) {
    const key = this.getCacheKey(filename, source, options);
    const cachePath = path.join(this.cacheDir, `${key}.json`);
    
    const cached = {
      result,
      timestamp: Date.now()
    };
    
    fs.writeFileSync(cachePath, JSON.stringify(cached));
  }
}

并行处理

使用worker线程加速编译:

// babel-worker.js
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
const babel = require('@babel/core');

if (!isMainThread) {
  // Worker线程处理
  parentPort.on('message', async ({ filename, source, options }) => {
    try {
      const result = await babel.transformAsync(source, {
        filename,
        ...options
      });
      
      parentPort.postMessage({ success: true, result });
    } catch (error) {
      parentPort.postMessage({ success: false, error: error.message });
    }
  });
} else {
  // 主线程调度器
  class ParallelBabel {
    constructor(workerCount = require('os').cpus().length) {
      this.workers = [];
      this.queue = [];
      this.activeJobs = 0;
      
      // 创建worker池
      for (let i = 0; i < workerCount; i++) {
        this.createWorker();
      }
    }
    
    createWorker() {
      const worker = new Worker(__filename);
      
      worker.on('message', ({ success, result, error }) => {
        const job = this.activeJobs.shift();
        
        if (success) {
          job.resolve(result);
        } else {
          job.reject(new Error(error));
        }
        
        this.processQueue();
      });
      
      this.workers.push(worker);
    }
    
    async transform(filename, source, options) {
      return new Promise((resolve, reject) => {
        const job = { filename, source, options, resolve, reject };
        
        const availableWorker = this.workers.find(w => !w.busy);
        
        if (availableWorker) {
          this.executeJob(availableWorker, job);
        } else {
          this.queue.push(job);
        }
      });
    }
    
    executeJob(worker, job) {
      worker.busy = true;
      this.activeJobs.push(job);
      
      worker.postMessage({
        filename: job.filename,
        source: job.source,
        options: job.options
      });
    }
    
    processQueue() {
      if (this.queue.length === 0) return;
      
      const availableWorker = this.workers.find(w => !w.busy);
      if (availableWorker) {
        const job = this.queue.shift();
        this.executeJob(availableWorker, job);
      }
    }
  }
  
  module.exports = ParallelBabel;
}

构建工具集成

Webpack集成

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              ['@babel/preset-env', {
                targets: {
                  browsers: ['> 1%', 'last 2 versions']
                },
                modules: false, // 让webpack处理模块
                useBuiltIns: 'usage',
                corejs: 3
              }],
              '@babel/preset-react'
            ],
            
            // 缓存配置
            cacheDirectory: true,
            cacheCompression: false,
            
            // 开发环境插件
            plugins: process.env.NODE_ENV === 'development' ? [
              'react-refresh/babel'
            ] : []
          }
        }
      }
    ]
  }
};

Rollup集成

// rollup.config.js
import babel from '@rollup/plugin-babel';

export default {
  input: 'src/index.js',
  output: {
    file: 'dist/bundle.js',
    format: 'es'
  },
  plugins: [
    babel({
      babelHelpers: 'bundled',
      exclude: 'node_modules/**',
      presets: [
        ['@babel/preset-env', {
          modules: false, // 让Rollup处理模块
          targets: {
            browsers: ['> 1%', 'last 2 versions']
          }
        }]
      ]
    })
  ]
};

Vite集成

// vite.config.js
import { defineConfig } from 'vite';
import { babel } from '@rollup/plugin-babel';

export default defineConfig({
  plugins: [
    // 开发环境使用SWC,生产环境使用Babel
    process.env.NODE_ENV === 'production' ? babel({
      babelHelpers: 'bundled',
      exclude: /node_modules/,
      extensions: ['.js', '.jsx', '.ts', '.tsx']
    }) : null
  ].filter(Boolean),
  
  // esbuild配置(开发环境)
  esbuild: {
    jsxFactory: 'React.createElement',
    jsxFragment: 'React.Fragment'
  }
});

最佳实践

配置文件策略

// babel.config.js - 项目级配置
module.exports = function(api) {
  // 缓存配置
  api.cache(true);
  
  const presets = [
    ['@babel/preset-env', {
      targets: {
        node: 'current'
      }
    }]
  ];
  
  const plugins = [];
  
  // 环境特定配置
  if (process.env.NODE_ENV === 'test') {
    plugins.push('babel-plugin-dynamic-import-node');
  }
  
  return {
    presets,
    plugins,
    env: {
      development: {
        plugins: ['react-refresh/babel']
      },
      production: {
        plugins: [
          ['transform-remove-console', { exclude: ['error', 'warn'] }]
        ]
      }
    }
  };
};

// .babelrc.js - 文件级配置
module.exports = {
  presets: ['@babel/preset-react'],
  plugins: ['@babel/plugin-proposal-class-properties']
};

类型检查集成

// 结合TypeScript的配置
module.exports = {
  presets: [
    ['@babel/preset-env', {
      targets: '> 0.25%, not dead'
    }],
    ['@babel/preset-typescript', {
      // 只移除类型,不做类型检查
      onlyRemoveTypeImports: true,
      
      // 允许命名空间
      allowNamespaces: true,
      
      // 允许声明字段
      allowDeclareFields: true
    }]
  ],
  
  plugins: [
    // 装饰器支持
    ['@babel/plugin-proposal-decorators', { legacy: true }],
    
    // 类属性支持
    ['@babel/plugin-proposal-class-properties', { loose: true }]
  ]
};

Babel作为JavaScript转译的标杆工具,不仅解决了语言版本兼容性问题,还为现代模块化开发提供了强大的转换能力。掌握Babel的配置和优化技巧,对于构建高效的现代JavaScript应用至关重要。


下一章: TypeScript模块

TypeScript模块

TypeScript作为JavaScript的超集,为模块化开发带来了强大的类型系统支持。它不仅提供了编译时类型检查,还增强了模块的导入导出机制,为大型项目的模块化架构提供了坚实的基础。

TypeScript模块系统

模块语法增强

TypeScript在ES模块基础上增加了类型信息:

// 类型导出
export type UserType = {
  id: number;
  name: string;
  email: string;
};

export interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

// 值和类型同时导出
export class UserService {
  async getUser(id: number): Promise<UserType> {
    // 实现...
  }
}

// 命名空间导出
export namespace Utils {
  export function formatDate(date: Date): string {
    return date.toISOString();
  }
  
  export type DateFormat = 'ISO' | 'Local' | 'UTC';
}

// 条件类型导出
export type ApiEndpoint<T extends string> = T extends 'users' 
  ? UserType[] 
  : T extends 'posts' 
  ? PostType[] 
  : unknown;

模块导入的类型支持

// 类型导入
import type { UserType, ApiResponse } from './types';
import type * as Types from './types';

// 值导入
import { UserService } from './services';
import { Utils } from './utils';

// 混合导入
import { CONFIG, type ConfigType } from './config';

// 动态导入与类型
const loadModule = async (): Promise<typeof import('./heavy-module')> => {
  return await import('./heavy-module');
};

// 条件导入
type ModuleType = typeof import('./module');
type AsyncModuleType = Awaited<typeof import('./async-module')>;

模块声明和环境声明

// 全局模块声明
declare global {
  interface Window {
    __APP_CONFIG__: AppConfig;
  }
  
  namespace NodeJS {
    interface ProcessEnv {
      NODE_ENV: 'development' | 'production' | 'test';
      API_URL: string;
    }
  }
}

// 模块声明
declare module '*.vue' {
  import { DefineComponent } from 'vue';
  const component: DefineComponent<{}, {}, any>;
  export default component;
}

declare module '*.module.css' {
  const classes: { readonly [key: string]: string };
  export default classes;
}

// 扩展已有模块
declare module 'express' {
  interface Request {
    user?: User;
  }
}

// 第三方库类型声明
declare module 'some-untyped-library' {
  export function someFunction(arg: string): number;
  export const CONSTANT: string;
}

编译配置

tsconfig.json详解

{
  "compilerOptions": {
    // 模块系统配置
    "module": "ES2022",
    "moduleResolution": "node",
    "target": "ES2020",
    
    // 模块检测
    "moduleDetection": "auto",
    
    // 输出控制
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    
    // 模块解析
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"],
      "@utils/*": ["src/utils/*"]
    },
    
    // 类型检查
    "strict": true,
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    
    // ES模块互操作
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    
    // 实验性特性
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    
    // 增量编译
    "incremental": true,
    "tsBuildInfoFile": ".tsbuildinfo",
    
    // 类型导入
    "verbatimModuleSyntax": false,
    "allowImportingTsExtensions": false
  },
  
  // 项目引用
  "references": [
    { "path": "./packages/core" },
    { "path": "./packages/utils" }
  ],
  
  // 包含和排除
  "include": [
    "src/**/*",
    "types/**/*"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "**/*.test.ts"
  ]
}

多包项目配置

// packages/core/tsconfig.json
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src",
    "composite": true
  },
  "include": ["src/**/*"],
  "references": []
}

// packages/utils/tsconfig.json  
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src",
    "composite": true
  },
  "include": ["src/**/*"],
  "references": [
    { "path": "../core" }
  ]
}

// tsconfig.base.json
{
  "compilerOptions": {
    "module": "ES2022",
    "target": "ES2020",
    "moduleResolution": "node",
    "strict": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "incremental": true
  }
}

高级模块模式

模块扩展模式

// 基础模块
// base-module.ts
export interface BaseConfig {
  name: string;
  version: string;
}

export class BaseService {
  constructor(protected config: BaseConfig) {}
  
  getName(): string {
    return this.config.name;
  }
}

// 扩展模块
// extended-module.ts
import { BaseConfig, BaseService } from './base-module';

export interface ExtendedConfig extends BaseConfig {
  features: string[];
  debug: boolean;
}

export class ExtendedService extends BaseService {
  constructor(protected config: ExtendedConfig) {
    super(config);
  }
  
  getFeatures(): string[] {
    return this.config.features;
  }
  
  isDebugEnabled(): boolean {
    return this.config.debug;
  }
}

// 模块聚合
export * from './base-module';
export { ExtendedService, type ExtendedConfig } from './extended-module';

插件架构模式

// 插件系统类型定义
export interface Plugin<T = any> {
  name: string;
  version: string;
  install(app: App, options?: T): void;
  uninstall?(app: App): void;
}

export interface App {
  use<T>(plugin: Plugin<T>, options?: T): this;
  unuse(pluginName: string): this;
  getPlugin<T extends Plugin>(name: string): T | undefined;
}

// 插件实现
export class ValidationPlugin implements Plugin<ValidationOptions> {
  name = 'validation';
  version = '1.0.0';
  
  install(app: App, options: ValidationOptions = {}) {
    // 安装验证插件
    app.addValidator(new Validator(options));
  }
  
  uninstall(app: App) {
    app.removeValidator(this.name);
  }
}

// 应用实现
export class Application implements App {
  private plugins = new Map<string, Plugin>();
  private validators = new Map<string, Validator>();
  
  use<T>(plugin: Plugin<T>, options?: T): this {
    if (this.plugins.has(plugin.name)) {
      throw new Error(`Plugin ${plugin.name} already installed`);
    }
    
    plugin.install(this, options);
    this.plugins.set(plugin.name, plugin);
    return this;
  }
  
  unuse(pluginName: string): this {
    const plugin = this.plugins.get(pluginName);
    if (plugin?.uninstall) {
      plugin.uninstall(this);
    }
    this.plugins.delete(pluginName);
    return this;
  }
  
  getPlugin<T extends Plugin>(name: string): T | undefined {
    return this.plugins.get(name) as T;
  }
  
  addValidator(validator: Validator): void {
    this.validators.set(validator.name, validator);
  }
  
  removeValidator(name: string): void {
    this.validators.delete(name);
  }
}

工厂模式模块

// 工厂接口
export interface Factory<T> {
  create(...args: any[]): T;
  canHandle(type: string): boolean;
}

// 具体工厂
export class HttpClientFactory implements Factory<HttpClient> {
  canHandle(type: string): boolean {
    return ['axios', 'fetch', 'xhr'].includes(type);
  }
  
  create(type: 'axios' | 'fetch' | 'xhr', config?: any): HttpClient {
    switch (type) {
      case 'axios':
        return new AxiosClient(config);
      case 'fetch':
        return new FetchClient(config);
      case 'xhr':
        return new XhrClient(config);
      default:
        throw new Error(`Unsupported client type: ${type}`);
    }
  }
}

// 工厂注册器
export class FactoryRegistry {
  private factories = new Map<string, Factory<any>>();
  
  register<T>(name: string, factory: Factory<T>): void {
    this.factories.set(name, factory);
  }
  
  create<T>(name: string, type: string, ...args: any[]): T {
    const factory = this.factories.get(name);
    if (!factory) {
      throw new Error(`Factory ${name} not found`);
    }
    
    if (!factory.canHandle(type)) {
      throw new Error(`Factory ${name} cannot handle type ${type}`);
    }
    
    return factory.create(type, ...args);
  }
}

// 使用示例
const registry = new FactoryRegistry();
registry.register('http', new HttpClientFactory());

const client = registry.create<HttpClient>('http', 'axios', {
  baseURL: 'https://api.example.com'
});

类型生成和导出

自动类型生成

// type-generator.ts
import * as ts from 'typescript';
import * as fs from 'fs';

export class TypeGenerator {
  private program: ts.Program;
  private checker: ts.TypeChecker;
  
  constructor(configPath: string) {
    const config = ts.readConfigFile(configPath, ts.sys.readFile);
    const parseResult = ts.parseJsonConfigFileContent(
      config.config,
      ts.sys,
      '.'
    );
    
    this.program = ts.createProgram(
      parseResult.fileNames,
      parseResult.options
    );
    this.checker = this.program.getTypeChecker();
  }
  
  generateApiTypes(sourceFile: string): string {
    const source = this.program.getSourceFile(sourceFile);
    if (!source) {
      throw new Error(`Source file ${sourceFile} not found`);
    }
    
    const types: string[] = [];
    
    ts.forEachChild(source, node => {
      if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node)) {
        const type = this.checker.getTypeAtLocation(node);
        const typeString = this.checker.typeToString(type);
        types.push(`export type ${node.name.text} = ${typeString};`);
      }
    });
    
    return types.join('\n');
  }
  
  generateSchemaTypes(schema: any): string {
    // 从JSON Schema生成TypeScript类型
    const generateType = (obj: any, name: string): string => {
      if (obj.type === 'object') {
        const properties = Object.entries(obj.properties || {})
          .map(([key, value]: [string, any]) => {
            const optional = !obj.required?.includes(key) ? '?' : '';
            const type = this.mapJsonSchemaType(value);
            return `${key}${optional}: ${type}`;
          })
          .join(';\n  ');
        
        return `export interface ${name} {\n  ${properties}\n}`;
      }
      
      return `export type ${name} = ${this.mapJsonSchemaType(obj)};`;
    };
    
    return generateType(schema, 'GeneratedType');
  }
  
  private mapJsonSchemaType(schema: any): string {
    switch (schema.type) {
      case 'string':
        return schema.enum ? schema.enum.map((v: string) => `'${v}'`).join(' | ') : 'string';
      case 'number':
      case 'integer':
        return 'number';
      case 'boolean':
        return 'boolean';
      case 'array':
        return `${this.mapJsonSchemaType(schema.items)}[]`;
      case 'object':
        return 'object';
      default:
        return 'unknown';
    }
  }
}

声明文件生成

// declaration-bundler.ts
export class DeclarationBundler {
  constructor(private options: {
    input: string;
    output: string;
    external?: string[];
  }) {}
  
  async bundle(): Promise<void> {
    const program = ts.createProgram([this.options.input], {
      declaration: true,
      emitDeclarationOnly: true,
      moduleResolution: ts.ModuleResolutionKind.NodeJs,
      module: ts.ModuleKind.ES2022,
      target: ts.ScriptTarget.ES2020
    });
    
    const declarations = new Map<string, string>();
    
    // 收集所有声明
    program.emit(undefined, (fileName, text) => {
      if (fileName.endsWith('.d.ts')) {
        declarations.set(fileName, text);
      }
    });
    
    // 合并声明
    const bundled = this.mergeDeclarations(declarations);
    
    // 写入输出文件
    fs.writeFileSync(this.options.output, bundled);
  }
  
  private mergeDeclarations(declarations: Map<string, string>): string {
    const imports = new Set<string>();
    const exports = new Set<string>();
    let content = '';
    
    for (const [fileName, text] of declarations) {
      // 解析导入导出
      const lines = text.split('\n');
      
      for (const line of lines) {
        if (line.startsWith('import ')) {
          imports.add(line);
        } else if (line.startsWith('export ')) {
          exports.add(line);
        } else if (line.trim() && !line.startsWith('//')) {
          content += line + '\n';
        }
      }
    }
    
    // 组装最终输出
    const result = [
      ...Array.from(imports),
      '',
      content,
      '',
      ...Array.from(exports)
    ].join('\n');
    
    return result;
  }
}

编译优化

增量编译

// incremental-compiler.ts
export class IncrementalCompiler {
  private program: ts.SemanticDiagnosticsBuilderProgram;
  private host: ts.CompilerHost;
  
  constructor(private configPath: string) {
    this.setupCompiler();
  }
  
  private setupCompiler(): void {
    const config = ts.readConfigFile(this.configPath, ts.sys.readFile);
    const parseResult = ts.parseJsonConfigFileContent(
      config.config,
      ts.sys,
      '.'
    );
    
    this.host = ts.createIncrementalCompilerHost(parseResult.options);
    
    this.program = ts.createIncrementalProgram({
      rootNames: parseResult.fileNames,
      options: {
        ...parseResult.options,
        incremental: true,
        tsBuildInfoFile: '.tsbuildinfo'
      },
      host: this.host
    });
  }
  
  compile(): ts.Diagnostic[] {
    const emitResult = this.program.emit();
    const diagnostics = [
      ...this.program.getConfigFileParsingDiagnostics(),
      ...this.program.getSyntacticDiagnostics(),
      ...this.program.getSemanticDiagnostics(),
      ...emitResult.diagnostics
    ];
    
    return diagnostics;
  }
  
  getAffectedFiles(): string[] {
    const affectedFiles: string[] = [];
    
    while (true) {
      const result = this.program.getSemanticDiagnosticsOfNextAffectedFile();
      if (!result) break;
      
      if (result.affected.fileName) {
        affectedFiles.push(result.affected.fileName);
      }
    }
    
    return affectedFiles;
  }
  
  watchMode(callback: (diagnostics: ts.Diagnostic[]) => void): void {
    const watchProgram = ts.createWatchProgram(
      ts.createWatchCompilerHost(
        this.configPath,
        {},
        ts.sys,
        ts.createSemanticDiagnosticsBuilderProgram,
        (diagnostic) => callback([diagnostic]),
        (diagnostic) => callback([diagnostic])
      )
    );
  }
}

并行类型检查

// parallel-type-checker.ts
import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';

if (!isMainThread) {
  // Worker线程
  const { files, options } = workerData;
  
  const program = ts.createProgram(files, options);
  const diagnostics = ts.getPreEmitDiagnostics(program);
  
  parentPort?.postMessage({
    diagnostics: diagnostics.map(d => ({
      file: d.file?.fileName,
      start: d.start,
      length: d.length,
      messageText: d.messageText,
      category: d.category,
      code: d.code
    }))
  });
} else {
  // 主线程
  export class ParallelTypeChecker {
    async checkFiles(files: string[], options: ts.CompilerOptions): Promise<ts.Diagnostic[]> {
      const chunkSize = Math.ceil(files.length / 4);
      const chunks = [];
      
      for (let i = 0; i < files.length; i += chunkSize) {
        chunks.push(files.slice(i, i + chunkSize));
      }
      
      const workers = chunks.map(chunk => 
        new Worker(__filename, {
          workerData: { files: chunk, options }
        })
      );
      
      const results = await Promise.all(
        workers.map(worker => 
          new Promise<{ diagnostics: any[] }>((resolve, reject) => {
            worker.on('message', resolve);
            worker.on('error', reject);
          })
        )
      );
      
      // 清理workers
      workers.forEach(worker => worker.terminate());
      
      // 合并结果
      return results.flatMap(result => result.diagnostics);
    }
  }
}

构建工具集成

Webpack集成

// webpack.config.ts
import type { Configuration } from 'webpack';
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';

const config: Configuration = {
  entry: './src/index.ts',
  
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: [
          {
            loader: 'ts-loader',
            options: {
              // 只进行转译,类型检查交给ForkTsCheckerWebpackPlugin
              transpileOnly: true,
              
              // 编译选项覆盖
              compilerOptions: {
                module: 'esnext',
                target: 'es2020'
              }
            }
          }
        ],
        exclude: /node_modules/
      }
    ]
  },
  
  plugins: [
    new ForkTsCheckerWebpackPlugin({
      typescript: {
        configFile: 'tsconfig.json',
        diagnosticOptions: {
          semantic: true,
          syntactic: true
        }
      },
      
      // ESLint集成
      eslint: {
        files: './src/**/*.{ts,tsx,js,jsx}'
      }
    })
  ],
  
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
    alias: {
      '@': path.resolve(__dirname, 'src')
    }
  }
};

export default config;

Vite集成

// vite.config.ts
import { defineConfig } from 'vite';
import typescript from '@rollup/plugin-typescript';

export default defineConfig({
  plugins: [
    typescript({
      tsconfig: './tsconfig.json',
      
      // 类型检查选项
      check: true,
      
      // 声明文件生成
      declaration: true,
      declarationDir: 'dist/types',
      
      // 排除测试文件
      exclude: ['**/*.test.ts', '**/*.spec.ts']
    })
  ],
  
  build: {
    lib: {
      entry: 'src/index.ts',
      name: 'MyLib',
      formats: ['es', 'cjs', 'umd']
    },
    
    rollupOptions: {
      external: ['vue', 'react'],
      output: {
        globals: {
          vue: 'Vue',
          react: 'React'
        }
      }
    }
  },
  
  // 类型检查脚本
  define: {
    __VERSION__: JSON.stringify(process.env.npm_package_version)
  }
});

esbuild集成

// esbuild.config.ts
import { build } from 'esbuild';
import { promises as fs } from 'fs';

async function buildWithTypes() {
  // JavaScript构建
  await build({
    entryPoints: ['src/index.ts'],
    bundle: true,
    outfile: 'dist/index.js',
    format: 'esm',
    target: 'es2020',
    
    // TypeScript支持
    loader: {
      '.ts': 'ts',
      '.tsx': 'tsx'
    },
    
    // 外部依赖
    external: ['react', 'react-dom']
  });
  
  // 单独的类型生成
  const tsc = spawn('tsc', [
    '--declaration',
    '--emitDeclarationOnly',
    '--outDir',
    'dist/types'
  ]);
  
  await new Promise((resolve, reject) => {
    tsc.on('close', (code) => {
      if (code === 0) resolve(void 0);
      else reject(new Error(`tsc exited with code ${code}`));
    });
  });
}

buildWithTypes().catch(console.error);

最佳实践

模块组织策略

// 功能模块结构
// src/modules/user/
//   ├── types.ts          // 类型定义
//   ├── service.ts        // 业务逻辑
//   ├── api.ts           // API调用
//   ├── store.ts         // 状态管理
//   └── index.ts         // 模块导出

// types.ts
export interface User {
  id: number;
  name: string;
  email: string;
  roles: Role[];
}

export interface CreateUserRequest {
  name: string;
  email: string;
  password: string;
}

export type UserRole = 'admin' | 'user' | 'guest';

// service.ts
import type { User, CreateUserRequest } from './types';
import { userApi } from './api';

export class UserService {
  async getUser(id: number): Promise<User> {
    return userApi.get(id);
  }
  
  async createUser(data: CreateUserRequest): Promise<User> {
    return userApi.create(data);
  }
}

export const userService = new UserService();

// index.ts - 统一导出
export type * from './types';
export { UserService, userService } from './service';
export { userApi } from './api';

类型安全的配置

// config.ts
interface DatabaseConfig {
  host: string;
  port: number;
  username: string;
  password: string;
  database: string;
}

interface ApiConfig {
  baseURL: string;
  timeout: number;
  retries: number;
}

interface AppConfig {
  database: DatabaseConfig;
  api: ApiConfig;
  features: {
    [K in FeatureFlag]: boolean;
  };
}

type FeatureFlag = 'userManagement' | 'analytics' | 'notifications';

// 配置验证
export function validateConfig(config: unknown): AppConfig {
  // 运行时类型检查
  if (!isObject(config)) {
    throw new Error('Config must be an object');
  }
  
  // 详细验证逻辑...
  return config as AppConfig;
}

// 环境特定配置
export const config: AppConfig = validateConfig({
  database: {
    host: process.env.DB_HOST || 'localhost',
    port: parseInt(process.env.DB_PORT || '5432'),
    username: process.env.DB_USER || 'postgres',
    password: process.env.DB_PASS || '',
    database: process.env.DB_NAME || 'app'
  },
  api: {
    baseURL: process.env.API_URL || 'http://localhost:3000',
    timeout: 5000,
    retries: 3
  },
  features: {
    userManagement: true,
    analytics: process.env.NODE_ENV === 'production',
    notifications: true
  }
});

依赖注入模式

// di-container.ts
type Constructor<T = {}> = new (...args: any[]) => T;
type Token<T> = Constructor<T> | string | symbol;

export class DIContainer {
  private services = new Map<Token<any>, any>();
  private singletons = new Map<Token<any>, any>();
  
  register<T>(token: Token<T>, implementation: Constructor<T>): void {
    this.services.set(token, implementation);
  }
  
  registerSingleton<T>(token: Token<T>, implementation: Constructor<T>): void {
    this.services.set(token, implementation);
    this.singletons.set(token, null);
  }
  
  resolve<T>(token: Token<T>): T {
    if (this.singletons.has(token)) {
      let instance = this.singletons.get(token);
      if (!instance) {
        instance = this.createInstance(token);
        this.singletons.set(token, instance);
      }
      return instance;
    }
    
    return this.createInstance(token);
  }
  
  private createInstance<T>(token: Token<T>): T {
    const implementation = this.services.get(token);
    if (!implementation) {
      throw new Error(`Service ${String(token)} not found`);
    }
    
    // 获取构造函数参数类型
    const dependencies = this.getDependencies(implementation);
    const resolvedDependencies = dependencies.map(dep => this.resolve(dep));
    
    return new implementation(...resolvedDependencies);
  }
  
  private getDependencies(constructor: Constructor): Token<any>[] {
    // 使用reflect-metadata获取依赖
    return Reflect.getMetadata('design:paramtypes', constructor) || [];
  }
}

// 使用装饰器简化注入
export function Injectable<T extends Constructor>(constructor: T) {
  return constructor;
}

export function Inject(token: Token<any>) {
  return function (target: any, propertyKey: string | symbol | undefined, parameterIndex: number) {
    const existingTokens = Reflect.getMetadata('design:paramtypes', target) || [];
    existingTokens[parameterIndex] = token;
    Reflect.defineMetadata('design:paramtypes', existingTokens, target);
  };
}

// 使用示例
@Injectable
export class UserService {
  constructor(
    @Inject('DATABASE') private db: Database,
    @Inject('LOGGER') private logger: Logger
  ) {}
}

TypeScript为JavaScript模块化开发带来了强大的类型系统支持,不仅提高了代码质量和开发效率,还为大型项目的模块化架构提供了坚实的基础。掌握TypeScript的模块系统和最佳实践,是现代前端开发的必备技能。


下一章: 运行环境差异

无构建开发

在现代 Web 开发中,随着浏览器对 ES 模块和现代 CSS 特性的普遍支持,我们可以考虑一种全新的开发方式:无构建开发(No Build Development)。这种方式避免了复杂的构建工具链,直接利用浏览器的原生能力。

本章节的核心理念受到 DHH 文章 “You can’t get faster than No Build” 的启发。

核心理念

“无构建“并不意味着完全没有任何处理步骤,而是指:

  • 避免复杂的编译过程:不需要 Webpack、Rollup、esbuild 等打包工具
  • 利用原生特性:充分使用浏览器已经支持的现代 JavaScript 和 CSS 特性
  • 简化开发流程:减少构建时间,提升开发体验

“You can’t get faster than No Build” - DHH

技术基础

ES 模块原生支持

现代浏览器已经原生支持 ES 模块,无需编译:

<!DOCTYPE html>
<html>
<head>
    <script type="module" src="./main.js"></script>
</head>
</html>
// main.js
import { utils } from './utils.js';
import { API_BASE } from './config.js';

console.log(utils.formatDate(new Date()));

Import Maps

Import Maps 让我们可以使用裸模块说明符,无需打包工具:

<script type="importmap">
{
  "imports": {
    "lodash": "https://cdn.skypack.dev/lodash-es",
    "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js",
    "@/": "./src/"
  }
}
</script>
// 现在可以直接导入
import _ from 'lodash';
import { createApp } from 'vue';
import { helper } from '@/utils/helper.js';

现代 CSS 特性

CSS 嵌套和自定义属性等特性已被广泛支持:

:root {
  --primary-color: #3498db;
  --border-radius: 4px;
}

.card {
  background: white;
  border-radius: var(--border-radius);
  
  .header {
    color: var(--primary-color);
    
    &:hover {
      opacity: 0.8;
    }
  }
}

实践案例

基础项目结构

project/
├── index.html
├── main.js
├── styles.css
├── src/
│   ├── components/
│   │   ├── header.js
│   │   └── footer.js
│   ├── utils/
│   │   └── helpers.js
│   └── api/
│       └── client.js

入口文件

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>无构建开发示例</title>
    <link rel="stylesheet" href="./styles.css">
    
    <script type="importmap">
    {
      "imports": {
        "@/": "./src/",
        "lit": "https://cdn.skypack.dev/lit"
      }
    }
    </script>
    
    <script type="module" src="./main.js"></script>
</head>
<body>
    <div id="app"></div>
</body>
</html>

组件模块

// src/components/header.js
export class HeaderComponent {
  constructor(title) {
    this.title = title;
  }
  
  render() {
    return `
      <header class="app-header">
        <h1>${this.title}</h1>
      </header>
    `;
  }
}
// main.js
import { HeaderComponent } from '@/components/header.js';
import { initializeApp } from '@/utils/helpers.js';

const header = new HeaderComponent('我的应用');
document.getElementById('app').innerHTML = header.render();

initializeApp();

优势与限制

优势

  1. 极速开发体验

    • 无编译等待时间
    • 文件保存即刷新
    • 调试更直观
  2. 简化的工具链

    • 减少依赖包
    • 降低配置复杂度
    • 更少的出错点
  3. 原生性能

    • 浏览器优化的模块加载
    • HTTP/2 多路复用优势
    • 按需加载天然支持

限制与考虑

  1. 兼容性要求

    • 需要支持 ES 模块的现代浏览器
    • Import Maps 的支持相对较新
  2. 开发约束

    • 不能使用需要编译的语法(JSX、TypeScript)
    • 第三方库需要有 ES 模块版本
  3. 生产环境

    • 可能需要 HTTP/2 支持
    • 文件数量较多时的网络开销

适用场景

适合的项目

  • 原型开发:快速验证想法
  • 小型项目:复杂度不高的应用
  • 教学演示:减少学习曲线
  • 内部工具:控制运行环境的场景

不适合的场景

  • 大型团队项目:需要严格的类型检查和规范
  • 复杂框架应用:依赖大量编译时优化
  • 向后兼容要求:需要支持旧版浏览器

工具支持

开发服务器

简单的 HTTP 服务器即可,支持 ES 模块:

# Python
python -m http.server 8080

# Node.js
npx http-server

# 或使用支持模块的开发服务器
npx es-dev-server

编辑器配置

现代编辑器已经很好地支持原生 ES 模块:

// .vscode/settings.json
{
  "typescript.preferences.includePackageJsonAutoImports": "on",
  "typescript.suggest.autoImports": true
}

与传统构建的对比

方面无构建开发传统构建
启动速度即时需要编译时间
热更新原生支持需要工具支持
调试体验直接调试源码需要 Source Map
部署复杂度简单需要构建步骤
文件大小未压缩优化压缩
兼容性现代浏览器可配置目标

总结

无构建开发代表了 Web 开发的一种回归:回归到 Web 平台的原生能力。随着浏览器对现代标准的支持越来越完善,这种开发方式在特定场景下提供了极佳的开发体验。

选择是否使用无构建开发,需要根据项目的具体需求、团队技能和目标用户来权衡。它不是银弹,但确实为我们提供了一个值得考虑的选择。

最好的构建工具就是不需要构建工具。

浏览器中的模块

浏览器作为 JavaScript 模块化的重要运行环境,经历了从无原生支持到全面支持 ES 模块的发展历程。现代浏览器对模块的支持为前端开发带来了革命性的变化。

浏览器模块支持演进

历史发展阶段

1. 早期阶段 (2009-2014)

  • 无原生模块支持:浏览器只能执行单个 JS 文件
  • 全局变量依赖:通过全局对象共享代码
  • 手动依赖管理:开发者需要手动控制脚本加载顺序
<!-- 早期的依赖管理方式 -->
<script src="jquery.js"></script>
<script src="bootstrap.js"></script> <!-- 依赖 jQuery -->
<script src="app.js"></script>      <!-- 依赖前两者 -->

2. AMD/RequireJS 时代 (2011-2015)

  • 异步模块定义:RequireJS 提供浏览器端模块化
  • 动态加载:支持按需加载模块
  • 依赖声明:明确的依赖关系管理
// AMD 模块定义
define(['jquery', 'underscore'], function($, _) {
  return {
    init: function() {
      // 模块逻辑
    }
  };
});

3. 打包工具时代 (2012-2020)

  • Webpack/Browserify:将 Node.js 模块系统带到浏览器
  • 构建时解析:编译时解决模块依赖
  • 单文件输出:打包成一个或几个文件

4. 原生 ES 模块时代 (2017-至今)

  • ES6 模块原生支持:所有现代浏览器支持
  • 静态导入:编译时确定依赖关系
  • 动态导入:运行时按需加载

ES 模块在浏览器中的使用

基础语法支持

静态导入/导出

<!-- 启用模块模式 -->
<script type="module">
  import { utils } from './utils.js';
  import React from './react.js';
  
  console.log(utils.formatDate(new Date()));
</script>
// utils.js
export function formatDate(date) {
  return date.toISOString().split('T')[0];
}

export const API_URL = 'https://api.example.com';

// 默认导出
export default class Logger {
  log(message) {
    console.log(`[${new Date().toISOString()}] ${message}`);
  }
}

动态导入

// 条件加载
async function loadFeature() {
  if (window.innerWidth > 768) {
    const { default: DesktopUI } = await import('./desktop-ui.js');
    return new DesktopUI();
  } else {
    const { default: MobileUI } = await import('./mobile-ui.js');
    return new MobileUI();
  }
}

// 懒加载路由
async function handleRoute(path) {
  switch (path) {
    case '/dashboard':
      const dashboard = await import('./pages/dashboard.js');
      dashboard.render();
      break;
    case '/profile':
      const profile = await import('./pages/profile.js');
      profile.render();
      break;
  }
}

模块解析规则

相对路径导入

// 当前目录
import { config } from './config.js';

// 父目录
import { utils } from '../utils/helpers.js';

// 深层路径
import { Component } from '../../shared/components/Button.js';

绝对路径导入

// 完整 URL
import { lodash } from 'https://cdn.skypack.dev/lodash';

// 从根路径
import { constants } from '/js/constants.js';

Import Maps (现代方案)

<script type="importmap">
{
  "imports": {
    "react": "https://esm.sh/react@18",
    "react-dom": "https://esm.sh/react-dom@18",
    "lodash": "https://cdn.skypack.dev/lodash",
    "@/": "/src/",
    "@utils/": "/src/utils/"
  }
}
</script>

<script type="module">
  // 使用映射的模块名
  import React from 'react';
  import { render } from 'react-dom';
  import { debounce } from 'lodash';
  import { API } from '@/api.js';
  import { helpers } from '@utils/helpers.js';
</script>

浏览器环境特性

网络加载特性

HTTP/2 多路复用

// 现代浏览器可以并行加载多个模块
import('./module1.js');  // 并行请求
import('./module2.js');  // 并行请求  
import('./module3.js');  // 并行请求

// 依赖关系自动解析
import { useEffect } from 'react';     // 先加载
import { Component } from './comp.js'; // 再加载依赖 react 的模块

预加载优化

<!-- 模块预加载 -->
<link rel="modulepreload" href="./critical-module.js">
<link rel="modulepreload" href="./utils.js">

<!-- 预解析 DNS -->
<link rel="dns-prefetch" href="//cdn.example.com">

<!-- 预连接到外部域 -->
<link rel="preconnect" href="https://fonts.googleapis.com">

缓存策略

// 服务器端设置缓存头
// Cache-Control: public, max-age=31536000, immutable

// 版本化文件名
import { utils } from './utils.a1b2c3.js';

// 动态版本控制
const version = '1.2.3';
const module = await import(`./api.${version}.js`);

安全特性

CORS 策略

// 跨域模块加载需要 CORS 支持
try {
  const external = await import('https://external-cdn.com/module.js');
} catch (error) {
  console.error('CORS 错误:', error);
  // 降级方案
  const fallback = await import('./local-fallback.js');
}

CSP (内容安全策略)

<!-- 允许模块脚本 -->
<meta http-equiv="Content-Security-Policy" 
      content="script-src 'self' 'unsafe-inline' https://cdn.skypack.dev;">

<script type="module">
  // 符合 CSP 策略的模块导入
  import { lib } from 'https://cdn.skypack.dev/lib';
</script>

Subresource Integrity (SRI)

<!-- 确保外部模块完整性 -->
<script type="module" 
        src="https://cdn.example.com/module.js"
        integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
        crossorigin="anonymous">
</script>

性能优化策略

代码分割和懒加载

路由级分割

class Router {
  async navigate(path) {
    this.showLoading();
    
    try {
      const route = this.routes[path];
      const module = await import(route.component);
      
      this.hideLoading();
      module.default.render();
    } catch (error) {
      this.showError('页面加载失败');
    }
  }
}

const router = new Router({
  routes: {
    '/': { component: './pages/home.js' },
    '/about': { component: './pages/about.js' },
    '/products': { component: './pages/products.js' }
  }
});

组件级懒加载

class LazyComponent {
  constructor(importFn) {
    this.importFn = importFn;
    this.component = null;
  }
  
  async render(container) {
    if (!this.component) {
      const module = await this.importFn();
      this.component = module.default;
    }
    
    return this.component.render(container);
  }
}

// 使用示例
const HeavyChart = new LazyComponent(() => import('./heavy-chart.js'));

document.querySelector('#load-chart').addEventListener('click', async () => {
  await HeavyChart.render(document.querySelector('#chart-container'));
});

资源优先级管理

Critical Resource First

<!-- 关键资源优先加载 -->
<link rel="modulepreload" href="./critical-app.js" as="script">
<link rel="modulepreload" href="./critical-styles.js" as="script">

<script type="module">
  // 立即加载关键模块
  import('./critical-app.js').then(app => app.init());
  
  // 延迟加载非关键模块
  setTimeout(() => {
    import('./analytics.js');
    import('./social-widgets.js');
  }, 1000);
</script>

Intersection Observer 懒加载

class ModuleLazyLoader {
  constructor() {
    this.observer = new IntersectionObserver(this.handleIntersection.bind(this));
  }
  
  observe(element, moduleUrl) {
    element.dataset.moduleUrl = moduleUrl;
    this.observer.observe(element);
  }
  
  async handleIntersection(entries) {
    for (const entry of entries) {
      if (entry.isIntersecting) {
        const moduleUrl = entry.target.dataset.moduleUrl;
        const module = await import(moduleUrl);
        
        module.default.render(entry.target);
        this.observer.unobserve(entry.target);
      }
    }
  }
}

// 使用示例
const loader = new ModuleLazyLoader();
loader.observe(document.querySelector('#feature-section'), './feature.js');

错误处理和调试

模块加载错误处理

class ModuleLoader {
  async loadModule(url, retries = 3) {
    for (let i = 0; i < retries; i++) {
      try {
        return await import(url);
      } catch (error) {
        console.warn(`模块加载失败 (${i + 1}/${retries}):`, error);
        
        if (i === retries - 1) {
          // 最终失败,尝试降级方案
          return this.loadFallback(url);
        }
        
        // 重试前等待
        await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
      }
    }
  }
  
  async loadFallback(originalUrl) {
    const fallbackMap = {
      './analytics.js': './analytics-lite.js',
      './charts.js': './charts-basic.js'
    };
    
    const fallbackUrl = fallbackMap[originalUrl];
    if (fallbackUrl) {
      console.info('使用降级模块:', fallbackUrl);
      return import(fallbackUrl);
    }
    
    throw new Error(`无法加载模块: ${originalUrl}`);
  }
}

开发环境调试

// 开发环境模块热重载
if (import.meta.hot) {
  import.meta.hot.accept('./utils.js', (newModule) => {
    console.log('模块已更新:', newModule);
    // 重新初始化使用该模块的组件
    app.updateUtils(newModule);
  });
}

// 模块加载性能监控
const moduleLoadTimes = new Map();

const originalImport = window.import;
window.import = function(url) {
  const startTime = performance.now();
  
  return originalImport(url).then(module => {
    const loadTime = performance.now() - startTime;
    moduleLoadTimes.set(url, loadTime);
    console.log(`模块 ${url} 加载耗时: ${loadTime.toFixed(2)}ms`);
    return module;
  });
};

兼容性和降级策略

浏览器支持检测

// 检测 ES 模块支持
function supportsESModules() {
  try {
    return typeof importFn === 'function' || 'noModule' in document.createElement('script');
  } catch {
    return false;
  }
}

// 渐进式加载策略
if (supportsESModules()) {
  // 现代浏览器:使用 ES 模块
  import('./modern-app.js').then(app => app.init());
} else {
  // 旧浏览器:加载打包后的版本
  const script = document.createElement('script');
  script.src = './legacy-app.bundle.js';
  document.head.appendChild(script);
}

Polyfill 和 Shim

<!-- 使用 nomodule 为旧浏览器提供降级 -->
<script type="module" src="./modern-app.js"></script>
<script nomodule src="./legacy-app.js"></script>

<!-- SystemJS 作为 ES 模块 polyfill -->
<script src="https://unpkg.com/systemjs/dist/system.min.js"></script>
<script>
  if (!window.supportsESModules) {
    System.import('./app.js');
  }
</script>

最佳实践总结

性能优化

  1. 使用 Import Maps 管理模块映射
  2. 实施代码分割 减少初始加载时间
  3. 合理使用预加载 优化关键路径
  4. 监控模块加载性能 识别瓶颈

可维护性

  1. 明确的模块边界 避免循环依赖
  2. 一致的导入路径 使用绝对路径或别名
  3. 适当的错误处理 提供降级方案
  4. 文档化依赖关系 便于团队协作

安全性

  1. 验证外部模块 使用 SRI 确保完整性
  2. 配置 CSP 策略 限制模块来源
  3. 审查第三方依赖 定期更新和检查

浏览器中的模块化为现代 Web 开发提供了强大的能力,合理利用这些特性可以构建出高性能、可维护的 Web 应用。


下一章: Node.js中的模块

Node.js中的模块

Node.js 作为服务端 JavaScript 运行环境,从一开始就支持模块化开发。它经历了从 CommonJS 到 ES 模块的演进,为服务端应用提供了强大的模块管理能力。

Node.js 模块系统演进

发展历程

1. CommonJS 时代 (2009-2017)

  • 原生 CommonJS 支持:Node.js 从创建之初就支持 require/exports
  • 同步加载:适合服务端环境的同步模块加载
  • npm 生态:围绕 CommonJS 建立的庞大包管理生态系统

2. ES 模块支持 (2017-至今)

  • 实验性支持 (Node.js 8.5+):通过 flag 启用 ES 模块
  • 稳定支持 (Node.js 12+):正式支持 ES 模块
  • 双模块系统:CommonJS 和 ES 模块并存

当前状态 (Node.js 18+)

// Node.js 现在同时支持两种模块系统
// CommonJS (默认)
const fs = require('fs');
const { readFile } = require('fs/promises');

// ES 模块 (需要配置)
import fs from 'fs';
import { readFile } from 'fs/promises';

CommonJS 模块系统

基础语法

module.exports 和 exports

// math.js - 多种导出方式
// 1. 单个函数导出
module.exports = function add(a, b) {
  return a + b;
};

// 2. 多个导出
exports.add = (a, b) => a + b;
exports.subtract = (a, b) => a - b;
exports.PI = 3.14159;

// 3. 对象导出
module.exports = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b,
  constants: {
    PI: 3.14159,
    E: 2.71828
  }
};

// 4. 类导出
class Calculator {
  add(a, b) { return a + b; }
  subtract(a, b) { return a - b; }
}

module.exports = Calculator;

require 导入

// 导入方式
// 1. 导入整个模块
const math = require('./math');
console.log(math.add(2, 3)); // 5

// 2. 解构导入
const { add, subtract } = require('./math');
console.log(add(2, 3)); // 5

// 3. 导入类
const Calculator = require('./calculator');
const calc = new Calculator();

// 4. 导入 Node.js 内置模块
const fs = require('fs');
const path = require('path');
const { createServer } = require('http');

模块解析机制

模块查找顺序

// require('./user') 的查找顺序:
// 1. ./user
// 2. ./user.js
// 3. ./user.json
// 4. ./user.node
// 5. ./user/package.json (main字段)
// 6. ./user/index.js
// 7. ./user/index.json
// 8. ./user/index.node

// require('lodash') 的查找顺序:
// 1. 当前目录 node_modules/lodash
// 2. 父目录 ../node_modules/lodash
// 3. 继续向上查找直到根目录
// 4. 全局 node_modules 目录
// 5. Node.js 内置模块

package.json 配置

{
  "name": "my-package",
  "version": "1.0.0",
  "main": "lib/index.js",          // CommonJS 入口
  "exports": {                     // 现代模块解析
    ".": {
      "import": "./esm/index.js",  // ES 模块入口
      "require": "./cjs/index.js"  // CommonJS 入口
    },
    "./utils": {
      "import": "./esm/utils.js",
      "require": "./cjs/utils.js"
    }
  },
  "type": "commonjs"               // 默认模块类型
}

模块缓存机制

// 模块缓存示例
// counter.js
let count = 0;
exports.increment = () => ++count;
exports.getCount = () => count;

// app.js
const counter1 = require('./counter');
const counter2 = require('./counter'); // 从缓存加载

counter1.increment();
console.log(counter2.getCount()); // 1 - 共享状态

// 手动清除缓存
delete require.cache[require.resolve('./counter')];
const counter3 = require('./counter');
console.log(counter3.getCount()); // 0 - 重新初始化

ES 模块系统

配置和启用

package.json 配置

{
  "type": "module",  // 启用 ES 模块模式
  "exports": {
    ".": {
      "import": "./index.js",
      "require": "./index.cjs"
    }
  }
}

文件扩展名规则

// 在 "type": "module" 项目中:
// .js   - ES 模块
// .mjs  - ES 模块 (明确)
// .cjs  - CommonJS 模块

// 在 "type": "commonjs" 项目中 (默认):
// .js   - CommonJS 模块
// .mjs  - ES 模块
// .cjs  - CommonJS 模块

ES 模块语法

导入导出

// math.mjs
export function add(a, b) {
  return a + b;
}

export const PI = 3.14159;

export default class Calculator {
  multiply(a, b) {
    return a * b;
  }
}

// app.mjs
import Calculator, { add, PI } from './math.mjs';
import * as math from './math.mjs';

const calc = new Calculator();
console.log(add(2, 3));
console.log(calc.multiply(4, 5));

动态导入

// 条件导入
async function loadModule(env) {
  if (env === 'production') {
    const prod = await import('./config/production.js');
    return prod.config;
  } else {
    const dev = await import('./config/development.js');
    return dev.config;
  }
}

// 懒加载
async function processLargeData(data) {
  // 只在需要时加载重型模块
  const { heavyProcessor } = await import('./heavy-processor.js');
  return heavyProcessor.process(data);
}

内置模块导入

// ES 模块方式导入内置模块
import { readFile, writeFile } from 'fs/promises';
import { join, dirname } from 'path';
import { createServer } from 'http';
import { fileURLToPath } from 'url';

// 获取当前文件路径 (ES 模块中没有 __dirname)
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// 使用 import() 动态导入 CommonJS 模块
const config = await import('./config.json', {
  assert: { type: 'json' }
});

双模块系统互操作

CommonJS 导入 ES 模块

// CommonJS 文件 (.cjs 或在 type: "commonjs" 项目中)
async function loadESModule() {
  // 只能通过动态导入
  const { default: Calculator, add } = await import('./math.mjs');
  
  const calc = new Calculator();
  return calc.multiply(add(2, 3), 4);
}

// 不能使用 require() 导入 ES 模块
// const math = require('./math.mjs'); // 错误!

ES 模块导入 CommonJS

// ES 模块文件 (.mjs 或在 type: "module" 项目中)
// 1. 默认导入
import fs from 'fs';              // 整个 exports 对象
import express from 'express';    // CommonJS 库

// 2. 命名导入 (如果支持)
import { readFile } from 'fs/promises';

// 3. 动态导入
const lodash = await import('lodash');
const _ = lodash.default;

// 4. 创建 require 函数 (兼容方案)
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const oldModule = require('./old-commonjs-module');

Node.js 特有特性

文件系统操作

// ES 模块中的文件操作
import { readFile, writeFile } from 'fs/promises';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

async function processFile() {
  const configPath = join(__dirname, 'config.json');
  const data = await readFile(configPath, 'utf8');
  const config = JSON.parse(data);
  
  // 处理配置...
  
  await writeFile(configPath, JSON.stringify(config, null, 2));
}

环境变量和配置

// 环境配置模块
// config.js
const config = {
  port: process.env.PORT || 3000,
  database: {
    host: process.env.DB_HOST || 'localhost',
    port: process.env.DB_PORT || 5432,
    name: process.env.DB_NAME || 'myapp'
  },
  jwt: {
    secret: process.env.JWT_SECRET || 'default-secret',
    expiresIn: process.env.JWT_EXPIRES || '1h'
  }
};

export default config;

// app.js
import config from './config.js';
import { createServer } from 'http';

const server = createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ message: 'Server running', config }));
});

server.listen(config.port, () => {
  console.log(`Server running on port ${config.port}`);
});

进程管理和集群

// cluster.js - 多进程管理
import cluster from 'cluster';
import { cpus } from 'os';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const numCPUs = cpus().length;

if (cluster.isPrimary) {
  console.log(`Primary ${process.pid} is running`);
  
  // Fork workers
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
  
  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died`);
    cluster.fork(); // 重启死掉的worker
  });
  
} else {
  // Worker 进程
  const { startApp } = await import('./app.js');
  startApp();
  
  console.log(`Worker ${process.pid} started`);
}

性能优化策略

模块预加载和缓存

// 模块预加载器
class ModulePreloader {
  constructor() {
    this.cache = new Map();
  }
  
  async preload(moduleUrls) {
    const promises = moduleUrls.map(async url => {
      try {
        const module = await import(url);
        this.cache.set(url, module);
        return { url, success: true };
      } catch (error) {
        console.warn(`Failed to preload ${url}:`, error);
        return { url, success: false, error };
      }
    });
    
    return Promise.all(promises);
  }
  
  get(url) {
    return this.cache.get(url);
  }
}

// 使用示例
const preloader = new ModulePreloader();
await preloader.preload([
  './heavy-computation.js',
  './database-utils.js',
  './email-service.js'
]);

代码分割和懒加载

// 路由懒加载
class Router {
  constructor() {
    this.routes = new Map();
  }
  
  register(path, moduleUrl) {
    this.routes.set(path, moduleUrl);
  }
  
  async handle(request) {
    const url = new URL(request.url, 'http://localhost');
    const moduleUrl = this.routes.get(url.pathname);
    
    if (!moduleUrl) {
      return { status: 404, body: 'Not Found' };
    }
    
    try {
      const module = await import(moduleUrl);
      return await module.default(request);
    } catch (error) {
      console.error('Route handler error:', error);
      return { status: 500, body: 'Internal Server Error' };
    }
  }
}

// 使用示例
const router = new Router();
router.register('/api/users', './handlers/users.js');
router.register('/api/posts', './handlers/posts.js');
router.register('/api/auth', './handlers/auth.js');

内存管理

// 内存高效的模块加载
import { Worker, isMainThread, parentPort } from 'worker_threads';

class MemoryEfficientProcessor {
  constructor() {
    this.workers = [];
    this.taskQueue = [];
  }
  
  async processInWorker(data, moduleUrl) {
    return new Promise((resolve, reject) => {
      const worker = new Worker(`
        import { parentPort } from 'worker_threads';
        import processor from '${moduleUrl}';
        
        parentPort.on('message', async (data) => {
          try {
            const result = await processor.process(data);
            parentPort.postMessage({ success: true, result });
          } catch (error) {
            parentPort.postMessage({ success: false, error: error.message });
          }
          process.exit(0); // 处理完后立即退出,释放内存
        });
      `, { eval: true });
      
      worker.postMessage(data);
      
      worker.on('message', ({ success, result, error }) => {
        if (success) {
          resolve(result);
        } else {
          reject(new Error(error));
        }
      });
      
      worker.on('error', reject);
    });
  }
}

调试和开发工具

调试配置

// debug.js - 开发环境调试工具
import { inspect } from 'util';

export class Debug {
  constructor(namespace) {
    this.namespace = namespace;
    this.enabled = process.env.DEBUG?.includes(namespace) || false;
  }
  
  log(...args) {
    if (!this.enabled) return;
    
    const timestamp = new Date().toISOString();
    const prefix = `[${timestamp}] ${this.namespace}:`;
    
    console.log(prefix, ...args.map(arg => 
      typeof arg === 'object' ? inspect(arg, { colors: true, depth: 3 }) : arg
    ));
  }
  
  error(...args) {
    const timestamp = new Date().toISOString();
    const prefix = `[${timestamp}] ${this.namespace}:ERROR`;
    console.error(prefix, ...args);
  }
}

// 使用示例
const debug = new Debug('app:server');
debug.log('Server starting...', { port: 3000, env: process.env.NODE_ENV });

性能监控

// performance.js
import { performance, PerformanceObserver } from 'perf_hooks';

export class PerformanceMonitor {
  constructor() {
    this.metrics = new Map();
    this.setupObserver();
  }
  
  setupObserver() {
    const obs = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        this.recordMetric(entry.name, entry.duration);
      }
    });
    obs.observe({ entryTypes: ['measure'] });
  }
  
  start(name) {
    performance.mark(`${name}-start`);
  }
  
  end(name) {
    performance.mark(`${name}-end`);
    performance.measure(name, `${name}-start`, `${name}-end`);
  }
  
  recordMetric(name, value) {
    if (!this.metrics.has(name)) {
      this.metrics.set(name, []);
    }
    this.metrics.get(name).push(value);
  }
  
  getAverageTime(name) {
    const times = this.metrics.get(name) || [];
    return times.length > 0 ? times.reduce((a, b) => a + b) / times.length : 0;
  }
}

// 使用示例
const monitor = new PerformanceMonitor();

async function loadHeavyModule() {
  monitor.start('module-load');
  const module = await import('./heavy-module.js');
  monitor.end('module-load');
  return module;
}

打包和部署

生产环境优化

// build.js - 构建脚本
import { build } from 'esbuild';
import { readdir, stat } from 'fs/promises';
import { join } from 'path';

async function findEntryPoints(dir) {
  const entries = [];
  const files = await readdir(dir);
  
  for (const file of files) {
    const filePath = join(dir, file);
    const stats = await stat(filePath);
    
    if (stats.isFile() && file.endsWith('.js')) {
      entries.push(filePath);
    }
  }
  
  return entries;
}

async function buildForProduction() {
  const entryPoints = await findEntryPoints('./src');
  
  await build({
    entryPoints,
    bundle: true,
    minify: true,
    sourcemap: false,
    target: 'node18',
    platform: 'node',
    format: 'esm',
    outdir: './dist',
    external: ['sharp', 'canvas'] // 不打包的原生依赖
  });
  
  console.log('Build completed successfully!');
}

buildForProduction().catch(console.error);

Docker 配置

# Dockerfile
FROM node:18-alpine

WORKDIR /app

# 复制 package 文件
COPY package*.json ./

# 安装依赖
RUN npm ci --only=production

# 复制源码
COPY dist/ ./dist/

# 设置环境变量
ENV NODE_ENV=production

# 启动应用
CMD ["node", "dist/index.js"]

最佳实践总结

模块组织

  1. 清晰的模块边界 - 每个模块职责单一
  2. 一致的导入路径 - 使用绝对路径或路径别名
  3. 避免循环依赖 - 通过依赖注入或重构解决

性能优化

  1. 合理使用缓存 - 利用模块缓存机制
  2. 懒加载非关键模块 - 提高启动速度
  3. Worker 隔离 - 防止内存泄漏

生产环境

  1. 环境配置分离 - 不同环境使用不同配置
  2. 错误处理完善 - 模块加载失败的降级方案
  3. 监控和日志 - 跟踪模块性能和错误

Node.js 的模块系统为服务端 JavaScript 开发提供了强大的能力,合理使用这些特性可以构建出高性能、可维护的后端应用。


下一章: Deno的模块系统

Deno的模块系统

Deno 是一个现代的 JavaScript/TypeScript 运行时,由 Node.js 的创始人 Ryan Dahl 开发。它从设计之初就原生支持 ES 模块,并采用了基于 URL 的模块导入系统,为模块化开发带来了全新的体验。

Deno 模块系统特点

核心设计理念

1. 原生 ES 模块支持

  • ES 模块优先:Deno 从一开始就完全支持 ES 模块
  • 无需 package.json:不依赖 npm 的包管理模式
  • 标准化导入:使用标准的 import/export 语法

2. 基于 URL 的模块导入

  • 去中心化:模块可以从任何 URL 导入
  • 明确的依赖:依赖关系通过 URL 明确表达
  • 版本控制:URL 中包含版本信息

3. 安全优先

  • 权限系统:模块需要明确的权限才能访问系统资源
  • 沙盒执行:默认情况下模块运行在受限环境中

基础模块语法

标准导入导出

// math.ts
export function add(a: number, b: number): number {
  return a + b;
}

export const PI = 3.14159;

export default class Calculator {
  multiply(a: number, b: number): number {
    return a * b;
  }
}

// app.ts
import Calculator, { add, PI } from './math.ts';
import * as math from './math.ts';

const calc = new Calculator();
console.log(add(2, 3));
console.log(calc.multiply(4, 5));

URL 导入

// 从远程 URL 导入
import { serve } from 'https://deno.land/std@0.200.0/http/server.ts';
import { parse } from 'https://deno.land/std@0.200.0/flags/mod.ts';

// 从 CDN 导入第三方库
import lodash from 'https://cdn.skypack.dev/lodash@4.17.21';
import React from 'https://esm.sh/react@18.2.0';

// 使用具体版本确保稳定性
import { assertEquals } from 'https://deno.land/std@0.200.0/testing/asserts.ts';

相对路径导入

// 项目结构
// src/
//   ├── utils/
//   │   ├── helpers.ts
//   │   └── constants.ts
//   ├── components/
//   │   └── Button.ts
//   └── app.ts

// app.ts
import { formatDate } from './utils/helpers.ts';
import { API_URL } from './utils/constants.ts';
import Button from './components/Button.ts';

// helpers.ts
export function formatDate(date: Date): string {
  return date.toISOString().split('T')[0];
}

// 注意:Deno 要求完整的文件扩展名
import { config } from './config.ts'; // ✅ 正确
import { config } from './config';     // ❌ 错误

依赖管理

deps.ts 模式

// deps.ts - 集中管理外部依赖
export { serve } from 'https://deno.land/std@0.200.0/http/server.ts';
export { parse } from 'https://deno.land/std@0.200.0/flags/mod.ts';
export { join, dirname } from 'https://deno.land/std@0.200.0/path/mod.ts';
export { assert, assertEquals } from 'https://deno.land/std@0.200.0/testing/asserts.ts';

// 重新导出并重命名
export { default as lodash } from 'https://cdn.skypack.dev/lodash@4.17.21';
export { default as React } from 'https://esm.sh/react@18.2.0';

// 应用代码中使用
// app.ts
import { serve, parse, React } from './deps.ts';

const handler = (req: Request) => {
  const url = new URL(req.url);
  const params = parse(url.searchParams.toString());
  
  return new Response(`Hello ${params.name || 'World'}!`);
};

serve(handler, { port: 8000 });

Import Maps

// import_map.json
{
  "imports": {
    "std/": "https://deno.land/std@0.200.0/",
    "fmt/": "https://deno.land/std@0.200.0/fmt/",
    "testing/": "https://deno.land/std@0.200.0/testing/",
    "react": "https://esm.sh/react@18.2.0",
    "react-dom": "https://esm.sh/react-dom@18.2.0",
    "@/": "./src/"
  }
}
// 使用 Import Maps 后的代码
import { serve } from 'std/http/server.ts';
import { red, bold } from 'fmt/colors.ts';
import { assertEquals } from 'testing/asserts.ts';
import React from 'react';
import { config } from '@/config.ts';

// 启动时指定 import map
// deno run --import-map=import_map.json app.ts

deno.json 配置

{
  "compilerOptions": {
    "allowJs": true,
    "lib": ["deno.window"],
    "strict": true
  },
  "importMap": "./import_map.json",
  "tasks": {
    "dev": "deno run --watch --allow-net --allow-read app.ts",
    "test": "deno test --allow-all",
    "fmt": "deno fmt",
    "lint": "deno lint"
  },
  "fmt": {
    "files": {
      "include": ["src/", "tests/"],
      "exclude": ["dist/"]
    }
  },
  "lint": {
    "files": {
      "include": ["src/"],
      "exclude": ["dist/"]
    }
  }
}

内置模块和标准库

Deno 标准库

// 文件系统操作
import { copy, ensureDir, exists } from 'std/fs/mod.ts';
import { readLines } from 'std/io/mod.ts';

// 网络请求
import { serve } from 'std/http/server.ts';
import { Status } from 'std/http/http_status.ts';

// 路径处理
import { join, dirname, basename, extname } from 'std/path/mod.ts';

// 日期时间
import { format, parse } from 'std/datetime/mod.ts';

// 编码解码
import { encode, decode } from 'std/encoding/base64.ts';
import { stringify, parse as parseCSV } from 'std/encoding/csv.ts';

// 测试工具
import { assertEquals, assertThrows } from 'std/testing/asserts.ts';
import { FakeTime } from 'std/testing/time.ts';

// 示例:文件服务器
async function fileServer() {
  const handler = async (req: Request): Promise<Response> => {
    const url = new URL(req.url);
    const filePath = join('.', url.pathname);
    
    if (await exists(filePath)) {
      const file = await Deno.readFile(filePath);
      return new Response(file);
    }
    
    return new Response('Not Found', { status: Status.NotFound });
  };
  
  console.log('Server running on http://localhost:8000');
  await serve(handler, { port: 8000 });
}

Web APIs

// Deno 支持现代 Web APIs
// Fetch API
const response = await fetch('https://api.github.com/users/denoland');
const data = await response.json();

// Web Streams
const readable = new ReadableStream({
  start(controller) {
    controller.enqueue('Hello ');
    controller.enqueue('World!');
    controller.close();
  }
});

// URL 构造器
const url = new URL('/api/users', 'https://example.com');
url.searchParams.set('page', '1');

// 加密 API
const data = new TextEncoder().encode('hello world');
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');

动态导入和代码分割

动态导入

// 条件导入
async function loadEnvironmentConfig(env: string) {
  switch (env) {
    case 'development':
      const devConfig = await import('./config/development.ts');
      return devConfig.default;
    case 'production':
      const prodConfig = await import('./config/production.ts');
      return prodConfig.default;
    default:
      throw new Error(`Unknown environment: ${env}`);
  }
}

// 懒加载模块
async function processData(data: unknown[]) {
  // 只在需要时加载数据处理模块
  const { DataProcessor } = await import('./utils/data-processor.ts');
  const processor = new DataProcessor();
  return processor.process(data);
}

// 插件系统
class PluginManager {
  private plugins = new Map<string, unknown>();
  
  async loadPlugin(name: string, url: string) {
    try {
      const plugin = await import(url);
      this.plugins.set(name, plugin.default);
      console.log(`Plugin ${name} loaded successfully`);
    } catch (error) {
      console.error(`Failed to load plugin ${name}:`, error);
    }
  }
  
  getPlugin(name: string) {
    return this.plugins.get(name);
  }
}

代码分割策略

// 路由级代码分割
class Router {
  private routes = new Map<string, () => Promise<{ default: Function }>>();
  
  register(path: string, moduleUrl: string) {
    this.routes.set(path, () => import(moduleUrl));
  }
  
  async handle(request: Request): Promise<Response> {
    const url = new URL(request.url);
    const routeLoader = this.routes.get(url.pathname);
    
    if (!routeLoader) {
      return new Response('Not Found', { status: 404 });
    }
    
    try {
      const module = await routeLoader();
      return await module.default(request);
    } catch (error) {
      console.error('Route handler error:', error);
      return new Response('Internal Server Error', { status: 500 });
    }
  }
}

// 使用示例
const router = new Router();
router.register('/api/users', './routes/users.ts');
router.register('/api/posts', './routes/posts.ts');
router.register('/health', './routes/health.ts');

权限系统

权限模型

// Deno 的权限系统确保安全性
// 需要明确指定权限才能访问系统资源

// 文件系统权限
// deno run --allow-read --allow-write app.ts

async function readConfig() {
  try {
    const config = await Deno.readTextFile('./config.json');
    return JSON.parse(config);
  } catch (error) {
    if (error instanceof Deno.errors.PermissionDenied) {
      console.error('Permission denied: Cannot read config file');
      console.error('Run with --allow-read flag');
    }
    throw error;
  }
}

// 网络权限
// deno run --allow-net app.ts

async function fetchData(url: string) {
  try {
    const response = await fetch(url);
    return await response.json();
  } catch (error) {
    if (error instanceof Deno.errors.PermissionDenied) {
      console.error('Permission denied: Cannot make network requests');
      console.error('Run with --allow-net flag');
    }
    throw error;
  }
}

// 环境变量权限
// deno run --allow-env app.ts

function getPort(): number {
  try {
    return parseInt(Deno.env.get('PORT') || '8000', 10);
  } catch (error) {
    if (error instanceof Deno.errors.PermissionDenied) {
      console.error('Permission denied: Cannot access environment variables');
      console.error('Run with --allow-env flag');
    }
    return 8000;
  }
}

权限检查和处理

// 运行时权限检查
async function safeFileOperation(path: string) {
  // 检查读取权限
  const readStatus = await Deno.permissions.query({ name: 'read', path });
  if (readStatus.state !== 'granted') {
    // 请求权限
    const readRequest = await Deno.permissions.request({ name: 'read', path });
    if (readRequest.state !== 'granted') {
      throw new Error('Read permission denied');
    }
  }
  
  // 检查写入权限
  const writeStatus = await Deno.permissions.query({ name: 'write', path });
  if (writeStatus.state !== 'granted') {
    const writeRequest = await Deno.permissions.request({ name: 'write', path });
    if (writeRequest.state !== 'granted') {
      throw new Error('Write permission denied');
    }
  }
  
  // 安全地执行文件操作
  const content = await Deno.readTextFile(path);
  const modified = content.toUpperCase();
  await Deno.writeTextFile(path, modified);
}

// 权限包装器
class PermissionWrapper {
  async withPermission<T>(
    permission: Deno.PermissionDescriptor,
    operation: () => Promise<T>
  ): Promise<T> {
    const status = await Deno.permissions.query(permission);
    
    if (status.state === 'denied') {
      throw new Error(`Permission denied: ${permission.name}`);
    }
    
    if (status.state === 'prompt') {
      const request = await Deno.permissions.request(permission);
      if (request.state !== 'granted') {
        throw new Error(`Permission rejected: ${permission.name}`);
      }
    }
    
    return await operation();
  }
}

// 使用示例
const permissionWrapper = new PermissionWrapper();

await permissionWrapper.withPermission(
  { name: 'net', host: 'api.example.com' },
  async () => {
    const response = await fetch('https://api.example.com/data');
    return await response.json();
  }
);

测试和开发工具

内置测试框架

// math_test.ts
import { assertEquals, assertThrows } from 'std/testing/asserts.ts';
import { add, divide } from './math.ts';

Deno.test('addition works correctly', () => {
  assertEquals(add(2, 3), 5);
  assertEquals(add(-1, 1), 0);
  assertEquals(add(0, 0), 0);
});

Deno.test('division works correctly', () => {
  assertEquals(divide(10, 2), 5);
  assertEquals(divide(7, 2), 3.5);
});

Deno.test('division by zero throws error', () => {
  assertThrows(
    () => divide(5, 0),
    Error,
    'Division by zero'
  );
});

// 异步测试
Deno.test('async operation', async () => {
  const result = await fetch('https://httpbin.org/json');
  const data = await result.json();
  assertEquals(typeof data, 'object');
});

// 测试权限
Deno.test({
  name: 'file operation test',
  permissions: { read: true, write: true },
  async fn() {
    await Deno.writeTextFile('./test.txt', 'hello');
    const content = await Deno.readTextFile('./test.txt');
    assertEquals(content, 'hello');
    await Deno.remove('./test.txt');
  }
});

基准测试

// benchmark.ts
import { bench, runBenchmarks } from 'std/testing/bench.ts';

// 简单基准测试
bench('string concatenation', () => {
  let result = '';
  for (let i = 0; i < 1000; i++) {
    result += 'a';
  }
});

bench('array join', () => {
  const arr = [];
  for (let i = 0; i < 1000; i++) {
    arr.push('a');
  }
  arr.join('');
});

// 异步基准测试
bench({
  name: 'async fetch',
  async fn() {
    await fetch('https://httpbin.org/json');
  }
});

// 运行基准测试
if (import.meta.main) {
  runBenchmarks();
}

开发工具集成

// dev.ts - 开发环境工具
import { serve } from 'std/http/server.ts';
import { serveDir } from 'std/http/file_server.ts';

class DevServer {
  private watcher?: Deno.FsWatcher;
  
  async start(port = 8000) {
    console.log(`Dev server starting on http://localhost:${port}`);
    
    // 文件监听和热重载
    this.setupFileWatcher();
    
    const handler = async (req: Request): Promise<Response> => {
      const url = new URL(req.url);
      
      // API 路由
      if (url.pathname.startsWith('/api/')) {
        return await this.handleApiRequest(req);
      }
      
      // 静态文件服务
      return serveDir(req, {
        fsRoot: './public',
        showDirListing: true,
      });
    };
    
    await serve(handler, { port });
  }
  
  private setupFileWatcher() {
    this.watcher = Deno.watchFs(['./src'], { recursive: true });
    
    (async () => {
      for await (const event of this.watcher!) {
        if (event.kind === 'modify') {
          console.log(`File changed: ${event.paths.join(', ')}`);
          // 触发重新加载
          await this.broadcastReload();
        }
      }
    })();
  }
  
  private async handleApiRequest(req: Request): Promise<Response> {
    const url = new URL(req.url);
    
    // 动态加载 API 路由
    try {
      const routePath = `./api${url.pathname.replace('/api', '')}.ts`;
      const module = await import(routePath);
      return await module.default(req);
    } catch (error) {
      console.error('API route error:', error);
      return new Response('API Error', { status: 500 });
    }
  }
  
  private async broadcastReload() {
    // 实现热重载逻辑
    console.log('Broadcasting reload...');
  }
  
  stop() {
    this.watcher?.close();
  }
}

// 启动开发服务器
if (import.meta.main) {
  const server = new DevServer();
  await server.start();
}

性能优化

模块缓存和预编译

// 缓存管理
class ModuleCache {
  private cache = new Map<string, unknown>();
  private loading = new Map<string, Promise<unknown>>();
  
  async load<T>(url: string): Promise<T> {
    // 检查缓存
    if (this.cache.has(url)) {
      return this.cache.get(url) as T;
    }
    
    // 检查是否正在加载
    if (this.loading.has(url)) {
      return await this.loading.get(url) as T;
    }
    
    // 开始加载
    const loadPromise = this.loadModule<T>(url);
    this.loading.set(url, loadPromise);
    
    try {
      const module = await loadPromise;
      this.cache.set(url, module);
      return module;
    } finally {
      this.loading.delete(url);
    }
  }
  
  private async loadModule<T>(url: string): Promise<T> {
    const module = await import(url);
    return module.default || module;
  }
  
  invalidate(url: string) {
    this.cache.delete(url);
  }
  
  clear() {
    this.cache.clear();
    this.loading.clear();
  }
}

// 使用示例
const moduleCache = new ModuleCache();

async function loadProcessor() {
  return await moduleCache.load('./processors/heavy-processor.ts');
}

Bundle 和部署

// build.ts - 生产环境构建脚本
import { bundle } from 'https://deno.land/x/emit@0.26.0/mod.ts';

async function buildForProduction() {
  console.log('Building for production...');
  
  const result = await bundle('./src/app.ts', {
    compilerOptions: {
      sourceMap: false,
      target: 'ES2022',
    },
  });
  
  // 写入构建结果
  await Deno.writeTextFile('./dist/app.js', result.code);
  
  // 生成元数据
  const metadata = {
    buildTime: new Date().toISOString(),
    version: '1.0.0',
    modules: result.files,
  };
  
  await Deno.writeTextFile(
    './dist/metadata.json',
    JSON.stringify(metadata, null, 2)
  );
  
  console.log('Build completed successfully!');
}

// Docker 部署配置
const dockerFile = `
FROM denoland/deno:1.37.0

WORKDIR /app

# 缓存依赖
COPY deps.ts ./
RUN deno cache deps.ts

# 复制源码
COPY . .

# 缓存入口文件
RUN deno cache app.ts

# 运行应用
CMD ["run", "--allow-net", "--allow-read", "app.ts"]
`;

await Deno.writeTextFile('./Dockerfile', dockerFile);

最佳实践总结

模块组织

  1. 使用 deps.ts 集中管理依赖 - 便于版本控制和更新
  2. Import Maps 简化导入路径 - 提高代码可读性
  3. 明确的文件扩展名 - Deno 要求完整的文件扩展名

安全性

  1. 最小权限原则 - 只授予必要的权限
  2. 运行时权限检查 - 优雅处理权限不足的情况
  3. URL 验证 - 验证远程模块的来源和完整性

性能优化

  1. 模块缓存 - 避免重复加载相同模块
  2. 代码分割 - 按需加载非关键模块
  3. 版本锁定 - 使用具体版本确保稳定性

开发体验

  1. 内置工具链 - 充分利用 Deno 的内置功能
  2. TypeScript 优先 - 享受类型安全的开发体验
  3. 标准化格式 - 使用 deno fmtdeno lint

Deno 的模块系统代表了 JavaScript 生态系统的现代化方向,通过标准化的导入机制、强大的安全模型和优秀的开发体验,为构建安全、高效的应用提供了新的可能性。


下一章: 最佳实践

Bun的模块系统

Bun 是一个现代的 JavaScript/TypeScript 运行时和包管理器,由 Jarred Sumner 开发。它以极速性能为目标,原生支持 TypeScript、JSX,并提供了一套完整的工具链,包括打包器、测试运行器和包管理器。

Bun 模块系统特点

核心设计理念

1. 速度优先

  • Zig 编写:使用 Zig 语言编写,性能极佳
  • JavaScriptCore 引擎:使用 Safari 的 JavaScript 引擎
  • 原生优化:针对模块加载和执行进行了大量优化

2. 现代标准支持

  • 原生 ES 模块:完全支持 ES 模块语法
  • TypeScript 内置:无需额外配置即可运行 TypeScript
  • JSX 原生支持:直接运行 JSX 代码

3. Node.js 兼容性

  • API 兼容:兼容大部分 Node.js API
  • npm 生态:支持现有的 npm 包
  • 无缝迁移:现有 Node.js 项目可以轻松迁移

基础模块语法

ES 模块支持

// math.ts
export function add(a: number, b: number): number {
  return a + b;
}

export const PI = 3.14159;

export default class Calculator {
  multiply(a: number, b: number): number {
    return a * b;
  }
}

// app.ts
import Calculator, { add, PI } from './math.ts';
import * as math from './math.ts';

const calc = new Calculator();
console.log(add(2, 3));
console.log(calc.multiply(4, 5));

TypeScript 零配置支持

// 无需 tsconfig.json 即可运行
// types.ts
interface User {
  id: number;
  name: string;
  email: string;
}

export type UserResponse = {
  user: User;
  token: string;
};

// api.ts
import type { UserResponse } from './types.ts';

export async function fetchUser(id: number): Promise<UserResponse> {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}

// 直接运行:bun run api.ts

JSX 原生支持

// component.tsx
interface Props {
  name: string;
  count: number;
}

export function Counter({ name, count }: Props) {
  return (
    <div>
      <h1>Hello {name}!</h1>
      <p>Count: {count}</p>
    </div>
  );
}

// app.tsx
import { Counter } from './component.tsx';

function App() {
  return (
    <div>
      <Counter name="World" count={42} />
    </div>
  );
}

// 直接运行:bun run app.tsx

内置模块和 API

Bun 专有 API

// 文件操作 - Bun.file API
const file = Bun.file('./data.json');
const contents = await file.text();
const data = await file.json();

// 写入文件
await Bun.write('./output.txt', 'Hello Bun!');
await Bun.write('./data.json', { message: 'Hello' });

// 密码哈希
const password = 'secret123';
const hash = await Bun.password.hash(password);
const isValid = await Bun.password.verify(password, hash);

// 环境变量
const port = Bun.env.PORT || '3000';
const isDev = Bun.env.NODE_ENV === 'development';

高性能 HTTP 服务器

// server.ts
const server = Bun.serve({
  port: 3000,
  async fetch(req) {
    const url = new URL(req.url);
    
    if (url.pathname === '/api/hello') {
      return new Response(JSON.stringify({ message: 'Hello Bun!' }), {
        headers: { 'Content-Type': 'application/json' }
      });
    }
    
    if (url.pathname === '/') {
      return new Response('Hello World!');
    }
    
    return new Response('Not Found', { status: 404 });
  },
});

console.log(`Server running on http://localhost:${server.port}`);

WebSocket 支持

// websocket-server.ts
const server = Bun.serve({
  port: 3001,
  async fetch(req, server) {
    const success = server.upgrade(req);
    if (success) {
      return undefined;
    }
    return new Response('Upgrade failed', { status: 500 });
  },
  websocket: {
    message(ws, message) {
      console.log('Received:', message);
      ws.send(`Echo: ${message}`);
    },
    open(ws) {
      console.log('WebSocket opened');
      ws.subscribe('chat');
    },
    close(ws, code, message) {
      console.log('WebSocket closed');
    }
  }
});

// 客户端代码
const ws = new WebSocket('ws://localhost:3001');
ws.onmessage = (event) => {
  console.log('Received:', event.data);
};
ws.send('Hello WebSocket!');

包管理和依赖

bun install - 极速包管理

# 安装依赖(比 npm 快 20-100 倍)
bun install

# 安装特定包
bun add react react-dom
bun add -d typescript @types/react

# 移除包
bun remove lodash

# 全局安装
bun add -g typescript

兼容 package.json

{
  "name": "my-bun-app",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "bun run --watch src/index.ts",
    "build": "bun build src/index.ts --outdir dist",
    "test": "bun test",
    "start": "bun run src/index.ts"
  },
  "dependencies": {
    "react": "^18.2.0",
    "express": "^4.18.2"
  },
  "devDependencies": {
    "@types/react": "^18.2.0",
    "@types/express": "^4.17.17"
  },
  "trustedDependencies": ["esbuild"]
}

workspaces 支持

// 根目录 package.json
{
  "name": "monorepo",
  "workspaces": ["packages/*"],
  "devDependencies": {
    "typescript": "^5.0.0"
  }
}

// packages/app/package.json
{
  "name": "@monorepo/app",
  "dependencies": {
    "@monorepo/shared": "workspace:*"
  }
}

// packages/shared/package.json
{
  "name": "@monorepo/shared",
  "exports": {
    ".": "./index.ts"
  }
}

模块解析和导入

Node.js 兼容导入

// 兼容 Node.js 模块
import fs from 'fs';
import path from 'path';
import { readFile } from 'fs/promises';

// 兼容 CommonJS
const express = require('express');
const lodash = require('lodash');

// Bun 特有的快速导入
import { Database } from 'bun:sqlite';
import { spawn } from 'bun:subprocess';

路径解析增强

// bun.config.js 或 bunfig.toml
export default {
  preload: ['./setup.ts'],
  external: ['sharp', 'canvas'],
  define: {
    __VERSION__: JSON.stringify('1.0.0'),
    __DEV__: 'true'
  }
};

// 使用路径别名
// tsconfig.json 中的 paths 会被自动识别
import { utils } from '@/utils';
import { components } from '~/components';

动态导入和懒加载

// 条件导入
async function loadRenderer(type: 'server' | 'client') {
  if (type === 'server') {
    const { ServerRenderer } = await import('./server-renderer.ts');
    return new ServerRenderer();
  } else {
    const { ClientRenderer } = await import('./client-renderer.ts');
    return new ClientRenderer();
  }
}

// 插件系统
class PluginLoader {
  private plugins = new Map<string, any>();
  
  async loadPlugin(name: string, path: string) {
    try {
      const plugin = await import(path);
      this.plugins.set(name, plugin.default || plugin);
      console.log(`Plugin ${name} loaded`);
    } catch (error) {
      console.error(`Failed to load plugin ${name}:`, error);
    }
  }
  
  getPlugin(name: string) {
    return this.plugins.get(name);
  }
}

内置工具链

Bun.build - 内置打包器

// build.ts
await Bun.build({
  entrypoints: ['./src/index.ts'],
  outdir: './dist',
  target: 'browser', // 'browser' | 'bun' | 'node'
  format: 'esm',     // 'esm' | 'cjs' | 'iife'
  minify: true,
  sourcemap: 'external',
  splitting: true,   // 代码分割
  
  // 环境变量处理
  env: 'inline',     // 'inline' | 'PUBLIC_*' | 'disable'
  
  // 插件系统
  plugins: [
    {
      name: 'custom-plugin',
      setup(build) {
        build.onLoad({ filter: /\.custom$/ }, async (args) => {
          const contents = await Bun.file(args.path).text();
          return {
            contents: `export default ${JSON.stringify(contents)}`,
            loader: 'js'
          };
        });
      }
    }
  ],
  
  // 外部依赖
  external: ['react', 'react-dom']
});

Macros - 构建时代码生成

// database.ts (macro 文件)
export function sql(strings: TemplateStringsArray, ...values: any[]) {
  // 构建时执行,生成优化的 SQL 查询代码
  const query = strings.reduce((acc, str, i) => {
    return acc + str + (values[i] ? `$${i + 1}` : '');
  }, '');
  
  return {
    query,
    params: values,
    execute: (db: any) => db.prepare(query).all(...values)
  };
}

// app.ts
import { sql } from './database.ts' with { type: 'macro' };

// 这会在构建时展开为优化的代码
const users = await sql`SELECT * FROM users WHERE id = ${userId}`;

// 构建后生成的代码类似:
// const users = await db.prepare("SELECT * FROM users WHERE id = $1").all(userId);

内置测试运行器

// math.test.ts
import { expect, test, describe, beforeAll, afterAll } from 'bun:test';
import { add, subtract } from './math.ts';

describe('Math functions', () => {
  test('addition', () => {
    expect(add(2, 3)).toBe(5);
    expect(add(-1, 1)).toBe(0);
  });
  
  test('subtraction', () => {
    expect(subtract(5, 3)).toBe(2);
    expect(subtract(1, 1)).toBe(0);
  });
  
  test('async operation', async () => {
    const result = await fetch('https://httpbin.org/json');
    const data = await result.json();
    expect(data).toHaveProperty('slideshow');
  });
});

// 运行测试:bun test
// 并行运行,速度极快

性能基准测试

// benchmark.ts
import { bench, run } from 'bun:test';

bench('string concatenation', () => {
  let result = '';
  for (let i = 0; i < 1000; i++) {
    result += 'a';
  }
});

bench('array join', () => {
  const arr = [];
  for (let i = 0; i < 1000; i++) {
    arr.push('a');
  }
  arr.join('');
});

bench('template literal', () => {
  let result = '';
  for (let i = 0; i < 1000; i++) {
    result = `${result}a`;
  }
});

await run();

数据库集成

内置 SQLite

// database.ts
import { Database } from 'bun:sqlite';

const db = new Database('mydb.sqlite');

// 创建表
db.exec(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL
  )
`);

// 预编译语句(性能最佳)
const insertUser = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');
const getUserById = db.prepare('SELECT * FROM users WHERE id = ?');
const getAllUsers = db.prepare('SELECT * FROM users');

// 插入数据
insertUser.run('John Doe', 'john@example.com');

// 查询数据
const user = getUserById.get(1);
const users = getAllUsers.all();

// 事务支持
const insertManyUsers = db.transaction((users) => {
  for (const user of users) {
    insertUser.run(user.name, user.email);
  }
});

insertManyUsers([
  { name: 'Alice', email: 'alice@example.com' },
  { name: 'Bob', email: 'bob@example.com' }
]);

db.close();

ORM 集成示例

// user.model.ts
interface User {
  id?: number;
  name: string;
  email: string;
  createdAt?: Date;
}

class UserModel {
  private db: Database;
  
  constructor(db: Database) {
    this.db = db;
    this.init();
  }
  
  private init() {
    this.db.exec(`
      CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        email TEXT UNIQUE NOT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
      )
    `);
  }
  
  create(user: Omit<User, 'id' | 'createdAt'>): User {
    const stmt = this.db.prepare('INSERT INTO users (name, email) VALUES (?, ?) RETURNING *');
    return stmt.get(user.name, user.email) as User;
  }
  
  findById(id: number): User | null {
    const stmt = this.db.prepare('SELECT * FROM users WHERE id = ?');
    return stmt.get(id) as User | null;
  }
  
  findAll(): User[] {
    const stmt = this.db.prepare('SELECT * FROM users ORDER BY created_at DESC');
    return stmt.all() as User[];
  }
  
  update(id: number, updates: Partial<User>): User | null {
    const fields = Object.keys(updates).map(key => `${key} = ?`).join(', ');
    const values = Object.values(updates);
    const stmt = this.db.prepare(`UPDATE users SET ${fields} WHERE id = ? RETURNING *`);
    return stmt.get(...values, id) as User | null;
  }
  
  delete(id: number): boolean {
    const stmt = this.db.prepare('DELETE FROM users WHERE id = ?');
    const result = stmt.run(id);
    return result.changes > 0;
  }
}

// 使用示例
import { Database } from 'bun:sqlite';

const db = new Database('app.db');
const userModel = new UserModel(db);

const newUser = userModel.create({
  name: 'Alice Smith',
  email: 'alice@example.com'
});

console.log('Created user:', newUser);

开发工具和调试

热重载开发

// dev-server.ts
const server = Bun.serve({
  port: 3000,
  development: true, // 启用开发模式
  
  async fetch(req) {
    const url = new URL(req.url);
    
    // API 路由
    if (url.pathname.startsWith('/api/')) {
      // 动态导入,支持热重载
      const handler = await import(`./api${url.pathname.replace('/api', '')}.ts`);
      return handler.default(req);
    }
    
    // 静态文件服务
    return new Response(Bun.file('./public/index.html'));
  }
});

// 文件监听器
const watcher = Bun.watch({
  recursive: true,
  paths: ['./src'],
  onchange(event, path) {
    console.log(`File changed: ${path}`);
    // 可以发送 WebSocket 消息通知客户端刷新
  }
});

console.log(`Dev server running on http://localhost:${server.port}`);

调试和日志

// logger.ts
class Logger {
  private isDev = Bun.env.NODE_ENV === 'development';
  
  info(message: string, ...args: any[]) {
    if (this.isDev) {
      console.log(`[INFO] ${new Date().toISOString()} - ${message}`, ...args);
    }
  }
  
  error(message: string, error?: Error) {
    console.error(`[ERROR] ${new Date().toISOString()} - ${message}`);
    if (error && this.isDev) {
      console.error(error.stack);
    }
  }
  
  perf<T>(name: string, fn: () => T): T {
    if (!this.isDev) return fn();
    
    const start = performance.now();
    const result = fn();
    const end = performance.now();
    
    console.log(`[PERF] ${name}: ${(end - start).toFixed(2)}ms`);
    return result;
  }
}

export const logger = new Logger();

// 使用示例
import { logger } from './logger.ts';

logger.perf('database-query', () => {
  return db.prepare('SELECT * FROM users').all();
});

性能优化

预编译和缓存

// 利用 Bun 的编译缓存
// bun.config.js
export default {
  // 预加载模块,提高启动速度
  preload: [
    './src/setup.ts',
    './src/config.ts'
  ],
  
  // 编译目标
  target: 'bun',
  
  // 外部依赖(不编译)
  external: ['sharp', 'canvas'],
  
  // 压缩
  minify: {
    whitespace: true,
    identifiers: true,
    syntax: true
  }
};

内存和性能监控

// monitor.ts
class PerformanceMonitor {
  private metrics = new Map<string, number[]>();
  
  measure<T>(name: string, fn: () => T): T {
    const start = performance.now();
    
    try {
      const result = fn();
      const duration = performance.now() - start;
      this.recordMetric(name, duration);
      return result;
    } catch (error) {
      const duration = performance.now() - start;
      this.recordMetric(`${name}-error`, duration);
      throw error;
    }
  }
  
  async measureAsync<T>(name: string, fn: () => Promise<T>): Promise<T> {
    const start = performance.now();
    
    try {
      const result = await fn();
      const duration = performance.now() - start;
      this.recordMetric(name, duration);
      return result;
    } catch (error) {
      const duration = performance.now() - start;
      this.recordMetric(`${name}-error`, duration);
      throw error;
    }
  }
  
  private recordMetric(name: string, value: number) {
    if (!this.metrics.has(name)) {
      this.metrics.set(name, []);
    }
    this.metrics.get(name)!.push(value);
  }
  
  getStats(name: string) {
    const values = this.metrics.get(name) || [];
    if (values.length === 0) return null;
    
    const sum = values.reduce((a, b) => a + b, 0);
    const avg = sum / values.length;
    const min = Math.min(...values);
    const max = Math.max(...values);
    
    return { avg, min, max, count: values.length };
  }
  
  getMemoryUsage() {
    return {
      rss: process.memoryUsage.rss(),
      heapUsed: process.memoryUsage().heapUsed,
      heapTotal: process.memoryUsage().heapTotal,
      external: process.memoryUsage().external
    };
  }
}

export const monitor = new PerformanceMonitor();

生产环境部署

Docker 配置

# Dockerfile
FROM oven/bun:1.0-alpine

WORKDIR /app

# 复制依赖文件
COPY package.json bun.lockb ./

# 安装依赖
RUN bun install --frozen-lockfile --production

# 复制源码
COPY src/ ./src/

# 构建应用
RUN bun build src/index.ts --outdir dist --target bun

# 设置环境变量
ENV NODE_ENV=production

# 暴露端口
EXPOSE 3000

# 启动应用
CMD ["bun", "run", "dist/index.js"]

部署脚本

// deploy.ts
import { $ } from 'bun';

async function deploy() {
  console.log('Starting deployment...');
  
  // 运行测试
  await $`bun test`;
  console.log('✅ Tests passed');
  
  // 构建生产版本
  await $`bun run build:prod`;
  console.log('✅ Build completed');
  
  // 构建 Docker 镜像
  await $`docker build -t myapp:latest .`;
  console.log('✅ Docker image built');
  
  // 推送到仓库(如果需要)
  if (Bun.env.PUSH_TO_REGISTRY === 'true') {
    await $`docker push myapp:latest`;
    console.log('✅ Image pushed to registry');
  }
  
  console.log('🚀 Deployment completed!');
}

deploy().catch(console.error);

最佳实践总结

性能优化

  1. 利用 Bun 的速度优势 - 充分使用内置 API
  2. 合理使用 Macros - 在构建时生成优化代码
  3. 数据库连接复用 - 使用连接池和预编译语句
  4. 避免不必要的依赖 - 利用 Bun 的内置功能

开发体验

  1. 零配置 TypeScript - 直接运行 .ts 文件
  2. 热重载开发 - 使用 --watch 标志
  3. 内置测试工具 - 使用 bun test 进行快速测试
  4. 一体化工具链 - 减少工具配置复杂度

生产环境

  1. 容器化部署 - 使用官方 Docker 镜像
  2. 环境变量管理 - 合理配置生产环境变量
  3. 监控和日志 - 实施完善的监控体系
  4. 渐进式迁移 - 从 Node.js 逐步迁移到 Bun

生态兼容性

  1. Node.js API 兼容 - 大部分现有代码可直接运行
  2. npm 包支持 - 兼容现有的 npm 生态
  3. 框架支持 - 支持 React、Vue、Express 等主流框架
  4. 工具集成 - 与现有开发工具链良好集成

Bun 代表了 JavaScript 运行时的下一代发展方向,通过极致的性能优化、现代化的工具链和优秀的开发体验,为 JavaScript 开发者提供了一个全新的选择。


下一章: 最佳实践

模块设计原则

良好的模块设计是构建可维护、可扩展应用的基础。本章将介绍模块化开发中的设计原则和最佳实践。

SOLID 原则在模块化中的应用

1. 单一职责原则 (Single Responsibility Principle)

每个模块应该只有一个变化的理由,即只负责一个功能领域。

❌ 违反单一职责的示例

// user-manager.js - 职责过多
export class UserManager {
  // 用户数据管理
  async saveUser(user) {
    await this.validateUser(user);
    await this.sendEmail(user);
    return await this.database.save(user);
  }
  
  // 数据验证
  validateUser(user) {
    if (!user.email || !user.name) {
      throw new Error('Invalid user data');
    }
  }
  
  // 邮件发送
  async sendEmail(user) {
    const emailClient = new EmailClient();
    await emailClient.send(user.email, 'Welcome!');
  }
  
  // 数据库操作
  async findUser(id) {
    return await this.database.find(id);
  }
}

✅ 遵循单一职责的改进

// user-validator.js - 只负责验证
export class UserValidator {
  static validate(user) {
    if (!user.email || !user.name) {
      throw new Error('Invalid user data');
    }
    
    if (!this.isValidEmail(user.email)) {
      throw new Error('Invalid email format');
    }
  }
  
  static isValidEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }
}

// email-service.js - 只负责邮件发送
export class EmailService {
  constructor(config) {
    this.client = new EmailClient(config);
  }
  
  async sendWelcomeEmail(user) {
    const template = await this.loadTemplate('welcome');
    const content = this.renderTemplate(template, { name: user.name });
    return await this.client.send(user.email, 'Welcome!', content);
  }
  
  async loadTemplate(name) {
    // 加载邮件模板
  }
  
  renderTemplate(template, data) {
    // 渲染模板
  }
}

// user-repository.js - 只负责数据持久化
export class UserRepository {
  constructor(database) {
    this.db = database;
  }
  
  async save(user) {
    return await this.db.collection('users').insert(user);
  }
  
  async findById(id) {
    return await this.db.collection('users').findOne({ _id: id });
  }
  
  async findByEmail(email) {
    return await this.db.collection('users').findOne({ email });
  }
}

// user-service.js - 协调各个服务
export class UserService {
  constructor(userRepository, emailService) {
    this.userRepository = userRepository;
    this.emailService = emailService;
  }
  
  async createUser(userData) {
    // 验证用户数据
    UserValidator.validate(userData);
    
    // 检查邮箱是否已存在
    const existingUser = await this.userRepository.findByEmail(userData.email);
    if (existingUser) {
      throw new Error('Email already exists');
    }
    
    // 保存用户
    const user = await this.userRepository.save(userData);
    
    // 发送欢迎邮件
    await this.emailService.sendWelcomeEmail(user);
    
    return user;
  }
}

2. 开闭原则 (Open/Closed Principle)

模块应该对扩展开放,对修改关闭。

// 抽象基类
// payment-processor.js
export class PaymentProcessor {
  async process(payment) {
    throw new Error('Must implement process method');
  }
  
  async validate(payment) {
    if (!payment.amount || payment.amount <= 0) {
      throw new Error('Invalid payment amount');
    }
  }
}

// 具体实现 - 不修改基类,只扩展
// stripe-processor.js
import { PaymentProcessor } from './payment-processor.js';

export class StripeProcessor extends PaymentProcessor {
  constructor(apiKey) {
    super();
    this.stripe = new Stripe(apiKey);
  }
  
  async process(payment) {
    await this.validate(payment);
    
    return await this.stripe.charges.create({
      amount: payment.amount,
      currency: payment.currency,
      source: payment.token
    });
  }
}

// paypal-processor.js
import { PaymentProcessor } from './payment-processor.js';

export class PayPalProcessor extends PaymentProcessor {
  constructor(config) {
    super();
    this.paypal = new PayPal(config);
  }
  
  async process(payment) {
    await this.validate(payment);
    
    return await this.paypal.payment.create({
      intent: 'sale',
      transactions: [{
        amount: {
          total: payment.amount,
          currency: payment.currency
        }
      }]
    });
  }
}

// payment-service.js - 使用策略模式
export class PaymentService {
  constructor() {
    this.processors = new Map();
  }
  
  registerProcessor(type, processor) {
    this.processors.set(type, processor);
  }
  
  async processPayment(type, payment) {
    const processor = this.processors.get(type);
    if (!processor) {
      throw new Error(`Unsupported payment type: ${type}`);
    }
    
    return await processor.process(payment);
  }
}

// 使用示例
const paymentService = new PaymentService();
paymentService.registerProcessor('stripe', new StripeProcessor(apiKey));
paymentService.registerProcessor('paypal', new PayPalProcessor(config));

// 添加新的支付方式不需要修改现有代码
paymentService.registerProcessor('alipay', new AlipayProcessor(config));

3. 里氏替换原则 (Liskov Substitution Principle)

子类必须能够替换其父类而不影响程序的正确性。

// cache-interface.js
export class CacheInterface {
  async get(key) {
    throw new Error('Must implement get method');
  }
  
  async set(key, value, ttl = 3600) {
    throw new Error('Must implement set method');
  }
  
  async delete(key) {
    throw new Error('Must implement delete method');
  }
}

// memory-cache.js
import { CacheInterface } from './cache-interface.js';

export class MemoryCache extends CacheInterface {
  constructor() {
    super();
    this.cache = new Map();
    this.timers = new Map();
  }
  
  async get(key) {
    return this.cache.get(key) || null;
  }
  
  async set(key, value, ttl = 3600) {
    this.cache.set(key, value);
    
    // 清除旧的定时器
    if (this.timers.has(key)) {
      clearTimeout(this.timers.get(key));
    }
    
    // 设置新的过期定时器
    const timer = setTimeout(() => {
      this.cache.delete(key);
      this.timers.delete(key);
    }, ttl * 1000);
    
    this.timers.set(key, timer);
  }
  
  async delete(key) {
    this.cache.delete(key);
    if (this.timers.has(key)) {
      clearTimeout(this.timers.get(key));
      this.timers.delete(key);
    }
  }
}

// redis-cache.js
import { CacheInterface } from './cache-interface.js';

export class RedisCache extends CacheInterface {
  constructor(client) {
    super();
    this.redis = client;
  }
  
  async get(key) {
    return await this.redis.get(key);
  }
  
  async set(key, value, ttl = 3600) {
    return await this.redis.setex(key, ttl, JSON.stringify(value));
  }
  
  async delete(key) {
    return await this.redis.del(key);
  }
}

// user-service.js - 可以无缝替换缓存实现
export class UserService {
  constructor(userRepository, cache) {
    this.userRepository = userRepository;
    this.cache = cache; // 可以是 MemoryCache 或 RedisCache
  }
  
  async getUser(id) {
    const cacheKey = `user:${id}`;
    
    // 先从缓存获取
    let user = await this.cache.get(cacheKey);
    if (user) {
      return JSON.parse(user);
    }
    
    // 缓存未命中,从数据库获取
    user = await this.userRepository.findById(id);
    if (user) {
      await this.cache.set(cacheKey, JSON.stringify(user), 1800);
    }
    
    return user;
  }
}

4. 接口隔离原则 (Interface Segregation Principle)

不应该强迫客户端依赖于它们不使用的接口。

❌ 违反接口隔离的示例

// 臃肿的接口
export class DatabaseService {
  // 用户相关
  async createUser(user) { /* ... */ }
  async updateUser(id, user) { /* ... */ }
  async deleteUser(id) { /* ... */ }
  
  // 订单相关
  async createOrder(order) { /* ... */ }
  async updateOrder(id, order) { /* ... */ }
  async deleteOrder(id) { /* ... */ }
  
  // 产品相关
  async createProduct(product) { /* ... */ }
  async updateProduct(id, product) { /* ... */ }
  async deleteProduct(id) { /* ... */ }
  
  // 报表相关
  async generateUserReport() { /* ... */ }
  async generateOrderReport() { /* ... */ }
  async generateProductReport() { /* ... */ }
}

✅ 遵循接口隔离的改进

// 细分的接口
// user-operations.js
export class UserOperations {
  constructor(database) {
    this.db = database;
  }
  
  async create(user) {
    return await this.db.collection('users').insert(user);
  }
  
  async update(id, user) {
    return await this.db.collection('users').update({ _id: id }, user);
  }
  
  async delete(id) {
    return await this.db.collection('users').delete({ _id: id });
  }
  
  async findById(id) {
    return await this.db.collection('users').findOne({ _id: id });
  }
}

// order-operations.js
export class OrderOperations {
  constructor(database) {
    this.db = database;
  }
  
  async create(order) {
    return await this.db.collection('orders').insert(order);
  }
  
  async findByUserId(userId) {
    return await this.db.collection('orders').find({ userId });
  }
  
  async updateStatus(id, status) {
    return await this.db.collection('orders').update(
      { _id: id }, 
      { $set: { status, updatedAt: new Date() } }
    );
  }
}

// report-generator.js
export class ReportGenerator {
  constructor(database) {
    this.db = database;
  }
  
  async generateUserReport(startDate, endDate) {
    return await this.db.collection('users').aggregate([
      { $match: { createdAt: { $gte: startDate, $lte: endDate } } },
      { $group: { _id: null, count: { $sum: 1 } } }
    ]);
  }
  
  async generateOrderReport(startDate, endDate) {
    return await this.db.collection('orders').aggregate([
      { $match: { createdAt: { $gte: startDate, $lte: endDate } } },
      { $group: { _id: '$status', count: { $sum: 1 }, total: { $sum: '$amount' } } }
    ]);
  }
}

// 客户端只依赖需要的接口
// user-service.js
import { UserOperations } from './user-operations.js';

export class UserService {
  constructor(database) {
    this.userOps = new UserOperations(database);
  }
  
  async createUser(userData) {
    // 只需要用户操作接口
    return await this.userOps.create(userData);
  }
}

5. 依赖倒置原则 (Dependency Inversion Principle)

高层模块不应该依赖低层模块,两者都应该依赖于抽象。

// 抽象层
// logger-interface.js
export class LoggerInterface {
  info(message, meta = {}) {
    throw new Error('Must implement info method');
  }
  
  error(message, error = null, meta = {}) {
    throw new Error('Must implement error method');
  }
  
  warn(message, meta = {}) {
    throw new Error('Must implement warn method');
  }
}

// notification-interface.js
export class NotificationInterface {
  async send(recipient, message, options = {}) {
    throw new Error('Must implement send method');
  }
}

// 具体实现
// console-logger.js
import { LoggerInterface } from './logger-interface.js';

export class ConsoleLogger extends LoggerInterface {
  info(message, meta = {}) {
    console.log(`[INFO] ${new Date().toISOString()} - ${message}`, meta);
  }
  
  error(message, error = null, meta = {}) {
    console.error(`[ERROR] ${new Date().toISOString()} - ${message}`, {
      error: error?.stack || error,
      ...meta
    });
  }
  
  warn(message, meta = {}) {
    console.warn(`[WARN] ${new Date().toISOString()} - ${message}`, meta);
  }
}

// file-logger.js
import { LoggerInterface } from './logger-interface.js';
import fs from 'fs/promises';

export class FileLogger extends LoggerInterface {
  constructor(logFile) {
    super();
    this.logFile = logFile;
  }
  
  async info(message, meta = {}) {
    await this.writeLog('INFO', message, meta);
  }
  
  async error(message, error = null, meta = {}) {
    await this.writeLog('ERROR', message, { error: error?.stack || error, ...meta });
  }
  
  async warn(message, meta = {}) {
    await this.writeLog('WARN', message, meta);
  }
  
  async writeLog(level, message, meta) {
    const timestamp = new Date().toISOString();
    const logEntry = JSON.stringify({ timestamp, level, message, meta }) + '\n';
    await fs.appendFile(this.logFile, logEntry);
  }
}

// email-notification.js
import { NotificationInterface } from './notification-interface.js';

export class EmailNotification extends NotificationInterface {
  constructor(emailService) {
    super();
    this.emailService = emailService;
  }
  
  async send(recipient, message, options = {}) {
    return await this.emailService.send({
      to: recipient,
      subject: options.subject || 'Notification',
      body: message,
      html: options.html || false
    });
  }
}

// 高层模块依赖抽象
// order-service.js
export class OrderService {
  constructor(orderRepository, logger, notificationService) {
    this.orderRepository = orderRepository;
    this.logger = logger; // 依赖抽象,不是具体实现
    this.notificationService = notificationService; // 依赖抽象
  }
  
  async createOrder(orderData) {
    try {
      this.logger.info('Creating new order', { orderData });
      
      const order = await this.orderRepository.create(orderData);
      
      await this.notificationService.send(
        orderData.customerEmail,
        `Your order ${order.id} has been created successfully`,
        { subject: 'Order Confirmation' }
      );
      
      this.logger.info('Order created successfully', { orderId: order.id });
      return order;
      
    } catch (error) {
      this.logger.error('Failed to create order', error, { orderData });
      throw error;
    }
  }
}

// 依赖注入配置
// app.js
import { OrderService } from './order-service.js';
import { ConsoleLogger } from './console-logger.js';
import { EmailNotification } from './email-notification.js';

// 可以轻松切换不同的实现
const logger = new ConsoleLogger(); // 或 new FileLogger('./app.log')
const notificationService = new EmailNotification(emailService);

const orderService = new OrderService(orderRepository, logger, notificationService);

模块耦合和内聚

高内聚的模块设计

模块内部的元素应该紧密相关,共同完成一个明确的功能。

// 高内聚的用户认证模块
// auth-module.js
export class AuthModule {
  constructor(userRepository, tokenService, passwordService) {
    this.userRepository = userRepository;
    this.tokenService = tokenService;
    this.passwordService = passwordService;
  }
  
  // 所有方法都与认证相关
  async login(email, password) {
    const user = await this.userRepository.findByEmail(email);
    if (!user) {
      throw new Error('User not found');
    }
    
    const isValidPassword = await this.passwordService.verify(password, user.hashedPassword);
    if (!isValidPassword) {
      throw new Error('Invalid password');
    }
    
    return this.tokenService.generate(user.id);
  }
  
  async register(userData) {
    const hashedPassword = await this.passwordService.hash(userData.password);
    const user = await this.userRepository.create({
      ...userData,
      hashedPassword
    });
    
    return this.tokenService.generate(user.id);
  }
  
  async verifyToken(token) {
    return this.tokenService.verify(token);
  }
  
  async refreshToken(refreshToken) {
    const userId = await this.tokenService.verifyRefresh(refreshToken);
    return this.tokenService.generate(userId);
  }
  
  async logout(token) {
    return this.tokenService.revoke(token);
  }
}

低耦合的模块间通信

模块之间的依赖应该最小化,通过明确的接口进行通信。

// 事件驱动的低耦合架构
// event-bus.js
export class EventBus {
  constructor() {
    this.listeners = new Map();
  }
  
  on(event, callback) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, []);
    }
    this.listeners.get(event).push(callback);
  }
  
  off(event, callback) {
    const callbacks = this.listeners.get(event);
    if (callbacks) {
      const index = callbacks.indexOf(callback);
      if (index > -1) {
        callbacks.splice(index, 1);
      }
    }
  }
  
  emit(event, data) {
    const callbacks = this.listeners.get(event) || [];
    callbacks.forEach(callback => {
      try {
        callback(data);
      } catch (error) {
        console.error(`Error in event listener for ${event}:`, error);
      }
    });
  }
}

// order-service.js - 发布事件,不直接依赖其他服务
export class OrderService {
  constructor(orderRepository, eventBus) {
    this.orderRepository = orderRepository;
    this.eventBus = eventBus;
  }
  
  async createOrder(orderData) {
    const order = await this.orderRepository.create(orderData);
    
    // 发布事件而不是直接调用其他服务
    this.eventBus.emit('order.created', {
      orderId: order.id,
      customerId: order.customerId,
      amount: order.amount,
      createdAt: order.createdAt
    });
    
    return order;
  }
}

// notification-service.js - 监听事件
export class NotificationService {
  constructor(emailService, eventBus) {
    this.emailService = emailService;
    this.eventBus = eventBus;
    
    // 订阅相关事件
    this.eventBus.on('order.created', this.handleOrderCreated.bind(this));
    this.eventBus.on('user.registered', this.handleUserRegistered.bind(this));
  }
  
  async handleOrderCreated(orderData) {
    await this.emailService.send(
      orderData.customerEmail,
      'Order Confirmation',
      `Your order ${orderData.orderId} has been confirmed.`
    );
  }
  
  async handleUserRegistered(userData) {
    await this.emailService.send(
      userData.email,
      'Welcome!',
      `Welcome to our platform, ${userData.name}!`
    );
  }
}

// analytics-service.js - 独立的分析服务
export class AnalyticsService {
  constructor(analyticsRepository, eventBus) {
    this.analyticsRepository = analyticsRepository;
    this.eventBus = eventBus;
    
    // 订阅所有感兴趣的事件
    this.eventBus.on('order.created', this.trackOrderCreated.bind(this));
    this.eventBus.on('user.registered', this.trackUserRegistered.bind(this));
    this.eventBus.on('user.login', this.trackUserLogin.bind(this));
  }
  
  async trackOrderCreated(orderData) {
    await this.analyticsRepository.recordEvent('order_created', {
      orderId: orderData.orderId,
      amount: orderData.amount,
      timestamp: orderData.createdAt
    });
  }
  
  async trackUserRegistered(userData) {
    await this.analyticsRepository.recordEvent('user_registered', {
      userId: userData.id,
      timestamp: userData.createdAt
    });
  }
  
  async trackUserLogin(loginData) {
    await this.analyticsRepository.recordEvent('user_login', {
      userId: loginData.userId,
      timestamp: loginData.timestamp
    });
  }
}

模块命名和组织

清晰的命名约定

// 好的命名约定
// services/user-authentication.service.js
export class UserAuthenticationService { }

// repositories/user.repository.js
export class UserRepository { }

// models/user.model.js
export class User { }

// utils/date.utils.js
export const DateUtils = { };

// config/database.config.js
export const databaseConfig = { };

// types/user.types.js
export interface User { }

// constants/http-status.constants.js
export const HTTP_STATUS = { };

目录结构组织

src/
├── components/           # 可复用组件
│   ├── ui/              # UI组件
│   │   ├── button/
│   │   ├── input/
│   │   └── modal/
│   └── business/        # 业务组件
│       ├── user-profile/
│       └── order-summary/
├── services/            # 业务服务层
│   ├── user.service.js
│   ├── order.service.js
│   └── payment.service.js
├── repositories/        # 数据访问层
│   ├── user.repository.js
│   └── order.repository.js
├── models/             # 数据模型
│   ├── user.model.js
│   └── order.model.js
├── utils/              # 工具函数
│   ├── date.utils.js
│   ├── validation.utils.js
│   └── format.utils.js
├── config/             # 配置文件
│   ├── app.config.js
│   ├── database.config.js
│   └── api.config.js
├── types/              # TypeScript 类型定义
│   ├── user.types.ts
│   └── api.types.ts
├── constants/          # 常量定义
│   ├── http-status.constants.js
│   └── error-codes.constants.js
└── tests/              # 测试文件
    ├── unit/
    ├── integration/
    └── e2e/

模块版本管理

语义化版本控制

// package.json
{
  "name": "@mycompany/user-service",
  "version": "1.2.3",
  "description": "User management service",
  "main": "dist/index.js",
  "exports": {
    ".": {
      "import": "./dist/esm/index.js",
      "require": "./dist/cjs/index.js",
      "types": "./dist/types/index.d.ts"
    },
    "./types": {
      "import": "./dist/esm/types.js",
      "require": "./dist/cjs/types.js",
      "types": "./dist/types/types.d.ts"
    }
  },
  "files": [
    "dist/"
  ],
  "engines": {
    "node": ">=16.0.0"
  }
}

向后兼容的 API 设计

// v1.0.0 - 初始版本
export class UserService {
  async getUser(id) {
    return await this.userRepository.findById(id);
  }
}

// v1.1.0 - 添加新功能,保持向后兼容
export class UserService {
  async getUser(id) {
    return await this.userRepository.findById(id);
  }
  
  // 新增方法
  async getUserWithProfile(id) {
    const user = await this.userRepository.findById(id);
    const profile = await this.profileRepository.findByUserId(id);
    return { ...user, profile };
  }
}

// v2.0.0 - 破坏性变更,新的主版本
export class UserService {
  // 修改方法签名,返回结构变化
  async getUser(id, options = {}) {
    const user = await this.userRepository.findById(id);
    
    if (options.includeProfile) {
      const profile = await this.profileRepository.findByUserId(id);
      return { user, profile };
    }
    
    return { user };
  }
  
  // 废弃的方法标记
  /**
   * @deprecated Use getUser with options.includeProfile instead
   */
  async getUserWithProfile(id) {
    console.warn('getUserWithProfile is deprecated. Use getUser with options.includeProfile instead.');
    return this.getUser(id, { includeProfile: true });
  }
}

总结

良好的模块设计原则包括:

🎯 核心原则

  • SOLID原则: 单一职责、开闭、里氏替换、接口隔离、依赖倒置
  • 高内聚低耦合: 模块内部紧密相关,模块间依赖最小
  • 明确的接口: 清晰的输入输出和职责边界

📝 命名和组织

  • 一致的命名约定: 清晰表达模块用途
  • 合理的目录结构: 按功能和层次组织
  • 语义化版本: 明确的版本变更策略

🔄 持续改进

  • 定期重构: 保持代码质量
  • 代码审查: 确保设计原则的执行
  • 文档维护: 保持文档与代码同步

遵循这些设计原则可以构建出易于维护、测试和扩展的模块化应用。


下一章: 性能优化

性能优化

在现代Web应用开发中,模块的性能优化对用户体验至关重要。本章将深入探讨JavaScript模块系统的各种性能优化策略。

代码分割 (Code Splitting)

代码分割是最重要的性能优化技术之一,它允许我们将应用拆分成多个较小的包,按需加载。

动态导入实现代码分割

// 路由级别的代码分割
const routes = [
  {
    path: '/dashboard',
    component: () => import('./views/Dashboard.vue')
  },
  {
    path: '/profile',
    component: () => import('./views/Profile.vue')
  }
];

// 功能级别的代码分割
async function loadImageEditor() {
  const { ImageEditor } = await import('./components/ImageEditor.js');
  return new ImageEditor();
}

// 条件加载
if (window.innerWidth > 768) {
  const { DesktopLayout } = await import('./layouts/DesktopLayout.js');
  app.use(DesktopLayout);
} else {
  const { MobileLayout } = await import('./layouts/MobileLayout.js');
  app.use(MobileLayout);
}

Bundle Splitting策略

// webpack.config.js
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        // 第三方库单独打包
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
        // 公共代码提取
        common: {
          name: 'common',
          minChunks: 2,
          chunks: 'all',
          enforce: true
        }
      }
    }
  }
};

// Vite配置
export default {
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          utils: ['lodash', 'moment'],
          ui: ['antd', '@mui/material']
        }
      }
    }
  }
};

懒加载与预加载

模块懒加载模式

// 懒加载管理器
class LazyModuleLoader {
  constructor() {
    this.loadedModules = new Map();
    this.loadingPromises = new Map();
  }

  async load(moduleName, importFn) {
    // 如果已加载,直接返回
    if (this.loadedModules.has(moduleName)) {
      return this.loadedModules.get(moduleName);
    }

    // 如果正在加载,返回加载Promise
    if (this.loadingPromises.has(moduleName)) {
      return this.loadingPromises.get(moduleName);
    }

    // 开始加载
    const loadPromise = importFn().then(module => {
      this.loadedModules.set(moduleName, module);
      this.loadingPromises.delete(moduleName);
      return module;
    });

    this.loadingPromises.set(moduleName, loadPromise);
    return loadPromise;
  }
}

// 使用示例
const loader = new LazyModuleLoader();

async function initChart() {
  const module = await loader.load('chart', () => import('./chart/Chart.js'));
  return new module.Chart();
}

智能预加载

// 预加载策略
class PreloadManager {
  constructor() {
    this.preloadedModules = new Set();
    this.observer = new IntersectionObserver(this.handleIntersection.bind(this));
  }

  // 预加载关键模块
  preloadCritical() {
    this.preload(() => import('./core/UserManager.js'));
    this.preload(() => import('./core/Router.js'));
  }

  // 根据用户行为预加载
  preloadOnHover(element, importFn) {
    element.addEventListener('mouseenter', () => {
      this.preload(importFn);
    }, { once: true });
  }

  // 视口预加载
  preloadOnVisible(element, importFn) {
    element.dataset.preload = importFn.toString();
    this.observer.observe(element);
  }

  async preload(importFn) {
    const moduleKey = importFn.toString();
    if (this.preloadedModules.has(moduleKey)) return;

    this.preloadedModules.add(moduleKey);
    
    // 使用link预加载
    const link = document.createElement('link');
    link.rel = 'modulepreload';
    link.href = this.getModuleURL(importFn);
    document.head.appendChild(link);
  }

  handleIntersection(entries) {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const importFn = new Function('return ' + entry.target.dataset.preload)();
        this.preload(importFn);
        this.observer.unobserve(entry.target);
      }
    });
  }
}

Tree Shaking优化

ES模块Tree Shaking最佳实践

// 错误的导入方式 - 会导入整个库
import * as _ from 'lodash';
import { Button } from '@mui/material';

// 正确的导入方式 - 支持Tree Shaking
import { debounce } from 'lodash-es';
import Button from '@mui/material/Button';

// utils.js - 确保可被Tree Shaking
export const formatDate = (date) => {
  return new Intl.DateTimeFormat('zh-CN').format(date);
};

export const formatCurrency = (amount) => {
  return new Intl.NumberFormat('zh-CN', {
    style: 'currency',
    currency: 'CNY'
  }).format(amount);
};

// 避免副作用
export const API_BASE_URL = 'https://api.example.com';

// 而不是
console.log('API module loaded'); // 这会阻止Tree Shaking
export const API_BASE_URL = 'https://api.example.com';

库的Tree Shaking友好设计

// package.json
{
  "name": "my-utils",
  "main": "dist/index.js",
  "module": "dist/index.esm.js",
  "sideEffects": false,
  "exports": {
    ".": {
      "import": "./dist/index.esm.js",
      "require": "./dist/index.js"
    },
    "./string": {
      "import": "./dist/string.esm.js",
      "require": "./dist/string.js"
    }
  }
}

// 模块化导出结构
// src/index.js
export { default as formatDate } from './date/formatDate.js';
export { default as formatCurrency } from './currency/formatCurrency.js';
export { default as debounce } from './function/debounce.js';
export { default as throttle } from './function/throttle.js';

// 分类导出
// src/string/index.js
export { default as capitalize } from './capitalize.js';
export { default as slugify } from './slugify.js';

缓存策略

模块缓存优化

// 浏览器缓存策略
class ModuleCache {
  constructor() {
    this.cache = new Map();
    this.version = '1.0.0';
  }

  async loadWithCache(moduleId, importFn) {
    const cacheKey = `${moduleId}_${this.version}`;
    
    // 检查内存缓存
    if (this.cache.has(cacheKey)) {
      return this.cache.get(cacheKey);
    }

    // 检查localStorage缓存
    const cached = this.getFromLocalStorage(cacheKey);
    if (cached) {
      this.cache.set(cacheKey, cached);
      return cached;
    }

    // 加载并缓存
    const module = await importFn();
    this.cache.set(cacheKey, module);
    this.saveToLocalStorage(cacheKey, module);
    
    return module;
  }

  getFromLocalStorage(key) {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : null;
    } catch {
      return null;
    }
  }

  saveToLocalStorage(key, data) {
    try {
      localStorage.setItem(key, JSON.stringify(data));
    } catch {
      // 存储失败时的降级处理
    }
  }
}

HTTP缓存配置

// webpack配置 - 文件名哈希
module.exports = {
  output: {
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].chunk.js'
  }
};

// nginx配置示例
// 对于带哈希的文件,设置长期缓存
location ~* \.(js|css)$ {
  if ($uri ~ ".*\.[a-f0-9]{8,}\.(js|css)$") {
    expires 1y;
    add_header Cache-Control "public, immutable";
  }
  
  # 对于不带哈希的文件,设置短期缓存
  expires 1h;
  add_header Cache-Control "public";
}

打包体积优化

依赖分析与优化

// webpack-bundle-analyzer使用
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      openAnalyzer: false,
      reportFilename: 'bundle-report.html'
    })
  ]
};

// 替换大型依赖
// 使用date-fns替代moment.js
import { format, parseISO } from 'date-fns';

// 使用原生API替代lodash
const unique = array => [...new Set(array)];
const groupBy = (array, key) => 
  array.reduce((groups, item) => {
    const group = item[key];
    groups[group] = groups[group] || [];
    groups[group].push(item);
    return groups;
  }, {});

压缩与混淆

// Terser配置
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true,
            drop_debugger: true,
            pure_funcs: ['console.log', 'console.info']
          },
          mangle: {
            safari10: true
          }
        }
      })
    ]
  }
};

// 移除开发代码
if (process.env.NODE_ENV === 'production') {
  // 生产环境代码
} else {
  // 开发环境代码,会被打包工具移除
}

运行时性能优化

模块初始化优化

// 延迟初始化
class PerformanceModule {
  constructor() {
    this._heavyResource = null;
    this._initialized = false;
  }

  async getHeavyResource() {
    if (!this._initialized) {
      await this.initialize();
    }
    return this._heavyResource;
  }

  async initialize() {
    if (this._initialized) return;
    
    // 只在需要时初始化重资源
    this._heavyResource = await this.createHeavyResource();
    this._initialized = true;
  }

  async createHeavyResource() {
    // 模拟重量级资源创建
    const { HeavyLibrary } = await import('./HeavyLibrary.js');
    return new HeavyLibrary();
  }
}

// 单例模式确保只初始化一次
const performanceModule = new PerformanceModule();
export default performanceModule;

内存管理

// 模块级别的内存管理
class ModuleMemoryManager {
  constructor() {
    this.modules = new WeakMap();
    this.timers = new Set();
    this.listeners = new Map();
  }

  register(module, cleanup) {
    this.modules.set(module, cleanup);
  }

  addTimer(timerId) {
    this.timers.add(timerId);
  }

  addListener(element, event, handler) {
    const key = { element, event };
    this.listeners.set(key, handler);
    element.addEventListener(event, handler);
  }

  cleanup() {
    // 清理定时器
    this.timers.forEach(timerId => clearTimeout(timerId));
    this.timers.clear();

    // 清理事件监听器
    this.listeners.forEach((handler, { element, event }) => {
      element.removeEventListener(event, handler);
    });
    this.listeners.clear();

    // 调用模块清理函数
    this.modules.forEach(cleanup => cleanup());
  }
}

// 模块使用示例
const memoryManager = new ModuleMemoryManager();

export function initialize() {
  const timer = setTimeout(() => {
    // 定时器逻辑
  }, 1000);
  
  memoryManager.addTimer(timer);
  
  const handler = () => console.log('clicked');
  memoryManager.addListener(document.body, 'click', handler);
}

export function destroy() {
  memoryManager.cleanup();
}

网络优化

模块预加载策略

<!-- HTML中的预加载 -->
<link rel="modulepreload" href="/modules/critical.js">
<link rel="preload" href="/modules/important.js" as="script" crossorigin>

<!-- 动态预加载 -->
<script>
const preloadModule = (href) => {
  const link = document.createElement('link');
  link.rel = 'modulepreload';
  link.href = href;
  document.head.appendChild(link);
};

// 预加载下一个可能需要的模块
preloadModule('/modules/next-page.js');
</script>

CDN优化

// 配置CDN加载
const CDN_BASE = 'https://cdn.example.com';

export async function loadFromCDN(moduleName, version = 'latest') {
  const url = `${CDN_BASE}/${moduleName}@${version}/index.js`;
  
  try {
    return await import(url);
  } catch (error) {
    // CDN失败时的降级方案
    console.warn(`CDN load failed for ${moduleName}, falling back to local`);
    return await import(`./local-modules/${moduleName}.js`);
  }
}

// 版本控制
const MODULE_VERSIONS = {
  'chart-lib': '2.1.0',
  'ui-components': '1.5.2'
};

export function getModuleURL(moduleName) {
  const version = MODULE_VERSIONS[moduleName];
  return `${CDN_BASE}/${moduleName}@${version}/index.js`;
}

性能监控

模块加载性能监控

class ModulePerformanceMonitor {
  constructor() {
    this.metrics = new Map();
  }

  startLoad(moduleName) {
    this.metrics.set(moduleName, {
      startTime: performance.now(),
      loadTime: null,
      size: null
    });
  }

  endLoad(moduleName, moduleSize) {
    const metric = this.metrics.get(moduleName);
    if (metric) {
      metric.loadTime = performance.now() - metric.startTime;
      metric.size = moduleSize;
    }
  }

  getMetrics() {
    return Array.from(this.metrics.entries()).map(([name, data]) => ({
      name,
      loadTime: data.loadTime,
      size: data.size,
      speed: data.size ? data.size / data.loadTime : null
    }));
  }

  reportMetrics() {
    const metrics = this.getMetrics();
    
    // 发送到分析服务
    if (typeof analytics !== 'undefined') {
      analytics.track('Module Performance', {
        modules: metrics,
        totalModules: metrics.length,
        averageLoadTime: metrics.reduce((sum, m) => sum + m.loadTime, 0) / metrics.length
      });
    }
    
    console.table(metrics);
  }
}

// 使用示例
const monitor = new ModulePerformanceMonitor();

export async function loadModule(moduleName, importFn) {
  monitor.startLoad(moduleName);
  
  try {
    const module = await importFn();
    
    // 估算模块大小(简化版本)
    const moduleSize = JSON.stringify(module).length;
    monitor.endLoad(moduleName, moduleSize);
    
    return module;
  } catch (error) {
    console.error(`Failed to load module ${moduleName}:`, error);
    throw error;
  }
}

// 页面卸载时报告性能数据
window.addEventListener('beforeunload', () => {
  monitor.reportMetrics();
});

通过实施这些性能优化策略,可以显著提升JavaScript应用的加载速度和运行性能,为用户提供更好的体验。

错误处理

在模块化开发中,良好的错误处理策略对于应用的稳定性和可维护性至关重要。本章将探讨JavaScript模块系统中的各种错误处理最佳实践。

模块加载错误处理

动态导入错误处理

// 基础错误处理
async function loadModule(modulePath) {
  try {
    const module = await import(modulePath);
    return module;
  } catch (error) {
    console.error(`Failed to load module ${modulePath}:`, error);
    throw new Error(`Module loading failed: ${error.message}`);
  }
}

// 带重试机制的模块加载
async function loadModuleWithRetry(modulePath, maxRetries = 3) {
  let lastError;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const module = await import(modulePath);
      return module;
    } catch (error) {
      lastError = error;
      console.warn(`Module load attempt ${attempt} failed:`, error);
      
      if (attempt < maxRetries) {
        // 指数退避策略
        await new Promise(resolve => 
          setTimeout(resolve, Math.pow(2, attempt) * 1000)
        );
      }
    }
  }
  
  throw new Error(`Module ${modulePath} failed to load after ${maxRetries} attempts: ${lastError.message}`);
}

// 优雅降级处理
async function loadModuleWithFallback(primaryPath, fallbackPath) {
  try {
    return await import(primaryPath);
  } catch (primaryError) {
    console.warn(`Primary module ${primaryPath} failed, trying fallback:`, primaryError);
    
    try {
      return await import(fallbackPath);
    } catch (fallbackError) {
      console.error(`Both primary and fallback modules failed:`, {
        primary: primaryError,
        fallback: fallbackError
      });
      
      // 返回最小功能实现
      return {
        default: () => console.log('Using minimal fallback implementation')
      };
    }
  }
}

错误分类处理

// 错误类型定义
class ModuleError extends Error {
  constructor(message, code, modulePath) {
    super(message);
    this.name = 'ModuleError';
    this.code = code;
    this.modulePath = modulePath;
  }
}

class NetworkError extends ModuleError {
  constructor(message, modulePath) {
    super(message, 'NETWORK_ERROR', modulePath);
    this.name = 'NetworkError';
  }
}

class ParseError extends ModuleError {
  constructor(message, modulePath) {
    super(message, 'PARSE_ERROR', modulePath);
    this.name = 'ParseError';
  }
}

// 错误处理策略
class ModuleLoader {
  constructor() {
    this.errorHandlers = new Map();
    this.setupDefaultHandlers();
  }

  setupDefaultHandlers() {
    this.errorHandlers.set('NETWORK_ERROR', this.handleNetworkError.bind(this));
    this.errorHandlers.set('PARSE_ERROR', this.handleParseError.bind(this));
    this.errorHandlers.set('PERMISSION_ERROR', this.handlePermissionError.bind(this));
  }

  async load(modulePath) {
    try {
      return await import(modulePath);
    } catch (error) {
      const categorizedError = this.categorizeError(error, modulePath);
      return this.handleError(categorizedError);
    }
  }

  categorizeError(error, modulePath) {
    if (error.message.includes('fetch')) {
      return new NetworkError(error.message, modulePath);
    }
    
    if (error.message.includes('Unexpected token')) {
      return new ParseError(error.message, modulePath);
    }
    
    return new ModuleError(error.message, 'UNKNOWN_ERROR', modulePath);
  }

  async handleError(error) {
    const handler = this.errorHandlers.get(error.code);
    if (handler) {
      return await handler(error);
    }
    
    console.error('Unhandled module error:', error);
    throw error;
  }

  async handleNetworkError(error) {
    console.warn('Network error detected, attempting offline fallback');
    // 尝试从缓存加载
    return this.loadFromCache(error.modulePath);
  }

  async handleParseError(error) {
    console.warn('Parse error detected, attempting alternative version');
    // 尝试加载兼容版本
    const fallbackPath = error.modulePath.replace('.js', '.compat.js');
    return this.load(fallbackPath);
  }

  async handlePermissionError(error) {
    console.warn('Permission error, requesting user authorization');
    // 请求用户授权或提供替代方案
    return this.requestPermissionAndRetry(error.modulePath);
  }
}

运行时错误处理

模块级错误边界

// 模块错误边界包装器
class ModuleErrorBoundary {
  constructor(moduleName) {
    this.moduleName = moduleName;
    this.errorCount = 0;
    this.maxErrors = 5;
    this.resetInterval = 60000; // 1分钟
    this.setupErrorHandling();
  }

  setupErrorHandling() {
    // 捕获未处理的Promise拒绝
    window.addEventListener('unhandledrejection', (event) => {
      if (this.isModuleError(event.reason)) {
        this.handleError(event.reason);
        event.preventDefault();
      }
    });

    // 捕获未处理的错误
    window.addEventListener('error', (event) => {
      if (this.isModuleError(event.error)) {
        this.handleError(event.error);
      }
    });
  }

  isModuleError(error) {
    return error && error.stack && 
           error.stack.includes(this.moduleName);
  }

  handleError(error) {
    this.errorCount++;
    
    console.error(`Error in module ${this.moduleName}:`, error);
    
    // 错误频率限制
    if (this.errorCount >= this.maxErrors) {
      console.warn(`Module ${this.moduleName} has exceeded error limit, disabling`);
      this.disableModule();
      return;
    }

    // 发送错误报告
    this.reportError(error);
    
    // 重置错误计数
    setTimeout(() => {
      this.errorCount = Math.max(0, this.errorCount - 1);
    }, this.resetInterval);
  }

  disableModule() {
    // 禁用有问题的模块
    window[`${this.moduleName}_disabled`] = true;
  }

  reportError(error) {
    // 发送到错误监控服务
    if (typeof errorReporting !== 'undefined') {
      errorReporting.captureException(error, {
        module: this.moduleName,
        errorCount: this.errorCount
      });
    }
  }
}

// 使用示例
const chartModuleBoundary = new ModuleErrorBoundary('chart-module');

函数级错误处理

// 高阶函数:添加错误处理
function withErrorHandling(fn, options = {}) {
  const {
    retries = 0,
    timeout = 5000,
    fallback = null,
    onError = console.error
  } = options;

  return async function(...args) {
    let lastError;
    
    for (let attempt = 0; attempt <= retries; attempt++) {
      try {
        // 添加超时控制
        const timeoutPromise = new Promise((_, reject) =>
          setTimeout(() => reject(new Error('Function timeout')), timeout)
        );
        
        const resultPromise = Promise.resolve(fn.apply(this, args));
        
        return await Promise.race([resultPromise, timeoutPromise]);
      } catch (error) {
        lastError = error;
        onError(`Attempt ${attempt + 1} failed:`, error);
        
        if (attempt < retries) {
          await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
        }
      }
    }
    
    // 如果所有重试都失败,使用fallback
    if (fallback) {
      console.warn('Using fallback function due to repeated failures');
      return fallback.apply(this, args);
    }
    
    throw lastError;
  };
}

// 使用示例
const safeApiCall = withErrorHandling(
  async (url) => {
    const response = await fetch(url);
    return response.json();
  },
  {
    retries: 3,
    timeout: 10000,
    fallback: () => ({ error: 'Service unavailable' }),
    onError: (msg, error) => console.warn(`API call failed: ${msg}`, error)
  }
);

异步错误处理

Promise错误处理模式

// Promise链错误处理
class AsyncModuleManager {
  constructor() {
    this.pendingOperations = new Map();
    this.errorQueue = [];
  }

  async executeWithErrorHandling(operationId, asyncFn) {
    // 防止重复执行
    if (this.pendingOperations.has(operationId)) {
      return this.pendingOperations.get(operationId);
    }

    const operation = this.createOperation(operationId, asyncFn);
    this.pendingOperations.set(operationId, operation);

    try {
      const result = await operation;
      this.pendingOperations.delete(operationId);
      return result;
    } catch (error) {
      this.pendingOperations.delete(operationId);
      this.handleAsyncError(operationId, error);
      throw error;
    }
  }

  createOperation(operationId, asyncFn) {
    return Promise.resolve()
      .then(() => asyncFn())
      .catch(error => {
        // 增强错误信息
        error.operationId = operationId;
        error.timestamp = new Date().toISOString();
        throw error;
      });
  }

  handleAsyncError(operationId, error) {
    this.errorQueue.push({
      operationId,
      error,
      timestamp: Date.now()
    });

    // 限制错误队列大小
    if (this.errorQueue.length > 100) {
      this.errorQueue.shift();
    }

    this.reportAsyncError(operationId, error);
  }

  reportAsyncError(operationId, error) {
    console.error(`Async operation ${operationId} failed:`, error);
    
    // 批量发送错误报告
    this.batchReportErrors();
  }

  batchReportErrors() {
    if (this.errorQueue.length === 0) return;

    const errors = this.errorQueue.splice(0);
    
    // 发送到监控服务
    if (typeof analytics !== 'undefined') {
      analytics.track('Batch Error Report', {
        errors: errors.map(e => ({
          operationId: e.operationId,
          message: e.error.message,
          timestamp: e.timestamp
        }))
      });
    }
  }
}

并发错误处理

// 并发操作错误处理
class ConcurrentModuleLoader {
  constructor(maxConcurrency = 5) {
    this.maxConcurrency = maxConcurrency;
    this.activeLoads = 0;
    this.queue = [];
    this.results = new Map();
    this.errors = new Map();
  }

  async loadModules(modulePaths) {
    const loadPromises = modulePaths.map(path => this.queueLoad(path));
    
    // 使用Promise.allSettled处理部分失败
    const results = await Promise.allSettled(loadPromises);
    
    return this.processResults(results, modulePaths);
  }

  async queueLoad(modulePath) {
    return new Promise((resolve, reject) => {
      this.queue.push({ modulePath, resolve, reject });
      this.processQueue();
    });
  }

  async processQueue() {
    if (this.activeLoads >= this.maxConcurrency || this.queue.length === 0) {
      return;
    }

    const { modulePath, resolve, reject } = this.queue.shift();
    this.activeLoads++;

    try {
      const module = await this.loadSingleModule(modulePath);
      this.results.set(modulePath, module);
      resolve(module);
    } catch (error) {
      this.errors.set(modulePath, error);
      reject(error);
    } finally {
      this.activeLoads--;
      this.processQueue(); // 处理下一个
    }
  }

  async loadSingleModule(modulePath) {
    try {
      return await import(modulePath);
    } catch (error) {
      console.error(`Failed to load ${modulePath}:`, error);
      throw new Error(`Module load failed: ${modulePath}`);
    }
  }

  processResults(results, modulePaths) {
    const successful = [];
    const failed = [];

    results.forEach((result, index) => {
      const modulePath = modulePaths[index];
      
      if (result.status === 'fulfilled') {
        successful.push({ modulePath, module: result.value });
      } else {
        failed.push({ modulePath, error: result.reason });
      }
    });

    // 记录加载统计
    console.log(`Module loading completed: ${successful.length} successful, ${failed.length} failed`);
    
    if (failed.length > 0) {
      console.warn('Failed modules:', failed);
    }

    return {
      successful,
      failed,
      hasErrors: failed.length > 0
    };
  }
}

错误监控与报告

错误监控系统

class ModuleErrorMonitor {
  constructor() {
    this.errorBuffer = [];
    this.reportInterval = 30000; // 30秒
    this.maxBufferSize = 50;
    this.setupPeriodicReporting();
  }

  captureError(error, context = {}) {
    const errorData = {
      message: error.message,
      stack: error.stack,
      timestamp: Date.now(),
      url: window.location.href,
      userAgent: navigator.userAgent,
      context
    };

    this.errorBuffer.push(errorData);

    // 立即处理严重错误
    if (this.isCriticalError(error)) {
      this.reportImmediately(errorData);
    }

    // 缓冲区溢出时发送
    if (this.errorBuffer.length >= this.maxBufferSize) {
      this.sendErrorReport();
    }
  }

  isCriticalError(error) {
    const criticalPatterns = [
      /cannot read property/i,
      /is not a function/i,
      /network error/i,
      /security/i
    ];

    return criticalPatterns.some(pattern => pattern.test(error.message));
  }

  setupPeriodicReporting() {
    setInterval(() => {
      if (this.errorBuffer.length > 0) {
        this.sendErrorReport();
      }
    }, this.reportInterval);
  }

  async sendErrorReport() {
    if (this.errorBuffer.length === 0) return;

    const errors = this.errorBuffer.splice(0);
    
    try {
      await this.submitErrors(errors);
      console.log(`Sent ${errors.length} error reports`);
    } catch (reportError) {
      console.error('Failed to send error report:', reportError);
      // 将错误放回缓冲区
      this.errorBuffer.unshift(...errors.slice(-10)); // 只保留最新的10个
    }
  }

  async submitErrors(errors) {
    const reportData = {
      errors,
      session: this.getSessionInfo(),
      performance: this.getPerformanceMetrics()
    };

    const response = await fetch('/api/errors', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(reportData)
    });

    if (!response.ok) {
      throw new Error(`Error reporting failed: ${response.status}`);
    }
  }

  getSessionInfo() {
    return {
      sessionId: this.getSessionId(),
      timestamp: Date.now(),
      modules: this.getLoadedModules()
    };
  }

  getPerformanceMetrics() {
    if (!window.performance) return null;

    return {
      memory: window.performance.memory ? {
        used: window.performance.memory.usedJSHeapSize,
        total: window.performance.memory.totalJSHeapSize
      } : null,
      timing: window.performance.timing
    };
  }

  async reportImmediately(errorData) {
    try {
      await this.submitErrors([errorData]);
    } catch (error) {
      console.error('Failed to send immediate error report:', error);
    }
  }
}

// 全局错误监控器
const errorMonitor = new ModuleErrorMonitor();

// 集成到模块加载器
const originalImport = window.import || (() => {});
window.import = function(specifier) {
  return originalImport(specifier).catch(error => {
    errorMonitor.captureError(error, { 
      type: 'module_load', 
      specifier 
    });
    throw error;
  });
};

错误恢复策略

自动恢复机制

class ModuleRecoveryManager {
  constructor() {
    this.failedModules = new Map();
    this.recoveryStrategies = new Map();
    this.setupRecoveryStrategies();
  }

  setupRecoveryStrategies() {
    this.recoveryStrategies.set('reload', this.reloadModule.bind(this));
    this.recoveryStrategies.set('fallback', this.useFallbackModule.bind(this));
    this.recoveryStrategies.set('retry', this.retryModuleLoad.bind(this));
    this.recoveryStrategies.set('degrade', this.degradeGracefully.bind(this));
  }

  async handleModuleFailure(modulePath, error, context = {}) {
    const failureInfo = {
      error,
      timestamp: Date.now(),
      attempts: 0,
      context
    };

    this.failedModules.set(modulePath, failureInfo);

    // 选择恢复策略
    const strategy = this.selectRecoveryStrategy(error, context);
    
    try {
      return await this.executeRecoveryStrategy(strategy, modulePath, failureInfo);
    } catch (recoveryError) {
      console.error(`Recovery failed for ${modulePath}:`, recoveryError);
      return this.handleRecoveryFailure(modulePath, recoveryError);
    }
  }

  selectRecoveryStrategy(error, context) {
    if (error.message.includes('network')) {
      return 'retry';
    }
    
    if (error.message.includes('parse')) {
      return 'fallback';
    }
    
    if (context.isCritical) {
      return 'reload';
    }
    
    return 'degrade';
  }

  async executeRecoveryStrategy(strategy, modulePath, failureInfo) {
    const handler = this.recoveryStrategies.get(strategy);
    if (!handler) {
      throw new Error(`Unknown recovery strategy: ${strategy}`);
    }

    console.log(`Attempting recovery for ${modulePath} using strategy: ${strategy}`);
    return await handler(modulePath, failureInfo);
  }

  async reloadModule(modulePath, failureInfo) {
    // 强制重新加载模块
    const cacheBustedPath = `${modulePath}?v=${Date.now()}`;
    return await import(cacheBustedPath);
  }

  async useFallbackModule(modulePath, failureInfo) {
    // 尝试加载备用模块
    const fallbackPaths = [
      modulePath.replace('.js', '.fallback.js'),
      modulePath.replace('.js', '.min.js'),
      './fallbacks/default-module.js'
    ];

    for (const fallbackPath of fallbackPaths) {
      try {
        return await import(fallbackPath);
      } catch (fallbackError) {
        console.warn(`Fallback ${fallbackPath} also failed:`, fallbackError);
      }
    }

    throw new Error('All fallback options exhausted');
  }

  async retryModuleLoad(modulePath, failureInfo) {
    failureInfo.attempts++;
    const maxRetries = 3;
    
    if (failureInfo.attempts >= maxRetries) {
      throw new Error(`Max retries (${maxRetries}) exceeded for ${modulePath}`);
    }

    // 指数退避
    const delay = Math.pow(2, failureInfo.attempts) * 1000;
    await new Promise(resolve => setTimeout(resolve, delay));

    return await import(modulePath);
  }

  async degradeGracefully(modulePath, failureInfo) {
    // 返回最小功能实现
    console.warn(`Gracefully degrading functionality for ${modulePath}`);
    
    return {
      default: function() {
        console.warn(`Module ${modulePath} is unavailable, using degraded functionality`);
        return null;
      },
      __degraded: true,
      __originalPath: modulePath
    };
  }

  async handleRecoveryFailure(modulePath, recoveryError) {
    console.error(`All recovery attempts failed for ${modulePath}`);
    
    // 移除失败的模块记录
    this.failedModules.delete(modulePath);
    
    // 发送失败报告
    errorMonitor.captureError(recoveryError, {
      type: 'recovery_failure',
      modulePath,
      originalError: this.failedModules.get(modulePath)?.error
    });

    // 返回空实现避免应用崩溃
    return this.degradeGracefully(modulePath, {});
  }
}

// 全局恢复管理器
const recoveryManager = new ModuleRecoveryManager();

通过实施这些错误处理策略,可以大大提高模块化应用的健壮性和用户体验,确保即使在出现错误时应用也能继续运行。

测试模块化代码

模块化代码的测试策略与传统代码有所不同,需要考虑模块间的依赖关系、异步加载、隔离性等因素。本章将详细介绍如何有效测试JavaScript模块。

模块测试基础

单元测试策略

// userService.js - 被测试的模块
export class UserService {
  constructor(httpClient, cache) {
    this.httpClient = httpClient;
    this.cache = cache;
  }

  async getUser(id) {
    const cacheKey = `user:${id}`;
    
    // 先检查缓存
    const cached = await this.cache.get(cacheKey);
    if (cached) {
      return cached;
    }

    // 从API获取
    const user = await this.httpClient.get(`/users/${id}`);
    
    // 缓存结果
    await this.cache.set(cacheKey, user, { ttl: 300 });
    
    return user;
  }

  async updateUser(id, data) {
    const user = await this.httpClient.put(`/users/${id}`, data);
    
    // 清除相关缓存
    await this.cache.delete(`user:${id}`);
    
    return user;
  }
}

export const createUserService = (httpClient, cache) => {
  return new UserService(httpClient, cache);
};
// userService.test.js - 单元测试
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { UserService } from '../userService.js';

describe('UserService', () => {
  let userService;
  let mockHttpClient;
  let mockCache;

  beforeEach(() => {
    // 创建模拟依赖
    mockHttpClient = {
      get: vi.fn(),
      put: vi.fn()
    };
    
    mockCache = {
      get: vi.fn(),
      set: vi.fn(),
      delete: vi.fn()
    };

    userService = new UserService(mockHttpClient, mockCache);
  });

  describe('getUser', () => {
    it('should return cached user when available', async () => {
      const userId = '123';
      const cachedUser = { id: userId, name: 'John' };
      
      mockCache.get.mockResolvedValue(cachedUser);

      const result = await userService.getUser(userId);

      expect(result).toBe(cachedUser);
      expect(mockCache.get).toHaveBeenCalledWith('user:123');
      expect(mockHttpClient.get).not.toHaveBeenCalled();
    });

    it('should fetch from API and cache when not in cache', async () => {
      const userId = '123';
      const apiUser = { id: userId, name: 'John' };
      
      mockCache.get.mockResolvedValue(null);
      mockHttpClient.get.mockResolvedValue(apiUser);

      const result = await userService.getUser(userId);

      expect(result).toBe(apiUser);
      expect(mockHttpClient.get).toHaveBeenCalledWith('/users/123');
      expect(mockCache.set).toHaveBeenCalledWith('user:123', apiUser, { ttl: 300 });
    });
  });

  describe('updateUser', () => {
    it('should update user and clear cache', async () => {
      const userId = '123';
      const updateData = { name: 'Jane' };
      const updatedUser = { id: userId, name: 'Jane' };
      
      mockHttpClient.put.mockResolvedValue(updatedUser);

      const result = await userService.updateUser(userId, updateData);

      expect(result).toBe(updatedUser);
      expect(mockHttpClient.put).toHaveBeenCalledWith('/users/123', updateData);
      expect(mockCache.delete).toHaveBeenCalledWith('user:123');
    });
  });
});

模块隔离测试

// testUtils.js - 测试工具
export class ModuleTestEnvironment {
  constructor() {
    this.originalModules = new Map();
    this.mockModules = new Map();
  }

  // 备份原始模块
  backupModule(modulePath, moduleObject) {
    this.originalModules.set(modulePath, moduleObject);
  }

  // 创建模块模拟
  mockModule(modulePath, mockImplementation) {
    this.mockModules.set(modulePath, mockImplementation);
  }

  // 恢复所有模块
  restoreAll() {
    this.originalModules.clear();
    this.mockModules.clear();
  }

  // 获取模拟的模块
  getMockedModule(modulePath) {
    return this.mockModules.get(modulePath);
  }
}

// 模块测试装饰器
export function testModule(testFn) {
  return async () => {
    const env = new ModuleTestEnvironment();
    
    try {
      await testFn(env);
    } finally {
      env.restoreAll();
    }
  };
}

// 使用示例
import { testModule } from './testUtils.js';

describe('Module Integration Tests', () => {
  it('should handle module dependencies correctly', testModule(async (env) => {
    // 模拟依赖模块
    env.mockModule('./httpClient.js', {
      get: vi.fn().mockResolvedValue({ data: 'mocked' }),
      post: vi.fn().mockResolvedValue({ success: true })
    });

    env.mockModule('./logger.js', {
      info: vi.fn(),
      error: vi.fn()
    });

    // 导入并测试目标模块
    const { DataService } = await import('./dataService.js');
    const service = new DataService();
    
    const result = await service.fetchData();
    expect(result.data).toBe('mocked');
  }));
});

异步模块测试

动态导入测试

// moduleLoader.js - 动态模块加载器
export class ModuleLoader {
  constructor() {
    this.loadedModules = new Map();
  }

  async loadModule(modulePath) {
    if (this.loadedModules.has(modulePath)) {
      return this.loadedModules.get(modulePath);
    }

    try {
      const module = await import(modulePath);
      this.loadedModules.set(modulePath, module);
      return module;
    } catch (error) {
      throw new Error(`Failed to load module ${modulePath}: ${error.message}`);
    }
  }

  async loadModuleWithTimeout(modulePath, timeout = 5000) {
    const timeoutPromise = new Promise((_, reject) =>
      setTimeout(() => reject(new Error('Module load timeout')), timeout)
    );

    return Promise.race([
      this.loadModule(modulePath),
      timeoutPromise
    ]);
  }

  getLoadedModuleCount() {
    return this.loadedModules.size;
  }

  clearCache() {
    this.loadedModules.clear();
  }
}
// moduleLoader.test.js - 异步模块测试
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { ModuleLoader } from '../moduleLoader.js';

describe('ModuleLoader', () => {
  let loader;

  beforeEach(() => {
    loader = new ModuleLoader();
  });

  describe('loadModule', () => {
    it('should load module successfully', async () => {
      // 使用实际存在的模块进行测试
      const module = await loader.loadModule('./testData/sampleModule.js');
      
      expect(module).toBeDefined();
      expect(module.default).toBeDefined();
      expect(loader.getLoadedModuleCount()).toBe(1);
    });

    it('should cache loaded modules', async () => {
      const modulePath = './testData/sampleModule.js';
      
      const module1 = await loader.loadModule(modulePath);
      const module2 = await loader.loadModule(modulePath);
      
      expect(module1).toBe(module2); // 应该是同一个对象
      expect(loader.getLoadedModuleCount()).toBe(1);
    });

    it('should handle module load failure', async () => {
      const invalidPath = './nonexistent/module.js';
      
      await expect(loader.loadModule(invalidPath))
        .rejects
        .toThrow(/Failed to load module/);
    });
  });

  describe('loadModuleWithTimeout', () => {
    it('should timeout when module takes too long to load', async () => {
      // 模拟慢加载的模块
      const slowModulePath = './testData/slowModule.js';
      
      await expect(loader.loadModuleWithTimeout(slowModulePath, 100))
        .rejects
        .toThrow('Module load timeout');
    });
  });
});

Promise并发测试

// concurrentLoader.js - 并发模块加载器
export class ConcurrentModuleLoader {
  constructor(maxConcurrency = 3) {
    this.maxConcurrency = maxConcurrency;
    this.activeLoads = 0;
    this.pendingLoads = [];
    this.results = new Map();
  }

  async loadModules(modulePaths) {
    const loadPromises = modulePaths.map(path => this.queueLoad(path));
    
    const results = await Promise.allSettled(loadPromises);
    
    return this.processResults(results, modulePaths);
  }

  async queueLoad(modulePath) {
    if (this.results.has(modulePath)) {
      return this.results.get(modulePath);
    }

    return new Promise((resolve, reject) => {
      this.pendingLoads.push({ modulePath, resolve, reject });
      this.processQueue();
    });
  }

  async processQueue() {
    if (this.activeLoads >= this.maxConcurrency || this.pendingLoads.length === 0) {
      return;
    }

    const { modulePath, resolve, reject } = this.pendingLoads.shift();
    this.activeLoads++;

    try {
      const module = await import(modulePath);
      this.results.set(modulePath, module);
      resolve(module);
    } catch (error) {
      reject(error);
    } finally {
      this.activeLoads--;
      this.processQueue();
    }
  }

  processResults(results, modulePaths) {
    const successful = [];
    const failed = [];

    results.forEach((result, index) => {
      const modulePath = modulePaths[index];
      
      if (result.status === 'fulfilled') {
        successful.push({ path: modulePath, module: result.value });
      } else {
        failed.push({ path: modulePath, error: result.reason });
      }
    });

    return { successful, failed };
  }
}
// concurrentLoader.test.js - 并发测试
import { describe, it, expect, beforeEach } from 'vitest';
import { ConcurrentModuleLoader } from '../concurrentLoader.js';

describe('ConcurrentModuleLoader', () => {
  let loader;

  beforeEach(() => {
    loader = new ConcurrentModuleLoader(2); // 最大并发数为2
  });

  it('should load multiple modules concurrently', async () => {
    const modulePaths = [
      './testData/module1.js',
      './testData/module2.js',
      './testData/module3.js'
    ];

    const startTime = Date.now();
    const result = await loader.loadModules(modulePaths);
    const endTime = Date.now();

    expect(result.successful).toHaveLength(3);
    expect(result.failed).toHaveLength(0);
    
    // 验证并发加载确实更快
    expect(endTime - startTime).toBeLessThan(3000); // 假设单个模块加载需要1秒
  });

  it('should handle mixed success and failure', async () => {
    const modulePaths = [
      './testData/validModule.js',
      './nonexistent/module.js',
      './testData/anotherValidModule.js'
    ];

    const result = await loader.loadModules(modulePaths);

    expect(result.successful).toHaveLength(2);
    expect(result.failed).toHaveLength(1);
    
    expect(result.failed[0].path).toBe('./nonexistent/module.js');
  });

  it('should respect concurrency limits', async () => {
    const loader = new ConcurrentModuleLoader(1); // 限制为1
    
    let concurrentCount = 0;
    let maxConcurrent = 0;

    // 创建模拟模块路径
    const modulePaths = Array.from({ length: 5 }, (_, i) => `./test${i}.js`);
    
    // 监控并发数
    const originalImport = global.import;
    global.import = async (path) => {
      concurrentCount++;
      maxConcurrent = Math.max(maxConcurrent, concurrentCount);
      
      await new Promise(resolve => setTimeout(resolve, 100));
      
      concurrentCount--;
      return { default: `module-${path}` };
    };

    try {
      await loader.loadModules(modulePaths);
      expect(maxConcurrent).toBe(1);
    } finally {
      global.import = originalImport;
    }
  });
});

集成测试

模块间集成测试

// integration.test.js - 集成测试
import { describe, it, expect, beforeEach, afterEach } from 'vitest';

describe('Module Integration Tests', () => {
  let testServer;
  let testDatabase;

  beforeEach(async () => {
    // 启动测试环境
    testDatabase = await startTestDatabase();
    testServer = await startTestServer();
  });

  afterEach(async () => {
    // 清理测试环境
    await testServer.stop();
    await testDatabase.cleanup();
  });

  it('should handle complete user workflow', async () => {
    // 导入真实模块
    const { UserService } = await import('../src/services/userService.js');
    const { HttpClient } = await import('../src/http/httpClient.js');
    const { RedisCache } = await import('../src/cache/redisCache.js');

    // 创建真实的依赖实例
    const httpClient = new HttpClient({
      baseURL: testServer.url,
      timeout: 5000
    });

    const cache = new RedisCache({
      host: testDatabase.host,
      port: testDatabase.port
    });

    const userService = new UserService(httpClient, cache);

    // 测试完整的用户工作流
    const userData = { name: 'Test User', email: 'test@example.com' };
    
    // 1. 创建用户
    const createdUser = await userService.createUser(userData);
    expect(createdUser.id).toBeDefined();

    // 2. 获取用户(应该从API获取)
    const fetchedUser = await userService.getUser(createdUser.id);
    expect(fetchedUser.name).toBe(userData.name);

    // 3. 再次获取用户(应该从缓存获取)
    const cachedUser = await userService.getUser(createdUser.id);
    expect(cachedUser.name).toBe(userData.name);

    // 4. 更新用户
    const updatedData = { name: 'Updated User' };
    const updatedUser = await userService.updateUser(createdUser.id, updatedData);
    expect(updatedUser.name).toBe(updatedData.name);

    // 5. 验证缓存已被清除
    const freshUser = await userService.getUser(createdUser.id);
    expect(freshUser.name).toBe(updatedData.name);
  });

  it('should handle module loading failures gracefully', async () => {
    // 模拟模块加载失败的情况
    const { ModuleManager } = await import('../src/core/moduleManager.js');
    
    const manager = new ModuleManager();
    
    // 注册模块加载失败的处理器
    const errorHandler = vi.fn();
    manager.onModuleLoadError(errorHandler);

    // 尝试加载不存在的模块
    const result = await manager.loadOptionalModule('./nonexistent.js');
    
    expect(result).toBeNull();
    expect(errorHandler).toHaveBeenCalled();
  });
});

端到端测试

// e2e.test.js - 端到端测试
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { chromium } from 'playwright';

describe('End-to-End Module Tests', () => {
  let browser;
  let context;
  let page;

  beforeAll(async () => {
    browser = await chromium.launch();
    context = await browser.newContext();
    page = await context.newPage();
  });

  afterAll(async () => {
    await browser.close();
  });

  it('should load modules dynamically in browser', async () => {
    // 访问测试页面
    await page.goto('http://localhost:3000/test.html');

    // 等待页面初始化
    await page.waitForLoadState('networkidle');

    // 触发动态模块加载
    await page.click('[data-testid="load-chart-module"]');

    // 等待模块加载完成
    await page.waitForSelector('[data-testid="chart-container"]');

    // 验证模块功能
    const chartTitle = await page.textContent('[data-testid="chart-title"]');
    expect(chartTitle).toBe('Sales Chart');

    // 验证模块间通信
    await page.click('[data-testid="update-chart-data"]');
    
    await page.waitForFunction(() => {
      const chart = document.querySelector('[data-testid="chart-container"]');
      return chart && chart.dataset.updated === 'true';
    });

    const updatedData = await page.getAttribute('[data-testid="chart-container"]', 'data-points');
    expect(parseInt(updatedData)).toBeGreaterThan(0);
  });

  it('should handle module load errors gracefully', async () => {
    await page.goto('http://localhost:3000/error-test.html');

    // 模拟网络故障
    await page.route('**/modules/broken-module.js', route => {
      route.fulfill({ status: 500, body: 'Server Error' });
    });

    // 触发有问题的模块加载
    await page.click('[data-testid="load-broken-module"]');

    // 验证错误处理
    await page.waitForSelector('[data-testid="error-message"]');
    
    const errorMessage = await page.textContent('[data-testid="error-message"]');
    expect(errorMessage).toContain('Module failed to load');

    // 验证fallback机制
    await page.waitForSelector('[data-testid="fallback-content"]');
    
    const fallbackText = await page.textContent('[data-testid="fallback-content"]');
    expect(fallbackText).toBe('Using fallback implementation');
  });
});

性能测试

模块加载性能测试

// performance.test.js - 性能测试
import { describe, it, expect } from 'vitest';
import { performance } from 'perf_hooks';

describe('Module Performance Tests', () => {
  it('should load modules within acceptable time limits', async () => {
    const moduleLoads = [
      './src/modules/chart.js',
      './src/modules/table.js',
      './src/modules/form.js',
      './src/modules/validation.js'
    ];

    const startTime = performance.now();
    
    const loadPromises = moduleLoads.map(async (modulePath) => {
      const moduleStartTime = performance.now();
      const module = await import(modulePath);
      const moduleEndTime = performance.now();
      
      return {
        path: modulePath,
        loadTime: moduleEndTime - moduleStartTime,
        module
      };
    });

    const results = await Promise.all(loadPromises);
    const totalTime = performance.now() - startTime;

    // 验证总加载时间
    expect(totalTime).toBeLessThan(2000); // 2秒内

    // 验证单个模块加载时间
    results.forEach(result => {
      expect(result.loadTime).toBeLessThan(500); // 每个模块500ms内
      expect(result.module).toBeDefined();
    });

    // 验证并发加载确实比串行快
    const averageTime = totalTime / results.length;
    expect(averageTime).toBeLessThan(totalTime);
  });

  it('should handle memory usage efficiently', async () => {
    const initialMemory = process.memoryUsage();
    
    // 加载大量模块
    const modulePromises = Array.from({ length: 50 }, (_, i) => 
      import(`./testData/module${i % 5}.js`) // 重复使用5个模块
    );

    await Promise.all(modulePromises);

    const finalMemory = process.memoryUsage();
    const memoryIncrease = finalMemory.heapUsed - initialMemory.heapUsed;

    // 验证内存增长在合理范围内(考虑模块缓存)
    expect(memoryIncrease).toBeLessThan(50 * 1024 * 1024); // 50MB
  });

  it('should demonstrate efficient module caching', async () => {
    const modulePath = './src/modules/heavyModule.js';
    
    // 第一次加载
    const firstLoadStart = performance.now();
    const firstModule = await import(modulePath);
    const firstLoadTime = performance.now() - firstLoadStart;

    // 第二次加载(应该从缓存获取)
    const secondLoadStart = performance.now();
    const secondModule = await import(modulePath);
    const secondLoadTime = performance.now() - secondLoadStart;

    // 验证缓存效果
    expect(secondModule).toBe(firstModule); // 同一个对象
    expect(secondLoadTime).toBeLessThan(firstLoadTime * 0.1); // 缓存加载应该快很多
  });
});

测试工具与配置

测试环境配置

// vitest.config.js - 测试配置
import { defineConfig } from 'vitest/config';
import path from 'path';

export default defineConfig({
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: ['./tests/setup.js'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'html', 'lcov'],
      exclude: [
        'node_modules/',
        'tests/',
        '**/*.test.js',
        '**/*.config.js'
      ]
    },
    // 模块解析配置
    alias: {
      '@': path.resolve(__dirname, './src'),
      '@tests': path.resolve(__dirname, './tests')
    },
    // 超时配置
    testTimeout: 10000,
    hookTimeout: 10000
  },
  // 模块转换配置
  esbuild: {
    target: 'es2020'
  }
});
// tests/setup.js - 测试设置
import { vi } from 'vitest';

// 全局模拟
global.fetch = vi.fn();

// 模拟浏览器API
Object.defineProperty(window, 'performance', {
  value: {
    now: vi.fn(() => Date.now()),
    mark: vi.fn(),
    measure: vi.fn()
  }
});

// 模拟动态导入
const originalImport = global.import;
global.import = vi.fn().mockImplementation((path) => {
  // 对测试模块使用真实导入
  if (path.startsWith('./testData/') || path.startsWith('../')) {
    return originalImport(path);
  }
  
  // 对其他模块返回模拟
  return Promise.resolve({
    default: `mocked-${path}`,
    __esModule: true
  });
});

// 清理函数
afterEach(() => {
  vi.clearAllMocks();
});

测试数据生成

// tests/testDataGenerator.js - 测试数据生成器
export class TestDataGenerator {
  static createMockModule(name, exports = {}) {
    return {
      default: exports.default || (() => `Mock ${name}`),
      ...exports,
      __moduleName: name,
      __isMock: true
    };
  }

  static createModuleTree(depth = 3, breadth = 3) {
    const tree = {
      modules: new Map(),
      dependencies: new Map()
    };

    for (let i = 0; i < breadth; i++) {
      const moduleName = `level0-module${i}`;
      const module = this.createMockModule(moduleName);
      
      tree.modules.set(moduleName, module);
      
      if (depth > 1) {
        const children = this.createModuleTree(depth - 1, breadth);
        tree.dependencies.set(moduleName, Array.from(children.modules.keys()));
        
        // 合并子模块
        children.modules.forEach((module, name) => {
          tree.modules.set(name, module);
        });
        
        children.dependencies.forEach((deps, name) => {
          tree.dependencies.set(name, deps);
        });
      }
    }

    return tree;
  }

  static async createAsyncModules(count = 10) {
    const modules = [];
    
    for (let i = 0; i < count; i++) {
      const delay = Math.random() * 1000; // 0-1秒随机延迟
      
      const module = await new Promise(resolve => {
        setTimeout(() => {
          resolve(this.createMockModule(`async-module-${i}`, {
            loadTime: delay,
            id: i
          }));
        }, delay);
      });
      
      modules.push(module);
    }

    return modules;
  }
}

通过这些全面的测试策略和工具,可以确保模块化代码的质量和可靠性,从单元测试到集成测试,从功能验证到性能监控,形成完整的测试体系。

微前端模块化

微前端架构将前端应用拆分为多个独立的、可部署的前端应用,每个应用负责特定的业务域。本章将探讨如何使用JavaScript模块系统实现微前端架构。

微前端基础概念

微前端架构特点

// 微前端架构的核心特征
const MicrofrontendFeatures = {
  // 技术栈无关性
  technologyAgnostic: {
    react: 'React应用',
    vue: 'Vue应用',
    angular: 'Angular应用',
    vanilla: '原生JavaScript应用'
  },
  
  // 独立部署
  independentDeployment: {
    cicd: '每个微前端有自己的CI/CD流水线',
    versioning: '独立版本管理',
    rollback: '独立回滚能力'
  },
  
  // 团队自治
  teamAutonomy: {
    ownership: '团队完全拥有微前端的开发和维护',
    decisions: '技术决策独立',
    timeline: '开发周期独立'
  }
};

// 微前端应用注册表
class MicrofrontendRegistry {
  constructor() {
    this.applications = new Map();
    this.routes = new Map();
    this.sharedDependencies = new Map();
  }

  // 注册微前端应用
  register(name, config) {
    this.applications.set(name, {
      name,
      url: config.url,
      mount: config.mount,
      unmount: config.unmount,
      activeWhen: config.activeWhen,
      dependencies: config.dependencies || [],
      sharedLibraries: config.sharedLibraries || []
    });
  }

  // 获取激活的应用
  getActiveApplications(location) {
    return Array.from(this.applications.values())
      .filter(app => app.activeWhen(location));
  }

  // 预加载应用
  async preloadApplication(name) {
    const app = this.applications.get(name);
    if (!app || app.loaded) return;

    try {
      const module = await import(app.url);
      app.loaded = true;
      app.module = module;
      return module;
    } catch (error) {
      console.error(`Failed to preload application ${name}:`, error);
      throw error;
    }
  }
}

模块联邦 (Module Federation)

Webpack Module Federation

// webpack.config.js - 主应用 (Shell/Host)
const ModuleFederationPlugin = require('@module-federation/webpack');

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  devServer: {
    port: 3000,
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'shell',
      remotes: {
        'user-app': 'userApp@http://localhost:3001/remoteEntry.js',
        'product-app': 'productApp@http://localhost:3002/remoteEntry.js',
        'cart-app': 'cartApp@http://localhost:3003/remoteEntry.js'
      },
      shared: {
        react: {
          singleton: true,
          requiredVersion: '^18.0.0'
        },
        'react-dom': {
          singleton: true,
          requiredVersion: '^18.0.0'
        }
      }
    })
  ]
};

// 微前端应用配置
// webpack.config.js - 用户管理应用
module.exports = {
  mode: 'development',
  entry: './src/index.js',
  devServer: {
    port: 3001,
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'userApp',
      filename: 'remoteEntry.js',
      exposes: {
        './UserList': './src/components/UserList',
        './UserProfile': './src/components/UserProfile',
        './UserManagement': './src/UserManagement'
      },
      shared: {
        react: {
          singleton: true,
          requiredVersion: '^18.0.0'
        },
        'react-dom': {
          singleton: true,
          requiredVersion: '^18.0.0'
        }
      }
    })
  ]
};

动态模块加载

// shell应用 - 动态加载微前端
class MicrofrontendLoader {
  constructor() {
    this.loadedApps = new Map();
    this.loadingPromises = new Map();
  }

  async loadMicrofrontend(appName, remoteName) {
    if (this.loadedApps.has(appName)) {
      return this.loadedApps.get(appName);
    }

    if (this.loadingPromises.has(appName)) {
      return this.loadingPromises.get(appName);
    }

    const loadingPromise = this._loadRemoteModule(appName, remoteName);
    this.loadingPromises.set(appName, loadingPromise);

    try {
      const app = await loadingPromise;
      this.loadedApps.set(appName, app);
      this.loadingPromises.delete(appName);
      return app;
    } catch (error) {
      this.loadingPromises.delete(appName);
      throw error;
    }
  }

  async _loadRemoteModule(appName, remoteName) {
    try {
      // 动态导入远程模块
      const module = await import(remoteName);
      
      return {
        name: appName,
        module,
        mount: module.mount || (() => {}),
        unmount: module.unmount || (() => {}),
        update: module.update || (() => {})
      };
    } catch (error) {
      console.error(`Failed to load microfrontend ${appName}:`, error);
      
      // 返回错误边界组件
      return this._createErrorBoundary(appName, error);
    }
  }

  _createErrorBoundary(appName, error) {
    return {
      name: appName,
      module: {
        default: () => ({
          render: (container) => {
            container.innerHTML = `
              <div class="microfrontend-error">
                <h3>应用 ${appName} 加载失败</h3>
                <p>错误信息: ${error.message}</p>
                <button onclick="location.reload()">重试</button>
              </div>
            `;
          }
        })
      },
      mount: () => {},
      unmount: () => {}
    };
  }
}

// 使用示例
const loader = new MicrofrontendLoader();

// React组件中使用
function App() {
  const [userApp, setUserApp] = useState(null);
  const [loading, setLoading] = useState(false);

  const loadUserApp = async () => {
    setLoading(true);
    try {
      const app = await loader.loadMicrofrontend('user-app', 'user-app/UserManagement');
      setUserApp(app);
    } catch (error) {
      console.error('Failed to load user app:', error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div>
      <nav>
        <button onClick={loadUserApp}>加载用户管理</button>
      </nav>
      {loading && <div>加载中...</div>}
      {userApp && <RemoteComponent app={userApp} />}
    </div>
  );
}

应用间通信

事件总线通信

// 微前端事件总线
class MicrofrontendEventBus {
  constructor() {
    this.events = new Map();
    this.wildcardEvents = new Map();
    this.middlewares = [];
  }

  // 添加中间件
  use(middleware) {
    this.middlewares.push(middleware);
  }

  // 订阅事件
  on(eventName, handler, options = {}) {
    const { once = false, priority = 0, namespace = 'global' } = options;
    
    const subscription = {
      handler,
      once,
      priority,
      namespace,
      id: Math.random().toString(36).substr(2, 9)
    };

    if (eventName.includes('*')) {
      // 通配符事件
      if (!this.wildcardEvents.has(eventName)) {
        this.wildcardEvents.set(eventName, []);
      }
      this.wildcardEvents.get(eventName).push(subscription);
    } else {
      // 普通事件
      if (!this.events.has(eventName)) {
        this.events.set(eventName, []);
      }
      this.events.get(eventName).push(subscription);
    }

    // 按优先级排序
    const handlers = this.events.get(eventName) || this.wildcardEvents.get(eventName);
    handlers.sort((a, b) => b.priority - a.priority);

    // 返回取消订阅函数
    return () => this.off(eventName, subscription.id);
  }

  // 取消订阅
  off(eventName, handlerOrId) {
    const removeFromArray = (array, predicate) => {
      const index = array.findIndex(predicate);
      if (index > -1) {
        array.splice(index, 1);
      }
    };

    const predicate = typeof handlerOrId === 'string' 
      ? (sub) => sub.id === handlerOrId
      : (sub) => sub.handler === handlerOrId;

    if (this.events.has(eventName)) {
      removeFromArray(this.events.get(eventName), predicate);
    }

    // 检查通配符事件
    this.wildcardEvents.forEach((handlers, pattern) => {
      if (this._matchPattern(pattern, eventName)) {
        removeFromArray(handlers, predicate);
      }
    });
  }

  // 发布事件
  async emit(eventName, data, options = {}) {
    const { timeout = 5000, retries = 0 } = options;
    
    // 应用中间件
    let processedData = data;
    for (const middleware of this.middlewares) {
      processedData = await middleware(eventName, processedData, options);
    }

    const event = {
      name: eventName,
      data: processedData,
      timestamp: Date.now(),
      source: options.source || 'unknown'
    };

    const promises = [];

    // 普通事件处理器
    if (this.events.has(eventName)) {
      const handlers = this.events.get(eventName);
      promises.push(...this._executeHandlers(handlers, event, timeout));
    }

    // 通配符事件处理器
    this.wildcardEvents.forEach((handlers, pattern) => {
      if (this._matchPattern(pattern, eventName)) {
        promises.push(...this._executeHandlers(handlers, event, timeout));
      }
    });

    // 等待所有处理器完成
    try {
      await Promise.allSettled(promises);
    } catch (error) {
      console.error(`Event ${eventName} handling failed:`, error);
      
      if (retries > 0) {
        console.log(`Retrying event ${eventName}, ${retries} attempts left`);
        return this.emit(eventName, data, { ...options, retries: retries - 1 });
      }
    }
  }

  _executeHandlers(handlers, event, timeout) {
    return handlers.map(async (subscription) => {
      try {
        const timeoutPromise = new Promise((_, reject) =>
          setTimeout(() => reject(new Error('Handler timeout')), timeout)
        );

        const handlerPromise = Promise.resolve(subscription.handler(event));
        
        await Promise.race([handlerPromise, timeoutPromise]);

        // 一次性订阅处理
        if (subscription.once) {
          this.off(event.name, subscription.id);
        }
      } catch (error) {
        console.error(`Handler failed for event ${event.name}:`, error);
      }
    });
  }

  _matchPattern(pattern, eventName) {
    const regex = new RegExp(
      pattern.replace(/\*/g, '.*').replace(/\?/g, '.')
    );
    return regex.test(eventName);
  }
}

// 全局事件总线实例
const eventBus = new MicrofrontendEventBus();

// 使用示例
// 用户应用 - 发布用户登录事件
eventBus.emit('user.login', {
  userId: '123',
  username: 'john_doe',
  timestamp: Date.now()
}, { source: 'user-app' });

// 购物车应用 - 监听用户登录
eventBus.on('user.login', (event) => {
  console.log('User logged in, updating cart:', event.data);
  // 更新购物车状态
});

// 导航应用 - 监听所有用户事件
eventBus.on('user.*', (event) => {
  console.log('User event received:', event.name, event.data);
  // 更新导航状态
});

状态共享

// 微前端状态管理
class MicrofrontendStore {
  constructor() {
    this.state = new Map();
    this.subscribers = new Map();
    this.middlewares = [];
    this.history = [];
    this.maxHistory = 50;
  }

  // 添加中间件
  use(middleware) {
    this.middlewares.push(middleware);
  }

  // 设置状态
  async setState(namespace, updates, options = {}) {
    const { merge = true, notify = true } = options;
    
    const currentState = this.state.get(namespace) || {};
    const newState = merge ? { ...currentState, ...updates } : updates;
    
    // 应用中间件
    let processedState = newState;
    for (const middleware of this.middlewares) {
      processedState = await middleware({
        action: 'setState',
        namespace,
        oldState: currentState,
        newState: processedState,
        updates
      });
    }

    // 记录历史
    this.history.push({
      timestamp: Date.now(),
      namespace,
      oldState: currentState,
      newState: processedState,
      action: 'setState'
    });

    if (this.history.length > this.maxHistory) {
      this.history.shift();
    }

    this.state.set(namespace, processedState);

    // 通知订阅者
    if (notify) {
      this._notifySubscribers(namespace, processedState, currentState);
    }

    return processedState;
  }

  // 获取状态
  getState(namespace, path) {
    const state = this.state.get(namespace);
    
    if (!state) return undefined;
    if (!path) return state;

    // 支持路径访问 'user.profile.name'
    return path.split('.').reduce((obj, key) => obj?.[key], state);
  }

  // 订阅状态变化
  subscribe(namespace, callback, options = {}) {
    const { path, immediate = false } = options;
    
    if (!this.subscribers.has(namespace)) {
      this.subscribers.set(namespace, []);
    }

    const subscription = {
      callback,
      path,
      id: Math.random().toString(36).substr(2, 9)
    };

    this.subscribers.get(namespace).push(subscription);

    // 立即调用回调
    if (immediate) {
      const state = this.getState(namespace, path);
      callback(state, undefined, namespace);
    }

    // 返回取消订阅函数
    return () => {
      const subs = this.subscribers.get(namespace);
      const index = subs.findIndex(sub => sub.id === subscription.id);
      if (index > -1) {
        subs.splice(index, 1);
      }
    };
  }

  // 清理命名空间
  clearNamespace(namespace) {
    this.state.delete(namespace);
    this.subscribers.delete(namespace);
  }

  // 获取所有命名空间
  getNamespaces() {
    return Array.from(this.state.keys());
  }

  _notifySubscribers(namespace, newState, oldState) {
    const subscribers = this.subscribers.get(namespace) || [];
    
    subscribers.forEach(({ callback, path }) => {
      try {
        const newValue = path ? this._getValueByPath(newState, path) : newState;
        const oldValue = path ? this._getValueByPath(oldState, path) : oldState;
        
        // 只在值实际改变时通知
        if (JSON.stringify(newValue) !== JSON.stringify(oldValue)) {
          callback(newValue, oldValue, namespace);
        }
      } catch (error) {
        console.error(`Subscriber callback failed for ${namespace}:`, error);
      }
    });
  }

  _getValueByPath(obj, path) {
    return path.split('.').reduce((current, key) => current?.[key], obj);
  }
}

// 全局状态存储
const microfrontendStore = new MicrofrontendStore();

// 添加日志中间件
microfrontendStore.use(async (context) => {
  console.log('State change:', context);
  return context.newState;
});

// 使用示例
// 用户应用
microfrontendStore.setState('user', {
  isLoggedIn: true,
  profile: {
    id: '123',
    name: 'John Doe',
    email: 'john@example.com'
  }
});

// 购物车应用监听用户状态
const unsubscribe = microfrontendStore.subscribe('user', (userState) => {
  if (userState.isLoggedIn) {
    console.log('User logged in, loading cart for:', userState.profile.name);
    // 加载用户购物车
  }
}, { immediate: true });

// 产品应用只监听用户ID
microfrontendStore.subscribe('user', (userId) => {
  console.log('User ID changed:', userId);
  // 更新推荐产品
}, { path: 'profile.id' });

路由管理

微前端路由系统

// 微前端路由管理器
class MicrofrontendRouter {
  constructor() {
    this.routes = new Map();
    this.activeApplications = new Set();
    this.currentLocation = this._getCurrentLocation();
    this.beforeRouteChangeHooks = [];
    this.afterRouteChangeHooks = [];
    
    this._setupEventListeners();
  }

  // 注册路由
  registerRoute(pattern, config) {
    this.routes.set(pattern, {
      ...config,
      pattern,
      regex: this._patternToRegex(pattern),
      active: false
    });
  }

  // 导航到指定路径
  async navigate(path, options = {}) {
    const { replace = false, state = null } = options;
    
    // 执行前置钩子
    for (const hook of this.beforeRouteChangeHooks) {
      const result = await hook(path, this.currentLocation);
      if (result === false) {
        console.log('Navigation cancelled by beforeRouteChange hook');
        return false;
      }
    }

    // 更新浏览器历史
    if (replace) {
      history.replaceState(state, '', path);
    } else {
      history.pushState(state, '', path);
    }

    // 更新当前位置并重新路由
    await this._handleLocationChange(path);
    
    return true;
  }

  // 添加路由变化钩子
  beforeRouteChange(hook) {
    this.beforeRouteChangeHooks.push(hook);
  }

  afterRouteChange(hook) {
    this.afterRouteChangeHooks.push(hook);
  }

  // 获取当前激活的应用
  getActiveApplications() {
    return Array.from(this.activeApplications);
  }

  async _handleLocationChange(newLocation = this._getCurrentLocation()) {
    const oldLocation = this.currentLocation;
    this.currentLocation = newLocation;

    // 计算需要激活和停用的应用
    const { toActivate, toDeactivate } = this._calculateApplicationChanges();

    // 停用应用
    for (const appName of toDeactivate) {
      await this._deactivateApplication(appName);
    }

    // 激活应用
    for (const appName of toActivate) {
      await this._activateApplication(appName);
    }

    // 执行后置钩子
    for (const hook of this.afterRouteChangeHooks) {
      await hook(newLocation, oldLocation);
    }
  }

  _calculateApplicationChanges() {
    const newActiveApps = new Set();
    
    // 检查每个路由是否匹配当前位置
    for (const [pattern, route] of this.routes) {
      if (route.regex.test(this.currentLocation)) {
        newActiveApps.add(route.application);
      }
    }

    const toActivate = new Set([...newActiveApps].filter(app => !this.activeApplications.has(app)));
    const toDeactivate = new Set([...this.activeApplications].filter(app => !newActiveApps.has(app)));

    return { toActivate, toDeactivate };
  }

  async _activateApplication(appName) {
    try {
      const route = this._findRouteByApplication(appName);
      if (!route) return;

      console.log(`Activating application: ${appName}`);
      
      // 加载应用模块
      let appModule;
      if (typeof route.loadApp === 'function') {
        appModule = await route.loadApp();
      } else if (typeof route.loadApp === 'string') {
        appModule = await import(route.loadApp);
      }

      // 挂载应用
      if (appModule && appModule.mount) {
        const mountProps = {
          domElement: route.domElementGetter ? route.domElementGetter() : document.getElementById(route.containerId),
          currentLocation: this.currentLocation,
          routeParams: this._extractRouteParams(route.pattern, this.currentLocation)
        };

        await appModule.mount(mountProps);
        route.mountedModule = appModule;
      }

      this.activeApplications.add(appName);
    } catch (error) {
      console.error(`Failed to activate application ${appName}:`, error);
    }
  }

  async _deactivateApplication(appName) {
    try {
      const route = this._findRouteByApplication(appName);
      if (!route || !route.mountedModule) return;

      console.log(`Deactivating application: ${appName}`);

      // 卸载应用
      if (route.mountedModule.unmount) {
        await route.mountedModule.unmount();
      }

      route.mountedModule = null;
      this.activeApplications.delete(appName);
    } catch (error) {
      console.error(`Failed to deactivate application ${appName}:`, error);
    }
  }

  _findRouteByApplication(appName) {
    for (const route of this.routes.values()) {
      if (route.application === appName) {
        return route;
      }
    }
    return null;
  }

  _extractRouteParams(pattern, path) {
    const paramNames = pattern.match(/:(\w+)/g)?.map(param => param.slice(1)) || [];
    const matches = path.match(this._patternToRegex(pattern));
    
    if (!matches) return {};

    const params = {};
    paramNames.forEach((name, index) => {
      params[name] = matches[index + 1];
    });

    return params;
  }

  _patternToRegex(pattern) {
    const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    const withParams = escaped.replace(/\\:(\w+)/g, '([^/]+)');
    return new RegExp(`^${withParams}$`);
  }

  _getCurrentLocation() {
    return window.location.pathname + window.location.search + window.location.hash;
  }

  _setupEventListeners() {
    // 监听浏览器历史变化
    window.addEventListener('popstate', () => {
      this._handleLocationChange();
    });

    // 拦截链接点击
    document.addEventListener('click', (event) => {
      if (event.target.tagName === 'A' && event.target.origin === window.location.origin) {
        event.preventDefault();
        this.navigate(event.target.pathname + event.target.search + event.target.hash);
      }
    });
  }
}

// 使用示例
const router = new MicrofrontendRouter();

// 注册微前端路由
router.registerRoute('/users/*', {
  application: 'user-app',
  loadApp: () => import('user-app/UserApp'),
  containerId: 'user-app-container'
});

router.registerRoute('/products/:category', {
  application: 'product-app',
  loadApp: 'product-app/ProductApp',
  domElementGetter: () => document.querySelector('#product-container')
});

router.registerRoute('/cart', {
  application: 'cart-app',
  loadApp: () => import('cart-app/CartApp'),
  containerId: 'cart-app-container'
});

// 添加路由守卫
router.beforeRouteChange(async (to, from) => {
  if (to.startsWith('/admin') && !isUserAdmin()) {
    console.log('Access denied to admin area');
    return false;
  }
  return true;
});

// 启动路由
router._handleLocationChange();

性能优化

资源共享与优化

// 共享资源管理器
class SharedResourceManager {
  constructor() {
    this.sharedLibraries = new Map();
    this.loadingPromises = new Map();
    this.dependencyGraph = new Map();
  }

  // 注册共享库
  registerSharedLibrary(name, config) {
    this.sharedLibraries.set(name, {
      name,
      version: config.version,
      url: config.url,
      eager: config.eager || false,
      singleton: config.singleton || false,
      requiredVersion: config.requiredVersion,
      shareScope: config.shareScope || 'default',
      loaded: false,
      module: null
    });

    if (config.eager) {
      this.preloadLibrary(name);
    }
  }

  // 预加载库
  async preloadLibrary(name) {
    if (this.loadingPromises.has(name)) {
      return this.loadingPromises.get(name);
    }

    const library = this.sharedLibraries.get(name);
    if (!library || library.loaded) {
      return library?.module;
    }

    const loadingPromise = this._loadLibrary(library);
    this.loadingPromises.set(name, loadingPromise);

    try {
      const module = await loadingPromise;
      library.loaded = true;
      library.module = module;
      this.loadingPromises.delete(name);
      return module;
    } catch (error) {
      this.loadingPromises.delete(name);
      throw error;
    }
  }

  // 获取共享库
  async getSharedLibrary(name, requiredVersion) {
    const library = this.sharedLibraries.get(name);
    
    if (!library) {
      throw new Error(`Shared library ${name} not found`);
    }

    // 版本兼容性检查
    if (requiredVersion && !this._isVersionCompatible(library.version, requiredVersion)) {
      console.warn(`Version mismatch for ${name}: required ${requiredVersion}, available ${library.version}`);
    }

    // 单例检查
    if (library.singleton && library.loaded) {
      return library.module;
    }

    return this.preloadLibrary(name);
  }

  // 分析依赖关系
  analyzeDependencies(appName, dependencies) {
    this.dependencyGraph.set(appName, dependencies);
    
    // 检测循环依赖
    const visited = new Set();
    const recursionStack = new Set();
    
    const hasCycle = (node) => {
      if (recursionStack.has(node)) return true;
      if (visited.has(node)) return false;

      visited.add(node);
      recursionStack.add(node);

      const deps = this.dependencyGraph.get(node) || [];
      for (const dep of deps) {
        if (hasCycle(dep)) return true;
      }

      recursionStack.delete(node);
      return false;
    };

    if (hasCycle(appName)) {
      console.warn(`Circular dependency detected for application: ${appName}`);
    }
  }

  // 预加载应用依赖
  async preloadDependencies(appName) {
    const dependencies = this.dependencyGraph.get(appName) || [];
    const loadPromises = dependencies.map(dep => this.preloadLibrary(dep));
    
    try {
      await Promise.allSettled(loadPromises);
    } catch (error) {
      console.error(`Failed to preload dependencies for ${appName}:`, error);
    }
  }

  _loadLibrary(library) {
    if (library.url.startsWith('http')) {
      // 远程模块
      return import(library.url);
    } else {
      // 本地模块
      return import(library.url);
    }
  }

  _isVersionCompatible(available, required) {
    // 简化的语义版本检查
    const parseVersion = (version) => version.replace(/[^\d.]/g, '').split('.').map(Number);
    const availableVersion = parseVersion(available);
    const requiredVersion = parseVersion(required);

    // 主版本必须匹配
    return availableVersion[0] === requiredVersion[0] && 
           availableVersion[1] >= requiredVersion[1];
  }
}

// 缓存优化
class MicrofrontendCache {
  constructor() {
    this.moduleCache = new Map();
    this.resourceCache = new Map();
    this.cacheStats = {
      hits: 0,
      misses: 0,
      evictions: 0
    };
    this.maxCacheSize = 50;
  }

  // 缓存模块
  cacheModule(url, module, metadata = {}) {
    if (this.moduleCache.size >= this.maxCacheSize) {
      this._evictOldestModule();
    }

    this.moduleCache.set(url, {
      module,
      timestamp: Date.now(),
      accessCount: 0,
      size: this._estimateSize(module),
      ...metadata
    });
  }

  // 获取缓存的模块
  getCachedModule(url) {
    const cached = this.moduleCache.get(url);
    
    if (cached) {
      cached.accessCount++;
      cached.lastAccessed = Date.now();
      this.cacheStats.hits++;
      return cached.module;
    }

    this.cacheStats.misses++;
    return null;
  }

  // 预取资源
  async prefetchResource(url, priority = 'low') {
    if (this.resourceCache.has(url)) {
      return this.resourceCache.get(url);
    }

    const link = document.createElement('link');
    link.rel = 'prefetch';
    link.href = url;
    link.fetchPriority = priority;
    
    document.head.appendChild(link);

    // 创建预取Promise
    const prefetchPromise = new Promise((resolve, reject) => {
      link.onload = () => resolve(url);
      link.onerror = () => reject(new Error(`Failed to prefetch ${url}`));
    });

    this.resourceCache.set(url, prefetchPromise);
    return prefetchPromise;
  }

  _evictOldestModule() {
    let oldestUrl = null;
    let oldestTime = Date.now();

    for (const [url, cached] of this.moduleCache) {
      if (cached.lastAccessed < oldestTime) {
        oldestTime = cached.lastAccessed;
        oldestUrl = url;
      }
    }

    if (oldestUrl) {
      this.moduleCache.delete(oldestUrl);
      this.cacheStats.evictions++;
    }
  }

  _estimateSize(obj) {
    return JSON.stringify(obj).length;
  }

  getStats() {
    return {
      ...this.cacheStats,
      cacheSize: this.moduleCache.size,
      hitRate: this.cacheStats.hits / (this.cacheStats.hits + this.cacheStats.misses)
    };
  }
}

// 全局实例
const sharedResourceManager = new SharedResourceManager();
const microfrontendCache = new MicrofrontendCache();

// 使用示例
// 注册共享库
sharedResourceManager.registerSharedLibrary('react', {
  version: '18.2.0',
  url: 'https://unpkg.com/react@18/umd/react.production.min.js',
  singleton: true,
  eager: true
});

sharedResourceManager.registerSharedLibrary('lodash', {
  version: '4.17.21',
  url: 'https://unpkg.com/lodash@4.17.21/lodash.min.js',
  singleton: false
});

// 预加载应用依赖
sharedResourceManager.analyzeDependencies('user-app', ['react', 'lodash']);
await sharedResourceManager.preloadDependencies('user-app');

通过这些微前端模块化技术,可以构建高度可扩展、可维护的大型前端应用,实现团队自治和技术栈多样化的同时,保持良好的用户体验和系统性能。

Web Workers中的模块

Web Workers为JavaScript提供了多线程能力,使得我们可以在后台线程中执行计算密集型任务。本章将探讨如何在Web Workers中使用ES模块系统。

Web Workers基础

Worker类型与模块支持

// 主线程 - main.js
// 1. 经典Worker (不支持ES模块)
const classicWorker = new Worker('./classic-worker.js');

// 2. 模块Worker (支持ES模块)
const moduleWorker = new Worker('./module-worker.js', { 
  type: 'module' 
});

// 3. 内联Worker
const inlineWorkerScript = `
  import { heavyComputation } from './utils.js';
  
  self.onmessage = function(e) {
    const result = heavyComputation(e.data);
    self.postMessage(result);
  };
`;

const blob = new Blob([inlineWorkerScript], { type: 'application/javascript' });
const inlineWorker = new Worker(URL.createObjectURL(blob), { 
  type: 'module' 
});

// Worker能力检测
function supportsModuleWorkers() {
  try {
    new Worker('data:text/javascript,', { type: 'module' }).terminate();
    return true;
  } catch (e) {
    return false;
  }
}

if (supportsModuleWorkers()) {
  console.log('Browser supports module workers');
} else {
  console.log('Falling back to classic workers');
}

模块Worker示例

// module-worker.js - 支持ES模块的Worker
import { calculatePrimes } from './math-utils.js';
import { formatResult } from './formatters.js';
import { Logger } from './logger.js';

const logger = new Logger('MathWorker');

// Worker全局作用域中的self
self.onmessage = async function(event) {
  const { type, data, id } = event.data;
  
  try {
    let result;
    
    switch (type) {
      case 'CALCULATE_PRIMES':
        logger.info(`Calculating primes up to ${data.limit}`);
        result = await calculatePrimes(data.limit);
        break;
        
      case 'PROCESS_ARRAY':
        logger.info(`Processing array of ${data.array.length} items`);
        result = await processLargeArray(data.array, data.operation);
        break;
        
      default:
        throw new Error(`Unknown task type: ${type}`);
    }
    
    // 发送格式化的结果
    self.postMessage({
      id,
      type: 'SUCCESS',
      data: formatResult(result),
      timestamp: Date.now()
    });
    
  } catch (error) {
    logger.error('Worker task failed:', error);
    
    self.postMessage({
      id,
      type: 'ERROR',
      error: {
        message: error.message,
        stack: error.stack
      },
      timestamp: Date.now()
    });
  }
};

// 动态导入模块
async function processLargeArray(array, operation) {
  // 根据操作类型动态加载处理模块
  const { default: processor } = await import(`./processors/${operation}.js`);
  return processor(array);
}

// 错误处理
self.onerror = function(error) {
  logger.error('Uncaught error in worker:', error);
};

self.onunhandledrejection = function(event) {
  logger.error('Unhandled promise rejection in worker:', event.reason);
};

logger.info('Math worker initialized');

Worker模块管理器

模块化Worker管理系统

// worker-manager.js - Worker模块管理器
class WorkerManager {
  constructor() {
    this.workers = new Map();
    this.taskQueue = new Map();
    this.taskId = 0;
    this.workerPool = new Map();
    this.maxWorkers = navigator.hardwareConcurrency || 4;
  }

  // 注册Worker类型
  registerWorker(name, config) {
    this.workers.set(name, {
      name,
      script: config.script,
      type: config.type || 'module',
      maxInstances: config.maxInstances || 1,
      pool: [],
      activeCount: 0,
      options: config.options || {}
    });
  }

  // 执行任务
  async executeTask(workerName, taskType, data, options = {}) {
    const taskId = ++this.taskId;
    const { timeout = 30000, priority = 'normal' } = options;
    
    return new Promise((resolve, reject) => {
      const task = {
        id: taskId,
        workerName,
        taskType,
        data,
        resolve,
        reject,
        priority,
        timestamp: Date.now(),
        timeout: setTimeout(() => {
          this._handleTaskTimeout(taskId);
        }, timeout)
      };

      this.taskQueue.set(taskId, task);
      this._processTaskQueue();
    });
  }

  // 获取或创建Worker实例
  async _getWorkerInstance(workerName) {
    const workerConfig = this.workers.get(workerName);
    if (!workerConfig) {
      throw new Error(`Worker ${workerName} not registered`);
    }

    // 尝试从池中获取空闲的Worker
    const availableWorker = workerConfig.pool.find(w => !w.busy);
    if (availableWorker) {
      return availableWorker;
    }

    // 如果池未满,创建新的Worker
    if (workerConfig.pool.length < workerConfig.maxInstances) {
      const worker = await this._createWorkerInstance(workerConfig);
      workerConfig.pool.push(worker);
      return worker;
    }

    // 等待Worker变为可用
    return this._waitForAvailableWorker(workerName);
  }

  async _createWorkerInstance(config) {
    const worker = new Worker(config.script, {
      type: config.type,
      ...config.options
    });

    const workerInstance = {
      worker,
      busy: false,
      tasks: new Set(),
      created: Date.now(),
      taskCount: 0
    };

    // 设置消息处理
    worker.onmessage = (event) => {
      this._handleWorkerMessage(workerInstance, event);
    };

    worker.onerror = (error) => {
      this._handleWorkerError(workerInstance, error);
    };

    // 等待Worker初始化完成
    await this._waitForWorkerReady(worker);

    return workerInstance;
  }

  _handleWorkerMessage(workerInstance, event) {
    const { id, type, data, error } = event.data;
    const task = this.taskQueue.get(id);
    
    if (!task) {
      console.warn(`Received message for unknown task ${id}`);
      return;
    }

    clearTimeout(task.timeout);
    this.taskQueue.delete(id);
    workerInstance.tasks.delete(id);

    if (type === 'SUCCESS') {
      task.resolve(data);
    } else if (type === 'ERROR') {
      task.reject(new Error(error.message));
    }

    // 标记Worker为空闲
    if (workerInstance.tasks.size === 0) {
      workerInstance.busy = false;
      this._processTaskQueue();
    }
  }

  _handleWorkerError(workerInstance, error) {
    console.error('Worker error:', error);
    
    // 拒绝所有待处理的任务
    for (const taskId of workerInstance.tasks) {
      const task = this.taskQueue.get(taskId);
      if (task) {
        clearTimeout(task.timeout);
        task.reject(new Error(`Worker error: ${error.message}`));
        this.taskQueue.delete(taskId);
      }
    }

    // 从池中移除损坏的Worker
    this._removeWorkerFromPool(workerInstance);
  }

  _processTaskQueue() {
    const pendingTasks = Array.from(this.taskQueue.values())
      .filter(task => !task.assigned)
      .sort((a, b) => {
        // 按优先级和时间戳排序
        const priorityOrder = { high: 3, normal: 2, low: 1 };
        const aPriority = priorityOrder[a.priority] || 2;
        const bPriority = priorityOrder[b.priority] || 2;
        
        if (aPriority !== bPriority) {
          return bPriority - aPriority;
        }
        
        return a.timestamp - b.timestamp;
      });

    for (const task of pendingTasks) {
      this._assignTaskToWorker(task);
    }
  }

  async _assignTaskToWorker(task) {
    try {
      const workerInstance = await this._getWorkerInstance(task.workerName);
      
      workerInstance.busy = true;
      workerInstance.tasks.add(task.id);
      workerInstance.taskCount++;
      task.assigned = true;

      // 发送任务到Worker
      workerInstance.worker.postMessage({
        id: task.id,
        type: task.taskType,
        data: task.data
      });

    } catch (error) {
      clearTimeout(task.timeout);
      task.reject(error);
      this.taskQueue.delete(task.id);
    }
  }

  _handleTaskTimeout(taskId) {
    const task = this.taskQueue.get(taskId);
    if (task) {
      task.reject(new Error('Task timeout'));
      this.taskQueue.delete(taskId);
    }
  }

  async _waitForWorkerReady(worker) {
    return new Promise((resolve) => {
      const checkReady = () => {
        worker.postMessage({ type: 'PING' });
        
        const handlePong = (event) => {
          if (event.data.type === 'PONG') {
            worker.removeEventListener('message', handlePong);
            resolve();
          }
        };
        
        worker.addEventListener('message', handlePong);
      };
      
      checkReady();
    });
  }

  // 终止所有Worker
  terminateAllWorkers() {
    for (const workerConfig of this.workers.values()) {
      for (const workerInstance of workerConfig.pool) {
        workerInstance.worker.terminate();
      }
      workerConfig.pool.length = 0;
    }
  }

  // 获取统计信息
  getStats() {
    const stats = {
      totalWorkers: 0,
      busyWorkers: 0,
      pendingTasks: 0,
      workerTypes: {}
    };

    for (const [name, config] of this.workers) {
      const busyCount = config.pool.filter(w => w.busy).length;
      stats.totalWorkers += config.pool.length;
      stats.busyWorkers += busyCount;
      
      stats.workerTypes[name] = {
        total: config.pool.length,
        busy: busyCount,
        maxInstances: config.maxInstances
      };
    }

    stats.pendingTasks = this.taskQueue.size;
    return stats;
  }
}

// 使用示例
const workerManager = new WorkerManager();

// 注册不同类型的Worker
workerManager.registerWorker('math', {
  script: './workers/math-worker.js',
  type: 'module',
  maxInstances: 2
});

workerManager.registerWorker('image', {
  script: './workers/image-worker.js',
  type: 'module',
  maxInstances: 1
});

// 执行任务
async function calculatePrimes(limit) {
  try {
    const result = await workerManager.executeTask('math', 'CALCULATE_PRIMES', 
      { limit }, 
      { timeout: 10000, priority: 'high' }
    );
    console.log('Primes calculated:', result);
  } catch (error) {
    console.error('Failed to calculate primes:', error);
  }
}

模块共享与通信

Worker间模块共享

// shared-module-manager.js - 跨Worker模块共享
class SharedModuleManager {
  constructor() {
    this.sharedModules = new Map();
    this.moduleCache = new Map();
    this.workers = new Set();
  }

  // 注册可共享的模块
  registerSharedModule(name, moduleUrl, exports = []) {
    this.sharedModules.set(name, {
      name,
      url: moduleUrl,
      exports,
      cached: false,
      module: null
    });
  }

  // 注册Worker实例
  registerWorker(worker, id) {
    this.workers.add({ worker, id });
  }

  // 预加载共享模块
  async preloadSharedModules() {
    const loadPromises = Array.from(this.sharedModules.keys())
      .map(name => this.loadSharedModule(name));
    
    await Promise.allSettled(loadPromises);
  }

  // 加载共享模块
  async loadSharedModule(name) {
    const moduleConfig = this.sharedModules.get(name);
    if (!moduleConfig) {
      throw new Error(`Shared module ${name} not found`);
    }

    if (moduleConfig.cached) {
      return moduleConfig.module;
    }

    try {
      // 在主线程中加载模块
      const module = await import(moduleConfig.url);
      
      // 缓存模块
      moduleConfig.module = module;
      moduleConfig.cached = true;
      
      // 通知所有Worker模块已可用
      this._broadcastModuleAvailable(name, moduleConfig);
      
      return module;
    } catch (error) {
      console.error(`Failed to load shared module ${name}:`, error);
      throw error;
    }
  }

  // 广播模块可用性
  _broadcastModuleAvailable(name, moduleConfig) {
    const message = {
      type: 'SHARED_MODULE_AVAILABLE',
      moduleName: name,
      moduleUrl: moduleConfig.url,
      exports: moduleConfig.exports
    };

    for (const workerInfo of this.workers) {
      try {
        workerInfo.worker.postMessage(message);
      } catch (error) {
        console.warn(`Failed to notify worker ${workerInfo.id}:`, error);
      }
    }
  }

  // 模块间通信代理
  createCommunicationBridge() {
    return {
      // Worker到Worker通信
      sendToWorker: (targetWorkerId, message) => {
        const targetWorker = Array.from(this.workers)
          .find(w => w.id === targetWorkerId);
        
        if (targetWorker) {
          targetWorker.worker.postMessage({
            type: 'WORKER_MESSAGE',
            from: 'main',
            data: message
          });
        } else {
          console.warn(`Worker ${targetWorkerId} not found`);
        }
      },

      // 广播消息到所有Worker
      broadcast: (message) => {
        for (const workerInfo of this.workers) {
          workerInfo.worker.postMessage({
            type: 'BROADCAST',
            from: 'main',
            data: message
          });
        }
      }
    };
  }
}

// Worker端的共享模块处理
// shared-worker-utils.js
class WorkerSharedModuleHandler {
  constructor() {
    this.availableModules = new Map();
    this.loadedModules = new Map();
  }

  // 处理主线程消息
  handleMainThreadMessage(event) {
    const { type, moduleName, moduleUrl, exports } = event.data;
    
    switch (type) {
      case 'SHARED_MODULE_AVAILABLE':
        this.availableModules.set(moduleName, { moduleUrl, exports });
        break;
        
      case 'WORKER_MESSAGE':
        this._handleWorkerMessage(event.data);
        break;
        
      case 'BROADCAST':
        this._handleBroadcast(event.data);
        break;
    }
  }

  // 动态加载共享模块
  async loadSharedModule(name) {
    if (this.loadedModules.has(name)) {
      return this.loadedModules.get(name);
    }

    const moduleInfo = this.availableModules.get(name);
    if (!moduleInfo) {
      throw new Error(`Shared module ${name} not available`);
    }

    try {
      const module = await import(moduleInfo.moduleUrl);
      this.loadedModules.set(name, module);
      return module;
    } catch (error) {
      console.error(`Failed to load shared module ${name} in worker:`, error);
      throw error;
    }
  }

  _handleWorkerMessage(messageData) {
    // 处理来自其他Worker的消息
    console.log('Received message from other worker:', messageData);
  }

  _handleBroadcast(messageData) {
    // 处理广播消息
    console.log('Received broadcast:', messageData);
  }
}

// 在Worker中使用
// worker-with-shared-modules.js
import { WorkerSharedModuleHandler } from './shared-worker-utils.js';

const sharedHandler = new WorkerSharedModuleHandler();

self.onmessage = async function(event) {
  const { type, data } = event.data;
  
  // 处理共享模块相关消息
  if (['SHARED_MODULE_AVAILABLE', 'WORKER_MESSAGE', 'BROADCAST'].includes(type)) {
    sharedHandler.handleMainThreadMessage(event);
    return;
  }

  // 处理业务逻辑
  try {
    let result;
    
    switch (type) {
      case 'PROCESS_WITH_SHARED_UTILS':
        // 动态加载共享工具模块
        const utils = await sharedHandler.loadSharedModule('utils');
        result = utils.processData(data);
        break;
        
      case 'CALCULATE_WITH_MATH_LIB':
        // 使用共享数学库
        const mathLib = await sharedHandler.loadSharedModule('mathLib');
        result = mathLib.complexCalculation(data);
        break;
        
      default:
        throw new Error(`Unknown task type: ${type}`);
    }
    
    self.postMessage({
      id: event.data.id,
      type: 'SUCCESS',
      data: result
    });
    
  } catch (error) {
    self.postMessage({
      id: event.data.id,
      type: 'ERROR',
      error: { message: error.message }
    });
  }
};

高级Worker模式

Worker池管理

// worker-pool.js - 高级Worker池管理
class AdvancedWorkerPool {
  constructor(options = {}) {
    this.maxWorkers = options.maxWorkers || navigator.hardwareConcurrency || 4;
    this.minWorkers = options.minWorkers || 1;
    this.idleTimeout = options.idleTimeout || 30000;
    this.maxTasksPerWorker = options.maxTasksPerWorker || 100;
    
    this.workerScript = options.workerScript;
    this.workerType = options.workerType || 'module';
    
    this.workers = [];
    this.taskQueue = [];
    this.runningTasks = new Map();
    this.workerStats = new Map();
    
    this.setupPool();
  }

  async setupPool() {
    // 创建最小数量的Worker
    for (let i = 0; i < this.minWorkers; i++) {
      await this.createWorker();
    }
  }

  async createWorker() {
    const worker = new Worker(this.workerScript, {
      type: this.workerType
    });

    const workerInfo = {
      id: Math.random().toString(36).substr(2, 9),
      worker,
      busy: false,
      taskCount: 0,
      totalTasks: 0,
      errors: 0,
      created: Date.now(),
      lastUsed: Date.now(),
      idleTimer: null
    };

    // 设置消息处理
    worker.onmessage = (event) => {
      this.handleWorkerMessage(workerInfo, event);
    };

    worker.onerror = (error) => {
      this.handleWorkerError(workerInfo, error);
    };

    this.workers.push(workerInfo);
    this.workerStats.set(workerInfo.id, {
      tasksCompleted: 0,
      averageTaskTime: 0,
      errorRate: 0
    });

    return workerInfo;
  }

  async executeTask(taskData, options = {}) {
    return new Promise((resolve, reject) => {
      const task = {
        id: Math.random().toString(36).substr(2, 9),
        data: taskData,
        resolve,
        reject,
        created: Date.now(),
        timeout: options.timeout || 30000,
        priority: options.priority || 'normal'
      };

      this.taskQueue.push(task);
      this.assignTasks();
    });
  }

  async assignTasks() {
    // 按优先级排序任务
    this.taskQueue.sort((a, b) => {
      const priorityOrder = { high: 3, normal: 2, low: 1 };
      return (priorityOrder[b.priority] || 2) - (priorityOrder[a.priority] || 2);
    });

    while (this.taskQueue.length > 0) {
      const availableWorker = this.getAvailableWorker();
      
      if (!availableWorker) {
        // 尝试创建新Worker
        if (this.workers.length < this.maxWorkers) {
          await this.createWorker();
          continue;
        } else {
          break; // 没有可用Worker,等待
        }
      }

      const task = this.taskQueue.shift();
      this.assignTaskToWorker(availableWorker, task);
    }
  }

  getAvailableWorker() {
    // 找到最适合的Worker
    const availableWorkers = this.workers.filter(w => !w.busy);
    
    if (availableWorkers.length === 0) {
      return null;
    }

    // 选择任务数最少的Worker
    return availableWorkers.reduce((best, current) => {
      return current.totalTasks < best.totalTasks ? current : best;
    });
  }

  assignTaskToWorker(workerInfo, task) {
    workerInfo.busy = true;
    workerInfo.taskCount++;
    workerInfo.totalTasks++;
    workerInfo.lastUsed = Date.now();

    // 清除空闲定时器
    if (workerInfo.idleTimer) {
      clearTimeout(workerInfo.idleTimer);
      workerInfo.idleTimer = null;
    }

    // 设置任务超时
    const timeoutId = setTimeout(() => {
      this.handleTaskTimeout(task.id);
    }, task.timeout);

    this.runningTasks.set(task.id, {
      task,
      worker: workerInfo,
      startTime: Date.now(),
      timeoutId
    });

    // 发送任务到Worker
    workerInfo.worker.postMessage({
      id: task.id,
      data: task.data
    });
  }

  handleWorkerMessage(workerInfo, event) {
    const { id, success, result, error } = event.data;
    const taskInfo = this.runningTasks.get(id);
    
    if (!taskInfo) {
      console.warn(`Received result for unknown task ${id}`);
      return;
    }

    const { task, timeoutId } = taskInfo;
    const duration = Date.now() - taskInfo.startTime;

    // 清除超时定时器
    clearTimeout(timeoutId);
    this.runningTasks.delete(id);

    // 更新Worker状态
    workerInfo.busy = false;
    workerInfo.taskCount--;
    workerInfo.lastUsed = Date.now();

    // 更新统计信息
    this.updateWorkerStats(workerInfo.id, duration, success);

    // 处理任务结果
    if (success) {
      task.resolve(result);
    } else {
      task.reject(new Error(error));
      workerInfo.errors++;
    }

    // 检查Worker是否需要回收
    this.checkWorkerRecycling(workerInfo);

    // 设置空闲定时器
    this.setIdleTimer(workerInfo);

    // 继续分配任务
    this.assignTasks();
  }

  handleWorkerError(workerInfo, error) {
    console.error(`Worker ${workerInfo.id} error:`, error);
    
    // 标记所有运行在该Worker上的任务为失败
    for (const [taskId, taskInfo] of this.runningTasks) {
      if (taskInfo.worker === workerInfo) {
        clearTimeout(taskInfo.timeoutId);
        taskInfo.task.reject(new Error(`Worker error: ${error.message}`));
        this.runningTasks.delete(taskId);
      }
    }

    // 从池中移除错误的Worker
    this.removeWorker(workerInfo);
  }

  handleTaskTimeout(taskId) {
    const taskInfo = this.runningTasks.get(taskId);
    if (taskInfo) {
      taskInfo.task.reject(new Error('Task timeout'));
      this.runningTasks.delete(taskId);
      
      // 释放Worker
      taskInfo.worker.busy = false;
      taskInfo.worker.taskCount--;
    }
  }

  updateWorkerStats(workerId, duration, success) {
    const stats = this.workerStats.get(workerId);
    if (stats) {
      stats.tasksCompleted++;
      stats.averageTaskTime = (stats.averageTaskTime + duration) / 2;
      
      if (!success) {
        stats.errorRate = (stats.errorRate * 0.9) + 0.1; // 指数移动平均
      } else {
        stats.errorRate = stats.errorRate * 0.95;
      }
    }
  }

  checkWorkerRecycling(workerInfo) {
    // 如果Worker执行的任务数过多,回收它
    if (workerInfo.totalTasks >= this.maxTasksPerWorker) {
      this.removeWorker(workerInfo);
      
      // 如果池中Worker数量少于最小值,创建新的
      if (this.workers.length < this.minWorkers) {
        this.createWorker();
      }
    }
  }

  setIdleTimer(workerInfo) {
    if (this.workers.length > this.minWorkers) {
      workerInfo.idleTimer = setTimeout(() => {
        if (!workerInfo.busy) {
          this.removeWorker(workerInfo);
        }
      }, this.idleTimeout);
    }
  }

  removeWorker(workerInfo) {
    const index = this.workers.indexOf(workerInfo);
    if (index > -1) {
      this.workers.splice(index, 1);
      this.workerStats.delete(workerInfo.id);
      
      if (workerInfo.idleTimer) {
        clearTimeout(workerInfo.idleTimer);
      }
      
      workerInfo.worker.terminate();
    }
  }

  // 获取池状态
  getPoolStats() {
    return {
      totalWorkers: this.workers.length,
      busyWorkers: this.workers.filter(w => w.busy).length,
      queuedTasks: this.taskQueue.length,
      runningTasks: this.runningTasks.size,
      workerStats: Object.fromEntries(this.workerStats)
    };
  }

  // 优雅关闭
  async shutdown() {
    // 等待所有运行中的任务完成
    while (this.runningTasks.size > 0) {
      await new Promise(resolve => setTimeout(resolve, 100));
    }

    // 终止所有Worker
    for (const workerInfo of this.workers) {
      if (workerInfo.idleTimer) {
        clearTimeout(workerInfo.idleTimer);
      }
      workerInfo.worker.terminate();
    }

    this.workers.length = 0;
    this.workerStats.clear();
  }
}

// 使用示例
const workerPool = new AdvancedWorkerPool({
  workerScript: './advanced-worker.js',
  workerType: 'module',
  maxWorkers: 8,
  minWorkers: 2,
  idleTimeout: 30000,
  maxTasksPerWorker: 50
});

// 执行计算密集型任务
async function runHeavyComputations() {
  const tasks = Array.from({ length: 20 }, (_, i) => ({
    operation: 'fibonacci',
    input: 40 + i,
    id: i
  }));

  try {
    const results = await Promise.all(
      tasks.map(task => 
        workerPool.executeTask(task, { 
          timeout: 10000, 
          priority: task.id < 5 ? 'high' : 'normal' 
        })
      )
    );

    console.log('All computations completed:', results);
    console.log('Pool stats:', workerPool.getPoolStats());
  } catch (error) {
    console.error('Computation failed:', error);
  }
}

runHeavyComputations();

Service Worker与模块

// service-worker.js - Service Worker中的模块使用
import { CacheManager } from './cache-manager.js';
import { NotificationManager } from './notification-manager.js';
import { SyncManager } from './sync-manager.js';

const cacheManager = new CacheManager();
const notificationManager = new NotificationManager();
const syncManager = new SyncManager();

// Service Worker安装
self.addEventListener('install', async (event) => {
  console.log('Service Worker installing...');
  
  event.waitUntil(
    cacheManager.precacheAssets([
      '/',
      '/app.js',
      '/styles.css',
      '/offline.html'
    ])
  );
});

// Service Worker激活
self.addEventListener('activate', async (event) => {
  console.log('Service Worker activating...');
  
  event.waitUntil(
    Promise.all([
      cacheManager.cleanupOldCaches(),
      self.clients.claim()
    ])
  );
});

// 网络请求拦截
self.addEventListener('fetch', (event) => {
  event.respondWith(handleFetch(event.request));
});

// 后台同步
self.addEventListener('sync', (event) => {
  if (event.tag === 'background-sync') {
    event.waitUntil(syncManager.handleBackgroundSync());
  }
});

// 推送通知
self.addEventListener('push', (event) => {
  const data = event.data ? event.data.json() : {};
  event.waitUntil(notificationManager.showNotification(data));
});

async function handleFetch(request) {
  // 动态导入处理模块
  const { default: fetchHandler } = await import('./fetch-handlers.js');
  return fetchHandler.handle(request, cacheManager);
}

// cache-manager.js - 缓存管理模块
export class CacheManager {
  constructor() {
    this.cacheName = 'app-cache-v1';
    this.dynamicCacheName = 'dynamic-cache-v1';
  }

  async precacheAssets(urls) {
    const cache = await caches.open(this.cacheName);
    return cache.addAll(urls);
  }

  async getCachedResponse(request) {
    const cache = await caches.open(this.cacheName);
    const response = await cache.match(request);
    
    if (response) {
      return response;
    }

    // 尝试动态缓存
    const dynamicCache = await caches.open(this.dynamicCacheName);
    return dynamicCache.match(request);
  }

  async cacheResponse(request, response) {
    const cache = await caches.open(this.dynamicCacheName);
    return cache.put(request, response.clone());
  }

  async cleanupOldCaches() {
    const cacheNames = await caches.keys();
    const oldCaches = cacheNames.filter(name => 
      name !== this.cacheName && name !== this.dynamicCacheName
    );

    return Promise.all(
      oldCaches.map(name => caches.delete(name))
    );
  }
}

通过这些技术,可以充分利用Web Workers的多线程能力,同时享受ES模块系统带来的代码组织和复用优势,构建高性能的Web应用。

WASM模块集成

WebAssembly (WASM) 是一种二进制指令格式,可以在现代浏览器中以接近原生的速度运行。本章将探讨如何将WASM模块与JavaScript模块系统集成。

WASM模块基础

WASM模块加载

// wasm-loader.js - WASM模块加载器
class WasmModuleLoader {
  constructor() {
    this.moduleCache = new Map();
    this.loadingPromises = new Map();
  }

  // 基础WASM模块加载
  async loadWasmModule(url, importObject = {}) {
    if (this.moduleCache.has(url)) {
      return this.moduleCache.get(url);
    }

    if (this.loadingPromises.has(url)) {
      return this.loadingPromises.get(url);
    }

    const loadingPromise = this._loadModule(url, importObject);
    this.loadingPromises.set(url, loadingPromise);

    try {
      const module = await loadingPromise;
      this.moduleCache.set(url, module);
      this.loadingPromises.delete(url);
      return module;
    } catch (error) {
      this.loadingPromises.delete(url);
      throw error;
    }
  }

  async _loadModule(url, importObject) {
    // 获取WASM字节码
    const response = await fetch(url);
    const bytes = await response.arrayBuffer();

    // 编译和实例化WASM模块
    const wasmModule = await WebAssembly.compile(bytes);
    const wasmInstance = await WebAssembly.instantiate(wasmModule, importObject);

    return {
      module: wasmModule,
      instance: wasmInstance,
      exports: wasmInstance.exports
    };
  }

  // 流式加载(适用于大型WASM模块)
  async loadWasmStreamingly(url, importObject = {}) {
    const response = await fetch(url);
    
    if (!WebAssembly.compileStreaming) {
      // 降级到普通加载
      return this.loadWasmModule(url, importObject);
    }

    try {
      const wasmModule = await WebAssembly.compileStreaming(response.clone());
      const wasmInstance = await WebAssembly.instantiate(wasmModule, importObject);

      return {
        module: wasmModule,
        instance: wasmInstance,
        exports: wasmInstance.exports
      };
    } catch (error) {
      console.warn('Streaming compilation failed, falling back to regular loading:', error);
      return this.loadWasmModule(url, importObject);
    }
  }

  // 预编译WASM模块
  async precompileWasm(url) {
    try {
      const response = await fetch(url);
      const wasmModule = await WebAssembly.compileStreaming(response);
      
      // 将预编译的模块存储在缓存中
      this.moduleCache.set(url, { module: wasmModule, precompiled: true });
      return wasmModule;
    } catch (error) {
      console.error(`Failed to precompile WASM module ${url}:`, error);
      throw error;
    }
  }

  // 从预编译模块创建实例
  async instantiatePrecompiled(url, importObject = {}) {
    const cached = this.moduleCache.get(url);
    
    if (!cached || !cached.precompiled) {
      throw new Error(`No precompiled module found for ${url}`);
    }

    const wasmInstance = await WebAssembly.instantiate(cached.module, importObject);
    
    return {
      module: cached.module,
      instance: wasmInstance,
      exports: wasmInstance.exports
    };
  }
}

// 全局WASM加载器实例
const wasmLoader = new WasmModuleLoader();

// 使用示例
async function loadMathWasm() {
  try {
    // 定义导入对象
    const importObject = {
      env: {
        console_log: (value) => console.log('WASM Log:', value),
        Math_pow: Math.pow,
        Date_now: Date.now
      }
    };

    const wasmModule = await wasmLoader.loadWasmStreamingly('./math.wasm', importObject);
    
    // 使用WASM导出的函数
    const result = wasmModule.exports.fibonacci(40);
    console.log('Fibonacci result:', result);
    
    return wasmModule;
  } catch (error) {
    console.error('Failed to load math WASM module:', error);
  }
}

ES模块与WASM集成

// math-wasm.js - WASM模块的ES模块封装
import wasmUrl from './math.wasm?url'; // Vite/Webpack的WASM导入

class MathWasm {
  constructor() {
    this.wasmModule = null;
    this.loaded = false;
  }

  async initialize() {
    if (this.loaded) return;

    try {
      const importObject = {
        env: {
          memory: new WebAssembly.Memory({ initial: 256 }),
          console_log: this._consoleLog.bind(this),
          abort: this._abort.bind(this)
        }
      };

      // 使用ES模块导入WASM
      const wasmModule = await import('./math.wasm');
      
      // 或者使用fetch加载
      // const response = await fetch(wasmUrl);
      // const wasmModule = await WebAssembly.instantiateStreaming(response, importObject);

      this.wasmModule = wasmModule;
      this.loaded = true;

      console.log('Math WASM module loaded successfully');
    } catch (error) {
      console.error('Failed to initialize Math WASM:', error);
      throw error;
    }
  }

  // 包装WASM函数为JS方法
  fibonacci(n) {
    this._ensureLoaded();
    return this.wasmModule.exports.fibonacci(n);
  }

  factorial(n) {
    this._ensureLoaded();
    return this.wasmModule.exports.factorial(n);
  }

  isPrime(n) {
    this._ensureLoaded();
    return Boolean(this.wasmModule.exports.is_prime(n));
  }

  // 处理复杂数据类型
  processArray(arr) {
    this._ensureLoaded();
    
    const memory = this.wasmModule.exports.memory;
    const malloc = this.wasmModule.exports.malloc;
    const free = this.wasmModule.exports.free;
    const processArray = this.wasmModule.exports.process_array;

    // 分配内存
    const inputSize = arr.length * 4; // 假设是32位整数
    const inputPtr = malloc(inputSize);
    
    try {
      // 复制数据到WASM内存
      const inputView = new Int32Array(memory.buffer, inputPtr, arr.length);
      inputView.set(arr);

      // 调用WASM函数
      const resultPtr = processArray(inputPtr, arr.length);
      
      // 读取结果
      const resultView = new Int32Array(memory.buffer, resultPtr, arr.length);
      const result = Array.from(resultView);

      // 释放内存
      free(inputPtr);
      free(resultPtr);

      return result;
    } catch (error) {
      free(inputPtr);
      throw error;
    }
  }

  _ensureLoaded() {
    if (!this.loaded) {
      throw new Error('WASM module not loaded. Call initialize() first.');
    }
  }

  _consoleLog(value) {
    console.log('WASM:', value);
  }

  _abort() {
    throw new Error('WASM execution aborted');
  }
}

// 导出单例实例
export const mathWasm = new MathWasm();

// 也可以导出类供多实例使用
export { MathWasm };

// 默认导出初始化函数
export default async function initMathWasm() {
  await mathWasm.initialize();
  return mathWasm;
}

高级WASM集成模式

WASM模块工厂

// wasm-module-factory.js - WASM模块工厂
class WasmModuleFactory {
  constructor() {
    this.modules = new Map();
    this.templates = new Map();
  }

  // 注册WASM模块模板
  registerTemplate(name, config) {
    this.templates.set(name, {
      name,
      url: config.url,
      importObject: config.importObject || {},
      initFunction: config.initFunction,
      exportMappings: config.exportMappings || {},
      memoryConfig: config.memoryConfig
    });
  }

  // 创建WASM模块实例
  async createModule(templateName, instanceId = null) {
    const template = this.templates.get(templateName);
    if (!template) {
      throw new Error(`WASM template ${templateName} not found`);
    }

    const id = instanceId || `${templateName}_${Date.now()}`;
    
    if (this.modules.has(id)) {
      return this.modules.get(id);
    }

    try {
      const wasmInstance = await this._instantiateFromTemplate(template);
      const moduleWrapper = this._createModuleWrapper(wasmInstance, template);
      
      this.modules.set(id, moduleWrapper);
      return moduleWrapper;
    } catch (error) {
      console.error(`Failed to create WASM module ${templateName}:`, error);
      throw error;
    }
  }

  async _instantiateFromTemplate(template) {
    // 准备导入对象
    const importObject = this._prepareImportObject(template);
    
    // 加载和实例化WASM
    const response = await fetch(template.url);
    const wasmModule = await WebAssembly.instantiateStreaming(response, importObject);
    
    return wasmModule;
  }

  _prepareImportObject(template) {
    const importObject = { ...template.importObject };
    
    // 设置内存
    if (template.memoryConfig) {
      importObject.env = importObject.env || {};
      importObject.env.memory = new WebAssembly.Memory(template.memoryConfig);
    }

    // 添加标准环境函数
    importObject.env = importObject.env || {};
    Object.assign(importObject.env, {
      console_log: console.log,
      console_error: console.error,
      Date_now: Date.now,
      Math_random: Math.random,
      Math_floor: Math.floor,
      Math_ceil: Math.ceil,
      performance_now: performance.now.bind(performance)
    });

    return importObject;
  }

  _createModuleWrapper(wasmInstance, template) {
    const wrapper = {
      instance: wasmInstance.instance,
      exports: wasmInstance.instance.exports,
      memory: wasmInstance.instance.exports.memory,
      
      // 原始函数调用
      call: (funcName, ...args) => {
        const func = wasmInstance.instance.exports[funcName];
        if (!func) {
          throw new Error(`Function ${funcName} not found in WASM module`);
        }
        return func(...args);
      },

      // 内存操作辅助函数
      memory: {
        read: (ptr, length, type = 'uint8') => {
          const memory = wasmInstance.instance.exports.memory;
          const TypedArray = this._getTypedArray(type);
          return new TypedArray(memory.buffer, ptr, length);
        },

        write: (ptr, data, type = 'uint8') => {
          const memory = wasmInstance.instance.exports.memory;
          const TypedArray = this._getTypedArray(type);
          const view = new TypedArray(memory.buffer, ptr, data.length);
          view.set(data);
        },

        allocate: (size) => {
          const malloc = wasmInstance.instance.exports.malloc;
          if (!malloc) {
            throw new Error('malloc function not found in WASM module');
          }
          return malloc(size);
        },

        free: (ptr) => {
          const free = wasmInstance.instance.exports.free;
          if (!free) {
            throw new Error('free function not found in WASM module');
          }
          free(ptr);
        }
      }
    };

    // 添加映射的导出函数
    for (const [jsName, wasmName] of Object.entries(template.exportMappings)) {
      const wasmFunc = wasmInstance.instance.exports[wasmName];
      if (wasmFunc) {
        wrapper[jsName] = wasmFunc.bind(wasmInstance.instance.exports);
      }
    }

    // 调用初始化函数
    if (template.initFunction && typeof template.initFunction === 'function') {
      template.initFunction(wrapper);
    }

    return wrapper;
  }

  _getTypedArray(type) {
    const typeMap = {
      'int8': Int8Array,
      'uint8': Uint8Array,
      'int16': Int16Array,
      'uint16': Uint16Array,
      'int32': Int32Array,
      'uint32': Uint32Array,
      'float32': Float32Array,
      'float64': Float64Array
    };

    return typeMap[type] || Uint8Array;
  }

  // 销毁模块实例
  destroyModule(instanceId) {
    const module = this.modules.get(instanceId);
    if (module && module.memory && module.memory.free) {
      // 清理分配的内存
      // 这里需要根据具体的WASM模块实现来清理
    }
    
    this.modules.delete(instanceId);
  }

  // 获取模块统计信息
  getModuleStats() {
    return {
      templateCount: this.templates.size,
      instanceCount: this.modules.size,
      templates: Array.from(this.templates.keys()),
      instances: Array.from(this.modules.keys())
    };
  }
}

// 使用示例
const wasmFactory = new WasmModuleFactory();

// 注册图像处理WASM模块模板
wasmFactory.registerTemplate('imageProcessor', {
  url: './image-processor.wasm',
  memoryConfig: { initial: 1024 }, // 64MB
  exportMappings: {
    blur: 'image_blur',
    sharpen: 'image_sharpen',
    resize: 'image_resize'
  },
  initFunction: (wrapper) => {
    // 模块初始化逻辑
    console.log('Image processor WASM module initialized');
    
    // 添加高级方法
    wrapper.processImage = async (imageData, operations) => {
      const ptr = wrapper.memory.allocate(imageData.length);
      
      try {
        wrapper.memory.write(ptr, imageData, 'uint8');
        
        for (const op of operations) {
          switch (op.type) {
            case 'blur':
              wrapper.blur(ptr, imageData.length, op.radius);
              break;
            case 'sharpen':
              wrapper.sharpen(ptr, imageData.length, op.amount);
              break;
          }
        }
        
        return wrapper.memory.read(ptr, imageData.length, 'uint8');
      } finally {
        wrapper.memory.free(ptr);
      }
    };
  }
});

// 创建和使用模块实例
async function processImageWithWasm(imageData) {
  const processor = await wasmFactory.createModule('imageProcessor', 'main-processor');
  
  const result = await processor.processImage(imageData, [
    { type: 'blur', radius: 5 },
    { type: 'sharpen', amount: 0.8 }
  ]);
  
  return result;
}

Worker中的WASM

// wasm-worker.js - 在Worker中使用WASM
import { WasmModuleFactory } from './wasm-module-factory.js';

class WasmWorker {
  constructor() {
    this.wasmFactory = new WasmModuleFactory();
    this.modules = new Map();
    this.setupWorker();
  }

  setupWorker() {
    // 注册WASM模块模板
    this.wasmFactory.registerTemplate('mathProcessor', {
      url: './math-processor.wasm',
      memoryConfig: { initial: 256 },
      exportMappings: {
        matrixMultiply: 'matrix_multiply',
        fft: 'fast_fourier_transform',
        solve: 'linear_solve'
      }
    });

    // 设置消息处理
    self.onmessage = async (event) => {
      await this.handleMessage(event);
    };
  }

  async handleMessage(event) {
    const { id, type, data } = event.data;

    try {
      let result;

      switch (type) {
        case 'LOAD_WASM_MODULE':
          result = await this.loadWasmModule(data.templateName, data.instanceId);
          break;

        case 'WASM_CALL':
          result = await this.callWasmFunction(data.instanceId, data.funcName, data.args);
          break;

        case 'WASM_PROCESS_ARRAY':
          result = await this.processArrayWithWasm(data.instanceId, data.operation, data.array);
          break;

        case 'DESTROY_WASM_MODULE':
          result = await this.destroyWasmModule(data.instanceId);
          break;

        default:
          throw new Error(`Unknown message type: ${type}`);
      }

      self.postMessage({
        id,
        type: 'SUCCESS',
        data: result
      });

    } catch (error) {
      self.postMessage({
        id,
        type: 'ERROR',
        error: {
          message: error.message,
          stack: error.stack
        }
      });
    }
  }

  async loadWasmModule(templateName, instanceId) {
    const module = await this.wasmFactory.createModule(templateName, instanceId);
    this.modules.set(instanceId, module);
    return { loaded: true, instanceId };
  }

  async callWasmFunction(instanceId, funcName, args) {
    const module = this.modules.get(instanceId);
    if (!module) {
      throw new Error(`WASM module ${instanceId} not found`);
    }

    return module.call(funcName, ...args);
  }

  async processArrayWithWasm(instanceId, operation, array) {
    const module = this.modules.get(instanceId);
    if (!module) {
      throw new Error(`WASM module ${instanceId} not found`);
    }

    // 分配内存并复制数据
    const size = array.length * 4; // 假设32位数字
    const ptr = module.memory.allocate(size);

    try {
      module.memory.write(ptr, array, 'float32');

      // 调用WASM处理函数
      let resultPtr;
      switch (operation) {
        case 'fft':
          resultPtr = module.fft(ptr, array.length);
          break;
        case 'matrixMultiply':
          // 需要额外的矩阵维度参数
          resultPtr = module.matrixMultiply(ptr, Math.sqrt(array.length));
          break;
        default:
          throw new Error(`Unknown operation: ${operation}`);
      }

      // 读取结果
      const result = Array.from(module.memory.read(resultPtr, array.length, 'float32'));
      
      // 清理内存
      module.memory.free(resultPtr);
      
      return result;
    } finally {
      module.memory.free(ptr);
    }
  }

  async destroyWasmModule(instanceId) {
    this.wasmFactory.destroyModule(instanceId);
    this.modules.delete(instanceId);
    return { destroyed: true, instanceId };
  }
}

// 初始化Worker
const wasmWorker = new WasmWorker();

// 主线程使用示例
// wasm-worker-client.js
class WasmWorkerClient {
  constructor() {
    this.worker = new Worker('./wasm-worker.js', { type: 'module' });
    this.taskId = 0;
    this.pendingTasks = new Map();
    this.setupWorker();
  }

  setupWorker() {
    this.worker.onmessage = (event) => {
      const { id, type, data, error } = event.data;
      const task = this.pendingTasks.get(id);

      if (task) {
        this.pendingTasks.delete(id);
        
        if (type === 'SUCCESS') {
          task.resolve(data);
        } else {
          task.reject(new Error(error.message));
        }
      }
    };
  }

  async sendTask(type, data) {
    return new Promise((resolve, reject) => {
      const id = ++this.taskId;
      
      this.pendingTasks.set(id, { resolve, reject });
      
      this.worker.postMessage({ id, type, data });
      
      // 设置超时
      setTimeout(() => {
        if (this.pendingTasks.has(id)) {
          this.pendingTasks.delete(id);
          reject(new Error('Task timeout'));
        }
      }, 30000);
    });
  }

  async loadWasmModule(templateName, instanceId) {
    return this.sendTask('LOAD_WASM_MODULE', { templateName, instanceId });
  }

  async callWasmFunction(instanceId, funcName, args) {
    return this.sendTask('WASM_CALL', { instanceId, funcName, args });
  }

  async processArray(instanceId, operation, array) {
    return this.sendTask('WASM_PROCESS_ARRAY', { instanceId, operation, array });
  }

  terminate() {
    this.worker.terminate();
  }
}

// 使用示例
const wasmClient = new WasmWorkerClient();

async function performHeavyMathComputation() {
  try {
    // 加载WASM模块
    await wasmClient.loadWasmModule('mathProcessor', 'math-1');
    
    // 生成测试数据
    const largeArray = new Float32Array(1024 * 1024);
    for (let i = 0; i < largeArray.length; i++) {
      largeArray[i] = Math.random();
    }
    
    // 在Worker中执行FFT
    const result = await wasmClient.processArray('math-1', 'fft', largeArray);
    
    console.log('FFT computation completed:', result.length);
    return result;
  } catch (error) {
    console.error('Computation failed:', error);
  }
}

WASM与现代构建工具集成

// vite.config.js - Vite中的WASM配置
import { defineConfig } from 'vite';

export default defineConfig({
  // WASM优化配置
  optimizeDeps: {
    exclude: ['*.wasm']
  },
  
  // 服务器配置
  server: {
    fs: {
      allow: ['..'] // 允许访问上级目录的WASM文件
    }
  },
  
  // 构建配置
  build: {
    target: 'esnext', // 确保支持顶级await
    rollupOptions: {
      external: ['*.wasm'],
      output: {
        // WASM文件处理
        assetFileNames: (assetInfo) => {
          if (assetInfo.name.endsWith('.wasm')) {
            return 'wasm/[name].[hash][extname]';
          }
          return 'assets/[name].[hash][extname]';
        }
      }
    }
  },
  
  // 插件配置
  plugins: [
    // 自定义WASM插件
    {
      name: 'wasm-plugin',
      configureServer(server) {
        server.middlewares.use('/wasm', (req, res, next) => {
          res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
          res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
          next();
        });
      },
      load(id) {
        if (id.endsWith('.wasm')) {
          // 返回WASM模块的ES模块包装
          return `
            const wasmUrl = new URL('${id}', import.meta.url).href;
            let wasmModule;
            
            export default async function(importObject = {}) {
              if (!wasmModule) {
                const response = await fetch(wasmUrl);
                wasmModule = await WebAssembly.instantiateStreaming(response, importObject);
              }
              return wasmModule;
            }
            
            export { wasmUrl };
          `;
        }
      }
    }
  ]
});

// webpack.config.js - Webpack中的WASM配置
module.exports = {
  experiments: {
    asyncWebAssembly: true,
    syncWebAssembly: true
  },
  
  module: {
    rules: [
      {
        test: /\.wasm$/,
        type: 'webassembly/async'
      }
    ]
  },
  
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        wasm: {
          test: /\.wasm$/,
          name: 'wasm-modules',
          chunks: 'all'
        }
      }
    }
  }
};

// 现代WASM模块使用示例
// image-processor.js
import wasmInit, { wasmUrl } from './image-processor.wasm';
import { WasmModuleCache } from './wasm-cache.js';

const wasmCache = new WasmModuleCache();

export class ImageProcessor {
  constructor() {
    this.wasmModule = null;
    this.initialized = false;
  }

  async initialize() {
    if (this.initialized) return;

    try {
      // 尝试从缓存获取
      this.wasmModule = await wasmCache.getOrLoad('image-processor', async () => {
        const importObject = {
          env: {
            memory: new WebAssembly.Memory({ initial: 1024 }),
            console_log: console.log
          }
        };
        
        return await wasmInit(importObject);
      });

      this.initialized = true;
    } catch (error) {
      console.error('Failed to initialize ImageProcessor WASM:', error);
      throw error;
    }
  }

  async processImage(imageData, filters = []) {
    await this.initialize();
    
    const { memory, process_image, malloc, free } = this.wasmModule.instance.exports;
    
    // 分配输入内存
    const inputSize = imageData.data.length;
    const inputPtr = malloc(inputSize);
    
    try {
      // 复制图像数据到WASM内存
      const inputView = new Uint8Array(memory.buffer, inputPtr, inputSize);
      inputView.set(imageData.data);
      
      // 处理每个滤镜
      for (const filter of filters) {
        const filterConfig = this._encodeFilter(filter);
        process_image(inputPtr, imageData.width, imageData.height, filterConfig);
      }
      
      // 读取处理后的数据
      const outputData = new Uint8Array(inputSize);
      outputData.set(inputView);
      
      return new ImageData(
        new Uint8ClampedArray(outputData),
        imageData.width,
        imageData.height
      );
      
    } finally {
      free(inputPtr);
    }
  }

  _encodeFilter(filter) {
    // 将JavaScript滤镜配置编码为WASM可理解的格式
    const filterTypes = {
      'blur': 1,
      'sharpen': 2,
      'brightness': 3,
      'contrast': 4
    };
    
    return (filterTypes[filter.type] || 0) | 
           ((filter.intensity || 1.0) * 255) << 8;
  }
}

// WASM缓存管理
class WasmModuleCache {
  constructor() {
    this.cache = new Map();
  }

  async getOrLoad(key, loaderFn) {
    if (this.cache.has(key)) {
      return this.cache.get(key);
    }

    const module = await loaderFn();
    this.cache.set(key, module);
    return module;
  }

  clear() {
    this.cache.clear();
  }
}

通过这些技术,可以将高性能的WASM模块无缝集成到JavaScript模块化应用中,实现接近原生性能的计算密集型功能,同时保持良好的开发体验和代码组织。

构建一个模块化库

本章将通过实际案例,演示如何从零开始构建一个现代化的JavaScript模块库,涵盖项目架构、模块设计、构建配置和发布流程。

项目需求分析

我们将构建一个名为@utils/toolkit的工具库,提供以下功能:

  • 字符串处理工具
  • 数组操作工具
  • 日期格式化工具
  • 异步工具函数
  • 数据验证工具

设计目标

  1. 模块化设计:每个功能模块独立,支持按需导入
  2. 多格式支持:同时支持ES模块和CommonJS
  3. TypeScript支持:提供完整的类型定义
  4. Tree Shaking友好:支持构建工具的死代码消除
  5. 体积优化:最小化打包体积

项目结构设计

toolkit/
├── src/                    # 源代码目录
│   ├── string/            # 字符串工具模块
│   │   ├── index.ts
│   │   ├── capitalize.ts
│   │   ├── kebabCase.ts
│   │   └── truncate.ts
│   ├── array/             # 数组工具模块
│   │   ├── index.ts
│   │   ├── chunk.ts
│   │   ├── unique.ts
│   │   └── groupBy.ts
│   ├── date/              # 日期工具模块
│   │   ├── index.ts
│   │   ├── format.ts
│   │   └── relative.ts
│   ├── async/             # 异步工具模块
│   │   ├── index.ts
│   │   ├── delay.ts
│   │   ├── timeout.ts
│   │   └── retry.ts
│   ├── validation/        # 验证工具模块
│   │   ├── index.ts
│   │   ├── email.ts
│   │   ├── url.ts
│   │   └── phone.ts
│   └── index.ts           # 主入口文件
├── dist/                  # 构建输出目录
├── tests/                 # 测试文件
├── examples/              # 使用示例
├── package.json
├── tsconfig.json
├── rollup.config.js
└── README.md

模块实现

1. 字符串工具模块

// src/string/capitalize.ts
export function capitalize(str: string): string {
  if (!str) return str;
  return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}

// src/string/kebabCase.ts
export function kebabCase(str: string): string {
  return str
    .replace(/([a-z])([A-Z])/g, '$1-$2')
    .replace(/[\s_]+/g, '-')
    .toLowerCase();
}

// src/string/truncate.ts
export interface TruncateOptions {
  length: number;
  suffix?: string;
  separator?: string;
}

export function truncate(str: string, options: TruncateOptions): string {
  const { length, suffix = '...', separator } = options;
  
  if (str.length <= length) return str;
  
  let truncated = str.slice(0, length - suffix.length);
  
  if (separator) {
    const lastIndex = truncated.lastIndexOf(separator);
    if (lastIndex > 0) {
      truncated = truncated.slice(0, lastIndex);
    }
  }
  
  return truncated + suffix;
}

// src/string/index.ts
export { capitalize } from './capitalize';
export { kebabCase } from './kebabCase';
export { truncate, type TruncateOptions } from './truncate';

2. 数组工具模块

// src/array/chunk.ts
export function chunk<T>(array: T[], size: number): T[][] {
  if (size <= 0) throw new Error('Chunk size must be positive');
  
  const chunks: T[][] = [];
  for (let i = 0; i < array.length; i += size) {
    chunks.push(array.slice(i, i + size));
  }
  return chunks;
}

// src/array/unique.ts
export function unique<T>(array: T[]): T[] {
  return Array.from(new Set(array));
}

export function uniqueBy<T, K>(array: T[], keyFn: (item: T) => K): T[] {
  const seen = new Set<K>();
  return array.filter(item => {
    const key = keyFn(item);
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
  });
}

// src/array/groupBy.ts
export function groupBy<T, K extends string | number | symbol>(
  array: T[], 
  keyFn: (item: T) => K
): Record<K, T[]> {
  return array.reduce((groups, item) => {
    const key = keyFn(item);
    if (!groups[key]) groups[key] = [];
    groups[key].push(item);
    return groups;
  }, {} as Record<K, T[]>);
}

// src/array/index.ts
export { chunk } from './chunk';
export { unique, uniqueBy } from './unique';
export { groupBy } from './groupBy';

3. 异步工具模块

// src/async/delay.ts
export function delay(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// src/async/timeout.ts
export class TimeoutError extends Error {
  constructor(message: string = 'Operation timed out') {
    super(message);
    this.name = 'TimeoutError';
  }
}

export function timeout<T>(
  promise: Promise<T>, 
  ms: number,
  errorMessage?: string
): Promise<T> {
  return Promise.race([
    promise,
    new Promise<never>((_, reject) => 
      setTimeout(() => reject(new TimeoutError(errorMessage)), ms)
    )
  ]);
}

// src/async/retry.ts
export interface RetryOptions {
  retries: number;
  delay?: number;
  backoff?: 'linear' | 'exponential';
  factor?: number;
}

export async function retry<T>(
  fn: () => Promise<T>,
  options: RetryOptions
): Promise<T> {
  const { retries, delay: baseDelay = 1000, backoff = 'linear', factor = 2 } = options;
  
  let lastError: Error;
  
  for (let attempt = 0; attempt <= retries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error as Error;
      
      if (attempt === retries) break;
      
      const delayMs = backoff === 'exponential' 
        ? baseDelay * Math.pow(factor, attempt)
        : baseDelay * (attempt + 1);
        
      await delay(delayMs);
    }
  }
  
  throw lastError!;
}

// src/async/index.ts
export { delay } from './delay';
export { timeout, TimeoutError } from './timeout';
export { retry, type RetryOptions } from './retry';

4. 主入口文件

// src/index.ts
// 字符串工具
export * as string from './string';
export { capitalize, kebabCase, truncate } from './string';

// 数组工具
export * as array from './array';
export { chunk, unique, uniqueBy, groupBy } from './array';

// 日期工具
export * as date from './date';

// 异步工具
export * as async from './async';
export { delay, timeout, retry, TimeoutError } from './async';

// 验证工具
export * as validation from './validation';

// 类型导出
export type { TruncateOptions } from './string';
export type { RetryOptions } from './async';

构建配置

1. TypeScript配置

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "node",
    "declaration": true,
    "declarationMap": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["dist", "node_modules", "tests"]
}

2. Rollup配置

// rollup.config.js
import typescript from '@rollup/plugin-typescript';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import { terser } from 'rollup-plugin-terser';

const isProduction = process.env.NODE_ENV === 'production';

export default [
  // ES模块构建
  {
    input: 'src/index.ts',
    output: {
      file: 'dist/index.esm.js',
      format: 'esm',
      sourcemap: true
    },
    plugins: [
      nodeResolve(),
      typescript({
        tsconfig: './tsconfig.json',
        declaration: true,
        declarationDir: './dist',
        rootDir: './src'
      }),
      ...(isProduction ? [terser()] : [])
    ]
  },
  // CommonJS构建
  {
    input: 'src/index.ts',
    output: {
      file: 'dist/index.cjs.js',
      format: 'cjs',
      sourcemap: true,
      exports: 'named'
    },
    plugins: [
      nodeResolve(),
      typescript({
        tsconfig: './tsconfig.json'
      }),
      ...(isProduction ? [terser()] : [])
    ]
  },
  // UMD构建(浏览器兼容)
  {
    input: 'src/index.ts',
    output: {
      file: 'dist/index.umd.js',
      format: 'umd',
      name: 'UtilsToolkit',
      sourcemap: true
    },
    plugins: [
      nodeResolve(),
      typescript({
        tsconfig: './tsconfig.json'
      }),
      ...(isProduction ? [terser()] : [])
    ]
  }
];

3. Package.json配置

{
  "name": "@utils/toolkit",
  "version": "1.0.0",
  "description": "A modular utility library for JavaScript",
  "main": "dist/index.cjs.js",
  "module": "dist/index.esm.js",
  "types": "dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.esm.js",
      "require": "./dist/index.cjs.js",
      "types": "./dist/index.d.ts"
    },
    "./string": {
      "import": "./dist/string/index.js",
      "require": "./dist/string/index.js",
      "types": "./dist/string/index.d.ts"
    },
    "./array": {
      "import": "./dist/array/index.js",
      "require": "./dist/array/index.js", 
      "types": "./dist/array/index.d.ts"
    },
    "./async": {
      "import": "./dist/async/index.js",
      "require": "./dist/async/index.js",
      "types": "./dist/async/index.d.ts"
    }
  },
  "files": [
    "dist"
  ],
  "scripts": {
    "build": "rollup -c",
    "build:prod": "NODE_ENV=production rollup -c",
    "dev": "rollup -c -w",
    "test": "jest",
    "test:watch": "jest --watch",
    "type-check": "tsc --noEmit",
    "lint": "eslint src --ext .ts",
    "prepublishOnly": "npm run build:prod"
  },
  "keywords": ["utilities", "toolkit", "javascript", "typescript"],
  "author": "Your Name",
  "license": "MIT",
  "devDependencies": {
    "@rollup/plugin-node-resolve": "^15.0.0",
    "@rollup/plugin-typescript": "^11.0.0",
    "@types/jest": "^29.0.0",
    "jest": "^29.0.0",
    "rollup": "^3.0.0",
    "rollup-plugin-terser": "^7.0.0",
    "typescript": "^5.0.0"
  }
}

使用示例

1. 完整导入

import * as toolkit from '@utils/toolkit';

const result = toolkit.string.capitalize('hello world');
const chunks = toolkit.array.chunk([1, 2, 3, 4, 5], 2);

2. 按需导入

import { capitalize, chunk } from '@utils/toolkit';

const title = capitalize('hello world');
const groups = chunk([1, 2, 3, 4, 5], 2);

3. 模块化导入

import { capitalize, kebabCase } from '@utils/toolkit/string';
import { delay, retry } from '@utils/toolkit/async';

// 字符串处理
const title = capitalize('hello world');
const slug = kebabCase('Hello World API');

// 异步操作
await delay(1000);
const result = await retry(() => fetchData(), { retries: 3 });

4. CommonJS使用

const { capitalize, chunk } = require('@utils/toolkit');

const title = capitalize('hello world');
const groups = chunk([1, 2, 3, 4, 5], 2);

测试策略

1. 单元测试

// tests/string/capitalize.test.ts
import { capitalize } from '../../src/string/capitalize';

describe('capitalize', () => {
  test('should capitalize first letter', () => {
    expect(capitalize('hello')).toBe('Hello');
  });

  test('should handle empty string', () => {
    expect(capitalize('')).toBe('');
  });

  test('should handle already capitalized string', () => {
    expect(capitalize('Hello')).toBe('Hello');
  });
});

2. 集成测试

// tests/integration/exports.test.ts
import * as toolkit from '../../src';

describe('Module Exports', () => {
  test('should export all string utilities', () => {
    expect(typeof toolkit.capitalize).toBe('function');
    expect(typeof toolkit.kebabCase).toBe('function');
    expect(typeof toolkit.truncate).toBe('function');
  });

  test('should export namespaced modules', () => {
    expect(typeof toolkit.string.capitalize).toBe('function');
    expect(typeof toolkit.array.chunk).toBe('function');
    expect(typeof toolkit.async.delay).toBe('function');
  });
});

优化技巧

1. Tree Shaking优化

// 确保每个函数都是独立导出
// ❌ 不好的做法
const utils = {
  capitalize: (str) => str.charAt(0).toUpperCase() + str.slice(1),
  kebabCase: (str) => str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
};
export default utils;

// ✅ 好的做法
export function capitalize(str) {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

export function kebabCase(str) {
  return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
}

2. 包体积优化

// 避免引入大型依赖
// ❌ 引入整个lodash
import _ from 'lodash';

// ✅ 只引入需要的函数
import { isEqual } from 'lodash/isEqual';

// ✅ 或者自己实现简单版本
export function isEqual(a, b) {
  return JSON.stringify(a) === JSON.stringify(b);
}

3. 类型优化

// 使用泛型提供更好的类型推断
export function map<T, U>(array: T[], fn: (item: T) => U): U[] {
  return array.map(fn);
}

// 使用条件类型
export type Flatten<T> = T extends (infer U)[] ? U : T;

export function flatten<T>(array: T[]): Flatten<T>[] {
  return array.flat() as Flatten<T>[];
}

发布流程

1. 版本管理

# 更新版本号
npm version patch  # 修复bug
npm version minor  # 新功能
npm version major  # 破坏性变更

# 构建和发布
npm run build:prod
npm publish

2. CI/CD配置

# .github/workflows/publish.yml
name: Publish to NPM

on:
  push:
    tags:
      - 'v*'

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
          registry-url: 'https://registry.npmjs.org'
      
      - run: npm ci
      - run: npm test
      - run: npm run build:prod
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

3. 语义化版本

{
  "scripts": {
    "release:patch": "npm version patch && git push --tags",
    "release:minor": "npm version minor && git push --tags", 
    "release:major": "npm version major && git push --tags"
  }
}

最佳实践总结

  1. 模块设计:保持单一职责,避免模块间强耦合
  2. 类型安全:提供完整的TypeScript类型定义
  3. 构建优化:支持多种模块格式,优化包体积
  4. 测试覆盖:保证高测试覆盖率和质量
  5. 文档完善:提供清晰的API文档和使用示例
  6. 版本管理:遵循语义化版本规范
  7. 持续集成:自动化测试和发布流程

通过这个完整的案例,我们展示了如何构建一个现代化、可维护的JavaScript模块库,从项目架构到发布流程的每个环节都进行了详细说明。


下一章: 大型项目模块组织

大型项目模块组织

本章将探讨如何在大型前端项目中进行模块化架构设计,包括项目结构规划、模块间通信、依赖管理和团队协作等方面的最佳实践。

项目背景

我们以一个大型电商平台前端项目为例,该项目具有以下特点:

  • 多个团队并行开发
  • 功能模块复杂且相互关联
  • 需要支持多种业务场景
  • 代码库规模超过100万行
  • 需要支持微前端架构

整体架构设计

1. 分层架构

大型电商平台
├── 应用层 (Applications)
│   ├── 用户端应用 (Customer App)
│   ├── 商家端应用 (Merchant App)
│   └── 管理端应用 (Admin App)
├── 业务层 (Business)
│   ├── 用户模块 (User)
│   ├── 商品模块 (Product)
│   ├── 订单模块 (Order)
│   ├── 支付模块 (Payment)
│   └── 营销模块 (Marketing)
├── 基础设施层 (Infrastructure)
│   ├── 网络服务 (Network)
│   ├── 状态管理 (State)
│   ├── 路由管理 (Router)
│   └── 缓存服务 (Cache)
└── 通用层 (Common)
    ├── UI组件库 (Components)
    ├── 工具函数 (Utils)
    ├── 常量定义 (Constants)
    └── 类型定义 (Types)

2. 目录结构设计

ecommerce-platform/
├── apps/                          # 应用层
│   ├── customer/                  # 用户端应用
│   ├── merchant/                  # 商家端应用
│   └── admin/                     # 管理端应用
├── packages/                      # 共享包
│   ├── business/                  # 业务模块
│   │   ├── user/
│   │   ├── product/
│   │   ├── order/
│   │   ├── payment/
│   │   └── marketing/
│   ├── infrastructure/            # 基础设施
│   │   ├── network/
│   │   ├── state/
│   │   ├── router/
│   │   └── cache/
│   ├── ui/                        # UI组件库
│   │   ├── components/
│   │   ├── themes/
│   │   └── styles/
│   └── common/                    # 通用工具
│       ├── utils/
│       ├── constants/
│       ├── types/
│       └── hooks/
├── tools/                         # 开发工具
│   ├── build/
│   ├── eslint-config/
│   └── test-utils/
├── docs/                          # 文档
└── scripts/                       # 脚本

模块设计原则

1. 领域驱动设计

每个业务模块按照领域驱动设计(DDD)原则组织:

// packages/business/user/
├── src/
│   ├── domain/                    # 领域层
│   │   ├── entities/              # 实体
│   │   │   ├── User.ts
│   │   │   └── Profile.ts
│   │   ├── valueObjects/          # 值对象
│   │   │   ├── Email.ts
│   │   │   └── Phone.ts
│   │   ├── services/              # 领域服务
│   │   │   └── UserService.ts
│   │   └── repositories/          # 仓储接口
│   │       └── UserRepository.ts
│   ├── infrastructure/            # 基础设施层
│   │   ├── repositories/          # 仓储实现
│   │   │   └── ApiUserRepository.ts
│   │   └── services/              # 外部服务
│   │       └── AuthService.ts
│   ├── application/               # 应用层
│   │   ├── useCases/              # 用例
│   │   │   ├── CreateUser.ts
│   │   │   ├── UpdateProfile.ts
│   │   │   └── GetUserById.ts
│   │   └── dtos/                  # 数据传输对象
│   │       ├── CreateUserDto.ts
│   │       └── UpdateProfileDto.ts
│   └── presentation/              # 表示层
│       ├── components/            # 组件
│       ├── hooks/                 # 钩子
│       └── stores/                # 状态管理
└── package.json

2. 模块边界定义

// packages/business/user/src/index.ts
// 只导出应用层和表示层的公共接口
export { CreateUser, UpdateProfile, GetUserById } from './application/useCases';
export { CreateUserDto, UpdateProfileDto } from './application/dtos';
export { UserProfile, UserSettings } from './presentation/components';
export { useUser, useUserProfile } from './presentation/hooks';
export { userStore } from './presentation/stores';

// 类型定义
export type { User, Profile } from './domain/entities';
export type { Email, Phone } from './domain/valueObjects';

3. 依赖注入配置

// packages/business/user/src/container.ts
import { Container } from 'inversify';
import { UserRepository } from './domain/repositories/UserRepository';
import { ApiUserRepository } from './infrastructure/repositories/ApiUserRepository';
import { CreateUser } from './application/useCases/CreateUser';

const userContainer = new Container();

// 绑定依赖
userContainer.bind<UserRepository>('UserRepository').to(ApiUserRepository);
userContainer.bind<CreateUser>('CreateUser').to(CreateUser);

export { userContainer };

模块间通信策略

1. 事件驱动架构

// packages/infrastructure/events/src/EventBus.ts
export interface Event {
  type: string;
  payload: any;
  timestamp: Date;
  source: string;
}

export class EventBus {
  private listeners: Map<string, Set<(event: Event) => void>> = new Map();

  subscribe(eventType: string, listener: (event: Event) => void): void {
    if (!this.listeners.has(eventType)) {
      this.listeners.set(eventType, new Set());
    }
    this.listeners.get(eventType)!.add(listener);
  }

  unsubscribe(eventType: string, listener: (event: Event) => void): void {
    this.listeners.get(eventType)?.delete(listener);
  }

  publish(event: Event): void {
    const listeners = this.listeners.get(event.type);
    if (listeners) {
      listeners.forEach(listener => listener(event));
    }
  }
}

// 单例实例
export const eventBus = new EventBus();

2. 跨模块通信示例

// packages/business/order/src/application/useCases/CreateOrder.ts
import { eventBus } from '@platform/infrastructure/events';
import { Order } from '../domain/entities/Order';

export class CreateOrder {
  async execute(orderData: CreateOrderDto): Promise<Order> {
    const order = await this.orderRepository.create(orderData);
    
    // 发布订单创建事件
    eventBus.publish({
      type: 'ORDER_CREATED',
      payload: { orderId: order.id, userId: order.userId },
      timestamp: new Date(),
      source: 'order-module'
    });
    
    return order;
  }
}

// packages/business/user/src/application/useCases/UpdateUserPoints.ts
import { eventBus } from '@platform/infrastructure/events';

export class UpdateUserPoints {
  constructor() {
    // 监听订单创建事件
    eventBus.subscribe('ORDER_CREATED', this.handleOrderCreated.bind(this));
  }

  private async handleOrderCreated(event: Event): Promise<void> {
    const { userId } = event.payload;
    await this.userRepository.addPoints(userId, 100);
  }
}

状态管理架构

1. 分层状态管理

// packages/infrastructure/state/src/Store.ts
import { configureStore } from '@reduxjs/toolkit';
import { userSlice } from '@platform/business/user';
import { productSlice } from '@platform/business/product';
import { orderSlice } from '@platform/business/order';

export const store = configureStore({
  reducer: {
    // 业务状态
    user: userSlice.reducer,
    product: productSlice.reducer,
    order: orderSlice.reducer,
    
    // 应用状态
    ui: uiSlice.reducer,
    router: routerSlice.reducer,
    
    // 基础设施状态
    network: networkSlice.reducer,
    cache: cacheSlice.reducer
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: ['persist/PERSIST']
      }
    }).concat(
      // 自定义中间件
      eventMiddleware,
      cacheMiddleware,
      loggerMiddleware
    )
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

2. 模块状态隔离

// packages/business/user/src/presentation/stores/userSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

interface UserState {
  currentUser: User | null;
  profile: Profile | null;
  loading: boolean;
  error: string | null;
}

const initialState: UserState = {
  currentUser: null,
  profile: null,
  loading: false,
  error: null
};

export const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    setUser: (state, action: PayloadAction<User>) => {
      state.currentUser = action.payload;
    },
    setProfile: (state, action: PayloadAction<Profile>) => {
      state.profile = action.payload;
    },
    setLoading: (state, action: PayloadAction<boolean>) => {
      state.loading = action.payload;
    },
    setError: (state, action: PayloadAction<string>) => {
      state.error = action.payload;
    }
  }
});

export const { setUser, setProfile, setLoading, setError } = userSlice.actions;

组件库设计

1. 设计系统组件

// packages/ui/components/src/Button/Button.tsx
import React from 'react';
import { styled } from '@platform/ui/themes';

export interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'small' | 'medium' | 'large';
  disabled?: boolean;
  loading?: boolean;
  children: React.ReactNode;
  onClick?: () => void;
}

const StyledButton = styled.button<ButtonProps>`
  /* 使用设计系统的token */
  font-family: ${({ theme }) => theme.typography.fontFamily};
  font-size: ${({ theme, size }) => theme.typography.fontSize[size || 'medium']};
  padding: ${({ theme, size }) => theme.spacing.button[size || 'medium']};
  border-radius: ${({ theme }) => theme.borderRadius.medium};
  
  /* 变体样式 */
  ${({ theme, variant }) => {
    switch (variant) {
      case 'primary':
        return `
          background-color: ${theme.colors.primary[500]};
          color: ${theme.colors.white};
          border: none;
        `;
      case 'secondary':
        return `
          background-color: transparent;
          color: ${theme.colors.primary[500]};
          border: 1px solid ${theme.colors.primary[500]};
        `;
      case 'danger':
        return `
          background-color: ${theme.colors.danger[500]};
          color: ${theme.colors.white};
          border: none;
        `;
      default:
        return '';
    }
  }}
`;

export const Button: React.FC<ButtonProps> = ({
  variant = 'primary',
  size = 'medium',
  disabled = false,
  loading = false,
  children,
  onClick
}) => {
  return (
    <StyledButton
      variant={variant}
      size={size}
      disabled={disabled || loading}
      onClick={onClick}
    >
      {loading ? 'Loading...' : children}
    </StyledButton>
  );
};

2. 复合组件模式

// packages/ui/components/src/DataTable/DataTable.tsx
import React from 'react';

interface DataTableProps<T> {
  data: T[];
  children: React.ReactNode;
}

interface DataTableHeaderProps {
  children: React.ReactNode;
}

interface DataTableBodyProps {
  children: React.ReactNode;
}

interface DataTableRowProps<T> {
  item: T;
  children: (item: T) => React.ReactNode;
}

function DataTable<T>({ data, children }: DataTableProps<T>) {
  return (
    <table className="data-table">
      {children}
    </table>
  );
}

function DataTableHeader({ children }: DataTableHeaderProps) {
  return (
    <thead>
      <tr>{children}</tr>
    </thead>
  );
}

function DataTableBody<T>({ children }: DataTableBodyProps) {
  return <tbody>{children}</tbody>;
}

function DataTableRow<T>({ item, children }: DataTableRowProps<T>) {
  return <tr>{children(item)}</tr>;
}

// 复合组件导出
DataTable.Header = DataTableHeader;
DataTable.Body = DataTableBody;
DataTable.Row = DataTableRow;

export { DataTable };

性能优化策略

1. 代码分割策略

// apps/customer/src/routes/index.tsx
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
import { LoadingSpinner } from '@platform/ui/components';

// 路由级别的代码分割
const HomePage = lazy(() => import('../pages/HomePage'));
const ProductPage = lazy(() => import('../pages/ProductPage'));
const OrderPage = lazy(() => import('../pages/OrderPage'));
const UserPage = lazy(() => import('../pages/UserPage'));

export function AppRoutes() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/products/*" element={<ProductPage />} />
        <Route path="/orders/*" element={<OrderPage />} />
        <Route path="/user/*" element={<UserPage />} />
      </Routes>
    </Suspense>
  );
}

2. 模块级别的懒加载

// packages/business/product/src/index.ts
import { lazy } from 'react';

// 懒加载复杂组件
export const ProductCatalog = lazy(() => import('./presentation/components/ProductCatalog'));
export const ProductDetail = lazy(() => import('./presentation/components/ProductDetail'));

// 立即导出轻量级模块
export { useProduct } from './presentation/hooks/useProduct';
export { productStore } from './presentation/stores/productSlice';
export type { Product, ProductCategory } from './domain/entities';

3. 缓存策略

// packages/infrastructure/cache/src/CacheManager.ts
export class CacheManager {
  private cache = new Map<string, { data: any; expiry: number }>();

  set<T>(key: string, data: T, ttl: number = 5 * 60 * 1000): void {
    this.cache.set(key, {
      data,
      expiry: Date.now() + ttl
    });
  }

  get<T>(key: string): T | null {
    const cached = this.cache.get(key);
    if (!cached) return null;

    if (Date.now() > cached.expiry) {
      this.cache.delete(key);
      return null;
    }

    return cached.data as T;
  }

  invalidate(pattern: string): void {
    const regex = new RegExp(pattern);
    for (const key of this.cache.keys()) {
      if (regex.test(key)) {
        this.cache.delete(key);
      }
    }
  }
}

export const cacheManager = new CacheManager();

构建和部署

1. Monorepo构建配置

// turbo.json
{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"]
    },
    "test": {
      "dependsOn": ["^build"],
      "outputs": ["coverage/**"]
    },
    "lint": {
      "outputs": []
    },
    "dev": {
      "cache": false
    }
  }
}

2. 微前端部署策略

// tools/build/webpack.config.js
const ModuleFederationPlugin = require('@module-federation/webpack');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'customer_app',
      filename: 'remoteEntry.js',
      exposes: {
        './App': './src/App',
        './routes': './src/routes'
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true },
        '@platform/ui': { singleton: true }
      }
    })
  ]
};

团队协作

1. 模块所有权

# .github/CODEOWNERS
# 业务模块所有权
/packages/business/user/ @user-team
/packages/business/product/ @product-team
/packages/business/order/ @order-team
/packages/business/payment/ @payment-team

# 基础设施
/packages/infrastructure/ @platform-team
/packages/ui/ @design-system-team

# 应用
/apps/customer/ @frontend-team
/apps/merchant/ @merchant-team
/apps/admin/ @admin-team

2. 开发工作流

// scripts/dev-workflow.ts
export class DevWorkflow {
  async runTests(modules: string[]): Promise<void> {
    // 只运行相关模块的测试
    for (const module of modules) {
      await this.runModuleTests(module);
    }
  }

  async buildAffectedModules(changedFiles: string[]): Promise<void> {
    const affectedModules = this.getAffectedModules(changedFiles);
    await this.buildModules(affectedModules);
  }

  private getAffectedModules(changedFiles: string[]): string[] {
    // 基于依赖图分析影响的模块
    return this.dependencyGraph.getAffected(changedFiles);
  }
}

监控和维护

1. 模块健康度监控

// tools/monitoring/src/ModuleHealthChecker.ts
export class ModuleHealthChecker {
  checkDependencyHealth(): HealthReport {
    const report: HealthReport = {
      circularDependencies: this.findCircularDependencies(),
      unusedDependencies: this.findUnusedDependencies(),
      outdatedDependencies: this.findOutdatedDependencies(),
      bundleSize: this.analyzeBundleSize()
    };
    return report;
  }

  generateReport(): void {
    const health = this.checkDependencyHealth();
    console.log('模块健康度报告:', health);
  }
}

2. 性能监控

// packages/infrastructure/monitoring/src/PerformanceMonitor.ts
export class PerformanceMonitor {
  trackModuleLoad(moduleName: string): void {
    const startTime = performance.now();
    
    // 监控模块加载时间
    import(moduleName).then(() => {
      const loadTime = performance.now() - startTime;
      this.reportMetric('module_load_time', loadTime, { module: moduleName });
    });
  }

  trackComponentRender(componentName: string, renderTime: number): void {
    this.reportMetric('component_render_time', renderTime, { component: componentName });
  }

  private reportMetric(metric: string, value: number, tags: Record<string, string>): void {
    // 发送到监控系统
    analytics.track(metric, value, tags);
  }
}

最佳实践总结

1. 架构设计原则

  • 单一职责:每个模块只负责一个业务领域
  • 依赖倒置:依赖抽象而不是具体实现
  • 开放封闭:对扩展开放,对修改封闭
  • 接口隔离:客户端不应该依赖它不需要的接口

2. 模块化策略

  • 清晰的边界:明确定义模块的输入和输出
  • 松耦合:减少模块间的直接依赖
  • 高内聚:相关功能应该在同一个模块内
  • 可测试性:模块应该易于单独测试

3. 团队协作

  • 模块所有权:每个模块有明确的负责团队
  • API契约:模块间通过稳定的API进行交互
  • 版本管理:使用语义化版本管理模块更新
  • 文档维护:保持API文档和架构文档的更新

4. 性能优化

  • 代码分割:按需加载模块和组件
  • 缓存策略:合理使用各种缓存机制
  • 包体积优化:避免重复依赖和无用代码
  • 运行时优化:使用虚拟列表、memo等技术

通过这套完整的大型项目模块化方案,我们可以构建出可维护、可扩展、高性能的企业级前端应用。


下一章: 模块懒加载实现

模块懒加载实现

本章将深入探讨JavaScript模块懒加载的实现方案,包括代码分割、动态导入、预加载策略和性能优化技巧。

懒加载概述

什么是懒加载

懒加载(Lazy Loading)是一种优化策略,将资源的加载推迟到实际需要时才进行。在模块化开发中,懒加载可以:

  • 减少初始加载时间
  • 降低内存占用
  • 按需加载功能模块
  • 改善用户体验

懒加载的应用场景

  1. 路由级别:页面路由按需加载
  2. 组件级别:复杂组件延迟加载
  3. 功能模块:可选功能的动态加载
  4. 第三方库:大型依赖的延迟引入

基础实现方案

1. 动态导入基础

// 传统的静态导入
import { heavyFunction } from './heavy-module.js';

// 动态导入 - 返回Promise
const loadHeavyModule = async () => {
  const { heavyFunction } = await import('./heavy-module.js');
  return heavyFunction;
};

// 使用示例
async function handleClick() {
  const heavyFunction = await loadHeavyModule();
  const result = heavyFunction(data);
  console.log(result);
}

2. 条件懒加载

// 基于条件的懒加载
class FeatureManager {
  private loadedModules = new Map();

  async loadFeature(featureName: string, condition: boolean) {
    if (!condition) return null;
    
    if (this.loadedModules.has(featureName)) {
      return this.loadedModules.get(featureName);
    }

    let module;
    switch (featureName) {
      case 'charts':
        module = await import('./features/charts.js');
        break;
      case 'editor':
        module = await import('./features/editor.js');
        break;
      case 'analytics':
        module = await import('./features/analytics.js');
        break;
      default:
        throw new Error(`Unknown feature: ${featureName}`);
    }

    this.loadedModules.set(featureName, module);
    return module;
  }
}

// 使用示例
const featureManager = new FeatureManager();

// 基于用户权限加载功能
if (user.hasPermission('analytics')) {
  const analytics = await featureManager.loadFeature('analytics', true);
  analytics.initialize();
}

React中的懒加载

1. 组件懒加载

// React.lazy 基础用法
import React, { Suspense, lazy } from 'react';

// 懒加载组件
const LazyComponent = lazy(() => import('./HeavyComponent'));
const LazyModal = lazy(() => import('./Modal'));

function App() {
  const [showModal, setShowModal] = useState(false);

  return (
    <div>
      <Suspense fallback={<div>Loading component...</div>}>
        <LazyComponent />
      </Suspense>
      
      {showModal && (
        <Suspense fallback={<div>Loading modal...</div>}>
          <LazyModal onClose={() => setShowModal(false)} />
        </Suspense>
      )}
    </div>
  );
}

2. 高级懒加载Hook

// useLazyComponent Hook
import { useState, useCallback, ComponentType } from 'react';

type LazyComponentLoader<T = {}> = () => Promise<{ default: ComponentType<T> }>;

interface UseLazyComponentReturn<T> {
  LazyComponent: ComponentType<T> | null;
  loading: boolean;
  error: Error | null;
  loadComponent: () => Promise<void>;
}

function useLazyComponent<T = {}>(
  loader: LazyComponentLoader<T>
): UseLazyComponentReturn<T> {
  const [LazyComponent, setLazyComponent] = useState<ComponentType<T> | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<Error | null>(null);

  const loadComponent = useCallback(async () => {
    if (LazyComponent) return; // 已加载

    setLoading(true);
    setError(null);

    try {
      const module = await loader();
      setLazyComponent(() => module.default);
    } catch (err) {
      setError(err as Error);
    } finally {
      setLoading(false);
    }
  }, [loader, LazyComponent]);

  return { LazyComponent, loading, error, loadComponent };
}

// 使用示例
function FeaturePage() {
  const { 
    LazyComponent: ChartComponent, 
    loading, 
    error, 
    loadComponent 
  } = useLazyComponent(() => import('./ChartComponent'));

  return (
    <div>
      <button onClick={loadComponent}>
        Load Chart Component
      </button>
      
      {loading && <div>Loading chart...</div>}
      {error && <div>Error loading chart: {error.message}</div>}
      {ChartComponent && <ChartComponent data={data} />}
    </div>
  );
}

3. 路由懒加载

// React Router懒加载
import { Routes, Route } from 'react-router-dom';
import { lazy, Suspense } from 'react';

// 懒加载页面组件
const HomePage = lazy(() => import('./pages/HomePage'));
const AboutPage = lazy(() => import('./pages/AboutPage'));
const ProfilePage = lazy(() => import('./pages/ProfilePage'));
const AdminPage = lazy(() => import('./pages/AdminPage'));

// 加载提示组件
function PageLoader() {
  return (
    <div className="page-loader">
      <div className="spinner" />
      <p>Loading page...</p>
    </div>
  );
}

function AppRouter() {
  return (
    <Suspense fallback={<PageLoader />}>
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/about" element={<AboutPage />} />
        <Route path="/profile" element={<ProfilePage />} />
        <Route path="/admin" element={<AdminPage />} />
      </Routes>
    </Suspense>
  );
}

高级懒加载策略

1. 预加载策略

// 智能预加载管理器
class PreloadManager {
  private preloadCache = new Map<string, Promise<any>>();
  private priorityQueue: string[] = [];

  // 预加载资源
  preload(modulePath: string, priority: 'high' | 'low' = 'low'): Promise<any> {
    if (this.preloadCache.has(modulePath)) {
      return this.preloadCache.get(modulePath)!;
    }

    const promise = import(modulePath);
    this.preloadCache.set(modulePath, promise);

    if (priority === 'high') {
      this.priorityQueue.unshift(modulePath);
    } else {
      this.priorityQueue.push(modulePath);
    }

    return promise;
  }

  // 在空闲时间预加载
  preloadOnIdle(modules: string[]): void {
    if ('requestIdleCallback' in window) {
      window.requestIdleCallback(() => {
        modules.forEach(module => this.preload(module, 'low'));
      });
    } else {
      // 降级方案
      setTimeout(() => {
        modules.forEach(module => this.preload(module, 'low'));
      }, 100);
    }
  }

  // 基于用户交互预加载
  preloadOnHover(element: HTMLElement, modulePath: string): void {
    element.addEventListener('mouseenter', () => {
      this.preload(modulePath, 'high');
    }, { once: true });
  }

  // 基于网络状态的智能预加载
  smartPreload(modules: string[]): void {
    if ('connection' in navigator) {
      const connection = (navigator as any).connection;
      
      // 快速网络连接时预加载更多
      if (connection.effectiveType === '4g') {
        modules.forEach(module => this.preload(module));
      } else if (connection.effectiveType === '3g') {
        // 3G网络只预加载高优先级模块
        modules.slice(0, 2).forEach(module => this.preload(module));
      }
    }
  }
}

// 使用示例
const preloadManager = new PreloadManager();

// 页面加载后预加载下一可能访问的页面
useEffect(() => {
  preloadManager.preloadOnIdle([
    './pages/ProfilePage',
    './pages/SettingsPage'
  ]);
}, []);

// 悬停时预加载
useEffect(() => {
  const profileLink = document.querySelector('[data-route="/profile"]');
  if (profileLink) {
    preloadManager.preloadOnHover(profileLink, './pages/ProfilePage');
  }
}, []);

2. 渐进式加载

// 渐进式模块加载器
class ProgressiveLoader {
  async loadModuleProgressively<T>(
    modulePath: string,
    onProgress?: (progress: number) => void
  ): Promise<T> {
    // 模拟加载进度(实际中可能需要自定义加载器)
    const steps = [
      { progress: 0.2, message: 'Downloading module...' },
      { progress: 0.5, message: 'Parsing module...' },
      { progress: 0.8, message: 'Initializing...' },
      { progress: 1.0, message: 'Complete!' }
    ];

    for (const step of steps) {
      await this.delay(100); // 模拟加载时间
      onProgress?.(step.progress);
    }

    return import(modulePath);
  }

  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// 使用示例
function ProgressiveLoadingComponent() {
  const [progress, setProgress] = useState(0);
  const [module, setModule] = useState(null);

  const loadModule = async () => {
    const loader = new ProgressiveLoader();
    const loadedModule = await loader.loadModuleProgressively(
      './heavy-module.js',
      setProgress
    );
    setModule(loadedModule);
  };

  return (
    <div>
      {progress < 1 && (
        <div className="progress-bar">
          <div 
            className="progress-fill" 
            style={{ width: `${progress * 100}%` }}
          />
          <span>{Math.round(progress * 100)}%</span>
        </div>
      )}
      {module && <div>Module loaded successfully!</div>}
    </div>
  );
}

3. 缓存优化

// 模块缓存管理器
class ModuleCacheManager {
  private cache = new Map<string, any>();
  private metadata = new Map<string, { 
    lastAccessed: number; 
    accessCount: number; 
    size: number 
  }>();

  async getModule<T>(modulePath: string): Promise<T> {
    // 检查缓存
    if (this.cache.has(modulePath)) {
      this.updateMetadata(modulePath);
      return this.cache.get(modulePath);
    }

    // 加载模块
    const module = await import(modulePath);
    
    // 缓存模块
    this.cache.set(modulePath, module);
    this.metadata.set(modulePath, {
      lastAccessed: Date.now(),
      accessCount: 1,
      size: this.estimateSize(module)
    });

    // 检查缓存大小限制
    this.evictIfNecessary();

    return module;
  }

  private updateMetadata(modulePath: string): void {
    const meta = this.metadata.get(modulePath);
    if (meta) {
      meta.lastAccessed = Date.now();
      meta.accessCount++;
    }
  }

  private estimateSize(module: any): number {
    // 简单的大小估算
    return JSON.stringify(module).length;
  }

  private evictIfNecessary(): void {
    const maxCacheSize = 10 * 1024 * 1024; // 10MB
    let currentSize = 0;

    for (const [, meta] of this.metadata) {
      currentSize += meta.size;
    }

    if (currentSize > maxCacheSize) {
      // LRU淘汰策略
      const entries = Array.from(this.metadata.entries())
        .sort(([, a], [, b]) => a.lastAccessed - b.lastAccessed);

      const toEvict = entries.slice(0, Math.ceil(entries.length * 0.2));
      for (const [modulePath] of toEvict) {
        this.cache.delete(modulePath);
        this.metadata.delete(modulePath);
      }
    }
  }

  // 预热缓存
  async warmUp(modulePaths: string[]): Promise<void> {
    const promises = modulePaths.map(path => this.getModule(path));
    await Promise.allSettled(promises);
  }

  // 清理缓存
  clear(): void {
    this.cache.clear();
    this.metadata.clear();
  }
}

// 全局缓存实例
export const moduleCache = new ModuleCacheManager();

性能监控与优化

1. 加载性能监控

// 模块加载性能监控
class ModulePerformanceMonitor {
  private metrics = new Map<string, {
    loadTime: number;
    loadCount: number;
    errors: number;
    averageLoadTime: number;
  }>();

  async monitorLoad<T>(modulePath: string, loader: () => Promise<T>): Promise<T> {
    const startTime = performance.now();
    
    try {
      const result = await loader();
      const loadTime = performance.now() - startTime;
      
      this.recordSuccess(modulePath, loadTime);
      return result;
    } catch (error) {
      this.recordError(modulePath);
      throw error;
    }
  }

  private recordSuccess(modulePath: string, loadTime: number): void {
    const existing = this.metrics.get(modulePath);
    
    if (existing) {
      existing.loadCount++;
      existing.averageLoadTime = (existing.averageLoadTime * (existing.loadCount - 1) + loadTime) / existing.loadCount;
    } else {
      this.metrics.set(modulePath, {
        loadTime,
        loadCount: 1,
        errors: 0,
        averageLoadTime: loadTime
      });
    }
  }

  private recordError(modulePath: string): void {
    const existing = this.metrics.get(modulePath);
    if (existing) {
      existing.errors++;
    } else {
      this.metrics.set(modulePath, {
        loadTime: 0,
        loadCount: 0,
        errors: 1,
        averageLoadTime: 0
      });
    }
  }

  getReport(): Record<string, any> {
    const report: Record<string, any> = {};
    
    for (const [modulePath, metrics] of this.metrics) {
      report[modulePath] = {
        ...metrics,
        successRate: metrics.loadCount / (metrics.loadCount + metrics.errors)
      };
    }
    
    return report;
  }

  // 找出性能瓶颈
  getSlowModules(threshold: number = 1000): string[] {
    return Array.from(this.metrics.entries())
      .filter(([, metrics]) => metrics.averageLoadTime > threshold)
      .map(([modulePath]) => modulePath);
  }
}

// 使用示例
const monitor = new ModulePerformanceMonitor();

async function loadModuleWithMonitoring(modulePath: string) {
  return monitor.monitorLoad(modulePath, () => import(modulePath));
}

2. 自适应加载策略

// 自适应模块加载器
class AdaptiveModuleLoader {
  private networkSpeed: 'slow' | 'medium' | 'fast' = 'medium';
  private userEngagement: 'low' | 'medium' | 'high' = 'medium';

  constructor() {
    this.detectNetworkSpeed();
    this.trackUserEngagement();
  }

  async loadModule<T>(
    modulePath: string, 
    options: {
      priority?: 'low' | 'medium' | 'high';
      fallback?: () => Promise<T>;
    } = {}
  ): Promise<T> {
    const strategy = this.getLoadingStrategy(options.priority);
    
    try {
      switch (strategy) {
        case 'immediate':
          return await import(modulePath);
          
        case 'deferred':
          await this.delay(500); // 延迟加载
          return await import(modulePath);
          
        case 'conditional':
          if (this.shouldLoadModule()) {
            return await import(modulePath);
          } else if (options.fallback) {
            return await options.fallback();
          }
          throw new Error('Module loading skipped');
          
        default:
          return await import(modulePath);
      }
    } catch (error) {
      if (options.fallback) {
        return await options.fallback();
      }
      throw error;
    }
  }

  private getLoadingStrategy(priority?: 'low' | 'medium' | 'high'): 'immediate' | 'deferred' | 'conditional' {
    // 高优先级模块立即加载
    if (priority === 'high') return 'immediate';
    
    // 慢网络下延迟加载低优先级模块
    if (this.networkSpeed === 'slow' && priority === 'low') {
      return 'conditional';
    }
    
    // 低参与度用户延迟加载
    if (this.userEngagement === 'low') {
      return 'deferred';
    }
    
    return 'immediate';
  }

  private detectNetworkSpeed(): void {
    if ('connection' in navigator) {
      const connection = (navigator as any).connection;
      const effectiveType = connection.effectiveType;
      
      if (effectiveType === '4g') {
        this.networkSpeed = 'fast';
      } else if (effectiveType === '3g') {
        this.networkSpeed = 'medium';
      } else {
        this.networkSpeed = 'slow';
      }
    }
  }

  private trackUserEngagement(): void {
    let interactionCount = 0;
    const startTime = Date.now();

    ['click', 'keydown', 'scroll'].forEach(event => {
      document.addEventListener(event, () => {
        interactionCount++;
      });
    });

    // 5秒后评估用户参与度
    setTimeout(() => {
      const timeSpent = Date.now() - startTime;
      const engagementScore = interactionCount / (timeSpent / 1000);
      
      if (engagementScore > 2) {
        this.userEngagement = 'high';
      } else if (engagementScore > 0.5) {
        this.userEngagement = 'medium';
      } else {
        this.userEngagement = 'low';
      }
    }, 5000);
  }

  private shouldLoadModule(): boolean {
    return this.networkSpeed !== 'slow' && this.userEngagement !== 'low';
  }

  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// 使用示例
const adaptiveLoader = new AdaptiveModuleLoader();

// 高优先级模块(立即加载)
const criticalModule = await adaptiveLoader.loadModule('./critical-module.js', {
  priority: 'high'
});

// 低优先级模块(可能延迟或跳过)
const optionalModule = await adaptiveLoader.loadModule('./optional-module.js', {
  priority: 'low',
  fallback: () => Promise.resolve({ default: () => 'Fallback content' })
});

构建工具集成

1. Webpack代码分割

// webpack.config.js
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        // 供应商代码分割
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
        // 异步代码分割
        async: {
          chunks: 'async',
          minSize: 30000,
          maxSize: 244000,
        },
        // 公共代码分割
        common: {
          name: 'common',
          minChunks: 2,
          chunks: 'all',
          enforce: true
        }
      }
    }
  },
  
  // 动态导入支持
  plugins: [
    new webpack.optimize.SplitChunksPlugin({
      chunks: 'async',
      name: (module, chunks, cacheGroupKey) => {
        const moduleFileName = module
          .identifier()
          .split('/')
          .reduceRight(item => item);
        return `${cacheGroupKey}-${moduleFileName}`;
      }
    })
  ]
};

2. Rollup懒加载配置

// rollup.config.js
export default {
  input: 'src/main.js',
  output: {
    dir: 'dist',
    format: 'es',
    // 动态导入作为单独的chunk
    chunkFileNames: 'chunks/[name]-[hash].js'
  },
  plugins: [
    // 代码分割插件
    resolve(),
    commonjs(),
    // 自定义动态导入处理
    {
      name: 'dynamic-import-handler',
      generateBundle(options, bundle) {
        // 处理动态导入的chunk
        Object.keys(bundle).forEach(fileName => {
          const chunk = bundle[fileName];
          if (chunk.type === 'chunk' && chunk.isDynamicEntry) {
            // 添加预加载提示
            chunk.code = `/* webpackPreload: true */ ${chunk.code}`;
          }
        });
      }
    }
  ]
};

实际应用案例

1. 电商网站的懒加载策略

// 电商网站模块懒加载方案
class EcommerceModuleLoader {
  private moduleCache = new Map();

  // 商品列表页面的懒加载
  async loadProductCatalog() {
    const modules = await Promise.allSettled([
      import('./components/ProductGrid'),
      import('./components/FilterPanel'),
      import('./components/SortDropdown')
    ]);

    return {
      ProductGrid: modules[0].status === 'fulfilled' ? modules[0].value.default : null,
      FilterPanel: modules[1].status === 'fulfilled' ? modules[1].value.default : null,
      SortDropdown: modules[2].status === 'fulfilled' ? modules[2].value.default : null
    };
  }

  // 购物车相关功能的懒加载
  async loadCartFeatures() {
    return import('./features/cart').then(module => ({
      Cart: module.Cart,
      CartItem: module.CartItem,
      CartSummary: module.CartSummary,
      useCart: module.useCart
    }));
  }

  // 支付流程的懒加载
  async loadPaymentFlow(paymentMethod: string) {
    switch (paymentMethod) {
      case 'stripe':
        return import('./payment/StripePayment');
      case 'paypal':
        return import('./payment/PayPalPayment');
      case 'alipay':
        return import('./payment/AlipayPayment');
      default:
        return import('./payment/DefaultPayment');
    }
  }

  // 管理后台功能的懒加载
  async loadAdminFeatures(userRole: string) {
    if (userRole !== 'admin') return null;

    const [
      { AdminDashboard },
      { ProductManager },
      { OrderManager },
      { UserManager }
    ] = await Promise.all([
      import('./admin/Dashboard'),
      import('./admin/ProductManager'),
      import('./admin/OrderManager'),
      import('./admin/UserManager')
    ]);

    return {
      AdminDashboard,
      ProductManager,
      OrderManager,
      UserManager
    };
  }
}

// 使用示例
const moduleLoader = new EcommerceModuleLoader();

// 页面级别的懒加载
function ProductPage() {
  const [modules, setModules] = useState(null);

  useEffect(() => {
    moduleLoader.loadProductCatalog().then(setModules);
  }, []);

  if (!modules) return <div>Loading...</div>;

  return (
    <div>
      {modules.ProductGrid && <modules.ProductGrid />}
      {modules.FilterPanel && <modules.FilterPanel />}
      {modules.SortDropdown && <modules.SortDropdown />}
    </div>
  );
}

2. 多语言网站的懒加载

// 多语言资源懒加载
class I18nLazyLoader {
  private loadedLanguages = new Set<string>();
  private languageCache = new Map<string, any>();

  async loadLanguage(locale: string): Promise<any> {
    // 检查缓存
    if (this.languageCache.has(locale)) {
      return this.languageCache.get(locale);
    }

    // 检查是否已在加载中
    if (this.loadedLanguages.has(locale)) {
      return new Promise(resolve => {
        const checkCache = () => {
          if (this.languageCache.has(locale)) {
            resolve(this.languageCache.get(locale));
          } else {
            setTimeout(checkCache, 50);
          }
        };
        checkCache();
      });
    }

    this.loadedLanguages.add(locale);

    try {
      // 动态加载语言包
      const translations = await import(`./locales/${locale}.json`);
      this.languageCache.set(locale, translations.default);
      return translations.default;
    } catch (error) {
      // 降级到英语
      if (locale !== 'en') {
        return this.loadLanguage('en');
      }
      throw error;
    }
  }

  // 预加载用户可能需要的语言
  async preloadLanguages(preferredLanguages: string[]): Promise<void> {
    const promises = preferredLanguages.map(lang => 
      this.loadLanguage(lang).catch(() => null) // 忽略加载失败
    );
    await Promise.allSettled(promises);
  }

  // 智能语言切换
  async switchLanguage(locale: string): Promise<any> {
    const translations = await this.loadLanguage(locale);
    
    // 更新页面语言
    document.documentElement.lang = locale;
    
    // 触发语言更新事件
    window.dispatchEvent(new CustomEvent('languageChanged', {
      detail: { locale, translations }
    }));

    return translations;
  }
}

// React Hook for i18n lazy loading
function useI18nLazy(locale: string) {
  const [translations, setTranslations] = useState(null);
  const [loading, setLoading] = useState(false);
  const loader = useRef(new I18nLazyLoader());

  useEffect(() => {
    setLoading(true);
    loader.current.loadLanguage(locale)
      .then(setTranslations)
      .finally(() => setLoading(false));
  }, [locale]);

  const switchLanguage = useCallback((newLocale: string) => {
    return loader.current.switchLanguage(newLocale);
  }, []);

  return { translations, loading, switchLanguage };
}

最佳实践总结

1. 懒加载策略选择

  • 路由级别:适合页面较多的应用
  • 组件级别:适合复杂的交互组件
  • 功能级别:适合可选功能模块
  • 资源级别:适合大型依赖库

2. 性能优化技巧

  • 预加载:基于用户行为预测需要的模块
  • 缓存策略:合理使用内存和持久化缓存
  • 降级方案:提供加载失败时的备选方案
  • 监控指标:跟踪加载性能和成功率

3. 用户体验考虑

  • 加载指示器:提供清晰的加载状态反馈
  • 渐进增强:确保核心功能在模块加载前可用
  • 错误处理:优雅处理加载失败的情况
  • 无感知切换:尽量减少用户等待时间

4. 开发工具集成

  • 构建优化:配置合适的代码分割策略
  • 开发调试:使用开发工具监控模块加载
  • 测试策略:模拟不同网络条件进行测试
  • 性能监控:建立完善的性能监控体系

通过合理应用这些懒加载技术,我们可以显著提升应用的性能和用户体验,同时保持代码的可维护性。


下一章: 模块相关工具对比

模块相关工具对比

本章将全面对比各种JavaScript模块化相关的工具,包括打包工具、转译工具、运行时环境和开发工具,帮助开发者根据项目需求选择合适的工具链。

打包工具对比

主流打包工具概览

工具类型主要特点适用场景学习曲线
Webpack模块打包器功能强大、生态丰富、配置复杂大型项目、复杂需求陡峭
RollupES模块打包器体积小、Tree Shaking优秀库开发、现代项目中等
Parcel零配置打包器开箱即用、自动优化快速原型、中小项目平缓
Vite现代构建工具快速启动、HMR优秀现代前端项目平缓
esbuild高性能打包器极快速度、Go语言编写开发构建、CI/CD中等
SWCRust编译器极快编译、Babel替代大型项目编译中等

详细对比分析

1. Webpack

// webpack.config.js - 复杂但强大的配置
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
    clean: true
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: 'babel-loader'
      },
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource'
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css'
    })
  ],
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all'
        }
      }
    }
  }
};

优势:

  • 生态系统最成熟,插件和loader丰富
  • 支持各种资源类型(JS、CSS、图片、字体等)
  • 强大的代码分割和优化功能
  • 广泛的社区支持和文档

劣势:

  • 配置复杂,学习曲线陡峭
  • 构建速度相对较慢
  • 配置文件可能变得非常复杂

适用场景:

  • 大型复杂项目
  • 需要精细控制构建过程
  • 遗留项目迁移

2. Rollup

// rollup.config.js - 简洁专注的配置
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel';
import terser from '@rollup/plugin-terser';

export default {
  input: 'src/main.js',
  output: [
    {
      file: 'dist/bundle.cjs.js',
      format: 'cjs'
    },
    {
      file: 'dist/bundle.esm.js',
      format: 'es'
    },
    {
      file: 'dist/bundle.umd.js',
      format: 'umd',
      name: 'MyLibrary'
    }
  ],
  plugins: [
    resolve(),
    commonjs(),
    babel({
      babelHelpers: 'bundled',
      exclude: 'node_modules/**'
    }),
    terser()
  ],
  external: ['lodash'] // 排除外部依赖
};

优势:

  • 优秀的Tree Shaking,生成的包体积小
  • 原生支持ES模块
  • 配置相对简单
  • 适合库开发

劣势:

  • 对CSS、图片等非JS资源支持有限
  • 插件生态相比Webpack较小
  • 不适合复杂的应用开发

适用场景:

  • JavaScript库开发
  • 现代ES模块项目
  • 需要最小化包体积的项目

3. Vite

// vite.config.js - 现代化的配置
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';

export default defineConfig({
  plugins: [vue()],
  build: {
    lib: {
      entry: resolve(__dirname, 'src/main.ts'),
      name: 'MyLib',
      fileName: 'my-lib'
    },
    rollupOptions: {
      external: ['vue'],
      output: {
        globals: {
          vue: 'Vue'
        }
      }
    }
  },
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src')
    }
  },
  server: {
    hmr: true,
    port: 3000
  }
});

优势:

  • 开发服务器启动极快
  • 优秀的热模块重载(HMR)
  • 基于Rollup,生产构建优化好
  • 开箱即用的TypeScript支持

劣势:

  • 相对较新,生态还在发展
  • 某些复杂场景可能需要额外配置
  • 主要针对现代浏览器

适用场景:

  • 现代前端项目(Vue、React、Svelte)
  • 需要快速开发体验的项目
  • TypeScript项目

4. esbuild

// esbuild配置 - 极简高效
const esbuild = require('esbuild');

esbuild.build({
  entryPoints: ['src/index.js'],
  bundle: true,
  outfile: 'dist/bundle.js',
  minify: true,
  sourcemap: true,
  target: ['es2015'],
  loader: {
    '.png': 'dataurl',
    '.svg': 'text'
  },
  define: {
    'process.env.NODE_ENV': '"production"'
  }
}).catch(() => process.exit(1));

优势:

  • 构建速度极快(Go语言编写)
  • 内置TypeScript支持
  • 零配置即可使用
  • 体积小,依赖少

劣势:

  • 功能相对简单
  • 插件生态有限
  • 不支持某些高级特性(如装饰器)

适用场景:

  • 需要极快构建速度的项目
  • 简单的打包需求
  • CI/CD流水线

构建性能对比

工具冷启动时间热更新时间构建时间内存占用
Webpack10-30s1-3s30-120s
Rollup5-15s2-5s15-60s
Vite1-3s<1s10-40s
esbuild<1s<1s5-20s
Parcel5-20s1-2s20-80s

注:时间基于中等规模项目的大致估算

转译工具对比

Babel vs SWC vs esbuild

1. Babel

// babel.config.js - 灵活强大的转译配置
module.exports = {
  presets: [
    ['@babel/preset-env', {
      targets: {
        browsers: ['> 1%', 'last 2 versions']
      },
      useBuiltIns: 'usage',
      corejs: 3
    }],
    '@babel/preset-typescript',
    '@babel/preset-react'
  ],
  plugins: [
    '@babel/plugin-proposal-class-properties',
    '@babel/plugin-proposal-decorators',
    ['@babel/plugin-transform-runtime', {
      corejs: 3,
      helpers: true,
      regenerator: true
    }]
  ]
};

优势:

  • 插件生态最丰富
  • 支持最新和实验性语法
  • 高度可配置
  • 社区支持最好

劣势:

  • 转译速度相对较慢
  • 配置复杂
  • 输出代码可能冗余

2. SWC

// .swcrc - 高性能的Rust转译器
{
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "tsx": true,
      "decorators": true
    },
    "transform": {
      "react": {
        "pragma": "React.createElement",
        "pragmaFrag": "React.Fragment",
        "throwIfNamespace": true
      }
    },
    "target": "es2015"
  },
  "module": {
    "type": "es6"
  },
  "minify": true
}

优势:

  • 转译速度极快(Rust编写)
  • 内存使用更少
  • 支持大部分Babel功能
  • 可作为Webpack loader使用

劣势:

  • 插件生态相对较小
  • 某些高级功能可能不支持
  • 错误信息不如Babel详细

3. TypeScript编译器

// tsconfig.json - 官方TypeScript编译器
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "node",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

转译工具性能对比:

工具转译速度类型检查插件支持学习成本
Babel⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
SWC⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
esbuild⭐⭐⭐⭐⭐⭐⭐⭐⭐
TypeScript⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

运行时环境对比

Node.js vs Deno vs Bun

1. Node.js

// package.json - 传统的Node.js项目
{
  "name": "nodejs-project",
  "type": "module", // 启用ES模块
  "engines": {
    "node": ">=18.0.0"
  },
  "scripts": {
    "start": "node src/index.js",
    "dev": "node --watch src/index.js"
  },
  "dependencies": {
    "express": "^4.18.0",
    "lodash": "^4.17.21"
  }
}

// src/index.js
import express from 'express';
import { readFile } from 'fs/promises';

const app = express();
const port = process.env.PORT || 3000;

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

2. Deno

// deno.json - 现代的Deno配置
{
  "tasks": {
    "start": "deno run --allow-net --allow-read src/main.ts",
    "dev": "deno run --allow-net --allow-read --watch src/main.ts"
  },
  "imports": {
    "express": "npm:express@^4.18.0",
    "std/": "https://deno.land/std@0.200.0/"
  }
}

// src/main.ts - 无需package.json
import { serve } from "std/http/server.ts";
import express from "express";

const handler = (req: Request): Response => {
  return new Response("Hello from Deno!");
};

serve(handler, { port: 3000 });

3. Bun

// bun项目结构更简洁
// package.json
{
  "name": "bun-project",
  "scripts": {
    "start": "bun run src/index.ts",
    "dev": "bun --watch src/index.ts"
  },
  "dependencies": {
    "@types/bun": "latest"
  }
}

// src/index.ts - 内置TypeScript支持
import { serve } from "bun";

serve({
  port: 3000,
  fetch(req) {
    return new Response("Hello from Bun!");
  },
});

console.log("Server running on http://localhost:3000");

运行时特性对比

特性Node.jsDenoBun
启动速度中等最快
包管理npm/yarn/pnpm内置/npm兼容内置
TypeScript需要转译原生支持原生支持
安全性完全权限默认安全完全权限
生态系统最丰富发展中发展中
Web标准部分支持完整支持部分支持
性能最好

模块解析对比

// Node.js - CommonJS/ESM混合
// CommonJS方式
const lodash = require('lodash');
module.exports = { utils: lodash };

// ESM方式 (需要"type": "module")
import lodash from 'lodash';
export { lodash as utils };

// Deno - 基于URL的模块系统
import { serve } from "https://deno.land/std@0.200.0/http/server.ts";
import lodash from "npm:lodash@4.17.21";

// Bun - 兼容Node.js + 增强
import lodash from 'lodash';
import { file } from 'bun'; // Bun特有API

开发工具对比

包管理器对比

npm vs yarn vs pnpm

特性npmYarn ClassicYarn Berrypnpm
安装速度中等最快
磁盘使用最低
Monorepo支持基础优秀优秀
离线安装有限支持支持支持
严格性宽松宽松严格严格
PnP支持

性能基准测试

# 安装时间测试 (React项目)
npm install     # ~45s
yarn install    # ~35s
pnpm install    # ~20s
bun install     # ~15s

# 磁盘使用测试 (node_modules大小)
npm:  150MB
yarn: 145MB  
pnpm: 50MB (硬链接)

测试框架对比

Jest vs Vitest vs Playwright

// Jest配置 - 成熟稳定
// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
  moduleNameMapping: {
    '^@/(.*)$': '<rootDir>/src/$1'
  },
  collectCoverageFrom: [
    'src/**/*.{js,jsx,ts,tsx}',
    '!src/index.js'
  ]
};

// Vitest配置 - 快速现代
// vitest.config.js
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    environment: 'jsdom',
    setupFiles: ['./src/setupTests.js'],
    coverage: {
      reporter: ['text', 'html']
    }
  }
});

// Playwright配置 - E2E测试
// playwright.config.js
module.exports = {
  testDir: './tests',
  timeout: 30000,
  retries: 2,
  use: {
    browserName: 'chromium',
    headless: true,
    screenshot: 'only-on-failure'
  },
  projects: [
    { name: 'Chrome', use: { ...devices['Desktop Chrome'] } },
    { name: 'Firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'Safari', use: { ...devices['Desktop Safari'] } }
  ]
};

Linting工具对比

ESLint vs Rome vs Biome

// ESLint配置 - 功能最全
// .eslintrc.js
module.exports = {
  extends: [
    'eslint:recommended',
    '@typescript-eslint/recommended',
    'plugin:react/recommended'
  ],
  parser: '@typescript-eslint/parser',
  plugins: ['@typescript-eslint', 'react'],
  rules: {
    'no-unused-vars': 'error',
    '@typescript-eslint/no-explicit-any': 'warn'
  }
};

// Biome配置 - 现代化工具
// biome.json
{
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "style": {
        "noUnusedImports": "error"
      }
    }
  },
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentSize": 2
  }
}

选型建议

基于项目类型的推荐

1. 小型项目/原型开发

// 推荐工具链
{
  "bundler": "Vite",
  "runtime": "Node.js/Bun", 
  "packageManager": "pnpm",
  "testing": "Vitest",
  "linting": "Biome"
}

2. 中型Web应用

// 推荐工具链
{
  "bundler": "Vite/Webpack",
  "transpiler": "SWC/Babel",
  "runtime": "Node.js",
  "packageManager": "pnpm/yarn",
  "testing": "Jest/Vitest",
  "linting": "ESLint + Prettier"
}

3. 大型企业项目

// 推荐工具链
{
  "bundler": "Webpack",
  "transpiler": "Babel/SWC",
  "runtime": "Node.js",
  "packageManager": "yarn/pnpm",
  "testing": "Jest + Playwright",
  "linting": "ESLint + Prettier",
  "monorepo": "Lerna/Nx"
}

4. 库开发

// 推荐工具链
{
  "bundler": "Rollup",
  "transpiler": "Babel/SWC",
  "testing": "Jest/Vitest",
  "packaging": "多格式输出(CJS/ESM/UMD)"
}

性能优先的选择

// 最快构建速度
{
  "bundler": "esbuild",
  "transpiler": "esbuild",
  "runtime": "Bun",
  "packageManager": "bun/pnpm"
}

// 最佳开发体验
{
  "bundler": "Vite",
  "transpiler": "SWC",
  "runtime": "Node.js",
  "packageManager": "pnpm",
  "testing": "Vitest"
}

决策矩阵

需求优先考虑次要选择不推荐
快速开发Vite + VitestParcelWebpack
最小包体积Rollup + TerseresbuildParcel
复杂配置WebpackRollupParcel
库开发RollupWebpackVite
性能极致esbuild + SWCRollupWebpack
企业级Webpack + JestVite + Vitestesbuild

迁移指南

从Webpack迁移到Vite

// webpack.config.js (迁移前)
module.exports = {
  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  module: {
    rules: [
      { test: /\.vue$/, loader: 'vue-loader' },
      { test: /\.css$/, use: ['style-loader', 'css-loader'] }
    ]
  },
  plugins: [new VueLoaderPlugin()]
};

// vite.config.js (迁移后)
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
  build: {
    outDir: 'dist'
  }
});

从npm迁移到pnpm

# 1. 删除node_modules和package-lock.json
rm -rf node_modules package-lock.json

# 2. 安装pnpm
npm install -g pnpm

# 3. 安装依赖
pnpm install

# 4. 更新脚本
# package.json中将npm替换为pnpm

总结

工具选择的关键因素

  1. 项目规模:小项目选择简单工具,大项目选择功能全面的工具
  2. 团队技能:考虑团队的学习成本和维护能力
  3. 性能要求:开发速度vs构建速度vs运行性能
  4. 生态系统:插件支持、社区活跃度、文档质量
  5. 长期维护:工具的稳定性和发展前景

趋势预测

  • 编译速度:Rust/Go工具将更多替代JavaScript工具
  • 零配置:开箱即用的工具将更受欢迎
  • 原生支持:TypeScript、ESM的原生支持将成为标配
  • 统一工具链:集成度更高的工具链将简化开发流程

选择工具时,应该根据具体项目需求和团队情况,权衡各种因素,而不是盲目追求最新或最快的工具。


下一章: 常见问题解答

常见问题解答

本章汇总了JavaScript模块化开发中最常遇到的问题和解决方案,帮助开发者快速解决实际开发中的困惑。

基础概念问题

Q1: ES模块和CommonJS有什么区别?

A: 主要区别包括:

特性ES模块 (ESM)CommonJS (CJS)
语法import/exportrequire()/module.exports
加载时机编译时静态分析运行时动态加载
Tree Shaking支持不支持
循环依赖更好处理可能有问题
顶层await支持不支持
浏览器支持原生支持需要打包
// ES模块
import { functionA } from './moduleA.js';
export const functionB = () => {};

// CommonJS
const { functionA } = require('./moduleA.js');
module.exports = { functionB: () => {} };

Q2: 什么时候使用动态导入?

A: 动态导入适用于以下场景:

// 1. 代码分割和懒加载
const loadChart = async () => {
  const { Chart } = await import('./chart-library.js');
  return new Chart();
};

// 2. 条件性模块加载
if (user.hasFeature('advanced')) {
  const advanced = await import('./advanced-features.js');
  advanced.initialize();
}

// 3. 运行时模块选择
const getTranslator = async (language) => {
  return await import(`./translators/${language}.js`);
};

// 4. 降级和错误处理
try {
  const modernModule = await import('./modern-features.js');
  return modernModule.default;
} catch {
  const fallback = await import('./fallback.js');
  return fallback.default;
}

Q3: 如何解决循环依赖问题?

A: 解决循环依赖的几种方法:

方法1: 重新设计模块结构

// 问题:A依赖B,B依赖A
// moduleA.js
import { functionB } from './moduleB.js';
export const functionA = () => functionB();

// moduleB.js  
import { functionA } from './moduleA.js'; // 循环依赖
export const functionB = () => functionA();

// 解决:提取公共依赖
// shared.js
export const sharedFunction = () => {};

// moduleA.js
import { sharedFunction } from './shared.js';
export const functionA = () => sharedFunction();

// moduleB.js
import { sharedFunction } from './shared.js';
export const functionB = () => sharedFunction();

方法2: 使用依赖注入

// moduleA.js
export class ServiceA {
  constructor(serviceB) {
    this.serviceB = serviceB;
  }
  
  doSomething() {
    return this.serviceB.helper();
  }
}

// moduleB.js
export class ServiceB {
  constructor(serviceA) {
    this.serviceA = serviceA;
  }
  
  helper() {
    return 'result';
  }
}

// container.js
import { ServiceA } from './moduleA.js';
import { ServiceB } from './moduleB.js';

const serviceB = new ServiceB();
const serviceA = new ServiceA(serviceB);
serviceB.serviceA = serviceA; // 如果需要的话

Q4: Tree Shaking不工作怎么办?

A: 确保Tree Shaking正常工作的检查清单:

// 1. 使用ES模块语法
// ✅ 正确
export const functionA = () => {};
export const functionB = () => {};

// ❌ 错误 - 会导致整个对象被包含
export default {
  functionA: () => {},
  functionB: () => {}
};

// 2. 避免副作用
// ❌ 有副作用的模块
console.log('Module loaded'); // 副作用
export const pureFunction = () => {};

// ✅ 纯模块
export const pureFunction = () => {};

// 3. 正确配置package.json
{
  "name": "my-package",
  "sideEffects": false, // 标记为无副作用
  // 或者指定有副作用的文件
  "sideEffects": ["./src/polyfills.js", "*.css"]
}

// 4. 打包工具配置
// webpack.config.js
module.exports = {
  mode: 'production', // 启用Tree Shaking
  optimization: {
    usedExports: true,
    sideEffects: false
  }
};

构建和工具问题

Q5: 模块解析失败怎么办?

A: 常见的模块解析问题和解决方案:

// 1. 文件扩展名问题
// ❌ 错误
import utils from './utils'; // 缺少扩展名

// ✅ 正确
import utils from './utils.js';

// 2. 相对路径问题
// ❌ 错误
import utils from 'utils'; // 应该是相对路径

// ✅ 正确
import utils from './utils.js';
import utils from '../shared/utils.js';

// 3. Node.js模块解析配置
// package.json
{
  "type": "module", // 启用ES模块
  "main": "./dist/index.cjs.js",
  "module": "./dist/index.esm.js",
  "exports": {
    ".": {
      "import": "./dist/index.esm.js",
      "require": "./dist/index.cjs.js"
    }
  }
}

// 4. TypeScript路径映射
// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@/*": ["*"],
      "@/components/*": ["components/*"]
    }
  }
}

Q6: 如何调试模块加载问题?

A: 调试模块加载的方法:

// 1. 使用浏览器开发者工具
// 在Network面板查看模块加载情况
// 在Sources面板设置断点

// 2. Node.js调试
// 启用模块加载跟踪
node --trace-warnings --experimental-loader ./my-loader.js app.js

// 3. 添加调试日志
const originalImport = window.__import__ || import;
window.__import__ = function(specifier) {
  console.log('Loading module:', specifier);
  return originalImport(specifier)
    .then(module => {
      console.log('Module loaded:', specifier, module);
      return module;
    })
    .catch(error => {
      console.error('Module load failed:', specifier, error);
      throw error;
    });
};

// 4. 使用Webpack Bundle Analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'server',
      openAnalyzer: true
    })
  ]
};

Q7: 如何优化构建性能?

A: 构建性能优化策略:

// 1. 启用并行构建
// webpack.config.js
const os = require('os');

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          {
            loader: 'thread-loader',
            options: {
              workers: os.cpus().length - 1
            }
          },
          'babel-loader'
        ]
      }
    ]
  }
};

// 2. 缓存配置
module.exports = {
  cache: {
    type: 'filesystem',
    cacheDirectory: path.resolve(__dirname, '.webpack-cache')
  }
};

// 3. 减少解析范围
module.exports = {
  resolve: {
    modules: [path.resolve(__dirname, 'src'), 'node_modules'],
    extensions: ['.js', '.jsx'] // 只包含必要的扩展名
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        include: path.resolve(__dirname, 'src'), // 限制处理范围
        exclude: /node_modules/
      }
    ]
  }
};

// 4. 使用更快的工具
// vite.config.js
import { defineConfig } from 'vite';

export default defineConfig({
  esbuild: {
    target: 'es2020'
  },
  optimizeDeps: {
    include: ['react', 'react-dom']
  }
});

兼容性问题

Q8: 如何在Node.js中同时支持ESM和CommonJS?

A: 创建双模式包的方法:

// 1. 使用条件导出
// package.json
{
  "name": "my-package",
  "main": "./dist/index.cjs",
  "module": "./dist/index.mjs",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs"
    }
  }
}

// 2. 源码示例
// src/index.js (共同源码)
export function myFunction() {
  return 'Hello World';
}

// 构建脚本生成两个版本
// dist/index.mjs (ESM版本)
export function myFunction() {
  return 'Hello World';
}

// dist/index.cjs (CommonJS版本)
function myFunction() {
  return 'Hello World';
}
module.exports = { myFunction };

// 3. 动态检测环境
// utils.js
const isESM = typeof module === 'undefined';

if (isESM) {
  // ESM环境
  export { myFunction };
} else {
  // CommonJS环境  
  module.exports = { myFunction };
}

Q9: 如何处理第三方库的模块兼容性?

A: 处理第三方库兼容性问题:

// 1. 使用打包工具转换
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /node_modules\/problematic-package/,
        use: 'babel-loader'
      }
    ]
  }
};

// 2. 创建包装模块
// wrappers/old-library.js
import oldLibrary from 'old-commonjs-library';

// 包装成ESM接口
export const { functionA, functionB } = oldLibrary;
export default oldLibrary;

// 3. 使用动态导入处理异步加载
const loadLibrary = async () => {
  try {
    // 尝试ESM版本
    return await import('modern-library');
  } catch {
    // 降级到CommonJS版本
    const lib = await import('old-library');
    return lib.default || lib;
  }
};

// 4. 配置模块解析别名
// webpack.config.js
module.exports = {
  resolve: {
    alias: {
      'problematic-lib': path.resolve(__dirname, 'src/wrappers/problematic-lib.js')
    }
  }
};

Q10: 如何在浏览器中处理模块兼容性?

A: 浏览器模块兼容性处理:

<!-- 1. 现代浏览器和旧浏览器分别处理 -->
<script type="module" src="modern.js"></script>
<script nomodule src="legacy.js"></script>

<!-- 2. 动态检测和加载 -->
<script>
  if ('noModule' in HTMLScriptElement.prototype) {
    // 支持ES模块的现代浏览器
    import('./modern-app.js').then(app => app.init());
  } else {
    // 旧浏览器加载打包后的版本
    const script = document.createElement('script');
    script.src = 'legacy-app.js';
    document.head.appendChild(script);
  }
</script>

<!-- 3. 使用importmap处理模块映射 -->
<script type="importmap">
{
  "imports": {
    "react": "https://esm.sh/react@18",
    "react-dom": "https://esm.sh/react-dom@18"
  }
}
</script>
<script type="module">
  import React from 'react';
  import ReactDOM from 'react-dom';
</script>

性能优化问题

Q11: 如何减少模块加载时间?

A: 模块加载性能优化:

// 1. 预加载关键模块
// 在HTML中预加载
<link rel="modulepreload" href="./critical-module.js">

// 在JavaScript中预加载
const preloadModule = (url) => {
  const link = document.createElement('link');
  link.rel = 'modulepreload';
  link.href = url;
  document.head.appendChild(link);
};

// 2. 代码分割优化
// 路由级分割
const HomePage = lazy(() => import('./pages/HomePage'));
const AboutPage = lazy(() => import('./pages/AboutPage'));

// 功能级分割
const loadFeature = async (featureName) => {
  const features = {
    charts: () => import('./features/charts'),
    editor: () => import('./features/editor')
  };
  return features[featureName]?.();
};

// 3. 打包优化
// webpack.config.js
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
        common: {
          minChunks: 2,
          chunks: 'all',
          enforce: true
        }
      }
    }
  }
};

// 4. 使用CDN和缓存
// 配置长期缓存
module.exports = {
  output: {
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].chunk.js'
  }
};

Q12: 如何监控模块加载性能?

A: 模块性能监控方法:

// 1. 使用Performance API
const modulePerformance = new Map();

const originalImport = window.import || import;
window.import = function(specifier) {
  const startTime = performance.now();
  
  return originalImport(specifier).then(module => {
    const loadTime = performance.now() - startTime;
    modulePerformance.set(specifier, {
      loadTime,
      timestamp: Date.now(),
      size: JSON.stringify(module).length
    });
    
    // 发送性能数据
    if (window.analytics) {
      window.analytics.track('module_loaded', {
        module: specifier,
        loadTime,
        size: JSON.stringify(module).length
      });
    }
    
    return module;
  });
};

// 2. 监控Core Web Vitals
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.entryType === 'largest-contentful-paint') {
      console.log('LCP:', entry.startTime);
    }
  }
});

observer.observe({ entryTypes: ['largest-contentful-paint'] });

// 3. 模块加载瀑布图分析
const analyzeModuleLoading = () => {
  const entries = performance.getEntriesByType('resource');
  const moduleEntries = entries.filter(entry => 
    entry.name.includes('.js') || entry.name.includes('.mjs')
  );
  
  console.table(moduleEntries.map(entry => ({
    name: entry.name,
    duration: entry.duration,
    transferSize: entry.transferSize,
    startTime: entry.startTime
  })));
};

调试和测试问题

Q13: 如何测试模块化代码?

A: 模块化代码测试策略:

// 1. 单元测试
// math.js
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;

// math.test.js
import { add, multiply } from './math.js';

describe('Math utilities', () => {
  test('add function', () => {
    expect(add(2, 3)).toBe(5);
  });
  
  test('multiply function', () => {
    expect(multiply(2, 3)).toBe(6);
  });
});

// 2. 模拟(Mock)外部依赖
// api.js
export const fetchUser = async (id) => {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
};

// userService.test.js
import { fetchUser } from './api.js';
jest.mock('./api.js');

const mockFetchUser = fetchUser as jest.MockedFunction<typeof fetchUser>;

test('user service', async () => {
  mockFetchUser.mockResolvedValue({ id: 1, name: 'John' });
  
  const user = await fetchUser(1);
  expect(user.name).toBe('John');
});

// 3. 测试动态导入
test('dynamic import', async () => {
  const module = await import('./utils.js');
  expect(typeof module.utilFunction).toBe('function');
});

// 4. 集成测试
// app.test.js
import { JSDOM } from 'jsdom';

const dom = new JSDOM('<!DOCTYPE html><div id="app"></div>');
global.window = dom.window;
global.document = window.document;

import('./app.js').then(app => {
  app.init();
  // 测试应用初始化后的状态
});

Q14: 如何处理模块中的错误?

A: 模块错误处理最佳实践:

// 1. 静态导入错误处理
// 使用try-catch包装使用模块的代码
try {
  import('./risky-module.js').then(module => {
    module.dangerousFunction();
  });
} catch (error) {
  console.error('Module error:', error);
}

// 2. 动态导入错误处理
const loadModuleWithFallback = async (modulePath, fallbackPath) => {
  try {
    return await import(modulePath);
  } catch (error) {
    console.warn(`Failed to load ${modulePath}, using fallback:`, error);
    return await import(fallbackPath);
  }
};

// 3. 模块内部错误处理
// robust-module.js
class ModuleError extends Error {
  constructor(message, code) {
    super(message);
    this.name = 'ModuleError';
    this.code = code;
  }
}

export const riskyFunction = (input) => {
  try {
    if (!input) {
      throw new ModuleError('Input is required', 'MISSING_INPUT');
    }
    // 危险操作
    return processInput(input);
  } catch (error) {
    // 记录错误
    console.error('Function failed:', error);
    
    // 重新抛出包装后的错误
    if (error instanceof ModuleError) {
      throw error;
    } else {
      throw new ModuleError('Processing failed', 'PROCESSING_ERROR');
    }
  }
};

// 4. 全局错误处理
window.addEventListener('unhandledrejection', (event) => {
  if (event.reason?.code === 'MODULE_LOAD_ERROR') {
    // 处理模块加载错误
    console.error('Module load failed:', event.reason);
    event.preventDefault();
  }
});

部署和生产问题

Q15: 如何优化生产环境的模块加载?

A: 生产环境优化策略:

// 1. 启用压缩和优化
// webpack.config.js
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  mode: 'production',
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true, // 移除console.log
            drop_debugger: true // 移除debugger
          }
        }
      })
    ]
  }
};

// 2. 配置缓存策略
// 服务器配置示例
app.use('/static', express.static('public', {
  maxAge: '1y', // 静态资源缓存1年
  etag: true,
  lastModified: true
}));

// 3. 使用Service Worker缓存模块
// sw.js
const CACHE_NAME = 'modules-v1';
const urlsToCache = [
  '/dist/main.js',
  '/dist/vendor.js',
  '/dist/runtime.js'
];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then((cache) => cache.addAll(urlsToCache))
  );
});

self.addEventListener('fetch', (event) => {
  if (event.request.url.includes('.js')) {
    event.respondWith(
      caches.match(event.request)
        .then((response) => response || fetch(event.request))
    );
  }
});

// 4. HTTP/2 Server Push
// 服务器配置
app.get('/', (req, res) => {
  // 推送关键模块
  res.push('/dist/critical.js', {
    response: { 'content-type': 'application/javascript' }
  });
  
  res.send(indexHTML);
});

Q16: 如何处理模块版本更新?

A: 模块版本管理策略:

// 1. 版本化文件名
// webpack.config.js
module.exports = {
  output: {
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].chunk.js'
  }
};

// 2. 版本检查和热更新
class ModuleVersionManager {
  constructor() {
    this.currentVersion = this.getCurrentVersion();
    this.checkInterval = 30000; // 30秒检查一次
  }
  
  getCurrentVersion() {
    return document.querySelector('meta[name="app-version"]')?.content;
  }
  
  async checkForUpdates() {
    try {
      const response = await fetch('/api/version');
      const { version } = await response.json();
      
      if (version !== this.currentVersion) {
        this.handleVersionUpdate(version);
      }
    } catch (error) {
      console.error('Version check failed:', error);
    }
  }
  
  handleVersionUpdate(newVersion) {
    // 通知用户有新版本
    if (confirm('新版本可用,是否刷新页面?')) {
      window.location.reload();
    }
  }
  
  startVersionChecking() {
    setInterval(() => this.checkForUpdates(), this.checkInterval);
  }
}

// 3. 渐进式更新
const updateModule = async (moduleName) => {
  try {
    // 预加载新版本
    const newModule = await import(`./modules/${moduleName}.js?v=${Date.now()}`);
    
    // 平滑切换
    if (window.modules?.[moduleName]) {
      // 调用旧模块的清理函数
      window.modules[moduleName].cleanup?.();
    }
    
    // 更新模块引用
    window.modules = window.modules || {};
    window.modules[moduleName] = newModule;
    
    // 初始化新模块
    newModule.init?.();
    
  } catch (error) {
    console.error(`Failed to update module ${moduleName}:`, error);
  }
};

其他常见问题

Q17: 如何在模块间共享数据?

A: 模块间数据共享方案:

// 1. 使用共享状态模块
// store.js
class Store {
  constructor() {
    this.state = {};
    this.listeners = [];
  }
  
  setState(updates) {
    this.state = { ...this.state, ...updates };
    this.listeners.forEach(listener => listener(this.state));
  }
  
  getState() {
    return this.state;
  }
  
  subscribe(listener) {
    this.listeners.push(listener);
    return () => {
      this.listeners = this.listeners.filter(l => l !== listener);
    };
  }
}

export const store = new Store();

// moduleA.js
import { store } from './store.js';

export const updateData = (data) => {
  store.setState({ moduleAData: data });
};

// moduleB.js
import { store } from './store.js';

const unsubscribe = store.subscribe((state) => {
  console.log('State updated:', state.moduleAData);
});

// 2. 使用事件系统
// eventBus.js
class EventBus {
  constructor() {
    this.events = {};
  }
  
  on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
  }
  
  emit(event, data) {
    if (this.events[event]) {
      this.events[event].forEach(callback => callback(data));
    }
  }
  
  off(event, callback) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(cb => cb !== callback);
    }
  }
}

export const eventBus = new EventBus();

// 3. 使用全局命名空间
// globals.js
window.APP_GLOBALS = window.APP_GLOBALS || {
  config: {},
  cache: new Map(),
  utils: {}
};

export default window.APP_GLOBALS;

Q18: 如何处理大型模块的加载?

A: 大型模块加载优化:

// 1. 模块分割
// 将大模块拆分成小块
// chart-module/index.js
export const loadBasicChart = () => import('./basic-chart.js');
export const loadAdvancedChart = () => import('./advanced-chart.js');
export const loadD3Chart = () => import('./d3-chart.js');

// 2. 流式加载
class StreamingModuleLoader {
  async loadModuleInChunks(modulePath, chunkSize = 1024 * 64) {
    const response = await fetch(modulePath);
    const reader = response.body?.getReader();
    
    if (!reader) {
      throw new Error('Streaming not supported');
    }
    
    let code = '';
    let done = false;
    
    while (!done) {
      const { value, done: readerDone } = await reader.read();
      done = readerDone;
      
      if (value) {
        code += new TextDecoder().decode(value);
        
        // 显示加载进度
        this.updateProgress(code.length, response.headers.get('content-length'));
      }
    }
    
    // 执行代码
    const blob = new Blob([code], { type: 'application/javascript' });
    const url = URL.createObjectURL(blob);
    const module = await import(url);
    URL.revokeObjectURL(url);
    
    return module;
  }
  
  updateProgress(loaded, total) {
    const progress = (loaded / total) * 100;
    console.log(`Loading progress: ${progress.toFixed(1)}%`);
  }
}

// 3. 分优先级加载
const loadModulesByPriority = async () => {
  // 高优先级模块
  const criticalModules = [
    import('./core.js'),
    import('./ui.js')
  ];
  
  await Promise.all(criticalModules);
  
  // 中优先级模块
  const importantModules = [
    import('./features.js'),
    import('./analytics.js')
  ];
  
  Promise.all(importantModules);
  
  // 低优先级模块(空闲时加载)
  requestIdleCallback(() => {
    import('./optional-features.js');
    import('./dev-tools.js');
  });
};

Q19: 如何实现模块的热重载?

A: 模块热重载实现:

// 1. 基础热重载检测
if (module.hot) {
  module.hot.accept('./my-module.js', () => {
    // 模块更新时的回调
    console.log('Module updated');
    
    // 重新导入更新的模块
    import('./my-module.js').then(newModule => {
      // 更新应用状态
      updateApplication(newModule);
    });
  });
}

// 2. 自定义热重载系统
class HotReloadManager {
  constructor() {
    this.modules = new Map();
    this.watchers = new Map();
  }
  
  register(modulePath, module) {
    this.modules.set(modulePath, module);
    
    if (this.isDevelopment()) {
      this.watchModule(modulePath);
    }
  }
  
  async watchModule(modulePath) {
    // 使用WebSocket或其他方式监听文件变化
    const ws = new WebSocket(`ws://localhost:3001/hmr`);
    
    ws.addEventListener('message', async (event) => {
      const { type, path } = JSON.parse(event.data);
      
      if (type === 'update' && path === modulePath) {
        await this.reloadModule(modulePath);
      }
    });
  }
  
  async reloadModule(modulePath) {
    try {
      // 清除模块缓存
      delete window.moduleCache?.[modulePath];
      
      // 重新导入模块
      const newModule = await import(`${modulePath}?t=${Date.now()}`);
      
      // 更新模块引用
      this.modules.set(modulePath, newModule);
      
      // 触发更新事件
      this.emit('moduleUpdated', { path: modulePath, module: newModule });
      
    } catch (error) {
      console.error('Hot reload failed:', error);
    }
  }
  
  isDevelopment() {
    return process.env.NODE_ENV === 'development';
  }
}

// 3. React组件热重载
// 使用React Fast Refresh
if (module.hot) {
  module.hot.accept('./MyComponent.jsx', () => {
    // React Fast Refresh会自动处理组件更新
  });
}

这些FAQ涵盖了JavaScript模块化开发中最常遇到的问题。遇到具体问题时,建议先查看相关工具的官方文档,然后参考这些解决方案进行调试和优化。


下一章: 参考资源

参考资源

本章整理了JavaScript模块化学习和开发中的重要参考资源,包括官方文档、技术规范、实用工具、社区资源和推荐阅读。

官方文档和规范

ECMAScript规范

浏览器API文档

Node.js文档

  • Node.js ES Modules

    • Node.js中的ES模块支持
    • CommonJS与ESM互操作
    • 模块解析算法
  • Node.js Modules

    • CommonJS模块系统
    • require()和module.exports
    • 模块缓存机制

构建工具文档

打包工具

Webpack

Rollup

Vite

  • Vite官方文档
    • 现代构建工具概念
    • 开发服务器和HMR
    • 插件开发指南

esbuild

  • esbuild文档
    • 高性能构建配置
    • API和命令行使用
    • 与其他工具集成

转译工具

Babel

SWC

  • SWC文档
    • Rust编写的快速编译器
    • 配置和使用指南
    • 性能基准测试

TypeScript

运行时环境

Node.js生态

  • npm文档

    • 包管理和发布
    • package.json配置
    • 语义化版本控制
  • yarn文档

    • 现代包管理器
    • Workspaces和Monorepo
    • Plug’n’Play特性
  • pnpm文档

    • 高效的包管理器
    • 硬链接和符号链接
    • 空间节省策略

现代运行时

Deno

  • Deno官方文档
    • 安全的JavaScript运行时
    • 内置TypeScript支持
    • 标准库和模块系统

Bun

  • Bun文档
    • 快速的JavaScript运行时
    • 内置包管理器
    • 兼容性和性能

技术博客和文章

权威技术博客

技术社区

在线学习资源

免费课程

付费课程

  • Frontend Masters

    • 专业前端课程
    • 行业专家授课
    • 深度技术讲解
  • Egghead.io

    • 简洁的技术视频
    • 特定技术专题
    • 实用技能培训

工具和资源

开发工具

代码编辑器插件

  • VS Code Extensions
    • ES6 Module Snippets
    • Auto Import - ES6, TS, JSX, TSX
    • Path Intellisense
    • Import Cost

在线工具

  • Webpack Bundle Analyzer

    • 包体积分析工具
    • 依赖关系可视化
    • 优化建议
  • Bundlephobia

    • npm包大小分析
    • 打包影响评估
    • 替代方案推荐
  • Can I Use

    • 浏览器兼容性查询
    • 功能支持状态
    • 使用统计数据

代码示例和模板

  • GitHub - Module Examples

    • 开源模块示例
    • 最佳实践展示
    • 学习参考代码
  • CodeSandbox

    • 在线代码编辑器
    • 模块化项目模板
    • 实时协作开发
  • JSFiddle

    • 快速原型开发
    • 代码片段分享
    • 实验和测试

书籍推荐

经典技术书籍

JavaScript核心技术

  • 《JavaScript高级程序设计》 (Professional JavaScript for Web Developers)

    • 作者: Nicholas C. Zakas
    • 全面的JavaScript语言指南
    • 模块化章节详细讲解
  • 《你不知道的JavaScript》 (You Don’t Know JS)

    • 作者: Kyle Simpson
    • 深入JavaScript语言机制
    • 模块系统底层原理
  • 《JavaScript语言精粹》 (JavaScript: The Good Parts)

    • 作者: Douglas Crockford
    • JavaScript最佳实践
    • 代码组织和模块化思想

前端工程化

  • 《前端工程化:体系设计与实践》

    • 现代前端开发流程
    • 模块化工具链
    • 最佳实践案例
  • 《Webpack实战:入门、进阶与优化》

    • Webpack深度使用指南
    • 模块打包优化
    • 实际项目应用

在线电子书

  • Eloquent JavaScript

    • 免费的JavaScript教程
    • 模块化编程章节
    • 交互式练习
  • Exploring ES6

    • ES6新特性详解
    • 模块系统深入分析
    • 实用示例代码

技术会议和活动

国际技术会议

  • JSConf

    • JavaScript技术大会
    • 最新技术趋势
    • 社区交流平台
  • React Conf

    • React生态大会
    • 组件和模块化
    • 最佳实践分享
  • VueConf

    • Vue.js技术大会
    • 现代前端开发
    • 工具链和生态

国内技术活动

开源项目和案例

优秀开源项目

模块化库

  • Lodash

    • 实用工具库
    • 模块化设计典范
    • Tree Shaking友好
  • RxJS

    • 响应式编程库
    • 操作符模块化
    • 函数式编程范式
  • Three.js

    • 3D图形库
    • 大型项目模块组织
    • 插件式架构

构建工具

  • Create React App

    • React应用脚手架
    • 零配置模块化构建
    • 最佳实践集成
  • Vue CLI

    • Vue.js项目脚手架
    • 插件化架构
    • 现代工具链集成

学习案例

  • TodoMVC

    • 相同应用不同框架实现
    • 模块化架构对比
    • 代码组织方式
  • RealWorld

    • 全栈应用示例
    • 前后端模块化
    • 最佳实践展示

标准和提案

W3C标准

  • Web Components

    • 组件化Web标准
    • 自定义元素和Shadow DOM
    • 模块化UI组件
  • Service Workers

    • 离线功能和缓存
    • 模块化渐进式应用
    • 性能优化策略

TC39提案

性能监控和分析

性能分析工具

  • Lighthouse

    • Web性能审计工具
    • 模块加载性能分析
    • 优化建议生成
  • WebPageTest

    • 网页性能测试
    • 模块加载瀑布图
    • 真实用户体验模拟
  • Chrome DevTools

    • 浏览器开发者工具
    • 网络面板分析
    • 性能面板调试

监控服务

  • Sentry

    • 错误监控和性能追踪
    • 模块加载错误分析
    • 用户体验监控
  • DataDog

    • 应用性能监控
    • 前端性能指标
    • 实时数据分析

社区和论坛

技术社区

中文社区

  • 掘金

    • 中文技术社区
    • 前端技术文章
    • 经验分享和讨论
  • 思否 (SegmentFault)

    • 中文问答社区
    • 技术问题解决
    • 代码片段分享
  • V2EX

    • 创意工作者社区
    • 技术讨论和分享
    • 职业发展交流

订阅推荐

技术周报

  • JavaScript Weekly

    • 每周JavaScript资讯
    • 新工具和库推荐
    • 技术文章精选
  • Frontend Focus

    • 前端技术周报
    • 设计和开发趋势
    • 浏览器新特性
  • React Status

    • React生态周报
    • 组件库和工具推荐
    • 最佳实践分享

技术博客

  • Overreacted

    • Dan Abramov的技术博客
    • React和JavaScript深度文章
    • 技术思考和见解
  • 2ality

    • Dr. Axel Rauschmayer的博客
    • JavaScript语言特性分析
    • ECMAScript规范解读

工具链配置模板

项目模板

  • create-vite

    • Vite项目模板
    • 多框架支持
    • 现代工具链配置
  • create-react-app

    • React项目模板
    • 零配置启动
    • 渐进式增强

配置示例

  • Awesome Webpack

    • Webpack资源汇总
    • 配置示例集合
    • 插件和loader推荐
  • Awesome Rollup

    • Rollup生态资源
    • 插件和工具推荐
    • 配置示例和教程

持续学习建议

学习路径

  1. 基础阶段

    • 掌握ES模块语法
    • 理解模块化概念
    • 学习基础工具使用
  2. 进阶阶段

    • 深入构建工具配置
    • 性能优化技巧
    • 模块化架构设计
  3. 高级阶段

    • 自定义构建工具
    • 大型项目架构
    • 团队协作和规范

实践建议

  • 动手实践: 通过实际项目加深理解
  • 源码阅读: 学习优秀开源项目的模块化设计
  • 社区参与: 积极参与技术讨论和贡献
  • 持续关注: 跟进技术发展和新特性

关注重点

  • 标准发展: 关注ECMAScript新特性
  • 工具演进: 跟进构建工具的发展
  • 性能优化: 持续学习性能优化技巧
  • 最佳实践: 学习和应用行业最佳实践

通过这些丰富的学习资源,开发者可以系统地掌握JavaScript模块化技术,并在实际项目中应用最佳实践。建议根据自己的技术水平和项目需求,有选择性地利用这些资源进行深入学习。


完结 🎉