超详细的webpack编译结果分析

447 阅读3分钟

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)
 }

webpack编译结果.png

webpack编译结果报错.png

在使用了 eval 函数之后出现报错的表现。

eval函数1.png

eval函数2.png

使用下面的方式可以更加清晰的认识到 eval 函数的作用

下面代码是否使用eval的对比:

 let a = 1;
 var b = 2;
 const c = null;
 ​
 a.webpack();

eval作用.png

将全部代码暴露。

使用eval函数

 let a = 1;
 var b = 2;
 const c = null;
 ​
 eval("a.webpack();")

eval作用2.png

我们可以看到现在代码做了一个隔离,只能看到eval函数内部的语句。上面的VM2888是使用的浏览器为我们提供的虚拟机编号,当然我们也可以自己指定名称。

修改虚拟机名称:

 eval("a.webpack();//# sourceURL=./src/index.js?")

eval函数作用3.png