webPack解析

webPack实例化以及编译解析

webpack是一个事件流打包工具,它用到了一个小型的库 tapable,tapable主要用于事件钩子的监听与触发.

  1. 实例化webpack(options)并得到一个compiler对象
  • 实例化compiler对象,compiler继承tapable 因此它具备钩子的操作能力,具体的操作项放在compilation
  • 在实例化了compiler对象之后就往它的身上挂载options(配置项)
  • 实例化NodeEnvironmentPlugin,这个操作就是让它具备了文件读写的能力(node的fs模块)
  • 具备文件读写能力之后,如果options中有插件配置,那么通过调用apply执行插件
  • 触发webpack环境钩子,触发webpack环境初始化之后钩子
  • 实例化WebpackOptionsApply,将内部默认的插件跟当前的compiler建立联系. 这它内部EntryOptionPlugin插件处理了入口模块的id,又用SingleEntryPlugin插件监听了make和compilation钩子
    -compilation钩子就是让compilation具备了利用normalModuleFactory工厂创建一个普通模块的能力. 说白了就是webpack要去打包一个模块,首先它得有自己的模块
    • make钩子的回调中会涉及到compilation.addEntry()的方法调用
    • 走到这里就意味着执行打包之前的所有准备工作就完成了
  • 最后返回实例化的Compiler
  1. 调用compiler.run方法
  • run方法其实就是一堆钩子按照顺序触发,最后调用了compile
  • compile中又执行了对应的操作
    • 参数准备(其中normalModuleFactory是后续用于创建模块的)
    • 触发beforeCompile钩子
    • 实例化compilation
    • 触发make,调用endEntry方法(这个时候就带着 context name entry等)开始编译处理chunk
  • 编译的过程其实就是将模块的内容polify之后插入到一个事先准备好的.ejs文件模板中,这就是thunk.最后用node的fs文件操作功能分局option.output输出至对应的文件.在这个过程中会触发对应的钩子函数.

对打包后的代码解析

我对一个空的index.js进行打包得到一个built.js文件这个文件内容如下:
在这里插入图片描述

  1. 打包后的文件就是一个函数自调用,函数接收的参数是一个对象
  2. 这个对象可以理解为是对模块的定义
  3. 它的是当前模块的路径与文件名的拼接也就是后续模块的id
  4. 它的是函数会将被加载模块的内容包裹在函数中
  5. 这个函数在将来某一时间点会被调用,同时会接收一定的参数,利用这些参数可以实现模块的加载

在index.js中引入login.js

在这里插入图片描述

手写webpack功能函数

(function (modules) {
  // 01 定义对象用于将来缓存被加载过的模块
  let installedModules = {}

  // 02 定义一个 __webpack_require__ 方法来替换 import require 加载操作
  function __webpack_require__(moduleId) {
    // 2-1 判断当前缓存中是否存在要被加载的模块内容,如果存在则直接返回
    if (installedModules[moduleId]) {
      return installedModules[moduleId].exports
    }

    // 2-2 如果当前缓存中不存在则需要我们自己定义{} 执行被导入的模块内容加载
    let module = installedModules[moduleId] = {
      i: moduleId,
      l: false,
      exports: {}
    }

    // 2-3 调用当前 moduleId 对应的函数,然后完成内容的加载
    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)

    // 2-4 当上述的方法调用完成之后,我们就可以修改 l 的值用于表示当前模块内容已经加载完成了
    module.l = true

    // 2-5 加载工作完成之后,要将拿回来的内容返回至调用的位置 
    return module.exports
  }

  // 03 定义 m 属性用于保存 modules 
  __webpack_require__.m = modules

  // 04 定义 c 属性用于保存 cache 
  __webpack_require__.c = installedModules

  // 05 定义 o 方法用于判断对象的身上是否存在指定的属性
  __webpack_require__.o = function (object, property) {
    return Object.prototype.hasOwnProperty(object, property)
  }

  // 06 定义 d 方法用于在对象的身上添加指定的属性,同时给该属性提供一个 getter 
  __webpack_require__.d = function (exports, name, getter) {
    if (!__webpack_require__.o(exports, name)) {
      Object.defineProperty(exports, name, { enumerable: true, get: getter })
    }
  }

  // 07 定义 r 方法用于标识当前模块是 es6 类型
  __webpack_require__.r = function (exports) {
    if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
      Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" })
    }
    Object.defineProperty(exports, '__esModule', { value: true })
  }

  // 08 定义 n 方法,用于设置具体的 getter 
  __webpack_require__.n = function (module) {
    let getter = module && module.__esModule ?
      function getDefault() { return module['default'] } :
      function getModuleExports() { return module }

    __webpack_require__.d(getter, 'a', getter)

    return getter
  }

  // 09 定义 P 属性,用于保存资源访问路径
  __webpack_require__.p = ""

  // 10 调用 __webpack_require__ 方法执行模块导入与加载操作
  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 _login_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./login.js */ "./src/login.js");
        console.log('index.js 执行了')
        console.log(_login_js__WEBPACK_IMPORTED_MODULE_0__["default"], '<------')
        console.log(_login_js__WEBPACK_IMPORTED_MODULE_0__["age"], '<------')
      }),
    "./src/login.js":
      (function (module, __webpack_exports__, __webpack_require__) {
        "use strict";
        __webpack_require__.r(__webpack_exports__);
        __webpack_require__.d(__webpack_exports__, "age", function () { return age; });
        __webpack_exports__["default"] = ('zce是一个帅哥');
        const age = 40
      })
  })

__webpack_require__方法(用来替换 import require 加载操作)

  • 判断模块对应的代码是否被加载过
  • 如果被加载过,直接返回加载的结果
  • 如果没被加载过,那么递归进行加载,最后返回其加载结果并标记缓存
 	// 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;
 	}

webpack_require的r方法(用于标识当前模块是 es6 类型)

  • 如果当前环境满足es6语法那么使用Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })给exports对象设置成[object Module] 在将来方便判断.(可以用Object.prototype.toString.call(exports)去验证)
  • 然后给exports对象添加__esModule属性为true
  • Object.defineProperty默认情况enumerable为false,就是不可枚举(正常取能取得到但是JSON.stringify(exports)就拿不到了)
// 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 });
};

webpack_require的o方法(判断object上是否有property属性)

  • 判断object上是否有property属性
 	// Object.prototype.hasOwnProperty.call
 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };

webpack_require的d方法(为当前exports对象添加属性name并设置getter函数)

  • 用于在对象的身上添加指定的属性,同时给该属性提供一个 getter
  • 利用Object.defineProperty为当前exports对象添加属性name并设置getter函数
	// 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 });
		}
	};

webpack_require的t方法(加载指定模块id对应的内容)

  • 加载指定模块id对应的内容
  • 然后对内容进行es标记,如果返回的值是对象,则写入get函数
  __webpack_require__.t = function (value, mode) {
    // 01 加载 value 对应的模块内容( value 一般就是模块 id )
    // 加载之后的内容又重新赋值给 value 变量
    if (mode & 1) {
      value = __webpack_require__(value)
    }

    if (mode & 8) {  // 加载了可以直接返回使用的内容
      return value
    }

    if ((mode & 4) && typeof value === 'object' && value && value.__esModule) {
      return value
    }

    // 如果 8 和 4 都没有成立则需要自定义 ns 来通过 default 属性返回内容
    let 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
  }

webpack_require的e方法(用于懒加载的实现,创建script标签并写入head中)

  • 用于懒加载的实现,创建script标签并写入head中
  • resolve, reject,promise保存在installedChunks中,用于JsonP的回调执行
	var installedChunks = {
 		"main": 0
 	};
 	__webpack_require__.e = function requireEnsure(chunkId) {
 		var promises = [];
 		// JSONP chunk loading for javascript
 		var installedChunkData = installedChunks[chunkId];
 		if(installedChunkData !== 0) { // 0 means "already installed".
 			// a Promise means "currently loading".
 			if(installedChunkData) {
 				promises.push(installedChunkData[2]);
 			} else {
 				// setup Promise in chunk cache
 				var promise = new Promise(function(resolve, reject) {
 					installedChunkData = installedChunks[chunkId] = [resolve, reject];
 				});
 				promises.push(installedChunkData[2] = promise);
 				// start chunk loading
 				var script = document.createElement('script');
 				var onScriptComplete;
 				script.charset = 'utf-8';
 				script.timeout = 120;
 				if (__webpack_require__.nc) {
 					script.setAttribute("nonce", __webpack_require__.nc);
 				}
 				script.src = jsonpScriptSrc(chunkId);
 				// create error before stack unwound to get useful stacktrace later
 				var error = new Error();
 				onScriptComplete = function (event) {
 					// avoid mem leaks in IE.
 					script.onerror = script.onload = null;
 					clearTimeout(timeout);
 					var chunk = installedChunks[chunkId];
 					if(chunk !== 0) {
 						if(chunk) {
 							var errorType = event && (event.type === 'load' ? 'missing' : event.type);
 							var realSrc = event && event.target && event.target.src;
 							error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
 							error.name = 'ChunkLoadError';
 							error.type = errorType;
 							error.request = realSrc;
 							chunk[1](error);
 						}
 						installedChunks[chunkId] = undefined;
 					}
 				};
 				var timeout = setTimeout(function(){
 					onScriptComplete({ type: 'timeout', target: script });
 				}, 120000);
 				script.onerror = script.onload = onScriptComplete;
 				document.head.appendChild(script);
 			}
 		}
 		return Promise.all(promises);
 	};

Jsonp回调函数webpackJsonpCallback

  • 合并模块定义,保存模块id和其内容到modules中
  • 执行installedChunks中的resolves回调.改变promise状态,触发.then链式调用
 	function webpackJsonpCallback(data) {
 		var chunkIds = data[0];
 		var moreModules = data[1];
 		var moduleId, chunkId, i = 0, resolves = [];
 		for(;i < chunkIds.length; i++) {
 			chunkId = chunkIds[i];
 			if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
 				resolves.push(installedChunks[chunkId][0]);
 			}
 			installedChunks[chunkId] = 0;
 		}
 		for(moduleId in moreModules) {
 			if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
 				modules[moduleId] = moreModules[moduleId];
 			}
 		}
 		if(parentJsonpFunction) parentJsonpFunction(data);
 		while(resolves.length) {
 			resolves.shift()();
 		}
 	};
 	// 定义变量存放数组
	var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
	// 保存原生带push方法
 	var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
 	// 重写原生的push方法。在将来login.built.js中调用的push就是这个重写的push
 	jsonpArray.push = webpackJsonpCallback;
 	// 浅拷贝
 	jsonpArray = jsonpArray.slice();
 	for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
 	var parentJsonpFunction = oldJsonpFunction;
 	// Load entry module and return exports
 	return __webpack_require__(__webpack_require__.s = "./src/index.js");

login.built.js

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([
  ["login"],
  {
    "./src/login.js": function (module, exports) {
      module.exports = "学习学习";
    },
  },
]);

采用esModal导出

  • 原login文件
	export default 'zcegg'
	export const age = 18
  • 转译之后的内容
"./src/login.js": (function(module, __webpack_exports__, __webpack_require__) {
	"use strict";
	// 标记是__esModule
	__webpack_require__.r(__webpack_exports__);
	// export处理
	__webpack_require__.d(__webpack_exports__, "age", function() { return age; });
	// export default 'zcegg'
	__webpack_exports__["default"] = ('zcegg');
	// age
	const age = 18
})
  • 最后得到一个的结果(是一个对象)
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值