Интервьюер: вебпака впринципе не будет?

Webpack
Интервьюер: вебпака впринципе не будет?

введение

Некоторое время назад я поставилwebpackИсходный код, вероятно, читается один раз,webpackприбыть4.xПосле версии его исходный код был относительно большим, а различные сценарии разработки были сильно абстрагированы, а стоимость чтения становилась все дороже и дороже.

Чрезмерный анализ исходного кода не всем полезен. В этой статье в основном хотят проанализироватьwebpackСоздайте процесс и реализуйте простойwebpackсделать всех правымиwebpackиметь общее представление о внутренних принципах. (Гарантированно поймешь, если не понял, то ударил меня 🙈)

Анализ процесса сборки webpack

Прежде всего, само собой разумеется, картинка выше~

webpackТекущий процесс является последовательным процессом, и следующие процессы будут выполняться последовательно от начала до конца:ShellПараметры считываются и объединяются в операторе, а параметры, требуемые средой выполнения, такие как подключаемые модули, которые будут использоваться, и подключаемые модули конфигурации, инициализируются; после завершения инициализации он будет вызыватьсяCompilerизrunПо-настоящему начатьwebpackСкомпилируйте процесс сборки,webpackПроцесс сборки включает в себяcompile,make,build,seal,emitЭтапы, которые выполняются для завершения процесса сборки.

инициализация

запуск опций входа

из файла конфигурации иShellПараметры считываются и объединяются в операторе для получения окончательного параметра.

запустить создание экземпляра

compiler: Инициализировать с параметрами, полученными на предыдущем шаге.Compilerобъекта, загружает все настроенные плагины, выполняетrunметод начинает выполнять компиляцию

Скомпилировать и построить

entry 确定入口

Согласно конфигурацииentryНайти все входные файлы

make 编译模块

Начиная с файла ввода, вызовите все настроенныеLoaderПереведите модуль, найдите модули, от которых зависит модуль, а затем повторите этот шаг, пока все файлы, от которых зависит запись, не будут обработаны на этом шаге.

build module 完成模块编译

После вышеуказанного шага используйтеLoaderПосле перевода всех модулей каждый модуль был окончательным переводом содержания, и зависимостей между ними

seal 输出资源

В соответствии с зависимостями между записью и модулем он собирается в модуль, содержащий несколько модулей один за другим.Chunk, затем поставьте каждыйChunkПреобразованный в отдельный файл и добавленный в список вывода, этот шаг является последней возможностью изменить содержимое вывода.

emit 输出完成

После определения выходного содержимого определите выходной путь и имя файла в соответствии с конфигурацией и запишите содержимое файла в файловую систему.

Проанализировав процесс построения, реализуем простуюwebpackХорошо ~

Получите простой веб-пакет

Готов к работе

Структура каталогов

Давайте сначала инициализируем проект, структура выглядит следующим образом:

|-- forestpack
    |-- dist
    |   |-- bundle.js
    |   |-- index.html
    |-- lib
    |   |-- compiler.js
    |   |-- index.js
    |   |-- parser.js
    |   |-- test.js
    |-- src
    |   |-- greeting.js
    |   |-- index.js
    |-- forstpack.config.js
    |-- package.json

Здесь я сначала объясню значение каждого файла/папки:

  • dist: каталог пакетов
  • lib: Основные файлы, в основном включаяcompilerа такжеparser
    • compiler.js: Корреляция компиляции.Compilerявляется классом и имеетrunметод для включения компиляции и сборкиmodule(buildModule) и выходной файл (emitFiles)
    • parser.js: Разобрать корреляцию. содержит синтаксический анализAST(getAST), собрать зависимости (getDependencies), конвертировать (es6转es5)
    • index.js: создать экземплярCompilerкласс и настроит параметры (соответствующиеforstpack.config.js) входящий
    • test.js: Тестовый файл, используемый для проверки функций методаconsoleиспользовать
  • src: исходный код. Это также соответствует нашему бизнес-коду
  • forstpack.config.js: Конфигурационный файл. аналогичныйwebpack.config.js
  • package.json: Мне не нужно больше говорить об этом~~ (Что, ты не знаешь??)

Завершите первые 30% кода для «Building the Wheel».

Проект готов, но чего-то не хватает~~

правильный! Сначала нам нужно улучшить основные файлы:forstpack.config.jsа такжеsrc.

прежде всегоforstpack.config.js:

const path = require("path");

module.exports = {
  entry: path.join(__dirname, "./src/index.js"),
  output: {
    path: path.join(__dirname, "./dist"),
    filename: "bundle.js",
  },
};

Содержание очень простое, определите вход и выход (вы слишком просты!! Не волнуйтесь, не торопитесь)

С последующимsrc, здесь вsrcВ каталоге определены два файла:

  • greeting.js:
// greeting.js
export function greeting(name) {
  return "你好" + name;
}
  • index.js:
import { greeting } from "./greeting.js";

document.write(greeting("森林"));

хорошо, вот мы и завершили всю работу, которую нужно подготовить. (В: Почему это так просто? О: Конечно, нам нужны основы, наша суть — «строительные колеса»!!)

Разберитесь с логикой

Для короткой остановки разберемся с логикой:

Q: Что мы будем делать?

A: сделай сравнениеwebpackсильнееsuper webpack(Извините, оплошность, я случайно сказал от всего сердца). Держитесь в тени (чтобы не получить пощечину)

Q: Как это сделать?

A: см. ниже (23333)

Q: Каков весь процесс?

A: Эй, наверное, процесс такой:

  • прочитать входной файл
  • Проанализируйте входной файл, рекурсивно прочитайте содержимое файла, от которого зависит модуль, и сгенерируйтеASTсинтаксическое дерево.
  • согласно сASTСинтаксическое дерево, которое генерирует код, который может запускать браузер.

Официальный старт

compile.js написать

const path = require("path");
const fs = require("fs");

module.exports = class Compiler {
  // 接收通过lib/index.js new Compiler(options).run()传入的参数,对应`forestpack.config.js`的配置
  constructor(options) {
    const { entry, output } = options;
    this.entry = entry;
    this.output = output;
    this.modules = [];
  }
  // 开启编译
  run() {}
  // 构建模块相关
  buildModule(filename, isEntry) {
    // filename: 文件名称
    // isEntry: 是否是入口文件
  }
  // 输出文件
  emitFiles() {}
};

compile.jsВ основном сделайте несколько вещей:

  • перениматьforestpack.config.jsНастройка параметров и инициализацияentry,output
  • Включить компиляциюrunметод. Модули сборки процессов, сбор зависимостей, выходные файлы и т.д.
  • buildModuleметод. В основном используется для построения модулей (поrunвызов метода)
  • emitFilesметод. выходной файл (такжеrunвызов метода)

сюда,compiler.jsВышла общая структура модуля, но после получения исходного кода модуля необходимо разобрать, заменить исходный код и получить зависимости модуля, что соответствует тому, что нам нужно улучшить ниже.parser.js.

написано parser.js

const fs = require("fs");
// const babylon = require("babylon");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const { transformFromAst } = require("babel-core");
module.exports = {
  // 解析我们的代码生成AST抽象语法树
  getAST: (path) => {
    const source = fs.readFileSync(path, "utf-8");

    return parser.parse(source, {
      sourceType: "module", //表示我们要解析的是ES模块
    });
  },
  // 对AST节点进行递归遍历
  getDependencies: (ast) => {
    const dependencies = [];
    traverse(ast, {
      ImportDeclaration: ({ node }) => {
        dependencies.push(node.source.value);
      },
    });
    return dependencies;
  },
  // 将获得的ES6的AST转化成ES5
  transform: (ast) => {
    const { code } = transformFromAst(ast, null, {
      presets: ["env"],
    });
    return code;
  },
};

После прочтения этого кода он немного сбивает с толку (обещание гарантированно будет понято 😤)

Не волнуйтесь, вы слушаете мою защиту! ! 😷

Здесь мы сначала сосредоточимся на некоторых из используемыхbabelМешок:

  • @babel/parser: используется для генерации исходного кодаAST
  • @babel/traverse:правильноASTУзлы проходятся рекурсивно
  • babel-core/@babel/preset-env: получитеES6изASTКонвертировано вES5

parser.jsСуществует три основных метода:

  • getAST: разобрать полученное содержимое модуля наASTсинтаксическое дерево
  • getDependencies: траверсAST, чтобы собрать используемые зависимости
  • transform: положить полученноеES6изASTКонвертировано вES5

Улучшить компилятор.js

Выше мы поставилиcompiler.jsФункции, которые будут использоваться вcompiler.js, конечно воспользуюсьparser.jsЧасть методов в (ерунда, иначе зачем бы я ставилparser.jsзакончил писать~~)

Перейдите непосредственно к коду:

const { getAST, getDependencies, transform } = require("./parser");
const path = require("path");
const fs = require("fs");

module.exports = class Compiler {
  constructor(options) {
    const { entry, output } = options;
    this.entry = entry;
    this.output = output;
    this.modules = [];
  }
  // 开启编译
  run() {
    const entryModule = this.buildModule(this.entry, true);
    this.modules.push(entryModule);
    this.modules.map((_module) => {
      _module.dependencies.map((dependency) => {
        this.modules.push(this.buildModule(dependency));
      });
    });
    // console.log(this.modules);
    this.emitFiles();
  }
  // 构建模块相关
  buildModule(filename, isEntry) {
    let ast;
    if (isEntry) {
      ast = getAST(filename);
    } else {
      const absolutePath = path.join(process.cwd(), "./src", filename);
      ast = getAST(absolutePath);
    }

    return {
      filename, // 文件名称
      dependencies: getDependencies(ast), // 依赖列表
      transformCode: transform(ast), // 转化后的代码
    };
  }
  // 输出文件
  emitFiles() {
    const outputPath = path.join(this.output.path, this.output.filename);
    let modules = "";
    this.modules.map((_module) => {
      modules += `'${_module.filename}' : function(require, module, exports) {${_module.transformCode}},`;
    });

    const bundle = `
        (function(modules) {
          function require(fileName) {
            const fn = modules[fileName];
            const module = { exports:{}};
            fn(require, module, module.exports)
            return module.exports
          }
          require('${this.entry}')
        })({${modules}})
    `;

    fs.writeFileSync(outputPath, bundle, "utf-8");
  }
};

оcompiler.jsВнутренняя функция , я еще раз сказал выше, вот основной видemitFiles:

emitFiles() {
    const outputPath = path.join(this.output.path, this.output.filename);
    let modules = "";
    this.modules.map((_module) => {
      modules += `'${_module.filename}' : function(require, module, exports) {${_module.transformCode}},`;
    });

    const bundle = `
        (function(modules) {
          function require(fileName) {
            const fn = modules[fileName];
            const module = { exports:{}};
            fn(require, module, module.exports)
            return module.exports
          }
          require('${this.entry}')
        })({${modules}})
    `;

    fs.writeFileSync(outputPath, bundle, "utf-8");
  }

здесьbundleЧто за черт?

Мы начинаем понимать следующееwebpackМеханизм документации 📦. Следующий код послеwebpackУпакованный и упрощенный код:

// dist/index.xxxx.js
(function(modules) {
  // 已经加载过的模块
  var installedModules = {};

  // 模块加载函数
  function __webpack_require__(moduleId) {
    if(installedModules[moduleId]) {
      return installedModules[moduleId].exports;
    }
    var module = installedModules[moduleId] = {
      i: moduleId,
      l: false,
      exports: {}
    };
    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
    module.l = true;
    return module.exports;
  }
  __webpack_require__(0);
})([
/* 0 module */
(function(module, exports, __webpack_require__) {
  ...
}),
/* 1 module */
(function(module, exports, __webpack_require__) {
  ...
}),
/* n module */
(function(module, exports, __webpack_require__) {
  ...
})]);

При простом анализе:

  • webpackОберните все модули (которые можно просто считать файлами) в функцию, передайте параметры по умолчанию и поместите все модули в массив с именемmodulesи передать индекс массива какmoduleId.
  • БудуmodulesПередайте самовыполняющуюся функцию, а самовыполняющаяся функция содержитinstalledModulesЗагруженный модуль и функция загрузки модуля, наконец, загружают входной модуль и возвращаются.
  • __webpack_require__Загрузка модуля, первый судьяinstalledModulesНезависимо от того, был ли он загружен, он вернется сразу после загрузкиexportsданные, переданные без загруженного модуляmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__)выполнить модуль иmodule.exportsОтдай это обратно.

(Что, черт возьми, это за дерьмо, которое вы упомянули выше? Я не понимаю, ах ах ах ах!!!)

Итак, позвольте мне сказать по-другому:

  • проходить черезwebpackТо, что упаковано, является анонимной функцией закрытия (IIFE)
  • modulesпредставляет собой массив, каждый элемент представляет собой функцию инициализации модуля
  • __webpack_require__используется для загрузки модуля, возвратаmodule.exports
  • пройти черезWEBPACK_REQUIRE_METHOD(0)стартовая программа

(Шёпотом бб: Как это, я так понимаю)

запись файла входа lib/index.js

В этот момент остался последний шаг (похоже, уже забрезжил рассвет победы). существуетlibсоздание каталогаindex.js:

const Compiler = require("./compiler");
const options = require("../forestpack.config");

new Compiler(options).run();

Логика здесь относительно проста: приведениеCompilerкласс и настроит параметры (соответствующиеforstpack.config.js) входящие.

бегатьnode lib/index.jsбудет вdistсоздается в каталогеbundle.jsдокумент.

(function (modules) {
  function require(fileName) {
    const fn = modules[fileName];
    const module = { exports: {} };
    fn(require, module, module.exports);
    return module.exports;
  }
  require("/Users/fengshuan/Desktop/workspace/forestpack/src/index.js");
})({
  "/Users/fengshuan/Desktop/workspace/forestpack/src/index.js": function (
    require,
    module,
    exports
  ) {
    "use strict";

    var _greeting = require("./greeting.js");

    document.write((0, _greeting.greeting)("森林"));
  },
  "./greeting.js": function (require, module, exports) {
    "use strict";

    Object.defineProperty(exports, "__esModule", {
      value: true,
    });
    exports.greeting = greeting;

    function greeting(name) {
      return "你好" + name;
    }
  },
});

И вышеwebpackупакованныйjsСравните файлы, они очень похожи?

давай! экспонат

мы вdistСоздано в каталогеindex.htmlфайл, сгенерированный пакет импортаbundle.jsдокумент:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="./bundle.js"></script>
  </body>
</html>

Откройте браузер в этот момент:

Как вы и хотели, мы получили ожидаемый результат~

Суммировать

через паруwebpackАнализ процесса сборки и реализация простогоforestpack, поверьте, вы правыwebpackПринцип построения имеет четкое понимание! (Конечно, здесьforestpackа такжеwebpackОн еще слаб и слаб по сравнению с ,)

Ссылаться на

Эта статья подготовлена ​​после прочтения курса «Игра с веб-пакетом» г-на Ченга Люфэна из Geek Time. Я очень рекомендую всем пройти этот курс.

❤️ Люблю тройное комбо

1. Если вы считаете, что эта статья неплохая, заходите сюдаДелитесь, лайкайте, смотритеСанлиан, пусть больше людей увидят это~

2. Подпишитесь на официальный аккаунтпередний лес, регулярно снабжаем вас свежими и галантереями и хорошими товарами.

3. На спецучастках носите маску и пользуйтесь средствами индивидуальной защиты.