Webpack探索【15】--- 基础构建原理详解(模块如何被组建&如何加载)&源码解读
本文主要说明Webpack模块构建和加载的原理,对构建后的源码进行分析。
一 说明
本文以一个简单的示例,通过对构建好的bundle.js源码进行分析,说明Webpack的基础构建原理。
本文使用的Webpack版本是4.32.2版本。
注意:之前也分析过Webpack3.10.0版本构建出来的bundle.js,通过和这次的Webpack 4.32.2版本对比,核心的构建原理基本一致,只是将模块索引id改为文件路径和名字、模块代码改为了eval(moduleString)执行的方式等一些优化改造。
二 示例
1)Webpack.config.js文件内容:
const path = require('path'); module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
mode: 'development' // 'production' 用于配置开发还是发布模式
};
2)创建src文件夹,添加入口文件index.js:
import moduleLog from './module.js'; document.write('index.js loaded.'); moduleLog();
3)在src目录下创建module.js文件:
export default function () {
document.write('module.js loaded.');
}
4)package.json文件内容:
{
"name": "webpack-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"webpack": "webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.32.2",
"webpack-cli": "^3.3.2"
},
"dependencies": {
"lodash": "^4.17.4"
}
}
三 执行构建
执行构建命令:npm run webpack
在dist目录下生成的bundle.js源码如下(下边代码是将注释去掉、压缩的代码还原后的代码):
(function (modules) {
// 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, {enumerable: true, get: getter});
}
}; // define __esModule on exports
__webpack_require__.r = function (exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, {value: 'Module'});
}
Object.defineProperty(exports, '__esModule', {value: true});
}; // create a fake namespace object
// mode & 1: value is a module id, require it
// mode & 2: merge all properties of value into the ns
// mode & 4: return value when already ns object
// mode & 8|1: behave like 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);
__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;
}; // 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 = "./src/index.js");
})
/************************************************************************/
({
"./src/index.js": (function (module, __webpack_exports__, __webpack_require__) {
"use strict"; __webpack_require__.r(__webpack_exports__);
var _module_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/module.js");
document.write('index.js loaded.');
Object(_module_js__WEBPACK_IMPORTED_MODULE_0__["default"])();
}), "./src/module.js": (function (module, __webpack_exports__, __webpack_require__) {
"use strict"; __webpack_require__.r(__webpack_exports__);
__webpack_exports__["default"] = (function () {
document.write('module.js loaded.');
});
})
});
四 源码解读
bundle.js整个代码实际就是一个自执行函数,当在html中加载该文件时,就是执行该自执行函数。
大概结构如下:
(function (modules) {
// The module cache
var installedModules = {};
// The require function
function __webpack_require__(moduleId) {...} // 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) {...}; // define __esModule on exports
__webpack_require__.r = function (exports) {...}; // create a fake namespace object
// mode & 1: value is a module id, require it
// mode & 2: merge all properties of value into the ns
// mode & 4: return value when already ns object
// mode & 8|1: behave like require
__webpack_require__.t = function (value, mode) {...}; // getDefaultExport function for compatibility with non-harmony modules
__webpack_require__.n = function (module) {...}; // Object.prototype.hasOwnProperty.call
__webpack_require__.o = function (object, property) {...}; // __webpack_public_path__
__webpack_require__.p = ""; // Load entry module and return exports
return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
({
"./src/index.js": (function (module, __webpack_exports__, __webpack_require__) {...}),
"./src/module.js": (function (module, __webpack_exports__, __webpack_require__) {...})
});
4.1 自执行函数的参数解读
该参数是一个对象,对象属性的key就是入口模块和它引用的所有模块文件的路径和名字组合。整个代码有多少文件被引入,就会有多少个属性对应。属性key对应的值是一个函数。该函数的内容具体是什么,后边会单独分析。
4.2 自执行函数体解读
自执行函数主要做了下边几件事:
1)定义了installedModules缓存模块的对象变量
该变量用于存储被加载过的模块相关信息。该对象的属性结构如下:
installedModules = {
"./src/index.js": {
i: moduleId,
l:false,
exports: {...}
}
}
installedModules对象的属性key就是模块的id,跟参数对象的key一样。
属性对象中有三个属性:
i:模块id,目前看和key是一样的。
l:标识该模块是否已经加载过。目前感觉这个变量没啥用,只有加载过的模块才会存到该变量中吧?可能还有其它用途,有待发现。
exports:加载完模块后的,模块导出的值都放在这个变量中。
2)定义了__webpack_require__函数,以及该函数上的各种属性
该函数是Webpack的最核心的函数,类似于RequireJS的require方法。用于文件模块的加载和执行。
详细内容会在下边专门讲到。
3)通过__webpack_require__函数加载入口模块
传入的参数是模块id,"./src/index.js"是入口模块的id标识。在这里正是启动了入口模块的加载。
4.3 __webpack_require__函数源码解读
该函数的源码如下:
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)判断该模块是否已经加载过了:如果已经加载过了,从installedModules缓存中找到该模块信息,并将之前加载该模块时保存的exports信息返回。
2)如果该模块没有被加载过:创建一个模块对象,用于保存该模块的信息,并将该模块存储到installedModules缓存变量中,该模块对象的属性详细见前边说明。
3)加载该模块的代码。modules是整个bundle.js文件中自执行函数的参数,详细见上边说明。注意:执行该模块的代码函数时,传入三个参数:模块信息对象、模块导出内容存储对象、__webpack_require__函数。将该函数传入的原因是:加载的当前模块,可能会依赖其它模块,需要__webpack_require__继续加载其它模块。
4)将该模块标识为已加载过的。
5)返回模块的导出值。
4.4 __webpack_require__函数的属性源码解读
该函数上定义了很多属性,各个属性的作用如下(英文是源码的原始注解,这里没有删除):
// expose the modules object (__webpack_modules__)
// 保存整个所有模块的原始信息,modules是整个bundle.js文件中自执行函数的参数,详细见上边说明。
__webpack_require__.m = modules; // expose the module cache
// 保存所有已加载模块的信息,具体见上边说明
__webpack_require__.c = installedModules; // define getter function for harmony exports
// 工具函数:给对应的exports对象上创建name属性
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {enumerable: true, get: getter});
}
}; // define __esModule on exports
// 给缓存中加载过的模块导出对象中,添加__esModule属性。
19 // TODO:具体这个属性的其它用途,待研究
__webpack_require__.r = function (exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, {value: 'Module'});
}
Object.defineProperty(exports, '__esModule', {value: true});
}; // create a fake namespace object
// mode & 1: value is a module id, require it
// mode & 2: merge all properties of value into the ns
// mode & 4: return value when already ns object
// mode & 8|1: behave like require
// TODO: 待研究该函数作用,后续研究完补充
__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;
}; // 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 = "";
通过上边的属性可以看出,通过__webpack_require__函数,可以获取到所有信息。
4.5 入口文件./src/index.js源码解读
上边分析自执行函数中,最主要的一行代码就是通过__webpack_require__函数加载了入口文件。
__webpack_require__(__webpack_require__.s = "./src/index.js")
./src/index.js源码如下:
"./src/index.js": (function (module, __webpack_exports__, __webpack_require__) {
"use strict"; __webpack_require__.r(__webpack_exports__);
var _module_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/module.js");
document.write('index.js loaded.');
Object(_module_js__WEBPACK_IMPORTED_MODULE_0__["default"])();
})
主要做了如下几件事:
1)调用__webpack_require__.r方法,具体参考上边说明。
2)通过__webpack_require__调用依赖的模块"./src/module.js",并将该模块的exports存入_module_js__WEBPACK_IMPORTED_MODULE_0__ 变量。
3)执行index.js自身代码。
可以看出,相比index.js源码,添加和修改了一些代码,源码中通过ES6的import导入模块方式改为了__webpack_require__方法。
4.6 引用模块./src/module.js源码解读
在上边入口模块index.js中,通过__webpack_require__加载了module.js模块。
该模块源码如下:
"./src/module.js": (function (module, __webpack_exports__, __webpack_require__) {
"use strict"; __webpack_require__.r(__webpack_exports__);
__webpack_exports__["default"] = (function () {
document.write('module.js loaded.');
});
})
主要做了如下几件事:
1)调用__webpack_require__.r方法,具体参考上边说明。
2)将导出值存入缓存该模块信息对象的exports属性对象中。
到此,bundle.js的所有源码已解读完毕。
五 基础构建&加载原理说明
从上边源码解读中,可以看出,整个构建过程如下:
1.将所有文件和内容存入自执行函数的参数对象;
2.通过__webpack_require__方法加载入口文件;
3.将加载了的文件信息缓存;
4.如果当前加载的文件依赖其它文件,就通过__webpack_require__继续加载其它文件;
5.直到入口文件执行完毕。
下边是一个简单的原理图,画的比较简陋:
Webpack探索【15】--- 基础构建原理详解(模块如何被组建&如何加载)&源码解读的更多相关文章
- Webpack探索【16】--- 懒加载构建原理详解(模块如何被组建&如何加载)&源码解读
本文主要说明Webpack懒加载构建和加载的原理,对构建后的源码进行分析. 一 说明 本文以一个简单的示例,通过对构建好的bundle.js源码进行分析,说明Webpack懒加载构建原理. 本文使用的 ...
- IntelliJ IDEA 2017版 spring-boot基础补充,原理详解
一.Spring发展史 1.Spring1.x 版本一时代主要是通过XML文件配置bean,在java和xml中不断切换,在学习java web 初期的时候经常使用 2.Spring2 ...
- JDBC详解系列(二)之加载驱动
---[来自我的CSDN博客](http://blog.csdn.net/weixin_37139197/article/details/78838091)--- 在JDBC详解系列(一)之流程中 ...
- 【流媒体开发】VLC Media Player - Android 平台源码编译 与 二次开发详解 (提供详细800M下载好的编译源码及eclipse可调试播放器源码下载)
作者 : 韩曙亮 博客地址 : http://blog.csdn.net/shulianghan/article/details/42707293 转载请注明出处 : http://blog.csd ...
- WinDBG详解进程初始化dll是如何加载的
一:背景 1.讲故事 有朋友咨询个问题,他每次在调试 WinDbg 的时候,进程初始化断点之前都会有一些 dll 加载到进程中,比如下面这样: Microsoft (R) Windows Debugg ...
- Webpack探索【3】--- loader详解
本文主要说明Webpack的loader相关内容.
- Webpack探索【5】--- plugins详解
本文主要讲plugins相关内容. https://gitbook.cn/gitchat/column/59e065f64f7fbe555e479204/topic/59e96d87a35cf44e1 ...
- Webpack探索【12】--- externals详解
本文主要讲externals相关内容. https://segmentfault.com/a/1190000012113011
- 详解web.xml中元素的加载顺序
一.背景 最近在项目中遇到了启动时出现加载service注解注入失败的问题,后来经过不懈努力发现了是因为web.xml配置文件中的元素加载顺序导致的,那么就抽空研究了以下tomcat在启动时web.x ...
随机推荐
- Maven项目导入到Eclipse时Build出现the user operation is waiting for building workspace to complete的问题解决
解决办法如下: 1.选择菜单栏的[Project],然后把菜单栏中[Build Automatically]前面的对钩去掉.
- django admin页面样式丢失问题
wamp 配置django admin页面样式丢失问题 第一种方法:在apache配置文件httpd.conf中加入如下代码:Alias /static "E:\Python27\Lib\s ...
- php 列出当前目录
$path="."; //初使化用户所操作目录 $prevpath=dirname($path); //初使化当前脚本所在目录 $dir_handle=opendir($path) ...
- 经验分享 | Burpsuite抓取非HTTP流量
使用Burp对安卓应用进行渗透测试的过程中,有时候会遇到某些流量无法拦截的情况,这些流量可能不是HTTP协议的,或者是“比较特殊”的HTTP协议(以下统称非HTTP流量).遇到这种情况,大多数人会选择 ...
- centos7 下编译ffmpeg
下载包: wget https://ffmpeg.org/releases/ffmpeg-4.1.tar.gz GCC如果没有yasm: http://yasm.tortall.net/Downloa ...
- 2017.2.28 activiti实战--第七章--Spring容器集成应用实例(五)普通表单
学习资料:<Activiti实战> 第七章 Spring容器集成应用实例(五)普通表单 第六章中介绍了动态表单.外置表单.这里讲解第三种表单:普通表单. 普通表单的特点: 把表单内容写在 ...
- Log4cplus入门
Log4cplus使用指南 1. Log4cplus简单介绍 log4cplus是C++编写的开源的日志系统,前身是java编写的log4j系统.受Apache Software License保护 ...
- 二叉查找树BST----java实现
二叉查找树BST----java实现 1.二叉查找树简单介绍 二叉查找树 ...
- 纯CSS实现的很酷的卡通肖像和眨眼动效
产品设计技术趋势 当前产品设计和开发的一个主要技术趋势除了响应式外, 还有尽量使用CSS/HTML5技术替代图片,这样能够获得非常好的设计扩展性和页面訪问性能. CSS卡通实例 以下就是一个英国WEB ...
- apache压缩页面, 全面加速网站
介绍: 网页压缩来进一步提升网页的浏览速度,它完全不需要任何的成本,只不过是会让您的服务器CPU占用率稍微提升一两个百分点而已或者更少. 原理: 网页压缩是一项由 WEB 服务器和浏览器之间共 ...