Webpack-02打包器

Webpack解决那些问题

  • 浏览器不支持 import/export,因为浏览器里这样运行不了
  • 代码仓库
  • 安装好依赖

怎样解决这个问题

不同浏览器功能不同

  • 现代浏览器可以通过<script type="module"> 来支持 import/export
  • 但是 IE8~15 不支持

兼容策略

  • 激进策略:所有代码放到 <script type="module">
    • 缺点:
      • http请求文件过多
      • 不被IE支持
  • 平稳兼容:把关键字转译为普通代码,并把所有文件打包为一个文件
    • 缺点:需要写复杂的代码,完成这个事。这就是我们的任务

解决问题1: import/export 转化成函数

  • 通过@babel/preset-env 把ES6 转化为 ES5
  • bundler_1.ts
1
2
3
4
5
6
7
const code = readFileSync(filepath).toString()
// step01 code 不要原封不动的放到 这里,而是变成es5
const { code: es5Code } = babel.transform(code, {
presets: ['@babel/preset-env']
})
// 初始化 depRelation[key]
depRelation[key] = { deps: [], code: es5Code }
  • 运行node -r ts-node/register bundler_1.ts 后打印 depRelation
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
{
'index.js': {
deps: [ 'a.js', 'b.js' ],
code: '"use strict";\n' +
'\n' +
'var _a = _interopRequireDefault(require("./a.js"));\n' +
'\n' +
'var _b = _interopRequireDefault(require("./b.js"));\n' +
'\n' +
'function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n' +
'\n' +
'console.log(_a["default"].getB());\n' +
'console.log(_b["default"].getA());'
},
'a.js': {
deps: [ 'b.js' ],
code: '"use strict";\n' +
'\n' +
'Object.defineProperty(exports, "__esModule", {\n' +
' value: true\n' +
'});\n' +
'exports["default"] = void 0;\n' +
'\n' +
'var _b = _interopRequireDefault(require("./b.js"));\n' +
'\n' +
'function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n' +
'\n' +
'var a = {\n' +
" value: 'a',\n" +
' getB: function getB() {\n' +
` return _b["default"].value + ' from a.js';\n` +
' }\n' +
'};\n' +
'var _default = a;\n' +
'exports["default"] = _default;'
},
'b.js': {
deps: [ 'a.js' ],
code: '"use strict";\n' +
'\n' +
'Object.defineProperty(exports, "__esModule", {\n' +
' value: true\n' +
'});\n' +
'exports["default"] = void 0;\n' +
'\n' +
'var _a = _interopRequireDefault(require("./a.js"));\n' +
'\n' +
'function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n' +
'\n' +
'var b = {\n' +
" value: 'b',\n" +
' getA: function getA() {\n' +
` return _a["default"].value + ' from b.js';\n` +
' }\n' +
'};\n' +
'var _default = b;\n' +
'exports["default"] = _default;'
}
}

观察a.js变化

1
2
3
4
5
6
7
// a.js代码
import b from './b.js'
const a = {
value: 'a',
getB: () => b.value + ' from a.js'
}
export default a
  • import 不见了,变成了 require()
  • export 不见了,变成了 exports['default']
  • 这里的code 是字符串

es5后的 a.js内容字符串整理

  • 疑问1
    • 实际效果是 exports['__esModule']=true
    • 给当前模块添加 __esModule 属性,方便跟 CommonJS模块 区分
    • 这样写是为了解决特定环境的一些问题,不要细究。。。
  • 疑问2 exports["default"] = void 0;
    • void 0; // 等价于 赋值 undefined
    • 强制清空 exports['default']的值
  • 疑问3 import b from './b.js' 变成了 var _b = _interopRequireDefault(require("./b.js"));
    • _interopRequireDefault _ 代表内部的一个函数
    • 意图就是给模块添加 default
  • 疑问4
    • 给 require 的obj添加一个 default
    • 如果是 _esModule 代表有默认导出
    • 反之 obj = { "default": obj }
  • 疑问5 b.value 变成了 _b["default"].value
    • 因为 require 的obj 经过 _interopRequireDefault 处理,有了默认 obj:{default:obj}
    • 所以 变成了 _b["default"].value
  • 疑问6 var _default = a;exports["default"] = _default;
    • 简化后就是 exports['default'] = a
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
"use strict"; //开启严格模式
// 疑问1
Object.defineProperty(exports, "__esModule", {value: true});
// 疑问2
exports["default"] = void 0;

// 疑问3
var _b = _interopRequireDefault(require("./b.js"));

// 疑问4
function _interopRequireDefault(obj) {
return obj && obj._esModule ? obj : { "default": obj };
}

var a = {
value: 'a',
getB: function getB() {
// 疑问5
return _b["default"].value + ' from a.js';
}
};

// 疑问6
var _default = a;
exports["default"] = _default;
  • 结论:我们通过 babel/core 把 ESModule 语法变成了 CommonJS 规则
    • CommonJS 就是使用 require/exports 定义模块

打包所有文件成为一个文件

  • 所有改动参考 bundler_1.ts => bundler_1.ts

第一步:改变depRelation的结构为数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var depRelation = [
{
key:'index.js',
deps:['a.js','b.js'],
code:`字符串code`
},
{
key:'a.js',
deps:['b.js'],
code:`字符串code`
},
{
key:'b.js',
deps:['a.js'],
code:`字符串code`
}
]

// 为什么把 depRelation 变成了数组,因为对象{} 没有第一项的概念
// 数组第一项是入口

第二步:code是字符串,需要变成一个函数

  • require, module, exports 是 CommonJS2 的规范
    • module基本用不到,可以忽略
    • 重点看 require/exports
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
code2 = `function (require, module, exports) {
${code}
}`
// 此时 code2还是字符串
// 把 `code:${code2}`写入到文件
code:function (require, module, exports) {
// ... code代码
}

// 最终写入的文件效果
var depRelation = [
{
key:'index.js',
deps:['a.js','b.js'],
code:function (require, module, exports) {
// ... code代码
}
},
...
]

第三步:execute 这个入口

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
26
27
28
29
const modules = {} // 用于缓存,防止重复加载
execute(depRelation[0].key) // a.js
function execute(key) {
// 如果已经 require 过,就直接返回上次的结果
if (modules[key]) { return modules[key] }
// 找到 a.js 的代码
var item = depRelation.find(i => i.key === key)
// 找不到就报错,中断执行
if (!item) { throw new Error(`${item} is not found`) }
// 把相对路径变成项目路径
var pathToKey = (path) => {...}
// 创建 require 函数
var require = (path) => {
return execute(pathToKey(path))
}
// 初始化当前模块
// modules['a.js'] = { __esModule: true } 辨识我是ES模块
modules[key] = { __esModule: true }
// 初始化 module 方便 code 往 module.exports 上添加属性
// module = { exports: modules['a.js'] }
var module = { exports: modules[key] }
// 调用 code 函数,往 module.exports 上添加导出属性
// 第二个参数 module 大部分时候是无用的,主要用于兼容旧代码
item.code(require, module, module.exports)
// item.code执行的时候,把它导出的内容挂在 module.exports

// 返回当前模块 modules['a.js']
return modules[key]
}

最终dist.js文件的内容

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
var depRelation = [{
key: "index.js",
deps: ["a.js", "b.js"],
code: function (require, module, exports) {
"use strict";

var _a = _interopRequireDefault(require("./a.js"));

var _b = _interopRequireDefault(require("./b.js"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

console.log(_a["default"].getB());
console.log(_b["default"].getA());
}
}, {
key: "a.js",
deps: ["b.js"],
code: function (require, module, exports) {
"use strict";

Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;

var _b = _interopRequireDefault(require("./b.js"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

var a = {
value: 'a',
getB: function getB() {
return _b["default"].value + ' from a.js';
}
};
var _default = a;
exports["default"] = _default;
}
}, {
key: "b.js",
deps: ["a.js"],
code: function (require, module, exports) {
"use strict";

Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;

var _a = _interopRequireDefault(require("./a.js"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

var b = {
value: 'b',
getA: function getA() {
return _a["default"].value + ' from b.js';
}
};
var _default = b;
exports["default"] = _default;
}
}]
var modules = {}
execute(depRelation[0].key)
function execute(key) {
// 如果已经 require 过,就直接返回上次的结果
if (modules[key]) { return modules[key] }
// 找到要执行的项目
var item = depRelation.find(i => i.key === key)
// 找不到就报错,中断执行
if (!item) { throw new Error(`${item} is not found`) }
// 把相对路径变成项目路径
var pathToKey = (path) => {
var dirname = key.substring(0, key.lastIndexOf('/') + 1)
var projectPath = (dirname + path).replace(/\.\//g, '').replace(/\/\//, '/')
return projectPath
}
// 创建 require 函数
var require = (path) => {
return execute(pathToKey(path))
}
// 初始化当前模块
modules[key] = { __esModule: true }
// 初始化 module 方便 code 往 module.exports 上添加属性
var module = { exports: modules[key] }
// 调用 code 函数,往 module.exports 上添加导出属性
// 第二个参数 module 大部分时候是无用的,主要用于兼容旧代码
item.code(require, module, module.exports)
// 返回当前模块
return modules[key]
}

打包器完成

  • 拼接构造 dist.js 的内容,然后写入到文件
  • 参考 bundeler_3.ts 内容