Обзор одной из серий webpack

исходный код Webpack

Автор: Цуй Цзин

введение

Webpack знаком с каждым интерфейсом, он рассматривает каждый статический файл как модуль и интегрирует для нас окончательные необходимые файлы js, css, изображения, шрифты и другие файлы после серии обработок. Картинка с официального сайта — яркое описание функции вебпака — бандл js/css/… (пакет мир ヾ(◍°∇°◍)ノ゙)

webpack

слова, написанные впереди

Прежде чем читать исходный код какой-либо вещи, нужно сначала понять, что это за вещь и как ею пользоваться. Таким образом, в процессе чтения исходного кода в мозгу может сформироваться целостное познание. Итак, давайте сначала разберемся, что происходит с кодом до и после упаковки 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.

Есть два момента для уточнения:

  1. Мы знаем, что import('xxx.js') вернет обещание об экземпляре Promise. Как это обещание обрабатывается в окончательном файле, упакованном webpack?

    Глобально перед загрузкой 0.jsinstalledChunksОбъект обещания сначала сохраняется в

    installedChunks[chunkId] = [resolve, reject, promise]
    

    разрешение Это значение будет использоваться в webpackJsonpCallback, затем будет введен код, который мы написалиimport('./c.js').then()в тогдашнем заявлении.

  2. Также есть особая логика в процессе обработки 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 и сравните его с процессом в нашей голове. Фактическая блок-схема веб-пакета выглядит следующим образом:

webpack流程

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