представлять
- Оригинальный адрес: introduction
- Оригинальный автор: Addy Osmani
- Адрес перевода: представлять
- Переводчик: Ян Мэн
- Корректор: Чжоу Вэнькан,Ян Цзянь
Современные веб-приложения часто используютупаковочный инструментдля создания производственных «упакованных» файлов (скриптов, стилей и т. д.), которые проходят черезоптимизацияа такжекомпрессияПосле этого он может быть загружен пользователями очень быстро. существуетоптимизация веб-производительности с помощью веб-пакетаВ этой серии статей мы расскажем, как использовать webpack для эффективной оптимизации ресурсов сайта. Это поможет пользователям быстрее загружать сайт и взаимодействовать с ним.
webpack — один из самых популярных инструментов для создания пакетов. Мы можем воспользоваться его возможностями для оптимизации кода,разделение кодаСкрипты можно разделить на основные и второстепенные части, а бесполезный код удалить (это всего лишь небольшой пример оптимизации), гарантируя, что ваше приложение будет иметь минимальные накладные расходы на сеть и затраты на обработку.
Сьюзи ЛуРазделение кода в Bundle Buddyвдохновение.
⭐️ Примечание:Мы создали приложение для упражнений, чтобы продемонстрировать, о чем идет речь в этой статье. Постарайтесь максимально использовать эти навыки:
webpack-training-project
Начнем оптимизацию с JavaScript, одного из самых ресурсоемких приложений в современных приложениях.
- Уменьшите размер внешних ресурсов
- Эффективно используйте постоянный кеш
- Приложения для мониторинга и анализа
- В заключение
Часть 1: Уменьшение размера передних ресурсов
- Оригинальный адрес: decrease frontend size
- Оригинальный автор: Ivan Akulov
- Адрес перевода: Уменьшить размер передней части
- Переводчик: Ян Цзянь
- Корректор: грязь кун,Чжоу Вэнькан
Когда вы оптимизируете приложение, первое, что нужно сделать, это максимально уменьшить его размер. Вот как это сделать с помощью webpack.
Использовать производственный режим (только для webpack4)
представлен веб-пакет 4новыйmode
логотип. Вы можете установить этот флаг на'development'
или'production'
чтобы сообщить веб-пакету, что вы создаете его для определенной среды:
// webpack.config.js
module.exports = {
mode: 'production',
};
При создании приложения для производства убедитесь, что вы включилиproduction
модель. Это заставит веб-пакет включить свои оптимизации, такие как: уменьшение размера, удаление кода в библиотеке, который доступен только в режиме разработчика.так далее.
Расширенное чтение
включить свернуть
⭐️Уведомление:Большинство из них работают только с webpack 3. если тыРежим производства включен в веб-пакете 4, минимизация на уровне пакета уже включена — вам просто нужно включитьпараметры загрузчика.
Минимизация размера происходит, когда вы сжимаете свой код, удаляя лишние пробелы, сокращая имена переменных и т. д. Например это:
// 原来的代码
function map(array, iteratee) {
let index = -1;
const length = array == null ? 0 : array.length;
const result = new Array(length);
while (++index < length) {
result[index] = iteratee(array[index], index, array);
}
return result;
}
↓
// 最小化后的代码
function map(n,r){let t=-1;for(const a=null==n?0:n.length,l=Array(a);++t<a;)l[t]=r(n[t],t,n);return l}
webpack поддерживает два способа минимизации кода:минимизация на уровне пакетаа такжеспециальные параметры загрузчика. Их следует использовать одновременно.
Минимизация на уровне пакета
При компиляции функция минимизации на уровне пакета сжимает весь пакет. Вот как это работает:
-
Ваш код выглядит так:
// comments.js import './comments.css'; export function render(data, target) { console.log('Rendered!'); }
-
webpack примерно скомпилирует его примерно так:
// bundle.js (part of) "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony export (immutable) */ __webpack_exports__["render"] = render; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__comments_css__ = __webpack_require__(1); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__comments_css_js___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__comments_css__); function render(data, target) { console.log('Rendered!'); }
-
Минификатор будет примерно сжиматься следующим образом:
// 最小化过的 bundle.js (part of) "use strict";function t(e,n){console.log("Rendered!")} Object.defineProperty(n,"__esModule",{value:!0}),n.render=t;var o=r(1);r.n(o)
В вебпаке 4,Минимизация на уровне пакета включается автоматически — независимо от того, находится ли он в производственном режиме или нет. Он использует под капотомUglifyJS свернут. (Если вам нужно отключить минимизацию, просто используйте режим разработки или поставьтеoptimization.minimize
параметры установлены наfalse
. )
В веб-пакете 3,вам нужно использовать напрямуюПлагин UglifyJS. Этот плагин поставляется с webpack, добавьте его в конфиг.plugins
раздел для включения:
// webpack.config.js
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.optimize.UglifyJsPlugin(),
],
};
⭐️Уведомление:В веб-пакете 3 плагин UglifyJS не может компилировать код старше ES2015 (т.е. ES6). Это означает, что если ваш код использует классы, стрелочные функции или другие новые возможности языка, вы не можете скомпилировать их в код ES5, иначе плагин выдаст ошибку.
Если вам нужно скомпилировать (код), который содержит новый синтаксис, используйтеuglifyjs-webpack-pluginплагин. Это также плагин, который поставляется с веб-пакетом, но версия обновлена и может компилировать код ES2015+.
параметры загрузчика
Второй способ минимизировать код — это параметры, специфичные для загрузчика (что такое загрузчик). С опцией загрузчика вы можете сжимать то, что минимизатор не может минимизировать. Например, когда вы используетеcss-loader
При импорте файла CSS файл компилируется в строку:
/* comments.css */
.comment {
color: black;
}
↓
// 最小化后的 bundle.js (部分代码)
exports=module.exports=__webpack_require__(1)(),
exports.push([module.i,".comment {\r\n color: black;\r\n}",""]);
Минификатор не может сжимать код, потому что это строка. Чтобы минимизировать содержимое файла, нам нужно настроить загрузчик следующим образом:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{ loader: 'css-loader', options: { minimize: true } },
],
},
],
},
};
Расширенное чтение
- Документация по плагину UglifyJs
- Другие популярные минификаторы:Babel Minify,Google Closure Compiler
уточнитьNODE_ENV=production
⭐️Уведомление:Это работает только с веб-пакетом 3. если тыИспользуйте webpack 4 в рабочем режиме,
NODE_ENV=production
Оптимизация включена — этот раздел можно пропустить.
Еще один способ уменьшить размер интерфейса — включить в свой кодNODE_ENV
переменная средыУстановить какproduction
.
библиотека будет читатьNODE_ENV
переменные, чтобы определить, в каком режиме они должны работать — в разработке или производстве. Некоторые библиотеки ведут себя по-разному в зависимости от этой переменной. Например, когдаNODE_ENV
не установленоproduction
, Vue.js выполнит дополнительную проверку и выведет предупреждение:
// vue/dist/vue.runtime.esm.js
// …
if (process.env.NODE_ENV !== 'production') {
warn('props must be strings when using array syntax.');
}
// …
React ведет себя аналогично — загружает dev-сборку с предупреждением:
// react/index.js
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react.production.min.js');
} else {
module.exports = require('./cjs/react.development.js');
}
// react/cjs/react.development.js
// …
warning$3(
componentClass.getDefaultProps.isReactClassApproved,
'getDefaultProps is only used on classic React.createClass ' +
'definitions. Use a static property named `defaultProps` instead.'
);
// …
Эти проверки и предупреждения обычно не нужны в производственной среде, но они все еще существуют в коде и увеличивают размер библиотеки.В вебпаке 4,добавлениемoptimization.nodeEnv: 'production'
вариант их удаления:
// webpack.config.js (基于 webpack 4)
module.exports = {
optimization: {
nodeEnv: 'production',
minimize: true,
},
};
В веб-пакете 3,затем используйтеDefinePlugin
заменить:
// webpack.config.js (基于 webpack 3)
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"',
}),
new webpack.optimize.UglifyJsPlugin(),
],
};
optimization.nodeEnv
варианты иDefinePlugin
Работают так же — заменяют все исполняемые process.env.NODE_ENV на определенное значение. С приведенной выше конфигурацией:
-
Webpack обернет все существующие
process.env.NODE_ENV
заменить"production"
:// vue/dist/vue.runtime.esm.js if (typeof val === 'string') { name = camelize(val); res[name] = { type: null }; } else if (process.env.NODE_ENV !== 'production') { warn('props must be strings when using array syntax.'); }
↓
// vue/dist/vue.runtime.esm.js if (typeof val === 'string') { name = camelize(val); res[name] = { type: null }; } else if ("production" !== 'production') { warn('props must be strings when using array syntax.'); }
-
потомminifierУдалит все как
if
такая ветка - потому что"production" !== 'production'
Всегда неправильно, плагин понимает, что код в этих ветках никогда не будет выполнен:// vue/dist/vue.runtime.esm.js if (typeof val === 'string') { name = camelize(val); res[name] = { type: null }; } else if ("production" !== 'production') { warn('props must be strings when using array syntax.'); }
↓
// vue/dist/vue.runtime.esm.js (without minification) if (typeof val === 'string') { name = camelize(val); res[name] = { type: null }; }
Расширенное чтение
- Что такое «переменные среды»
- о:
DefinePlugin
,EnvironmentPlugin
Документация по веб-пакету
Использование модулей ES (модуль)
Другой способ уменьшить размер интерфейса — использоватьмодуль ЕС.
Когда вы используете модули ES, веб-пакет может выполнять встряску деревьев. Tree-shaking — это когда сборщик обходит все дерево зависимостей, проверяет, какие зависимости используются, и удаляет бесполезные. Итак, если вы используете синтаксис модуля ES, webpack может удалить неиспользуемый код:
-
Вы написали файл с несколькими экспортами, но приложение использует только один из них:
// comments.js export const render = () => { return 'Rendered!'; }; export const commentRestEndpoint = '/rest/comments'; // index.js import { render } from './comments.js'; render();
-
вебпак понимает
commentRestEndpoint
Не используется и не будет генерировать отдельный экспорт в бандле:// bundle.js (和 comments.js 有关联的部分) (function(module, __webpack_exports__, __webpack_require__) { "use strict"; const render = () => { return 'Rendered!'; }; /* harmony export (immutable) */ __webpack_exports__["a"] = render; const commentRestEndpoint = '/rest/comments'; /* unused harmony export commentRestEndpoint */ })
-
minifierУдалить неиспользуемые переменные:
// bundle.js (part that corresponds to comments.js) (function(n,e){"use strict";var r=function(){return"Rendered!"};e.b=r})
Это справедливо даже для библиотек, написанных с модулями ES.
⭐️Уведомление:В вебпаке тряска деревьев не будет работать без минификатора. Webpack удаляет только неиспользуемые переменные экспорта, а минификатор удаляет неиспользуемый код. Так что, если собрать бандл без минификатора, то сокращения не будет.
Однако вам не нужно специально использовать встроенный минификатор webpack (
UglifyJsPlugin
). Любой минификатор поддерживает удаление мертвого кода (например,Babel Minify pluginилиGoogle Closure Compiler plugin) буду работать.
❗предупреждать:Не компилируйте модули ES в модули CommonJS.
Если вы используете Babel
babel-preset-env
илиbabel-preset-es2015
, проверьте их предустановленные настройки. По умолчанию они будут использовать ESimport
а такжеexport
Перенесено в CommonJSrequire
а такжеmodule.exports
.пройти через{ modules: false }
Опцииотключить его.
То же самое с TypeScript: не забудьте добавить
tsconfig.json
установить в{ "compilerOptions": { "module": "es2015" } }
.
Расширенное чтение
- «Углубитесь в модули ES6»
- документация веб-пакетаО встряхивании деревьев
Оптимизация изображений
Изображение занимает размер страницыбольше половины. Хотя они не так критичны, как JavaScript (например, они не блокируют рендеринг), они все равно потребляют значительную часть полосы пропускания. Можно использовать в вебпакеurl-loader
,svg-url-loader
а такжеimage-webpack-loader
оптимизировать их.
url-loader
Встраивайте небольшие статические файлы в приложение. Без настройки ему необходимо передать файл, поместить его в скомпилированный пакет и вернуть URL-адрес файла. Однако, если мы укажемlimit
вариант, он закодирует файл меньше, чем без конфигурацииURL данных Base64и вернуть этот URL. Это встраивает изображение в код JavaScript и сохраняет HTTP-запрос:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(jpe?g|png|gif)$/,
loader: 'url-loader',
options: {
// 小于 10kB(10240字节)的内联文件
limit: 10 * 1024,
},
},
],
}
};
// index.js
import imageUrl from './image.png';
// → 如果图片小于 10kB, `imageUrl` 将包含
// 编码后的图片: 'data:image/png;base64,iVBORw0KGg…'
// → 如果图片大于 10B,该 loader 将创建一个新文件,
// 并且 `imageUrl` 将会包含它的 url: `/2fcd56a1920be.png`
⭐️Уведомление:Встроенные изображения уменьшают количество отдельных запросов, что хорошо (даже через HTTP/2), но увеличивает время загрузки/анализа пакетов и потребление памяти. Убедитесь, что вы не встраиваете большие или много изображений, иначе добавленное время пакета может перевесить преимущества встраивания.
svg-url-loader
работает какurl-loader
- за исключением того, что он используетURL encodingвместо Base64 для кодирования файла. Это работает для изображений SVG — поскольку файлы SVG представляют собой обычный текст, этот эффект масштабирования кодирования еще более выражен:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.svg$/,
loader: 'svg-url-loader',
options: {
// 小于 10kB(10240字节)的内联文件
limit: 10 * 1024,
// 移除 url 中的引号
// (在大多数情况下它们都不是必要的)
noquotes: true,
},
},
],
},
};
⭐️Уведомление:В svg-url-loader есть опции для улучшения поддержки IE, но в других браузерах он хуже. Если вам нужна совместимость с браузером IE,настраивать
iesafe: true
Опции.
image-webpack-loader
Все проверенные изображения будут сжаты. Он поддерживает изображения в форматах JPG, PNG, GIF и SVG, поэтому мы используем его для всех этих типов изображений.
Этот загрузчик не может вставлять изображения в приложение, поэтому он долженurl-loader
так же какsvg-url-loader
использовать вместе. Чтобы избежать копирования его в два правила одновременно (один для изображений JPG / PNG / GIF и один для SVG) мы используемenforce: 'pre'
Вынесено в этот загрузчик отдельным правилом:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(jpe?g|png|gif|svg)$/,
loader: 'image-webpack-loader',
// 这会应用该 loader,在其它之前
enforce: 'pre',
},
],
},
};
Настройки загрузчика по умолчанию в порядке, но если вы хотите пойти дальше и настроить его, см.Опции плагина. Чтобы выбрать конкретные параметры, см.Рекомендации по оптимизации изображений.
Расширенное чтение
зависимости оптимизации
В среднем более половины размера Javascript приходится на зависимости, и некоторые из них могут не понадобиться.
Например, Lodash (начиная с версии 4.17.4) добавляет в комплект 72 КБ минимизированного кода. Но если вы используете только его 20 методов, то около 65 КБ кода бесполезны.
Другой пример — Moment.js. Версия 2.19.1 имеет размер 223 КБ, что является огромным — по состоянию на октябрь 2017 года средний размер JavaScript для страницы452 KB. Однако 170 КБ из этогофайл локализации. Если вы не используете многоязычную версию Moment.js, эти файлы будут бесцельно раздувать пакет.
Все эти зависимости могут быть легко оптимизированы. Мы собрали методы оптимизации в репозитории GitHub —посмотри!
Включить конкатенацию модулей (также называемую подъемом области действия) для модулей ES.
⭐️Уведомление:При использовании в производственном режимеwebpack 4, тандем модулей включен. Не стесняйтесь пропустить этот раздел.
Когда вы создаете пакет, webpack превращает каждый модуль в функцию:
// index.js
import {render} from './comments.js';
render();
// comments.js
export function render(data, target) {
console.log('Rendered!');
}
↓
// bundle.js (part of)
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
var __WEBPACK_IMPORTED_MODULE_0__comments_js__ = __webpack_require__(1);
Object(__WEBPACK_IMPORTED_MODULE_0__comments_js__["a" /* render */])();
}),
/* 1 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_exports__["a"] = render;
function render(data, target) {
console.log('Rendered!');
}
})
В прошлом модули CommonJS/AMD нужно было изолировать друг от друга. Однако это увеличивает размер и производительность каждого модуля.
В webpack 2 появилась поддержка модулей ES, в отличие от модулей CommonJS и AMD, которые можно объединять в пакеты, не превращая каждый модуль в функцию. И webpack 3 делает возможным такое связывание — черезПодключение модуля. Вот как работает подключение модуля:
// index.js
import {render} from './comments.js';
render();
// comments.js
export function render(data, target) {
console.log('Rendered!');
}
↓
// 与前面的代码段不同,此包只有一个模块
// 它包含来自两个文件的代码
// bundle.js (部分; 通过 ModuleConcatenationPlugin 编译)
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
// 级联模块: ./comments.js
function render(data, target) {
console.log('Rendered!');
}
// 级联模块: ./index.js
render();
})
Увидеть разницу? В обычном связывании модуль 0 нуждается в модулях 1.render
. Подключить с помощью модулей,require
Модуль 1 удаляется простой заменой на нужную функцию. Пакеты имеют меньшие модули - и меньше накладных расходов на модули!
быть вwebpack 4включить эту функцию вoptimization.concatenateModules
Варианты:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
concatenateModules: true,
},
};
существуетwebpack 3в использованииModuleConcatenationPlugin
Плагин:
// webpack.config.js (for webpack 3)
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.optimize.ModuleConcatenationPlugin(),
],
};
⭐️Уведомление:Хотите знать, почему это поведение не включено по умолчанию? Модуль подключения отличный,Но это увеличивает время сборки и разрывает модуль горячей замены. Вот почему он включен только в производственной среде.
Расширенное чтение
- Документация по веб-пакетуfor ModuleConcatenationPlugin
- «Введение в подъем объема»
- Что делает этот плагинПодробное описание
использоватьexternals
, если вы включаете как веб-пакет, так и код, не относящийся к веб-пакету
У вас может быть большой проект, часть кода которого скомпилирована с помощью webpack, а часть — нет. Подобно сайту видеохостинга, виджет проигрывателя может быть создан с помощью веб-пакета, а окружающая страница — нет:
(Абсолютно случайный видеохостинг)
Если блок кода имеет общие зависимости, вы можете поделиться ими, чтобы избежать многократной загрузки кода. Это черезWebPack.externals
ОпцииЗавершено - заменяет модули переменными или другим дополнительным импортом.
если зависит отwindow
доступно в
Если ваш код, не относящийся к веб-пакету, зависит от определенных зависимостей, эти зависимостиwindow
можно получить как переменную в псевдониме имени зависимости к имени переменной:
// webpack.config.js
module.exports = {
externals: {
'react': 'React',
'react-dom': 'ReactDOM',
},
};
С этой конфигурацией webpack не будет упаковыватьreact
а такжеreact-dom
Мешок. Вместо этого они будут заменены чем-то вроде этого:
// bundle.js (part of)
(function(module, exports) {
// 导出 `window.React` 的模块。 没有 `externals`,
// 这个模块会包含整个的 React 包
module.exports = React;
}),
(function(module, exports) {
// 导出 `window.React` 的模块。 没有 `externals`,
// 这个模块会包含整个的 ReactDOM 包
module.exports = ReactDOM;
})
Если зависимости загружаются как пакеты AMD
Если ваш код, отличный от webpack, не предоставляет зависимостей дляwindow
, все становится сложнее. Однако, если код, не относящийся к веб-пакету, принимает эти зависимости какПакет AMD, вы все равно можете не загружать один и тот же код дважды.
Как это сделать, скомпилируйте код веб-пакета в пакет AMD и добавьте модуль к URL-адресам библиотеки:
// webpack.config.js
module.exports = {
output: { libraryTarget: 'amd' },
externals: {
'react': { amd: '/libraries/react.min.js' },
'react-dom': { amd: '/libraries/react-dom.min.js' },
},
};
webpack завернет пакет вdefine()
и сделать его зависимым от этих URL-адресов:
// bundle.js (开始)
define(["/libraries/react.min.js", "/libraries/react-dom.min.js"], function () { … });
Если код, не относящийся к веб-пакету, использует те же URL-адреса для загрузки своих зависимостей, эти файлы будут загружены только один раз — дополнительные запросы будут использовать кеш загрузчика.
⭐️Уведомление:Webpack заменяет только те, которые явно соответствуют
externals
Импорт ключа объекта. Это означает, что если вы напишетеimport React from 'react/umd/react.production.min.js'
, эта библиотека не будет исключена из комплекта. Это разумно - webpack не знаетimport 'react'
а такжеimport 'react/umd/react.production.min.js'
Это одно и то же - так что будьте осторожны.
Расширенное чтение
- документация веб-пакета
externals
Суммировать
- Если вы используете webpack 4, включите производственный режим.
- Минимизируйте свой код с помощью параметров минимизации на уровне пакета и загрузчика.
- Удалите код, который используется только в среде разработки, добавив
NODE_ENV
заменитьproduction
- Используйте модули ES, чтобы включить встряхивание дерева
- Сжать изображение
- Включить определенные оптимизации зависимостей
- Включить подключение модуля
- использовать
externals
, если это работает для вас