Современные веб-приложения, как правило, богаты контентом, и сайт должен загружать много ресурсов, особенно много js-файлов. Файл js получается с сервера, размер определяет скорость передачи, после того, как браузер получит файл js, его нужно распаковать, разобрать, скомпилировать и выполнить.Контролируйте размер кода jsа такженагрузка по требованиюЭто очень важно для производительности интерфейса и пользовательского опыта.
Эта статья изTree Shaking
и代码分割
Две части знакомят с оптимизацией пакетов js, и те, кто заинтересован, могут следовать и практиковаться вместе.
клонировать следующие элементыGitHub.com/Джейсон INTJ U/…, это простой React SPA, вы можете понять его с первого взгляда.
Tree Shaking
Простое понимание Tree Shaking: удалите некоторый неиспользуемый код при упаковке, чтобы минимизировать размер упакованного кода. Его подробное введение может относиться кПрактика оптимизации производительности Tree-Shaking — принципы.
После установки клона проекта и зависимостей сначалаnpm run build
Пакет исходного кода, размер и распределение следующие (гдеsrc/utils/utils.js
Размер пакета этого файла11.72Kb
):
src/containers/About/test.js
Только цитируется, но не используется,src/utils/utils.js
Этот файл представляет собой набор служебных функций, их очень много, и мы используем только одну из них. По умолчанию весь файл упакован вmain.js
Теперь, очевидно, это много избыточности, просто чтобы использоватьTree Shaking
оптимизация.
Исправлять.babelrc
{
"presets": [["env", { "modules": false }], "react", "stage-0"]
}
Исправлятьpackage.json
{
"name": "optimizing-js",
"version": "1.0.0",
"sideEffects": false
}
После этой настройки это означает, что все модули не имеют побочных эффектов, а неиспользуемые модули можно удалить.На данный момент результаты упаковки следующие:
import React from 'react';
// 只引入了 arraySum, utils.js 中的其他方法不会被打包
import { arraySum } from '@utils/utils';
import './test'; // 引用,“未使用”,不会被打包
import './About.scss'; // 引用,“未使用”,不会被打包
class About extends React.Component {
render() {
const sum = arraySum([12, 3]);
return (
<div className="page-about">
<h1>About Page</h1>
<div> 12 plus 3 equals {sum}</div>
</div>
);
}
}
export default About;
Как упоминалось в комментариях выше, Tree Shaking считает их неиспользуемым кодом, поэтому его можно удалить. Но на самом деле мы знаем, что это не так,test.js
Его можно удалить, но css и scss — полезные коды, их нужно просто импортировать. Поэтому необходимо модифицироватьsideEffects
Значение:
{
"sideEffects": [
"*.css", "*.scss", "*.sass"
]
}
сказал, кроме[]
Файлы (типы) в , и другие файлы не имеют побочных эффектов и могут быть безопасно удалены. Результаты упаковки на данный момент:
Как видите, файлы стилей, такие как css, теперь упаковываются по расписанию. Если есть другие типы файлов, которые имеют побочные эффекты, но также нуждаются в упаковке, вsideEffects: []
Это может быть конкретный файл или файл определенного типа.
О том, почему эффект Tree Shaking может быть достигнут путем изменения этих двух мест, вы можете обратиться кDevelopers.Google.com/Web/Женщины большие…Или другие статьи, которые здесь подробно не объясняются.
разделение кода
Для одностраничного приложения, если все ресурсы запаковать в один js, можно не сомневаться, что объем будет очень большим, первый экран будет долго загружаться белым экраном, а пользовательский опыт будет крайне бедных. Итак, чтобы разбить код на небольшой js, оптимизировать время загрузки.
Отдельный код сторонней библиотеки
Код сторонней библиотеки извлекается отдельно и отделяется от бизнес-кода, чтобы уменьшить размер файла js. существуетwebpack.base.conf.js
Добавить в:
module: {...},
optimization: {
splitChunks: {
cacheGroups: {
venders: {
test: /node_modules/,
name: 'vendors',
chunks: 'all'
}
}
}
},
plugins: ...
динамический импорт
использоватьПредложение ECMAScriptизdynamic import
Синтаксис для загрузки компонентов в бизнесе асинхронно. Способ применения следующий:
// src/containers/App/App.js
// 注释掉此行代码
// import About from '@containers/About/About';
// 修改模块为动态导入形式
<Route path="/about" render={() => import(/* webpackChunkName: "about" */ '@containers/About/About').then(module => module.default)}/>
Результаты упаковки на данный момент:
могу видеть,<About> 组件
Соответствующие файлы js были отдельно упакованы webpack. В то же время, сочетаяreact-router
, разделенный<About> 组件
также сделалнагрузка по требованию: при посещении страницы «О программе»about.js
будет загружаться браузером.
Обратите внимание, что сейчас мы просто используемdynamic import
, не учитываются многие пограничные случаи, такие как: ход загрузки, сбой загрузки, тайм-аут и другая обработка. Компонент более высокого порядка может быть разработан для включения этой обработки исключений. В сообществе есть отличныйreact-loadable, приятно поваляться в тени под большим деревом~
npm i react-loadable
// src/containers/App/App.js
import Loadable from 'react-loadable';
// 代码分割 & 异步加载
const LoadableAbout = Loadable({
loader: () => import(/* webpackChunkName: "about" */ '@containers/About/About'),
loading() {
return <div>Loading...</div>;
}
});
class App extends React.Component {
render() {
return (
<BrowserRouter>
<div>
<Header />
<Route exact path="/" component={Home} />
<Route path="/docs" component={Docs} />
<Route path="/about" component={LoadableAbout} />
</div>
</BrowserRouter>
);
}
}
react-loadableТакже предусмотрена функция предварительной загрузки. Если есть статистические данные, показывающие, что пользователь, скорее всего, попадет на страницу «О программе» после входа на домашнюю страницу, то мы будем загружать ее при загрузке домашней страницы.about.js
, так что когда пользователь перейдет на страницу «О программе», ресурсы js будут загружены, и взаимодействие с пользователем будет лучше.
// src/containers/App/App.js
componentDidMount() {
LoadableAbout.preload();
}
Если некоторые учащиеся не очень хорошо знакомы с панелью «Сеть», вы можете взглянутьChrome DevTools — Сеть.
Извлечение повторно используемого бизнес-кода
Код сторонних библиотек был извлечен отдельно, но в бизнес-коде также будет некоторый повторно используемый код, например, некоторые библиотеки функций инструментов.utils.js
. в настоящее время,About 组件
иDocs 组件
котируютсяutils.js
, webpack упаковывает только одну копиюutils.js
существуетmain.js
Внутри main.js загружается на домашнюю страницу, и другие страницы, использующие utils.js, могут, естественно, нормально обращаться к нему, что соответствует нашим ожиданиям. Но в настоящее время мы асинхронно загружаем только страницу «О нас». Что, если страница «Документы» также загружается асинхронно?
// src/containers/App/App.js
// 注释掉此行代码
// import Docs from '@containers/Docs/Docs';
const LoadableDocs = Loadable({
loader: () => import(/* webpackChunkName: "docs" */ '@containers/Docs/Docs'),
loading() {
return <div>Loading...</div>;
}
});
class App extends React.Component {
render() {
return (
<BrowserRouter>
<div>
<Header />
<Route exact path="/" component={Home} />
<Route path="/docs" component={LoadableDocs} />
<Route path="/about" component={LoadableAbout} />
</div>
</BrowserRouter>
);
}
}
Результаты упаковки на данный момент:
Как видите, utils.js упакован как в about.js, так и в docs.js, повторяю!
существуетwebpack.base.conf.js
Добавить в:
module: {...},
optimization: {
splitChunks: {
cacheGroups: {
venders: {
test: /node_modules/,
name: 'vendors',
chunks: 'all'
},
default: {
minSize: 0,
minChunks: 2,
reuseExistingChunk: true,
name: 'utils'
}
}
}
},
plugins: ...
Затем упакуйте, чтобы увидеть результат:
Как и ожидалось, utils.js также упакован отдельно.
Отдельные сторонние библиотеки, которые не используются на главной странице и имеют небольшую степень повторного использования.
Предположим, теперь, когда Docs.js ссылаетсяlodash
Эта трехсторонняя библиотека:
import React from 'react';
import _ from 'lodash';
import { arraySum } from '@utils/utils';
import './Docs.scss';
class Docs extends React.Component {
render() {
const sum = arraySum([1, 3]);
const b = _.sum([1, 3]);
return (
<div className="page-docs">
<h1>Docs Page</h1>
<div> 1 plus 3 equals {sum}</div>
<br />
<div>use _.sum, 1 plus 3 equals {b} too.</div>
</div>
);
}
}
export default Docs;
Результат упаковки:
lodash.js используется только на странице "Документы", и может быть несколько посещений страницы "Документы". Было бы неразумно упаковывать lodash.js в venders.js, который будет загружаться на домашней странице.
Исправлятьwebpack.base.conf.js
:
...
venders: {
test: /node_modules\/(?!(lodash)\/)/, // 去除 lodash,剩余的第三方库打成一个包,命名为 vendors-common
name: 'vendors-common',
chunks: 'all'
},
lodash: {
test: /node_modules\/lodash\//, // lodash 库单独打包,并命名为 vender-lodash
name: 'vender-lodash'
},
default: {
minSize: 0,
minChunks: 2,
reuseExistingChunk: true,
name: 'utils'
}
...
В настоящее время lodash упакован отдельно в пакет, а загрузка страницы Документов по требованию достигла идеального эффекта загрузки.
тайник
После упаковки проекта ресурсы развертываются на стороне сервера, и клиенту необходимо запросить у сервера загрузку этих ресурсов, прежде чем пользователь сможет увидеть содержимое. Используя кэширование, клиент может значительно сократить количество ненужных запросов и временных задержек и выполнять загрузку только при обновлении ресурсов. Чтобы определить, был ли файл обновлен, используйте文件名 + hash
может достичь цели. В этом случае использование'[name].[contenthash:8].js'
.
Однако при упаковке исполняемый код веб-пакета иногда приводит к определенным ситуациям, например: ничего не изменилось, хэш двух кодов сборки отличается или код файла изменен, но это приводит к тому, что хэш некоторые немодифицированные файлы кода также изменились.This is caused by the injection of the runtime and manifest which changes every build.
Примечание. Используются разные версии веб-пакета, что может привести к разным результатам упаковки. В более новых версиях может не быть этой проблемы с хэшем, но на всякий случай рекомендуется выполнить следующие шаги.
Отдельный код webpack runtimeChunk
// webpack.base.conf.js
optimization: {
runtimeChunk: {
name: 'manifest'
},
splitChunks: {...}
}
На данный момент этого можно добиться: изменив файл, изменится только хэш этого файла и файла manifest.js, а хэш других файлов останется неизменным. Перед упаковкой:
// About.scss
.page-about {
padding-left: 30px;
color: #545880; // 修改字体颜色
}
После модификации:
HashedModuleIdsPlugin
Добавление или удаление некоторых модулей может привести к изменению хэша нерелевантных файлов, потому что, когда пакеты webpack в соответствии с порядком импортируемых модулей,module.idСамоинкремент, который приведет к тому, что некоторые модулиmodule.idизменения, что, в свою очередь, приводит к изменению хэша файла.
Решение: используйте встроенный веб-пакетHashedModuleIdsPlugin, плагин генерирует соответствующий на основе относительного пути импортированного модуляmodule.id, так что если содержимое не меняется плюсmodule.idЕсли изменений нет, сгенерированный хэш не изменится.
// webpack.prod.conf.js
const webpack = require('webpack');
...
plugins: [new webpack.HashedModuleIdsPlugin(), new BundleAnalyzerPlugin()]
Полный оптимизированный код см.GitHub.com/Джейсон INTJ U/…
Полезные статьи:webpack разделяет сторонние библиотеки и публичные файлы
Developers.Google.com/Web/Женщины большие…