打包原理简略讲就是生成ast语法树,依据语法树生成对应的js代码这里仅剖析打包输入的后果,从后果剖析webpack对咱们代码做了啥剖析入口文件// main.js// 通过CommonJS标准导入const show = require('./show.js');// 执行 show 函数show('Webpack');依赖文件// show.js// 操作 DOM 元素,把 content 显示到网页上function show(content) { window.document.getElementById('app').innerText = 'Hello,' + content;}// 通过 CommonJS 标准导出 show 函数module.exports = show;打包后果// bundle.js( // webpackBootstrap 启动函数 // modules 即为寄存所有模块的数组,数组中的每一个元素都是一个函数 function (modules) { // 装置过的模块都寄存在这外面 // 作用是把曾经加载过的模块缓存在内存中,晋升性能 var installedModules = {}; // 去数组中加载一个模块,moduleId 为要加载模块在数组中的 index // 作用和 Node.js 中 require 语句类似 function __webpack_require__(moduleId) { // 如果须要加载的模块曾经被加载过,就间接从内存缓存中返回 if (installedModules[moduleId]) { return installedModules[moduleId].exports; } // 如果缓存中不存在须要加载的模块,就新建一个模块,并把它存在缓存中 var module = installedModules[moduleId] = { // 模块在数组中的 index i: moduleId, // 该模块是否曾经加载结束 l: false, // 该模块的导出值 exports: {} }; // 从 modules 中获取 index 为 moduleId 的模块对应的函数 // 再调用这个函数,同时把函数须要的参数传入 modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); // 把这个模块标记为已加载 module.l = true; // 返回这个模块的导出值 return module.exports; } // Webpack 配置中的 publicPath,用于加载被宰割进来的异步代码 __webpack_require__.p = ""; // 应用 __webpack_require__ 去加载 index 为 0 的模块,并且返回该模块导出的内容 // index 为 0 的模块就是 main.js 对应的文件,也就是执行入口模块 // __webpack_require__.s 的含意是启动模块对应的 index return __webpack_require__(__webpack_require__.s = 0); })( // 所有的模块都寄存在了一个数组里,依据每个模块在数组的 index 来辨别和定位模块 [ /* 0 */ (function (module, exports, __webpack_require__) { // 通过 __webpack_require__ 标准导入 show 函数,show.js 对应的模块 index 为 1 const show = __webpack_require__(1); // 执行 show 函数 show('Webpack'); }), /* 1 */ (function (module, exports) { function show(content) { window.document.getElementById('app').innerText = 'Hello,' + content; } // 通过 CommonJS 标准导出 show 函数 module.exports = show; }) ]);// 以上看上去简单的代码其实是一个立刻执行函数,能够简写为如下:(function(modules) { // 模仿 require 语句 function __webpack_require__() { } // 执行寄存所有模块数组中的第0个模块 __webpack_require__(0);})([/*寄存所有模块的数组*/])能够看到bundle.js是一个自执行函数,入参就是main.js和show.js革新后的代码块所形成的数组自执行函数里运行了__webpack_require__这个函数,入参是0,0其实就是代码块数组中对应的入参,示意第一个代码块再来看__webpack_require__函数,首先执行的是缓存判断,通过moduleId判断之前是否曾经加载过,如果加载过,间接返回间接的加载后果exports,mouduleId就是不同代码模块在入参数组中的index而如果没有加载过,则新建一个对象,重要的是这个对象中的exports属性,外面寄存的就是加载模块后,对应模块export进去的货色而后用这个exports作为上下文去执行对应的代码块,传递参数为方才新建的module,module里的exports,以及__webpack_require__这个办法自身而后看到main.js中的require被革新成了__webpack_require__,__webpack_require__(1)代表加载第二个代码块第二个代码块中,定义了show这个办法,而后show会作为module.exports的导出,也就是赋值给了installedModules[0].module.exports,也就是这个导出曾经被缓存起来了,下次再有别的中央用到,会间接被导出这就是webpack大抵的打包思路,将各个独自的模块革新成数组作为入参,传给自执行函数,同时保护一个installedModules记录加载过的模块,利用模块数组中的index作为key值,exports记录导出对象按需加载因为单页利用也会有路由这个概念,在没有切换到对应路由之前,可能并不心愿浏览器对这部分页面的js进行下载,从而晋升首页关上的速度,就波及到一个懒加载,即按需加载的问题webpack的按需加载是通过import(XXX)实现的,import()是一个提案,而webpack反对了它// 异步加载 show.jsimport(/* webpackChunkName: 'show' */ './show').then((module) => { // 执行 show 函数 const show = module.default; show('Webpack');});通过这种形式打包,咱们能够发现最终打包进去的文件分成了两个,bundle.js和show.xxx.js其中/* webpackChunkName: 'show' */是专门正文给webpack看的,为的是指定按需加载的包的名字,同时记得在webpack的配置文件的entry中,配置chunkFilename: '[name].[hash].js',不然这个指定不会失效先来看入口文件,将临时没有用到的函数都暗藏后如下:(function (modules) { // webpackJsonp 用于从异步加载的文件中装置模块 window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) { // ... 先省略 }; // 缓存曾经装置的模块 var installedModules = {}; // 存储每个 Chunk 的加载状态; // 键为 Chunk 的 ID,值为0代表曾经加载胜利 var installedChunks = { 1: 0 }; // 模仿 require 语句,和下面介绍的统一 function __webpack_require__(moduleId) { // ... 省略和下面一样的内容 } // 用于加载被宰割进来的,须要异步加载的 Chunk 对应的文件 __webpack_require__.e = function requireEnsure(chunkId) { // ... 先省略 }; // 加载并执行入口模块,和下面介绍的统一 return __webpack_require__(__webpack_require__.s = 0);})( // 寄存所有没有通过异步加载的,随着执行入口文件加载的模块 [ // main.js 对应的模块 (function (module, exports, __webpack_require__) { // 通过 __webpack_require__.e 去异步加载 show.js 对应的 Chunk __webpack_require__.e('show').then(__webpack_require__.bind(null, 'show')).then((show) => { // 执行 show 函数 show('Webpack'); }); }) ]);能够看到import(xxx).then被替换成了__webpack_require__.e(0).then,__webpack_require__.e(0)返回了一个promise第一个then里相当于执行了__webpack_require__(1),但很显著能够看到自执行函数的入参数组只有一个元素,不存在[1],这个[1]是什么时候被插入的呢看一下__webpack_require__.e的实现__webpack_require__.e = function requireEnsure(chunkId) { // 从下面定义的 installedChunks 中获取 chunkId 对应的 Chunk 的加载状态 var installedChunkData = installedChunks[chunkId]; // 如果加载状态为0示意该 Chunk 曾经加载胜利了,间接返回 resolve Promise if (installedChunkData === 0) { return new Promise(function (resolve) { resolve(); }); } // installedChunkData 不为空且不为0示意该 Chunk 正在网络加载中 if (installedChunkData) { // 返回寄存在 installedChunkData 数组中的 Promise 对象 return installedChunkData[2]; } // installedChunkData 为空,示意该 Chunk 还没有加载过,去加载该 Chunk 对应的文件 var promise = new Promise(function (resolve, reject) { installedChunkData = installedChunks[chunkId] = [resolve, reject]; }); installedChunkData[2] = promise; // 通过 DOM 操作,往 HTML head 中插入一个 script 标签去异步加载 Chunk 对应的 JavaScript 文件 var head = document.getElementsByTagName('head')[0]; var script = document.createElement('script'); script.type = 'text/javascript'; script.charset = 'utf-8'; script.async = true; script.timeout = 120000; // 文件的门路为配置的 publicPath、chunkId 拼接而成 script.src = __webpack_require__.p + "" + chunkId + ".bundle.js"; // 设置异步加载的最长超时工夫 var timeout = setTimeout(onScriptComplete, 120000); script.onerror = script.onload = onScriptComplete; // 在 script 加载和执行实现时回调 function onScriptComplete() { // 避免内存泄露 script.onerror = script.onload = null; clearTimeout(timeout); // 去查看 chunkId 对应的 Chunk 是否装置胜利,装置胜利时才会存在于 installedChunks 中 var chunk = installedChunks[chunkId]; if (chunk !== 0) { if (chunk) { chunk[1](new Error('Loading chunk ' + chunkId + ' failed.')); } installedChunks[chunkId] = undefined; } }; head.appendChild(script); return promise; };首先判断这个chunkId是否曾经加载过,如果是的话,间接返回一个resolve的promise如果不为空又不为0,阐明正在加载中,这里的installedChunks[chunkId]是一个数组,外面保留着[resovle, reject],是在发动网络申请的时候赋值的如果下面两个判断都没击中,阐明是没有加载过,上面开始结构加载办法,次要是通过jsonp的模式首先新建一个promise,并对installedChunks[chunkId]赋值,把这个promise以及他的resolve和reject保留在外面,这也是下面为什么能够通过判断installedChunks[chunkId]不为空又不为0即正处于申请当中,间接返回数组第三个值,即新建的promise,让后续操作能够在这个promise上进行回调的注册而后前面的办法就是通过结构一个script标签,插入到head中,保障代码能马上被下载,同时定义代码执行结束时的回调,判断是曾经加载了代码,如果加载胜利革除监听等,如果加载失败,抛出异样最初返回这个promise,供内部注册回调而这里通过jsonp加载的代码就是打包分离出来的另一个文件show.xx.js,也就是异步加载的show.js相干的代码webpackJsonp( // 在其它文件中寄存着的模块的 ID ['show'], // 本文件所蕴含的模块 {// show.js 所对应的模块 show: (function (module, exports) { function show(content) { window.document.getElementById('app').innerText = 'Hello,' + content; } module.exports = show; }) });接着看webpackJsonp这个办法是怎么定义的window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) { // 把 moreModules 增加到 modules 对象中 // 把所有 chunkIds 对应的模块都标记成曾经加载胜利 var moduleId, chunkId, i = 0, resolves = [], result; for (; i < chunkIds.length; i++) { chunkId = chunkIds[i]; if (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]; } } while (resolves.length) { resolves.shift()(); } };chunkIds代表本人这个文件的id名,因为动静加载的时候,是利用动静加载的文件名形成script标签进行下载的,这里传入这个id是为了触发后续promise的resolve以及标记模块以及被加载moreModules就是对应的代码模块汇合executeModules 就是加载实现后须要被执行模块的index首先遍历installedChunks,后面提到过installedChunks[chunkId]通过网络下载的时候,回赋予三个值,代表其对应的promise,这里取出第一个resolve,保存起来,同时将加载标记置为0,示意已加载而后遍历动静加载的模块,把代码块塞到modules数组里最初执行之前保留下来的resolve函数,触发__webpack_require__.e(0).then的执行这样动静加载的代码通过结构jsonp进行下载,并且将对应代码传到bundle.js的modules中进行保留,而后在then函数中通过__webpack_require__执行模块,缓存输入这里为了便于了解,有对代码做肯定调整,实在的输入状况,能够通过具体打包输入查看,这里仅形容具体打包思路