webpack 代码分割一点事

webpack 俨然已经成为前端最主流的构建工具,其功能多种多样,我们今天就来分析下关于代码分割这部分的一点事,并在最后讲述如何实现在webpack编译出的代码里手动添加一个异步chunk。

什么是chunkId与moduleId?

每个chunkId对应的是一个js文件,每个moduleId对应的是一个个js文件的内容的模块(一个js文件里面可以require多个资源,每个资源分配一个moduleId),所以它两的关系就是一个chunkId可能由很多个moduleId组成。

在webpack 编译出来的代码有定义了一个名称为__webpack_require__的函数,这个函数就是用来加载模块的,所以它的参数自然就是moduleId,如下:

/******/ 	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编译出来的代码是通过什么方法进行异步加载js文件的,从代码中我们可以找到一个名为requireEnsure的函数,这个函数便是来做这个事情的,那自然而然它的参数就是chunkId了,如下:

/******/ 	__webpack_require__.e = function requireEnsure(chunkId) {
/******/ 		var installedChunkData = installedChunks[chunkId];
/******/ 		if(installedChunkData === 0) {
/******/ 			return new Promise(function(resolve) { resolve(); });
/******/ 		}
/******/
/******/ 		// a Promise means "currently loading".
/******/ 		if(installedChunkData) {
/******/ 			return installedChunkData[2];
/******/ 		}
/******/
/******/ 		// setup Promise in chunk cache
/******/ 		var promise = new Promise(function(resolve, reject) {
/******/ 			installedChunkData = installedChunks[chunkId] = [resolve, reject];
/******/ 		});
/******/ 		installedChunkData[2] = promise;
/******/
/******/ 		// start chunk loading
/******/ 		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;
/******/
/******/ 		if (__webpack_require__.nc) {
/******/ 			script.setAttribute("nonce", __webpack_require__.nc);
/******/ 		}
/******/ 		script.src = __webpack_require__.p + "" + ({"0":"home-chunk","1":"users-chunk","2":"about-chunk"}[chunkId]||chunkId) + ".js";
/******/ 		var timeout = setTimeout(onScriptComplete, 120000);
/******/ 		script.onerror = script.onload = onScriptComplete;
/******/ 		function onScriptComplete() {
/******/ 			// avoid mem leaks in IE.
/******/ 			script.onerror = script.onload = null;
/******/ 			clearTimeout(timeout);
/******/ 			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;
/******/ 	};

从上面代码我们可以看出requireEnsure其实就是通过动态创建script标签来加载js文件的,但是这里不是每次访问这个js文件,都进行创建script请求的,在创建script前,requireEnsure会先通过installedChunks读取下是否已经有缓存了,如果有缓存直接使用便可。

一个Demo

干说原理,都抽象啊,还是从一个demo来分析,你轻松我也轻松??,废话少说,先亮出demo地址:https://github.com/canfoo/webpack-code-split

这个demo是一个react项目,既然要讲代码分割,那么肯定要实现动态路由加载了,如下:

export default {
  path: ‘/‘,
  component: Core,
  indexRoute: {
    getComponent(location, cb) {
      System.import(/* webpackChunkName: "home-chunk" */ ‘./components/Home‘)
        .then(loadRoute(cb))
        .catch(errorLoading);
    },
  },
  childRoutes: [
    {
      path: ‘about‘,
      getComponent(location, cb) {
        System.import(/* webpackChunkName: "about-chunk" */ ‘./components/About‘)
          .then(loadRoute(cb))
          .catch(errorLoading);
      },
    },
    {
      path: ‘users‘,
      getComponent(location, cb) {
        System.import(/* webpackChunkName: "users-chunk" */ ‘./components/Users‘)
          .then(loadRoute(cb))
          .catch(errorLoading);
      },
    },
    {
      path: ‘*‘,
      getComponent(location, cb) {
        System.import(/* webpackChunkName: "home-chunk" */ ‘./components/Home‘)
          .then(loadRoute(cb))
          .catch(errorLoading);
      },
    },
  ],
};

如上,demo总共实现三个页面,三个页面都是通过分割的形式进行加载,名称分别为:home-chunk、about-chunk、users-chunk,build下,启动服务(anywhere),打开浏览器,从控制台我们也看到我们想要的结果:

这时候我们打开编辑器看看webpack编译的代码app.js,直接搜索“about-chunk”,我们就定位到上文所说的requireEnsure的函数里有一段这样的代码:

/******/         script.src = __webpack_require__.p + "" + ({"0":"home-chunk","1":"users-chunk","2":"about-chunk"}[chunkId]||chunkId) + ".js";

从这段代码,可以看到每个chunk对应key都是一个数字,这个数字正是chunkId,所以这也是为什么上文说requireEnsure函数的参数是chunkId的原因,因为这个函数正是通过chunkId来异步加载对应的js文件的。

接下来我们来看看代码是在何时调用了这个方法的,全局搜索__webpack_require__.e,我们便知道了:

exports.default = {
  path: ‘/‘,
  component: _Core2.default,
  indexRoute: {
    getComponent: function getComponent(location, cb) {
      __webpack_require__.e/* import() */(0).then(__webpack_require__.bind(null, 227)).then(loadRoute(cb)).catch(errorLoading);
    }
  },
  childRoutes: [{
    path: ‘about‘,
    getComponent: function getComponent(location, cb) {
      __webpack_require__.e/* import() */(2).then(__webpack_require__.bind(null, 268)).then(loadRoute(cb)).catch(errorLoading);
    }
  }, {
    path: ‘users‘,
    getComponent: function getComponent(location, cb) {
      __webpack_require__.e/* import() */(1).then(__webpack_require__.bind(null, 269)).then(loadRoute(cb)).catch(errorLoading);
    }
  },
  {
    path: ‘*‘,
    getComponent: function getComponent(location, cb) {
      __webpack_require__.e/* import() */(0).then(__webpack_require__.bind(null, 227)).then(loadRoute(cb)).catch(errorLoading);
    }
  }]
};

从上面代码就可以看到,正是路由访问适合,调取了这个函数的。我们直接分析其中一个路由就可以了,比如about路由,由上面代码可以看到,当路径访问about时候,就调取对应的getComponent函数,这个函数里面首先执行__webpack_require__.e方法,成功再通过then执行__webpack_require__方法,即先去加载chunk文件,然后再去加载当前chunk文件里的模块,因此我们可以从这里推断出,上面方法中由两个数字 1 和 268 ,这两个数字肯定就是chunkId和modleId了,很显然,1 就是chunkId,而 268 就是moduleId。到了这里,我们是不是也可以推论出 about-chunk.js 文件中是不是也会存在 1 和 268 这两个数字呢?答案是肯定的,我们打开about-chunk.js文件:

webpackJsonp([2],{

/***/ 268:
/***/ (function(module, exports, __webpack_require__) {

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _react = __webpack_require__(13);

var _react2 = _interopRequireDefault(_react);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var About = function About() {
  return _react2.default.createElement(
    ‘div‘,
    null,
    ‘About‘
  );
};

exports.default = About;

/***/ })

});

如上所示,这个文件里直接调用了webpackJsonp方法,而这个方法第一个参数就是chunkIds 列表,而第二个参数就是一个moduleId与模块的对象,而这里正出现我们上文出现两个数字 1 和 268,那为什么需要这两个数字呢。这时候我们还得继续看代码,看webpackJsonp是何方神圣,直接贴代码:

/******/ 	window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
/******/ 		// add "moreModules" to the modules object,
/******/ 		// then flag all "chunkIds" as loaded and fire callback
/******/ 		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];
/******/ 			}
/******/ 		}
/******/ 		if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);
/******/ 		while(resolves.length) {
/******/ 			resolves.shift()();
/******/ 		}
/******/
/******/ 	};

从上面代码可以看出,webpackJsonpCallback主要做的是就是将每个chunk存入到resolves中,并最后依次执行,另外还行chunk里模块缓存到modules变量。

仿照一个异步chunk?

至此,我们就粗略分析完了代码分割事情,那么我们是不是可以直接在编译出的代码里添加一些代码就可以生成一个新的异步chunk了?当然可以了!

假设我们要添加一个新的chunk,名称为test-chunk

第一步,首先到页面添加个路由,这个比较简单,就不贴代码了。

第二步,就在路由代码中添加一个新的路径,这里添加路径就需要手动生成chunkId和moduleId,我们就取以最大的chunkId和moduleId分别加1,即令chunkId=3,moduleId=270:

{
    path: ‘test‘,
    getComponent: function getComponent(location, cb) {
      // 3 为 chunkId ,270 为moduleId
      __webpack_require__.e/* import() */(3).then(__webpack_require__.bind(null, 270)).then(loadRoute(cb)).catch(errorLoading);
    }
  }

第三步,手动生成一个新的test-chunk.js(copy about-chunk.js文件内容改改部门内容即可),这里要注意的是需要将 chunkId=3,moduleId=270 正确的填入到参数里:

webpackJsonp([3],{  // chunkId

/***/ 270: // moduleId
/***/ (function(module, exports, __webpack_require__) {

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _react = __webpack_require__(13);

var _react2 = _interopRequireDefault(_react);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var Test = function Test() {
  return _react2.default.createElement(
    ‘div‘,
    null,
    ‘Test‘
  );
};

exports.default = Test;

/***/ })

});

到此就添加完毕了,这时候刷新页面,并点击"Test"链接,就可以看到浏览器正确加载出js文件了(这个效果其实在上图效果有体现了,这里就不贴出了)

原文地址:https://www.cnblogs.com/canfoo/p/10083706.html

时间: 2024-10-31 04:47:50

webpack 代码分割一点事的相关文章

webpack代码分割;

var webpack=require('webpack') var path=require('path') module.exports={ entry:{ 'pageA':'./src/pageA', 'pageB':'./src/pageB', 'vendor':['lodash'] }, output:{ path:path.resolve(__dirname, './dist'), filename:'[name].bundle.js', chunkFilename: "[name]

webpack练手项目之easySlide(二):代码分割

Hello,大家好. 在上一篇 webpack练手项目之easySlide(一):初探webpack  中我们一起为大家介绍了webpack的基本用法,使用webpack对前端代码进行模块化打包. 但是乍一看webpack只是将所有资源打包到一个JS文件中而已,并没有做到真正的按需加载,这当然不是我们所想要的. 不急,今天的这一章我们就来一起继续探索webpack的另外一个功能:code split. 1.什么是code split  英文不好,暂且将其翻译为代码分割.也就是我们根据实际业务需求

The way of Webpack learning (III.) -- codeSplitting &amp; lazyLoding(代码分割和路由懒加载)

代码分割:对于一个大型的web项目来说,如果为了减少http请求,只打包出一个bundle.js文件,那么只要我们的需求修改了一点点,整个bundle.js都需要重新加载,得不偿失.所以我们不妨把代码分割成一块一块的,按需加载,而且还能利用浏览器缓存机制,如果文件没有修改,直接从缓存读取.也就是说,代码分割就是把代码切成很多块(chunk). 懒加载:按需加载,页面需要什么文件我才去加载什么文件.我现在只知道应用在路由懒加载中,就是根据路由,按需加载不同的文件. 在上一章节提到使用Commonc

【转载】关于烂代码的那些事

http://kb.cnblogs.com/page/526768/ ============上篇============ 1. 摘要 最近写了不少代码,review了不少代码,也做了不少重构,总之是对着烂代码工作了几周.为了抒发一下这几周里好几次到达崩溃边缘的情绪,我决定写一篇文章谈一谈烂代码的那些事.这里是上篇,谈一谈烂代码产生的原因和现象. 2. 写烂代码很容易 刚入程序员这行的时候经常听到一个观点:你要把精力放在ABCD(需求文档/功能设计/架构设计/理解原理)上,写代码只是把想法翻译成

关于烂代码的那些事(中)

1. 摘要 这是烂代码系列的第二篇,在文章中我会跟大家讨论一下如何尽可能高效和客观的评价代码的优劣. 在发布了关于烂代码的那些事(上)之后,发现这篇文章竟然意外的很受欢迎,很多人也描(tu)述(cao)了各自代码中这样或者那样的问题. 最近部门在组织bootcamp,正好我负责培训代码质量部分,在培训课程中让大家花了不少时间去讨论.改进.完善自己的代码.虽然刚毕业的同学对于代 码质量都很用心,但最终呈现出来的质量仍然没能达到“十分优秀”的程度. 究其原因,主要是不了解好的代码“应该”是什么样的.

webpack4 系列教程(四): 单页面解决方案--代码分割和懒加载

本节课讲解webpack4打包单页应用过程中的代码分割和代码懒加载.不同于多页面应用的提取公共代码,单页面的代码分割和懒加载不是通过webpack配置来实现的,而是通过webpack的写法和内置函数实现的. 目前webpack针对此项功能提供 2 种函数: import(): 引入并且自动执行相关 js 代码 require.ensure(): 引入但需要手动执行相关 js 代码 本文将会进行逐一讲解. >>> 本节课源码 >>> 所有课程源码 1. 准备工作 此次代码

React中的代码分割

代码分割想要解决的问题是:经打包工具??生成的bundle文件过大,特别是在我们引入第三方库的情况下. 在React中,我们有一下几种解决方案: 1. 动态加载 1 // math.js 2 export function add(a, b) { 3 return a + b 4 } 5 6 // 未使用动态加载 7 import { add } from './math.js' 8 console.log(add(10, 20)) 9 10 // 使用动态加载 11 import("./mat

关于烂代码的那些事(上)

1. 摘要 最近写了不少代码,review了不少代码,也做了不少重构,总之是对着烂代码工作了几周.为了抒发一下这几周里好几次到达崩溃边缘的情绪,我决定写一篇文章谈一谈烂代码的那些事.这里是上篇,谈一谈烂代码产生的原因和现象. 2. 写烂代码很容易 刚入程序员这行的时候经常听到一个观点:你要把精力放在ABCD(需求文档/功能设计/架构设计/理解原理)上,写代码只是把想法翻译成编程语言而已,是一个没什么技术含量的事情. 当时的我在听到这种观点时会有一种近似于高冷的不屑:你们就是一群傻X,根本不懂代码

关于烂代码的那些事

关于烂代码的那些事(上) 关于烂代码的那些事(中) 关于烂代码的那些事(下)