简单分析babel和webpack编译后的文件

  1. babel6babel7 后,可以使用babel-plugin-add-module-exports插件,解决 require(xxx).defaultdefault 的问题。

  2. webpack 直接被编译后的 es5 js文件是没法直接被import的,被import后会得到空的对象, 如果要直接引用这个文件,需要设置libraryTarget: 'xxx'

  3. 写了小demo,了解了下babel编译后的文件和webpack编译后的文件。


demo例子

下面是分析 babel,webpack 编译后文件的小例子。后面的内容基于此demo分析。

1
2
3
4
5
6
7
8
9
10
export default class Test {  
constructor() {

}

a() {
alert('b')
console.log('a')
}
}


分析babel7编译后的文件

下面是对上面的 demo 进行 babel 编译后的文件做了一个简单的分析,看下他们做了什么事情。

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
'use strict';

Object.defineProperty(exports, "__esModule", {
value: true
});

var _createClass = (function() {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function(Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
})();

function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}

var Test = function () {
function Test() {
_classCallCheck(this, Test);
}

_createClass(Test, [{
key: 'a',
value: function a() {
alert('b');
console.log('a');
}
}]);

return Test;
}();

exports.default = Test;

从下面的 Test 开始看,定义了一个立即执行函数 Test, Test 内部调用了 _classCallCheck, 这个保证了 ES 模块,只能通过 new 来调用。也就是

1
2
3
4
// 正确
let t = new Test()
// 错误
let t = Test()

后面又执行了 _createClass方法,把函数的方法以 key, value 的形式传给 函数体,然后函数内利用 defineProperty方法,给Test添加属性。使用 defineProperty的时候,定义了enumerable,configurable,writable的值。注意 static 的方法,不能被继承,只能用类名来调用,应该写在 constructor 而不是 constructor.prototype

最后直接暴露出了 default 方法。exports.default = Test;

如果我们直接这么使用的话,调用 Test 方法时就需要这么使用require('xxx').default,这当然不是我们期望的。

所以可以利用插件babel-plugin-add-module-exportsexports.default = Test转换为module.exports = exports.default 或者 export { _default as default }; (这个在babel6,babel7之前是不需要的。如果你使用 babel6 or babel7 你可以会使用这个插件)。

babel 配置为

1
2
3
4
5
6
7
8
9
10
{  
"presets": [
["@babel/preset-env", {
"modules": false
}]
],
"plugins": [
"add-module-exports"
]
}

至于是转换成 module.exports = exports.default 还是 export { _default as default }; 取决与有没有设置 module:false

若设置了 module:false ,babel不会进行转换 es6 的 import, export 等。交给后面的webpack来处理。所以会转换成 { _default as default };

若没有设置则,babel会转成 module.exports = exports.default, 因为会认为你要的是纯的 ES5 语法。

注意babel,有 loose: true, loose: false 两种模式,分别表明了你是要用上面那种 _createClass 的形式生成编译文件,还是使用 Test.prototype.xxx 这种继承的模式来生成文件。建议是不要使用 不严格模式。


分析 webpack 编译后的文件

下面是同样一个 demo 被webpack编译后的内容:

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
(function(modules) {
var installedModules = {};

function __webpack_require__(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = (installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
});
modules[moduleId].call(
module.exports,
module,
module.exports,
__webpack_require__
);
module.l = true;
return module.exports;
}
__webpack_require__.m = modules;
__webpack_require__.c = installedModules;
__webpack_require__.d = function(exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
enumerable: true,
get: getter
});
}
};
__webpack_require__.r = function(exports) {
if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, {
value: "Module"
});
}
Object.defineProperty(exports, "__esModule", {
value: true
});
};
__webpack_require__.t = function(value, mode) {
if (mode & 1) value = __webpack_require__(value);
if (mode & 8) return value;
if (mode & 4 && typeof value === "object" && value && value.__esModule)
return value;
var ns = Object.create(null);
__webpack_require__.r(ns);
Object.defineProperty(ns, "default", {
enumerable: true,
value: value
});
if (mode & 2 && typeof value != "string")
for (var key in value)
__webpack_require__.d(
ns,
key,
function(key) {
return value[key];
}.bind(null, key)
);
return ns;
};
__webpack_require__.n = function(module) {
var getter =
module && module.__esModule
? function getDefault() {
return module["default"];
}
: function getModuleExports() {
return module;
};
__webpack_require__.d(getter, "a", getter);
return getter;
};
__webpack_require__.o = function(object, property) {
return Object.prototype.hasOwnProperty.call(object, property);
};
__webpack_require__.p = "";
return __webpack_require__((__webpack_require__.s = "./class1.mjs"));
})({
"./class1.mjs": function(
__webpack_module__,
__webpack_exports__,
__webpack_require__
) {
"use strict";
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, "default", function() {
return Test;
});

function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}

function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}

function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
var Test = (function() {
function Test() {
_classCallCheck(this, Test);
}
_createClass(Test, [
{
key: "a",
value: function a() {
alert("b");
console.log("a");
}
}
]);
return Test;
})();
}
});

可以看到显示源文件被编译后跟上面的babel没有区别。被编译后的内容作为参数modules带到了立即执行函数里。执行

1
return __webpack_require__(__webpack_require__.s = "./class1.mjs");

webpack 的核心,就是__webpack_require 这个函数做了什么事情。

  1. 检查是否模块已经缓存,如果已经缓存了则直接返回
  2. 定义模块 module, module 内有一个exports属性
  3. 执行modules[moduleId].call,实际上就是调用了被babel编译后的模块,将 module.exports引用赋值给__webpack_exports__
  4. 然后执行__webpack_require__.d(__webpack_exports__, "default", function() { return Test; });,把module.exports上赋值给相应的模块。
  5. return __webpack_require__后的结果即为当前加载的模块。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/******/ function __webpack_require__(moduleId) {  
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }

从刚刚的分析中可以得到:

webpack 编译后的文件是一个立即执行函数,并没有暴露出什么内容,如果直接copy到浏览器里,是一个 Module 对象,也就是并没有赋值给任何值如 window, module.exports, exports ,作用域只在当前。因此解释了为什么 webpack 被编译后的文件没办法直接被import 或者 require

针对这种情况,可以在webpack中设置:libraryTarget: 'xxx', 用来把最后得到的内容,暴露出去。如若值为 commonjs2则会是最外面会加上 module.exports, 若值为 commonjs会最终都为 exports, 若值为 var则是在window上可以使用。还有其他的值,这里就不一一列举了。


总结

之所以会出这篇文章,是因为最近在优化组里的一些公共组件,优化过程中遇到了一些问题,最后发现其实还是对这些欠缺一些了解。不总结出来,怕是对不起自己这几天的纠结。📚🌲