Цзо Линь, фронтенд-разработчик отдела передовых технологий WeDoctor. На волне Интернета любите жизнь и технологии.
предисловие
Если вы читали эту статью в серии сводок -Куда делся бесполезный код? Tree-shaking свертки проекта по снижению веса, то вы должны быть знакомы с встряхиванием деревьев. Если вы не знакомы со знаниями, связанными с встряхиванием деревьев, нажмите на статью выше и потратьте 5 минут, чтобы понять: что такое встряхивание деревьев.
Как всем известно, он не поддерживает встряску дерева, он также реализует встряску дерева в своей версии 2.x, любопытство и накопитель с самого начала реализовали встряску дерева, а WebPack должен увидеть Rollup. эффект, только тот же, значит принцип TREE-Shaking тот же?
Из-за таких сомнений передо мной лежит эта статья.
Механизм реализации встряхивания дерева
После беглого просмотра официальной документации и статей я обнаружил, что для веб-пакета существует несколько способов реализовать встряхивание дерева! Однако оба они отличаются от свертки.
Настройка и использование webpack в первые дни были непростыми, поэтому инженеры по настройке webpack пошутили, что хотя конфигурация webpack сейчас сильно упрощена, webpack4 тоже претендует на 0 конфигурацию, но если он включает в себя сложные и комплексные функции упаковки, не 0 конфигурация может быть реализована. Очень полезно понять принцип его работы и настройки.Далее давайте разберемся с принципом реализации tree-shaking в webpack.
Tree-shaking -- rollup VS Webpack
-
rollup
Он анализирует поток программы во время компиляции и упаковки.Благодаря статическим модулям ES6 (экспорт и импорт не могут быть изменены во время выполнения), мы можем определить, какой код нам нужен при упаковке. -
webpack
При упаковке он может только пометить неиспользуемый код, не удаляя его, и на самом деле это такие инструменты, как UglifyJS, babili и terser, для сжатия кода, которые идентифицируют неиспользуемый код и завершают встряску дерева. Проще говоря, инструмент минификации считывает пакет webpack и удаляет неиспользуемый код из пакета перед минификацией.
我们提到了标记未使用代码,也提到了 UglifyJS、babili、terser 等压缩工具,那么 webpack 与压缩工具是怎么实现 tree-shaking 的呢?先来了解下 webpack 中实现 tree-shaking 的前世今生吧!
Webpack реализует 3 этапа Tree-shaking
Этап 1: UglifyJS
код разметки webpack + babel транспилирует ES5 --> UglifyJS сжимает и удаляет бесполезный код Чтобы узнать о самой ранней версии Webpack, реализующей встряхивание дерева, вы можете обратиться к этой статье.Как использовать встряхивание дерева с Webpack 2, у Наггетс есть и переведенная версия, конечно, если не хотите тратить время на археологию, то можете прочитать и следующее краткое содержание:
- UGLifyjs не поддерживает ES6 и выше, вам нужно использовать Babel, чтобы скомпилировать код до ES5, а затем использовать UGLifysjs, чтобы очистить бесполезный код;
- Скомпилируйте код в ES5 через Babel, но сделайте так, чтобы модули ES6 не зависели от пресетов Babel: настройте пресеты Babel, чтобы не преобразовывать модули, и соответствующим образом настройте конфигурацию плагинов Webpack;
- Чтобы избежать побочных эффектов, пометьте его как чистый (без побочных эффектов), чтобы UglifyJS мог с этим справиться, в основном процесс компиляции веб-пакета предотвращает встряхивание дерева классов, он работает только с функциями, а позже, поддерживая назначения после компиляции классов. Маркировка как @ __PURE__ решил проблему.
// .babelrc
{
"presets": [
["env", {
"loose": true, // 宽松模式
"modules": false // 不转换 module,保持 ES6 语法
}]
]
}
// webpack.config.js
module: {
rules: [
{ test: /\.js$/, loader: 'babel-loader' }
]
},
plugins: [
new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: true
},
output: {
comments: false
},
sourceMap: false
})
]
Второй этап: BabelMinify
код разметки webpack -->Babili(например, BabelMinify) для сжатия и удаления бесполезного кода Позже Babili был переименован в BabelMinify, инструмент сжатия кода, основанный на Babel, и Babel уже понял новый синтаксис через наш парсер Babylon, и в то же время интегрировал функцию сжатия UglifyJS в babili, которая по сути такая же, как функция UglifyJS. , но с помощью плагина babili не нужно переводить, а напрямую сжимать, делая код меньше.
Как правило, есть два способа использовать Babili для замены uglify: плагин Babili и предустановка babel-loader. существуетофициальная документацияИ последнее замечание: Babel Minify лучше всего подходит для новейших браузеров (с полной поддержкой ES6+), а также может использоваться с обычными пресетами Babel es2015 для предварительной компиляции кода.
Использование babel-loader в webpack с последующим импортом minify в качестве предустановки будет работать быстрее, чем использование плагина BabelMinifyWebpackPlugin (описано далее). Потому что размер файла, обрабатываемого babel-minify, будет меньше.
Третий этап: Терсер
код разметки webpack --> Terser сжимает и удаляет бесполезный код (имеется встроенный webpack5) terser — это набор инструментов для синтаксического анализа и сжатия/управления JavaScript для ES6+. если ты видел этоissue, вы будете знать, что все больше и больше людей отказываются от уродства и бросаются в объятия терсера, и причина очень ясна:
- uglify больше не поддерживается и не поддерживает синтаксис ES6+.
- По умолчанию webpack имеет встроенную конфигурацию плагина terser для сжатия кода.
Что касается побочных эффектов, то возможность обнаружения неиспользуемых модулей была расширена по сравнению с официальной версией webpack 4, а свойство «sideEffects» файла package.json используется в качестве маркера для предоставления компилятору подсказок, указывающих, какие файлы в проекте "чистый (чистые модули ES2015)", by Это безопасно удаляет неиспользуемые части файла.
При работе с webpack4 вам придется вручную настраивать плагин сжатия, но последний webpack5 имеет встроенную реализацию tree-shaking! Реализуйте древовидную тряску в продакшене без настройки!
Процесс встряхивания дерева в Webpack
Код разметки веб-пакета
В общем, webpack помечает код, в основном операторы импорта и экспорта, в 3 категории:
- Весь импорт отмечен как
/* harmony import */
- Все используемые экспорты отмечены как
/* harmony export ([type]) */
,в[type]
Относится к внутренним компонентам веб-пакета, может быть обязательным, неизменяемым и т. д. - Неиспользованные экспорты отмечены как
/* unused harmony export [FuncName] */
,в[FuncName]
имя метода для экспорта
Прежде всего, нам нужно знать, что для нормального запуска бизнес-проектов Webpack необходимо интегрировать бизнес-код, написанный разработчиками, а также поддержку и развертывание этих бизнес-кодов.Время выполнения一并打包到产物(bundle)中。 落到 Webpack 源码实现上,运行时的生成逻辑可以划分为打包阶段中的两个步骤:
- Коллекция зависимостей: просмотрите модули кода и соберите зависимости функций модулей, чтобы определить список зависимостей всего проекта от среды выполнения Webpack;
- генерировать
Отметьте все импорты в среде выполнения:
const exportsType = module.getExportsType(
chunkGraph.moduleGraph,
originModule.buildMeta.strictHarmonyModule
);
runtimeRequirements.add(RuntimeGlobals.require);
const importContent = `/* harmony import */ ${optDeclaration}${importVar} = __webpack_require__(${moduleId});\n`;
// 动态导入语法分析
if (exportsType === "dynamic") {
runtimeRequirements.add(RuntimeGlobals.compatGetDefaultExport);
return [
importContent, // 标记/* harmony import */
`/* harmony import */ ${optDeclaration}${importVar}_default = /*#__PURE__*/${RuntimeGlobals.compatGetDefaultExport}(${importVar});\n` // 通过 /*#__PURE__*/ 注释可以告诉 webpack 一个函数调用是无副作用的
]; // 返回 import 语句和 compat 语句
}
Отметьте все используемые и неиспользуемые экспорты в среде выполнения:
// 在运行时状态定义 property getters
generate() {
const { runtimeTemplate } = this.compilation;
const fn = RuntimeGlobals.definePropertyGetters;
return Template.asString([
"// define getter functions for harmony exports",
`${fn} = ${runtimeTemplate.basicFunction("exports, definition", [
`for(var key in definition) {`,
Template.indent([
`if(${RuntimeGlobals.hasOwnProperty}(definition, key) && !${RuntimeGlobals.hasOwnProperty}(exports, key)) {`,
Template.indent([
"Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });"
]),
"}"
]),
"}"
])};`
]);
}
// 输入为 generate 上下文
getContent({ runtimeTemplate, runtimeRequirements }) {
runtimeRequirements.add(RuntimeGlobals.exports);
runtimeRequirements.add(RuntimeGlobals.definePropertyGetters);
const unusedPart =
this.unusedExports.size > 1
? `/* unused harmony exports ${joinIterableWithComma(
this.unusedExports
)} */\n`
: this.unusedExports.size > 0
? `/* unused harmony export ${first(this.unusedExports)} */\n`
: "";
const definitions = [];
for (const [key, value] of this.exportMap) {
definitions.push(
`\n/* harmony export */ ${JSON.stringify(
key
)}: ${runtimeTemplate.returningFunction(value)}`
);
}
const definePart =
this.exportMap.size > 0
? `/* harmony export */ ${RuntimeGlobals.definePropertyGetters}(${
this.exportsArgument
}, {${definitions.join(",")}\n/* harmony export */ });\n`
: "";
return `${definePart}${unusedPart}`; // 作为初始化代码包含的源代码
}
}
Сжать и очистить Дафа
UglifyJS
Возьмите UGLICEITYS в качестве примера, UGLifysjs является интерпретатором JS, министерстве, компрессор, элементарий красотки (парсер, министерство, компрессор или красивый инструментарий). Для конкретного введения вы можете проверить UGLIPYSкитайское руководство.
Если вы не хотите просматривать такой длинный документ, вы можете прочитать краткое изложение параметров конфигурации сжатия, которые прямо указывают на встряхивание дерева!
-
dead_code
-- Удалить код, на который нет ссылок // Это выглядит знакомо! Бесполезный код! -
drop_debugger
-- удалить отладчик -
unused
-- Уничтожить неиспользуемые функции и переменные. (Если не установлено "keep_assign", простое прямое присвоение переменной также не считается ссылкой.) -
toplevel
- Убейте функции Unreferection («Функты») и / или переменные («vars») в объеме верхнего уровня (по умолчанию неверно, True означает, что все переменные функции убиты) -
warnings
-- При удалении бесполезного кода отображать предупреждение // Это мило~ -
pure_getters
-- По умолчанию false.Если вы передадите true, UglifyJS будет считать, что ссылки на свойства объекта (например, foo.bar или foo["bar"] ) не имеют побочных эффектов функции. -
pure_funcs
-- По умолчанию null.Вы можете передать массив имен, и UglifyJS будет считать, что эти функции не имеют побочных эффектов.
Возьмите каштан:
plugins: [
new UglifyJSPlugin({
uglifyOptions: {
compress: {
// 这样该函数会被认为没有函数副作用,整个声明会被废弃。在目前的执行情况下,会增加开销(压缩会变慢)。
pure_funcs: ['Math.floor']
}
}
})
],
- @PURE/ or /#PURE/ Комментарий, функция будет помечена как чистая. E.G /@PURE/foo();
На самом деле, среди стольких конфигураций сжатия, в дополнение к ручной настройке для решения проблемы с побочными эффектами, просто использование конфигурации по умолчанию UglifyJS может удалить бесполезный код разметки для достижения встряхивания дерева.
terser
В примере terser, terser для ES6+ — это интерпретатор JavaScript и комплект манглеров/компрессоров. Подробности можно посмотретьофициальная документация. 虽然没有中文文档,但是一眼扫过去也可以看出来配置参数和 UglifyJS 没有太大区别。当然很明显地多了一些参数:
-
arrows
- Если преобразование короче, литеральные методы класса и объекта также будут преобразованы в выражение стрелки. -
ecma
-- Включение опции сжатия с ES2015 или более поздней версии для преобразования кода ES5 в меньший эквивалент ES6+.
Видимо потому, что terser поддерживает синтаксис ES6+, что является одним из его преимуществ перед UglifyJS.
Производительность сжатия ПК
В настоящее время Webpack обновлен до версии 5.X, а плагин terser встроен по умолчанию и не нуждается в настройке, хотя по умолчанию используется в производственной среде.TerserPluginА также лучший выбор для сжатия кода, но есть и другие варианты. Подождите, а наша тема не потрясающая? Как вдруг в дороге избавиться от компрессионного инструмента...
По сути, это инструмент сжатия, который реализует встряхивание дерева, поэтому кажется, что в производительности инструмента сжатия нет ничего плохого!
СОВЕТ: Сжатие происходит в производственной среде, поэтому древовидная тряска может выполняться только в производственной среде. Следующие 3 настраиваемых плагина требуют версии веб-пакета не ниже V4+.
UglifyjsWebpackPlugin
Основное использование также проще:
// webpack.config.js
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
optimization: {
minimizer: [new UglifyJsPlugin()],
},
};
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
plugins: [
new UglifyJsPlugin()
]
}
BabelMinifyWebpackPlugin
Как правило, есть два способа использовать babili для замены UglifyJS: плагин Babili и предустановка babel-loader.
Babili плагин
// webpack.config.js
const MinifyPlugin = require("babel-minify-webpack-plugin");
module.exports = {
plugins: [
new MinifyPlugin(minifyOpts, pluginOpts)
]
}
существуетофициальная документацияИ последнее замечание: Babel Minify лучше всего подходит для новейших браузеров (с полной поддержкой ES6+), а также может использоваться с обычными пресетами Babel es2015 для предварительной компиляции кода.
在 webpack 中使用 babel-loader,然后再引入 minify 作为一个 preset 会比直接使用 BabelMinifyWebpackPlugin 插件执行得更快。 Потому что размер файла, обрабатываемого babel-minify, будет меньше.
I.BABELRC настроен следующим образом:
{
"presets": ["es2015"],
"env": {
"production": {
"presets": ["minify"]
}
}
}
Но наличие плагина BabelMinifyWebpackPlugin должно сыграть свою незаменимую роль:
- загрузчик webpack работает с одним файлом, предустановка minify, поскольку загрузчик webpack будет обрабатывать каждый файл так, как если бы он выполнялся непосредственно в глобальной области браузера (по умолчанию), и не будет оптимизировать некоторый контент в области верхнего уровня;
- Когда исключение node_modules не выполняется через babel-loader, оптимизация babel-minify не применяется к исключенным файлам;
- При использовании babel-loader код, сгенерированный webpack для модульной системы, не оптимизируется babel-minify;
- Плагин WebPack может работать на всей выходе от кусочки / расслоения и может оптимизировать весь пункт.
Используя первый метод:
TerserWebpackPlugin
Как и плагины uglify и babelMinify, плагин terser прост в настройке и использовании.
webpack.config.js
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [new TerserPlugin()],
},
};
Кажется, что результат соответствует ожиданиям, а поскольку сам код файла у меня небольшой по размеру, преимущество размера сжатого пакета не очевидно, но время сжатия все же относительно очевидно.
Сравнение производительности официальных данных
приходи сновадокументация bableMinifyСравнение приведено в:
Пакет реагирует:Упаковка:
Пакет Лодаш:Пакет 3.js:
резюме
Давайте сначала посмотримОбласть выпускаПользователи сети, как сказать:
大意是 terser 压缩性能相较于 uglify 提升了三倍! Хороший!
大意是说:鉴于 terser-webpack-plugin 得到维护并且有更多的正确性修复,绝对是首选 -- 即使没有性能改进(事实上还是有所改进的),也值得切换。 最后一句话总结:webpack 打包 + terser 压缩才是最终的不二之选!webpack5 内置 terser 说明了一切!
Работа с побочными эффектами
«Побочный эффект» определяется как код, который выполняет особое поведение при импорте, а не просто предоставляет экспорт или экспорт. Например, полифиллы, которые влияют на глобальную область видимости и, как правило, не обеспечивают экспорт.
О побочных эффектах накопительного также было введено. Некоторые импортированные модули, пока они введены, будут иметь важное влияние на приложение. Например, глобальные таблицы стилей, JavaScript или набор глобальных файлов конфигурации являются хорошим примером.
Параметры конфигурации
- true — это значение по умолчанию, если не указано другое значение. Это означает, что все файлы имеют побочные эффекты, то есть ни один из них не может трясти дерево.
- false сообщает Webpack, что никакие файлы не имеют побочных эффектов и что все файлы могут трястись деревом.
- Третье значение […] — это массив путей к файлам. Он сообщает webpack, что ни один из ваших файлов не имеет побочных эффектов, кроме включенных в массив. Следовательно, все файлы, кроме указанных, безопасны для древовидной тряски.
{
"name": "your-project",
"sideEffects": false
// "sideEffects": [ // 数组方式支持相关文件的相对路径、绝对路径和 glob 模式
// "./src/some-side-effectful-file.js",
// "*.css"
//]
}
Каждый проект должен быть STACTEFFECT имуществом на массив ложных или файловых путей, если ваш код имеет некоторые побочные эффекты, его можно изменить, чтобы обеспечить массив на работе, необходимо правильно настроить метки STIPEFECTS.
тег в коде
в состоянии пройти /#PUREАннотация / сообщает webpack, что вызов функции не имеет побочных эффектов. Используется для пометки функций как свободных от побочных эффектов (чистых) перед их вызовом.
Входные параметры, переданные в функцию, не могут быть отмечены комментарием прямо сейчас, и каждую метку нужно отделить.
Если неиспользуемая переменная определяет начальное значение, которое считается свободным от побочных эффектов (чистым), оно будет помечено как мертвый код, не будет выполняться и будет очищено инструментом минификации. когдаoptimization.innerGraphДля этого поведения установлено значение true, оно будет открыто, а в файле webpack5.xoptimization.innerGraphПо умолчаниюtrue
.
- Прежде всего, он будет включен только в производственном режиме.Больше оптимизаций, включая код сжатия и встряхивание дерева, о которых мы говорили в этой статье;
- Используйте синтаксис модуля ES2015 (I.E. Импорт и экспорт);
- Убедитесь, что ни один компилятор не преобразует синтаксис модуля ES2015 в CommonJS, установите для модулей в пресетах значение false, чтобы запретить Babel компилировать код модуля.
Суммировать
- Если вы разрабатываете библиотеку JavaScript, используйте rollup! И укажите версию модуля ES6, адрес файла записи установлен в поле модуля package.json;
- Использование веб-пакета, даже если это старая версия, может отдать приоритет более краткому плагину в качестве инструмента сжатия;
- Чтобы избежать побочных эффектов, старайтесь не писать код с побочными эффектами и используйте синтаксис модуля ES2015;
- В файле Project Package.json добавьте ввод STILEFEFTEFCECTS, установите недвижимость SITEEFFECTS в False, или пропустите /#PURE/ Обратите внимание, что вынуждены удалить некоторый код, который не генерирует побочные эффекты;
- В Webpack также представлен дополнительный инструмент минимизации (например, Terser), который удаляет мертвый код.