Webpack编译结果浅析
如今webpack已经是一个不可或缺的前端构建工具,借助这个构建工具,我们可以使用比较新的技术(浏览器不能直接支持)来开发。
你是否好奇你写的代码经过webpack构建之后会生成什么东西?是否有时调试遇到莫名其妙的问题?
本文不讲,只是基于几个基础的例子,简要分析一下 webpack@4.20.2 构建后的代码结构,当然了,并不全面,时间问题能力问题还不能理解到位。
代码比较长,生成的代码也比较晦涩比较绕,也可能条理不顺,客官坐好咧~
一、webpack的运行机制
webpack的运行过程实际上可以归纳为这个步骤
读取配置参数 -> 相关事件绑定(插件参与) -> 识别各入口entry模块 -> 编译文件(loader参与)-> 生成文件
首先读取我们的配置文件如 webpack.config.js,然后事件流就参与进来绑定相关的事件,webpack中的事件使用 tapable 来管理,在这一阶段,除了绑定webpack内置的一大堆事件之外,还支持自定义的一些事件处理。
配置中的 plugins部分,实际上也可以看作是一些自定义的事件处理,因为插件将在定义的”相关时刻“插入到编译过程中处理资源,这里的”相关时刻“指的就是 订阅-发布 模式中的发布环节
webpack支持多个入口模块,所以还需要进行各入口模块的分析(这里的入口模块只能为js模块),比如以下两个入口模块
分析完入口模块,接下来分析该模块的依赖,并使用相关loader进行编译(如果需要loader的话),真正的编译环节是在这里。
期间会使用ast抽象语法树来分析语法,直到编译完成,输出到相应的文件中
可以来看看这篇文章 webpack运行机制
二、webpack编译结果
由最简单的例子开始
2.1 无依赖的单个模块
./main.js
console.log('main');
./webpack.config.js
module.exports = { // entry: './main', entry: { main: './main' }, mode: 'none', output: { path: path.resolve(__dirname, 'dist'), filename: '[name].js' } };
注意,在webpack4中默认的mode对 development和production进行了一些特殊配置,为了简化,这里就设置成none
编译一个文件,将在dist目录中生成
./dist/main.js
1 /******/ (function(modules) { // webpackbootstrap 2 /******/ // the module cache 3 /******/ var installedmodules = {}; 4 /******/ 5 /******/ // the require function 6 /******/ function __webpack_require__(moduleid) { 7 /******/ 8 /******/ // check if module is in cache 9 /******/ if(installedmodules[moduleid]) { 10 /******/ return installedmodules[moduleid].exports; 11 /******/ } 12 /******/ // create a new module (and put it into the cache) 13 /******/ var module = installedmodules[moduleid] = { 14 /******/ i: moduleid, 15 /******/ l: false, 16 /******/ exports: {} 17 /******/ }; 18 /******/ 19 /******/ // execute the module function 20 /******/ modules[moduleid].call(module.exports, module, module.exports, __webpack_require__); 21 /******/ 22 /******/ // flag the module as loaded 23 /******/ module.l = true; 24 /******/ 25 /******/ // return the exports of the module 26 /******/ return module.exports; 27 /******/ } 28 /******/ 29 /******/ 30 /******/ // expose the modules object (__webpack_modules__) 31 /******/ __webpack_require__.m = modules; 32 /******/ 33 /******/ // expose the module cache 34 /******/ __webpack_require__.c = installedmodules; 35 /******/ 36 /******/ // define getter function for harmony exports 37 /******/ __webpack_require__.d = function(exports, name, getter) { 38 /******/ if(!__webpack_require__.o(exports, name)) { 39 /******/ object.defineproperty(exports, name, { enumerable: true, get: getter }); 40 /******/ } 41 /******/ }; 42 /******/ 43 /******/ // define __esmodule on exports 44 /******/ __webpack_require__.r = function(exports) { 45 /******/ if(typeof symbol !== 'undefined' && symbol.tostringtag) { 46 /******/ object.defineproperty(exports, symbol.tostringtag, { value: 'module' }); 47 /******/ } 48 /******/ object.defineproperty(exports, '__esmodule', { value: true }); 49 /******/ }; 50 /******/ 51 /******/ // create a fake namespace object 52 /******/ // mode & 1: value is a module id, require it 53 /******/ // mode & 2: merge all properties of value into the ns 54 /******/ // mode & 4: return value when already ns object 55 /******/ // mode & 8|1: behave like require 56 /******/ __webpack_require__.t = function(value, mode) { 57 /******/ if(mode & 1) value = __webpack_require__(value); 58 /******/ if(mode & 8) return value; 59 /******/ if((mode & 4) && typeof value === 'object' && value && value.__esmodule) return value; 60 /******/ var ns = object.create(null); 61 /******/ __webpack_require__.r(ns); 62 /******/ object.defineproperty(ns, 'default', { enumerable: true, value: value }); 63 /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); 64 /******/ return ns; 65 /******/ }; 66 /******/ 67 /******/ // getdefaultexport function for compatibility with non-harmony modules 68 /******/ __webpack_require__.n = function(module) { 69 /******/ var getter = module && module.__esmodule ? 70 /******/ function getdefault() { return module['default']; } : 71 /******/ function getmoduleexports() { return module; }; 72 /******/ __webpack_require__.d(getter, 'a', getter); 73 /******/ return getter; 74 /******/ }; 75 /******/ 76 /******/ // object.prototype.hasownproperty.call 77 /******/ __webpack_require__.o = function(object, property) { return object.prototype.hasownproperty.call(object, property); }; 78 /******/ 79 /******/ // __webpack_public_path__ 80 /******/ __webpack_require__.p = ""; 81 /******/ 82 /******/ 83 /******/ // load entry module and return exports 84 /******/ return __webpack_require__(__webpack_require__.s = 0); 85 /******/ }) 86 /************************************************************************/ 87 /******/ ([ 88 /* 0 */ 89 /***/ (function(module, exports) { 90 91 92 console.log('main'); 93 94 95 /***/ }) 96 /******/ ]);
可以看到首先是一个匿名函数,在87行时自执行传入
[ /* 0 */ /***/ (function(module, exports) { console.log('main'); /***/ }) /******/ ]
这个是modules,表示有一个模块需要加载
第3行使用 installedmodules 来缓存已经加载的模块
webpack由最初支持 commonjs模块规范,到后来要支持es6的模块等,为了兼容不同的模块机制,定义了一个 __webpack_require__ 函数作为webpack内部的require
/******/ // the require function /******/ 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, // 模块id /******/ 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; // 返回模块的导出项目 /******/ }
其中,这个调用非常重要
modules[moduleid].call(module.exports, module, module.exports, __webpack_require__)
结合匿名函数传入的参数来看,modules[moduleid] 其实就是这个
(function(module, exports) { console.log('main'); /***/ })
第一个参数 module.exports 实际上就是上面模块的导出项,是为了保证this能正确地指向module,第二第三个参数按着顺序来,第四个参数一般用于依赖
因为这里 main.js没有依赖其他模块,所以没有传进来
最后 return module.exports; 实际上就是返回了模块的导出项,在上面的84行中,入口模块被引入 。从而自动地加载第一个模块并执行
return __webpack_require__(__webpack_require__.s = 0); // __webpack_require__.s为入口文件,此处引用模块id
另外再看其它代码,
/******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; // 将模块存起来 /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedmodules; // 将已经加载的模块存起来 /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; // 设置的 publicpath
这里没什么可说的,这里的publicpath对应于 output中的配置,如
output: { publicpath: './dist/', path: path.resolve(__dirname, 'dist'), filename: '[name].js' },
另外
/******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ object.defineproperty(exports, name, { enumerable: true, get: getter }); /******/ } /******/ }; /******/ /******/ // define __esmodule on exports /******/ __webpack_require__.r = function(exports) { /******/ if(typeof symbol !== 'undefined' && symbol.tostringtag) { /******/ object.defineproperty(exports, symbol.tostringtag, { value: 'module' }); /******/ } /******/ object.defineproperty(exports, '__esmodule', { value: true }); /******/ }; /******/ // object.prototype.hasownproperty.call /******/ __webpack_require__.o = function(object, property) { return object.prototype.hasownproperty.call(object, property); };
这里 __webpack_require__.o 这里只是hasownproperty的包装
__webpack_require__.d 这里是对exports定义一个属性(当前模块未用到,暂且如此,理解不到位)
__webpack_require__.r 这里是对es6模块中的export的支持(当前模块未用到,暂且如此,理解不到位)
还有这个,这个就更难理解了
/******/ // create a fake namespace object /******/ // mode & 1: value is a module id, require it /******/ // mode & 2: merge all properties of value into the ns /******/ // mode & 4: return value when already ns object /******/ // mode & 8|1: behave like require /******/ __webpack_require__.t = function(value, mode) { /******/ if(mode & 1) value = __webpack_require__(value); /******/ if(mode & 8) return value; /******/ if((mode & 4) && typeof value === 'object' && value && value.__esmodule) return value; /******/ var ns = object.create(null); /******/ __webpack_require__.r(ns); /******/ object.defineproperty(ns, 'default', { enumerable: true, value: value }); /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); /******/ return ns; /******/ }; /******/ /******/ // getdefaultexport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esmodule ? /******/ function getdefault() { return module['default']; } : /******/ function getmoduleexports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ };
__webpack_require__.t 暂时不说明了,还看不懂怎么调用的..
__webpack_require__.n 这个主要也是为 es6模块服务的,也没能理解好,知道的可以在评论区留言哈~
2. 有依赖的单个模块
先使用最基础的commonjs模块规范 require, exports ,module.exports 有助于理解上面那个模块的导出项目
./main.js
let number = require('./number'); console.log('main', number);
./number.js
let n = 10; exports.n = n;
编译后,生成的文件变化的只是匿名函数传入的部分
./dist/main.js
// 省略 /******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) { let number = __webpack_require__(1); console.log('main', number); /***/ }), /* 1 */ /***/ (function(module, exports) { let n = 10; exports.n = n; /***/ }) /******/ ]);
注意到前面的数字即是模块的id,也可图中的一致
这里__webpack_require__参数被传进来,main.js中引入number这个模块 __webpack_require__(1);
number模块中 exports.n = n,注意这里的 exports即是调用时的第二个参数
modules[moduleid].call(module.exports, module, module.exports, __webpack_require__);
所以此时 n属性被存入module的export导出项中,从而__webpack_require__(1) 就能获取这个导出项
换种方式,使用es6的模块导出
更改 ./number.js
let n = 10; export { n };
编译后 ./dist/main.js
/******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) { let number = __webpack_require__(1); console.log('main', number); /***/ }), /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "n", function() { return n; }); let n = 10; /***/ }) /******/ ]);
可以看到模块1变了,为了兼容 export ,使用 __webpack_require__.r 定义了它为es6模块,再使用__webpack_require__.d 将 n保存到模块的导出项中
__webpack_require__.d 函数中的 getter即为 这里的 function() { return n; },通过设置为对象的get属性,可以获取到 n这个返回值
var o = {}; object.defineproperty(o, 'abc', { get: function() { return 123; } }); console.log(o.abc); // 123
所以将 let n = 10 定义在后面也是没问题的,因为getter是在number模块被调用返回之后才使用的
接着,我们把引入依赖文件改为import
./main.js
import {n} from './number'; console.log('main', n);
编译后 ./dist/main.js
/******/ ([ /* 0 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony import */ var _number__webpack_imported_module_0__ = __webpack_require__(1); console.log('main', _number__webpack_imported_module_0__["n"]); /***/ }), /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "n", function() { return n; }); let n = 10; /***/ }) /******/ ]);
同样的,这时main模块用到了es6的模块引入方式,所以 __webpack_require__.r(__webpack_exports__);
var _number__webpack_imported_module_0__ = __webpack_require__(1);
这个 __webpack_require__(1) 实际上就是 number模块的模块导出项,自然就能取到属性 n 了
接下来,着眼那个 default字眼,继续更换模块的导入导出方式
./main.js
import n from './number'; console.log('main', n);
./number.js
let n = 10; export default n;
./dist/main.js
/******/ ([ /* 0 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony import */ var _number__webpack_imported_module_0__ = __webpack_require__(1); console.log('main', _number__webpack_imported_module_0__["default"]); /***/ }), /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); let n = 10; /* harmony default export */ __webpack_exports__["default"] = (n); /***/ }) /******/ ]);
可以看到,变化只是属性变成了default
再来一种 es6的方式
./main.js
import n from './number'; console.log('main', n);
./number.js
import {str as n} from './str'; export default n;
./str.js
export var str = 10;
编译后
./dist/main.js
/******/ ([ /* 0 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony import */ var _number__webpack_imported_module_0__ = __webpack_require__(1); console.log('main', _number__webpack_imported_module_0__["default"]); /***/ }), /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony import */ var _str__webpack_imported_module_0__ = __webpack_require__(2); /* harmony default export */ __webpack_exports__["default"] = (_str__webpack_imported_module_0__["str"]); /***/ }), /* 2 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "str", function() { return str; }); var str = 10; /***/ }) /******/ ]);
可以看到 {str as n} 也是没什么影响的,通过上面的例子应该基本能理解模块的依赖了
3. 多个入口模块
如果不提取多模块之间的公共部分,多个入口模块和单个的不同之处就是多了一个文件而已,它们是独立的。
所以这里就不多说了
4. 异步加载模块
webpack支持使用require.ensure来异步加载模块
./main.js
console.log('main'); settimeout(() => { require([], (require) => { let number = require('./number'); console.log(number.n); }); }, 1000);
./number.js
let n = 10; export { n };
webpack.config.js中要加上 publicpath,防止异步模块加载路径出错
output: { publicpath: './dist/', path: path.resolve(__dirname, 'dist'), filename: '[name].js' }
编译后,生成的 1.js即为异步的模块number
./dist/1.js
(window["webpackjsonp"] = window["webpackjsonp"] || []).push([[1],[ /* 0 */, /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "n", function() { return n; }); let n = 10; /***/ }) ]]);
可以看到,这里首先获取 (window["webpackjsonp"] = window["webpackjsonp"] || []), 再调用 push 传入模块及其依赖
jsonp类似我们跨域中的动态插入脚本,这里也是一样,动态插入一个script标签,把src设置好就加载这个异步模块了
push参数中第一个为当前异步模块
看看 ./dist/main.js
1 /******/ (function(modules) { // webpackbootstrap 2 /******/ // install a jsonp callback for chunk loading 3 /******/ function webpackjsonpcallback(data) { 4 /******/ var chunkids = data[0]; 5 /******/ var moremodules = data[1]; 6 /******/ 7 /******/ 8 /******/ // add "moremodules" to the modules object, 9 /******/ // then flag all "chunkids" as loaded and fire callback 10 /******/ var moduleid, chunkid, i = 0, resolves = []; 11 /******/ for(;i < chunkids.length; i++) { 12 /******/ chunkid = chunkids[i]; 13 /******/ if(installedchunks[chunkid]) { 14 /******/ resolves.push(installedchunks[chunkid][0]); 15 /******/ } 16 /******/ installedchunks[chunkid] = 0; 17 /******/ } 18 /******/ for(moduleid in moremodules) { 19 /******/ if(object.prototype.hasownproperty.call(moremodules, moduleid)) { 20 /******/ modules[moduleid] = moremodules[moduleid]; 21 /******/ } 22 /******/ } 23 /******/ if(parentjsonpfunction) parentjsonpfunction(data); 24 /******/ 25 /******/ while(resolves.length) { 26 /******/ resolves.shift()(); 27 /******/ } 28 /******/ 29 /******/ }; 30 /******/ 31 /******/ 32 /******/ // the module cache 33 /******/ var installedmodules = {}; 34 /******/ 35 /******/ // object to store loaded and loading chunks 36 /******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched 37 /******/ // promise = chunk loading, 0 = chunk loaded 38 /******/ var installedchunks = { 39 /******/ 0: 0 40 /******/ }; 41 /******/ 42 /******/ 43 /******/ 44 /******/ // script path function 45 /******/ function jsonpscriptsrc(chunkid) { 46 /******/ return __webpack_require__.p + "" + ({}[chunkid]||chunkid) + ".js" 47 /******/ } 48 /******/ 49 /******/ // the require function 50 /******/ function __webpack_require__(moduleid) { 51 /******/ 52 /******/ // check if module is in cache 53 /******/ if(installedmodules[moduleid]) { 54 /******/ return installedmodules[moduleid].exports; 55 /******/ } 56 /******/ // create a new module (and put it into the cache) 57 /******/ var module = installedmodules[moduleid] = { 58 /******/ i: moduleid, 59 /******/ l: false, 60 /******/ exports: {} 61 /******/ }; 62 /******/ 63 /******/ // execute the module function 64 /******/ modules[moduleid].call(module.exports, module, module.exports, __webpack_require__); 65 /******/ 66 /******/ // flag the module as loaded 67 /******/ module.l = true; 68 /******/ 69 /******/ // return the exports of the module 70 /******/ return module.exports; 71 /******/ } 72 /******/ 73 /******/ // this file contains only the entry chunk. 74 /******/ // the chunk loading function for additional chunks 75 /******/ __webpack_require__.e = function requireensure(chunkid) { 76 /******/ var promises = []; 77 /******/ 78 /******/ 79 /******/ // jsonp chunk loading for javascript 80 /******/ 81 /******/ var installedchunkdata = installedchunks[chunkid]; 82 /******/ if(installedchunkdata !== 0) { // 0 means "already installed". 83 /******/ 84 /******/ // a promise means "currently loading". 85 /******/ if(installedchunkdata) { 86 /******/ promises.push(installedchunkdata[2]); 87 /******/ } else { 88 /******/ // setup promise in chunk cache 89 /******/ var promise = new promise(function(resolve, reject) { 90 /******/ installedchunkdata = installedchunks[chunkid] = [resolve, reject]; 91 /******/ }); 92 /******/ promises.push(installedchunkdata[2] = promise); 93 /******/ 94 /******/ // start chunk loading 95 /******/ var head = document.getelementsbytagname('head')[0]; 96 /******/ var script = document.createelement('script'); 97 /******/ var onscriptcomplete; 98 /******/ 99 /******/ script.charset = 'utf-8'; 100 /******/ script.timeout = 120; 101 /******/ if (__webpack_require__.nc) { 102 /******/ script.setattribute("nonce", __webpack_require__.nc); 103 /******/ } 104 /******/ script.src = jsonpscriptsrc(chunkid); 105 /******/ 106 /******/ onscriptcomplete = function (event) { 107 /******/ // avoid mem leaks in ie. 108 /******/ script.onerror = script.onload = null; 109 /******/ cleartimeout(timeout); 110 /******/ var chunk = installedchunks[chunkid]; 111 /******/ if(chunk !== 0) { 112 /******/ if(chunk) { 113 /******/ var errortype = event && (event.type === 'load' ? 'missing' : event.type); 114 /******/ var realsrc = event && event.target && event.target.src; 115 /******/ var error = new error('loading chunk ' + chunkid + ' failed.\n(' + errortype + ': ' + realsrc + ')'); 116 /******/ error.type = errortype; 117 /******/ error.request = realsrc; 118 /******/ chunk[1](error); 119 /******/ } 120 /******/ installedchunks[chunkid] = undefined; 121 /******/ } 122 /******/ }; 123 /******/ var timeout = settimeout(function(){ 124 /******/ onscriptcomplete({ type: 'timeout', target: script }); 125 /******/ }, 120000); 126 /******/ script.onerror = script.onload = onscriptcomplete; 127 /******/ head.appendchild(script); 128 /******/ } 129 /******/ } 130 /******/ return promise.all(promises); 131 /******/ }; 132 /******/ 133 /******/ // expose the modules object (__webpack_modules__) 134 /******/ __webpack_require__.m = modules; 135 /******/ 136 /******/ // expose the module cache 137 /******/ __webpack_require__.c = installedmodules; 138 /******/ 139 /******/ // define getter function for harmony exports 140 /******/ __webpack_require__.d = function(exports, name, getter) { 141 /******/ if(!__webpack_require__.o(exports, name)) { 142 /******/ object.defineproperty(exports, name, { enumerable: true, get: getter }); 143 /******/ } 144 /******/ }; 145 /******/ 146 /******/ // define __esmodule on exports 147 /******/ __webpack_require__.r = function(exports) { 148 /******/ if(typeof symbol !== 'undefined' && symbol.tostringtag) { 149 /******/ object.defineproperty(exports, symbol.tostringtag, { value: 'module' }); 150 /******/ } 151 /******/ object.defineproperty(exports, '__esmodule', { value: true }); 152 /******/ }; 153 /******/ 154 /******/ // create a fake namespace object 155 /******/ // mode & 1: value is a module id, require it 156 /******/ // mode & 2: merge all properties of value into the ns 157 /******/ // mode & 4: return value when already ns object 158 /******/ // mode & 8|1: behave like require 159 /******/ __webpack_require__.t = function(value, mode) { 160 /******/ if(mode & 1) value = __webpack_require__(value); 161 /******/ if(mode & 8) return value; 162 /******/ if((mode & 4) && typeof value === 'object' && value && value.__esmodule) return value; 163 /******/ var ns = object.create(null); 164 /******/ __webpack_require__.r(ns); 165 /******/ object.defineproperty(ns, 'default', { enumerable: true, value: value }); 166 /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); 167 /******/ return ns; 168 /******/ }; 169 /******/ 170 /******/ // getdefaultexport function for compatibility with non-harmony modules 171 /******/ __webpack_require__.n = function(module) { 172 /******/ var getter = module && module.__esmodule ? 173 /******/ function getdefault() { return module['default']; } : 174 /******/ function getmoduleexports() { return module; }; 175 /******/ __webpack_require__.d(getter, 'a', getter); 176 /******/ return getter; 177 /******/ }; 178 /******/ 179 /******/ // object.prototype.hasownproperty.call 180 /******/ __webpack_require__.o = function(object, property) { return object.prototype.hasownproperty.call(object, property); }; 181 /******/ 182 /******/ // __webpack_public_path__ 183 /******/ __webpack_require__.p = "./dist/"; 184 /******/ 185 /******/ // on error function for async loading 186 /******/ __webpack_require__.oe = function(err) { console.error(err); throw err; }; 187 /******/ 188 /******/ var jsonparray = window["webpackjsonp"] = window["webpackjsonp"] || []; 189 /******/ var oldjsonpfunction = jsonparray.push.bind(jsonparray); 190 /******/ jsonparray.push = webpackjsonpcallback; 191 /******/ jsonparray = jsonparray.slice(); 192 /******/ for(var i = 0; i < jsonparray.length; i++) webpackjsonpcallback(jsonparray[i]); 193 /******/ var parentjsonpfunction = oldjsonpfunction; 194 /******/ 195 /******/ 196 /******/ // load entry module and return exports 197 /******/ return __webpack_require__(__webpack_require__.s = 0); 198 /******/ }) 199 /************************************************************************/ 200 /******/ ([ 201 /* 0 */ 202 /***/ (function(module, exports, __webpack_require__) { 203 204 205 206 console.log('main'); 207 208 settimeout(() => { 209 __webpack_require__.e(/* amd require */ 1).then(function() { var __webpack_amd_require_array__ = []; ((require) => { 210 let number = __webpack_require__(1); 211 212 console.log(number.n); 213 }).apply(null, __webpack_amd_require_array__);}).catch(__webpack_require__.oe); 214 }, 1000); 215 216 217 /***/ }) 218 /******/ ]);
这下蹦出了许多代码,从这里开始会比较绕,需要有耐心!
按照代码执行顺序来分析,思路就清晰了
38行中定义了installedchunks这个新变量,它指代依赖模块(不仅包括此处的异步模块,也包括后续会说到的公共模块,runtime模块等),而上面installedmodules指的是所有的模块
/******/ // object to store loaded and loading chunks /******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched /******/ // promise = chunk loading, 0 = chunk loaded /******/ var installedchunks = { /******/ 0: 0 /******/ };
前面的0表示模块id,在这里指的就是 ./main.js这个入口模块了,它初始的状态就被webpack设置成已加载
/******/ // script path function /******/ function jsonpscriptsrc(chunkid) { /******/ return __webpack_require__.p + "" + ({}[chunkid]||chunkid) + ".js" /******/ }
这里就是异步模块的路径了,({}[chunkid]||chunkid) 这个只是为了防止出错做的处理
__webpack_require__ 函数的内容没变
75行多了一个 __webpack_require__.e 用来加载异步模块,这个稍后再讲
继续到182行开始
/******/ // __webpack_public_path__ /******/ __webpack_require__.p = "./dist/"; /******/ /******/ // on error function for async loading /******/ __webpack_require__.oe = function(err) { console.error(err); throw err; }; /******/ /******/ 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]); /******/ var parentjsonpfunction = oldjsonpfunction; /******/ /******/ /******/ // load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 0);
这里的publicpath就是我们刚刚设置的
__webpack_require__.oe 只是用于处理错误
初始会判断是否有window["webpackjsonp"]存在,有的话就缓存起来,并将this的指向设置好 jsonparray.push.bind(jsonparray)
要理清楚 jsonparray.push ,它不是简单的数组,所以有些绕,它指向了第3行webpackjsonpcallback这个函数
如果初始已经有待加载的依赖模块,则在for循环中直接加载。此处初始阶段是没有值的,所以可以直接略过
要看明白webpackjsonpcallback这个函数,得从调用它的地方开始,在216行中开始调用
settimeout(() => { __webpack_require__.e(/* amd require */ 1).then(function() { var __webpack_amd_require_array__ = []; ((require) => { let number = __webpack_require__(1); console.log(number.n); }).apply(null, __webpack_amd_require_array__);}).catch(__webpack_require__.oe); }, 1000);
/******/ // this file contains only the entry chunk. /******/ // the chunk loading function for additional chunks /******/ __webpack_require__.e = function requireensure(chunkid) { /******/ var promises = []; // promise队列,支持模块加载完成后多个异步回调 /******/ /******/ /******/ // jsonp chunk loading for javascript /******/ /******/ var installedchunkdata = installedchunks[chunkid]; // 未加载 /******/ if(installedchunkdata !== 0) { // 0 means "already installed". /******/ /******/ // a promise means "currently loading". // 加载中,则支持下一个回调加入 /******/ if(installedchunkdata) { /******/ promises.push(installedchunkdata[2]); /******/ } else { // 初始化一个promise来加载 /******/ // setup promise in chunk cache /******/ var promise = new promise(function(resolve, reject) { // 将resolve和reject存入模块中,方便其他地方调用 /******/ installedchunkdata = installedchunks[chunkid] = [resolve, reject]; /******/ }); // installedchunkdata的第三项即为一个promise对象,并存入promises队列中 /******/ promises.push(installedchunkdata[2] = promise); /******/ /******/ // start chunk loading /******/ var head = document.getelementsbytagname('head')[0]; /******/ var 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; // 调用reject /******/ chunk[1](error); /******/ } /******/ installedchunks[chunkid] = undefined; /******/ } /******/ }; /******/ var timeout = settimeout(function(){ /******/ onscriptcomplete({ type: 'timeout', target: script }); /******/ }, 120000); /******/ script.onerror = script.onload = onscriptcomplete; // 在head标签中插入脚本 /******/ head.appendchild(script); /******/ } /******/ } /******/ return promise.all(promises); /******/ };
一秒钟后加载这个异步模块 ./1.js ,该模块加载完成后就开始执行
(window["webpackjsonp"] = window["webpackjsonp"] || []).push([[1],[ /* 0 */, /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "n", function() { return n; }); let n = 10; /***/ }) ]]);
此时的 window["webpackjsonp"] 已经被这句代码影响,jsonparray.push = webpackjsonpcallback; 所以push实际上调用的是 webpackjsonpcallback函数
/******/ // install a jsonp callback for chunk loading /******/ function webpackjsonpcallback(data) { /******/ var chunkids = data[0]; // 依赖的模块id,此时是[1] /******/ var moremodules = data[1]; // 依赖的模块内容 /******/ /******/ /******/ // add "moremodules" to the modules object, /******/ // then flag all "chunkids" as loaded and fire callback /******/ var moduleid, chunkid, i = 0, resolves = []; // 遍历依赖的模块进行加载 /******/ for(;i < chunkids.length; i++) { /******/ chunkid = chunkids[i]; /******/ if(installedchunks[chunkid]) { /******/ resolves.push(installedchunks[chunkid][0]); // 存储将要执行的resolve /******/ } /******/ 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()(); // 执行所有resolve /******/ } /******/ /******/ };
如果多依赖一个呢
./main.js
console.log('main'); settimeout(() => { require(['./str'], (require) => { let number = require('./number'); console.log(number.n); }); }, 1000);
这时只有 ./1.js改变了,差不不大,一样的道理
(window["webpackjsonp"] = window["webpackjsonp"] || []).push([[1],[ /* 0 */, /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "str", function() { return str; }); var str = 10; /***/ }), /* 2 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "n", function() { return n; }); let n = 10; /***/ }) ]]);
5. 提取公共模块
./webpack.config.js
entry: { main: './main', test: './test' }, optimization: { // 提取公共部分为common.js,使劲地提取吧.. splitchunks: { name: 'common', chunks: 'all', minsize: 1 } },
./main.js
import './chunk'; import {n} from './number'; console.log('main', n);
./test.js
import './chunk'; console.log('test');
编译后
./dist/common.js
(window["webpackjsonp"] = window["webpackjsonp"] || []).push([[1],[ /* 0 */, /* 1 */ /***/ (function(module, exports) { console.log('chunk'); /***/ }) ]]);
可以看到 chunk模块(id为1)被共用,被提取出来
再看看 ./dist/test.js
1 /******/ (function(modules) { // webpackbootstrap 2 /******/ // install a jsonp callback for chunk loading 3 /******/ function webpackjsonpcallback(data) { 4 /******/ var chunkids = data[0]; 5 /******/ var moremodules = data[1]; 6 /******/ var executemodules = data[2]; 7 /******/ 8 /******/ // add "moremodules" to the modules object, 9 /******/ // then flag all "chunkids" as loaded and fire callback 10 /******/ var moduleid, chunkid, i = 0, resolves = []; 11 /******/ for(;i < chunkids.length; i++) { 12 /******/ chunkid = chunkids[i]; 13 /******/ if(installedchunks[chunkid]) { 14 /******/ resolves.push(installedchunks[chunkid][0]); 15 /******/ } 16 /******/ installedchunks[chunkid] = 0; 17 /******/ } 18 /******/ for(moduleid in moremodules) { 19 /******/ if(object.prototype.hasownproperty.call(moremodules, moduleid)) { 20 /******/ modules[moduleid] = moremodules[moduleid]; 21 /******/ } 22 /******/ } 23 /******/ if(parentjsonpfunction) parentjsonpfunction(data); 24 /******/ 25 /******/ while(resolves.length) { 26 /******/ resolves.shift()(); 27 /******/ } 28 /******/ 29 /******/ // add entry modules from loaded chunk to deferred list 30 /******/ deferredmodules.push.apply(deferredmodules, executemodules || []); 31 /******/ 32 /******/ // run deferred modules when all chunks ready 33 /******/ return checkdeferredmodules(); 34 /******/ }; 35 /******/ function checkdeferredmodules() { 36 /******/ var result; 37 /******/ for(var i = 0; i < deferredmodules.length; i++) { 38 /******/ var deferredmodule = deferredmodules[i]; 39 /******/ var fulfilled = true; 40 /******/ for(var j = 1; j < deferredmodule.length; j++) { 41 /******/ var depid = deferredmodule[j]; 42 /******/ if(installedchunks[depid] !== 0) fulfilled = false; 43 /******/ } 44 /******/ if(fulfilled) { 45 /******/ deferredmodules.splice(i--, 1); 46 /******/ result = __webpack_require__(__webpack_require__.s = deferredmodule[0]); 47 /******/ } 48 /******/ } 49 /******/ return result; 50 /******/ } 51 /******/ 52 /******/ // the module cache 53 /******/ var installedmodules = {}; 54 /******/ 55 /******/ // object to store loaded and loading chunks 56 /******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched 57 /******/ // promise = chunk loading, 0 = chunk loaded 58 /******/ var installedchunks = { 59 /******/ 2: 0 60 /******/ }; 61 /******/ 62 /******/ var deferredmodules = []; 63 /******/ 64 /******/ // the require function 65 /******/ function __webpack_require__(moduleid) { 66 /******/ 67 /******/ // check if module is in cache 68 /******/ if(installedmodules[moduleid]) { 69 /******/ return installedmodules[moduleid].exports; 70 /******/ } 71 /******/ // create a new module (and put it into the cache) 72 /******/ var module = installedmodules[moduleid] = { 73 /******/ i: moduleid, 74 /******/ l: false, 75 /******/ exports: {} 76 /******/ }; 77 /******/ 78 /******/ // execute the module function 79 /******/ modules[moduleid].call(module.exports, module, module.exports, __webpack_require__); 80 /******/ 81 /******/ // flag the module as loaded 82 /******/ module.l = true; 83 /******/ 84 /******/ // return the exports of the module 85 /******/ return module.exports; 86 /******/ } 87 /******/ 88 /******/ 89 /******/ // expose the modules object (__webpack_modules__) 90 /******/ __webpack_require__.m = modules; 91 /******/ 92 /******/ // expose the module cache 93 /******/ __webpack_require__.c = installedmodules; 94 /******/ 95 /******/ // define getter function for harmony exports 96 /******/ __webpack_require__.d = function(exports, name, getter) { 97 /******/ if(!__webpack_require__.o(exports, name)) { 98 /******/ object.defineproperty(exports, name, { enumerable: true, get: getter }); 99 /******/ } 100 /******/ }; 101 /******/ 102 /******/ // define __esmodule on exports 103 /******/ __webpack_require__.r = function(exports) { 104 /******/ if(typeof symbol !== 'undefined' && symbol.tostringtag) { 105 /******/ object.defineproperty(exports, symbol.tostringtag, { value: 'module' }); 106 /******/ } 107 /******/ object.defineproperty(exports, '__esmodule', { value: true }); 108 /******/ }; 109 /******/ 110 /******/ // create a fake namespace object 111 /******/ // mode & 1: value is a module id, require it 112 /******/ // mode & 2: merge all properties of value into the ns 113 /******/ // mode & 4: return value when already ns object 114 /******/ // mode & 8|1: behave like require 115 /******/ __webpack_require__.t = function(value, mode) { 116 /******/ if(mode & 1) value = __webpack_require__(value); 117 /******/ if(mode & 8) return value; 118 /******/ if((mode & 4) && typeof value === 'object' && value && value.__esmodule) return value; 119 /******/ var ns = object.create(null); 120 /******/ __webpack_require__.r(ns); 121 /******/ object.defineproperty(ns, 'default', { enumerable: true, value: value }); 122 /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); 123 /******/ return ns; 124 /******/ }; 125 /******/ 126 /******/ // getdefaultexport function for compatibility with non-harmony modules 127 /******/ __webpack_require__.n = function(module) { 128 /******/ var getter = module && module.__esmodule ? 129 /******/ function getdefault() { return module['default']; } : 130 /******/ function getmoduleexports() { return module; }; 131 /******/ __webpack_require__.d(getter, 'a', getter); 132 /******/ return getter; 133 /******/ }; 134 /******/ 135 /******/ // object.prototype.hasownproperty.call 136 /******/ __webpack_require__.o = function(object, property) { return object.prototype.hasownproperty.call(object, property); }; 137 /******/ 138 /******/ // __webpack_public_path__ 139 /******/ __webpack_require__.p = "./dist/"; 140 /******/ 141 /******/ var jsonparray = window["webpackjsonp"] = window["webpackjsonp"] || []; 142 /******/ var oldjsonpfunction = jsonparray.push.bind(jsonparray); 143 /******/ jsonparray.push = webpackjsonpcallback; 144 /******/ jsonparray = jsonparray.slice(); 145 /******/ for(var i = 0; i < jsonparray.length; i++) webpackjsonpcallback(jsonparray[i]); 146 /******/ var parentjsonpfunction = oldjsonpfunction; 147 /******/ 148 /******/ 149 /******/ // add entry module to deferred list 150 /******/ deferredmodules.push([3,1]); 151 /******/ // run deferred modules when ready 152 /******/ return checkdeferredmodules(); 153 /******/ }) 154 /************************************************************************/ 155 /******/ ({ 156 157 /***/ 3: 158 /***/ (function(module, __webpack_exports__, __webpack_require__) { 159 160 "use strict"; 161 __webpack_require__.r(__webpack_exports__); 162 /* harmony import */ var _chunk__webpack_imported_module_0__ = __webpack_require__(1); 163 /* harmony import */ var _chunk__webpack_imported_module_0___default = /*#__pure__*/__webpack_require__.n(_chunk__webpack_imported_module_0__); 164 165 166 167 console.log('test'); 168 169 170 171 /***/ }) 172 173 /******/ });
先看150行,初始不再马上加载入口模块,而是先将入口模块和其依赖的公共模块保存起来,再进行处理加载
/******/ // add entry module to deferred list /******/ deferredmodules.push([3,1]); // 这里的3为test模块,1为chunk公共模块 /******/ // run deferred modules when ready /******/ return checkdeferredmodules();
/******/ function checkdeferredmodules() { /******/ var result; // deferredmodules的结构长这样 [[3,1]],对每一项进行处理 /******/ for(var i = 0; i < deferredmodules.length; i++) { /******/ var deferredmodule = deferredmodules[i]; /******/ var fulfilled = true; // 从第二项开始,为依赖的模块 /******/ for(var j = 1; j < deferredmodule.length; j++) { /******/ var depid = deferredmodule[j]; // 依赖的模块未加载 /******/ if(installedchunks[depid] !== 0) fulfilled = false; /******/ } // 已经加载,则清除,并开始加载入口模块,deferredmodule的第一项即为这里的test入口模块 /******/ if(fulfilled) { /******/ deferredmodules.splice(i--, 1); /******/ result = __webpack_require__(__webpack_require__.s = deferredmodule[0]); /******/ } /******/ } /******/ return result; /******/ }
注意到这里也有 webpackjsonpcallback 函数,不过它的参数数组中有三项,第三项 var executemodules = data[2]; 暂时还没用到,先略过
/******/ // add entry modules from loaded chunk to deferred list /******/ deferredmodules.push.apply(deferredmodules, executemodules || []); /******/ /******/ // run deferred modules when all chunks ready /******/ return checkdeferredmodules();
上面这个,主要是为了兼容公共模块和入口模块的兼容顺序,什么意思呢?
假如没有这段代码,那么这样是可行的
<script type="text/javascript" src="./dist/common.js"></script> <script type="text/javascript" src="./dist/main.js"></script>
但common放后面就不行
<script type="text/javascript" src="./dist/main.js"></script> <script type="text/javascript" src="./dist/common.js"></script>
common放在后面会导致初始调用checkdeferredmodules时 公共模块的fulfilled为false,此时将无法加载入口模块
所以需要在webpackjsonpcallback中再判断处理一次
6. 提取runtime运行时模块
上面代码中,./dist/main.js 和 ./dist/test.js 都有很多运行时的代码,我们可以将其提取出来,一并放到 common.js中
./webpack.config.js
optimization: { // 提取runtime代码到common.js文件中 runtimechunk: { name: 'common' }, // 提取公共部分为common.js,使劲地提取吧.. splitchunks: { name: 'common', chunks: 'all', minsize: 1 } },
编译后,看看 ./dist/test.js 干净了许多
(window["webpackjsonp"] = window["webpackjsonp"] || []).push([[2],{ /***/ 3: /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony import */ var _chunk__webpack_imported_module_0__ = __webpack_require__(1); /* harmony import */ var _chunk__webpack_imported_module_0___default = /*#__pure__*/__webpack_require__.n(_chunk__webpack_imported_module_0__); console.log('test'); /***/ }) },[[3,1]]]);
不过,注意到这里push的参数多了第三项 [[3,1]],根据上面的分析,这个1应该就是公共模块了
来看看 ./dist/common.js
1 /******/ (function(modules) { // webpackbootstrap 2 /******/ // install a jsonp callback for chunk loading 3 /******/ function webpackjsonpcallback(data) { 4 /******/ var chunkids = data[0]; 5 /******/ var moremodules = data[1]; 6 /******/ var executemodules = data[2]; 7 /******/ 8 /******/ // add "moremodules" to the modules object, 9 /******/ // then flag all "chunkids" as loaded and fire callback 10 /******/ var moduleid, chunkid, i = 0, resolves = []; 11 /******/ for(;i < chunkids.length; i++) { 12 /******/ chunkid = chunkids[i]; 13 /******/ if(installedchunks[chunkid]) { 14 /******/ resolves.push(installedchunks[chunkid][0]); 15 /******/ } 16 /******/ installedchunks[chunkid] = 0; 17 /******/ } 18 /******/ for(moduleid in moremodules) { 19 /******/ if(object.prototype.hasownproperty.call(moremodules, moduleid)) { 20 /******/ modules[moduleid] = moremodules[moduleid]; 21 /******/ } 22 /******/ } 23 /******/ if(parentjsonpfunction) parentjsonpfunction(data); 24 /******/ 25 /******/ while(resolves.length) { 26 /******/ resolves.shift()(); 27 /******/ } 28 /******/ 29 /******/ // add entry modules from loaded chunk to deferred list 30 /******/ deferredmodules.push.apply(deferredmodules, executemodules || []); 31 /******/ 32 /******/ // run deferred modules when all chunks ready 33 /******/ return checkdeferredmodules(); 34 /******/ }; 35 /******/ function checkdeferredmodules() { 36 /******/ var result; 37 /******/ for(var i = 0; i < deferredmodules.length; i++) { 38 /******/ var deferredmodule = deferredmodules[i]; 39 /******/ var fulfilled = true; 40 /******/ for(var j = 1; j < deferredmodule.length; j++) { 41 /******/ var depid = deferredmodule[j]; 42 /******/ if(installedchunks[depid] !== 0) fulfilled = false; 43 /******/ } 44 /******/ if(fulfilled) { 45 /******/ deferredmodules.splice(i--, 1); 46 /******/ result = __webpack_require__(__webpack_require__.s = deferredmodule[0]); 47 /******/ } 48 /******/ } 49 /******/ return result; 50 /******/ } 51 /******/ 52 /******/ // the module cache 53 /******/ var installedmodules = {}; 54 /******/ 55 /******/ // object to store loaded and loading chunks 56 /******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched 57 /******/ // promise = chunk loading, 0 = chunk loaded 58 /******/ var installedchunks = { 59 /******/ 1: 0 60 /******/ }; 61 /******/ 62 /******/ var defer
相关文章:
-
-
教程所示图片使用的是 github 仓库图片,网速过慢的朋友请移步 "《webpack4 系列教程(十一):字体文件处理》原文地址" 。或者来我的小... [阅读全文]
-
前言 前言 最近在开发小程序,产品经理提了一个需求,要求微信小程序换头像,用户剪裁图片必须是圆形,也在github上看了一些例子,一般剪裁图片用的都... [阅读全文]
-
var date = new Date(); //Sat Sep 29 2018 10:44:43 GMT+0800 (中国标准时间) ]new D... [阅读全文]
-
思路分析:有3种情况 第一种情况,当前页面curPage < 4 第二种情况,当前页面curPage == 4 第三种情况,当前页面curPa... [阅读全文]
-
通过调用函数改变其内容: 输出: 点击出现: ... [阅读全文]
-
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论