模块化概述

什么是模块化

模块化是一种将代码分解为独立、可重用组件的编程方法。每个模块都有自己的作用域,可以导出特定的功能供其他模块使用。

// 模块化的优势:
// 1. 代码组织:将相关功能组织在一起
// 2. 作用域隔离:避免全局变量污染
// 3. 依赖管理:明确模块间的依赖关系
// 4. 代码复用:模块可以在多个地方使用
// 5. 维护性:便于代码的维护和更新
// 6. 测试:模块可以独立测试

// 模块化发展历程:
// 1. 全局变量时代(无模块化)
// 2. IIFE(立即执行函数表达式)
// 3. CommonJS(Node.js)
// 4. AMD(异步模块定义)
// 5. UMD(通用模块定义)
// 6. ES6 Modules(现代标准)

// 早期的全局变量方式(问题很多)
var utils = {
  add: function(a, b) {
    return a + b;
  },
  multiply: function(a, b) {
    return a * b;
  }
};

// 全局变量的问题
// 1. 命名冲突
// 2. 依赖关系不明确
// 3. 难以维护
// 4. 无法控制访问权限

IIFE模块模式

// IIFE(Immediately Invoked Function Expression)模块模式
// 解决了作用域隔离问题

// 基本IIFE模块
var MathUtils = (function() {
  // 私有变量和函数
  var PI = 3.14159;
  
  function validateNumber(num) {
    return typeof num === 'number' && !isNaN(num);
  }
  
  // 公共API
  return {
    add: function(a, b) {
      if (!validateNumber(a) || !validateNumber(b)) {
        throw new Error('参数必须是数字');
      }
      return a + b;
    },
    
    multiply: function(a, b) {
      if (!validateNumber(a) || !validateNumber(b)) {
        throw new Error('参数必须是数字');
      }
      return a * b;
    },
    
    circleArea: function(radius) {
      if (!validateNumber(radius) || radius < 0) {
        throw new Error('半径必须是非负数字');
      }
      return PI * radius * radius;
    },
    
    // 获取PI值(只读)
    getPI: function() {
      return PI;
    }
  };
})();

// 使用IIFE模块
console.log('加法:', MathUtils.add(5, 3));
console.log('乘法:', MathUtils.multiply(4, 7));
console.log('圆面积:', MathUtils.circleArea(5));
console.log('PI值:', MathUtils.getPI());

// 无法访问私有变量
// console.log(PI); // 错误:PI未定义
// console.log(MathUtils.PI); // undefined

// 带参数的IIFE模块(依赖注入)
var StringUtils = (function($) {
  // 依赖jQuery(如果可用)
  var hasJQuery = typeof $ !== 'undefined';
  
  return {
    capitalize: function(str) {
      if (typeof str !== 'string') return '';
      return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
    },
    
    truncate: function(str, length, suffix) {
      if (typeof str !== 'string') return '';
      suffix = suffix || '...';
      if (str.length <= length) return str;
      return str.slice(0, length - suffix.length) + suffix;
    },
    
    // 如果有jQuery,使用jQuery的方法
    escapeHtml: function(str) {
      if (hasJQuery && $.fn) {
        return $('<div>').text(str).html();
      }
      // 原生实现
      return str
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
    }
  };
})(typeof jQuery !== 'undefined' ? jQuery : undefined);

// 使用StringUtils
console.log('首字母大写:', StringUtils.capitalize('hello world'));
console.log('截断文本:', StringUtils.truncate('这是一个很长的文本内容', 10));
console.log('HTML转义:', StringUtils.escapeHtml('<script>alert("XSS")</script>'));

// 模块扩展模式
var MathUtils = (function(module) {
  // 扩展现有模块
  module.subtract = function(a, b) {
    return a - b;
  };
  
  module.divide = function(a, b) {
    if (b === 0) {
      throw new Error('除数不能为零');
    }
    return a / b;
  };
  
  return module;
})(MathUtils || {});

console.log('减法:', MathUtils.subtract(10, 3));
console.log('除法:', MathUtils.divide(15, 3));

// 子模块模式
var App = (function() {
  var modules = {};
  
  return {
    // 注册模块
    module: function(name, definition) {
      if (typeof definition === 'function') {
        modules[name] = definition();
      } else {
        modules[name] = definition;
      }
      return this;
    },
    
    // 获取模块
    getModule: function(name) {
      return modules[name];
    },
    
    // 获取所有模块
    getModules: function() {
      return Object.keys(modules);
    }
  };
})();

// 注册模块
App.module('user', function() {
  var users = [];
  
  return {
    add: function(user) {
      users.push(user);
    },
    
    getAll: function() {
      return users.slice(); // 返回副本
    },
    
    findById: function(id) {
      return users.find(user => user.id === id);
    }
  };
});

App.module('logger', {
  log: function(message) {
    console.log(`[${new Date().toISOString()}] ${message}`);
  },
  
  error: function(message) {
    console.error(`[${new Date().toISOString()}] ERROR: ${message}`);
  }
});

// 使用子模块
const userModule = App.getModule('user');
const logger = App.getModule('logger');

userModule.add({ id: 1, name: '张三' });
userModule.add({ id: 2, name: '李四' });

logger.log('用户模块测试');
console.log('所有用户:', userModule.getAll());
console.log('查找用户:', userModule.findById(1));

console.log('已注册的模块:', App.getModules());

CommonJS模块

Node.js中的CommonJS

// CommonJS是Node.js使用的模块系统
// 特点:
// 1. 同步加载
// 2. 运行时加载
// 3. 值的拷贝
// 4. 单例模式

// math.js - 导出模块
const PI = 3.14159;

// 私有函数
function validateNumber(num) {
  return typeof num === 'number' && !isNaN(num);
}

// 导出单个函数
function add(a, b) {
  if (!validateNumber(a) || !validateNumber(b)) {
    throw new Error('参数必须是数字');
  }
  return a + b;
}

// 导出对象
const calculator = {
  multiply(a, b) {
    if (!validateNumber(a) || !validateNumber(b)) {
      throw new Error('参数必须是数字');
    }
    return a * b;
  },
  
  divide(a, b) {
    if (!validateNumber(a) || !validateNumber(b)) {
      throw new Error('参数必须是数字');
    }
    if (b === 0) {
      throw new Error('除数不能为零');
    }
    return a / b;
  }
};

// 导出类
class Circle {
  constructor(radius) {
    if (!validateNumber(radius) || radius < 0) {
      throw new Error('半径必须是非负数字');
    }
    this.radius = radius;
  }
  
  area() {
    return PI * this.radius * this.radius;
  }
  
  circumference() {
    return 2 * PI * this.radius;
  }
}

// 多种导出方式
module.exports = {
  add,
  calculator,
  Circle,
  PI: () => PI // 只读访问
};

// 或者分别导出
// exports.add = add;
// exports.calculator = calculator;
// exports.Circle = Circle;
// exports.getPI = () => PI;

// 注意:不能直接赋值给exports
// exports = { add }; // 错误!这样不会导出任何内容

// app.js - 导入模块
const math = require('./math'); // 导入整个模块
const { add, Circle } = require('./math'); // 解构导入

// 使用导入的模块
console.log('加法:', math.add(5, 3));
console.log('乘法:', math.calculator.multiply(4, 7));

const circle = new math.Circle(5);
console.log('圆面积:', circle.area());
console.log('圆周长:', circle.circumference());

// 使用解构导入
console.log('解构导入加法:', add(10, 20));
const smallCircle = new Circle(3);
console.log('小圆面积:', smallCircle.area());

// 模块缓存演示
// counter.js
let count = 0;

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

// main.js
const counter1 = require('./counter');
const counter2 = require('./counter');

console.log('counter1 === counter2:', counter1 === counter2); // true
console.log('初始计数:', counter1.getCount()); // 0

counter1.increment();
console.log('counter1增加后:', counter1.getCount()); // 1
console.log('counter2的计数:', counter2.getCount()); // 1(共享状态)

// 条件加载
function loadModule(moduleName) {
  try {
    return require(moduleName);
  } catch (error) {
    console.error(`无法加载模块 ${moduleName}:`, error.message);
    return null;
  }
}

// 动态加载
const moduleName = process.env.NODE_ENV === 'development' ? './dev-config' : './prod-config';
const config = loadModule(moduleName);

// 模块路径解析
// require('./math')        - 相对路径
// require('/abs/path/math') - 绝对路径
// require('lodash')        - node_modules中的包
// require('fs')            - 内置模块

// 查看模块解析路径
console.log('模块解析路径:', require.resolve('./math'));
console.log('require.cache keys:', Object.keys(require.cache));

// 清除模块缓存(谨慎使用)
function clearModuleCache(modulePath) {
  const resolvedPath = require.resolve(modulePath);
  delete require.cache[resolvedPath];
}

// 模块包装函数
// Node.js实际上将每个模块包装在一个函数中:
// (function(exports, require, module, __filename, __dirname) {
//   // 模块代码
// });

console.log('当前文件名:', __filename);
console.log('当前目录:', __dirname);
console.log('模块对象:', module);

CommonJS模块工具

// module-loader.js - 高级模块加载器
class ModuleLoader {
  constructor() {
    this.cache = new Map();
    this.loading = new Map();
  }
  
  // 异步加载模块(模拟)
  async load(modulePath) {
    if (this.cache.has(modulePath)) {
      return this.cache.get(modulePath);
    }
    
    if (this.loading.has(modulePath)) {
      return this.loading.get(modulePath);
    }
    
    const loadPromise = this.loadModule(modulePath);
    this.loading.set(modulePath, loadPromise);
    
    try {
      const module = await loadPromise;
      this.cache.set(modulePath, module);
      this.loading.delete(modulePath);
      return module;
    } catch (error) {
      this.loading.delete(modulePath);
      throw error;
    }
  }
  
  async loadModule(modulePath) {
    // 模拟异步加载
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        try {
          const module = require(modulePath);
          resolve(module);
        } catch (error) {
          reject(error);
        }
      }, Math.random() * 100);
    });
  }
  
  // 预加载模块
  async preload(modulePaths) {
    const promises = modulePaths.map(path => this.load(path));
    return Promise.all(promises);
  }
  
  // 清除缓存
  clearCache(modulePath) {
    if (modulePath) {
      this.cache.delete(modulePath);
    } else {
      this.cache.clear();
    }
  }
  
  // 获取缓存信息
  getCacheInfo() {
    return {
      cached: Array.from(this.cache.keys()),
      loading: Array.from(this.loading.keys()),
      cacheSize: this.cache.size
    };
  }
}

// 使用模块加载器
const loader = new ModuleLoader();

// 异步加载示例
(async () => {
  try {
    const math = await loader.load('./math');
    console.log('异步加载的数学模块:', math.add(1, 2));
    
    // 预加载多个模块
    await loader.preload(['./math', './counter']);
    console.log('预加载完成');
    
    console.log('缓存信息:', loader.getCacheInfo());
  } catch (error) {
    console.error('模块加载失败:', error);
  }
})();

// 模块依赖分析器
class DependencyAnalyzer {
  constructor() {
    this.dependencies = new Map();
    this.visited = new Set();
  }
  
  analyze(modulePath) {
    if (this.visited.has(modulePath)) {
      return this.dependencies.get(modulePath) || [];
    }
    
    this.visited.add(modulePath);
    
    try {
      const fs = require('fs');
      const path = require('path');
      
      const content = fs.readFileSync(modulePath, 'utf8');
      const deps = this.extractDependencies(content);
      
      this.dependencies.set(modulePath, deps);
      
      // 递归分析依赖
      deps.forEach(dep => {
        if (dep.startsWith('./') || dep.startsWith('../')) {
          const depPath = path.resolve(path.dirname(modulePath), dep + '.js');
          if (fs.existsSync(depPath)) {
            this.analyze(depPath);
          }
        }
      });
      
      return deps;
    } catch (error) {
      console.error(`分析模块 ${modulePath} 失败:`, error.message);
      return [];
    }
  }
  
  extractDependencies(content) {
    const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
    const dependencies = [];
    let match;
    
    while ((match = requireRegex.exec(content)) !== null) {
      dependencies.push(match[1]);
    }
    
    return [...new Set(dependencies)]; // 去重
  }
  
  getDependencyTree() {
    const tree = {};
    for (const [module, deps] of this.dependencies) {
      tree[module] = deps;
    }
    return tree;
  }
  
  findCircularDependencies() {
    const circular = [];
    const visiting = new Set();
    const visited = new Set();
    
    const visit = (module, path = []) => {
      if (visiting.has(module)) {
        const cycleStart = path.indexOf(module);
        circular.push(path.slice(cycleStart).concat(module));
        return;
      }
      
      if (visited.has(module)) {
        return;
      }
      
      visiting.add(module);
      const deps = this.dependencies.get(module) || [];
      
      deps.forEach(dep => {
        visit(dep, path.concat(module));
      });
      
      visiting.delete(module);
      visited.add(module);
    };
    
    for (const module of this.dependencies.keys()) {
      if (!visited.has(module)) {
        visit(module);
      }
    }
    
    return circular;
  }
}

// 使用依赖分析器
const analyzer = new DependencyAnalyzer();
// analyzer.analyze('./app.js');
// console.log('依赖树:', analyzer.getDependencyTree());
// console.log('循环依赖:', analyzer.findCircularDependencies());

ES6模块

ES6模块语法

// ES6模块是JavaScript的官方模块标准
// 特点:
// 1. 静态结构(编译时确定)
// 2. 异步加载
// 3. 值的引用
// 4. 严格模式
// 5. 顶层this为undefined

// math-es6.js - ES6模块导出

// 命名导出
export const PI = 3.14159;

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

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

// 批量导出
function subtract(a, b) {
  return a - b;
}

function divide(a, b) {
  if (b === 0) {
    throw new Error('除数不能为零');
  }
  return a / b;
}

export { subtract, divide };

// 重命名导出
function power(base, exponent) {
  return Math.pow(base, exponent);
}

export { power as pow };

// 默认导出
export default class Calculator {
  constructor() {
    this.history = [];
  }
  
  calculate(operation, a, b) {
    let result;
    
    switch (operation) {
      case 'add':
        result = add(a, b);
        break;
      case 'subtract':
        result = subtract(a, b);
        break;
      case 'multiply':
        result = multiply(a, b);
        break;
      case 'divide':
        result = divide(a, b);
        break;
      case 'power':
        result = power(a, b);
        break;
      default:
        throw new Error(`不支持的操作: ${operation}`);
    }
    
    this.history.push({ operation, a, b, result, timestamp: Date.now() });
    return result;
  }
  
  getHistory() {
    return [...this.history];
  }
  
  clearHistory() {
    this.history = [];
  }
}

// 条件导出
if (typeof window !== 'undefined') {
  // 浏览器环境
  export const environment = 'browser';
} else {
  // Node.js环境
  export const environment = 'node';
}

// app-es6.js - ES6模块导入

// 导入默认导出
import Calculator from './math-es6.js';

// 导入命名导出
import { add, multiply, PI } from './math-es6.js';

// 导入重命名
import { pow as power } from './math-es6.js';

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

// 混合导入
import Calculator, { add, subtract } from './math-es6.js';

// 动态导入(ES2020)
async function loadMathModule() {
  try {
    const mathModule = await import('./math-es6.js');
    console.log('动态导入的模块:', mathModule);
    
    const calc = new mathModule.default();
    console.log('动态计算:', calc.calculate('add', 5, 3));
  } catch (error) {
    console.error('动态导入失败:', error);
  }
}

// 条件动态导入
async function loadModuleConditionally(condition) {
  if (condition) {
    const { add, multiply } = await import('./math-es6.js');
    return { add, multiply };
  } else {
    const { subtract, divide } = await import('./math-es6.js');
    return { subtract, divide };
  }
}

// 使用导入的模块
const calculator = new Calculator();
console.log('ES6计算器:', calculator.calculate('add', 10, 5));
console.log('直接使用函数:', add(3, 7));
console.log('使用命名空间:', MathUtils.multiply(4, 6));
console.log('PI值:', PI);

// 模块的值是引用,不是拷贝
// counter-es6.js
let count = 0;

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

export function getCount() {
  return count;
}

export { count }; // 导出变量的引用

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

console.log('初始计数:', count); // 0
increment();
console.log('增加后计数:', count); // 仍然是0(导入时的值)
console.log('通过函数获取:', getCount()); // 1

// 注意:不能修改导入的变量
// count = 10; // 错误:Assignment to constant variable

ES6模块高级特性

// 模块聚合和重新导出
// utils/index.js - 聚合模块

// 重新导出其他模块的内容
export { add, subtract, multiply, divide } from './math.js';
export { capitalize, truncate } from './string.js';
export { debounce, throttle } from './function.js';

// 重新导出并重命名
export { default as MathCalculator } from './calculator.js';
export { Logger as AppLogger } from './logger.js';

// 重新导出所有内容
export * from './array.js';
export * from './object.js';

// 重新导出并添加新功能
export { format as formatDate } from './date.js';
export function getCurrentDate() {
  return new Date().toISOString().split('T')[0];
}

// 条件重新导出
if (process.env.NODE_ENV === 'development') {
  export { debug } from './debug.js';
}

// 模块工厂模式
// module-factory.js
export function createModule(config) {
  return {
    name: config.name,
    version: config.version,
    
    init() {
      console.log(`模块 ${this.name} v${this.version} 已初始化`);
    },
    
    destroy() {
      console.log(`模块 ${this.name} 已销毁`);
    }
  };
}

export class ModuleManager {
  constructor() {
    this.modules = new Map();
  }
  
  register(name, moduleFactory, config) {
    if (this.modules.has(name)) {
      throw new Error(`模块 ${name} 已存在`);
    }
    
    const module = moduleFactory(config);
    this.modules.set(name, module);
    return module;
  }
  
  get(name) {
    return this.modules.get(name);
  }
  
  unregister(name) {
    const module = this.modules.get(name);
    if (module && typeof module.destroy === 'function') {
      module.destroy();
    }
    return this.modules.delete(name);
  }
  
  list() {
    return Array.from(this.modules.keys());
  }
}

// 使用模块工厂
import { createModule, ModuleManager } from './module-factory.js';

const manager = new ModuleManager();

manager.register('auth', createModule, {
  name: 'Authentication',
  version: '1.0.0'
});

manager.register('logger', createModule, {
  name: 'Logger',
  version: '2.1.0'
});

const authModule = manager.get('auth');
authModule.init();

console.log('已注册的模块:', manager.list());

// 异步模块加载器
class AsyncModuleLoader {
  constructor() {
    this.cache = new Map();
    this.loading = new Map();
  }
  
  async load(modulePath) {
    // 检查缓存
    if (this.cache.has(modulePath)) {
      return this.cache.get(modulePath);
    }
    
    // 检查是否正在加载
    if (this.loading.has(modulePath)) {
      return this.loading.get(modulePath);
    }
    
    // 开始加载
    const loadPromise = this.loadModule(modulePath);
    this.loading.set(modulePath, loadPromise);
    
    try {
      const module = await loadPromise;
      this.cache.set(modulePath, module);
      this.loading.delete(modulePath);
      return module;
    } catch (error) {
      this.loading.delete(modulePath);
      throw error;
    }
  }
  
  async loadModule(modulePath) {
    try {
      const module = await import(modulePath);
      return module;
    } catch (error) {
      throw new Error(`无法加载模块 ${modulePath}: ${error.message}`);
    }
  }
  
  async loadMultiple(modulePaths) {
    const promises = modulePaths.map(path => this.load(path));
    return Promise.all(promises);
  }
  
  async loadWithFallback(primaryPath, fallbackPath) {
    try {
      return await this.load(primaryPath);
    } catch (error) {
      console.warn(`主模块加载失败,使用备用模块: ${error.message}`);
      return this.load(fallbackPath);
    }
  }
  
  preload(modulePaths) {
    // 预加载但不等待完成
    modulePaths.forEach(path => {
      this.load(path).catch(error => {
        console.warn(`预加载模块 ${path} 失败:`, error.message);
      });
    });
  }
  
  clearCache(modulePath) {
    if (modulePath) {
      this.cache.delete(modulePath);
    } else {
      this.cache.clear();
    }
  }
  
  getCacheInfo() {
    return {
      cached: Array.from(this.cache.keys()),
      loading: Array.from(this.loading.keys()),
      cacheSize: this.cache.size
    };
  }
}

// 使用异步模块加载器
const loader = new AsyncModuleLoader();

// 预加载常用模块
loader.preload([
  './utils/math.js',
  './utils/string.js',
  './utils/array.js'
]);

// 按需加载
async function handleUserAction(action) {
  switch (action) {
    case 'calculate':
      const mathModule = await loader.load('./utils/math.js');
      return mathModule.add(1, 2);
      
    case 'format':
      const stringModule = await loader.load('./utils/string.js');
      return stringModule.capitalize('hello world');
      
    default:
      throw new Error(`未知操作: ${action}`);
  }
}

// 模块依赖注入
class DependencyInjector {
  constructor() {
    this.dependencies = new Map();
    this.singletons = new Map();
  }
  
  // 注册依赖
  register(name, factory, options = {}) {
    this.dependencies.set(name, {
      factory,
      singleton: options.singleton || false,
      dependencies: options.dependencies || []
    });
  }
  
  // 解析依赖
  async resolve(name) {
    const dependency = this.dependencies.get(name);
    if (!dependency) {
      throw new Error(`依赖 ${name} 未注册`);
    }
    
    // 检查单例缓存
    if (dependency.singleton && this.singletons.has(name)) {
      return this.singletons.get(name);
    }
    
    // 解析依赖的依赖
    const resolvedDeps = await Promise.all(
      dependency.dependencies.map(dep => this.resolve(dep))
    );
    
    // 创建实例
    const instance = await dependency.factory(...resolvedDeps);
    
    // 缓存单例
    if (dependency.singleton) {
      this.singletons.set(name, instance);
    }
    
    return instance;
  }
  
  // 批量解析
  async resolveAll(names) {
    const promises = names.map(name => this.resolve(name));
    return Promise.all(promises);
  }
}

// 使用依赖注入
const injector = new DependencyInjector();

// 注册依赖
injector.register('logger', async () => {
  const { Logger } = await import('./logger.js');
  return new Logger();
}, { singleton: true });

injector.register('database', async (logger) => {
  const { Database } = await import('./database.js');
  return new Database(logger);
}, { dependencies: ['logger'] });

injector.register('userService', async (database, logger) => {
  const { UserService } = await import('./user-service.js');
  return new UserService(database, logger);
}, { dependencies: ['database', 'logger'] });

// 解析并使用
(async () => {
  try {
    const userService = await injector.resolve('userService');
    console.log('用户服务已就绪:', userService);
  } catch (error) {
    console.error('依赖解析失败:', error);
  }
})();

模块打包工具

Webpack基础配置

// webpack.config.js - Webpack配置
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  // 入口文件
  entry: {
    main: './src/index.js',
    vendor: './src/vendor.js'
  },
  
  // 输出配置
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].chunk.js',
    clean: true // 清理输出目录
  },
  
  // 模式
  mode: process.env.NODE_ENV || 'development',
  
  // 开发服务器
  devServer: {
    contentBase: './dist',
    hot: true,
    port: 3000
  },
  
  // 模块解析
  resolve: {
    extensions: ['.js', '.jsx', '.ts', '.tsx'],
    alias: {
      '@': path.resolve(__dirname, 'src'),
      'utils': path.resolve(__dirname, 'src/utils')
    }
  },
  
  // 模块规则
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      },
      {
        test: /\.css$/,
        use: [
          process.env.NODE_ENV === 'production'
            ? MiniCssExtractPlugin.loader
            : 'style-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'
        }
      }
    }
  }
};

// package.json - 脚本配置
{
  "scripts": {
    "build": "webpack --mode=production",
    "dev": "webpack serve --mode=development",
    "analyze": "webpack-bundle-analyzer dist/main.*.js"
  },
  "devDependencies": {
    "webpack": "^5.0.0",
    "webpack-cli": "^4.0.0",
    "webpack-dev-server": "^4.0.0",
    "babel-loader": "^8.0.0",
    "@babel/core": "^7.0.0",
    "@babel/preset-env": "^7.0.0",
    "css-loader": "^6.0.0",
    "style-loader": "^3.0.0",
    "mini-css-extract-plugin": "^2.0.0",
    "html-webpack-plugin": "^5.0.0"
  }
}

Rollup配置

// rollup.config.js - Rollup配置
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';
import { defineConfig } from 'rollup';

export default defineConfig({
  input: 'src/index.js',
  
  output: [
    {
      file: 'dist/bundle.cjs.js',
      format: 'cjs',
      exports: 'auto'
    },
    {
      file: 'dist/bundle.esm.js',
      format: 'esm'
    },
    {
      file: 'dist/bundle.umd.js',
      format: 'umd',
      name: 'MyLibrary',
      globals: {
        'lodash': '_'
      }
    }
  ],
  
  external: ['lodash'], // 外部依赖
  
  plugins: [
    resolve({
      browser: true,
      preferBuiltins: false
    }),
    commonjs(),
    babel({
      babelHelpers: 'bundled',
      exclude: 'node_modules/**',
      presets: ['@babel/preset-env']
    }),
    terser() // 压缩代码
  ]
});

// 多入口配置
const configs = [
  {
    input: 'src/main.js',
    output: {
      file: 'dist/main.js',
      format: 'esm'
    }
  },
  {
    input: 'src/worker.js',
    output: {
      file: 'dist/worker.js',
      format: 'iife'
    }
  }
];

export default configs.map(config => ({
  ...config,
  plugins: [
    resolve(),
    commonjs(),
    babel({ babelHelpers: 'bundled' })
  ]
}));

模块化最佳实践

// 1. 模块设计原则

// 单一职责原则
// user.js - 只处理用户相关逻辑
export class User {
  constructor(id, name, email) {
    this.id = id;
    this.name = name;
    this.email = email;
  }
  
  validate() {
    return this.name && this.email && this.email.includes('@');
  }
}

// user-service.js - 只处理用户服务逻辑
import { User } from './user.js';

export class UserService {
  constructor(apiClient) {
    this.apiClient = apiClient;
    this.cache = new Map();
  }
  
  async getUser(id) {
    if (this.cache.has(id)) {
      return this.cache.get(id);
    }
    
    const userData = await this.apiClient.get(`/users/${id}`);
    const user = new User(userData.id, userData.name, userData.email);
    this.cache.set(id, user);
    return user;
  }
}

// 2. 接口隔离
// interfaces.js - 定义接口
export const ILogger = {
  log: () => {},
  error: () => {},
  warn: () => {}
};

export const IStorage = {
  get: () => {},
  set: () => {},
  remove: () => {},
  clear: () => {}
};

// console-logger.js - 实现日志接口
export class ConsoleLogger {
  log(message) {
    console.log(`[LOG] ${message}`);
  }
  
  error(message) {
    console.error(`[ERROR] ${message}`);
  }
  
  warn(message) {
    console.warn(`[WARN] ${message}`);
  }
}

// local-storage.js - 实现存储接口
export class LocalStorage {
  get(key) {
    try {
      const value = localStorage.getItem(key);
      return value ? JSON.parse(value) : null;
    } catch {
      return null;
    }
  }
  
  set(key, value) {
    try {
      localStorage.setItem(key, JSON.stringify(value));
      return true;
    } catch {
      return false;
    }
  }
  
  remove(key) {
    localStorage.removeItem(key);
  }
  
  clear() {
    localStorage.clear();
  }
}

// 3. 依赖倒置
// app.js - 高层模块不依赖低层模块
import { UserService } from './user-service.js';
import { ConsoleLogger } from './console-logger.js';
import { LocalStorage } from './local-storage.js';
import { ApiClient } from './api-client.js';

class Application {
  constructor(logger, storage, apiClient) {
    this.logger = logger;
    this.storage = storage;
    this.userService = new UserService(apiClient);
  }
  
  async start() {
    this.logger.log('应用启动');
    
    try {
      const user = await this.userService.getUser(1);
      this.storage.set('currentUser', user);
      this.logger.log('用户加载成功');
    } catch (error) {
      this.logger.error('用户加载失败: ' + error.message);
    }
  }
}

// 依赖注入
const logger = new ConsoleLogger();
const storage = new LocalStorage();
const apiClient = new ApiClient('https://api.example.com');

const app = new Application(logger, storage, apiClient);
app.start();

// 4. 模块版本管理
// version.js - 版本信息
export const VERSION = '1.2.3';
export const BUILD_DATE = '2024-01-15';
export const COMMIT_HASH = 'abc123def456';

// feature-flags.js - 功能开关
export const FEATURES = {
  NEW_UI: true,
  BETA_FEATURE: false,
  EXPERIMENTAL_API: process.env.NODE_ENV === 'development'
};

export function isFeatureEnabled(feature) {
  return FEATURES[feature] || false;
}

// 5. 模块测试
// math.test.js - 模块测试
import { add, multiply, divide } from './math.js';

describe('Math Utils', () => {
  test('add function', () => {
    expect(add(2, 3)).toBe(5);
    expect(add(-1, 1)).toBe(0);
  });
  
  test('multiply function', () => {
    expect(multiply(3, 4)).toBe(12);
    expect(multiply(0, 5)).toBe(0);
  });
  
  test('divide function', () => {
    expect(divide(10, 2)).toBe(5);
    expect(() => divide(10, 0)).toThrow('除数不能为零');
  });
});

// 6. 模块文档
/**
 * 数学工具模块
 * @module MathUtils
 * @version 1.0.0
 * @author Developer
 * @since 2024-01-01
 */

/**
 * 两数相加
 * @param {number} a - 第一个数
 * @param {number} b - 第二个数
 * @returns {number} 两数之和
 * @throws {Error} 当参数不是数字时抛出错误
 * @example
 * // 基本用法
 * const result = add(2, 3); // 5
 * 
 * // 错误处理
 * try {
 *   add('2', 3);
 * } catch (error) {
 *   console.error(error.message);
 * }
 */
export function add(a, b) {
  if (typeof a !== 'number' || typeof b !== 'number') {
    throw new Error('参数必须是数字');
  }
  return a + b;
}

本章总结

本章全面介绍了JavaScript的模块化编程:

  1. 模块化概述:理解了模块化的重要性和发展历程
  2. IIFE模块:学习了早期的模块化解决方案和设计模式
  3. CommonJS:掌握了Node.js的模块系统和使用方法
  4. ES6模块:了解了现代JavaScript的官方模块标准
  5. 打包工具:学习了Webpack和Rollup等构建工具的配置
  6. 最佳实践:掌握了模块设计原则、测试和文档编写

模块化是现代JavaScript开发的基础,它帮助我们构建可维护、可扩展的大型应用程序。通过合理的模块设计和工具使用,我们可以显著提高开发效率和代码质量。

下一章我们将学习JavaScript的面向对象编程,包括类、继承、多态等核心概念。