6. — React в бандле весит больше чем lib/react.js .
— Несколько версий lodash или underscore .
— Moment.js грузит 100+ локалей.
— Непонятные полифилы.
— Не работает tree shaking.
— Изменяется кеш для неизменившихся чанков.
— И так далее.
Проблемы
8. — CommonJS.
— Резолв путей до файлов.
— Устройство бандла изнутри.
— Глобальные константы и DefinePlugin.
— UglifyJS и dead code elimination.
— ES6 modules и tree shaking.
— Выделение чанков и асинхронная подгрузка.
— Анализ результатов сборки.
План
10. // module.js
const one = 1;
exports.two = 2;
module.exports = { one };
// othermodule.js
const m = require('module');
console.log(m.one);
console.log(m.two); // ошибка
CommonJS
11. — JS-файл с переменными require , exports , module .
— this равно exports .
— exports по умолчанию равно {} .
— Чтобы мы могли использовать npm-модули в браузере, нам нужно
эмулировать в браузере это поведение.
CommonJS
13. — / , ./ , ../ – как в файловой системе.
— Библиотеки:
— папка с именем модуля в node_modules ,
— если нет в node_modules , ищем рекурсивно выше по дереву,
— Добавляем в конец пути .js или /index.js .
Схема резолва путей в node.js
Схема резолва путей в webpack 2
Резолв путей в require()
22. 1. Ищет инициированный модуль в кэше.
2. Создает заглушку и добавляет её в массив.
3. Выполняет код модуля с this равным module.exports .
Что делает __webpack_require__
23. modules[moduleId].call(
module.exports , // exports будет this для модуля
module,
module.exports , // по-умолчанию равно {}
__webpack_require__
);
Что делает __webpack_require__
25. 1. Ищет инициированный модуль в кэше.
2. Создает заглушку и добавляет её в массив.
3. Выполняет код модуля с this равным module.exports .
4. Возвращает module.exports .
Что делает __webpack_require__
26. const p = '4';
const m = require("./module" + p );
Require в бандле:
const m = __webpack_require__(3)("./module" + p );
Как работает подключение
модуля по маске
27. var map = {
" ./module ": 1, " ./module.js ": 1,
" ./module2 ": 0, " ./module2.js ": 0,
}
function webpackContext(...) {/* код выбора модуля */};
module.exports = webpackContext;
Справка по Dependency management
Модуль резолва пути в бандле
28. require('./locale/' + name); // 118 локалей
Решение:
new webpack.ContextReplacementPlugin(
/moment[/]locale$/,
/ en|ru /
)
moment/moment.js
ПРОБЛЕМА 2
32. const version = "1.0.1" ;
if ( false ) {
const module = require('module'); // не импортирован
// что-то делаем
}
Преобразованный код
33. // До преобразования
const { NODE_ENV } = process.env ;
// После преобразования
var _process$env = process.env,
NODE_ENV = _process$env.NODE_ENV;
Ссылка на песочницу с Babel
Неточная строка для замены
ПРОБЛЕМА 3
34. var _process$env = process.env,
NODE_ENV = "production";
if (NODE_ENV !== "production") {
const m = __webpack_require__(0) ;
}
Неточная строка для замены
ПРОБЛЕМА 3
36. process – стандартная переменная в node.js, webpack её заполифилит
для совместимости.
Многие другие переменные из node.js тоже будут заменены.
Полифилы node.js
ПРОБЛЕМА 4
48. var _process$env = process.env,
NODE_ENV = "production";
if ( NODE_ENV !== "production" ) {
const m = __webpack_require__(0);
}
Переменные в условиях
ПРОБЛЕМА 6
50. // CommonJS
const m = require('./m');
exports.one = 1;
module.exports = 2;
// ES Modules
import m from './m';
export const one = 1;
export default 2;
Импорты и экспорты обязательно иммутабельны.
Документация по import, export
Отличия от CommonJS
51. В теории webpack может точно определить, что используется у нас
в приложении, и помечать их соответственно.
На практике все несколько сложнее.
Tree shaking
53. // module.js
export const one = 1;
export const two = 2;
// index.js
import { one, two } from './module';
console.log(one);
Пример импорта и экспорта
55. const one = 1;
__webpack_exports__["a"] = one;
const two = 2;
// нет __webpack_exports__ за ненадобностью
module.js в бандле
56. import module3 from './folder/module-3';
// импорт из method попадет в сборку
export const method = () => module3 ;
// export.default пометится как используемый
export default 1223;
Работает только с экспортами
ПРОБЛЕМА 7
57. Такой код импортирует всю библиотеку:
import { method } from 'lodash-es';
Решение:
— import { method } from 'lodash-es/method'
— babel-plugin-lodash
Работает только с экспортами
ПРОБЛЕМА 7
58. Выключите в Babel обработку modules , чтобы он не менял их
на require и exports :
{"presets": [
["latest", {
"es2015": { "modules": false }
}]
]}
Важно!
60. — Синхронные и асинхронные.
— В первый файл добавляется функция window["webpackJsonp"] .
— В следующих файлах вызывается функция webpackJsonp со списком
модулей и id модулей, которые надо запустить.
Чанки
61. webpackJsonp(
[0], // id чанка
[{ 22: function(...){...} }], // массив модулей
[12] // id модулей для запуска
]);
Чанки
62. — Синхронные и асинхронные.
— В первый файл добавляется функция window["webpackJsonp"] .
— В следующих файлах вызывается функция webpackJsonp со списком
модулей и id модулей, которые надо запустить.
— Все модули попадают в общий массив и используются оттуда.
Чанки
68. import() -чанки игнорируются CommonsChunkPlugin .
Чтобы не игнорировались, добавьте:
new webpack.optimize.CommonsChunkPlugin({
...,
children: true
})
Вложенные чанки
ПРОБЛЕМА 8
70. Пример c node_modules не работает для кэша:
1. При добавлении файлов меняются индексы;
2. Код загрузки и инициализации живет в первом файле:
— меняется стартовый индекс,
— меняются ссылки на чанки.
Изменяющиеся индексы
ПРОБЛЕМА 9
71. (function(modules) {
var installedModules = {};
function __webpack_require__(moduleId) {
/* код инициализации */
}
// ...
return __webpack_require__(1) ; // корневой файл
})([ /* массив модулей */ ]);
Изменяющиеся индексы
ПРОБЛЕМА 9
72. Создание имени чанка при наличии [chunkhash] :
script.src = chunkId + "." + {
"0":" 588290cc688bace070b6 ",
"1":" 5966f9b147ab01778e34 ",
}[chunkId] + ".js";
Изменяющиеся индексы
ПРОБЛЕМА 9
73. — NamedModulesPlugin() – меняет индексы на пути до файла.
— HashedModuleIdsPlugin() — меняет индексы на хэш содержимого.
Фиксируем имена модулей
77. Строит treemap бандлов. Удобно проверять, не попали ли в бандл:
— Две версии одной библиотеки.
— Копии библиотеки в разных чанках.
— Бибиотеки, которые должны были вырезаться по условию.
— Непредвиденные зависимости у библиотек.
— Просто большие файлы.
webpack-bundle-analyzer
79. Показывает отношения между файлами в графе — кто на кого
ссылается, кто кого добавил в сборку. Удобно использовать, чтобы
понять:
— Кто именно использует файл.
— Кто именно подключил библиотеку.
webpack-runtime-analyzer
81. — Сделайте пустой бандл и посмотрите содержимое, там 40 строчек.
— Не бойтесь ходить в исходники и смотреть что получилось в коде.
— После добавления библиотек всегда запускайте анализатор банда
и смотрите что он с собой притащил.
— После добавления чанков проверяйте их содержимое.
Итого
82. Алексей Иванов, Злые марсиане
Twitter: @iadramelk
Внутреннее устройство
и оптимизация бандла webpack