JS模块系统

5 阅读2分钟

1. ES6 Modules (ESM)

现代 JavaScript 官方标准,支持静态分析和 tree-shaking。

// 导出
export const name = 'value';
export function func() {};
export default Component;

// 导入
import Component, { name } from './module.js';
import * as all from './module.js';

特点

  • 静态结构,便于优化
  • 支持异步加载
  • 浏览器原生支持(现代浏览器)
  • 需要文件扩展名 .js

2. CommonJS (CJS)

Node.js 的默认模块系统,主要用于服务器端。

// 导出
module.exports = { 
  name: 'value',
  func: function() {}
};
// 或
exports.name = 'value';

// 导入
const module = require('./module');
const { name } = require('./module');

特点

  • 同步加载
  • 运行时动态加载
  • module.exports 和 exports 指向同一对象
  • 每个模块有 requiremoduleexports__dirname__filename

3. UMD (Universal Module Definition)

兼容多种环境的通用模式。

(function(root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD
    define(['dependency'], factory);
  } else if (typeof exports === 'object') {
    // CommonJS
    module.exports = factory(require('dependency'));
  } else {
    // 浏览器全局变量
    root.myModule = factory(root.dependency);
  }
}(this, function(dependency) {
  // 模块代码
  return {
    name: 'value'
  };
}));

特点

  • 兼容 AMD、CommonJS 和全局变量
  • 文件较大(包含兼容代码)
  • 常用于库的打包

4. AMD (Asynchronous Module Definition)

主要用于浏览器端的异步加载。

// 定义模块
define(['dependency1', 'dependency2'], function(dep1, dep2) {
  return {
    method: function() {
      return dep1.something() + dep2.something();
    }
  };
});

// 使用
require(['module'], function(module) {
  module.method();
});

特点

  • 异步加载,适合浏览器
  • RequireJS 是实现者
  • 声明依赖,回调执行

对比

特性ES6 ModulesCommonJSUMDAMD
加载方式同步/异步同步多种异步
环境浏览器/Node.jsNode.js所有环境浏览器
Tree-shaking支持不支持不支持不支持
静态分析支持不支持不支持部分支持
语法import/exportrequire/module.exports兼容语法define/require

现代项目中的使用

{
  "name": "@webgl2d/core",
  "version": "1.0.0",
  "description": "",
  "main": "lib/index.cjs.js",
  "module": "lib/index.es.js",
  "types": "lib/index.d.ts",
  "exports": {
    ".": {
      "types": "./lib/index.d.ts",
      "import": "./lib/index.es.js",
      "require": "./lib/index.cjs.js",
      "default": "./lib/index.umd.js"
    }
  },
  "scripts": {
    "dev": "vue-tsc -b && vite build --watch",
    "build": "vue-tsc -b && vite build"
  },
  "type": "module",
  "files": [
    "lib",
    "package.json"
  ],
  "keywords": [],
  "author": "",
  "license": "ISC",
  "packageManager": "pnpm@10.24.0",
  "dependencies": {
    "gl-matrix": "^3.4.4",
    "uuid": "^13.0.0",
    "vite-plugin-dts": "^4.5.4"
  },
  "devDependencies": {
    "@types/node": "^24.10.1",
    "@vitejs/plugin-vue": "^6.0.1",
    "typescript": "~5.9.3",
    "vite": "^7.2.4",
    "vue-tsc": "^3.1.4"
  }
}

构建工具中的配置

// WebGL2D Core Library Vite Configuration
import { defineConfig ,type UserConfig } from "vite";
import dts from "vite-plugin-dts";
import { resolve } from "path";

let config:UserConfig = {
  plugins: [
    dts({
      tsconfigPath: "tsconfig.app.json",
      insertTypesEntry: true,
      copyDtsFiles: true,
      // 指定需要为哪些文件生成类型定义
      include: [
        "src/**/*.d.ts",
        "src/**/*.vue",
        "src/**/*.ts",
        "index.ts",
      ],
    }),
  ],
  resolve: {
    alias: {
      "@": resolve(__dirname, "./src"),
    },
  },
  build: {
    outDir: "lib",
    lib: {
      name: "WebGL2DCore",
      entry: resolve(__dirname, "./index.ts"),
      fileName: (format, entryName) => `${entryName}.${format}.js`,
      formats: ["es", "umd", "cjs"],
    },
    rollupOptions: {
      // 确保外部化处理那些你不想打包进库的依赖
      external: ["vue"],
      output: {
        // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
        globals: {
          vue: "Vue",
        },
      },
    },
  },
}
export default defineConfig(config);

最佳实践

  1. 库开发:同时提供 ES6 和 CommonJS 版本

  2. 现代应用:优先使用 ES6 模块

  3. Node.js

    • 设置 "type": "module" 使用 ES6 模块
    • 或使用 .mjs 扩展名表示 ES6 模块
    • .cjs 扩展名表示 CommonJS
  4. 浏览器

    <!-- ES6 模块 -->
    <script type="module" src="app.js"></script>
    
    <!-- 传统脚本 -->
    <script nomodule src="legacy.js"></script>
    
  5. 动态导入(ES2020):

    // 动态导入,返回 Promise
    const module = await import('./module.js');
    

选择哪种模块系统取决于:

  • 目标运行环境(浏览器/Node.js)
  • 是否需要向后兼容
  • 构建工具的配置
  • 是否需要 tree-shaking 优化