webpack打包后代码分析

Symbol.toStringTag

Symbol.toStringTag可以定义Object.prototype.toString.call之后[object Xxx]中Xxx部分的现实值

let obj1 = {};
Object.defineProperty(obj1, Symbol.toStringTag, { value: 'Obj1' });
let obj2 = {};
Object.defineProperty(obj2, Symbol.toStringTag, { value: 'Obj2' });
console.log(Object.prototype.toString.call(obj1));//[object Obj1]
console.log(Object.prototype.toString.call(obj2));//[object Obj2]
复制代码

打包后的代码模块规范都是commonjs

key是模块的id,无论什么模块,模块id都是相对于项目根目录的相对路径

源文件无论采用哪种模块规范,在打包后文件中都是common.js

(() => {
  var modules = ({
    "./src/tilte.js":
      ((module) => {
        module.exports = 'title';
      })
  });
  var cache = {};
  function require(moduleId) {
    var cachedModule = cache[moduleId];
    if (cachedModule !== undefined) {
      return cachedModule.exports;
    }
    var module = cache[moduleId] = {
      exports: {}
    };
    modules[moduleId](module, module.exports, require);
    return module.exports;
  }
  var exports = {};
  (() => {
    let title = require("./src/tilte.js");
    console.log(title);
  })();
})()
复制代码

不同模块规范间互相加载的规范

可以通过代码中模块引入和导出的关键字来确定是什么规范:

COMMONJS:

require exports module

ES MODULE:

export import

commonJs加载es模块:

index.js

let title = require('./title')
console.log(title)
console.log(title.age)
复制代码

title.js:

export default 'title_name';
export const age = 'title_age';
复制代码

在require.r方法中会对模块的exports添加一些特殊属性,例如下面的Symbol.toStringTag和__esModule:

var modules = ({
  "./src/title.js":
  ((module, exports, require) => {
    //ES 模块转换成COMM之后有哪些不一样的地方?
    //exports.__esModule=true 你可以通过它来判断转换前是不是ES 模块
    require.r(exports);
    //ES 模块默认导出export default 会挂载到exports.default上,age会挂载到exports.age上
    require.d(exports, {
      "default": () => (_DEFAULT_EXPORT__),
      "age": () => (age)
    });
    const _DEFAULT_EXPORT__ = ('title_name');
    const age = 'title_age';
  })
});
var cache = {};
function require(moduleId) {
  var cachedModule = cache[moduleId];
  if (cachedModule !== undefined) {
    return cachedModule.exports;
  }
  var module = cache[moduleId] = {
    exports: {}
  };
  modules[moduleId](module, module.exports, require);
  return module.exports;
}
require.d = (exports, definition) => {
  for (var key in definition) {
    if (require.o(definition, key) && !require.o(exports, key)) {
      Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
    }
  }
};
require.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
require.r = (exports) => {
  if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
    Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
  }
  Object.defineProperty(exports, '__esModule', { value: true });
};
var exports = {};
let title = require("./src/title.js");
console.log(title);
console.log(title.age);
复制代码

可以理解为commonjs将es模块转换成了commonjs模块

commonjs esmodule 导出值的区别

commonjs值导出

esmodule引用导出

var modules = {
  './src/title.js': (module, exports, require) => {
    //一旦webpack检测到你的代码里有export 和import关键字,它就认为这是一个ES Module
    require.r(exports);
    require.d(exports, {
      default: () => DEFAULT_EXPORT,
      age: () => age
    });
    var DEFAULT_EXPORT = 'title_name';
    var age = 'title_age';
    setTimeout(() => {
      age = 'new'
    }, 1000);
  },
  "./src/common.js":
    ((module, exports, require) => {
      var age = { x: 1 }
      exports.age = age;
      setTimeout(() => {
        //age = { x: 2 }
        age.x = 10;
      }, 1000);
    })
}

let title = require("./src/common.js");
setTimeout(() => {
  console.log(title.age);
}, 3000);

let title = require("./src/title.js");
setTimeout(() => {
  console.log(title.age);
}, 3000);
复制代码

es模块加载commonjs模块:

index.js:

import title from './title'
console.log(title.name)
console.log(title.age)
复制代码

title.js:

module.exports = {
	name: 'aaa',
  age: 12
}
复制代码

打包后代码:

var modules = ({
  "./src/title.js":
  ((module, exports, require) => {
    exports.name = 'title_name';
    exports.age = 'title_age';
  })
});
var cache = {};
function require(moduleId) {
  var cachedModule = cache[moduleId];
  if (cachedModule !== undefined) {
    return cachedModule.exports;
  }
  var module = cache[moduleId] = {
    exports: {}
  };
  modules[moduleId](module, module.exports, require);
  return module.exports;
}
require.n = (module) => {
	var getter = module && module.__esModule ?
    () => module['default'] :
  	() => module
  require.d(getter, {a: getter})
  return getter
};
require.d = (exports, definition) => {
  for (var key in definition) {
    if (require.o(definition, key) && !require.o(exports, key)) {
      Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
    }
  }
};
require.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
require.r = (exports) => {
  if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
    Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
  }
  Object.defineProperty(exports, '__esModule', { value: true });
};
var exports = {};
(() => {
  "use strict;"
  require.r(exports);
  // 这里require的模块,不知道是esmodule,还是commjs
  // 所以要想取得默认导出,要加判断,这个判断就在require.n里面
  var _title_0__ = require("./src/title.js");
  var _title_0__default = /*#__PURE__*/require.n(_title_0__);
  console.log(_title_0__default().name);
  console.log(_title_0__default().age);
})();
复制代码

require.d在这里暂时没有用处

异步加载

index.js:

const load = document.getElementById('load');
load.addEventListener('click', () => {
    import(/* webpackChunkName: 'title' */'./title').then(result => {
        console.log(result.default);
    });
});
复制代码

title.js:

module.exports = 'title';
复制代码

index.js被转换成:

const load = document.getElementById('load');
load.addEventListener('click', () => {
  //懒加载或者说动态加载title这个代码块,返回一个promise,然后再加载此模块,得到模块exports,然后打印就可以了
  //代码的主是一块代码,每个代码块里面会有若干个模块
  //通过 require.e("title")动态加载title代码块,通过 jsonp加载title.main.js文件,取得对应的代码块,
  //然后把代码块里的模块定义合并到当前文件的modules里
  //然后通过require加载"./src/title.js"模块,得到返回值,不管原来是comonjs还是es,都会转成es
  require.e("title").then(() => require("./src/title.js")).then(result => {
    console.log(result);
  });
});
复制代码

简化后的代码:

//模块定义空的

var modules = ({});
var cache = {};
function require(moduleId) {
  var cachedModule = cache[moduleId];
  if (cachedModule !== undefined) {
    return cachedModule.exports;
  }
  var module = cache[moduleId] = {
    exports: {}
  };
  modules[moduleId](module, module.exports, require);
  return module.exports;
}
//chunkId title ,promises=[]
//已经安装好的或者说加载好的代码块
//key代码块的名字,入口默认就是main
//值0表示已经就绪 加载完成
var installedChunks = {
  main: 0,
  //title: [resolve, reject,promise]
  //title: 0
}
require.l = (url) => {
  var script = document.createElement('script');
  script.src = url;
  document.head.appendChild(script);
}
require.e = (chunkId) => {
  let installedChunkData;
  let promise = new Promise((resolve, reject) => {
    installedChunkData = installedChunks[chunkId] = [resolve, reject]
  });
  installedChunkData[2] = promise
  var url = chunkId + '.main.js'
  require.l(url);
  return promise;
}
var webpackJsonpCallback = (data) => {
  let [chunkIds, moreModules] = data;
  //把返回的模块定义合并到当前的模块定义对象modules里
  for (let moduleId in moreModules) {
    modules[moduleId] = moreModules[moduleId];
  }
  for (let i = 0; i < chunkIds.length; i++) {
    let chunkId = chunkIds[i];
    let resolve = installedChunks[chunkId][0];
    installedChunks[chunkId] = 0;
    resolve();//调用promise的resolve方法让promise成功
  }
}
var chunkLoadingGlobal = window["webpackChunk_2_bundle"] = [];
chunkLoadingGlobal.push = webpackJsonpCallback;

const load = document.getElementById('load');
load.addEventListener('click', () => {
  //懒加载或者说动态加载title这个代码块,返回一个promise,然后再加载此模块,得到模块exports,然后打印就可以了
  //代码的主是一块代码,每个代码块里面会有若干个模块
  //通过 require.e("title")动态加载title代码块,通过 jsonp加载title.main.js文件,取得对应的代码块,
  //然后把代码块里的模块定义合并到当前文件的modules里
  //然后通过require加载"./src/title.js"模块,得到返回值,不管原来是comonjs还是es,都会转成es
  require.e("title").then(() => require("./src/title.js")).then(result => {
    console.log(result);
  });
});
复制代码

打包后title被单独打包了出来

执行过程:

1、按钮点击时,执行require.e(‘title’),这个方法会给这个模块创建一个promise,存到全局变量installedChunks中,存储结构为kv键值对:

title: [resolve, reject, promise]

key是模块的名称,值是这个模块promise相关的一些方法或对象

2、然后通过require.l方法加载,这个方法就是向页面插入script,script执行时,会向window[“webpackChunk_2_bundle”]中添加这个异步模块的相关信息,window[“webpackChunk_2_bundle”]是一个全局数组,它的push方法被改写过

var chunkLoadingGlobal = window["webpackChunk_2_bundle"] = [];
chunkLoadingGlobal.push = webpackJsonpCallback;
复制代码

因此到此会继续执行webpackJsonpCallback方法

3、在webpackJsonpCallback方法中,是可以拿到模块的名称和具体代码的,然后通过这个名称可以找到这个模块在installedChunks中对应的promise相关的方法,执行该模块的resolve方法,并将其在installedChunks中的标志改为0,代表已经加载过

4、接下来,就会执行上述promise订阅的方法:

() => require("./src/title.js")
复制代码

5、通过require方法中modules[moduleId](module, module.exports, require)的执行,模块内代码开始执行,exports将作为resolve的参数,title就可以console打印出来了

完整代码:

(() => {
  var modules = ({});
  var cache = {};
  function require(moduleId) {
    var cachedModule = cache[moduleId];
    if (cachedModule !== undefined) {
      return cachedModule.exports;
    }
    var module = cache[moduleId] = {
      exports: {}
    };
    modules[moduleId](module, module.exports, require);
    return module.exports;
  }
  require.m = modules;
  (() => {
    var getProto = Object.getPrototypeOf ? (obj) => (Object.getPrototypeOf(obj)) : (obj) => (obj.__proto__);
    var leafPrototypes;
    require.t = function (value, mode) {
      if (mode & 1) value = this(value);
      if (mode & 8) return value;
      if (typeof value === 'object' && value) {
        if ((mode & 4) && value.__esModule) return value;
        if ((mode & 16) && typeof value.then === 'function') return value;
      }
      var ns = Object.create(null);
      require.r(ns);
      var def = {};
      leafPrototypes = leafPrototypes || [null, getProto({}), getProto([]), getProto(getProto)];
      for (var current = mode & 2 && value; typeof current == 'object' && !~leafPrototypes.indexOf(current); current = getProto(current)) {
        Object.getOwnPropertyNames(current).forEach((key) => (def[key] = () => (value[key])));
      }
      def['default'] = () => (value);
      require.d(ns, def);
      return ns;
    };
  })();
  (() => {
    require.d = (exports, definition) => {
      for (var key in definition) {
        if (require.o(definition, key) && !require.o(exports, key)) {
          Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
        }
      }
    };
  })();
  (() => {
    require.f = {};
    require.e = (chunkId) => {
      return Promise.all(Object.keys(require.f).reduce((promises, key) => {
        require.f[key](chunkId, promises);
        return promises;
      }, []));
    };
  })();
  (() => {
    require.u = (chunkId) => {
      return "" + chunkId + ".main.js";
    };
  })();
  (() => {
    require.g = (function () {
      if (typeof globalThis === 'object') return globalThis;
      try {
        return this || new Function('return this')();
      } catch (e) {
        if (typeof window === 'object') return window;
      }
    })();
  })();
  (() => {
    require.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
  })();
  (() => {
    var inProgress = {};
    var dataWebpackPrefix = "2.bundle:";
    require.l = (url, done, key, chunkId) => {
      if (inProgress[url]) { inProgress[url].push(done); return; }
      var script, needAttach;
      if (key !== undefined) {
        var scripts = document.getElementsByTagName("script");
        for (var i = 0; i < scripts.length; i++) {
          var s = scripts[i];
          if (s.getAttribute("src") == url || s.getAttribute("data-webpack") == dataWebpackPrefix + key) { script = s; break; }
        }
      }
      if (!script) {
        needAttach = true;
        script = document.createElement('script');
        script.charset = 'utf-8';
        script.timeout = 120;
        if (require.nc) {
          script.setAttribute("nonce", require.nc);
        }
        script.setAttribute("data-webpack", dataWebpackPrefix + key);
        script.src = url;
      }
      inProgress[url] = [done];
      var onScriptComplete = (prev, event) => {
        script.onerror = script.onload = null;
        clearTimeout(timeout);
        var doneFns = inProgress[url];
        delete inProgress[url];
        script.parentNode && script.parentNode.removeChild(script);
        doneFns && doneFns.forEach((fn) => (fn(event)));
        if (prev) return prev(event);
      }
        ;
      var timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000);
      script.onerror = onScriptComplete.bind(null, script.onerror);
      script.onload = onScriptComplete.bind(null, script.onload);
      needAttach && document.head.appendChild(script);
    };
  })();
  (() => {
    require.r = (exports) => {
      if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
      }
      Object.defineProperty(exports, '__esModule', { value: true });
    };
  })();
  (() => {
    var scriptUrl;
    if (require.g.importScripts) scriptUrl = require.g.location + "";
    var document = require.g.document;
    if (!scriptUrl && document) {
      if (document.currentScript)
        scriptUrl = document.currentScript.src
      if (!scriptUrl) {
        var scripts = document.getElementsByTagName("script");
        if (scripts.length) scriptUrl = scripts[scripts.length - 1].src
      }
    }
    if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");
    scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/?.*$/, "").replace(//[^/]+$/, "/");
    require.p = scriptUrl;
  })();
  (() => {
    var installedChunks = {
      "main": 0
    };
    require.f.j = (chunkId, promises) => {
      var installedChunkData = require.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;
      if (installedChunkData !== 0) {
        if (installedChunkData) {
          promises.push(installedChunkData[2]);
        } else {
          if (true) {
            var promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject]));
            promises.push(installedChunkData[2] = promise);
            var url = require.p + require.u(chunkId);
            var error = new Error();
            var loadingEnded = (event) => {
              if (require.o(installedChunks, chunkId)) {
                installedChunkData = installedChunks[chunkId];
                if (installedChunkData !== 0) installedChunks[chunkId] = undefined;
                if (installedChunkData) {
                  var errorType = event && (event.type === 'load' ? 'missing' : event.type);
                  var realSrc = event && event.target && event.target.src;
                  error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
                  error.name = 'ChunkLoadError';
                  error.type = errorType;
                  error.request = realSrc;
                  installedChunkData[1](error);
                }
              }
            };
            require.l(url, loadingEnded, "chunk-" + chunkId, chunkId);
          } else installedChunks[chunkId] = 0;
        }
      }
    };
    var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
      var [chunkIds, moreModules, runtime] = data;
      var moduleId, chunkId, i = 0;
      if (chunkIds.some((id) => (installedChunks[id] !== 0))) {
        for (moduleId in moreModules) {
          if (require.o(moreModules, moduleId)) {
            require.m[moduleId] = moreModules[moduleId];
          }
        }
        if (runtime) var result = runtime(require);
      }
      if (parentChunkLoadingFunction) parentChunkLoadingFunction(data);
      for (; i < chunkIds.length; i++) {
        chunkId = chunkIds[i];
        if (require.o(installedChunks, chunkId) && installedChunks[chunkId]) {
          installedChunks[chunkId][0]();
        }
        installedChunks[chunkIds[i]] = 0;
      }
    }
    var chunkLoadingGlobal = self["webpackChunk_2_bundle"] = self["webpackChunk_2_bundle"] || [];
    chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
    chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
  })();
  const load = document.getElementById('load');
  load.addEventListener('click', () => {
    require.e("title").then(require.t.bind(require, "./src/title.js", 23)).then(result => {
      console.log(result.default);
    });
  });
})()
  ;
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享