Вы, должно быть, видели много тестов электронных устройств, сегодня мы также проведем практическую оценку новой версии программного обеспечения - Webpack 5!
С 2017 года, когда было объявлено голосование по v5, до первой бета-версии в октябре 2019 года, в настоящее время 5.0.0-beta.16. Сейчас, в процессе сбора отзывов пользователей и экологического обновления, я думаю, что оно скоро будет официально выпущено. этот разОбновление фокуса: 性能改进、Tree Shaking、Code Generation、Module Federation。
Давайте проследим за журналом изменений, чтобы начать, и протестируем ключевое содержимое~
Оптимизировать постоянный кеш
Сначала кратко поговорим о концепции графа в Webpack:
Когда Webpack выполняется, он использует сконфигурированную запись в качестве записи, рекурсивно анализирует зависимости файлов, строит график и записывает взаимосвязь между модулями в коде. Каждый раз, когда файл обновляется, рекурсивный процесс начинается заново и график меняется.
Если вы просто и грубо перестроите график и перекомпилируете, это приведет к огромным потерям производительности. Webpack использует кэширование для добавочной компиляции, что повышает производительность сборки.
Основное содержимое в кеше (в виде памяти/диска) — это объекты модуля, а при компиляции граф будет храниться на жестком диске в виде бинарного или json-файла. Всякий раз, когда изменяется код, меняются зависимости между модулями и меняется график, Webpack считывает записи и выполняет инкрементную компиляцию.
Кэш можно настроить с помощью загрузчика до:
- использовать
cache-loader
Результат компиляции можно записать в кеш жесткого диска.При повторной сборке Webpack, если файл не изменился, он напрямую извлечет кеш - Есть также некоторые загрузчики с собственной конфигурацией кеша, такие как
babel-loader
, настраиваемые параметрыcacheDirectory
Используйте кеш для записи каждого результата компиляции на диск (по умолчанию в каталог node_modules/.cache/babel-loader)
Кэш по умолчанию в V5memory
, вы можете изменить настройки для записи на жесткий диск:
module.exports = {
cache: {
type: 'filesystem',
// cacheDirectory 默认路径是 node_modules/.cache/webpack
cacheDirectory: path.resolve(__dirname, '.temp_cache')
}
};
Примечание:
для большинстваnode_modules
Хеширование для построения зависимостей обходится дорого и замедляет выполнение Webpack. Чтобы избежать этой ситуации, Webpack добавил некоторые оптимизации, которые по умолчанию пропущены.node_modules
и использоватьpackage.json
серединаversion
а такжеname
в качестве источника данных.
Оптимизируйте долгосрочное кэширование
Цели Webpack 5moduleId
а такжеchunkId
Оптимизирован метод расчета, добавлена детерминированная стратегия генерации moduleId и chunkId. moduleId вычисляется в соответствии с путем контекстного модуля, chunkId вычисляется в соответствии с содержимым блока, и, наконец, для moduleId и chunkId генерируется 3-4-значный идентификатор для реализации долгосрочного кэширования, которое включено по умолчанию в производственной среде.
- Сравните исходный moduleId
Исходное значение по умолчанию для moduleId — это автоматически увеличивающийся идентификатор, что может легко привести к аннулированию файлового кэша.
До v4 можно было установитьHashedModuleIdsPlugin
Плагин переопределяет правило moduleId по умолчанию, которое использует хэш, сгенерированный путем к модулю, в качестве moduleId.
В v4 можно настроитьoptimization.moduleIds = 'hashed'
- Сравните исходный chunkId
Значение по умолчанию исходного chunkId увеличивается на id.
Например, в такой конфигурации при добавлении новой записи количество чанков также будет увеличиваться, а также увеличится chunkId.
можно установить раньшеNamedChunksPlugin
плагин для стабилизации chunkId или настроитьoptimization.chunkIds = 'named'
Скрипт NodeJS polyfill удален.
Изначально целью Webpack было разрешить запуск модулей Node в браузере. Но теперь, по мнению Webpack, большинство модулей разрабатываются специально для внешнего интерфейса. В v4 и предыдущих версиях скрипты полифилла автоматически добавляются в большинство модулей Node, а полифиллы добавляются в окончательный бандл, что обычно не требуется. Это поведение будет прекращено в v5.
Например следующий код:
// index.js
import sha256 from 'crypto-js/sha256';
const hashDigest = sha256('hello world');
console.log(hashDigest);
В v4 активно добавляетсяcrypto
полифилл, то естьcrypto-browserify
. Код, который мы запущены, не требуется, но окончательный пакет становится больше, а результат компиляции417 kb:
В v5, если это так, вам будет предложено подтвердить. Если вы подтвердите, что вам не нужен полифилл узла, следуйте инструкциям, чтобы установить для псевдонима значение false. Конечным результатом компиляции является только5.69 kb:
настроитьresolve.alias: { crypto: false }
:
Результат выполнения браузера:
Лучшее встряхивание дерева
Теперь есть этот кусок кода:
// inner.js
export const a = 'aaaaaaaaaa';
export const b = 'bbbbbbbbbb';
// module.js
import * as inner from "./inner";
export { inner };
// index.js
import * as module from "./module";
console.log(module.inner.a);
В v4 нет сомнений, что все переменные a и b приведенного выше кода упакованы:
но мы только что позвонилиa
переменная, в идеале она должна бытьb
определяется какunused
, не упакован. Эта оптимизация была реализована в v5.
Модули будут проанализированы в v5export
а такжеimport
Зависимости между окончательным генерированием кода очень лаконичны:
существенное изменение
Если вышеперечисленные изменения и оптимизации являются обычными способами, то следующие функции немного неожиданны.
Module Federation
Позвольте Webpack достичь эффекта онлайн-среды выполнения, позволяя напрямую обмениваться кодом между независимыми приложениями с помощью CDN, больше не нужно устанавливать пакет NPM локально, создавать и публиковать его!
Первоначальный замысел дизайна
Webpack соглашается с тем, что несколько отдельных сборок должны составлять приложение. Эти независимые сборки не зависят друг от друга и поэтому могут разрабатываться и развертываться отдельно. Это часто называют микроинтерфейсом, но это нечто большее.
Прежде чем мы сможем надеяться, это как поделиться кодом?
NPM
Поддерживайте NPM-пакет CommonComponents для установки и использования в различных проектах. Если пакет NPM обновлен, соответствующий проект должен установить новую версию, скомпилировать ее локально и упаковать в пакет.
UMD
Преимущество UMD во время выполнения. Недостатки также очевидны, объемная оптимизация неудобна, возможны конфликты версий.
микро интерфейс
Совместное использование между отдельными приложениями также является проблемой. Обычно существует два метода упаковки:
- Подприложения упаковываются независимо, а модули разъединяются, но общедоступные зависимости нелегко поддерживать и обрабатывать.
- Все приложение упаковано вместе, что может решить общие зависимости, однако большое количество проектов делает упаковку медленной, и ее нелегко расширять в будущем.
Webpack 5 реализует совершенно новое решение
Как видно из рисунка, эта схема заключается в прямом использовании связки одного приложения с другим.
Приложение может выводить модульно, то есть потреблять само себя или динамически распределять подмодули времени выполнения другим приложениям.
Теория более абстрактна, давайте попробуем.
практический тест
Теперь есть два приложенияapp1
(локальный хост: 3001),app2
(локальный хост: 3002):
Входной файл:
// app1 & app2: index.js
import App from "./App";
import React from "react";
import ReactDOM from "react-dom";
ReactDOM.render(<App />, document.getElementById("root"));
app2
произведеноButton
Компоненты:
// app2: Button.js
import React from "react";
const Button = () => <button>App 2 Button</button>;
export default Button;
app2
собственное потреблениеButton
Компоненты:
// app2: App.js
import LocalButton from "./Button";
import React from "react";
const App = () => (
<div>
<h1>Basic Host-Remote</h1>
<h2>App 2</h2>
<LocalButton />
</div>
);
export default App;
app1
Цитироватьapp2
изButton
Компоненты:
// app1: App.js
import React from "react";
const RemoteButton = React.lazy(() => import("app2/Button"));
const App = () => (
<div>
<h1>Basic Host-Remote</h1>
<h2>App 1</h2>
<React.Suspense fallback="Loading Button">
<RemoteButton />
</React.Suspense>
</div>
);
export default App;
Посмотрим на производствоButton
компонентapp2
, его конфигурационный файл:
// app2: webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { ModuleFederationPlugin } = require("webpack").container;
const path = require("path");
module.exports = {
entry: "./src/index",
mode: "development",
devServer: {
contentBase: path.join(__dirname, "dist"),
port: 3002,
},
output: {
publicPath: "http://localhost:3002/",
},
module: {
rules: [
// ...
],
},
plugins: [
new ModuleFederationPlugin({
name: "app2Lib",
library: { type: "var", name: "app2Lib" },
filename: "app2-remote-entry.js",
exposes: {
Button: "./src/Button",
},
shared: ["react", "react-dom"],
}),
new HtmlWebpackPlugin({
template: "./index.html",
}),
],
};
Эта конфигурация описывает необходимость выставитьButton
компоненты, зависимостиreact
,react-dom
. управлятьexposes
а такжеshared
Модульapp2Lib
, сгенерированный файл записи называетсяapp-remote-entry.js
.
app1
файл конфигурации:
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { ModuleFederationPlugin } = require("webpack").container;
const path = require("path");
module.exports = {
entry: "./src/index",
mode: "development",
devServer: {
contentBase: path.join(__dirname, "dist"),
port: 3001,
},
output: {
publicPath: "http://localhost:3001/",
},
module: {
rules: [
// ...
],
},
plugins: [
new ModuleFederationPlugin({
name: "app1",
library: { type: "var", name: "app1" },
remotes: {
app2: "app2Lib",
},
shared: ["react", "react-dom"],
}),
new HtmlWebpackPlugin({
template: "./index.html",
}),
],
};
Эта конфигурация описывает использование удаленного модуляapp2Lib
,полагатьсяreact
,react-dom
.
последний шаг:
существуетapp1
загрузить в htmlapp2-remote-entry.js
:
// app1: index.html
<html>
<head>
<script src="http://localhost:3002/app2-remote-entry.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
результат операции:
цитируетсяapp2/Button
Как ты это нашел?
пройти черезapp1
конфигурационный файл, знайapp2
является удаленной нагрузкой. в сгенерированномapp1 main.js
описанный как:
посмотреть здесьdata
множество:
data[1]
которыйwebpack/container/reference/app2
, вот возвратapp2Lib
Объект:
module.exports = app2Lib;
data[0]
которыйwebpack/container/remote-overrides/a46c3e
, который представлен здесьapp2
потребностиreact
,react-dom
зависит от и возвращаетсяapp2Lib
:
module.exports = (external) => {
if (external.override) {
external.override(Object.assign({
"react": () => {
return Promise.resolve().then(() => {
return () => __webpack_require__(/*! react */ "./node_modules/react/index.js")
})
},
"react-dom": () => {
return Promise.resolve().then(() => {
return () => __webpack_require__(/*! react-dom */ "./node_modules/react-dom/index.js")
})
}
}, __webpack_require__.O))
}
return external;
};
Итак, наконецpromise
Задание становится:
var promise = app2Lib.get('Button');
Глядя на это таким образом,app2Lib
Это глобальная переменная.
Продолжай читатьapp1
загруженapp2-remote-entry.js
содержание. Конечно, достаточно, глобальная переменная была сгенерированаapp2Lib
:
app2Lib
Объект имеет два метода, а именно:
var get = (module) => {
return (
__webpack_require__.o(moduleMap, module)
? moduleMap[module]()
: Promise.resolve().then(() => {
throw new Error('Module \"' + module + '\" does not exist in container.');
})
);
};
var override = (override) => {
Object.assign(__webpack_require__.O, override);
};
так,app2/Button
фактическиapp2Lib.get('Button')
, затем найти модуль по отображению, затем__webpack_require__
:
var moduleMap = {
"Button": () => {
return __webpack_require__.e("src_Button_js").then(() =>
() => __webpack_require__(/*! ./src/Button */ "./src/Button.js")
);
}
};
Наконец сказатьshared: ['react', 'react-dom']
:
app2
Указывает на необходимость полагаться наreact
,react-dom
, и ожидайте, что приложение-потребитель предоставит его. еслиapp1
Если указанная версия не указана или указанная версия не указана, код комментируется следующим образом:
plugins: [
new ModuleFederationPlugin({
name: "app1",
library: { type: "var", name: "app1" },
remotes: {
'app2': "app2Lib",
},
// shared: ["react", "react-dom"],
// 版本不一致同理
// shared: {
// "react-15": "react",
// "react-dom": "react-dom",
// },
}),
new HtmlWebpackPlugin({
template: "./index.html",
}),
]
Ну, только сейчасapp1 main.js
серединаdata[0]
которыйwebpack/container/remote-overrides/a46c3e
станет:
module.exports = (external) => {
if (external.override) {
external.override(__webpack_require__.O);
// external.override(Object.assign({
// "react": () => {
// return Promise.resolve().then(() => {
// return () => __webpack_require__(/*! react */ "./node_modules/react/index.js")
// })
// },
// "react-dom": () => {
// return Promise.resolve().then(() => {
// return () => __webpack_require__(/*! react-dom */ "./node_modules/react-dom/index.js")
// })
// }
// }, __webpack_require__.O))
}
return external;
};
app1
затем изapp2
нагрузкаreact
полагаться:
резюме, согласноapp2
настроенexposes
& shared
контент, генерировать соответствующие файлы модулей и отношения сопоставления модулей с помощью глобальных переменныхapp2Lib
получить доступ;app1
Глобальная переменнаяget
знать, как загрузитьbutton.js
,override
Модуль, который знает об общих зависимостях.
Выше на первый взгляд Federation выглядит как DLL + External, но преимущество в том, что вам не нужно вручную поддерживать и упаковывать зависимости, а код загружается во время выполнения.
В этом режиме отладка также проста, нет необходимости копировать-вставлять код илиnpm link
, просто запустите приложение.
Только здесьButton
Компонент в качестве примера,Button
Это может быть компонент, страница или приложение. Внедрение федерации модулей в сочетании с рядом задач, таких как автоматизированные процессы, также требует от каждого попрактиковаться в соответствующих сценариях.
практика исследования сообщества
- Примеры различных сложных сцен:GitHub.com/module — Поделитесь...
- Тенсент:Изучите применение новых функций webpack5, Объединение модулей, в документации Tencent.
- Муравей:Исследуйте объединенные модули, открывайте приложения за считанные секунды, решения для наборов приложений, улучшения решения для микроинтерфейсной загрузки и т. д.
- Байду:эксперимент по обновлению reskript webpack5
Другие преимущества
- Top Level Await
- SplitChunks поддерживает более гибкое разделение ресурсов.
- Чанки, не содержащие JS-код, больше не будут генерировать JS-файлы.
- Вывод генерирует код спецификации ES6 по умолчанию, а также поддерживает конфигурацию от 5 до 11.
- ......
Пожалуйста, прочитайте подробностиChanglog
Вышеуказанная демонстрация также официально предоставлена для ознакомления. Автор также обновил и протестировал свои собственные внутренние проекты, и в процессе этого может возникнуть несовместимость некоторых плагинов. В соответствии с официальными инструкциями журнала изменений вы можете найти ответ и временно изменить код соответствующего плагина. Если вы столкнулись с этим при попытке обновления, вы можете справиться с этим самостоятельно, а также сообщить об этом сообществу, чтобы совместно продвигать процесс выпуска новой версии.
В целом, после первоначального опыта Webpack 5 объем упаковки и скорость значительно улучшились, а настройка большинства функций также стала более удобной и ловкой.Федерация модулей бросается в глаза. Если вы заинтересованы, вы можете обменяться своими интерпретациями и исследованиями.
Если вам интересно узнать о новых вещах, например, специализироваться на технологиях, готовы поделиться и заинтересованы в продуктах для обмена мгновенными сообщениями, базовых механизмах настольных клиентов и базовой конструкции платформы, добро пожаловать к нам!
Автор статьи: Ван Синьюй
Бизнес ByteDance Feishu, массовый отклик, очень быстрый ответ, приходите и станьте моим коллегой~Введение в работу