в предыдущей статье«Модульная серия» подробно разъясняет AMD, CommonJS, CMD, UMD, ES6., мы можем изучить различные модульные механизмы. Затем давайте проанализируем механизм модульности webpack. (в основном о части JS)
Когда дело доходит до веб-пакета, можно сказать, что это инструмент, который очень тесно связан с нашим проектом разработки.Будь то ежедневная разработка, интервью или самосовершенствование, он неотделим, потому что он приносит большое удобство в нашу разработку и обучение. Но поскольку webpack — это очень большая инженерная система, мы обескуражены. В виде этой диаграммы эта статья надеется медленно приподнять сложную завесу и, наконец, раскрыть ее истинное лицо. Ниже приведен мой список систем, связанных с webpack.
Эта статья о打包 - CommonJS
Модуль в основном разделен на две части
- Роль веб-пакета
- Механизм модульности и реализация webpack
Роль веб-пакета
В сегодняшней диверсификации нашего внешнего интерфейса многие инструменты стали очень большими, чтобы удовлетворить наши растущие потребности в разработке, такие как webpack. По нашему мнению, он объединяет все функции, связанные с разработкой, упаковкой модулей, понижением версии кода, оптимизацией файлов, проверкой кода и так далее. Именно из-за такого огромного инструмента мы обескуражены.Конечно, другой момент в том, что частый апгрейд вебпака и неразбериха с поддерживаемыми версиями окружающих его экологических плагинов также усугубляют наш страх перед ним.
Так должны ли мы задуматься о том, что принесло нам появление веб-пакета? Зачем нам это нужно? Все вышеперечисленные понижения версии кода (преобразование Babel), компиляция SCSS и обнаружение спецификаций кода выигрывают от его системы подключаемых модулей и механизма загрузки и не принадлежат ему полностью.
Так что мне кажется, что ядром его функциональности являетсяПакет, а упаковка позволяет выполнять модульные спецификации непосредственно в браузере. Итак, давайте посмотрим на функции, которые дает упаковка:
- Изоляция модуля
- загрузка зависимости модуля
Изоляция модуля
Если мы не используем метод упаковки, все наши модули напрямую выставляются глобально, то есть монтируются наwindow/global
этот объект. Может быть, это приемлемо, когда объем кода небольшой, и проблем не будет так много. Особенно в случае увеличения кода и совместной работы нескольких человек влияние на глобальное пространство непредсказуемо, если вам нужно обратиться к каждой разработке, чтобы узнать, используют ли они текущие имена переменных.
Например (только для примера, реальный проект будет намного сложнее, чем следующий), в начале наш user1 написал несколько модулей, которые работали очень гладко.
├── bar.js function bar(){}
├── baz.js function baz(){}
└── foo.js function foo(){}
Однако с бизнес итерацией сложность проекта возрастает, и приходит пользователь 2. В это время пользователю 2 необходимо разработать бизнес foo, в котором также есть модуль baz.Код также быстро пишется, и выглядит как следующий.
├── bar.js function bar(){}
├── baz.js function baz(){}
├── foo
│ └── baz.js function baz(){}
└── foo.js function foo(){}
Но в это время к пользователю пришел начальник 2. Почему первоначальный бизнес пошел не так после добавления нового бизнеса? В это время было обнаружено, что новый модуль, написанный пользователем2, перезаписал модуль пользователя1, что и вызвало аварию.
Поэтому, когда мы выставляем все модули в глобальную разработку, мы должны быть очень осторожны, чтобы избежать ошибок, потому что нам легко неосознанно и тайно скрыть наши ранее определенные функции, тем самым вызвав ошибки.
Таким образом, первая основная функция, привносимая вебпаком, — это изоляция.Каждый модуль оборачивается в новый модуль в виде замыкания и помещается в локальную область видимости, а все объявления функций не выставляются напрямую в глобальную.
Получается, что мы вызываем функцию foo, но webpack поможет нам сгенерировать уникальный идентификатор модуля, так что нам вообще не нужно беспокоиться о конфликтах модулей, и теперь мы можем спокойно писать код.
baz.js
module.exports = function baz (){}
foo/baz.js
module.exports = function baz (){}
main.js
var baz = require('./baz.js');
var fooBaz = require('./foo/baz.js');
baz();
fooBaz();
Возможно, способ, который вы сказали ранее, также можно изменить, изменив способ имени функции, но исходная область действия — это весь проект, вы должны убедиться, что текущее имя не конфликтует во всем проекте, теперь вам нужно только убедитесь, что имя находится в одном файле. Не конфликтуйте. (Также очень легко найти конфликты для зависимостей верхнего уровня)
загрузка зависимости модуля
Еще одна важная функция — загрузка зависимостей модулей. Каковы преимущества этого подхода? Давайте также сначала посмотрим на пример, чтобы увидеть, какие проблемы возникнут в исходном виде?
User1 написал 3 модуля, из которых baz зависит от bar.
После написания user1 выходит в сеть, используя порядок указания зависимостей.
<script src="./bar.js"></script>
<script src="./baz.js"></script>
<script src="./foo.js"></script>
Но вскоре после этого user2 взял на себя бизнес. Пользователь 2 обнаружил, что разработанный им модуль abc можно быстро разработать, опираясь на модуль bar. Но небрежный user2 не совсем понимает зависимости. Расположение abc было указано произвольно, из-за чего модуль bar не мог быть найден при запуске abc.
<script src="./abc.js"></script>
<script src="./bar.js"></script>
<script src="./baz.js"></script>
<script src="./foo.js"></script>
Итак, здесь webpack использует для обработки спецификацию модулей CommonJS/ES. Сделайте так, чтобы каждый модуль ссылался друг на друга, не учитывая порядок окончательной фактической презентации. В конечном итоге он будет упакован как пакетный модуль, и нет необходимости вручную импортировать по порядку.
baz.js
const bar = require('./bar.js');
module.exports = function baz (){
...
bar();
...
}
abc.js
const bar = require('./bar.js');
module.exports = function baz (){
...
bar();
...
}
<script src="./bundle.js"></script>
Механизм модульности и реализация webpack
На основе двух вышеупомянутых функций, изоляции модулей и агрегации зависимостей модулей. Теперь мы можем очень четко знать основную роль webpack.
- Чтобы максимально снизить сложность написания и стоимость понимания, я не использовал парсинг AST, (конечно, AST не сложен, я объясню, что такое AST и процесс реализации парсера AST в следующих статьях .
- Реализована только поддержка CommonJS
Как работают пакеты
Чтобы реализовать веб-пакет, мы можем использовать обратный метод, чтобы увидеть, как пакет работает после того, как веб-пакет упакован.
Исходный файл
// index.js
const b = require('./b');
b();
// b.js
module.exports = function () {
console.log(11);
}
после сборки(удален некоторый мешающий код)
(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;
}
return __webpack_require__((__webpack_require__.s = 0));
})([
/* 0 */
function(module, exports, __webpack_require__) {
var b = __webpack_require__(1);
b();
},
/* 1 */
function(module, exports) {
module.exports = function() {
console.log(11);
};
},
]);
Так работают связки. Из приведенной выше блок-схемы мы видим, что есть четыре ключевых момента.
- Зарегистрированные модули (хранит зарегистрированные модули)
- Список модулей (используется для хранения всех упакованных модулей)
- Поиск модулей (от исходных древовидных зависимостей модулей до плоского поиска)
- Упаковка модуля (все оригинальные модули упакованы один раз)
реализация веб-пакета
При анализе пакетов все, что нам нужно сделать, это 4 вещи.
- перебрать все модули
- модульная упаковка
- Предоставляет зарегистрированные модули, переменные списка модулей и импортированные функции.
- Постоянный экспорт
обход модулей
Прежде всего, давайте представим структуру модуля, которая может дать нам быстрое понимание Структура относительно проста и состоит из содержимого и идентификатора модуля.
interface GraphStruct {
context: string;
moduleId: string;
}
{
"context": `function(module, exports, require) {
const bar = require('./bar.js');
const foo = require('./foo.js');
console.log(bar());
foo();
}`,
"moduleId": "./example/index.js"
}
Далее мы объясним получение файла входа.Когда мы получаем файл входа, нам нужно проанализировать его зависимости. Проще говоря, получитьrequire
значение, чтобы мы могли перейти к следующему модулю. Так как я не хочу вводить дополнительные знания в эту часть, я также упомянул об этом в начале.AST
разбор, чтобы получитьrequire
модуль, здесь мы используем регулярное выражение.
用来匹配全局的 require
const REQUIRE_REG_GLOBAL = /require\(("|')(.+)("|')\)/g;
用来匹配 require 中的内容
const REQUIRE_REG_SINGLE = /require\(("|')(.+)("|')\)/;
const context = `
const bar = require('./bar.js');
const foo = require('./foo.js');
console.log(bar());
foo();
`;
console.log(context.match(REQUIRE_REG_GLOBAL));
// ["require('./bar.js')", "require('./foo.js')"]
Так как обход модуля представляет собой не только простую однослойную структуру, это вообще древовидная структура, поэтому здесь я использую глубокий обход. В основном через обычные, чтобы соответствоватьrequire
Зависимости в , а затем рекурсивно получить модули, и, наконец, модули, которые были пройдены через глубину, сохраняются в виде массива. (Не понимаю глубокий обход, его можно понимать как модуль рекурсивного сбора данных)
Ниже приведена реализация кода
...
private entryPath: string
private graph: GraphStruct[]
...
createGraph(rootPath: string, relativePath: string) {
// 通过获取文件内容
const context = fs.readFileSync(rootPath, 'utf-8');
// 匹配出依赖关系
const childrens = context.match(REQUIRE_REG_GLOBAL);
// 将当前的模块存储下来
this.graph.push({
context,
moduleId: relativePath,
})
const dirname = path.dirname(rootPath);
if (childrens) {
// 如有有依赖,就进行递归
childrens.forEach(child => {
const childPath = child.match(REQUIRE_REG_SINGLE)[2];
this.createGraph(path.join(dirname, childPath), childPath);
});
}
}
модульная упаковка
Чтобы изолировать модуль, мы инкапсулируем слой функций извне, а затем передаем соответствующую симуляциюrequire
а такжеmodule
Позволяет модулям нормально регистрироваться и импортироваться.
function (module, exports, require){
...
},
Предоставляет зарегистрированные модули, переменные списка модулей и импортированные функции.
Этот шаг относительно прост, если вы предоставляете зарегистрированные переменные модуля, переменные списка модулей и функции импорта в соответствии с проанализированной нами блок-схемой.
/* modules = {
"./example/index.js": function (module, exports, require) {
const a = require("./a.js");
const b = require("./b.js");
console.log(a());
b();
},
...
};*/
bundle(graph: GraphStruct[]) {
let modules = '';
graph.forEach(module => {
modules += `"${module.moduleId}":function (module, exports, require){
${module.context}
},`;
});
const bundleOutput = `
(function(modules) {
var installedModules = {};
// 导入函数
function 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,
require
);
// 设置标记已经注册
module.l = true;
// 返回实际模块
return module.exports;
}
require("${graph[0].moduleId}");
})({${modules}})
`;
return bundleOutput;
}
Постоянный экспорт
Наконец, настойчиво запишите сгенерированный пакет на диск, и все готово.
fs.writeFileSync('bundle.js', this.bundle(this.graph))
Полный код состоит менее чем из 100 строк. Подробности можно посмотреть в полном примере ниже.
адрес гитхаба:GitHub.com/flower1995116/…
конец
Вышеупомянутое представляет только мое личное понимание, я надеюсь, что это поможет вам в вашем понимании веб-пакета.Если что-то не так, пожалуйста, укажите больше.
Добро пожаловать в публичный аккаунт«Записки осеннего ветра»., в основном записывайте инструменты, которые интересны в повседневной жизни, и делитесь практиками разработки, сохраняя глубину и направленность. Отвечатьwebpack
Получить обзорную картуxmind 原图
.
FAQ
Вопрос: Почему вы решили написать эту статью?
Р: На самом деле, это в основном для рисования, что является совершенно новым.
В: Будет ли следующая статья?
Р: Да, следующая статья ориентировочно связана с модулем ES и разделением кода.