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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容