webpack

您所在的位置:网站首页 import导入函数 webpack

webpack

2023-03-11 02:28| 来源: 网络整理| 查看: 265

前言 文章结构采用【指出阶段目标,然后以需解决问题为入口,以解决思路为手段】达到本文目标,若使诸君稍有启发,不枉此文心力^-^ 目标

理解webpack的重要概念-【代码分割】,以及其实现import函数

关键点

对于随着功能而使用的代码,可以先拆分出来打包到一个单独的js文件中(代码分割),然后在使用时动态创建script标签进行引入。

import语法的实现 先看使用 ## index.js btn.addEventListener('click',()=>{ import( /* webpackChunkName: "title" */ "./title.js" ).then((result)=>{ console.log(result); }) }) 复制代码 Document 异步加载 复制代码 核心问题

如何让import语法包裹的模块在执行时才引入

解决思路

采用JSONP的思路,首先,将动态引入模块单独打成一个js文件;其次,在import执行时创建script标签传入src为引入模块地址;从而实现动态加载的效果,注意,JSONP必然是异步的,所以必须要结合Promise;

前置知识

JSONP是以前很流行的一种跨域手段(面试问跨域必答),其核心就在于利用script标签的src属性不收浏览器安全协议的限制(同源策略,域名端口协议三者必须相同,否则即跨域),再具体而言,就是向服务器发起具体特定(比如这就是异步加载模块的逻辑代码)js文件的请求,然后获得其结果(此处就是模块导出值,会默认放到window下名为webpackJsonp的数组中)

实现逻辑

核心:在引入异步加载模块后再执行用户自定义逻辑,promise实现订阅发布,promise收集用户

思路

触发时将异步模块以jsonp进行引入,此处必然是异步,所以需要利用promise进行订阅发布,先订阅收集内部对引入模块的处理

在引入模块的代码执行时完成模块的安装(加入到模块源上),同时发布模块处理操作获得模块返回值(调用对应promise的resolve方法)

在动态引入的模块中需实现

将被引入模块标为已加载,并记录此异步引入模块 将被引入模块对象存入初始的modules中,统一管理 执行用户逻辑(即将promise标为成功态,从而执行then中的回调)

将返回值交给用户定义函数,完成引入

具体实现

定义方法 __webpack_require__.e 其核心是通过jsonp(创建script标签)加载动态引入代码,其次两点 缓存 + 异步 ;

installedChunks对象用于记录模块状态,实现缓存 // 用于存放加载过的和加载中的代码块 // key :代码块名 chunkId // value : undefined 未加载 null 预加载 Promise 代码块加载中 0 加载完成 var installedChunks = { 0: 0 }; 复制代码 若模块未加载,则通过chunkId拼接src并创建script标签异步加载模块 // 根据模块名获得引入路径 script.src = jsonpScriptSrc(chunkId); 。。。 // 将脚本插入文档 开始获取依赖模块内容 document.head.appendChild(script); 复制代码 动态引入的模块中需实现逻辑, 因为要记录,而且可能有多个异步引入模块,所以可以采用数组; 因为在记录的同时还有执行【存入初始modules】【改变模块状态】等逻辑,所以可以用装饰者设计模式,重写此数组实例的push方法,从而在存入同时执行其余逻辑;(此写法在vue源码实现数据劫持也有应用,可见【xxxxx】) ## 自执行函数中默认执行 // 重写数组push var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; // 获取原push方法 var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); // 将push指向自定义的函数 jsonpArray.push = webpackJsonpCallback; 。。。 /** * 异步加载模块中会执行这个函数进行模块安装 * @param {Array} data * [ * chunkIds:Array, 模块id数组 个人感觉其实只会有一个id,因为异步加载时自然只会加载一个chunk;没想明白为什么要设计成数组,如有知道的请解惑 * [string|number] * modules:Array 模块函数数组 即之前说到的包裹用户自定义逻辑的函数,采用数组是因为在webpac4.441版本后将数组下标作为chunkId了,所以main的chunkId是0,在此例中title的chunkId是1,那么0处就需要empty去占位; * [fn] * ] */ function webpackJsonpCallback(data) { // 模块id数组 var chunkIds = data[0]; // 模块函数数组 var moreModules = data[1]; // 模块ID(被安装进modules时的标识) 代码块ID 循环索引 这个异步加载模块对应的promise会有resolve函数,会被存在resolves中 var moduleId, chunkId, i = 0, resolves = []; // 【将被引入模块标为已加载,并记录此异步引入模块】 // 1. 循环存储异步加载模块对应的promise的resolve函数 末尾会执行以将promise标为成功态,从而执行then中的第一个回调(promise规范中称为onFullFinished) // 2. 将被引入模块标为已加载 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; } // 将被引入模块对象存入初始的modules中,统一管理 for(moduleId in moreModules) { if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { modules[moduleId] = moreModules[moduleId]; } } // parentJsonpFunction指的是数组原始的push方法,执行以保证webpackJsonp数组的状态 if(parentJsonpFunction) parentJsonpFunction(data); // 执行用户逻辑(即将promise标为成功态,从而执行then中的回调) while(resolves.length) { resolves.shift()(); } }; 复制代码 在异步加载的模块中,就会执行webpackJsonp的push方法(其实是webpackJsonpCallback方法),从而完成安装和引入,至此,我们的模块源modules找那个就有了我们的title模块;下一步只要复用之前逻辑,进行模块的安装就好 (window["webpackJsonp"] = window["webpackJsonp"] || []).push([ // chunkId [1], [ // 占位 代表main模块 /* 0 */, /* 1 */ // title模块对应的模块安装函数 /***/ (function(module, exports) { module.exports = "title" /***/ }) ] ]); 复制代码 此时,我们的modules上已经有异步加载的模块信息了,该以什么方式导出呢(commonJs还是es),webpack中采用了默认es但也支持commonJs的方式,此逻辑在__webpack_require__.t中实现,__webpack_require__.e返回promise的回调中会执行t方法;在在实现此方法前,我们需要了解js的位运算& A & B 先将A B转为二进制,如果两位数的同位都是 1 则设置每位为 1 否则为0; 复制代码

​ 要区别处理导出方式,自然要进行判断,在webpack中采用的就是位运算&;

十进制二进制判断优先级为true时执行逻辑100011执行__webpack_require__方法,进行模块安装200104将模块对象的属性和值拷贝到ns上401003会继续判断是不是es模块,如果是则直接返回,如果不是则向下执行定义一个es模块对象ns(此为默认返回值)810002直接返回,注意判断此优先级是2

举例解释:在本例中,传递的是7(第一位是chunkId,第二位是判断标识)

__webpack_require__.e(/* import() | title */ 1).then(__webpack_require__.t.bind(null, 1, 7)).then((result)=>{ console.log(result); }) 复制代码

7转为二进制是0111,所以执行为

执行__webpack_require__方法 不直接返回(注意判断优先级) 不是es模块对象,向下执行定义一个es模块对象ns 将模块对象的属性和值拷贝到ns上,返回此ns对象 __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; }; 复制代码 简化版测试用例,可自行debuge加深理解 // The module cache var installedModules = {},modules = { moduleA(module,exports){ exports.value = "moduleA" }, moduleB(module,exports){ exports.__esModule = true; exports.default = {value:"moduleB"} } }; // 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 = function(exports) { if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); } Object.defineProperty(exports, '__esModule', { value: true }); }; __webpack_require__.d = function(exports, name, getter) { if(!__webpack_require__.o(exports, name)) { Object.defineProperty(exports, name, { enumerable: true, get: getter }); } }; __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; __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; }; let result1 = __webpack_require__.t("moduleA",1); // 0b0001 console.log("result1",result1); let result2 = __webpack_require__.t("moduleA",9); // 0b1001 console.log("result2",result2); let result3 = __webpack_require__.t("moduleA",5); // 0b0101 console.log("result3",result3); let result4 = __webpack_require__.t("moduleA",7); // 0b0111 console.log("result4",result4); 复制代码 show the code (编译后加注释版源码) main.js (function(modules) { // webpackBootstrap /** * 异步加载模块中会执行这个函数进行模块安装 * @param {Array} data * [ * chunkIds:Array, 模块id数组 个人感觉其实只会有一个id,因为异步加载时自然只会加载一个chunk;没想明白为什么要设计成数组,如有知道的请解惑 * [string|number] * modules:Array 模块函数数组 即之前说到的包裹用户自定义逻辑的函数,采用数组是因为在webpac4.441版本后将数组下标作为chunkId了,所以main的chunkId是0,在此例中title的chunkId是1,那么0处就需要empty去占位; * [fn] * ] */ function webpackJsonpCallback(data) { // 模块id数组 var chunkIds = data[0]; // 模块函数数组 var moreModules = data[1]; // 模块ID(被安装进modules时的标识) 代码块ID 循环索引 这个异步加载模块对应的promise会有resolve函数,会被存在resolves中 var moduleId, chunkId, i = 0, resolves = []; // 【将被引入模块标为已加载,并记录此异步引入模块】 // 1. 循环存储异步加载模块对应的promise的resolve函数 末尾会执行以将promise标为成功态,从而执行then中的第一个回调(promise规范中称为onFullFinished) // 2. 将被引入模块标为已加载 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; } // 将被引入模块对象存入初始的modules中,统一管理 for(moduleId in moreModules) { if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { modules[moduleId] = moreModules[moduleId]; } } // parentJsonpFunction指的是数组原始的push方法,执行以保证webpackJsonp数组的状态 if(parentJsonpFunction) parentJsonpFunction(data); // 执行用户逻辑(即将promise标为成功态,从而执行then中的回调) while(resolves.length) { resolves.shift()(); } }; // The module cache var installedModules = {}; // 用于存放加载过的和加载中的代码块 // key :代码块名 chunkId // value : undefined 未加载 null 预加载 Promise 代码块加载中 0 加载完成 var installedChunks = { 0: 0 }; // script path function function jsonpScriptSrc(chunkId) { // __webpack_require__.p 是指 publicPath return __webpack_require__.p + "" + ({"1":"title"}[chunkId]||chunkId) + ".js" } // 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; } // This file contains only the entry chunk. // The chunk loading function for additional chunks __webpack_require__.e = function requireEnsure(chunkId) { // 因为加载代码块是异步的,所以需要用到Promise var promises = []; var installedChunkData = installedChunks[chunkId]; // 判断此代码块是否被安装过 if(installedChunkData !== 0) { // 0 means "already installed". // 如果不为0且是一个数组(其第二项是一个promise,前两项是promise的resolve和reject函数) if(installedChunkData) { // 则存入promise队列 promises.push(installedChunkData[2]); } else { // 如果不为0且不存在 即 undefined 未加载 null 预加载 var promise = new Promise(function(resolve, reject) { installedChunkData = installedChunks[chunkId] = [resolve, reject]; }); // 将promise存入第二项 promises.push(installedChunkData[2] = promise); // 开始代码块导入逻辑 创建script标签 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); //此处是用于加载超时提示 当模块加载超过120000ms时,则会在浏览器中抛出异常,提示用户 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); }; // 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 = ""; // on error function for async loading __webpack_require__.oe = function(err) { console.error(err); throw err; }; var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); 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 = 0); }) /************************************************************************/ ([ /* 0 */ (function(module, exports, __webpack_require__) { btn.addEventListener('click',()=>{ __webpack_require__.e(/* import() | title */ 1).then(__webpack_require__.t.bind(null, 1, 7)).then((result)=>{ console.log(result); }) }) }) ]); 复制代码 title.js (window["webpackJsonp"] = window["webpackJsonp"] || []).push([ [1], [ /* 0 */, /* 1 */ /***/ (function(module, exports) { module.exports = "title" /***/ }) ] ]); 复制代码


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3