предисловие
При оптимизации интерфейсных приложений чрезвычайно важно контролировать размер загружаемых ресурсов, в большинстве случаев мы можем контролировать размер, разделять и повторно использовать ресурсы в процессе упаковки и компиляции.
Эта статья в основном основана на упаковке веб-пакетов и использует экологически разработанные одностраничные приложения, такие как React и vue, в качестве примера, чтобы проиллюстрировать, как обрабатывать ресурсы и кешировать на уровне упаковки веб-пакета.Главное, что нам нужно сделать, это оптимизировать конфигурация веб-пакета, в то же время вносится небольшое количество изменений в бизнес-код.
В то же время можно использовать анализ упакованных ресурсов.webpack-bundle-analyzer
Плагины, конечно, есть еще много необязательных плагинов для анализа.В этой статье этот плагин в основном используется как пример для анализа.
СОВЕТ: версия веб-пакета @3.6.0.
Среда упаковки и сжатие кода
Сначала у нас есть базовая конфигурация веб-пакета:
// webpack.config.js
const path = require('path');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const PROJECT_ROOT = path.resolve(__dirname, './');
module.exports = {
entry: {
index: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[chunkhash:4].js'
},
module: {
rules: [
{
test: /\.js[x]?$/,
use: 'babel-loader',
include: PROJECT_ROOT,
exclude: /node_modules/
}
]
},
plugins: [
new BundleAnalyzerPlugin()
],
resolve: {
extensions: ['.js', '.jsx']
},
};
Выполните упаковку, и вы увидите, что js проекта имеет более 1M:
Hash: e51afc2635f08322670b
Version: webpack 3.6.0
Time: 2769ms
Asset Size Chunks Chunk Names
index.caa7.js 1.3 MB 0 [emitted] [big] index
Просто добавьте плагиныDefinePlugin
а такжеUglifyJSPlugin
Вы можете уменьшить объем, добавить плагины:
// webpack.config.js
...
{
...
plugins: [
new BundleAnalyzerPlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production')
}),
new UglifyJSPlugin({
uglifyOptions: {
ie8: false,
output: {
comments: false,
beautify: false,
},
mangle: {
keep_fnames: true
},
compress: {
warnings: false,
drop_console: true
},
}
})
]
...
}
Вы можете увидеть вывод упаковки в это время:
Hash: 84338998472a6d3c5c25
Version: webpack 3.6.0
Time: 9940ms
Asset Size Chunks Chunk Names
index.89c2.js 346 kB 0 [emitted] [big] index
Размер кода был уменьшен с 1.3M до 346K.
DefinePlugin
DefinePlugin позволяет создать глобальную константу, которую можно настроить во время компиляции. Это может быть полезно, чтобы разрешить различное поведение для режима разработки и режима выпуска. Если ведение журнала выполняется в сборке разработки, а не в сборке выпуска, можно использовать глобальную константу, чтобы решить, вести журнал или нет. Вот тут-то и пригодится DefinePlugin, установите его и забудьте о правилах разработки и выпуска билдов.
В нашем бизнес-коде и коде сторонних пакетов нам часто приходится судитьprocess.env.NODE_ENV
делать разную обработку, а в производстве нам явно не нужны не-production
часть обработки.
Здесь мы устанавливаемprocess.env.NODE_ENV
дляJSON.stringify('production')
, что означает, что среда упаковки настроена на производственную среду. сотрудничать позжеUglifyJSPlugin
Плагины могут удалить часть избыточного кода при упаковке для производства.
UglifyJSPlugin
UglifyJSPlugin в основном используется для разбора и сжатия кода js.uglify-es
Для обработки кода js он имеет различные параметры конфигурации:GitHub.com/Webpack-con…
Благодаря сжатию кода и удалению избыточности размер упакованных ресурсов значительно уменьшается.
Разделение кода/загрузка по требованию
В одностраничных приложениях, таких как React или Vue, управление маршрутизацией страниц и представлениями реализуется интерфейсом, а соответствующая бизнес-логика находится в коде js.
Когда приложение разработано с большим количеством страниц и логики, окончательные сгенерированные файловые ресурсы js также будут довольно большими.
Однако, когда мы открываем страницу, соответствующую URL-адресу, нам действительно не нужно все код JS, а только основной код выполнения и код бизнес-логика, соответствующий просмотру, а затем загрузить следующий вид. Для загрузки этой части код.
Поэтому оптимизация, которую можно сделать в этом отношении, заключается в загрузке кода js по запросу.
Отложенная загрузка или загрузка по запросу — отличный способ оптимизировать веб-страницы или приложения. Этот метод фактически разделяет ваш код в некоторых логических точках останова, а затем ссылается или собирается ссылаться на другие новые блоки кода после выполнения определенных операций в некоторых блоках кода. Это ускоряет первоначальную загрузку приложения и уменьшает его общий размер, поскольку некоторые блоки кода могут никогда не загрузиться.
В веб-пакете предусмотрена технология динамического импорта для реализации разделения кода.Во-первых, конфигурация каждого подмодуля должна быть настроена в конфигурации веб-пакета:
// webpack.config.js
const path = require('path');
const webpack = require('webpack');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const PROJECT_ROOT = path.resolve(__dirname, './');
module.exports = {
entry: {
index: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[chunkhash:4].js',
chunkFilename: '[name].[chunkhash:4].child.js',
},
module: {
rules: [
{
test: /\.js[x]?$/,
use: 'babel-loader',
include: PROJECT_ROOT,
exclude: /node_modules/
}
]
},
plugins: [
new BundleAnalyzerPlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production')
}),
new UglifyJSPlugin({
uglifyOptions: {
ie8: false,
output: {
comments: false,
beautify: false,
},
mangle: {
keep_fnames: true
},
compress: {
warnings: false,
drop_console: true
},
}
}),
],
resolve: {
extensions: ['.js', '.jsx']
},
};
Основными из них, которые необходимо определить, являютсяoutput
серединаchunkFilename
, которое является именем файла экспортированного кода разделения, здесь установлено значение[name].[chunkhash:4].child.js
,один из нихname
Соответствующее имя модуля или идентификатор,chunkhash
это хэш содержимого модуля.
Затем в бизнес-коде webpack предоставляет два способа динамического импорта:
-
import('path/to/module') -> Promise
, require.ensure(dependencies: String[], callback: function(require), errorCallback: function(error), chunkName: String)
В основном рекомендуется для последней версии веб-пакетаimport()
ПутьПримечание: для импорта используется Promise, поэтому необходимо убедиться, что полифилл Promise поддерживается в коде..
// src/index.js
function getComponent() {
return import(
/* webpackChunkName: "lodash" */
'lodash'
).then(_ => {
var element = document.createElement('div');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
return element;
}).catch(error => 'An error occurred while loading the component');
}
getComponent().then(component => {
document.body.appendChild(component);
})
Вы можете увидеть упакованную информацию:
Hash: d6ba79fe5995bcf9fa4d
Version: webpack 3.6.0
Time: 7022ms
Asset Size Chunks Chunk Names
lodash.89f0.child.js 85.4 kB 0 [emitted] lodash
index.316e.js 1.96 kB 1 [emitted] index
[0] ./src/index.js 441 bytes {1} [built]
[2] (webpack)/buildin/global.js 509 bytes {0} [built]
[3] (webpack)/buildin/module.js 517 bytes {0} [built]
+ 1 hidden module
Вы можете видеть, что упакованный код сгенерированindex.316e.js
а такжеlodash.89f0.child.js
два файла, последний черезimport
добиться расщепления.import
он получаетpath
Параметр относится к пути к подмодулю, а также обратите внимание, что к нему можно добавить строку комментария/* webpackChunkName: "lodash" */
, аннотация не бесполезна, она определяет подмодуль
название, которое соответствуетoutput.chunkFilename
середина[name]
.import
Функция возвращает промис, и при асинхронной загрузке в код подмодуля она будет выполнять последующие операции, такие как обновление представления и т.д.
Загрузка по требованию в React
При разработке React с помощью React-Router часто требуется возможность загрузки кода по запросу в соответствии с маршрутом.Следующее представляет собой динамически загружаемый компонент React на основе технологии динамического импорта кода webpack:
import React, { Component } from 'react';
export default function lazyLoader (importComponent) {
class AsyncComponent extends Component {
state = { Component: null }
async componentDidMount () {
const { default: Component } = await importComponent();
this.setState({
Component: Component
});
}
render () {
const Component = this.state.Component;
return Component
? <Component {...this.props} />
: null;
}
}
return AsyncComponent;
};
существуетRoute
середина:
<Switch>
<Route exact path="/"
component={lazyLoader(() => import('./Home'))}
/>
<Route path="/about"
component={lazyLoader(() => import('./About'))}
/>
<Route
component={lazyLoader(() => import('./NotFound'))}
/>
</Switch>
существуетRoute
отображается вlazyLoader
Компонент, возвращаемый функцией, которая будет выполнена после монтированияimportComponent
функция (оба:() => import('./About')
) динамически загружает соответствующий модуль компонента (разделенный код) и отображает компонент после успешной загрузки.
Код упакован таким образом:
Hash: 02a053d135a5653de985
Version: webpack 3.6.0
Time: 9399ms
Asset Size Chunks Chunk Names
0.db22.child.js 5.82 kB 0 [emitted]
1.fcf5.child.js 4.4 kB 1 [emitted]
2.442d.child.js 3 kB 2 [emitted]
index.1bbc.js 339 kB 3 [emitted] [big] index
Извлечь общие ресурсы
Длинный кеш для сторонних библиотек
Во-первых, для некоторых относительно больших сторонних библиотек, таких как react, react-dom, react-router и т.п., используемых в React, мы не хотим, чтобы они упаковывались повторно, и не хотим каждый раз менять версия обновлена Эта часть ресурса вызывает перезагрузку на стороне пользователя.
Здесь вы можете использовать CommonsChunkPlugin webpack для извлечения этих общих ресурсов;
Плагин CommonsChunkPlugin — это дополнительная функция для создания отдельного файла (также называемого чанком), который содержит общие модули для чанков с несколькими входами. Убрав общие модули, окончательный синтезированный файл можно загрузить один раз в начале, а затем сохранить в кэше для последующего использования. Это дает прирост скорости, потому что браузер быстро извлекает общий код из кеша, а не загружает файл большего размера каждый раз, когда открывается новая страница.
Во-первых, вам нужно добавить запись в запись для упаковки библиотеки, которую необходимо извлечь.Здесь мы будем'react', 'react-dom', 'react-router-dom', 'immutable'
упакованы индивидуальноvendor
середина;
Затем определите плагин в плагинахCommonsChunkPlugin
плагин, покаname
Установить какvendor
в том, что они связаны, а затемminChunks
Установить какInfinity
Предотвратить упаковку другого кода.
// webpack.config.js
const path = require('path');
const webpack = require('webpack');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const PROJECT_ROOT = path.resolve(__dirname, './');
module.exports = {
entry: {
index: './src0/index.js',
vendor: ['react', 'react-dom', 'react-router-dom', 'immutable']
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[chunkhash:4].js',
chunkFilename: '[name].[chunkhash:4].child.js',
},
module: {
rules: [
{
test: /\.js[x]?$/,
use: 'babel-loader',
include: PROJECT_ROOT,
exclude: /node_modules/
}
]
},
plugins: [
new BundleAnalyzerPlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production')
}),
new UglifyJSPlugin({
uglifyOptions: {
ie8: false,
output: {
comments: false,
beautify: false,
},
mangle: {
keep_fnames: true
},
compress: {
warnings: false,
drop_console: true
},
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: Infinity,
}),
],
resolve: {
extensions: ['.js', '.jsx']
},
};
Вы можете увидеть пакет:
Hash: 34a71fcfd9a24e810c21
Version: webpack 3.6.0
Time: 9618ms
Asset Size Chunks Chunk Names
0.2c65.child.js 5.82 kB 0 [emitted]
1.6e26.child.js 4.4 kB 1 [emitted]
2.e4bc.child.js 3 kB 2 [emitted]
index.4e2f.js 64.2 kB 3 [emitted] index
vendor.5fd1.js 276 kB 4 [emitted] [big] vendor
можно увидетьvendor
упакован отдельно.
Упакуйте снова, когда мы изменим бизнес-код:
Hash: cd3f1bc16b28ac97e20a
Version: webpack 3.6.0
Time: 9750ms
Asset Size Chunks Chunk Names
0.2c65.child.js 5.82 kB 0 [emitted]
1.6e26.child.js 4.4 kB 1 [emitted]
2.e4bc.child.js 3 kB 2 [emitted]
index.4d45.js 64.2 kB 3 [emitted] index
vendor.bc85.js 276 kB 4 [emitted] [big] vendor
Пакет вендора тоже запакован, но у него изменился хэш файла, что явно не соответствует нашим требованиям к длинному кешу.
Это связано с тем, что webpack будет генерировать код среды выполнения при использовании CommoChunkPlugin (он в основном используется для сопоставления взаимосвязей модулей кода), и даже если код поставщика не изменен, среда выполнения все равно будет следовать изменениям упаковки и вводить verdor , поэтому хэш начнет меняться. Решение состоит в том, чтобы извлечь эту часть кода среды выполнения отдельно и изменить предыдущую.CommonsChunkPlugin
для:
// webpack.config.js
...
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor', 'runtime'],
minChunks: Infinity,
}),
...
Выполните упаковку, и вы увидите, что сгенерированных кодов больше.runtime
При этом даже при изменении бизнес-кода хеш-значение вендора остается неизменным.
Конечно этоruntime
На самом деле он очень короткий, мы можем напрямую встроить его в html, если используемhtml-webpack-plugin
Плагин обрабатывает html, можно комбинироватьhtml-webpack-inline-source-plugin
Плагин обрабатывает свои
в линию.
абстракция государственных ресурсов
Упакованные нами js-ресурсы включают в себя наборы js-ресурсов из разных записей и подмодулей, но одни и те же зависимые модули или код будут многократно загружаться между ними, поэтому некоторые ресурсы, от которых они зависят вместе, можно упаковать в общий с помощью плагина CommonsChunkPlugin. Ресурсы.
// webpack.config.js
plugins: [
new BundleAnalyzerPlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production')
}),
new UglifyJSPlugin({
uglifyOptions: {
ie8: false,
output: {
comments: false,
beautify: false,
},
mangle: {
keep_fnames: true
},
compress: {
warnings: false,
drop_console: true
},
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor', 'runtime'],
minChunks: Infinity,
}),
new webpack.optimize.CommonsChunkPlugin({
// ( 公共chunk(commnons chunk) 的名称)
name: "commons",
// ( 公共chunk 的文件名)
filename: "commons.[chunkhash:4].js",
// (模块必须被 3个 入口chunk 共享)
minChunks: 3
})
],
Вы можете видеть, что он был добавлен здесьcommons
Когда ресурс зависит от трех или более фрагментов, эти ресурсы будут отдельно извлечены и упакованы вcommons.[chunkhash:4].js
документ.
Выполните пакет и увидите результат следующим образом:
Hash: 2577e42dc5d8b94114c8
Version: webpack 3.6.0
Time: 24009ms
Asset Size Chunks Chunk Names
0.2eee.child.js 90.8 kB 0 [emitted]
1.cfbc.child.js 89.4 kB 1 [emitted]
2.557a.child.js 88 kB 2 [emitted]
vendor.66fd.js 275 kB 3 [emitted] [big] vendor
index.688b.js 64.2 kB 4 [emitted] index
commons.a61e.js 1.78 kB 5 [emitted] commons
но нашел здесьcommons.[chunkhash].js
По сути, фактического контента нет, но очевидно, что каждый подмодуль также зависит от одних и тех же зависимостей.
с помощьюwebpack-bundle-analyzer
Проанализируем волну:
Вы можете видеть, что все три модуля зависят отlodash
, однако он не извлекается.
Это связано с тем, что чанк в CommonsChunkPlugin ссылается на каждую запись в записи, поэтому он не влияет на дочерний фрагмент, отделенный от записи.
Можно настроить в плагине CommonsChunkPluginchildren
Параметр также упаковывает общедоступные зависимости разделенных подмодулей вcommons
середина:
// webpack.config.js
plugins: [
new BundleAnalyzerPlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production')
}),
new UglifyJSPlugin({
uglifyOptions: {
ie8: false,
output: {
comments: false,
beautify: false,
},
mangle: {
keep_fnames: true
},
compress: {
warnings: false,
drop_console: true
},
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor', 'runtime'],
minChunks: Infinity,
}),
new webpack.optimize.CommonsChunkPlugin({
// ( 公共chunk(commnons chunk) 的名称)
name: "commons",
// ( 公共chunk 的文件名)
filename: "commons.[chunkhash:4].js",
// (模块必须被 3个 入口chunk 共享)
minChunks: 3
}),
new webpack.optimize.CommonsChunkPlugin({
// (选择所有被选 chunks 的子 chunks)
children: true,
// (在提取之前需要至少三个子 chunk 共享这个模块)
minChunks: 3,
})
],
Проверьте эффект упаковки:
Публичные ресурсы его подмодулей упакованы вindex
, и не идеально упакованы вcommons
среди них, или потому чтоcommons
Для входного модуля на входе, и нет трех модулей входа, разделяющих ресурсы;
Возможность удаления в однократных заявкахcommons
, в то время как в подмодулеCommonsChunkPlugin
конфиг в конфигеasync
дляtrue
:
// webpack.config.js
plugins: [
new BundleAnalyzerPlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production')
}),
new UglifyJSPlugin({
uglifyOptions: {
ie8: false,
output: {
comments: false,
beautify: false,
},
mangle: {
keep_fnames: true
},
compress: {
warnings: false,
drop_console: true
},
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor', 'runtime'],
minChunks: Infinity,
}),
new webpack.optimize.CommonsChunkPlugin({
// (选择所有被选 chunks 的子 chunks)
children: true,
// (异步加载)
async: true,
// (在提取之前需要至少三个子 chunk 共享这个模块)
minChunks: 3,
})
],
Проверьте эффект:
Общие ресурсы подмодулей упакованы в0.9c90.child.js
В, модуль является общим достоянием подмодуля.
tree shaking
Встряхивание дерева — это термин, обычно используемый для описания удаления мертвого кода из контекста JavaScript. Он основан на статических структурных функциях модульной системы ES2015, таких как импорт и экспорт. Этот термин и концепция фактически возникли из набора инструментов для упаковки модулей ES2015.
Когда мы вводим определенный вывод зависимости, нам может понадобиться только определенная часть кода зависимости, а другая часть кодаunused
Да, если эту часть кода можно удалить, конечный объем упакованного ресурса также может быть значительно уменьшен.
Во-первых, реализация встряски дерева в webpack основана на механизме модуля es2015, поддерживаемом внутри webpack.Большую часть времени мы используем babel для компиляции js-кода, и babel будет обрабатывать его через собственный механизм загрузки модулей, что приводит к встряхивание дерева в веб-пакете
Обработка не удастся. Поэтому нужно отключить обработку загрузки модуля в конфигурации babel:
// .babelrc
{
"presets": [
[
"env", {
"modules": false,
}
],
"stage-0"
],
...
}
Тогда давайте посмотрим, как webpack обрабатывает запакованный код, например, есть входной файлindex.js
с однимutils.js
документ:
// utils.js
export function square(x) {
return x * x;
}
export function cube(x) {
return x * x * x;
}
// index.js
import { cube } from './utils.js';
console.log(cube(10));
Упакованный код:
// index.bundle.js
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* unused harmony export square */
/* harmony export (immutable) */ __webpack_exports__["a"] = cube;
function square(x) {
return x * x;
}
function cube(x) {
return x * x * x;
}
Видно, что толькоcube
функция__webpack_exports__
экспорт иsquare
функция отмечена какunused harmony export square
, однако в упакованном коде обаsquare
не экспортируется, но он все еще существует в коде, и как удалить его код можно сделать, добавивUglifyjsWebpackPlugin
плагин для обработки.