Автор: Цуй Цзин
введение
Webpack знаком с каждым интерфейсом, он рассматривает каждый статический файл как модуль и интегрирует для нас окончательные необходимые файлы js, css, изображения, шрифты и другие файлы после серии обработок. Картинка с официального сайта — яркое описание функции вебпака — бандл js/css/… (пакет мир ヾ(◍°∇°◍)ノ゙)
слова, написанные впереди
Прежде чем читать исходный код какой-либо вещи, нужно сначала понять, что это за вещь и как ею пользоваться. Таким образом, в процессе чтения исходного кода в мозгу может сформироваться целостное познание. Итак, давайте сначала разберемся, что происходит с кодом до и после упаковки webpack? найти простой пример
Входной файл — main.js, в котором представлены a.js и b.js.
// main.js
import { A } from './a'
import B from './b'
console.log(A)
B()
// a.js
export const A = 'a'
// b.js
export default function () {
console.log('b')
}
После многих разрушений веб-пакета он наконец стал файлом: bundle.js. Сначала проигнорируйте детали и посмотрите на самую внешнюю структуру кода.
(function(modules){
...(webpack的函数)
return __webpack_require__(__webpack_require__.s = "./demo01/main.js");
})(
{
"./demo01/a.js": (function(){...}),
"./demo01/b.js": (function(){...}),
"./demo01/main.js": (function(){...}),
}
)
Самый внешний слой — это функция немедленного выполнения, а параметр — это модули. a.js, b.js и main.js окончательно компилируются в три функции (здесь и далее эти три функции называются функциями модуля), а ключом является относительный путь файла. bundle.js будет выполняться для__webpack_require__(__webpack_require__.s = "./demo01/main.js");
то есть через__webpack_require__('./demo01/main.js')
Запустите выполнение основной функции входа.
Из основного интерфейса bundle.js хорошо видно, что каждый файл для webpack — это модуль. мы написалиimport 'xxx'
, то наконец__webpack_require__
выполнение функции. Чаще мы используемimport A from 'xxx'
илиimport { B } from 'xxx'
, можно догадаться, это__webpack_require__
В дополнение к поиску соответствующего 'xxx' для выполнения функция также должна вернуть содержимое, экспортированное из 'xxx'.
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;
}
При вызове каждой функции модуля параметрыmodule
,module.exports
,__webpack_require__
.module.exports
Используется для сбора всего экспорта xxx в модуль. См. модуль в "./demo/a.js"
(function(module, __webpack_exports__, __webpack_require__) {
// ...
__webpack_require__.d(__webpack_exports__, "A", function() { return A; });
const A = 'a'
/***/ })
// ...
__webpack_require__.d = function(exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
configurable: false,
enumerable: true,
get: getter
});
}
};
// Object.prototype.hasOwnProperty.call
__webpack_require__.o = function(object, property) {
return Object.prototype.hasOwnProperty.call(object, property);
};
// ...
__webpack_require__.d(__webpack_exports__, "A", function() { return A; });
Простое понимание
__webpack_exports__.A = A;
а также__webpack_exports__
на самом деле выше__webpack_require__
входящийmoule.exports
, чтобы переменная A собираласьmodule.exports
середина. так что наш
import { A } from './a.js'
console.log(A)
он компилируется в
var _a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./demo/a.js");
console.log(_a__WEBPACK_IMPORTED_MODULE_0__["A"])
Для b.js мы используемexport default
, webpack добавит атрибут по умолчанию в module.exports после обработки.
__webpack_exports__["default"] = (function () {
console.log('b')
});
наконецimport B from './b.js
компилируется в
var _b__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__("./demo/b.js")
Object(_b__WEBPACK_IMPORTED_MODULE_1__["default"])()
Асинхронная загрузка
В webpack мы можем легко реализовать асинхронную загрузку, начав с простого демо.
// c.js
export default {
key: 'something'
}
// main.js
import('./c').then(test => {
console.log(test)
})
Упакованный результат, загружаемый асинхронно c.js, окончательно упаковывается в отдельный файл 0.js.
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{
"./demo/c.js": (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
__webpack_exports__["default"] = ({
key2: 'key2'
});
})
}]);
Упрощенно, реализация
var t = window["webpackJsonp"] = window["webpackJonsp"] || [])
t.push([[0], {function(){...}}])
Когда выполняется import('./c.js'), 0.js фактически загружается путем вставки тега скрипта в HTML. После загрузки 0.js будет выполнен метод window["webpackJsonp"].push. В main.js есть еще один раздел:
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
jsonpArray.push = webpackJsonpCallback;
Здесь он был изменен Метод push окна["webpackJsonp"] оборачивает метод push слоем логики webpackJonspCallback. Когда 0.js загружен, он выполнитсяwindow["webpackJsonp"].push
В это время вы войдете в логику исполнения WebPackjsonPCallback.
function webpackJsonpCallback(data) {
var chunkIds = data[0];
var moreModules = data[1];
// add "moreModules" to the modules object,
// then flag all "chunkIds" as loaded and fire callback
var moduleId, chunkId, i = 0, resolves = [];
for(;i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if(installedChunks[chunkId]) {
resolves.push(installedChunks[chunkId][0]);
}
installedChunks[chunkId] = 0;
}
for(moduleId in moreModules) {
if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId];
}
}
if(parentJsonpFunction) parentJsonpFunction(data);
while(resolves.length) {
resolves.shift()();
}
};
В webpackJsonpCallback чанки и модули в 0.js будут сохранены в глобальную переменную modules, и будет установлен флагinstalledChunks.
Есть два момента для уточнения:
-
Мы знаем, что import('xxx.js') вернет обещание об экземпляре Promise. Как это обещание обрабатывается в окончательном файле, упакованном webpack?
Глобально перед загрузкой 0.js
installedChunks
Объект обещания сначала сохраняется вinstalledChunks[chunkId] = [resolve, reject, promise]
разрешение Это значение будет использоваться в webpackJsonpCallback, затем будет введен код, который мы написали
import('./c.js').then()
в тогдашнем заявлении. -
Также есть особая логика в процессе обработки webpackJsonp в main.js:
jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); ... jsonpArray = jsonpArray.slice(); for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); var parentJsonpFunction = oldJsonpFunction;
То есть, если уже есть глобальный
window["webpackJsonp"]
Затем перед заменой функции push исходный метод push будет сохранен как oldJsonpFunction, а существующий метод push будет сохранен вwindow["webpackJsonp"]
Содержимое , выполнить один за другимwebpackJsonpCallback
. И вwebpackJsonpCallback
Контент, который также будет загружаться асинхронно вparentJsonpFunction
выполнить то же самоеif(parentJsonpFunction) parentJsonpFunction(data);
Какой смысл в этой синхронизации? Представьте себе следующий сценарий в случае нескольких записей в веб-пакете, например следующую конфигурацию
{ entry: { bundle1: 'bundle1.js', bundle2: 'bundle2.js' } }
И пакет1, и пакет2 используют асинхронную загрузку 0.js. И пакет1, и пакет2 загружаются на одной странице. Затем, в соответствии с приведенной выше логикой, поток выполнения выглядит следующим образом:
Как видно из приведенного выше рисунка, этот дизайн может синхронизировать асинхронные модули в bundle1.js и bundle2.js для множественных мест входа, что не только гарантирует, что на 0.js можно ссылаться в двух файлах одновременно, но и тоже не перезагружается.
При асинхронной загрузке следует обратить внимание на две вещи:
-
Promise
Промисы используются для асинхронной загрузки в webpack. Для совместимости с более ранними версиями Android, такими как код 4.x, требуется глобальный полифилл Promise.
-
window["webpackJsonp"]
На HTML будет загружена несколько файлов, упакованных независимо от WebPack. Затем функции обратного вызова для асинхронной загрузки этих файлов, по умолчанию называются «WebPackjonsp», что будет конфликтовать друг с другом. Это имя функции по умолчанию должно быть изменено через конфигурацию yource.jsonpфункции.
Общий процесс компиляции webpack
Зная приведенный выше вывод, посмотрите на общий процесс webpack в соответствии с выводом. Здесь мы не рассматриваем логику кеширования вебпака, обработки ошибок, просмотра и т.п., а смотрим только на основной процесс. Во-первых, в конфигурационном файле будет записан файл ввода, чтобы определить, с какого файла начинается обработка веб-пакета.
шаг 1 обработка файла конфигурации веб-пакета
Когда мы писали запись в конфигурационный файл, мы должны были написать./main.js
На этот раз относительный каталог, поэтому будет процесс превращения относительного каталога в абсолютный каталог
анализ местоположения файла step2
webpack должен начать с входного файла и найти все файлы по пути. тогда будет
шаг 3 загрузить файл парсинг файла step4 step5 Найдите другие файлы, представленные файлом, из результата синтаксического анализа
При загрузке файлов мы настроим множество загрузчиков в веб-пакете для обработки babel-преобразования js-файлов и т. д. Также должен быть парсинг загрузчика и выполнение загрузчика, соответствующее файлу.
step3.1 Найдите все соответствующие загрузчики и выполните их один за другим
После обработки полного входного файла другие зависимые файлы получаются и рекурсивно обрабатываются. Наконец-то получил модуль всех файлов. Конечным результатом является упакованный пакетный файл. так что будет
модуль step4 объединен в чанк выходной окончательный файл
Основываясь на использовании и результатах webpack, мы угадали примерный процесс в webpack. Тогда взгляните на исходный код webpack и сравните его с процессом в нашей голове. Фактическая блок-схема веб-пакета выглядит следующим образом:
После того, как у нас будет общее представление об общей структуре и процессе, мы можем разделить исходный код на части для подробного чтения. Дальнейшие действия будут представлены в серии статей:
-
Введение в низкоуровневый Tapable
Tapable, используемый в нижней части веб-пакета, используется для обработки различных типов хуков.Эта часть в основном знакомит с принципом Tapable.Обновлено, нажмите для просмотра
-
процесс разрешения
Как обрабатываются различные относительные/абсолютные пути, псевдонимы и т. д., которые мы прописали в веб-пакете, и, наконец, найден правильный исполняемый файл.Обновлено, нажмите для просмотра
-
Обработка погрузчиков
В конфиге вебпака написано, как загружаются и парсятся различные загрузчики; обновленоПодробное объяснение загрузчика webpack 1 Подробное объяснение загрузчика webpack 2 Подробное объяснение загрузчика webpack 3
-
генерация модуля
Как анализируются файлы js, анализируются зависимости и рекурсивно обрабатываются все зависимости; обновляетсямодуль генерирует 1 модуль 2 поколения
-
генерация чанков
Создание графа зависимостей между различными файлами в проекте и окончательное объединение модулей в фрагменты в соответствии с определенными правилами.Обновлено, нажмите для просмотра
-
Генерация финальных файлов
После прохождения всех вышеперечисленных процессов в памяти сохраняется всевозможная информация о сгенерированных файлах, как интегрировать и выплевывать все файлы, которые реально выполняются в итоге.Обновлено, нажмите для просмотра