webpack需要进行打包之后对模块化进行转化、语法转化和样式编译。
下面使用模块a和模块index来说明webpack编译结果
模块a
console.log('moudule a');
module.exports = 'a';
模块index
const a = require('./a');
console.log(a);
console.log('index');
webpack打包
这是webpack在删去不同模块化兼容后的一个编译结果,使用 立即执行函数创造一个作用域,防止变量被污染。传入的参数是一个对象,这个对象的key是不同模块的路径,value是一个函数,这个函数的参数在CommonJS和es6模块化中表现的形式是不同的,如下所示:
CommonJS:
"./src/a.js": (function(module, exports) {}),
"./src/index.js": (function(module, exports, __webpack_require__) {})
ES6模块化:
"./src/a.js": (function(module, __webpack_exports__, __webpack_require__) {}),
"./src/index.js": (function(module, __webpack_exports__, __webpack_require__) {})
编译结果分析
但是在编译结果中不同的模块化其实是差不多的,下面以CommonJS为例,分析一下webpack的编译结果。
(function(modules) { // webpackBootstrap
// 模块化缓存,多次导入会调用缓存
var installedModules = {};
// require 函数,相当于运行一个模块
function __webpack_require__(moduleId) {
// 先判断是否有缓存,如果有就调用缓存
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// 没有缓存就向 installedModules 保存一个缓存结果,key 是路径
var module = installedModules[moduleId] = {
i: moduleId, // 模块的 ID
l: false, // 是否被加载完成
exports: {} // 模块被调用后的执行结果
};
// moduleId 改变下面立即执行函数中的this 指向,并传入参数
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// 模块是否被加载完成的标记
module.l = true;
// 返回模块运行后的结果
return module.exports;
}
// Load entry module and return exports 调用 require 函数,执行入口文件
return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
({
"./src/a.js": (function(module, exports) {
eval("console.log('moudule a');\nmodule.exports = 'a';\n\n//# sourceURL=webpack:///./src/a.js?");
}),
"./src/index.js": (function(module, exports, __webpack_require__) {
eval("const a = __webpack_require__(/*! ./a */ "./src/a.js");\nconsole.log(a);\nconsole.log('index');\n\n\n//# sourceURL=webpack:///./src/index.js?");
})
});
手动实现webpack 打包结果
所以根据上面的编译代码分析可以手动实现一个webpack编译结果
(function (modules){
var moduleExports = {}; // 用于缓存模块的导出和结果
// require 函数相当于是运行了一个模块,得到模块导出结果
function require(moduleId) { // moduleId 就是模块的路径
if(moduleExports[moduleId]) {
return moduleExports[moduleId];
}
var func = modules[moduleId]; // 得到该模块对应的函数
var module = {
exports: {}
}
func(module, module.exports, require); // 运行模块
var result = module.exports; // 得到模块导出的结果
moduleExports[moduleId] = result; // 将模块缓存起来
return result; // 将结果返回出去,也就是 require 得到的结果
}
require('./src/index.js');// require 函数相当于是运行了一个模块,得到模块导出结果
})({
'./src/a.js': function(module, exports) {
console.log('moudule a');
module.exports = 'a';
},
'./src/index.js': function(module, exports, require) {
const a = require('./src/a.js');
console.log(a);
console.log('index');
}
})
eval函数
eval() 函数会将传入的字符串当做 JavaScript 代码进行执行,会有一个自己的作用域。
[eval函数,mdn链接](eval() - JavaScript | MDN (mozilla.org))
手动写的立即执行函数的参数函数中,函数内容直接就是模块中所写的内容,这样没有转化过的当然是可以的,但是也是有缺点的,当我们的diamante报错时,在浏览器中检查源码得到的是webpack编译后的结果,这样的结果不直观,也不利于快速找到错误。
'./src/index.js': function(module, exports, require) {
const a = require('./src/a.js');
console.log(a);
console.log('index');
console.log(c)
}
在使用了 eval 函数之后出现报错的表现。
使用下面的方式可以更加清晰的认识到 eval 函数的作用
下面代码是否使用eval的对比:
let a = 1;
var b = 2;
const c = null;
a.webpack();
将全部代码暴露。
使用eval函数
let a = 1;
var b = 2;
const c = null;
eval("a.webpack();")
我们可以看到现在代码做了一个隔离,只能看到eval函数内部的语句。上面的VM2888是使用的浏览器为我们提供的虚拟机编号,当然我们也可以自己指定名称。
修改虚拟机名称:
eval("a.webpack();//# sourceURL=./src/index.js?")