不废话看看官方怎么谈5,Webpack5的新特性javascript
安装webpack(v5)威武版,不用用怎么知道他有多好用?html
npm init -y npm i webpack@next --save-dev
能够直接经过@next方式来安装webpack5版本,目前版本是"^5.0.0-alpha.23"
先来看下基本结构java
├── bootstrap.js // 手动启动webpack ├── pack.js // 本身实现的webpack ├── package-lock.json ├── package.json ├── README.md ├── src │ ├── a.js // 入口文件会引用 a.js │ └── index.js // 打包的入口文件 └── webpack.config.js // webpack配置文件
a.js只是导出个变量而已,很是的简单webpack
module.exports = 'webyouxuan'
index.js负责引入a.js模块web
let webyouxuan= require('./a'); console.log(webyouxuan)
webpack.config文件算法
const path = require('path'); module.exports = { mode:'development', entry:'./src/index.js', output:{ path:path.join(__dirname,'dist'), filename:'main.js' } }
你会发现和webpack4的配置基本没有变化
开始打包,你会发现npx webpack 须要webpack-cli的支持,比较尴尬的是,到目前尚未与之匹配的webpack-cli,没办法啦,咱们只好手动启动webpack了~~~npm
bootstrap.js 引入webpack进行打包项目json
const webpack = require('webpack'); const webpackOptions = require('./webpack.config'); // 须要将 配置文件传入到webpack中,打包成功后咱们打印stats信息 webpack(webpackOptions,(err,stats)=>{ if(err){ console.log(err); }else{ console.log(stats.toJson()) } })
看下打包出来的信息:bootstrap
咱们须要掌握一些关键词:数组
发现比webpack4,打包出来的结果确实少了很多!更简洁,更容易读懂(这里我已把注释删掉)。
// 2.总体函数是个自执行函数 ((modules) => { // 3.module传入的为全部打包后的结果 var installedModules = {}; function __webpack_require__(moduleId) { if (installedModules[moduleId]) { // 作缓存的能够先不理 return installedModules[moduleId].exports; } var module = (installedModules[moduleId] = { // 5.建立模块,每一个模块都有一个exports对象 i: moduleId, l: false, exports: {} }); modules[moduleId](module, module.exports, __webpack_require__); // 6.调用对应的模块函数,将模块exports传入 module.l = true; // 8.用户会将结果放到module.exports对象上 return module.exports; } function startup() { // 经过入口开始加载 return __webpack_require__("./src/index.js"); // 默认返回的是 module.exports结果; } return startup(); // 4.启动加载 })({ // 1.列出打包后的模块 "./src/a.js": module => { eval( "module.exports = 'webyouxuan'\n\n//# sourceURL=webpack:///./src/a.js?" ); }, "./src/index.js": (__unusedmodule, __unusedexports, __webpack_require__) => { // 7.加载其余模块,拿到其余模块的module.exports结果 eval( 'let webyouxuan = __webpack_require__(/*! ./a */ "./src/a.js");\nconsole.log(webyouxuan)\n\n//# sourceURL=webpack:///./src/index.js?' ); } });
总的来讲不难理解,其实仍是内部实现了个___webpack_require__
方法,若是看不懂就多来几遍,看懂了发现也没什么。。。
这样咱们能够直接把main.js在html中直接引入啦~,发现是否是已经能够打印出webyouxuan啦,顺便作个广告,关注咱们!持续推送精品文章,给你点个赞👍
咱们在index入口文件中,采用 import语法动态导入文件
const button = document.createElement('button'); button.innerHTML = '关注 webyouxuan'; document.body.appendChild(button); document.addEventListener('click',()=>{ import('./a').then(data=>{ console.log(data.default); }) });
再回头看编译的结果,貌似好像打包出来的结果有些复杂啦,不要紧!其实核心很简单就是个jsonp加载文件。
打包出来的结果多了个src_a_js.main.js
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([ ["src_a_js"], { "./src/a.js": module => { eval( "module.exports = 'webyouxuan'\n\n//# sourceURL=webpack:///./src/a.js?" ); } } ]);
最终会经过script标签加载这个文件,加载后默认会调用 window下的 webpackJsonp的push方法
咱们来看看 main.js 发生了哪些变化,话说有代码些小复杂,分段来看
先来看index模块作了那些事,为了看着方便我来把代码整理一下
"./src/index.js": ((__unusedmodule, __unusedexports, __webpack_require__) => { eval(` const button = document.createElement('button'); button.innerHTML = '关注 webyouxuan'; document.body.appendChild(button); document.addEventListener('click',()=>{ __webpack_require__.e("src_a_js").then( __webpack_require__.t.bind(__webpack_require__, "./src/a.js", 7)).then(data=>{ console.log(data.default); }) }) `); })
这里出现了两个方法 __webpack_require__.e
和__webpack_require__.t
__webpack_require__.e
方法看似是用来加载文件的,我们来找一找
__webpack_require__.f = {}; __webpack_require__.e = (chunkId) => { // chunkId => src_a_js动态加载的模块 return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => { __webpack_require__.f[key](chunkId, promises); // 调用j方法 将数组传入 return promises; }, [])); }; var installedChunks = { "main": 0 // 默认main已经加载完成 }; // f方法上有个j属性 __webpack_require__.f.j = (chunkId, promises) => { var installedChunkData = Object.prototype.hasOwnProperty.call(installedChunks, chunkId) ? installedChunks[chunkId] : undefined; if(installedChunkData !== 0) { // 默认installedChunks确定没有要加载的模块 if(installedChunkData) { promises.push(installedChunkData[2]); } else { if(true) { // 建立一个promise 把当前的promise 成功失败保存到 installedChunks var promise = new Promise((resolve, reject) => { installedChunkData = installedChunks[chunkId] = [resolve, reject]; }); // installedChunks[src_a_js] = [resolve,reject,promise] promises.push(installedChunkData[2] = promise); // 这句的意思是看是否配置publicPath,配置了就加个前缀 var url = __webpack_require__.p + __webpack_require__.u(chunkId); // 1)建立script标签 var script = document.createElement('script'); var onScriptComplete; script.charset = 'utf-8'; script.timeout = 120; script.src = url; // 2)开始加载这个文件 var error = new Error(); onScriptComplete = function (event) { // 完成工做 script.onerror = script.onload = null; clearTimeout(timeout); var reportError = loadingEnded(); if(reportError) { 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; reportError(error); } }; var timeout = setTimeout(function(){ // 超时工做 onScriptComplete({ type: 'timeout', target: script }); }, 120000); script.onerror = script.onload = onScriptComplete; document.head.appendChild(script); // 3)将标签插入到页面 } else installedChunks[chunkId] = 0; } } };
虽然代码量比较多,其实核心就干了一件事 : 建立script标签,文件加载回来那确定就会调用push方法咯!
先跳过这段看下面的
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([ ["src_a_js"], { "./src/a.js": module => { eval( "module.exports = 'webyouxuan'\n\n//# sourceURL=webpack:///./src/a.js?" ); } } ]);
function webpackJsonpCallback(data) { // 3) 文件加载后会调用此方法 var chunkIds = data[0]; // data是什么来着,你看看src_a_js怎么写的你就知道了 看上面! ["src_a_js"] 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]) { // installedChunks[src_a_js] = [resolve,reject,promise] 这个是上面作的 // 很好理解 其实就是取到刚才放入的promise的resolve方法 resolves.push(installedChunks[chunkId][0]); } installedChunks[chunkId] = 0; // 模块加载完成 } for(moduleId in moreModules) { // 将新增模块与默认的模块进行合并 也是就是modules模块,这样modules中就多了动态加载的模块 if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { __webpack_require__.m[moduleId] = moreModules[moduleId]; } } if(runtime) runtime(__webpack_require__); if(parentJsonpFunction) parentJsonpFunction(data); while(resolves.length) { // 调用promise的resolve方法,这样e方法就调用完成了 resolves.shift()(); } }; var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; // 1) window["webpackJsonp"]等于一个数组 var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); jsonpArray.push = webpackJsonpCallback; // 2) 重写了数组的push方法 var parentJsonpFunction = oldJsonpFunction;
加载的文件会调用 webpackJsonpCallback方法,内部就是将新增的模块合并到modules上,而且让__webpack_require__.e
完成
__webpack_require__.t = function(value, mode) { // t方法其实很简单就是 if(mode & 1) value = this(value); // 就是调用__webpack_require__加载最新的模块 };
这样用户就能够拿到新增的模块结果啦~~~,源码虽难,可是多看几遍总会有收获!
到此咱们就将webpack5的懒加载功能整个过了一遍,其实思路和webpack4的懒加载同样呢~,不过不得不说webpack5打包出来的代码更加简洁啦! 期待webpack5正式发版!!!