乐趣区

关于前端:前端工程化8Webpack5打包文件核心源码解读

Webpack 打包文件外围源码解读

请联合源码浏览本页内容

本文次要剖析了 Webpack 打包后的源码;在 Webpack 打包文件出问题的时候,咱们能够依据根本的程序结构来进行调试定位。例如:在执行 Webpack 构建后,将生成的文件放到 dist 目录,咱们剖析的即是 dist 目录的 built.js。

1. 示例我的项目的 Webpack 相干配置:

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  devtool: 'none',
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'built.js',
    path: path.resolve('dist')
  },
  plugins: [
    new HtmlWebpackPlugin({template: './src/index.html'})
  ]
}

2. 主体构造

  1. 打包后的文件就是一个函数自调用,以后函数调用时传入一个对象
  2. 这个对象咱们为了不便将之称为是模块定义,它就是一个键值对
  3. 这个键名就是以后被加载模块的文件名与某个目录的拼接()
  4. 这个键值就是一个函数,和 node.js 里的模块加载有一些相似,会将被加载模块中的内容包裹于一个函数中
  5. 这个函数在未来某个工夫点上会被调用,同时会接管到肯定的参数,利用这些参数就能够实现模块的加载操作
  6. 针对于上述的代码就相当于是将模块定义:{“ 模块 id”:(function(){})} 传递给了 modules
(function(modules){//....})({"模块 id":(function(){})
})
  1. 示例:
(function (modules) { // webpackBootstrap
    //....
})({
    "./src/index.js":
      (function (module, exports) {console.log('index.js 内容')
        module.exports = '入口文件导出内容'
    })
});

3. 构建流程

webpack 构建流程以及如何产生的打包文件这样的代码构造,参考后续相干文章。

1、启动 webpack
2、启动 webpack-cli
3、创立编译对象 compiler
4、实例化编译对象 compiler,预埋外围钩子
5、执行办法 compiler.run(),启动编译
6、执行办法 compiler.compile(),实现编译,输入文件

4. 外围函数性能(待补充)

  // 缓存模块,如果曾经加载过的模块就间接应用缓存
  var installedModules = {};

  // 上面的这个办法就是 webpack 当中自定义的,它的核心作用就是返回模块的 exports 
  function __webpack_require__(moduleId) {}

  // 将模块定义保留一份,通过 m 属性挂载到自定义的办法身上
  __webpack_require__.m = modules;

  // 将所有模块的缓存数据保留一份
  __webpack_require__.c = installedModules;

  // 判断被传入的对象 obj 身上是否具备指定的属性 **** , 如果有则返回 true 
  __webpack_require__.o = function (object, property) {return Object.prototype.hasOwnProperty.call(object, property); };
  
  // 如果以后 exports 身上不具备 name 属性,则条件成立,则给 exports 增加一个 name 属性并复工一个拜访器 getter
  __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
  // 判断是否 ES Modules,而后增加标记
  __webpack_require__.r = function (exports) {
    // 上面的条件如果成立就阐明是一个  esModule 
    if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
      // 往 exports 身上增加一个属性,值是 Module;// 该值能够通过 Object.prototype.toString.call(exports) 拜访
      Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module'});
    }
    // 如果条件不成立,咱们也间接在 exports 对象的身上增加一个 __esModule 属性,它的值就是 true 
    Object.defineProperty(exports, '__esModule', { value: true});
  };

  // 如果 module 是 ES Modules 模块,返回 default
  // 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_public_path__
  __webpack_require__.p = "";

  // __webpack_require__.s 存储模块 id 值
  return __webpack_require__(__webpack_require__.s = "./src/index.js");

__webpack_require__.r 办法增加的标记:

__webpack_require__.d 给属性 age 增加 getter 办法(返回 age):

5. 外围函数性能相干的一些知识点

1. call 办法:

  • 执行函数
  • call(obj,pra1,pra2,…)

2. Object.defineProperty 的作用:

  • enumerable 参数,get 参数
  • 参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

3. Symbol.toStringTag

  • Symbol.toStringTag 是一个内置 symbol,它通常作为对象的属性键应用
  • 该对象对应的属性值应该为字符串类型,这个字符串用来示意该对象的自定义类型标签
  • 通常只有内置的 Object.prototype.toString() 办法会去读取这个标签并把它蕴含在本人的返回值里。
  • 参考:https://segmentfault.com/a/1190000021043630

4. 按位与的性能,可实现自定义条件的判断

    // & 与运算,某一位两者雷同返回 1,不同返回 0
    //(1)1、2、4、8 可了解为 4 个固定开关,mode 设置对应的值能够判断是否关上某个开关://(2)0001、0010、0100、1000 为 4 个二进制开关与 mode 进行“& 与运算”能够判断是否对应的开关类型。

6. 性能函数应用场景

1. CommonJS 模块打包 ,Webpack 默认应用 CommonJS 标准解决打包内容:

  • 如果模块是应用 CommonJS 形式导入,Webpack 不须要额定解决。
let obj = require('./login.js')
  • 如果模块是应用 CommonJS 形式导出,Webpack 不须要额定解决。
module.exports = '拉勾教育'

2. ES Modules 模块打包

  • 如果模块是应用 ES Modules 形式导入,那么 Webpack 会进行解决。
import name, {age} from './login.js'
  • 如果模块是应用 ES Modules 形式导出,那么 Webpack 会进行解决。
export default '拉勾教育'
export const age = 100
  • 导入导出都用 ES Modules 的示例:
(function (modules) { // webpackBootstrap
  //...

  return __webpack_require__(__webpack_require__.s = "./src/index.js");
})({// modules 作为参数:{KEY: VALUE}
    "./src/index.js":
      (function (module, __webpack_exports__, __webpack_require__) {
        "use strict";
        __webpack_require__.r(__webpack_exports__); // 标记为 ES Modules
        var _login_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/login.js"); // 加载 login.js 这个模块
        console.log('index.js 内容加载了')
        console.log(_login_js__WEBPACK_IMPORTED_MODULE_0__["default"], '---->', _login_js__WEBPACK_IMPORTED_MODULE_0__["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"] = ('拉勾教育');
        const age = 100
      })
  });

7. 按需加载模块执行流程(重要)

加载示例代码:

// idnex.js
let oBtn = document.getElementById('btn')

oBtn.addEventListener('click', function () {import(/*webpackChunkName: "login"*/'./login.js').then((login) => {console.log(login)
  })
})

console.log('index.js 执行了')
// login.js
module.exports = "懒加载导出内容"

加载流程梳理:

  1. 触发了模块的异步加载后,首先执行 __webpack_require__.e 办法
  2. __webpack_require__.e 办法加载 js 文件,返回值为:promise
  • e 办法性能 1:判断是否加载过模块

    1. 曾经加载 => 应用本地缓存
    2. 没加载过 => 继续执行加载
  • e 办法性能 2:采纳 jsonp 的形式加载 js 文件模块

    jsonp 是如何加载 js 模块的?

    1. 在 html 中创立一个 script 标签,script 标签的 src 指向 js 文件
    2. js 文件中定义了 callback 办法,js 文件加载实现后执行 callback 办法
  • 全局变量:变量赋值,定义一些别名不便模块加载后全局调用

    1. webpackJsonp = jsonpArray // 非代码,表明相等而已
    2. webpackJsonp.push = jsonpArray.push = webpackJsonpCallback
  1. 加载 js 文时执行 callback 办法:webpackJsonp.push(这里的 jsonp 和 promise 是怎么配合的?)

    1. webpackJsonp.push 曾经提前定义好,且在模块中理论调用的是 webpackJsonpCallback
    2. webpackJsonpCallback 的作用是:合并异步加载的模块到 modules 外面,扭转 promise 状态;
  2. 而后执行 __webpack_require__.t 办法加载模块并返回模块的内容

    • t 办法性能:加载模块内容,在依据模块导入形式(CommonJS 或 ES Modules)进行解决,而后再导出。
    • 其中波及到按位与的功能设计:
    • 阐明:& 与运算,二进制数某一位两者雷同返回 1,不同返回 0
    • 例如:0001、0010、0100、1000 为 4 个二进制开关 1 /2/4/8,7 示意关上了 1,2,4 的开关;8 敞开的意思
  3. 最初拿到模块导出的内容,应用其数据执行后续代码

存在的问题,应用 Single-Spa 动静加载不同子系统的同名文件时,模块名 id 会呈现抵触

Webpack5 打包源码示例

特地鸣谢:拉勾教育前端高薪训练营

退出移动版