webpack
Одна из самых выдающихся возможностей веб-пакета, которая, помимо
JavaScript, также черезloaderпредставлятьлюбой другой тип документа.
Основные концепции Webpack:
-
Entry(Запись): первый шаг в выполнении сборки Webpack начнется с записи, которую можно абстрагировать во входные данные. -
Module(模块):在 Webpack 里一切皆模块,一个模块对应着一个文件。 Webpack будет рекурсивно находить все зависимые модули, начиная с настроенного Entry. -
Chunk(Code Chunk): Chunk состоит из нескольких модулей для слияния и разделения кода. -
Loader(Конвертер модулей): используется для преобразования исходного содержимого модуля в новое содержимое по мере необходимости. -
Plugin(Расширенный плагин): соответствующие события будут транслироваться в определенное время в процессе сборки Webpack, и плагины могут прослушивать эти события и изменять выходные результаты.
Процесс выполнения веб-пакета
От начала до конца webpack последовательно выполняет следующие процессы:
- Инициализация: парсинг параметров конфигурации webpack, производство
Compilerпример - Зарегистрировать плагин: вызвать плагин
applyметод, переданный плагинуcompilerСсылка на экземпляр, подключаемый модуль вызывает API, предоставленный Webpack через компилятор, чтобы подключаемый модуль мог отслеживать все последующие узлы событий. - Начать компиляцию: прочитать входной файл
- Разобрать файл: использовать
loaderРазобрать файл в абстрактное синтаксическое деревоAST - Генерация полагается на карте: идентифицировать зависимости для каждого файла (пересечения)
- Вывод: сгенерированный хороший код преобразования
chunk - Сгенерируйте окончательный упакованный файл
ps: Поскольку webpack динамически загружает все зависимости в соответствии с графом зависимостей, каждый модуль может четко выражать свои собственные зависимости, что позволяет избежать упаковки неиспользуемых модулей.
Babel
Babel — это набор инструментов, в основном используемый для преобразования кода версии ECMAScript 2015+ в обратно совместимый.JavaScriptсинтаксис для работы в текущих и старых браузерах или других средах:
Основная функция
- Преобразование синтаксиса
- пройти через
Polyfillспособ добавить отсутствующие функции в целевой среде (через@babel/polyfillмодуль) - преобразование исходного кода (
codemods)
основной модуль
-
@babel/parser: отвечает за разбор кода в абстрактное синтаксическое дерево. -
@babel/traverse: Инструмент для обхода абстрактного синтаксического дерева, мы можем разобрать определенный узел в синтаксическом дереве, а затем выполнить некоторые операции. -
@babel/core: преобразование кода, например, кода ES6 в режим ES5.
Результат объединения Webpack
В типичном приложении или сайте, созданном с помощью веб-пакета, есть три основных типа кода:
- Исходный код: вы или ваша команда написано исходный код.
- Зависимости: любые сторонние зависимости, от которых будет зависеть ваш исходный код.
libraryили "vendor«Код. - Управление файлами:
webpackизruntimeиспользоватьmanifestУправляйте всеми взаимодействиями модуля.
runtime: Когда модуль взаимодействия, модуль подключения требуетсяЛогика загрузки и парсинга. В том числе подключение загруженных модулей в браузере и логика выполнения лениво загруженных модулей.
manifest: когда компилятор начинает выполнять, анализировать и отображать приложение, он сохраняет подробные маркеры всех модулей. Этот набор данных называется «Манифест»,
После упаковки и отправки в браузер модуль анализируется и загружается во время выполнения через манифест. Какой бы синтаксис модуля вы ни выбрали, эти операторы import или require теперь переводятся вwebpack_requireметод, который указывает на идентификатор модуля. Используя данные в манифесте, среда выполнения сможет запросить идентификатор модуля и получить за ним соответствующий модуль.
в:
-
importилиrequireзаявление преобразуется в__webpack_require__ - Асинхронный импорт преобразуется в
require.ensure(Обёртка промисов будет использоваться в Webpack 4)
Сравнивать
-
gulpЭто средство запуска задач: оно используется для автоматизации общих задач разработки, таких как проверка проекта (lint), сборка (build) и тестирование (test) -
webpackЯвляется упаковщиком: Помогает взять JavaScript и таблицы стилей, готовые к развертыванию, и преобразовать их в удобный для браузера формат. Например, JavaScript может сжимать, разбивать фрагменты и откладывать загрузку.
Оптимизация веб-пакета
DllPlugin + DllReferencePlugin
Для того, чтобы сократить время строительства, раздельная упаковка.
Как DLLREMENCEPLUGIN, так и DLL Plugin Dllplugin используются в другом настроек WebPack.
DllPluginЭтот плагин создает пакет только для dll (dll-only-bundle) в дополнительной отдельной настройке веб-пакета. Этот плагин создаст файл с именем manifest.json, который используется для созданияDLLReferencePluginСопоставлены со связанными зависимостями.
webpack.vendor.config.js
new webpack.DllPlugin({
context: __dirname,
name: "[name]_[hash]",
path: path.join(__dirname, "manifest.json"),
})
webpack.app.config.js
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require("./manifest.json"),
name: "./my-dll.js",
scope: "xyz",
sourceType: "commonjs2"
})
CommonsChunkPlugin
Вынув общий модуль, окончательный синтезированный файл может быть загружен один раз в начале, а затем сохранен в кэше для последующего использования. Это дает прирост скорости, потому что браузер быстро извлекает общий код из кеша, а не загружает файл большего размера каждый раз, когда открывается новая страница.
Если общедоступный файл извлекается как файл, то когда пользователь посещает веб-страницу, загружает общедоступный файл, а затем посещает другие веб-страницы, которые зависят от общедоступного файла, кэш файла в браузере используется напрямую, поэтому что общедоступный файл используется только браузером. передать один раз.
entry: {
vendor: ["jquery", "other-lib"], // 明确第三方库
app: "./entry"
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
// filename: "vendor.js"
// (给 chunk 一个不同的名字)
minChunks: Infinity,
// (随着 entry chunk 越来越多,
// 这个配置保证没其它的模块会打包进 vendor chunk)
})
]
// 打包后的文件
<script src="vendor.js" charset="utf-8"></script>
<script src="app.js" charset="utf-8"></script>
UglifyJSPlugin
По сути, скаффолдинг включает в себя этот плагин, который будет анализировать синтаксическое дерево кода JS и понимать значение кода, чтобы удалить недопустимый код, удалить входной код журнала, сократить имена переменных и другие оптимизации.
const UglifyJSPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
//...
plugins: [
new UglifyJSPlugin({
compress: {
warnings: false, //删除无用代码时不输出警告
drop_console: true, //删除所有console语句,可以兼容IE
collapse_vars: true, //内嵌已定义但只使用一次的变量
reduce_vars: true, //提取使用多次但没定义的静态值到变量
},
output: {
beautify: false, //最紧凑的输出,不保留空格和制表符
comments: false, //删除所有注释
}
})
]
ExtractTextPlugin + PurifyCSSPlugin
ExtractTextPlugin извлекает текст (CSS) из пакетов в отдельные файлы, PurifyCSSPlugin очищает CSS (это не очень полезно)
module.exports = {
module: {
rules: [
{
test: /\.css$/,
loader: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
localIdentName: 'purify_[hash:base64:5]',
modules: true
}
}
]
})
}
]
},
plugins: [
...,
new PurifyCSSPlugin({
purifyOptions: {
whitelist: ['*purify*']
}
})
]
};
DefinePlugin
DefinePlugin может автоматически обнаруживать изменения окружающей среды с высокой эффективностью.
При фронтальной разработке требуются разные конфигурации в разных средах приложений. Например: мокер API в среде разработки, подделка данных в процессе тестирования и печать отладочной информации. Ручная обработка этой информации о конфигурации не только обременительна, но и чревата ошибками.
использоватьDefinePluginНастроенные глобальные константы
Обратите внимание, что поскольку этот плагин выполняет замену текста напрямую, указанное значение должно содержать фактические кавычки внутри самой строки. Как правило, есть два способа добиться этого эффекта, используя' "production" 'или используйтеJSON.stringify('production').
new webpack.DefinePlugin({
// 当然,在运行node服务器的时候就应该按环境来配置文件
// 下面模拟的测试环境运行配置
'process.env':JSON.stringify('dev'),
WP_CONF: JSON.stringify('dev'),
}),
тестовое заданиеDefinePlugin:записывать
if (WP_CONF === 'dev') {
console.log('This is dev');
} else {
console.log('This is prod');
}
после упаковкиWP_CONF === 'dev'будет скомпилирован вfalse
if (false) {
console.log('This is dev');
} else {
console.log('This is prod');
}
Очистить недоступные коды
при использованииDefinePluginПосле плагина упакованный код будет иметь много избыточности. в состоянии пройтиUglifyJsPluginОчистить недостижимый код.
[
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false, // 去除warning警告
dead_code: true, // 去除不可达代码
},
warnings: false
}
})
]
Окончательная упаковка и код упаковки станутconsole.log('This is prod')
Прикрепленная документация Uglify:GitHub.com/Секретарь, о, о, УГ в…
Используйте DefinePlugin для различения сред + UglifyJsPlugin для очистки недоступного кода, чтобы уменьшить размер упакованного кода.
HappyPack
HappyPackМогуВключить преобразование многопроцессорного загрузчикаЧтобы разбить задачу на несколько дочерних процессов, наконец, отправьте результат основному процессу.
использовать
exports.plugins = [
new HappyPack({
id: 'jsx',
threads: 4,
loaders: [ 'babel-loader' ]
}),
new HappyPack({
id: 'styles',
threads: 2,
loaders: [ 'style-loader', 'css-loader', 'less-loader' ]
})
];
exports.module.rules = [
{
test: /\.js$/,
use: 'happypack/loader?id=jsx'
},
{
test: /\.less$/,
use: 'happypack/loader?id=styles'
},
]
ParallelUglifyPlugin
ParallelUglifyPluginМогуВключить многопроцессорное сжатие файлов JS
import ParallelUglifyPlugin from 'webpack-parallel-uglify-plugin';
module.exports = {
plugins: [
new ParallelUglifyPlugin({
test,
include,
exclude,
cacheDir,
workerCount,
sourceMap,
uglifyJS: {
},
uglifyES: {
}
}),
],
};
BundleAnalyzerPlugin
Плагин анализа результатов упаковки webpack
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
Внешние расширения (externals)
Это не плагин, это опция конфигурации wenpack
Параметр конфигурации externals обеспечивает способ «исключен из выходного пакета, зависящего от». Вместо этого создается пакет зависимых от зависимых от наличия пользовательской среды (среды потребителя) т.е. Эта функция часто наиболее полезна для разработчиков библиотек, но она будет использоваться в самых разных приложениях.
entry: {
entry: './src/main.js',
vendor: ['vue', 'vue-router', 'vuex']
},
externals: {
// 从输出的 bundle 中排除 echarts 依赖
echarts: 'echarts',
}
test & include & exclude
Уменьшена область поиска файлов для повышения скорости.
Пример
{
test: /\.css$/,
include: [
path.resolve(__dirname, "app/styles"),
path.resolve(__dirname, "vendor/styles")
]
}
Принципиальный анализ Webpack HMR
Горячая замена модуля (сокращенно HMR)
Содержит следующее:
- Карта обновления тепла
- Шаги горячего обновления объясняют
Первый шаг: webpack следит за файловой системой и упаковывает ее в память
webpack-dev-middleware вызывает API веб-пакета для наблюдения за файловой системой.Когда файл изменяется, веб-пакет перекомпилирует и упаковывает файл, а затем сохраняет его в памяти.
webpack упаковывает файл bundle.js в память. Причина того, что файл не создается, заключается в том, что доступ к коду в памяти быстрее, чем доступ к файлу в файловой системе, а также это снижает накладные расходы на запись кода в файл.
Все благодаряmemory-fs, memory-fs является зависимой библиотекой webpack-dev-middleware, webpack-dev-middleware заменяет исходную outputFileSystem из webpack экземпляром MemoryFileSystem, так что код будет выводиться в память.
Исходный код этой части в webpack-dev-middleware выглядит следующим образом:
// compiler
// webpack-dev-middleware/lib/Shared.js
var isMemoryFs = !compiler.compilers &&
compiler.outputFileSystem instanceof MemoryFileSystem;
if(isMemoryFs) {
fs = compiler.outputFileSystem;
} else {
fs = compiler.outputFileSystem = new MemoryFileSystem();
}
Шаг 2: devServer уведомляет браузер об изменении файла
При запуске DevServer,sockjsНа стороне сервера и браузера создается длинное соединение WebSocket, чтобы информировать браузер о необходимости компилировать WebPack и пакеты.Наиболее важными шагами являются WebPack-Dev-Server, вызывающий событие Compile Done монитора API WebPack, когда компиляция завершена. Затем WebPack -Dev-Server отправляет новое значение HASH модуля после компиляции пакетов в браузер.
// webpack-dev-server/lib/Server.js
compiler.plugin('done', (stats) => {
// stats.hash 是最新打包文件的 hash 值
this._sendStats(this.sockets, stats.toJson(clientStats));
this._stats = stats;
});
...
Server.prototype._sendStats = function (sockets, stats, force) {
if (!force && stats &&
(!stats.errors || stats.errors.length === 0) && stats.assets &&
stats.assets.every(asset => !asset.emitted)
) { return this.sockWrite(sockets, 'still-ok'); }
// 调用 sockWrite 方法将 hash 值通过 websocket 发送到浏览器端
this.sockWrite(sockets, 'hash', stats.hash);
if (stats.errors.length > 0) { this.sockWrite(sockets, 'errors', stats.errors); }
else if (stats.warnings.length > 0) { this.sockWrite(sockets, 'warnings', stats.warnings); } else { this.sockWrite(sockets, 'ok'); }
};
Шаг 3: webpack-dev-server/client получает сообщение сервера и отвечает
webpack-dev-server изменяет атрибут entry в конфигурации webpack и добавляет код webpack-dev-client, так что код для получения сообщений websocket будет в итоговом файле bundle.js.
Когда webpack-dev-server/client получает сообщение типа хэш, он временно сохраняет хеш-значение и выполняет операцию перезагрузки приложения после получения сообщения типа ok.
В операции перезагрузки webpack-dev-server/client решит, следует ли обновить браузер или выполнить горячее обновление (HMR) кода в соответствии с горячей конфигурацией. код показывает, как показано ниже:
// webpack-dev-server/client/index.js
hash: function msgHash(hash) {
currentHash = hash;
},
ok: function msgOk() {
// ...
reloadApp();
},
// ...
function reloadApp() {
// ...
if (hot) {
log.info('[WDS] App hot update...');
const hotEmitter = require('webpack/hot/emitter');
hotEmitter.emit('webpackHotUpdate', currentHash);
// ...
} else {
log.info('[WDS] App updated. Reloading...');
self.location.reload();
}
}
Шаг 4: webpack получает последнюю проверку хеш-значения и запрашивает код модуля.
Первый webpack/hot/dev-сервер (далее dev-сервер) слушает Шаг webpack-dev-сервер/клиент отправленwebpackHotUpdateсообщение, вызовите метод проверки в webpack/lib/HotModuleReplacement.runtime (сокращенно среда выполнения HMR), чтобы определить, есть ли новое обновление.
В процессе проверки используются два метода: hotDownloadManifest и hotDownloadUpdateChunk в файле webpack/lib/JsonpMainTemplate.runtime (называемый средой выполнения jsonp).
hotDownloadManifest должен вызвать AJAX, чтобы спросить сервер, есть ли обновленный файл, и если есть список обновленных файлов, он будет отправлен обратно в браузер. Этот метод возвращает последнее хеш-значение.
hotDownloadUpdateChunk запрашивает последний код модуля через jsonp, а затем возвращает код в среду выполнения HMR.Среда выполнения HMR выполнит дальнейшую обработку на основе возвращенного кода нового модуля, что может быть связано с обновлением страницы или горячим обновлением модуля. Этот метод возвращает блок кода, соответствующий последнему хеш-значению.
Наконец, новый блок кода возвращается в среду выполнения HMR для горячего обновления модуля.
PS: Почему код обновления модуля отправляется не напрямую в браузер на третьем шаге по вебсокету, а через jsonp?
Насколько я понимаю, разделение функциональных блоков, каждый модуль выполняет свои обязанности, dev-сервер/клиент отвечает только за передачу сообщений, а не за приобретение новых модулей, и эти задачи должна выполнять среда выполнения HMR. , а среда выполнения HMR должна быть местом для получения нового кода. И из-за того, что не используется webpack-dev-server, использование webpack-hot-middleware и webpack также может завершить процесс горячего обновления модуля.В использовании webpack-hot-middleware есть интересная вещь, он не использует websocket, и используется ли EventSource. Подводя итог, в рабочем процессе HMR новый код модуля не должен помещаться в сообщения веб-сокета.
Шаг 5: HotModuleReplacement.runtime для горячего обновления модуля
Этот шаг является ключевым во всем горячем обновлении модуля (HMR), а горячее обновление модуля происходит в методе hotApply среды выполнения HMR.
// webpack/lib/HotModuleReplacement.runtime
function hotApply() {
// ...
var idx;
var queue = outdatedModules.slice();
while(queue.length > 0) {
moduleId = queue.pop();
module = installedModules[moduleId];
// ...
// remove module from cache
delete installedModules[moduleId];
// when disposing there is no need to call dispose handler
delete outdatedDependencies[moduleId];
// remove "parents" references from all children
for(j = 0; j < module.children.length; j++) {
var child = installedModules[module.children[j]];
if(!child) continue;
idx = child.parents.indexOf(moduleId);
if(idx >= 0) {
child.parents.splice(idx, 1);
}
}
}
// ...
// insert new code
for(moduleId in appliedUpdate) {
if(Object.prototype.hasOwnProperty.call(appliedUpdate, moduleId)) {
modules[moduleId] = appliedUpdate[moduleId];
}
}
// ...
}
Обработка ошибок при горячем обновлении модуля. Если при горячем обновлении возникает ошибка, горячее обновление откатывается к обновлению браузера. Эта часть кода находится в коде dev-сервера. Краткий код выглядит следующим образом:
module.hot.check(true).then(function(updatedModules) {
if(!updatedModules) {
return window.location.reload();
}
// ...
}).catch(function(err) {
var status = module.hot.status();
if(["abort", "fail"].indexOf(status) >= 0) {
window.location.reload();
}
});
Шаг 6: Что должен делать бизнес-код?
После замены старого модуля кодом нового модуля наш бизнес-код не знает, что код изменился, то есть при изменении файла hello.js нам нужно вызвать метод accept HMR в файле index.js. file., добавьте функцию обработки после обновления модуля и вовремя вставьте возвращаемое значение метода hello на страницу. код показывает, как показано ниже
// index.js
if(module.hot) {
module.hot.accept('./hello.js', function() {
div.innerHTML = hello()
})
}