webpack产物解析

当我们在开发一个前端工程的时候,我们不可避免的会使用到webpack作为打包工具,而有时候为了定位问题,不得不去看打包完成之后的js文件,这个时候就需要我们对webpack的构建产物有一定的了解才行了。

最简单的构建

我们先来看一个最简单的工程结构。

index.js文件:

1
2
let test = require('./src/test');
console.log(test);

test.js文件:

1
2
const str = "test is loaded";
module.exports = str;

webpack.config.js文件:

1
2
3
4
5
6
7
8
9
var path = require('path');

module.exports = {
entry: './index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
};

当我们通过这份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
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ 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;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __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;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

let test = __webpack_require__(1);
console.log(test);


/***/ }),
/* 1 */
/***/ (function(module, exports) {

const str = "test2 is loaded";
module.exports = str;

/***/ })
/******/ ]);

这个js我们抽象一点看,它其实就是一个自执行函数:

1
2
3
4
5
(function(modules) {
//...
})([function(module, exports, __webpack_require__) {
//..
}])

index.js对应的代码是:

1
2
3
4
5
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

let test = __webpack_require__(1);
console.log(test);

test.js对应的代码是:

1
2
3
4
5
6
/***/ }),
/* 1 */
/***/ (function(module, exports) {

const str = "test2 is loaded";
module.exports = str;

而在自执行函数体中,我们最终执行的是下面这段代码:

1
2
/******/    // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);

可以看到,调用了__webpack_require__这个函数,并且传入的参数为0。那么下面就让我们来看一下这个函数吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/******/    // The require function
/******/ 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;
/******/ }

注释写的非常清晰了:

  1. 判断是否在缓存的数组中
  2. 如果不在,则创建一个module对象
  3. 调用modules[moduleId]这个方法
  4. 将module对象的loaded标记置为true
  5. 返回module.exports

这里我们知道,首先我们传入的moduleId为0,所以调用的是modules[0]这个函数,也就是index.js对应的部分。而在index.js对应的代码中,又会调用__webpack_require__(1),根据上面的代码,我们知道该函数会调用test.js对应的代码部分:

1
2
3
4
5
/* 1 */
/***/ (function(module, exports) {

const str = "test2 is loaded";
module.exports = str;

这段代码最后将module.exports赋值成了str,所以__webpack_require__(1)最终得到的就是这个str。

从这段分析中我们得出以下几个结论:

  1. webpack构建出来的产物js其实就是一个自执行函数。
  2. 在这个自执行函数中,会调用第一个function作为入口函数,而这个function就是我们webpack配置中的entry文件。
  3. __webpack_require__函数会执行对应的modules[moduleId]方法,并返回module.export。

结合ES6

下面我们看一下结合ES6的import from语法的例子。

index.js文件:

1
2
3
4
5
import test from './src/test';
let test2 = require('./src/test2');

console.log(test);
console.log(test2);

test.js文件:

1
2
const str = 'test is loaded';
export default str;

test2.js文件:

1
2
const str = "test2 is loaded";
module.exports = str;

webpack文件:

1
2
3
4
5
6
7
8
9
var path = require('path');

module.exports = {
entry: './index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
};

最终的构建产物:

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
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ 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;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __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;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";

Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__src_test__ = __webpack_require__(1);

let test2 = __webpack_require__(2);

console.log(__WEBPACK_IMPORTED_MODULE_0__src_test__["a" /* default */]);
console.log(test2);

/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";

const str = 'test is loaded';
/* harmony default export */ __webpack_exports__["a"] = (str);

/***/ }),
/* 2 */
/***/ (function(module, exports) {

const str = "test2 is loaded";
module.exports = str;

/***/ })
/******/ ]);

可以看到对于test2的部分还是一样,而对于使用import from的test1,有了一点不一样的处理:

1
2
3
4
5
6
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";

const str = 'test is loaded';
/* harmony default export */ __webpack_exports__["a"] = (str);

首先,和test2不同的是,这里不再是直接将exports赋值成一个对象,而是创建一个key,将这个key赋值到str上。这样是因为import form可以同时导入多个对象,所以这里也使用了key的形式来适配。

1
2
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__src_test__ = __webpack_require__(1);
console.log(__WEBPACK_IMPORTED_MODULE_0__src_test__["a" /* default */]);

而在index.js对应的部分中,也使用这样的方式获取数据。

结合babel-loader

js还是之前那三个js,修改一下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
// var path = require('path')
// var webpack = require('webpack')
// var MyTestPlugin = require('src/plugins/testPlugin')
//
// module.exports = {
// entry: './index.js',
// output: {
// path: path.resolve(__dirname, './dist'),
// publicPath: '/dist/',
// filename: 'build.js'
// },
// module: {
// rules: [
// {
// test: /\.css$/,
// use: [
// 'vue-style-loader',
// 'css-loader'
// ],
// }, {
// test: /\.vue$/,
// loader: 'vue-loader',
// options: {
// loaders: {
// }
// // other vue-loader options go here
// }
// },
// {
// test: /\.js$/,
// loader: 'test-loader',
// exclude: /node_modules/,
// options: {
// replaceMap: {
// "loaded": "yeah"
// }
// }
//
// },
// {
// test: /\.(png|jpg|gif|svg)$/,
// loader: 'file-loader',
// options: {
// name: '[name].[ext]?[hash]'
// }
// }
// ]
// },
// resolveLoader: {
// modules: [path.join(__dirname, './src/loaders'), 'node_modules'],
// plugins: [path.join(__dirname, './src/plugins'), 'node_modules']
// },
// resolve: {
// alias: {
// 'vue$': 'vue/dist/vue.esm.js'
// },
// extensions: ['*', '.js', '.vue', '.json']
// },
// devServer: {
// historyApiFallback: true,
// noInfo: true,
// overlay: true
// },
// performance: {
// hints: false
// },
// devtool: '#eval-source-map',
// plugins: [
// new MyTestPlugin({
// key: 'value'
// })
// ]
// };
//
// if (process.env.NODE_ENV === 'production') {
// module.exports.devtool = '#source-map'
// // http://vue-loader.vuejs.org/en/workflow/production.html
// module.exports.plugins = (module.exports.plugins || []).concat([
// new webpack.DefinePlugin({
// 'process.env': {
// NODE_ENV: '"production"'
// }
// }),
// new webpack.optimize.UglifyJsPlugin({
// sourceMap: true,
// compress: {
// warnings: false
// }
// }),
// new webpack.LoaderOptionsPlugin({
// minimize: true
// })
// ])
// }

var path = require('path');

module.exports = {
entry: './index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use:
[
{
loader: 'babel-loader',
options: {
// 不显示大于500KB提示
compact: true,
presets: [
"es2015",
"stage-0",
"stage-1",
"stage-2",
"stage-3"
]
}
},
]
}
]
}
};

这样打包出来的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
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ 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;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __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;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

var _test=__webpack_require__(1);var _test2=_interopRequireDefault(_test);function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj};}var test2=__webpack_require__(2);console.log(_test2.default);console.log(test2);

/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

Object.defineProperty(exports,"__esModule",{value:true});var str='test is loaded';exports.default=str;

/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

var str="test2 is loaded";module.exports=str;

/***/ })
/******/ ]);

可以看到多了下面这个函数:

1
2
3
function _interopRequireDefault(obj){
return obj&&obj.__esModule?obj:{default:obj};
}

对于有__esModule标记的,直接返回,对于没有的,返回一个default对象。