跳转到内容

感谢赞赏

微信
支付宝

感谢赞赏

赞赏码

前端工程化

JS 模块化规范演进历史

1. 原始的 JS 模块化

每个人编写的模块通过 script标签引入到页面中,然后就可以使用模块中的内容了。

2. CommonJS 模块化规范

随着 nodejs 的诞生,为了解决在 nodejs 中的模块化问题,社区自发形成了很多的模块化方案,其中 CommonJS 模块化规范得到了时间的检验,并最终确定为主流的模块化规范,这是一套社区的模块化规范,使用 commonjs 规范编写的模块并不能直接在浏览器中运行。后来出现了browserifywebpack 等编译、打包工具,使得 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)

注意事项:

  1. 每个模块内部的:thisexportsmodule.exports 在初始时,都指向同一个空对象,这个空对象就是当前模块导出的数据。
  2. 无论如何修改导出对象,最终导出的都是以module.exports 为准。
  3. 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模块化规范的导入导出方式:

  1. export 分别导出语法
js
export const add = (a, b) => a + b
  1. export 统一导出语法
js
const sub = (a, b) => a - b
const mul = (a, b) => a * b

export { sub, mul }
  1. export default 默认导出语法
js
export default (a, b) => a + b
  1. export { mul as alias } 在统一导出方式中取别名
js
export { mul as alias }
  1. import * as alias 统一导入语法(通用)
js
import * as utils from './utils.js'
  1. 命名导入
js
import { mul as multi } from './utils.js'
  1. 默认导入
js
import utils from './utils.js'
  1. 动态导入
js
async function main() {
  const utils = await import('./utils.js')
  console.log(utils)
}

main()
  1. 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)

注意事项:

  1. 如果使用了分别导出,则不能使用统一导出,否则会报 SyntaxError: Duplicate export of 'greet'。这两种导出本质是都是同一种方式。
  2. 使用 import 导入模块时,如果需要默认导入和按需导入在同一行,则默认导入需要写在最前面。
  3. 导入、导出都可以使用as关键字给变量名取别名。
  4. 如果要在服务端运行ES6模块化规范的代码,第一种方式可以在项目的package.json文件中添加"type": "module",第二种方式可以在文件名后添加.mjs后缀,表示该模块是ES6模块化规范的模块。

4. AMD 模块化规范

信息

AMDAsynchronous 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" }
licenseSPDX 许可证标识(如 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 文件名.ts
bash
$ 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 版本的方式比上面这种工具更简单,它的安装、使用教程推荐:参考博客

gnvmnvm 二选一,总的来说 gnvm 类似于是对 nvm 的二次封装,让一些常用操作的命令都更容易记忆和使用.

六、tsc

作用:

主要功能:编译 typescript 脚本生成同名的 javascript 脚本.

安装

bash
$ npm i -g typescript           # 需要下载的包

使用

bash
$ tsc ./main.ts         # 后面可以接其他参数,详情参考 ts 语言官网

七、发布 npm 包

推荐

规范参考:前往官网

用心创造世界,用技术改变未来。