webpack Code Splitting浅析

2023-10-24 10:59
文章标签 code 浅析 webpack splitting

本文主要是介绍webpack Code Splitting浅析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Code Splitting是webpack的一个重要特性,他允许你将代码打包生成多个bundle。对多页应用来说,它是必须的,因为必须要配置多个入口生成多个bundle;对于单页应用来说,如果只打包成一个bundle可能体积很大,导致无法利用浏览器并行下载的能力,且白屏时间长,也会导致下载很多可能用不到的代码,每次上线用户都得下载全部代码,Code Splitting能够将代码分割,实现按需加载或并行加载多个bundle,可利用并发下载能力,减少首次访问白屏时间,可以只上线必要的文件。


三种Code Splitting方式

webpack提供了三种方式来切割代码,分别是:

  1. 多entry方式
  2. 公共提取
  3. 动态加载
    本文将简单介绍多entry方式和公共提取方式,重点介绍的是动态加载。这几种方式可以根据需要组合起来使用。这里是官方文档,中文 英文

多entry方式

这种方式就是指定多个打包入口,从入口开始将所有依赖打包进一个bundle,每个入口打包成一个bundle。此方式特别适合多页应用,我们可以每个页面指定一个入口,从而每个页面生成一个js。此方式的核心配置代码如下:

const path = require('path');module.exports = {mode: 'development',entry: {page1: './src/page1.js',page2: './src/page2.js'},output: {filename: '[name].bundle.js',path: path.resolve(__dirname, 'dist')}
};

上边的配置最终将生成两个bundle, 即page1.bundle.js和page2.bundle.js。

公共提取

这种方式将公共模块提取出来生成一个bundle,公共模块意味着有可能有很多地方使用,可能导致每个生成的bundle都包含公共模块打包生成的代码,造成浪费,将公共模块提取出来单独生成一个bundle可有效解决这个问题。这里贴一个官方文档给出的配置示例:

  const path = require('path');module.exports = {mode: 'development',entry: {index: './src/index.js',another: './src/another-module.js'},output: {filename: '[name].bundle.js',path: path.resolve(__dirname, 'dist')},// 关键optimization: {splitChunks: {chunks: 'all'}}};

这个示例中index.js和another-module.js中都import了loadsh,如果不配置optimization,将生成两个bundle, 两个bundle都包含loadsh的代码。配置optimization后,loadsh代码被单独提取到一个vendors~another~index.bundle.js。

动态加载

动态加载的含义就是讲代码打包成多个bundle, 需要用到哪个bundle时在加载他。这样做的好处是可以让用户下载需要用到的代码,避免无用代码下载。确定是操作体验可能变差,因为操作之后可能还有一个下载代码的过程。关于动态加载,后面详解。


实现一个简单的动态加载

动态加载就是要实现可以在代码里边去加载其他js,这个太简单了,新建script标签插入dom就可以了,如下:

function loadScript(url) {const script = document.createElement('script');script.src = url;document.head.appendChild(script);
}

只需要在需要加载某个js时调用即可,例如需要点击按钮时加载js可能就如下边这样。

btn.onClick = function() {console.log('1');loadScript('http://abc.com/a.js');
}

看上去非常简单,事实上webpack也是这么做的,但是他的处理更加通用和精细。


webpack动态加载

webpak打包出来的代码怎么执行

现有一个文件test2.js, 其中代码为

console.log('1')

此文件通过webpack打包后输出如下,删除了部分代码,完整版可自己尝试编译一个,也可查看web-test(这个项目是基于react,express,webpack的用于web相关实验的项目,里边使用了code splitting方案来基于路由拆分代码,与code splitting相关的实验放在test-code-split分支)。

(function (modules) { // webpackBootstrap// The module cachevar installedModules = {};// The require functionfunction __webpack_require__(moduleId) {// Check if module is in cacheif (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 functionmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);// Flag the module as loadedmodule.l = true;// Return the exports of the modulereturn module.exports;}return __webpack_require__(__webpack_require__.s = "./test2.js");
})({"./test2.js":(function (module, exports, __webpack_require__) {"use strict";eval("\n\nconsole.log('1');\n\n//# sourceURL=webpack:///./test2.js?");})});

不知大家是不是跟大雄一样之前从未看过webpack编译产出的代码。其实看一下还是挺有趣的,原来我们的代码是放在eval中执行的。细看下这段代码,其实并不复杂。他是一个自执行函数,参数是一个对象,key是模块id(moduleId), value是函数,这个函数是里边是执行我们写的代码,在自执行函数体内是直接调用了一个__webpack_require__,参数就是入口moduleId, __webpack_require__方法里值执行给定模块id对应的函数,核心代码是modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

上面是没有import命令的情况,对于有import命令的情况,产出和上边类似,只是自执行函数的参数有变化。例如:

// 入口文件test2.js
import './b.js'
console.log('1')
// b.js
console.log('b')

这段代码产出的自执行函数里边的参数如下:

// 自执行函数里边的参数
{"./b.js":(function (module, exports, __webpack_require__) {"use strict";eval("\n\nconsole.log('b');\n\n//# sourceURL=webpack:///./b.js?");}),"./test2.js":(function (module, exports, __webpack_require__) {"use strict";eval("\n\n__webpack_require__(/*! ./b.js */ \"./b.js\");\n\nconsole.log('1');\n\n//# sourceURL=webpack:///./test2.js?");})
}

./test2.js这个moduleId对应的函数的eval里边调用了__webpack_require__方法,为了看起来方便,将eval中的字符串拿出来,如下

__webpack_require__("./b.js");
console.log('1');

原来import命令在webpack中就是被转换成了__webpack_require__的调用。太奇妙了,但是话说为啥模块里边为啥要用eval来执行我们写的代码,大雄还是比较困惑的。

webpack动态code splitting方案

经过一番铺垫,终于到主题了,即webpack是如何实现动态加载的。前文大雄给了一个粗陋的动态加载的方法--loadScript, 说白了就是动态创建script标签。webpack中也是类似的,只是他做了一些细节处理。本文只介绍主流程,具体实现细节大家可以自己编译产出一份代码进行研究。

首先需要介绍在webpack中如何使用code splitting,非常简单,就像下边这样

import('lodash').then(_ => {// Do something with lodash (a.k.a '_')...});

我们使用了一个import()方法, 这个import方法经过webpack打包后类似于前文提到的loadScript, 大家可以参看下边的代码:

__webpack_require__.e = function requireEnsure(chunkId) {var promises = [];// JSONP chunk loading for javascriptvar 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 cachevar promise = new Promise(function(resolve, reject) {installedChunkData = installedChunks[chunkId] = [resolve, reject];});promises.push(installedChunkData[2] = promise);// start chunk loadingvar 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);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;var error = new Error('Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')');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);
};

是不是非常熟悉,代码中也调用了document.createElement('script')来创建script标签,最后插入到head里。这段代码所做的就是动态加载js,加载失败时reject,加载成功resolve,这里并不能看到resolve的情况,resolve是在拆分出去的代码里调用一个全局函数实现的。拆分出的js如下:

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{/***/ "./b.js":
/*!**************!*\!*** ./b.js ***!\**************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {"use strict";
eval("\n\nconsole.log('b');\n\n//# sourceURL=webpack:///./b.js?");/***/ })}]);

在webpackJsonp方法里调用了对应的resolve,具体如下:

function webpackJsonpCallback(data) {var chunkIds = data[0];var moreModules = data[1];// add "moreModules" to the modules object,// then flag all "chunkIds" as loaded and fire callbackvar moduleId, chunkId, i = 0, resolves = [];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];}}if(parentJsonpFunction) parentJsonpFunction(data);while(resolves.length) {resolves.shift()();}};

这里的挂到全局的webpackJsonp是个数组,其push方法被改为webpackJsonpCallback方法的数组。所以每次在执行webpackJsonp时实际是在调用webpackJsonpCallback方法。

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])

总结起来,webpack的动态加载流程大致如下:

webpack动态加载原理图

总结

本文对webpack打包出的代码的结构和执行过程作了简单分析,介绍了webpack中code splitting的几种方式,重点分析了一下动态加载的流程。分析的不一定完全正确,大家可以自己使用webpack打包产出代码进行研究,一定会有所收获。大雄看完至少大概知道了原来webpack编出来的代码是那样执行的、Promise原来可以那么灵活的使用。


大雄在学习web开发或在项目中遇到问题时经常需要做一些实验, 在react出了什么新的特性时也常常通过做实验来了解一下. 最开始常常直接在公司的项目做实验, 直接拉个test分支就开搞, 这样做有如下缺点:

  • 在公司的项目去做实验本身就是一件不好的事情
  • 公司的项目里边只有前端的部分, 想要做接口有关的实验不方便. 例如想测试跨域的响应头Access-Control-Allow-Origin就得再启一个web服务器
  • 实验过的东西零散, 过一段时间想查找却找不到了

基于以上原因, 特搭建了个基于react,webpack,express的用于web开发相关实验的项目web-test.欢迎使用。

转载于:https://www.cnblogs.com/floor/p/10788304.html

这篇关于webpack Code Splitting浅析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/274679

相关文章

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

(入门篇)JavaScript 网页设计案例浅析-简单的交互式图片轮播

网页设计已经成为了每个前端开发者的必备技能,而 JavaScript 作为前端三大基础之一,更是为网页赋予了互动性和动态效果。本篇文章将通过一个简单的 JavaScript 案例,带你了解网页设计中的一些常见技巧和技术原理。今天就说一说一个常见的图片轮播效果。相信大家在各类电商网站、个人博客或者展示页面中,都看到过这种轮播图。它的核心功能是展示多张图片,并且用户可以通过点击按钮,左右切换图片。

Debugging Lua Project created in Cocos Code IDE creates “Waiting for debugger to connect” in Win-7

转自 I Installed Cocos Code IDE and created a new Lua Project. When Debugging the Project(F11) the game window pops up and gives me the message waiting for debugger to connect and then freezes. Also a

LLVM入门2:如何基于自己的代码生成IR-LLVM IR code generation实例介绍

概述 本节将通过一个简单的例子来介绍如何生成llvm IR,以Kaleidoscope IR中的例子为例,我们基于LLVM接口构建一个简单的编译器,实现简单的语句解析并转化为LLVM IR,生成对应的LLVM IR部分,代码如下,文件名为toy.cpp,先给出代码,后面会详细介绍每一步分代码: #include "llvm/ADT/APFloat.h"#include "llvm/ADT/S

VS Code 调试go程序的相关配置说明

用 VS code 调试Go程序需要在.vscode/launch.json文件中增加如下配置:  // launch.json{// Use IntelliSense to learn about possible attributes.// Hover to view descriptions of existing attributes.// For more information,

code: 400, msg: Required request body is missing 错误解决

引起这个错误的原因是,请求参数按照get方式给。 应该给json字符串才对 补充: 1. @RequestBody String resource 加@RequestBody必须给json字符串,否则会报错400,记如标题错误。 不加这个的进行请求的话,其实post和get就没有什么区别了。 2. List<String> indexCodes=(List<String>)json.

iOS项目发布提交出现invalid code signing entitlements错误。

1、进入开发者账号,选择App IDs,找到自己项目对应的AppId,点击进去编辑, 2、看下错误提示出现  --Specifically, value "CVYZ6723728.*" for key "com.apple.developer.ubiquity-container-identifiers" in XX is not supported.-- 这样的错误提示 将ubiquity

风暴项目个性化推荐系统浅析

风暴项目的主要任务是搭建自媒体平台,作为主开发人员的我希望把工作重心放在个性化推荐系统上。 目前风暴项目的个性化推荐是基于用户行为信息记录实现的,也就是说对于每条资讯,数据库中有字段标明其类型。建立一张用户浏览表,对用户的浏览行为进行记录,从中可以获取当前用户对哪类资讯感兴趣。 若用户第一次登陆,则按默认规则选取热点资讯做推荐,及所有资讯按浏览量降序排序,取前4个。另外,我考虑到后期可能有商业

中国书法——孙溟㠭浅析碑帖《越州石氏帖》

孙溟㠭浅析碑帖《越州石氏帖》 《越州石氏帖》  是一部汇集多本摹刻的帖,南宋时期的会稽石邦哲(字熙明)把家藏的一些法书碑帖集中一起摹刻成的,宋理宗时临安书商陈思《宝刻丛编》有记載这部帖的目录。现在还存有宋代时拓的残缺本,大多是相传的晋朝唐朝的小楷,后人多有临摹学习,并以此版本重新摹刻。 (图片来源于网络) 图文/氿波整理

浅析网页不安装插件播放RTSP/FLV视频的方法

早期很多摄像头视频流使用的是RTSP、RTMP协议,播放这类协议的视频通常是在网页上安装插件。但现在越来越多的用户,对于网页安装插件比较反感,且随着移动设备的普及,用户更多的希望使用手机、平板等移动设备,直接可以查看这些协议的视频。那是否有什么方案可以直接网页打开RTSP、RTMP协议的视频,直接观看不用安装插件呢?而且对于摄像头的数据,尽可能低延迟的获取实时画面。  其实很多摄像头厂家也注意到