предисловие
Для оптимизации пакетов веб-пакетов не существует фиксированного режима. Как правило, наши общие оптимизации — это распаковка, разбиение на фрагменты, сжатие и т. д., которые не применимы к каждому проекту. Для конкретных проектов требуется непрерывная отладка и оптимизация.
Для webpack4 рекомендуется настраивать с нуля, на ранней стадии проекта использовать конфигурацию webpack4 по умолчанию.
Далее в этой статье будут перечислены все технические решения, применимые к webpack для оптимизации скорости упаковки, и даны соответствующие ограничения, пожалуйста, используйте их в реальных проектах. Если у вас есть какие-либо вопросы, пожалуйста, свяжитесь с Mr. Bottle.
1. Проанализируйте скорость упаковки
Первым шагом в оптимизации скорости сборки веб-пакета является знание того, на чем сосредоточить свои усилия. мы можем пройтиspeed-measure-webpack-plugin
Измерьте время, затрачиваемое на различных этапах сборки веб-пакета:
// 分析打包时间
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
// ...
module.exports = smp.wrap(prodWebpackConfig)
Конкретные проекты имеют свои специфические узкие места построения производительности, ниже мы оптимизируем каждое звено упаковки.
2. Проанализируйте ссылки, влияющие на скорость упаковки
В разделе «Принцип слежения: написание упаковщика JavaScript вручную» мы уже говорили о том, что упаковка — это процесс упаковки всех зависимых модулей в один файл, начиная с входного файла.Конечно, процесс упаковки включает в себя различные процессы компиляции и оптимизации.
Какие общие моменты в процессе упаковки влияют на скорость сборки?
1. Для начала упаковки нам нужно получить все зависимые модули
Поиск всех зависимостей, что занимает определенное время, то есть время поиска, то мы уверены:
Первое, что нам нужно оптимизировать, — это время поиска.
2. Разрешить все зависимые модули (преобразовать в код, запускаемый браузером)
webpack анализирует соответствующие файлы в соответствии с нашим настроенным загрузчиком. В ежедневной разработке нам нужно использовать загрузчик для преобразования js, css, изображений, шрифтов и других файлов, а объем преобразованных файловых данных также очень велик. Из-за однопоточной природы js эти операции преобразования не могут обрабатывать файлы одновременно, а должны обрабатывать файлы один за другим.
Второй момент, который нам нужно оптимизировать, — это время синтаксического анализа.
3. Упаковать все зависимые модули в один файл
Запакуйте весь разобранный код в файл, чтобы обновить загруженный браузером пакет (уменьшить время белого экрана), webpack оптимизирует код.
Сжатие JS является завершающим этапом публикации и компиляции.Обычно webpack нужно застопорить на некоторое время.Это связано с тем, что для сжатия JS необходимо разобрать код в синтаксическое дерево AST, затем проанализировать и обработать AST по сложным правилам, и, наконец, восстановить AST в JS, этот процесс включает в себя много вычислений, поэтому занимает много времени, и легко застрять при упаковке.
Третий момент, который нам нужно оптимизировать, — это время сжатия.
4. Вторичная упаковка
При изменении небольшого файла в проекте нам требуется перепаковка, все файлы должны быть перепакованы, это занимает столько же времени, сколько и первоначальная упаковка, но большинство файлов в проекте остаются без изменений, особенно сторонние библиотеки.
Четвертый момент, который нам нужно оптимизировать, — это время вторичной упаковки.
3. Оптимизируйте время синтаксического анализа — включите многопроцессорную упаковку
Webpack, работающий на Node.js, является однопоточным, то есть упаковка webpack может обрабатываться только один за другим.Когда webpack нужно упаковать большое количество файлов, время упаковки будет больше.
1. thread-loader (официальная рекомендация webpack4)
Если этот загрузчик размещается перед другими загрузчиками, загрузчик, размещенный после этого загрузчика, будет работать в отдельном пуле рабочих [worker pool].Рабочий процесс — это процесс nodeJS [процесс node.js], и время обработки каждого отдельного процесса составляет верхний предел составляет 600 мс, и обмен данными каждого процесса также ограничен этим временем.
Загрузчик потоков также очень прост в использовании.Пока загрузчик потоков размещается перед другими загрузчиками, загрузчики после загрузчика потоков будут выполняться в отдельном пуле рабочих процессов.
Например:
module.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
// 创建一个 js worker 池
use: [
'thread-loader',
'babel-loader'
]
},
{
test: /\.s?css$/,
exclude: /node_modules/,
// 创建一个 css worker 池
use: [
'style-loader',
'thread-loader',
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[name]__[local]--[hash:base64:5]',
importLoaders: 1
}
},
'postcss-loader'
]
}
// ...
]
// ...
}
// ...
}
Примечание. Загрузчик потоков размещается после загрузчика стилей, потому что загрузчик, стоящий за загрузчиком потоков, не может получить доступ к файлам или получить настройки параметров веб-пакета.
Официально заявлено, что каждый воркер занимает около 600 мс, поэтому официал обеспечивает оптимизацию пула воркеров, чтобы предотвратить высокую задержку при запуске воркера:Разогрев
// ...
const threadLoader = require('thread-loader');
const jsWorkerPool = {
// options
// 产生的 worker 的数量,默认是 (cpu 核心数 - 1)
// 当 require('os').cpus() 是 undefined 时,则为 1
workers: 2,
// 闲置时定时删除 worker 进程
// 默认为 500ms
// 可以设置为无穷大, 这样在监视模式(--watch)下可以保持 worker 持续存在
poolTimeout: 2000
};
const cssWorkerPool = {
// 一个 worker 进程中并行执行工作的数量
// 默认为 20
workerParallelJobs: 2,
poolTimeout: 2000
};
threadLoader.warmup(jsWorkerPool, ['babel-loader']);
threadLoader.warmup(cssWorkerPool, ['css-loader', 'postcss-loader']);
module.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'thread-loader',
options: jsWorkerPool
},
'babel-loader'
]
},
{
test: /\.s?css$/,
exclude: /node_modules/,
use: [
'style-loader',
{
loader: 'thread-loader',
options: cssWorkerPool
},
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[name]__[local]--[hash:base64:5]',
importLoaders: 1
}
},
'postcss-loader'
]
}
// ...
]
// ...
}
// ...
}
ПРИМЕЧАНИЕ: Пожалуйста, используйте только на загрузчиках, отнимающих много времени.
2. HappyPack
В процессе создания веб-пакета большая часть времени фактически используется для синтаксического анализа загрузчика, преобразования и сжатия кода.HappyPack может использовать несколько процессов для упаковки файлов (ядра процессора по умолчанию -1), а использование многоядерного процессора выше. HappyPack позволяет Webpack обрабатывать несколько задач одновременно, использовать возможности многоядерного процессора и разбивать задачи на несколько подпроцессов для одновременного выполнения.После обработки подпроцессов результаты отправляются в основной процесс.
Идея обработки happypack заключается в том, чтобы расширить процесс выполнения исходного веб-пакета до загрузчика с одного процесса на многопроцессный режим, при этом исходный процесс остается неизменным. Есть также некоторые ограничения в использовании HappyPack, он совместим только с некоторыми основными загрузчиками, вы можете проверить официальный загрузчик для деталей.Список совместимости.
Примечание: Ahmad Amireh рекомендовал thread-loader и объявил, что happypack больше не будет поддерживаться, поэтому он устарел.
const path = require('path')
const webpack = require("webpack");
const HappyPack = require('happypack'); // 多进程loader
// node 提供的系统操作模块
const os = require('os');
// 构造出共享进程池,根据系统的内核数量,指定进程池个数,也可以其他数量
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
const createHappyPlugin = (id, loaders) => new HappyPack({
// 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
id: id,
// 如何处理 .js 文件,用法和 Loader 配置中一样
loaders: loaders,
// 其它配置项(可选)
// 代表共享进程池,即多个 HappyPack 实例都使用同一个共享进程池中的子进程去处理任务,以防止资源占用过多
threadPool: happyThreadPool,
// 是否允许 HappyPack 输出日志,默认是 true
verbose: true
// threads:代表开启几个子进程去处理这一类型的文件,默认是3个,类型必须是整数
});
const clientWebpackConfig = {
// ...
module: {
rules: [
{
test: /\.(js|jsx)$/,
// 把对 .js .jsx 文件的处理转交给 id 为 happy-babel 的 HappyPack 实例
use: ["happypack/loader?id=happy-babel"],
// 排除 node_modules 目录下的文件
// node_modules 目录下的文件都是采用的 ES5 语法,没必要再通过 Babel 去转换
exclude: /node_modules/,
}
]
},
// ...
plugins: [
createHappyPlugin('happy-babel', [{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', "@babel/preset-react"],
plugins: [
["import", { "libraryName": "antd", "style": true }],
['@babel/plugin-proposal-class-properties',{loose:true}]
],
cacheDirectory: true,
// Save disk space when time isn't as important
cacheCompression: true,
compact: true,
}
}]),
// ...
]
}
Обратите внимание, что если проект небольшой, многопроцессорная упаковка может фактически замедлить упаковку.
4. Разумное использование кеша (сокращение времени непрерывной сборки и увеличение времени начальной сборки)
Существует несколько способов использования кэширования веб-пакетов, например, использованиеcache-loader
,HardSourceWebpackPlugin
илиbabel-loader
изcacheDirectory
логотип. Все эти методы кэширования имеют дополнительные затраты на запуск. Локальная экономия времени во время повторного запуска огромна, но начальный (холодный) запуск на самом деле будет медленнее.
Если ваша производственная версия проекта должна каждый раз выполнять первоначальную сборку, кэширование может увеличить время сборки и замедлить работу. Если нет, они значительно сократят время вторичной сборки.
1. cache-loader
Как и thread-loader, cache-loader также очень прост в использовании.Вам нужно только добавить этот загрузчик перед некоторыми загрузчиками с высокой производительностью, чтобы кэшировать результаты на диск и значительно повысить скорость вторичного построения.
module.exports = {
module: {
rules: [
{
test: /\.ext$/,
use: ['cache-loader', ...loaders],
include: path.resolve('src'),
},
],
},
};
⚠️ Обратите внимание, что сохранение и чтение этих файлов кеша потребует некоторого времени, поэтому используйте этот загрузчик только для загрузчиков с высокой производительностью.
2. HardSourceWebpackPlugin
- Первая сборка займет обычное время
- Вторая сборка будет значительно быстрее (примерно на 90% быстрее сборки).
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
const clientWebpackConfig = {
// ...
plugins: [
new HardSourceWebpackPlugin({
// cacheDirectory是在高速缓存写入。默认情况下,将缓存存储在node_modules下的目录中
// 'node_modules/.cache/hard-source/[confighash]'
cacheDirectory: path.join(__dirname, './lib/.cache/hard-source/[confighash]'),
// configHash在启动webpack实例时转换webpack配置,
// 并用于cacheDirectory为不同的webpack配置构建不同的缓存
configHash: function(webpackConfig) {
// node-object-hash on npm can be used to build this.
return require('node-object-hash')({sort: false}).hash(webpackConfig);
},
// 当加载器、插件、其他构建时脚本或其他动态依赖项发生更改时,
// hard-source需要替换缓存以确保输出正确。
// environmentHash被用来确定这一点。如果散列与先前的构建不同,则将使用新的缓存
environmentHash: {
root: process.cwd(),
directories: [],
files: ['package-lock.json', 'yarn.lock'],
},
// An object. 控制来源
info: {
// 'none' or 'test'.
mode: 'none',
// 'debug', 'log', 'info', 'warn', or 'error'.
level: 'debug',
},
// Clean up large, old caches automatically.
cachePrune: {
// Caches younger than `maxAge` are not considered for deletion. They must
// be at least this (default: 2 days) old in milliseconds.
maxAge: 2 * 24 * 60 * 60 * 1000,
// All caches together must be larger than `sizeThreshold` before any
// caches will be deleted. Together they must be at least this
// (default: 50 MB) big in bytes.
sizeThreshold: 50 * 1024 * 1024
},
}),
new HardSourceWebpackPlugin.ExcludeModulePlugin([
{
test: /.*\.DS_Store/
}
]),
]
}
В-пятых, оптимизируйте время сжатия
1. webpack3
Добавить, когда webpack3 начинает упаковку--optimize-minimize
, так что Webpack автоматически внедрит UglifyJSPlugin с конфигурацией по умолчанию.
или:
module.exports = {
optimization: {
minimize: true,
},
}
Сжатие кода JavaScript требует сначала преобразовать код в синтаксическое дерево AST, представленное абстракцией объектов, а затем применить различные правила для анализа и обработки AST.Этот процесс требует больших вычислительных ресурсов и времени. ноUglifyJsPlugin
является однопоточным, поэтому мы можем использоватьParallelUglifyPlugin
.
ParallelUglifyPlugin
Плагин реализует многопроцессорное сжатие,ParallelUglifyPlugin
Будет открыто несколько подпроцессов, и работа по сжатию нескольких файлов будет распределена между несколькими подпроцессами для завершения, каждый подпроцесс фактически проходитUglifyJS
Чтобы сжать код, но в параллельное выполнение. такParallelUglifyPlugin
Сжатие нескольких файлов может быть выполнено быстрее.
2. webpack4
в вебпаке4webpack.optimize.UglifyJsPlugin
Устаревший.
Также не рекомендуется использовать ParallelUglifyPlugin, проект в основном в стадии необслуживания, вопрос не обрабатывается, пр не сливается.
встроенное использование webpack4 по умолчаниюterser-webpack-plugin
Плагин сжимает оптимизированный код, в то время как плагин используетterser
уменьшить масштабJavaScript
.
что лаконичнее?
Так называемый терсер, официальное определение:
Парсер JavaScript, набор инструментов менеджера/компрессора для ES6+.
Почему webpack выбирает лаконичный?
uglify-es больше не поддерживается, а uglify-js не поддерживает ES6+.
terser — это форк uglify-es, который в основном сохраняет совместимость API и CLI с uglify-es и uglify-js@3.
терсер запускает несколько процессов
Используйте несколько процессов для параллельной работы, чтобы увеличить скорость сборки. Количество одновременных запусков по умолчанию равноos.cpus().length - 1
.
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
parallel: true,
}),
],
},
};
Может значительно ускорить сборку, поэтому настоятельно рекомендуется включить многопроцессорность.
В-шестых, оптимизируйте время поиска — сузьте область поиска файлов, чтобы уменьшить ненужную работу по компиляции.
Когда веб-пакет упакован, он будет настроен изentry
Запустите, проанализируйте оператор импорта файла записи, а затем проанализируйте его рекурсивно.При встрече с оператором импорта веб-пакет сделает две вещи:
- Найдите соответствующий файл для импорта в соответствии с оператором импорта. Например
require('react')
Файл, соответствующий оператору импорта,./node_modules/react/react.js
,require('./util')
Соответствующий файл./util.js
. - Используйте загрузчик в конфигурации для обработки файла в соответствии с суффиксом импортируемого файла. Например, файлы JavaScript, разработанные с помощью ES6, необходимо обрабатывать с помощью babel-loader.
Хотя вышеупомянутые две вещи очень быстро обрабатывают файл, когда проект большой, количество файлов станет очень большим, и в это время будет выявлена проблема медленной скорости построения. Хотя нельзя избежать двух вышеупомянутых вещей, необходимо свести к минимуму возникновение двух вышеуказанных вещей, чтобы повысить скорость.
В следующих разделах описаны способы их оптимизации.
1. Оптимизировать конфигурацию загрузчика
При использовании Loader вы можете передатьtest
,include
,exclude
Три элемента конфигурации, которые нужно нажать на загрузчик, чтобы применить правила к файлу
2. Оптимизация конфигурации resolve.module
resolve.modules
Используется для настройки каталогов, в которые веб-пакет находит сторонние модули,resolve.modules
Значение по умолчанию['node_modules']
, что означает сначала перейти в текущий каталог./node_modules
Найдите искомый модуль в каталоге, если не найдете, перейдите в верхний каталог../node_modules
Найди, если не найдешь, иди../../node_modules
Найдите его и так далее.
3. Оптимизация конфигурации resolve.alias
resolve.alias
Элемент конфигурации сопоставляет исходный путь импорта с новым путем импорта через псевдонимы, сокращая трудоемкие операции рекурсивного синтаксического анализа.
4. Оптимизация конфигурации resolve.extensions
Когда оператор импорта не имеет суффикса файла, веб-пакет автоматически добавит суффикс в соответствии с разрешением.расширение, чтобы попытаться спросить, существует ли файл, поэтому в конфигурацииresolve.extensions
По возможности следует отметить следующие моменты:
-
resolve.extensions
Старайтесь, чтобы список был как можно меньше, и не записывайте то, что невозможно в элементе, в список пробных суффиксов. - Файловый суффикс с наибольшей частотой должен быть помещен вверху первым, чтобы выйти из процесса поиска как можно скорее.
- При написании оператора импорта в исходном коде необходимо максимально привести суффикс, чтобы избежать процесса поиска.
5. Оптимизируйте конфигурацию resolve.mainFields
Есть некоторые сторонние модули, которые предоставляют немного кода для разных сред. Например, два кода с использованием ES5 и ES6 предоставляются соответственно, и позиции этих двух кодов записываются вpackage.json
файл следующим образом:
{
"jsnext:main": "es/index.js",// 采用 ES6 语法的代码入口文件
"main": "lib/index.js" // 采用 ES5 语法的代码入口文件
}
веб-пакет будет основан наmainFields
конфигурация, чтобы решить, какой код использовать первым,mainFields
По умолчанию используется следующее:
mainFields: ['browser', 'main']
webpack пойдет по порядку в массивеpackage.json
При поиске в файле будет использован только первый найденный.
Если вы хотите сначала использовать код ES6, вы можете настроить его следующим образом:
mainFields: ['jsnext:main', 'browser', 'main']
6. Оптимизируйте конфигурацию module.noParse
module.noParse
Элемент конфигурации позволяет Webpack игнорировать рекурсивный анализ некоторых файлов, которые не имеют модульной структуры, что повышает производительность сборки. Причина в том, что некоторые библиотеки, такие как jQuery и ChartJS, огромны и не принимают модульных стандартов, что делает для Webpack трудоемким и бессмысленным анализ этих файлов.
7. Подробная конфигурация
// 编译代码的基础配置
module.exports = {
// ...
module: {
// 项目中使用的 jquery 并没有采用模块化标准,webpack 忽略它
noParse: /jquery/,
rules: [
{
// 这里编译 js、jsx
// 注意:如果项目源码中没有 jsx 文件就不要写 /\.jsx?$/,提升正则表达式性能
test: /\.(js|jsx)$/,
// babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
use: ['babel-loader?cacheDirectory'],
// 排除 node_modules 目录下的文件
// node_modules 目录下的文件都是采用的 ES5 语法,没必要再通过 Babel 去转换
exclude: /node_modules/,
},
]
},
resolve: {
// 设置模块导入规则,import/require时会直接在这些目录找文件
// 可以指明存放第三方模块的绝对路径,以减少寻找
modules: [
path.resolve(`${project}/client/components`),
path.resolve('h5_commonr/components'),
'node_modules'
],
// import导入时省略后缀
// 注意:尽可能的减少后缀尝试的可能性
extensions: ['.js', '.jsx', '.react.js', '.css', '.json'],
// import导入时别名,减少耗时的递归解析操作
alias: {
'@compontents': path.resolve(`${project}/compontents`),
}
},
};
Все вышеперечисленное является оптимизацией производительности сборки, связанной с сужением диапазона поиска файлов.После преобразования в соответствии с вышеуказанными методами в соответствии с потребностями вашего собственного проекта ваша скорость сборки определенно улучшится.
6. Прошлая серия вебпаков
Пять визуальных решений для анализа узких мест производительности упаковки webpack
Лучшее руководство по настройке для веб-пакета
Принцип снупинга: написание сборщика JavaScript от руки
Пришло время отказаться от Postman и попробовать собственный плагин артефактов VS Code 👏👏👏
Если вы считаете, что это хорошо, поставьте лайк! 👍👍👍
Хотите увидеть предыдущую серию статей,Нажмите, чтобы перейти на домашнюю страницу блога github.
7. Идти до конца
-
❤️ Получайте удовольствие, продолжайте учиться и всегда продолжайте программировать. 👨💻
-
Если у вас есть какие-либо вопросы или уникальные идеи, добро пожаловать в комментарии или свяжитесь с бутылкой напрямую (отсканируйте код, чтобы подписаться на официальный аккаунт, и ответьте на 123)! 👀👇
-
👇Приглашаем обратить внимание: джентльмен с бутылкой переднего плана, обновляется ежедневно! 👇