
前端工程化
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"] |
常用第三方库
一、nrm
说明:
官方解释:nrm 是官方开发的
npm registry管理工具, 可以轻松地查看和切换当前使用的仓库源.
安装
bash
$ npm i -g nrm常用命令
bash
$ nrm ls # 查看所有的支持源(有*号的表示当前所使用的源,以下[name]表示源的名称)
$ nrm use [name] # 将npm下载源切换成指定的源
$ nrm help # 查看nrm帮助
$ nrm home [name] # 跳转到指定源的官网
$ nrm add [name url] # 添加自定义源(name是自定义源的名字,ulr是自定义源的url)
$ nrm del [name] # 删除源
$ nrm test [name] # 自动测试所有镜像源速度使用时遇到的疑问?
注意:
通过
nrm use [name]切换镜像之后,再nrm ls查看镜像列表时,如果 * 号 (代表我们当前使用的镜像源) 消失, 那么按照下面的操作即可恢复正常
提示:
例 nodejs/node_global/node_modules/nrm 在该目录下找到 cli.js,打开 cli.js 文件,找到函数 onList(文件 132 行)
前面这种通过修改源代码的方式已经失效了,可以通过帮助命令来查看怎么使用, 可以通过
nrm current来查看自己正在使用的镜像源是哪个
另外, 我已经亲测, 通过
cmd或者bash终端运行nrm ls能够正常显示*(即*标记的就是我们正在使用的npm镜像源).记录于 2024/1/27
二、ts-node
说明:
不编译生成
.js文件, 看起来像直接运行.ts文件一样, 但每次在写完代码之后都需要手动去执行一次命令
安装
bash
$ npm i -g ts-node # 全局安装 nrm 工具使用
bash
$ ts-node 文件名.tsbash
$ ts-node index.ts三、nodemon
说明:
能够“自动编译”TS 代码, 根据 TS、JS 文件代码的变更实时热更新(重启服务), 方便在终端查看效果, 算是
ts-node的升级版, 但是它依赖于ts-node, 也就是说, 需要提前安装ts-node.
安装
bash
$ npm install -g nodemon使用
说明:
直接运行
nodemon不接参数的话, 默认运行的是index.js文件, 如果想指定运行的文件, 可以在命令后面加上参数, 例如nodemon index.ts. 更多使用方法可以使用帮助命令nodemon --help来查看.
bash
$ nodemon # 默认运行 index.js 文件bash
$ nodemon index.ts四、nvm
注意:
这个不是 npm 包,需要单独下载的工具,工具安装、使用的教程推荐:参考博客
介绍:
主要功能:可以使用命令行方式更新
nodejs版本
NVM 允许用户:
- 使用简单的命令在本地下载任何远程长期支持 (LTS) 版本的 Node.js。
- 直接从命令行在 Node.js 的多个版本之间轻松切换。
- 设置别名以轻松在不同下载版本的
Node.js之间切换。
五、gnvm
推荐:
提示:
该工具需要单独下载,它只有一个文件,而且切换
Node.js版本的方式比上面这种工具更简单,它的安装、使用教程推荐:参考博客
gnvm和nvm二选一,总的来说gnvm类似于是对nvm的二次封装,让一些常用操作的命令都更容易记忆和使用.
六、tsc
作用:
主要功能:编译
typescript脚本生成同名的javascript脚本.
安装
bash
$ npm i -g typescript # 需要下载的包使用
bash
$ tsc ./main.ts # 后面可以接其他参数,详情参考 ts 语言官网七、发布 npm 包
推荐
规范参考:前往官网

