前端工程化:CommonJS、AMD、CMD、UMD和ES Modules的区别

本文详细探讨了前端工程化中的模块化技术,包括CommonJS、AMD、CMD、UMD及ES6 Modules,对比了它们的特点与适用场景,帮助开发者更好地理解和选择合适的模块化方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前端工程化思想一直以来都是一个非常热议的话题,其实前端工程化从技术方面来说,无非就是这么几点:CommonJS、AMD、CMD、UMD和ES6 Modules,那么我们就来说说前端这几个模块。

1、CommonJS

说到CommonJS,那就的从他的出发点说起了,commonjs的出发点是js没有完善的模块系统。标准库较少,缺少包管理工具,而在node.js兴起之后。能让js在任何地方运行,特别是服务端,也能具备开发大型项目的能力,所以commonjs也就应用而生了。node.js是commonjs的最佳实践者。他有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global.实际使用时,用module.exports定义当前模块对外输出的接口(不推荐直接使用exports),用require加载模块。

commonjs用同步的方式加载模块,在服务端,模块文件够存储在本地磁盘,读取会非常快。所以这样做不会有问题。但是在浏览器端,限制于网络等原因,更合理的加载方式应该是异步加载。

暴露模块:module.exports = value;或者exports.xxx = value;

引入模块:const xxx = require('xxxx')

commonjs规范:

  • 一个文件就是一个模块,拥有单独的作用域;
  • 普通方式定义的变量、函数、对象属于该模块内;
  • 通过require来加载模块;
  • 通过module.exports或者exports来暴露模块中的内容;

注意:

  1. 当module.exports和exports同时存在的时候,module.exports会覆盖exports;
  2. 当模块内全都是exports是,其实就等同于module.exports;
  3. exports其实就是module.exports的子集;
  4. 所有代码都是运行在模块作用域,不会污染全局作用域;
  5. 模块可以被多次加载,但是只是会在第一次运行的时候加载。然后运行结果就会被缓存了。以后的加载就直接读取缓存的结果;
  6. 模块加载的顺序是按照代码出现的顺序同步加载的;
  7. __dirname代表当前模块所在的文件路径
  8. __filename代表当前模块文件所在的文件路径+文件名

2、AMD

Asynchronous Module Definition,异步加载模块。他是在一个在浏览器端模块化的开发规范。不是原生js的规范。使用AMD规范进行页面需求开发,需要用到对应的函数库,require.js。

AMD采用异步加载的模块的方式。模块的加载并不会影响到后面语句的执行。所有以来这个模块的语句都定义在一个回掉函数中,等到加载完成之后,这个回掉函数才会执行。

使用require.js来实现AMD规范的模块化:用require.config()指定引用路径。用define()来定义模块用require来加载模块。

define('moduleName',['a','b'],function(ma, mb) {
    return someExportValue;
})

require(['a', 'b'], function(ma, mb) {
    //    do something
})

require主要解决的问题:

  • 文件可以有依赖关系,被依赖的文件需要早于依赖它的文件加载的浏览器
  • js加载的时候浏览会停滞页面的渲染,加载文件越多,页面响应的时间就会越长
  • 异步加载前置

语法:difine(id, dependencies, factory)

  • id:可选参数,用来定义模块的标识,如果没有提供该参数,脚本文件名(去掉拓展名)
  • dependencies:是一个当前模块要用的模块名称组成的数组
  • factory:工厂方法,模块初始化要执行的函数,或者对象,如果改为函数,他应该只被执行一次,如果是对象,此对象应该为模块的输出值。

3、CMD

CMD是另一种模块化方案,它和AMD很类似,不同点在于:AMD推崇依赖前置,提前执行,而CMD推崇依赖就近,延迟执行,这个规范其实就是sea.js推广过程产生的。

因为CMD推崇一个文件一个模块。因此经常会用文件名作为模块id,CMD推崇依赖就近,所以一般不在define的参数中写依赖,而是在factory中写:define(id,deps,factory)

factory: function(require,exports,module) {}

  • require:factory的第一个参数,用来获取其他模块提供的接口
  • exports:一个对象,用来向外提供模块接口
  • module:一个对象,上面存储了与当前模块相关联的一些属性和方法
//    定义没有依赖的模块
define(function(require, exports, module){
    exports.xxx = value
    module.exports = value
})

//    定义有依赖的模块
define(function(require, exports, module){
    var module1 = require('./module1.js')
    require.async('./module.js', function(m2) {
        //    do something
    })
    export.xxx = value
})

//    引入模块
define(function(require, exports, module){
    const m1 = require('./module.js')
    me.show()
})

4、UMD

一个整合了commonJS和AMD规范的方法。希望能解决跨平台模块的解决方案。

运行的原理:

UMD会先判断是否支持Node.js的模块(export)是不是存在。存在则使用node.js的模块方式。

再判断是不是支持AMD(define是不是存在。存在则使用AMD方式加载模块。

(function(window, factory){
    if (typeof exports === 'object') {
        module.exports = factory()
    } else if (typeof define === 'function' && define.amd) {
        define(factory)
    } else {
        window.eventUtil = factory()
    }
})(this,function () {
    //    do something
})

5、ES6Module

ES6在语言标准的层面上,实现了模块化功能,而且实现的相当简单,旨在成为浏览器和服务器通用的模块化解决方案,其模块化功能主要由俩个命令构成:exports和import,export命令由于规定模块的对外接口,import命令用于输入其他模块的功能。其实ES6还提供了export default的命令。为模块指定默认输出。对应的import语句不需要大括号。这也更接近AMD的引用写法。

ES6模块不是对象,import命令被JavaScript引擎静态分析,在编译的时候就引入模块代码。而不是在代码运行时加载,所以无法实现条件加载。也就使得静态分析成为可能。

(1)export

export可以导出的是对象中包含多个属性、方法,export default只能导出一个可以不具名的函数。我们可以输用import引入。同时我们也可以直接使用require使用,原因是webpack启用了server相关。

(2)import

import { fn } from './xxx' //    export导出的方式

import fn from 'xx' //    export default方式

总结:

commonJS是同步加载的,主要是node.js也就是服务端也应用的模块化机制。通过module.exports导出声明。通过require('')加载。每个文件都是一个模块。他有自己的独立的作用域。文件内的变量,属性函数等不能被外界访问,node会将模块缓存,在第二次加载的时候会从缓存中获取。

AMD是异步加载的,主要是在浏览器环境下应用,require.js是遵守AMD规范的模块化工具。它通过define()定义声明,通过require('',function() {})来加载。

ES6模块化加载时,通过exports default导出。用import来导入,可以通过对导出内容进行解构。ES6模块运行机制与commonjs运行机制不一样。js引擎对脚本静态分析的时候,遇到模块加载指令后会生成一个只读引用。等到脚本真正执行的时候。才会通过引用模块中获取值,在引用到执行的过程中,模块中的值发生变化,导入的这里也会跟着发生变化。ES6模块是动态引入的。并不会缓存值。模块里总是绑定其所在的模块。

插件的工程化编写 本节内容 本节将介绍如何使用 Node.js 项目编译出海豹可使用的插件,面向有前端经验的开发者。 我们假定你了解如何使用前端工具链,你应当具备诸如命令行、Node.js、npm/pnpm 等工具的使用知识。如果你对这些内容感到陌生,请自行了解或转至 使用单 JS 文件编写,手册不会介绍这些相关背景知识。 如果你打算使用 TypeScript,或者需要编写大型插件,希望更加工程化以方便维护,可以创建项目使用前端工具链来编译出插件。 海豹提供了相应的 模板项目。注册扩展指令的代码已经写好,可以直接编译出一个可直接装载的 JS 扩展文件。 Clone 或下载模板项目 推荐的流程: 在 模板项目仓库 点击 Use this template 按钮,使用该模板在自己的 GitHub 上创建一个扩展的仓库,并设置为自己的扩展的名字; git clone 到本地,进行开发。 如果不打算使用 GitHub 托管仓库,希望先在本地编写: 在 模板项目仓库 点击 Code 按钮,在出现的浮窗中选择 Download ZIP,这样就会下载一个压缩包; 解压后进行开发。 补全信息 当插件开发完成后(或者开始开发时),你还需要修改几处地方: header.txt:这个文件是你插件的描述信息; tools/build-config.js:最开头一行 var filename = 'sealdce-js-ext.js'; 改成你中意的名字,注意不要与现有的重名。这决定了编译时输出的插件文件名。 (可选)package.json:修改其中 name version description 等项目描述信息,不过不修改也不会影响编译。 使用编译 在确认你所使用的包管理器后,在命令行使用如下命令安装依赖: npm install 当你写好了代码,需要工程编译为插件的单 js 文件以便上传到海豹骰时,在命令行使用如下命令: npm run build 编译成功的 js 文件在 dist 目录下,默认的名字是 sealdce-js-ext.js。 目录结构 只列出其中主要的一些文件 src index.ts:你的扩展的代码就写在这个文件里。 tools build-config.js:一些编译的配置,影响 index.ts 编译成 js 文件的方式; build.js:在命令 npm run build 执行时所运行的脚本,用于读取 build-config 并按照配置进行编译。 types seal.d.ts:类型文件,海豹核心提供的扩展 API。 header.txt:扩展头信息,会在编译时自动加到目标文件头部; package.json:命令 npm install 时就在安装这个文件里面所指示的依赖包; tsconfig.json:TypeScript 的配置文件。 其他问题 我能在项目中引用 npm 包吗? 当然可以,像正常的前端项目一样,你可以在其中引用其他 npm 包,比如模板项目中就为你引入了常用的 lodash-es。 一般来说纯 JS 编写的包都是可以引用的,一些强 native 相关的包可能存在兼容性问题,你需要自行尝试。 推荐你尽量使用 esm 格式的包,不过 commonjs 格式的包也是可以使用的,如 dayjs。其他格式的支持更多问题排查,请查阅模板项目所使用的构建工具 esbuild 的文档,tools/build-config.js 中即是 esbuild 的配置项。 我想使用的 API 没有被自动提示,直接使用被提示错误,如何解决? types/seal.d.ts 文件中维护了海豹提供的 API,但目前来说维护的并不完全。如果你发现有一些存在的 API 未被提示,可以手动在 types/seal.d.ts 补上来解决报错。 有时 seal.d.ts 会有更新,可以去模板项目仓库看看有没有最新的,有的话可以替换到你的项目中。也非常欢迎你向模板仓库提 PR 来帮忙完善。 默认输出的插件代码是压缩过的,如何尽量保持产物的可读性? 调整 tools/build-config.js 中的选项,关闭 minify: module.exports = { ... build: { ... minify: false, ... } } 这个是它的js排堆编写方式,不知道能不能更简便地达成上面的要求
最新发布
07-11
<think>我们正在讨论的是如何工程化开发海豹插件。海豹核心使用Node.js,因此我们可以使用现代Node.js项目开发方式。以下步骤将涵盖从初始化到部署的全过程: ### 1. 项目初始化 ```bash mkdir seal-plugin-project cd seal-plugin-project npm init -y ``` ### 2. 目录结构设计 ``` ├── src/ # 源代码 │ ├── plugins/ # 插件目录 │ │ └── vote.js # 投票插件示例 │ └── index.js # 入口文件 ├── config/ # 配置文件 ├── test/ # 单元测试 ├── scripts/ # 构建脚本 ├── package.json ├── webpack.config.js # 打包配置 └── README.md ``` ### 3. 配置开发环境 安装基础依赖: ```bash npm install --save seal-js-sdk # 海豹官方SDK npm install --save-dev webpack webpack-cli babel-loader @babel/core @babel/preset-env ``` ### 4. Webpack配置示例 (`webpack.config.js`) ```javascript const path = require('path'); module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js', library: 'MySealPlugins', libraryTarget: 'umd' }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } } ] } }; ``` ### 5. 插件开发规范 `src/plugins/vote.js` 示例: ```javascript import { Plugin, command } from 'seal-js-sdk'; export default class VotePlugin extends Plugin { constructor() { super('vote', '投票系统'); this.voteStatus = 'idle'; } @command('vote start') startVote(ctx, ...options) { // 解析参数逻辑 this.voteStatus = 'active'; return ctx.reply('投票已启动!'); } // 其他命令... } ``` ### 6. 入口文件整合 (`src/index.js`) ```javascript import VotePlugin from './plugins/vote'; export default { install(seal) { seal.registerPlugin(new VotePlugin()); } }; ``` ### 7. 构建与打包 在`package.json`中添加构建脚本: ```json { "scripts": { "build": "webpack --mode production" } } ``` 运行构建: ```bash npm run build ``` ### 8. 测试部署 将生成的`dist/bundle.js`导入海豹核心: ```javascript const seal = require('seal-core'); const MyPlugins = require('./dist/bundle'); seal.use(MyPlugins); ``` ### 工程化最佳实践 1. **模块化设计** 每个插件独立文件,通过入口文件统一导出 2. **使用装饰器** 利用`@command`等装饰器清晰定义指令[^1] 3. **持续集成** 配置CI/CD自动测试构建: ```yaml # .github/workflows/ci.yml name: CI on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 - run: npm ci - run: npm run build ``` 4. **单元测试** 使用Mocha/Jest进行插件测试: ```javascript describe('VotePlugin', () => { it('should start vote', () => { const plugin = new VotePlugin(); const ctx = mockContext(); plugin.startVote(ctx, 'option1', 'option2'); assert.equal(plugin.voteStatus, 'active'); }); }); ``` 5. **版本管理** 遵循语义化版本控制(SemVer): ```bash npm version patch # 修复bug npm version minor # 新增功能 npm version major # 重大变更 ``` ### 现代工具链整合 | 工具 | 用途 | |---------------|--------------------------| | TypeScript | 静态类型检查 | | ESLint | 代码规范检查 | | Prettier | 代码格式化 | | Husky | Git钩子管理 | | Jest | 单元测试 | | GitHub Actions| CI/CD流水线 | ### 注意事项 1. **海豹核心兼容性** 注意SDK版本与海豹核心的兼容性 2. **错误边界处理** 插件内添加异常捕获: ```javascript try { // 插件逻辑 } catch (err) { ctx.reply(`插件错误: ${err.message}`); } ``` 3. **性能优化** 避免阻塞操作,使用异步处理耗时任务 4. **文档生成** 使用JSDoc生成插件文档: ```bash npm install --save-dev jsdoc ``` > 通过以上工程化实践,可构建出高质量、易维护的海豹插件系统。实际开发中建议采用TypeScript以获得更好的类型安全[^1]。 §§相关问题§§ 1. 如何为海豹插件编写单元测试? 2. TypeScript在海豹插件开发中的具体应用? 3. 如何实现海豹插件的热更新功能?</think>以下是基于 Node.js 现代前端工具链工程化开发海豹插件的完整指南,结合最佳实践行业标准: ### 一、工程化开发海豹插件的完整流程 #### 1. 项目初始化与架构设计 ```bash # 创建项目目录 mkdir seal-plugin-vote && cd seal-plugin-vote # 初始化 Node.js 项目 npm init -y # 安装海豹核心依赖 npm install seal-core --save # 创建目录结构 mkdir -p src/{commands,services,utils} test config ``` **推荐目录结构**: ``` ├── src/ │ ├── commands/ # 指令处理模块 │ ├── services/ # 核心业务逻辑 │ ├── utils/ # 工具函数 │ └── index.js # 插件入口 ├── test/ # 单元测试 ├── config/ # 配置文件 ├── webpack.config.js # 构建配置 └── package.json ``` #### 2. 配置现代工具链 **安装开发依赖**: ```bash npm install -D webpack webpack-cli babel-loader @babel/core @babel/preset-env eslint prettier jest ``` **webpack 配置示例** (`webpack.config.js`): ```javascript const path = require('path'); module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'vote-plugin.bundle.js', library: 'VotePlugin', libraryTarget: 'umd' }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } } ] } }; ``` #### 3. 插件核心实现 **业务逻辑层** (`src/services/voteService.js`): ```javascript class VoteService { constructor() { this.voteStatus = 'idle'; this.voteRecords = new Map(); this.voteOptions = []; } startVote(options, duration) { this.voteOptions = options; this.voteStatus = 'active'; this.voteRecords.clear(); // 设置定时结束 this.endTimer = setTimeout(() => this.endVote(), duration * 60 * 1000); return `投票开始!选项:${options.join(' | ')}`; } castVote(userId, choice) { if (this.voteStatus !== 'active') return '当前无活跃投票'; if (this.voteRecords.has(userId)) return '您已投票'; this.voteRecords.set(userId, choice); return `投票成功!选择:${this.voteOptions[choice]}`; } endVote() { if (this.voteStatus !== 'active') return; clearTimeout(this.endTimer); this.voteStatus = 'ended'; // 统计结果 const results = this.calculateResults(); return this.formatResults(results); } calculateResults() { const counts = new Array(this.voteOptions.length).fill(0); for (const choice of this.voteRecords.values()) { counts[choice]++; } return counts; } formatResults(counts) { let output = "投票结果:\n"; this.voteOptions.forEach((option, i) => { output += `[${i}] ${option}: ${counts[i]}票\n`; }); // 找出获胜者 const maxVotes = Math.max(...counts); const winners = counts .map((count, i) => ({ count, option: this.voteOptions[i] })) .filter(item => item.count === maxVotes); if (winners.length === 1) { output += `\n🎉 获胜者:${winners[0].option}`; } else { output += `\n🏆 并列获胜:${winners.map(w => w.option).join(',')}`; } return output; } } module.exports = VoteService; ``` #### 4. 指令适配层 (`src/commands/voteCommand.js`) ```javascript const VoteService = require('../services/voteService'); module.exports = (seal) => { const voteService = new VoteService(); seal.command('vote', { start: { description: '启动新投票', execute: (ctx, args) => { const options = []; let duration = 5; // 默认5分钟 // 解析参数:.vote start 选项1 选项2 -t 10 for (let i = 0; i < args.length; i++) { if (args[i] === '-t' && args[i+1]) { duration = parseInt(args[i+1], 10); i++; // 跳过时间参数 } else { options.push(args[i]); } } return voteService.startVote(options, duration); } }, choose: { description: '投票选择', execute: (ctx, args) => { if (!args[0]) return '请指定选项编号'; return voteService.castVote(ctx.userId, parseInt(args[0], 10)); } }, end: { description: '结束投票', execute: () => voteService.endVote() } }); }; ``` #### 5. 插件入口 (`src/index.js`) ```javascript const initVoteCommand = require('./commands/voteCommand'); module.exports = (seal) => { // 注册指令 initVoteCommand(seal); // 注册定时任务 seal.scheduler.registerJob('vote-check', { cron: '*/1 * * * *', // 每分钟检查 execute: () => { // 自动结束超时投票的逻辑 } }); console.log('投票插件已加载'); }; ``` ### 二、工程化最佳实践 1. **模块化设计** - 分离核心逻辑与平台适配层 - 使用依赖注入模式 ```javascript // 在测试中可注入模拟依赖 const testService = new VoteService(mockStorage); ``` 2. **配置管理** ```javascript // config/default.js module.exports = { defaultVoteDuration: 5, // 分钟 maxOptions: 10, allowAnonymous: false }; ``` 3. **单元测试** (使用 Jest) ```javascript // test/voteService.test.js describe('VoteService', () => { let service; beforeEach(() => { service = new VoteService(); }); test('应正确统计投票结果', () => { service.startVote(['A', 'B'], 5); service.castVote('user1', 0); service.castVote('user2', 1); service.castVote('user3', 0); const result = service.endVote(); expect(result).toContain('A: 2票'); expect(result).toContain('获胜者:A'); }); }); ``` 4. **持续集成配置** (GitHub Actions) ```yaml name: CI on: [push] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: '16' - run: npm ci - run: npm test ``` ### 三、高级优化技巧 1. **持久化存储** ```javascript // 使用 Redis 或 LevelDB 代替内存存储 const { Level } = require('level'); const db = new Level('./vote-data'); async saveVoteState() { await db.put('vote-state', JSON.stringify({ status: this.voteStatus, options: this.voteOptions, records: [...this.voteRecords] })); } ``` 2. **性能优化** - 使用增量统计代替全量计算 - 实现投票结果的懒计算 ```javascript get results() { if (!this._results) { this._results = this.calculateResults(); } return this._results; } ``` 3. **安全防护** ```javascript castVote(userId, choice) { // 防止注入攻击 if (typeof choice !== 'number' || !Number.isInteger(choice)) { throw new Error('无效的选项类型'); } // 防止刷票 if (this.voteRecords.has(userId)) { throw new Error('重复投票'); } } ``` ### 四、部署流程 1. **构建插件** ```bash npm run build # 执行 webpack 构建 ``` 2. **集成到海豹核心** ```javascript // 海豹主程序 const seal = require('seal-core'); const VotePlugin = require('./dist/vote-plugin.bundle'); seal.use(VotePlugin); ``` 3. **容器化部署** (Docker) ```Dockerfile FROM node:16-alpine WORKDIR /app COPY package*.json ./ RUN npm install --production COPY dist ./dist CMD ["node", "seal-main.js"] ``` > 通过以上工程化实践,可构建出高性能、可维护的海豹插件。实际开发中建议采用 TypeScript 获得更好的类型安全[^1],并使用 CI/CD 流程确保质量。 ### 五、监控与维护 1. 添加日志记录: ```javascript const winston = require('winston'); const logger = winston.createLogger({ level: 'info', format: winston.format.json(), transports: [new winston.transports.File({ filename: 'vote-plugin.log' })] }); ``` 2. 实现健康检查接口: ```javascript seal.get('/vote/health', (req, res) => { res.json({ status: 'ok', activeVotes: voteService.activeCount }); }); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值