跳转到内容

感谢赞赏

微信
支付宝

感谢赞赏

赞赏码

前端工程化

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"]

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