введение
Некоторое время назад я поставил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. На спецучастках носите маску и пользуйтесь средствами индивидуальной защиты.