打包后的bundle.js源码分析

ctrl+k ctrl+0 缩进

  • 打包后的函数是个函数自调用。当前函数调用时传入一个对象
  • 这个对象为了方便称之为模块定义,是个键值对
  • 对象键是入口文件路径拼接
  • 对象键值是个函数,类似于commonjs模块加载,包裹模块被加载的内容
  • 这个函数在将来某时间下会被调用,函数可以接收参数,利用参数可以实现模块的加载
  • 将对象实参传给modules形参

# bundle.js (function(modules){ … // 缓存被加载过的模块 var installedModules = {}; // 核心函数,webpack中自定义的.作用是返回传入的模块的导出内容module.exports // 这个moduleId是iife传入参数对象的key function webpack_require(moduleId){ if(installedModules[moduleId]) { return installedModules[moduleId].exports; } var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; // 模块内的this指向 module.exports modules[moduleId].call(module.exports, module, module.exports, webpack_require); module.l = true; return module.exports } … return webpack_require(webpack_require.s = “./src/index.js”); })({ “./src/index.js”: (function(module, exports) { console.log(“index.js content”); }) })
一些属性方法
// 将模块定义保存一份,通过m属性挂载到自定义方法上 webpack_require.m = modules // 挂载缓存的 webpack_require.c = installedModules // 判断被传入的对象object 是否具有指定的属性,有返回true webpack_require.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; // 给对象人为添加属性,外部可以通过getter访问这个属性 webpack_require.d = function(exports, name, getter) { if(!webpack_require.o(exports, name)) { Object.defineProperty(exports, name, { enumerable: true, get: getter }); } }; // 给 exports 强行加标记 webpack_require.r = function(exports) { //如果成立说明是个 ESModule if(typeof Symbol !== ‘undefined’ && Symbol.toStringTag) { // 向对象exports添加一个唯一字符串Symbol.toStringTag标记,值人为定义成Module // Object.prototype.toString.call(exports) === Module Object.defineProperty(exports, Symbol.toStringTag, { value: ‘Module’ }); } // 向对象exports上添加一个__esModule属性,值是true Object.defineProperty(exports, ‘esModule’, { value: true }) }; // 提供一个getter方法,返回一个getter方法,如果是ESM返回默认值,否则返回模块 webpackrequire.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.config中的publicPath,根目录,默认”/“ // index.html不自动生成时,找不到index内引入的内容,添加’dist/‘ webpack_require.p = “” // 标记入口文件 webpack_require.s = “./src/index.js” // 调用t时,我们会拿到被加载模块得内容value // 对于value可能直接返回,或者处理之后返回 // 接收两个参数,一个value表示模块id,第二个值 mode 是一个十进制数 // &1 位运算获取 模块id的返回值 // 8,4,ns,2是对模块返回值进行的额外处理,然后返回使用 // 1,8成立相当于加载了CJS规范直接返回value // 1,4成立相当于加载了ESM直接返回value // 如果上述都不成立,继续处理value,将其挂载到ns的default属性上 // 如果value是非基本类型,例如{name:xx,age:10},则可以通过ns.name,ns.age触发getter方法获取 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); webpackrequire.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; };
备注:t方法中的 bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

  • 如果CommonJS导出,CommonJS导入,这些方法不会调用# login.js // 01 使用commonjs module.exports = “你好js” # index.js // 01 require引入commonjs const name = require(‘./login’) console.log(“name”);
  • 如果ESM导出,CommonJS引入,d(),r(),[‘default’]有使用# login.js // 02 使用ESM export default ‘你好ESM’ export const age =18 # index.js // 02 require引入ESM const obj = require(‘./login’) console.log(obj.default,”———“,obj.age);
  • 如果CommonJS导出,ESM引入,r(),n()有使用 (import会走r())# login.js // 03 使用commonjs module.exports = “你好Commonjs” # index.js // 03 ESM引入commonjs import name from ‘./login’ console.log(name);
  • 如果ESM导出,ESM引入# login.js // 04 使用ESM export default ‘你好ESM’ export const age =18 # index.js // 04 ESM引入ESM import defalutname,{age} from ‘./login’ console.log(defalutname,”———“,age);
  • 如果import()动态导入.t() e()# login.js module.exports = “mc” # index.js let btn = document.getElementById(“btn”) btn.addEventListener(‘click’,function(){ import(/webpackChunkName: “login1”/‘./login.js’).then((login)=>{ console.log(login) }) })添加webpackChunkName会生成login1.bundle.js文件,否则是0.bundle.js,查看bundle.js源码

点击时增加script标签,懒加载的核心原理是jsonp,t方法可以根据文件内容不同进行处理,取决于传入数值(8,7,6,3,2,1)
image.png

有个小问题插播,为什么age未定义先返回了?

“./src/login.js” : (function(module, webpack_exports, webpack_require) { “use strict”; webpack_require.r(webpack_exports); webpack_require.d(webpack_exports, “age”, function() { return age; }); webpack_exports[“default”] = (‘你好ESM’); const age =18 })
看下面代码
let obj={} Object.defineProperty(obj,”age”,{ get:()=>{ console.log(1) return age } }) const age = 18 console.log(obj) // 输出看图
image.png
解答:输出看图,get方法虽然返回了一个未定义的常量age,但是只要在使用Obj.age触发get方法之前,把get内返回的age定义了就可以取到。因此,虽然const age在后面定义,但是在这个定义之后再触发get方法,都是可行的,方法只在调用时候才会去确定是否定义了。