Jacky's blog Jacky's blog
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Jacky Wang

行到水穷处,坐看云起时
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Node

  • VSCode插件

  • Webpack

    • webpack源码分析一:AST语法
    • webpack源码分析二:模块化语法
      • 0.前言
      • 1. 前期知识回顾
        • EMAScript规范
        • CommonJS
      • 2.ESModule 转译到CommonJS
        • 转译解析1
        • 转译解析2
        • 转译解析3
        • 转译解析总结
      • 3.应用:构建建简易的Webpack打包器
        • 改造1:通过在 code 函数外部再包一层函数
        • 改造2:构造 execut 函数
        • 总览打包器代码:
      • 4.总结
    • webpack源码分析三:源码分析
    • webpack 配置[TODO]
    • 如何利用requireContext实现批量导入
  • Redux

  • React源码

  • 组件库

  • React高阶系列

  • UMI插件

  • 前端工程化

  • 单元测试vitest

  • 重点技术
  • Webpack
wangjiasheng
2022-03-28
目录

webpack源码分析二:模块化语法

# 0.前言

本篇博客为webpack_deom02 (opens new window)项目仓库笔记,主要解决两个问题:

  1. 如何把 es6 模块化语法转为commonJS 模块语法。
  2. 如何将所有依赖打包并执行,即bundle.js 文件。

# 1. 前期知识回顾

# EMAScript规范

// 导入语法
import math from "./math"  // default 导出
math.basicNum
math.add
import { basicNum, add } from "./math" // 非 default 导出

// 导出语法
// 1. 统一导出
export default { add,basicNum } 
export {add,basicNum}

// 2. 各自导出
export const add = () => {}
export const basicNum = 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# CommonJS

// 导入
const math = require("./math") 

// 导出语法
// 1.统一导出
module.exports = {
  add: add
  basicNum : basicNum
}
// 以上写法等价于下面(可以简化为写法二)
module.exports.add = add

// 2.使用 exports 导出
exports.add = add 
// 注:使用简化写法导出时,切记不能整体挂载,如:exports = { add, basicNum} 
// 这种写法是错误的,因为 CommonJS 会在头部自动加上 exports = module.exports 
// 重新赋值后,exports 将会失去 modele.exports 的引用地址。

// 结论:
// 若在 CommonJS 规范中,我们只使用 `module.expots` 导出语法,`exports`(会存在被误覆盖的情况)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

在转化过程中,我们着重关注导出过程,CommonJS 规范默认相当于 EMAScript 语法的default 导出,如果想要直接获取内部的变量属性或者方法,可以这么写:

const add = require("./math").add
const basicNum = require("./math").basicNum
1
2

转译目标:

  1. 将 es6的导入import →\rightarrow→ commonJS的导入require
  2. 将 es6的两种导出语法:default 导出 与各自导出语法 export {x} →\rightarrow→ commonJS 的导出语法 module.exports

# 2.ESModule 转译到CommonJS

在【案例1】中的compare 文件夹中已经列出了经@babel/preset-env转换前后的代码,共可以发现3处不同:

# 转译解析1

// 转译后,在头部多出以下字段
Object.defineProperty(exports, "__esModule", {value: true}); 
exports["default"] = void 0; // void 0 等价于 undefined
1
2
3

可以将 Object.defineProperty 语法简化为以下:

exports["__esModule"] = true
exports["default"] = undefined
1
2

以上两个片段的代码是等价的,从简化的代码可以发现主要做了如下处理:

  1. exports 字段上添加了__esModule 属性。
  2. 将 exports 字段上的 default属性清空。

# 转译解析2

// 转译前: 
import b from "./b.js"

// 转译后:
var _b = _interopRequireDefault(require("./b.js"));
function _interopRequireDefault(obj) {
	return obj && obj.__esModule ? obj : { "default": obj };
}
1
2
3
4
5
6
7
8

如果没有_interopRequireDefault 函数,则就只是将 import 转为 require语法。

区别在于 b.js 模块本身的内容,分为ES6 模块还是 CommonJS模块:

  1. 如果b.js是 ESMoudle 模块,则等价于var _b = require("./b.js")
  2. 如果b.js 是 commonJS 规范,则类等价于var _b.default = require("./b.js")。

# 转译解析3

// 转译前:
export default a

// 转译后:
var _default = a;
exports["default"] = _default;
// 以上等价于
exports["default"] = a
1
2
3
4
5
6
7
8

目的:CommonJS 规范和 EMAScript 规范在默认导出时的语法差异:绑定在 default 属性后挂载到 exports。

# 转译解析总结

  1. 通过 exports.__esModule 标识当前模块是否为EMAScript模块。
  2. 统一ESModule规范和 CommonJS 规范导入导出的规范,保证在代码中所有的导入导出的所有模块都是携带default字段的。
    • 如果是ESModule规范,导出时exports[default]=...
    • 如果是CommonJS规范,导入时使用_interopRequireDefault 帮你添加default字段。

# 3.应用:构建建简易的Webpack打包器

有了上面的基础后,开始搭建核心目标:手动构建一个 webpack 打包器,打包器需要达到以下功能:

  1. 减少文件请求次数:现代浏览器在处理import语法时,会生成多个请求,而使用Webpack打包器则可以将多个文件合并为一个bundle.js文件。

  2. 宿主环境的降级:将import语法转变为CommmonJs语法,主要可以看bundle.js中depRelation中的code:function(){ es5语法 }。

    这里的 es5Code 是直接通过 @babel/preset-env 转化得到的。

  3. 使用modules 缓存各个模块的计算结果,代码片段如下:

    if (modules[key]) { return modules[key] }

    如果已经计算过,则直接弹出。其中,key 是文件名,如a.js、b.css这种。


在《webpack源码分析1》中,已构建 depRelation 数组,结构如下:

var depRelation = { 
  {key: 'index.js', deps: ['a.js', 'b.js'], code: function... },
  {key: 'a.js', deps: ['b.js'], code: function... },
  {key: 'b.js', deps: ['a.js'], code: function... }
}
1
2
3
4
5

其中,code 内容是通过 @babel/core 将代码转译为ESCode,且不可执行。 构建打包器的步骤如下:

# 改造1:通过在 code 函数外部再包一层函数

目的:提供 es5Code 中缺少的 require 和 exports。

code: `function(require, module, exports) {
       ${ es5Code }
}`
1
2
3

而具体的 require 和 exports 函数由下面的 execut 函数提供。

# 改造2:构造 execut 函数

目的:执行 require 函数,并将结果存入缓存对象(modules)中,核心代码逻辑如下:

var modules = {}
function execute(key) {
  // 如果已经 require 过,就直接返回上次的结果
  if (modules[key]) { return modules[key] }
  // 找到要执行的项目
  var item = depRelation.find(i => i.key === key)
  var pathToKey = (path) => ...... // 把相路径变成项目路径
  
  // 创建 require 函数
  var require = (path) => {
    return execute(pathToKey(path))
  }
  
  // 初始化当前模块
  modules[key] = { __esModule: true }
  
  // 初始化 module 方便 code 往 module.exports 上添加属性
  var module = { exports: modules[key] }
  
  // 调用 code 函数,往 module.exports 上添加导出属性
  item.code(require, module, module.exports)
  
  // 返回当前模块
  return modules[key]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 总览打包器代码:

var depRelation = [ 
  {key: 'index.js', deps: ['a.js', 'b.js'], code: function... },
  {key: 'a.js', deps: ['b.js'], code: function... },
  {key: 'b.js', deps: ['a.js'], code: function... }
] 
var modules = {} // modules 用于缓存所有模块
execute(depRelation[0].key)
function execute(key){
  var require = ...
  var module = ...
  item.code(require, module, module.exports)
  ...
}
// 详见 dist.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 4.总结

本篇博客主要讨论了以下内容:

  1. webpack 在ESModule →\rightarrow→ CommonJS 模块转译的三个差异。
  2. 通过提供 require 和 exports ,完成一个简易的 webpack 打包器。
编辑 (opens new window)
上次更新: 2022/04/06, 15:04:00
webpack源码分析一:AST语法
webpack源码分析三:源码分析

← webpack源码分析一:AST语法 webpack源码分析三:源码分析→

最近更新
01
如何理解浏览器的 user agent 用户代理的含义?
11-05
02
浏览器事件循环机制
10-31
03
浏览器页面渲染机制【2023】
10-15
更多文章>
Theme by Vdoing | Copyright © 2020-2023
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式