requirejs模块化工具
RequireJS 完全指南:从入门到精通的模块化开发实践
虽然现在 ES Modules(ESM)已经成为前端模块化的主流,但在很多老项目、遗留系统中,RequireJS 依然是核心的模块加载器。最近帮团队重构一个老项目,又重新梳理了一遍 RequireJS 的完整用法,发现它的很多设计思想至今依然值得学习。
今天就结合官方文档和实战经验,把 RequireJS 从入门到精通的所有内容整理出来,从基本用法到高级配置,再到避坑指南,一篇文章讲透。
一、先搞懂:RequireJS 是什么?解决了什么问题?
RequireJS 是一个轻量级的 JavaScript 文件和模块加载器,是 AMD(Asynchronous Module Definition,异步模块定义)规范的最佳实现者之一。最新版本压缩后仅约 14KB,非常轻量,且兼容性极佳:
| 浏览器 | 兼容性 |
|---|---|
| IE 6+ | ✅ |
| Firefox 2+ | ✅ |
| Safari 3.2+ | ✅ |
| Chrome 3+ | ✅ |
| Opera 10+ | ✅ |
它不仅优化了浏览器端的模块加载,也能在 Node.js、Rhino 等 JavaScript 环境中使用。
传统开发的痛点
在没有模块化加载器的时代,我们是这样写代码的:
1 | |
这种方式带来了三个致命问题:
- JS 阻塞页面渲染:script 标签默认同步加载,会阻塞 DOM 渲染,导致页面白屏;
- 全局变量污染:所有模块的变量都挂在 window 上,容易命名冲突;
- 依赖关系混乱:必须手动保证 script 标签的加载顺序(比如 jQuery 必须在插件之前加载),维护成本极高。
RequireJS 的核心价值
RequireJS 就是为了解决这些问题而生的,它能带来:
- ✅ 异步加载,不阻塞渲染:模块并行加载,页面先显示内容;
- ✅ 模块化开发,避免全局污染:每个模块有独立作用域,通过 define/require 管理依赖;
- ✅ 自动管理依赖关系:不用手动维护 script 标签顺序,RequireJS 自动处理;
- ✅ 支持 CDN fallback:远程 CDN 加载失败时,自动切换到本地文件;
- ✅ 内置优化工具:r.js 可以将多个模块合并压缩,提升生产环境性能。
二、快速上手:5分钟写出第一个 RequireJS 模块
1. 下载与引入
去 RequireJS 官网 下载 require.js,放到项目的 js 目录下。
2. 目录结构建议
为了方便管理,推荐这样的目录结构:
1 | |
3. 入口文件:data-main
在 HTML 中引入 require.js,并通过 data-main 属性指定入口文件:
1 | |
注意:
data-main中的路径可以省略.js后缀,RequireJS 会自动补全。
4. 定义模块:define()
用 define() 函数定义一个模块,比如 js/app/a.js:
1 | |
如果模块有依赖,把依赖放在数组里:
1 | |
5. 加载模块:require()
在入口文件 js/main.js 中,用 require() 加载并使用模块:
1 | |
三、核心配置:require.config() 详解
require.config() 是 RequireJS 的核心,通过它可以灵活配置模块的加载规则。以下是最常用的配置选项:
1. baseUrl:模块根路径
baseUrl 是所有模块查找的根路径,默认是 data-main 指定的文件所在目录,或者 HTML 页面所在目录。
1 | |
2. paths:模块路径映射
paths 用于给模块起别名,或者配置远程 CDN 地址,还支持多个路径 fallback(CDN 失败时自动加载本地文件):
1 | |
注意:
paths中的路径不要加.js后缀,RequireJS 会自动补全。
3. shim:兼容非 AMD 模块
很多老库(比如旧版 jQuery、Backbone)没有用 define() 定义模块,这时候就需要 shim 来“垫”成可用的 AMD 模块。
shim 有两个核心配置:
deps:模块的依赖数组;exports:模块暴露的全局变量名。
1 | |
重要提醒:
shim仅用于非 AMD 模块,已经用define()定义的模块不要用shim,否则会出问题。
4. bundles:模块打包配置
bundles 用于配置“多个模块打包在一个文件中”的场景,告诉 RequireJS 哪些模块在同一个 bundle 里:
1 | |
当加载 util 时,RequireJS 会自动加载 primary 这个 bundle 文件。
5. map:模块版本映射
map 可以让不同的模块加载不同版本的依赖,解决版本冲突问题:
1 | |
6. 其他常用配置
1 | |
四、高级用法:解决复杂场景问题
1. 循环依赖处理
如果出现“a 依赖 b,b 依赖 a”的循环依赖,RequireJS 会在 b 的回调中给 a 传 undefined,这时候需要用 require() 动态获取 a:
1 | |
建议:尽量避免循环依赖,这通常意味着模块设计有问题。
2. 加载插件:text、domReady、i18n
RequireJS 支持丰富的加载插件,这里介绍三个最常用的:
(1)text.js:加载文本文件(HTML/CSS 模板)
下载 text.js,放到项目中,用 text! 前缀加载文本:
1 | |
(2)domReady.js:等待 DOM 加载完成
下载 domReady.js,用 domReady! 前缀确保回调在 DOM 就绪后执行:
1 | |
(3)i18n.js:国际化支持
下载 i18n.js,创建如下目录结构:
1 | |
根语言包 js/nls/colors.js:
1 | |
法语语言包 js/nls/fr-fr/colors.js:
1 | |
使用国际化模块:
1 | |
3. 错误处理
RequireJS 提供了三种错误处理方式:
(1)全局错误监听
1 | |
(2)require() 的错误回调
1 | |
(3)paths 数组 fallback
前面已经介绍过,paths 可以配置多个路径,自动 fallback:
1 | |
4. 多版本模块共存
通过 context 配置,可以在同一个页面加载多个版本的模块:
1 | |
5. 在 Node.js 中使用 RequireJS
RequireJS 也可以在 Node.js 中使用,实现前后端模块复用:
1 | |
Node.js 中使用:
1 | |
五、生产环境优化:用 r.js 合并压缩
RequireJS 自带了一个强大的优化工具 r.js,可以将多个模块合并压缩成一个文件,减少 HTTP 请求,提升性能。
1. 安装 r.js
1 | |
2. 编写构建配置文件
创建 build.js:
1 | |
3. 执行构建
1 | |
构建完成后,会生成 dist/main-built.js,这是一个合并压缩后的文件。
4. 生产环境使用
在 HTML 中直接引入构建后的文件:
1 | |
六、避坑指南:90%的人都会踩的坑
1. data-main 的异步问题
data-main 指定的文件是异步加载的,不要在它之后立即写 require():
1 | |
正确写法:把所有 require() 都放在 main.js 里,或者用嵌套 require():
1 | |
2. 模块定义的规范
- 一个文件一个模块:不要在一个文件里写多个
define(); - 不要手动给模块命名:
define('moduleName', [], function() {})会降低模块可移植性,让优化工具自动命名; - 相对路径的正确使用:在
define()中用相对路径时,要确保require是依赖之一:1
2
3define(['require', './relative/module'], function(require) {
var mod = require('./relative/module');
});
3. shim 的使用限制
shim仅用于非 AMD 模块,已经用define()定义的模块不要用;- 不要混合使用 CDN 和 shim:如果 jQuery 从 CDN 加载,shim 配置的 Backbone 可能会在 jQuery 加载前执行,导致错误。解决方案:构建时把 jQuery 内联到打包文件中。
4. 路径配置的注意事项
- 不要在
paths中写.js后缀; - 如果模块 ID 以
/开头、包含协议(http://)、以.js结尾,会直接作为 URL 处理,不经过baseUrl和paths配置。
七、写在最后
虽然现在 ES Modules(ESM)已经成为前端模块化的标准,Webpack、Vite 等构建工具也大行其道,但 RequireJS 依然有它的价值:
- 很多老项目、遗留系统还在使用 RequireJS,维护和重构都需要它;
- 它的异步加载、依赖管理思想,对理解模块化开发依然有很大帮助;
- 在一些不需要构建工具的简单场景,RequireJS 依然是轻量高效的选择。
技术迭代很快,但核心思想是相通的。希望这篇文章能帮你彻底搞懂 RequireJS,无论是维护老项目还是学习模块化思想,都能有所收获。