Webpack原理

一、 研究webpack原理,我们从最近单的hello webpack 开始, 以下是代码实现

// webpack.config.js
const path = require("path");
module.exports = {
  entry: "./src/index.js",
  mode: "development",
  output: {
    path: path.resolve(__dirname, "./dist"),
    filename: "main.js"
  },
};

// src/index.js
console.log("hello webpack");

执行webpack输出打包目录dist

/******/ (function(modules) { // webpackBootstrap
/******/    // The module cache
/******/    var installedModules = {};
/******/
/******/    // 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,
/******/            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;
/******/    }
/******/
/******/
/******/    // expose the modules object (__webpack_modules__)
/******/    __webpack_require__.m = modules;
/******/
/******/    // expose the module cache
/******/    __webpack_require__.c = installedModules;
/******/
/******/    // 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 });
/******/    };
/******/
/******/    // 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;
/******/    };
/******/
/******/    // Object.prototype.hasOwnProperty.call
/******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/    // __webpack_public_path__
/******/    __webpack_require__.p = "";
/******/
/******/
/******/    // Load entry module and return exports
/******/    return __webpack_require__(__webpack_require__.s = "./src/index.js");
/******/ })
/************************************************************************/
/******/ ({

/***/ "./src/index.js":
/*!**********************!*\
  !*** ./src/index.js ***!
  \**********************/
/*! no static exports found */
/***/ (function(module, exports) {

eval("console.log(\"hello webpack\");\n\n\n//# sourceURL=webpack:///./src/index.js?");

/***/ })

/******/ });

以上打包输出内容是可以直接在浏览器执行的


屏幕快照 2022-02-16 上午11.34.51.png

打包输出的dist文件是一个webpackBootstrap的自启动函数,模块以文件位置"./src/index.js"作为key, 编译后的源码(function(module, exports) {eval("console.log(\"hello webpack\");\n\n\n//# sourceURL=webpack:///./src/index.js?");/***/ })作为value的对象为参数;另外用到的export require等webpack会替换成自己的__webpack_require__,简单说就是对路径的处理,便于浏览器能正常运行

二、webpack实现

看到这里来分一下webpack做了哪些事情:

  1. 接收一份webpack.config.js的配置
  2. 分析出入口模块位置
    • 读取入口模块内容,分析内容
    • 哪些是依赖
    • 哪些是源码 - es6, jsx 需要编译处理,最终浏览器能够执行
    • 分析其他模块
  3. 拿到对象数据结构
    • 模块路径
    • 处理好的内容
  4. 创建bundle.js
    • 启动器函数,来补充代码⾥有可能出现的module exports
      require,让浏览器能够顺利的执⾏
程序目录
.
├── dist                    # node脚本
│   └── main.js             # 打包输出
├── lib                     # webpack源码目录
│   └── webpack.js          # 源码
├── src                     # 项目代码
│   ├── a.js
│   ├── b.js 
│   └── index.js            # 程序入口 
├── bundle.js               # webpack打包入口
└── webpack.config.js       # webpack配置

src/index.js

//为什么要分析依赖,因为我们需要拿到路径
import { add } from "./a.js";
console.log(add(1, 2));
console.log("hello webpack");
console.log("test");

src/a.js

export const add = function(a, b) {
  return a + b;
};

export const minus = function(a, b) {
  return a - b;
};

bundle.js

const fs = require("fs");
const path = require("path");
const parser = require("@babel/parser");
const { transformFromAst } = require("@babel/core");
const tarverse = require("@babel/traverse").default;
module.exports = class webpack {
  constructor(options) {
    //入口信息
    let { entry, output } = options;
    //出口信息
    this.entry = entry;
    this.output = output;
    this.module = [];
  }
  run() {
    //开始分析入口模块的内容

    const info = this.parse(this.entry);

    this.module.push(info);

    for (let i = 0; i < this.module.length; i++) {
      const item = this.module[i];
      const { dependencies } = item;
      if (dependencies) {
        for (let j in dependencies) {
          this.module.push(this.parse(dependencies[j]));
        }
      }
    }
    // console.log(this.module);
    // 数据结构转换
    const obj = {};
    this.module.forEach(item => {
      obj[item.entryFile] = {
        dependencies: item.dependencies,
        code: item.code
      };
    });

    this.file(obj);
  }

  parse(entryFile) {
    const content = fs.readFileSync(entryFile, "utf-8");
    //使用parser分析内容,返回ast 抽象语法树
    const ast = parser.parse(content, {
      sourceType: "module"
    });
    //提取依赖路径信息
    const dependencies = {};
    tarverse(ast, {
      ImportDeclaration({ node }) {
        //node.source.value 这个路径是相对于入口的相对路径
        const newPathName =
          "./" + path.join(path.dirname(entryFile), node.source.value);

        dependencies[node.source.value] = newPathName;
      }
    });

    //代码编译
    const { code } = transformFromAst(ast, null, {
      presets: ["@babel/preset-env"]
    });

    //信息汇总,返回
    return {
      entryFile,
      dependencies,
      code
    };
  }
  file(code) {
    //生成bundle.js =》dist/main.js
    const filePath = path.join(this.output.path, this.output.filename);
    const newCode = JSON.stringify(code);
    const bundle = `(function(graph){
        function require(module){
            function localRequire(relativePath){
              return require(graph[module].dependencies[relativePath])
            }
            var exports = {};
            (function(require,exports,code){
                eval(code)
            })(localRequire,exports,graph[module].code)
            return exports;
        }
        require('${this.entry}')
    })(${newCode})`;

    //写文件操作
    fs.writeFileSync(filePath, bundle, "utf-8");
  }
};

dist/main.js

(function(graph){
        function require(module){
            function localRequire(relativePath){
              return require(graph[module].dependencies[relativePath])
            }
            var exports = {};
            (function(require,exports,code){
                eval(code)
            })(localRequire,exports,graph[module].code)
            return exports;
        }
        require('./src/index.js')
    })({"./src/index.js":{"dependencies":{"./a.js":"./src/a.js"},"code":"\"use strict\";\n\nvar _a = require(\"./a.js\");\n\n//为什么要分析依赖,因为我们需要拿到路径\nconsole.log((0, _a.add)(1, 2));\nconsole.log(\"hello webpack\");\nconsole.log(\"test\");"},"./src/a.js":{"dependencies":{},"code":"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n  value: true\n});\nexports.minus = exports.add = void 0;\n\nvar add = function add(a, b) {\n  return a + b;\n};\n\nexports.add = add;\n\nvar minus = function minus(a, b) {\n  return a - b;\n};\n\nexports.minus = minus;"}})

最终浏览器是可以执行的


屏幕快照 2022-02-16 下午1.24.45.png

你可能感兴趣的:(Webpack原理)