Webpack: магия вне Хогвартса
Содержание
- Что такое Webpack?
- Терминология. Модуль, чанк, ассет, бандл
- Как устроен процесс сборки бандла
- Что получается на выходе у бандла?
- Немного черной магии напоследок
Что такое Webpack?
- Бандлер
- Сборочный пайплайн
Терминология: модуль
- Cамая маленькая сущность
- Обычно инкапсулирует один файл
- Основа для построения графа зависимостей
- Именно к модулям применяются лоадеры Webpack
Пример модуля
sum.js
const sum = (a, b) => a + b
export default sum
multiply.js
const multiply = (a, b) => a * b
export default multiply
main.js (entrypoint)
import sum from './sum'
import multiply from './multiply'
console.log(sum(2, 4))
console.log(multiply(3, 5))
Webpack строит дерево зависимостей:
Терминология: чанк
- Cущность, включающая в себя несколько модулей
- Инкапсулируемые модули связаны друг с другом
- Граф зависимостей чанков - основа графа зависимостей ассетов
-
Всегда присутствует чанк-загрузчик, который предоставляет __webpack_require__
(о загрузчике мы поговорим чуть позже)
Как Webpack видит проект сейчас
Терминология: ассет
- Ассет - набор чанков, заключённых в одном файле
Добавляем слой вложенности
Терминология: бандл
- Бандл - результат сборки приложения
- Бандл включает в себя ассеты, которые включают в себя чанки, состоящие из модулей
- Граф зависимостей чанков и ассетов - основа графа зависимостей бандла
Результат сборки: простой
Результат сборки: чуть посложнее
Процесс сборки Webpack
Плагины могут выполняться во время любой стадии сборки;
больше информации можно найти на bit.ly/webpack-hooks
Валидация конфигурации
Препроцессинг и построение графа зависимостей
Что на выходе у нашего бандла?
Один файл app.js, имеющий примерно такую структуру:
(function(modules) {
var installedModules = {}
function __webpack_require__(moduleId) {
// код
}
return __webpack_require__(__webpack_require__.s = "./main.js")
})({
"./main.js": (function(module, exports, require) {
"use strict";
eval("содержимое main, обёрнутое в __webpack_require__")
}),
// то же самое для других файлов
})
Что мы можем сказать, глядя на этот файл?
- Это IIFE
- Принимает в себя список модулей
- Возвращает __webpack_require__ с moduleId главного модуля
- Все модули представляют собой функцию, вызывающуюся со следующими параметрами
- Модуль
- Объект экспортов модуля
- Экземпляр __webpack_require__
- Все модули внутри представляют вызов eval, который мутирует переданный в изначальную функцию объект экспортов
Попробуем угадать, что делает __webpack_require__
Подсказка - все модули в JS - синглтоны
...ответ на загадку
- Исходя из названия функции, она всегда будет возвращать экспорты
- Сначала она будет сверяться с кэшем инициализированных модулей.
- В случае, если кэш найден - возвращаем экспорты этого модуля
- В противном случае пытаемся найти инициализатор модуля в списке переданных нам
- Вызываем инициализатор с пустым объектом - будущим exports
- Кэшируем модуль
- Возвращаем экспорты модуля
Проверим себя
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__ всё же не так прост!
(function (modules) {
var installedModules = {};
// define webpackRequire()
__webpack_require__.m = modules;
__webpack_require__.c = installedModules;
__webpack_require__.p = ""; // publicPath из конфига
__webpack_require__.d = () => {} // Геттер для Harmony-экспортов
__webpack_require__.r = () => {} // Приводит объект экспортов из вызова require
// к виду Harmony
// __esModule = true
// Symbol.toStringTag = Module
__webpack_require__.t = () => {} // Создаёт фейковый неймспейс-объект
__webpack_require__.n = () => {} // функция getDefaultExport()
__webpack_require__.o = () => {} // алиас Object.hasOwnProperty
})({/* modules */})
...и к этому всему вы по умолчанию имеете доступ из вашего кода!
Просто обратитесь к __webpack_require__
Disclaimer: API официально не открыт, но уже очень давно кардинально не менялся
Но зачем всё это нужно?
Давайте займёмся магией вне Хогвартса
Вместе мы разберём, как можно менять исходный код приложения прямо из самого себя
Step 1: пишем простой плагин для Вебпака
Наш плагин будет приводить имена чанков в нашем приложении от номеров к именам файлов
class CustomPlugin {
apply(compiler) {
compiler.hooks.compilation.tap('CustomPlugin', c => {
// Установив хук на событие компиляции
// мы устанавливаем хук на подсобытие beforeModuleIds
c.hooks.beforeModuleIds.tap(
'CustomPlugin',
modules => renameModules(modules)
)
})
}
}
Step 1.2: пишем простой плагин для Вебпака
...переименовываем чанки!
const renameModules = modules => modules.forEach(module => {
module.id = path
.relative(module.context, module.resource)
.replace(/\\/g, '/')
})
Step 2: первый урок Тёмных Искусств
...переходим в Visual Studio Code
Спасибо за внимание!
Осторожнее с магией вне Хогвартса ;)