不同配置下的webpack打包文件分析

bundle分析

无splitChunk时的main.bundle.js

如果不splitChunk,也不用dll提前构建,name打包出来的就只有一个文件,就叫main.bundle.js吧,这个文件干啥呢?VScode command + K + 0折叠所有代码:
在这里插入图片描述
哦 , 原来是个自执行函数,看看参数:
在这里插入图片描述

一大坨object, 第一个是入口文件,其他貌似是依赖。接着看看函数拿这些参数要干啥

前面都是方法的定义,走到最后一行:

return __webpack_require__(__webpack_require__.s = "./app.js");

webpack_require

执行__webpack_require__("./app.js),这个方法比较简单:

/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// installedModules缓存中有moduleId模块直接取出来用
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// installedModules缓存中没有的话就以moduleId为key给自己加一个
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// modules就是那一大坨object,就是执行那一大坨object中key:"./app.js"对应的value,是个方法
/******/    // 模块中一般都会module.exports要返回的结果,这不就写到installedModules[moduleId].exports中了嘛
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// 把moduleId的加载状态记为已加载
/******/ 		module.l = true;
/******/
/******/ 		// Return installedModules[moduleId].exports
/******/ 		return module.exports;
/******/ 	}

哦,就是一个带缓存及模块加载状态的webpack require嘛,,接着看看"./app.js"对应的回调方法要干啥:

一大坨eval,里面还是字符串,晕,为啥?不清楚

定睛一看,是要require vue的运行时代码和vue模板文件,这里应该就会构建DOM树吧
在这里插入图片描述

optimization

webpack配置

现在给webpack配置文件添加:

optimization: {
    splitChunks: {
      chunks: 'all',  // 依赖都分离出来
      name: 'vendor', // 分离出来的bundle名称,命名即output中的name
      minChunks: 1,
      // minChunks: 2, // 被共享的最小次数,如loadash只被引用了一次是不是将其抽离的
    },
   // runtimeChunk: true
  },

就是分离依赖的意思

build结果及main.bundle.js

build agian,看看dist文件输出,除过main.bundle.js还有一个vendor.bundle.js,

先看看main.bundle.js,还是一个自执行函数,参数还是那一大坨object,不过方法体中貌似多了很多东西,捡最重要的说

var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
/******/ 	var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);

					// 就是下面这句最重要,在jsonpArray.push时执行webpackJsonpCallback,而在jsonpArray就是window["webpackJsonp"],
          // 所以当先加载main.bundle.js后加载vendor.bundle.js时也会执行到webpackJsonpCallback
/******/ 	jsonpArray.push = webpackJsonpCallback; 
					
          // 再将jsonpArray还原为自己本来的样子,去掉push方法
/******/ 	jsonpArray = jsonpArray.slice();
/******/ 	for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
/******/ 	var parentJsonpFunction = oldJsonpFunction;

/******/ 	// add entry module to deferred list,splitChunk出来的bundle也是一个entry
/******/ 	deferredModules.push(["./app.js","vendor"]);  

这段代码非常关键,也说明为啥main.bundle.js和vendor.bundle.js引入顺序不同也能运行成功的原因。

window[“webpackJsonp”]是啥?容我打开vendor.bundle.js,手动格式化后给大家看看:
在这里插入图片描述
正是给window[“webpackJsonp”] push一个数组(arr),arr0又是一个数组,arr1结果又是那一大坨object,原来是分离出的依赖
在这里插入图片描述
看到这里就能看懂main.bundle.js截取的代码注释了

webpackJsonpCallback window[“webpackJsonp”] push时标记vendor已加载

而webpackJsonpCallback干啥呢

// window["webpackJsonp"] push的东西是一个数组,往上第二个找图,就是参数data
function webpackJsonpCallback(data) {
/******/ 		var chunkIds = data[0]; //  ["vendor"]
/******/ 		var moreModules = data[1]; // 一大坨object
/******/ 		var executeModules = data[2];
/******/
/******/ 		var moduleId, chunkId, i = 0, resolves = [];
/******/ 		for(;i < chunkIds.length; i++) {
/******/ 			chunkId = chunkIds[i]; // chunkId为'vendor'
/******/ 			// some code here and installedChunks初始值为{ "main": 0 }
/******/ 			installedChunks[chunkId] = 0;
/******/ 		}

						// modules中加入vendor.bundle.js中的那一大坨object,当然main.bundle.js自执行方法的入参对应的那一大坨object也在其中,至此,所有的依赖都加载进来了
/******/ 		for(moduleId in moreModules) {
/******/ 			if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/ 				modules[moduleId] = moreModules[moduleId];
/******/ 			}
/******/ 		}
/******/    // some code here
/******/
/******/ 		// run deferred modules when all chunks ready,英文解释已经很清晰了
/******/ 		return checkDeferredModules();
/******/ 	};

checkDeferredModules bundle加载完后执行app.js回调

现在看看这个checkDeferredModules,它才是最终执行代码

// deferredModules.push(["./app.js","vendor"]);

/******/ 	function checkDeferredModules() {
/******/ 		var result;
/******/ 		for(var i = 0; i < deferredModules.length; i++) {
/******/ 			var deferredModule = deferredModules[i]; // deferredModule为["./app.js","vendor"]
/******/ 			var fulfilled = true;
/******/ 			for(var j = 1; j < deferredModule.length; j++) { // 从1开始
/******/ 				var depId = deferredModule[j]; // depId为vendor
/******/ 				if(installedChunks[depId] !== 0) fulfilled = false;
/******/ 			}
							// 如果vendor加载后执行__webpack_require__("./app.js");即执行main.bundle.js自执行函数入参中"./app.js"对应的value回调
/******/ 			if(fulfilled) {
/******/ 				deferredModules.splice(i--, 1);
/******/ 				result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
/******/ 			}
/******/ 		}
/******/
/******/ 		return result;
/******/ 	}

最开始的时候已经解释了"./app.js"回调是干啥的

运行时代码抽离

splitChunk后发现main.bundle.js自执行函数不纯洁,如果把他的入参也取出来,那main.bundle.js不就是真正的runtime了

optimize帮你解决,配置为:optimize.runtimeChunk: true

build again,发现这时有3个文件,分别是:
在这里插入图片描述

这时runtime~main.bundle.js中就是运行时的代码了,而main.bundle.js和vendor.bundle.js都是给window[“webpackJsonp”] push了

DllPlugin (dynamic link libray)动态链接库

终于说到DllPlugin了,它的目的就是提前构建,然后vendor中需要moduleId时只需从提前构建的那一大坨object里取就行,既然是提前构建,那必须不能和webpack.config.js一起运行啊,那就新建一个webpack.dll.js吧

const webpack = require('webpack');
const path = require('path');

module.exports = {
  mode: "development",
  entry: {
    commonLib: ['vue']
  },
  output: {
    path: path.resolve(__dirname, './dllJs'),
    filename: 'dll.[name].js',
    library: '[name]_library'
  },
  plugins: [
    new webpack.DllPlugin({
      path: './[name].manifest.json',
      name: '[name]_library',
      context: __dirname
    })
  ]
}

和webpack普通的配置一样,entry中包含的依赖即需要提前构建的,这里以vue举例,webpack.DllPlugin里的name要和output中的library对应,后面会说到

webpack --config webpack.dll.js

执行后发现dllJs文件下有一个dll.commonLib.js文件,根目录下有一个commonLib.manifest.json文件,看看commonLib.manifest.json文件,包含了vue的运行时代码路径。

"./node_modules/vue/dist/vue.runtime.esm.js": {
      "id": "./node_modules/vue/dist/vue.runtime.esm.js",
      "buildMeta": {
        "exportsType": "namespace",
        "providedExports": ["default"]
      }
    },

而dll.commonLib.js呢?
在这里插入图片描述
将一个很熟悉的自执行函数赋值给了commonLib_library,这个名字就是 webpack.DllPlugin中的name。

打开看看,自执行函数的入参最重要的是包含了vue的运行时代码,很符合预期,这就是我要提前构建的代码呀
在这里插入图片描述
此方法中关键的一步

return __webpack_require__(__webpack_require__.s = 0);

0就是那一大坨object中
在这里插入图片描述
看样子是将此文件中的__webpack_require__写到入参module的exports中

全局来看这个文件就是:

var commonLib_librarycommonLib_library = __webpack_require__

和webpack.config.js结合

那提前构建好了后怎么用呢,这就需要在webpack.config.js中添加配置了

optimization: {
    splitChunks: {
      chunks: 'all',
      name: 'vendor',
      minChunks: 1
    },
    runtimeChunk: true
  },
plugins: [
    new VueLoaderPlugin(),
    new webpack.DllReferencePlugin({
      context: __dirname,   // 我猜是node_modules的路径。。。,可省略
      manifest: require('./commonLib.manifest.json') // 引入dll构建好的json
    })
  ]

build webpack.config.js again

生成的文件是
在这里插入图片描述
先看看 main.bundle.js,仍然是window[“webpackJsonp”] push ,找找vue.runtime.esm.js

/***/ "./node_modules/vue/dist/vue.runtime.esm.js":
/*!*************************************************************************************************!*\
  !*** delegated ./node_modules/vue/dist/vue.runtime.esm.js from dll-reference commonLib_library ***!
  \*************************************************************************************************/
/*! exports provided: default */
/***/ (function(module, exports, __webpack_require__) {

eval("module.exports = (__webpack_require__(/*! dll-reference commonLib_library */ \"dll-reference commonLib_library\"))(\"./node_modules/vue/dist/vue.runtime.esm.js\");\n\n//# sourceURL=webpack:///delegated_./node_modules/vue/dist/vue.runtime.esm.js_from_dll-reference_commonLib_library?");

/***/ }),

恶心的eval,果然evil

上面最关键的是执行

module.exports = (__webpack_require__("dll-reference commonLib_library"))("./node_modules/vue/dist/vue.runtime.esm.js")

webpack_require(“dll-reference commonLib_library”)又是什么鬼,command + f后输入 “dll-reference commonLib_library”:
在这里插入图片描述
原来是把commonLib_library写到module.exports上,commonLib_library就是dll.commonLib.js执行结果,及dll.commonLib.js里边的__webpack_require__

所以使是用dll.commonLib.js里边的__webpack_require__("./node_modules/vue/dist/vue.runtime.esm.js"),哦哦,原理是就是这么动态链接的!!!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Webpack打包配置文件是一个 JavaScript 文件,通常被命名为 webpack.config.js。该文件用于配置 Webpack打包规则和选项。 以下是一个示例的 webpack.config.js 文件的基本结构: ```javascript const path = require('path'); module.exports = { // 入口文件配置 entry: './src/index.js', // 输出文件配置 output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js' }, // 模块加载器配置 module: { rules: [ // 加载器规则 ] }, // 插件配置 plugins: [ // 插件列表 ], // 其他配置选项 }; ``` 在上述配置文件中,可以根据需要进行以下配置: 1. 入口文件配置(entry):指定项目的入口文件。可以是单个文件路径,也可以是一个对象,用于多入口文件配置。 2. 输出文件配置(output):指定打包后的输出文件的位置和名称。可以通过 path 属性指定输出路径,filename 属性指定输出文件名。 3. 模块加载器配置(module.rules):用于配置不同类型文件的加载器。可以通过 rules 数组中的每一项来指定对特定类型文件的处理规则,如使用 babel-loader 处理 JavaScript 文件,使用 style-loader 和 css-loader 处理 CSS 文件等。 4. 插件配置(plugins):用于配置 Webpack 插件。可以通过 plugins 数组中的每一项来添加或配置不同的插件,如 HtmlWebpackPlugin 用于生成 HTML 文件,CleanWebpackPlugin 用于清理输出目录等。 5. 其他配置选项:还有其他一些常用的配置选项,如 resolve 用于配置模块解析规则,devServer 用于配置开发服务器等。 根据项目的需求,可以在 webpack.config.js 中添加适当的配置来满足项目的打包需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值