跳转到内容

扫码关注

扫码关注

感谢赞赏

微信
支付宝

前端工程化 ​

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)

注意事项:

  1. 每个模块内部的:this、exports、module.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 模块化规范 ​

信息

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" }
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 版本的方式比上面这种工具更简单,它的安装、使用教程推荐:参考博客

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

六、tsc ​

作用:

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

安装 ​

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

使用 ​

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

七、发布 npm 包 ​

推荐

规范参考:前往官网

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