Webpack поддерживает несколько систем модулей одновременно. Это необходимо для совместимости со старым кодом, библиотеками npm, браузерными сборками и современными стандартами JavaScript.
Наиболее распространённые форматы:
Основная задача Webpack — привести все типы модулей к единому внутреннему графу зависимостей, после чего выполнить оптимизацию и сформировать итоговые бандлы.
CommonJS — модульная система, появившаяся в экосистеме Node.js задолго до стандартизации ES-модулей.
Главные особенности:
require;module.exports или
exports;Пример модуля:
// math.js
function sum(a, b) {
return a + b;
}
module.exports = {
sum
};
Импорт:
const math = require('./math');
console.log(math.sum(2, 3));
Webpack умеет анализировать вызовы require() и строить
граф зависимостей.
Пример:
const logger = require('./logger');
const config = require('./config');
Во время сборки Webpack:
require;После сборки код превращается в структуру вида:
(() => {
const __webpack_modules__ = {
'./src/logger.js': (module) => {
module.exports = function () {
console.log('log');
};
}
};
})();
Webpack эффективно работает только со статически определяемыми путями.
Корректно:
require('./utils');
Проблемно:
require(moduleName);
или:
require('./modules/' + name);
В подобных случаях Webpack не может заранее определить набор файлов.
Результат:
При динамическом require Webpack создаёт специальный
context-модуль.
Пример:
const lang = require('./lang/' + locale + '.json');
Webpack может включить:
./lang/en.json
./lang/fr.json
./lang/de.json
Вместо одного файла в бандл попадёт целая директория.
Webpack поддерживает require.resolve.
Пример:
const path = require.resolve('./config');
Webpack вычисляет путь во время сборки.
Это полезно:
Основной механизм экспорта:
module.exports = function () {
console.log('hello');
};
Сокращённая ссылка:
exports.sum = sum;
exports.sub = sub;
Эквивалентно:
module.exports.sum = sum;
Некорректный вариант:
exports = {
sum
};
Проблема заключается в потере связи с
module.exports.
Webpack сохраняет это поведение, поскольку полностью совместим со спецификой CommonJS.
ESM — официальный модульный стандарт JavaScript.
Использует:
importexportПример:
// math.js
export function sum(a, b) {
return a + b;
}
Импорт:
import { sum } from './math';
console.log(sum(2, 3));
ESM анализируется ещё до выполнения кода.
Это позволяет:
Одно из важнейших преимуществ ESM.
Модуль:
export function used() {
console.log('used');
}
export function unused() {
console.log('unused');
}
Импорт:
import { used } from './module';
Webpack способен удалить unused из
production-сборки.
Для CommonJS такая оптимизация значительно сложнее.
ESM использует live bindings.
Экспорт:
export let counter = 0;
export function increment() {
counter++;
}
Импорт:
import { counter, increment } from './store';
increment();
console.log(counter);
Значение counter обновляется автоматически.
Webpack распознаёт:
import
export
import()
Пример:
import userService from './services/userService';
После анализа создаётся граф зависимостей.
Webpack сохраняет информацию:
Для tree shaking важно наличие побочных эффектов.
{
"sideEffects": false
}
Это сообщает Webpack:
Некоторые модули выполняют код при импорте:
import './styles.css';
или:
console.log('init');
Webpack не может удалить такие модули автоматически.
ESM поддерживает динамический импорт.
import('./admin')
.then(module => {
module.init();
});
Webpack интерпретирует это как точку разделения кода.
Результат:
Dynamic import всегда возвращает Promise:
const module = await import('./feature');
Webpack строит runtime-загрузчик чанков.
В реальных проектах часто смешиваются оба формата.
CommonJS:
module.exports = {
hello() {
console.log('hello');
}
};
ESM:
import lib from './lib';
lib.hello();
Webpack создаёт совместимый слой interoperability.
ESM:
export default function () {
console.log('hello');
}
CommonJS:
const mod = require('./module');
Результат может содержать:
mod.default
Поскольку ESM использует default export как отдельную сущность.
Webpack часто добавляет специальный флаг:
exports.__esModule = true;
Он нужен для определения:
Внутри Webpack ES-модули исторически назывались Harmony Modules.
В runtime можно встретить:
__webpack_require__.d
или:
__webpack_require__.r
Эти функции помогают:
UMD (Universal Module Definition) создавался как способ поддержки:
Типичный UMD-шаблон:
(function (root, factory) {
if (typeof exports === 'object' && typeof module === 'object') {
module.exports = factory();
}
else if (typeof define === 'function' && define.amd) {
define([], factory);
}
else {
root.MyLibrary = factory();
}
})(this, function () {
return {
hello() {
console.log('hello');
}
};
});
Webpack анализирует шаблоны:
module.exportsdefine.amdПосле этого модуль включается в граф зависимостей.
Webpack умеет генерировать UMD-бандлы.
Пример:
module.exports = {
output: {
filename: 'library.js',
library: 'MyLibrary',
libraryTarget: 'umd'
}
};
Результат:
libraryTarget: 'commonjs'
Экспорт:
module.exports = ...
libraryTarget: 'commonjs2'
Использует полноценный module.exports.
libraryTarget: 'amd'
Формирует AMD-модуль.
libraryTarget: 'umd'
Создаёт универсальный формат.
libraryTarget: 'var'
Экспортирует библиотеку как глобальную переменную.
Webpack поддерживает генерацию настоящих ES-модулей.
Конфигурация:
module.exports = {
experiments: {
outputModule: true
},
output: {
module: true
}
};
Результат:
import/export;Флаг включает экспериментальный режим ESM-output.
Особенности:
<script type="module">.Webpack умеет исключать зависимости из сборки.
Пример:
module.exports = {
externals: {
react: 'React'
}
};
В этом случае:
Позволяет определить формат внешней зависимости.
Пример:
module.exports = {
externalsType: 'commonjs'
};
Возможные значения:
Babel способен преобразовывать ESM в CommonJS.
Пример:
import { sum } from './math';
После трансформации:
const math = require('./math');
Если Babel преобразует ESM слишком рано:
Правильная настройка:
{
modules: false
}
Webpack позволяет управлять режимом загрузки.
Пример:
import(
/* webpackMode: "lazy" */
'./admin'
);
Режимы:
import(
/* webpackMode: "eager" */
'./module'
);
Модуль включается в основной bundle без отдельного chunk.
Стандартное поведение.
Создаётся отдельный chunk.
Webpack пытается использовать уже загруженный модуль.
Новый chunk не создаётся.
Несколько динамических импортов объединяются в один chunk.
Webpack поддерживает AMD.
Пример:
define(['dep'], function(dep) {
return {
hello() {}
};
});
Однако в современных проектах AMD практически вытеснен ESM.
Webpack может взаимодействовать с SystemJS.
Пример output:
output: {
libraryTarget: 'system'
}
Используется редко, в основном:
{
"type": "module"
}
Node.js интерпретирует .js как ESM.
{
"type": "commonjs"
}
Файлы считаются CommonJS.
ES-модуль.
CommonJS-модуль.
Зависит от type в package.json.
Webpack выбирает entry-файл пакета через
resolve.mainFields.
Пример:
resolve: {
mainFields: ['browser', 'module', 'main']
}
Многие библиотеки публикуют:
{
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js"
}
Webpack предпочитает ESM-версию.
Причины:
Современные пакеты используют exports map.
{
"exports": {
"import": "./esm/index.js",
"require": "./cjs/index.js"
}
}
Webpack выбирает нужный вариант автоматически.
Проблема возникает, когда пакет одновременно содержит:
Возможна ситуация двойной загрузки:
require('pkg')
и:
import 'pkg'
Могут создать два независимых экземпляра библиотеки.
Для production-сборок предпочтительнее:
Наименее оптимальны:
Webpack генерирует собственный runtime.
Основные задачи runtime:
Webpack использует внутренний cache:
var __webpack_module_cache__ = {};
После первого выполнения модуль сохраняется.
Повторный импорт использует кэшированный экземпляр.
Поведение совпадает с CommonJS:
console.log('init');
Будет выполнено только при первой загрузке модуля.
ESM создаёт namespace object.
Пример:
import * as utils from './utils';
Webpack генерирует объект с live bindings.
Пример:
export default class UserService {}
Импорт:
import UserService from './UserService';
Webpack хранит default export отдельно от named exports.
export const API_URL = '/api';
export function request() {}
Импорт:
import { API_URL, request } from './config';
Webpack может анализировать использование каждого экспорта отдельно.
Пример:
export * from './math';
или:
export { sum } from './math';
Webpack объединяет цепочки экспортов в единый граф зависимостей.
Webpack поддерживает циклические зависимости.
Пример:
// a.js
import { b } from './b';
// b.js
import { a } from './a';
Но возможны:
Экспортируется текущее состояние объекта.
Используются live bindings.
Поведение отличается при раннем обращении к значениям.
В production Webpack активирует:
Все эти оптимизации особенно эффективны именно для ESM.
Webpack объединяет модули в единый scope.
Технология называется:
Module Concatenation
Работает лучше со статическими ES-модулями.
ES-модули дают Webpack максимум информации:
Это позволяет: