
前端工程化
JS 模块化规范演进历史
1. 原始的 JS 模块化
每个人编写的模块通过 script标签引入到页面中,然后就可以使用模块中的内容了。
2. CommonJS 模块化规范
随着 nodejs 的诞生,为了解决在 nodejs 中的模块化问题,社区自发形成了很多的模块化方案,其中 CommonJS 模块化规范得到了时间的检验,并最终确定为主流的模块化规范,这是一套社区的模块化规范,使用 commonjs 规范编写的模块并不能直接在浏览器中运行。后来出现了browserify,webpack 等编译、打包工具,使得 CommonJS 模块化规范编写的代码经过编译、打包后也可以用于浏览器中运行。
js
// 1. 使用 exports 导出模块
exports.add = (a, b) => a + b
// 2. 使用 module.exports 导出模块
module.exports = {
add,
}js
const utils = require('./utils')
// 或者加后缀
// const utils = require('./utils.js')
// 也可以解构方式导入
// const { add } = require('./utils.js')
const result = utils.add(1, 2)注意事项:
- 每个模块内部的:
this、exports、module.exports在初始时,都指向同一个空对象,这个空对象就是当前模块导出的数据。 - 无论如何修改导出对象,最终导出的都是以
module.exports为准。 exports是对module.exports的引用,仅为了方便给导出对象添加属性,所以不能使用exports = value的形式导出数据,但是可以使用module.exports = value的形式导出数据。
- ⚠️ 在
commonjs模块化规范中,实际是通过内置函数来实现模块化的,我们可以通过arguments.callee.toString()获取当前模块的代码,从以下的代码我们可以看出,一个 JS 模块在执行时,是被包裹在一个内置函数中执行的,所以每个模块都有自己的作用域。
js
// utils.js
function (exports, require, module, __filename, __dirname) {
exports.add = (a, b) => {
return a + b
}
exports.sub = (a, b) => {
return a - b
}
console.log(arguments.callee.toString())
}3. ESM 模块化规范
信息
ES6模块化规范是ECMA官方标准的规范,它是在语言标准的层面上实现了模块化功能,是目前最流行的模块化规范,且浏览器与服务器均支持该规范。
ES6模块化规范的导入导出方式:
export分别导出语法
js
export const add = (a, b) => a + bexport统一导出语法
js
const sub = (a, b) => a - b
const mul = (a, b) => a * b
export { sub, mul }export default默认导出语法
js
export default (a, b) => a + bexport { mul as alias }在统一导出方式中取别名
js
export { mul as alias }import * as alias统一导入语法(通用)
js
import * as utils from './utils.js'- 命名导入
js
import { mul as multi } from './utils.js'- 默认导入
js
import utils from './utils.js'- 动态导入
js
async function main() {
const utils = await import('./utils.js')
console.log(utils)
}
main()import可以不接受任何数据
js
import './utils.js'提示
通过模块化解决了:(1)全局污染问题 (2)依赖混乱问题 (3)数据安全问题
js
const add = (a, b) => a + b
const sub = (a, b) => a - b
const mul = (a, b) => a * b
// 分别导出
// export const greet = (name) => `hello ${name}` // 错误演示
// 不能既分别导出又统一导出,否则会报 SyntaxError: Duplicate export of 'greet'
// 统一导出
export {
add,
// 使用 as 给导出模块取别名,取的别名如果是 default, 则该模块会等价 export default 默认导出
mul as default,
mul,
sub, // greet
}js
import mul, { add, mul as multi, sub } from './utils.js'
// 如果需要默认导入和按需导入在同一行,则默认导入需要写在最前面
console.log(add, sub, mul, multi)注意事项:
- 如果使用了
分别导出,则不能使用统一导出,否则会报SyntaxError: Duplicate export of 'greet'。这两种导出本质是都是同一种方式。 - 使用
import导入模块时,如果需要默认导入和按需导入在同一行,则默认导入需要写在最前面。 - 导入、导出都可以使用
as关键字给变量名取别名。 - 如果要在服务端运行
ES6模块化规范的代码,第一种方式可以在项目的package.json文件中添加"type": "module",第二种方式可以在文件名后添加.mjs后缀,表示该模块是ES6模块化规范的模块。
4. AMD 模块化规范
信息
AMD 是Asynchronous Module Definition的缩写,即异步模块定义。它是一种模块化规范,主要用于浏览器端。该模块化规范需要依赖require.js库,所以需要先引入require.js库,然后才能使用AMD模块化规范。
- 引入
require.js库
在
require.js库中,define函数用于定义模块,require函数用于导入模块。在引入require.js库后,需要在script标签上指定模块的入口文件data-main属性,该属性值为模块的入口文件路径。
html
<script data-main="./main.js" src="https://cdn.bootcdn.net/ajax/libs/require.js/2.3.7/require.min.js"></script>- 在
main.js文件中编写配置,注册模块
js
/** AMD 模块化的入口文件,要编写配置对象,并且有固定写法 */
requirejs.config({
// 基本路径
baseUrl: './js',
// 模块表示名 与 模块路径的映射关系
paths: {
utils: 'utils',
math: 'math',
},
})js
/** AMD 使用 require 函数的第一个参数表示依赖模块,
* 第二个参数表示回调函数,回调函数中的参数表示依赖模块的导出结果,会按照依赖顺序将模块对象依次传入到对应形参上
*/
require(['utils', 'math'], function (utils, math) {
console.log(utils)
console.log(math)
const r1 = utils.getTime()
const r2 = math.add(1, 2)
console.log('当前时间:', r1, '\n1 + 2 = ', r2)
})- 定义模块
js
// ./js/math.js
const add = (a, b) => a + b
const sub = (a, b) => a - b
define(function () {
return {
add,
sub,
}
})js
// ./js/utils.js
function getTime() {
const t = new Date()
const hours = t.getHours()
const minutes = t.getMinutes()
const seconds = t.getSeconds()
return `${padZero(hours)}:${padZero(minutes)}:${padZero(seconds)}`
}
function padZero(n) {
return n.toString().padStart(2, '0')
}
// AMD 使用 define 函数定义模块
define(function () {
return {
getTime,
padZero,
}
})上古模块化规范
- UMD
- CMD
前端工程化是什么
前端工程化是指使用工程化工具和方法,将前端开发过程中的各个环节进行规范化、自动化和优化,以提高开发效率和代码质量。
前端工程化是一个非常广泛的概念,它包括了前端开发的方方面面,比如:
- 构建
- 规范
- 质量
- 性能
- 安全
- 维护
- 测试
- 部署
- 监控
- 分析
packages.json 配置
packages.json是 Node.js 项目中的一个重要文件,用于管理项目依赖、配置和脚本。
- 包的基础配置字段如下:
| 字段 | 描述 | 示例 |
|---|---|---|
| name | 包名。pnpm add vitepress、代码里 import 'vitepress' 解析到的就是这个名字(在 npm 注册表/ workspace 里唯一标识)。 | "name": "vitepress" |
| version | 语义化版本号,供依赖解析、changelog、发布脚本使用。 | "version": "1.0.0" |
| description | 简短说明,主要在 npm 包页、部分工具展示里用。 | "description": "这是一个示例项目" |
| keywords | 关键词数组,方便在 npm 上被搜索到。 | "keywords": ["node", "express", "static"] |
| author | 作者字段,给人看或给工具用,不参与运行时解析。 | "author": "John Doe <john.doe@example.com>" |
| homepage | 项目主页 URL(文档站等)。 | "homepage": "https://vitepress.dev" |
| bugs | 提 Issue 的入口(一般是 GitHub Issues),部分客户端会链过去。 | "bugs": { "url": "https://github.com/vuejs/vitepress/issues" } |
| repository | 源码仓库位置(github:vuejs/vitepress 这种简写或完整 URL),方便工具链、Renovate 等识别。 | "repository": { "type": "git", "url": "https://github.com/vuejs/vitepress.git" } |
| license | SPDX 许可证标识(如 MIT),影响合规与再分发。 | "license": "MIT" |
- 模块系统解析配置字段如下:
| 字段 | 描述 | 示例 |
|---|---|---|
| type | 标记该包的模块系统类型 | "type": "module" |
| main | 传统主入口(CJS),即别人通过 require('xxx') 导入你的包时,默认会找这个文件。 | "main": "index.js" |
| types | 传统类型入口:告诉 TypeScript「根包名对应的 .d.ts 在哪」。有 exports 时,现代工具会优先看 exports 里的 types 条件。 | "types": "index.d.ts" |
| exports | 声明本包对外暴露的 子路径入口 及 条件导出,别人通过 import 'xxx' 或 require('xxx') 导入你的包时,会根据条件匹配到对应的文件。 | "exports": { ".": { "types": "./types/index.d.ts", "default": "./dist/node/index.js"}} |
| files | 声明本包对外暴露的文件列表,别人通过 import 'xxx' 或 require('xxx') 导入你的包时,会根据文件列表匹配到对应的文件。 | "files": ["dist", "types"] |

