vue webpack打包入口文件是哪个_webpack 打包文件分析(上)

前言

webpack 是一个用于静态资源打包的工具。它分析你的项目结构,会递归地构建依赖关系,找到其中脚本、图片、样式等将其转换和打包输出为浏览器能识别的资源。

本篇文章仅对 webpack 打包输出的文件进行简要的分析。

项目准备

项目地址

看一下几个关键文件:

  • 依赖文件 src/foo.js

1
module.exports = 'foo';
  • 入口文件 src/index.js

12
const foo = require('./foo.js');console.log(foo)
  • webpack 配置文件 webpack.config.js

1234567891011
const path = require('path');module.exports = {  mode: 'development', // 标识不同的环境,development 开发 | production 生产  devtool: 'none', // 不生成 source map 文件  entry: './src/index.js', // 文件入口  output: {    path: path.resolve(__dirname, 'dist'), // 输出目录    filename: 'bundle.js', // 输出文件名称  }}

bundle 分析

首先放上打包输出文件 dist/bundle.js

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
(function(modules) {  // 模块缓存对象  var installedModules = {};  function __webpack_require__(moduleId) {    if(installedModules[moduleId]) {      return installedModules[moduleId].exports;    }    // 创建一个新的模块对象    var module = installedModules[moduleId] = {      i: moduleId, // 模块id,即模块所在的路径      l: false, // 该模块是否已经加载过了      exports: {} // 导出对象    };    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);    // 标识模块已经加载过了    module.l = true;    return module.exports;  }  // 该属性用于公开modules对象 (__webpack_modules__)  __webpack_require__.m = modules;  // 该属性用于公开模块缓存对象  __webpack_require__.c = installedModules;  // 该属性用于定义兼容各种模块规范输出的getter函数,d即Object.defineProperty  __webpack_require__.d = function(exports, name, getter) {    if(!__webpack_require__.o(exports, name)) {      Object.defineProperty(exports, name, { enumerable: true, get: getter });    }  };  // 该属性用于在导出对象exports上定义 __esModule = true,表示该模块是一个 ES 6 模块  __webpack_require__.r = function(exports) {    // 定义这种模块的Symbol.toStringTag为 [object Module]    if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {      Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });    }    Object.defineProperty(exports, '__esModule', { value: true });  };  // 创建一个命名空间对象  // mode & 1: 传入的value为模块id,使用__webpack_require__加载该模块  // mode & 2: 将传入的value的所有的属性都合并到ns对象上  // mode & 4: 当ns对象已经存在时,直接返回value。表示该模块已经被包装过了  // mode & 8|1: 行为类似于require  __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);    // 将ns对象标识为es模块    __webpack_require__.r(ns);    // 给ns对象定义default属性,值为传入的value    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;  };  // 获取模块的默认导出对象,这里区分 CommonJS 和 ES module 两种方式  __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;  };  // 该属性用于判断对象自身属性中是否具有指定的属性,o即Object.prototype.hasOwnProperty  __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };  // 该属性用于存放公共访问路径,默认为'' (__webpack_public_path__)  __webpack_require__.p = "";  // 加载入口模块并返回模块的导出对象  return __webpack_require__(__webpack_require__.s = "./src/index.js");})({  "./src/foo.js":  (function(module, exports) {    module.exports = 'foo';  }),  "./src/index.js":  (function(module, exports, __webpack_require__) {    const foo = __webpack_require__("./src/foo.js");    console.log(foo)  })});

根据上面的源码可以看出,最终打包出的是一个自执行函数。

首先,这个自执行函数它接收一个参数 modulesmodules为一个对象,其中 key 为打包的模块文件的路径,对应的 value 为一个函数,其内部为模块文件定义的内容。

然后,我们再来看一看自执行函数的函数体部分。函数体返回 __webpack_require__(__webpack_require__.s = "./src/index.js") 这段代码,此处为加载入口模块并返回模块的导出对象。

可以发现,webpack 自己实现了一套加载机制,即 __webpack_require__,可以在浏览器中使用。该方法接收一个 moduleId,返回当前模块的导出对象。

webpack 文件加载 (__webpack_require__)

123456789101112131415
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;}// ...

首先,当前作用域顶端声明了 installedModules 这个对象,它用于缓存加载过的模块。在 __webpack_require__ 方法内部,会对于传入的 moduleId 在缓存对象中查找对应的模块是否存在,如果已经存在,返回该模块对象的导出对象;否则,创建一个新的模块对象,记录当前模块 id、标识模块是否加载过、以及定义导出对象,同时将它放到缓存对象中。

接下来就是重要的一步,执行模块的函数内容,传入 modulemodule.exports 及 __webpack_require__ 作为参数。

1
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

也就是去执行自执行函数传入的 modules 对象中当前 moduleId 对应的函数。接着将该模块标识为已经加载的状态,最后返回当前模块的导出对象。此时便完成了模块的加载任务。

接着,再来看看传入的 modules 对象部分。

1234567891011
({  "./src/foo.js":  (function(module, exports) {    module.exports = 'foo';  }),  "./src/index.js":  (function(module, exports, __webpack_require__) {    const foo = __webpack_require__("./src/foo.js");    console.log(foo)  })})

观察函数体内容,可以看到对于依赖模块 foo.js 而言,函数体内即为 foo.js 文件中的定义内容。而对于入口模块 index.js,则需要执行 __webpack_require__ 方法将依赖的文件加载进来使用。

那么,到此为止,我们已经明白了 webpack 加载模块的基本原理。但细心的你一定发现了,我们的文件导入导出遵循的是 CommonJS 规范,而 webpack 是基于 Node.js 实现的,所以在文件加载部分并没有特别的处理。因此,这里我们来看看不同模块规范相互加载时,webpack 是如何处理的。

harmony(和谐,即对于不同模块规范加载的一个兼容处理)

  • CommonJS 加载 CommonJS

这种方式即我们上面示例的加载方式,就不做赘述了。

CommonJS 加载 ES module

src/foo.js

1
export default 'foo';

src/index.js

12
const foo = require('./foo.js');console.log(foo)

dist/bundle.js

123456789101112
({  "./src/foo.js":  (function(module, __webpack_exports__, __webpack_require__) {    __webpack_require__.r(__webpack_exports__);    __webpack_exports__["default"] = ('foo');  }),  "./src/index.js":  (function(module, exports, __webpack_require__) {    const foo = __webpack_require__("./src/foo.js");    console.log(foo)  })})

由打包后的源码可以发现,当 foo.js 使用 ES module 方式导出,与之前的相比,多了 __webpack_require__.r(__webpack_exports__)这段代码,__webpack_exports__ 很好理解,即模块的导出对象。那么,__webpack_require__.r 方法是干嘛的呢?

12345678
// ...__webpack_require__.r = function(exports) {  if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {    Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });  }  Object.defineProperty(exports, '__esModule', { value: true });};// ...

根据其实现可知,该方法将传入的对象标识上 __esModule=true,即表明该模块为 ES 6 模块。同时定义该对象的 Symbol.toStringTag 为 Module,即当使用 Object.prototype.toString.call 时将返回 [object Module]

最后,将模块的内容挂在 __webpack_exports__ 的 default 属性上。

ES module 加载 ES module

src/foo.js

1
export default 'foo';

src/index.js

12
import foo from './foo.js';console.log(foo)

dist/bundle.js

12345678910111213
({  "./src/foo.js":  (function(module, __webpack_exports__, __webpack_require__) {    __webpack_require__.r(__webpack_exports__);    __webpack_exports__["default"] = ('foo');  }),  "./src/index.js":  (function(module, __webpack_exports__, __webpack_require__) {    __webpack_require__.r(__webpack_exports__);    var _foo_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/foo.js");    console.log(_foo_js__WEBPACK_IMPORTED_MODULE_0__["default"])  })})

当入口文件 index.js 和依赖文件 foo.js 都遵循 ES module 的方式时,可以发现在 index.js 中,对于获取导出对象的方式也有所不同。

_foo_js__WEBPACK_IMPORTED_MODULE_0__ 用来接收导入的文件,并通过 default 属性获取到文件的默认导出内容。

那么,是如何实现这种方式的呢?

1234567891011121314151617
// ...__webpack_require__.d = function(exports, name, getter) {  if(!__webpack_require__.o(exports, name)) {    Object.defineProperty(exports, name, { enumerable: true, get: getter });  }};// ...__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__.o 其实就是 Object.prototype.hasOwnProperty 的一个重写,用于判断对象自身属性中是否具有指定的属性。而 __webpack_require__.d 即 Object.defineProperty,这里用于定义兼容各种模块规范输出的 getter 函数。__webpack_require__.n 则是用于获取模块的默认导出对象,兼容 CommonJS 和 ES module 两种方式。

ES module 加载 CommonJS

src/foo.js

1
module.exports = 'foo';

src/index.js

12
import foo from './foo.js';console.log(foo)

dist/bundle.js

12345678910111213
({  "./src/foo.js":  (function(module, exports) {    module.exports = 'foo';  }),  "./src/index.js":  (function(module, __webpack_exports__, __webpack_require__) {    __webpack_require__.r(__webpack_exports__);    var _foo_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/foo.js");    var _foo_js__WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n(_foo_js__WEBPACK_IMPORTED_MODULE_0__);    console.log(_foo_js__WEBPACK_IMPORTED_MODULE_0___default.a)  })})

当入口文件 index.js 以 ES module 的方式加载遵循 CommonJS 规范的 foo.js 时,通过 __webpack_require__ 加载传入的模块。

将得到的模块 _foo_js__WEBPACK_IMPORTED_MODULE_0__ 再传入 __webpack_require__.n 方法获取到该模块的默认导出对象。因为 foo.js 中的内容是通过 export 导出,而非 export default 导出。因此 foo 被挂在了 default 的一个 a 属性上。

结语

webpack 对于不同模块规范的相互加载的处理,我们已经有了基本的了解。但此时我们的文件加载都是同步的,那么文件的异步加载又是怎么样的呢?

且听下回分解。


推荐阅读:

高校技术社团 ifLab 如何通过线上训练营促进编程学习

为什么说学会学习是开发者重要且必备的技能?

[fCC 100] 祝贺 freeCodeCamp 全球社区 2019 Top Contributors

活动预告

2939684aee1c5f59635085ee300f81f9.png

6.20 本周六上午 10:00 - 11:30,开发者 S1ng S1ng 直播做 freeCodeCamp 中级算法题目,欢迎大家在 bilibili 搜索关注 freeCodeCamp 后进入直播间交流、学习!

383c1e40502588343dbbc97e56f82b7c.png

扫码观看 S1ng S1ng 直播做基础算法题目的视频

8bd0dbbbf8280750b847e64ad67dfb96.png

非营利组织 freeCodeCamp.org 自 2014 年成立以来,以“帮助人们免费学习编程”为使命,创建了大量免费的编程教程,包括交互式课程、视频课程、文章等。线下开发者社区遍布 160 多个国家、2000 多个城市。我们正在帮助全球数百万人学习编程,希望让世界上每个人都有机会获得免费的优质的编程教育资源,成为开发者或者运用编程去解决问题。

你也想成为

freeCodeCamp 社区的贡献者吗

欢迎点击以下文章了解

✨✨ 招募丨freeCodeCamp 翻译计划 成为 freeCodeCamp 专栏作者,与世界各地的开发者分享技术知识

在 freeCodeCamp 专栏阅读更多

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值