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 ...
随机推荐
- bzoj 5125: [Lydsy1712月赛]小Q的书架
新学了一波 决策单调性 dp 套路.... 这种dp一般是长这样的 => f[i][j] = max/min { f[i-1][k] + cost(k+1,j)} ,其中cost函数满足四边形 ...
- Error Code: 1055 incompatible with sql_mode=only_full_group_by
OperationalError at / (1055, "Expression #1 of ORDER BY clause is not in GROUP BY clause and co ...
- 【GLSL教程】(二)在OpenGL中使用GLSL 【转】
http://blog.csdn.net/racehorse/article/details/6616256 设置GLSL 这一节讲述在OpenGL中配置GLSL,假设你已经写好了顶点shader和像 ...
- Upan
http://www.xiazaijidi.com/ http://www.ushendu.com/
- GIS可视化
作为一名GIS专业的学生,一晃也毕业三年了,在supermap也呆了三年多了,做的最多的就是浏览器端的GIS展示,最近也想分享一下我们团队在浏览器端GIS可视化的一些成果,算是做个宣传吧!有用的着的可 ...
- windows 控制台cmd乱码的解决办法
windows 控制台cmd乱码的解决办法 我本机的系统环境: OS Name: Microsoft Windows 10 企业版 OS Version: 10.0.14393 N/A Build 1 ...
- springBoot与多数据源的配置
http://www.cnblogs.com/shenlanzhizun/p/5846475.html 最近有点忙,更新有点慢.今天进来说说一说springBoot中如何配置多数据源. 第一,新建一个 ...
- 24. Spring Boot环境变量读取和属性对象的绑定【从零开始学Spring Boot】
转:http://blog.csdn.net/linxingliang/article/details/52069509 凡是被spring管理的类,实现接口EnvironmentAware 重写方法 ...
- java中日期格式的转换和应用
java中主要有3个类用于日期格式转换 DateFormat .SimpleDateFormat.Calendar SimpleDateFormat函数的继承关系: java.lang.Obje ...
- google PLDA + 实现原理及源代码分析
LDA背景 LDA(隐含狄利克雷分布)是一个主题聚类模型,是当前主题聚类领域最火.最有力的模型之中的一个,它能通过多轮迭代把特征向量集合按主题分类. 眼下,广泛运用在文本主题聚类中. LDA的开源实现 ...