Установка пакета
npm i @dmitriym09/web-minify --save
Думаю, самое лучшее описание библиотеки для разработчика — пример кода=)
Пример
web-minify — middleware-функция:
const htmlminify = require('html-minifier').minify;
const csso = require('csso').minify;
const postcss = require('postcss');
const precss = require('precss');
const autoprefixer = require('autoprefixer');
const minify = require('web-minify');
app.use(minify([
{
contentType: /css/,
minify: async (data, req, res) => {
let resData = (await postcss([precss, autoprefixer]).process(data, { from: undefined })).css;
resData = csso(resData).css;
return resData;
}
}
]));
В данном примере будут перехватываются все ответы с Content-Type, содержащие подстроку «css». Тело ответа обрабатывается с помощью csso, postcss, precss, autoprefixer. В параметре contentType передается String (будет искаться вхождение String.prototype.indexOf()) или RegExp (RegExp.prototype.test()). Параметр minify — функция Function(data:String, req:Request, res:Response), должна возвращать String с новым телом или Promise, который в свою очередь разрешается String. При неотловленом исключении клиент получит ответ 500.
Как уже сказал, большинство существующих популярных библиотек с похожим функционалом хорошо минифицирует статические файлы. Однако минификация сгенерированных в коде (например html шаблонизатором) ответов не работает. Одна из проблем — ответ может отправляться частями, а для обработки обычно нужны полные данные. Соответственно нужно перехватывать все отправки пользователю, собирать и уже в конце обрабатывать и отсылать. Это нужно учитывать при использовании web-minify: тот терабайтный файл, который вы хотите отправить клиенту и который попадает под contentType, накапливаться в памяти.
Примеры
Минификация HTML с помощью html-minifier из юнит-тестов
const htmlminify = require('html-minifier').minify;
it('HTML', (done) => {
const app = createServer([minify([
{
contentType: 'html',
minify: (data) => {
let res = htmlminify(data, {
removeAttributeQuotes: true,
collapseWhitespace: true,
conservativeCollapse: false,
decodeEntities: true,
keepClosingSlash: false,
preserveLineBreaks: false,
preventAttributesEscapin,
processConditionalComments: true,
removeAttributeQuotes: true,
removeComments: true,
trimCustomFragments: true,
useShortDoctype: true
});
return res;
}
}
])], function(req, res) {
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.end(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<h1>Test</h1>
<p>Test</p>
</body>`);
});
request(app)
.get('/')
.set('Accept', 'text/html; charset=utf-8')
.expect('Content-Type', 'text/html; charset=utf-8')
.expect('<!doctype html><html lang=en><head><meta charset=utf-8></head><body><h1>Test</h1><p>Test</p></body></html>')
.expect(200)
.end(done)
});
Модификации JSON и кода ответа с возвратом Promise из юнит-тестов
it('JSON', (done) => {
const app = createServer([minify([
{
contentType: /json/,
minify: (data, req, res) => {
return new Promise(function(resolve, reject) {
try {
res.statusCode = 456;
let o = JSON.parse(data);
o.dt = new Date('2018-09-28T11:05:13.492Z')
resolve(JSON.stringify(o))
}
catch(exc) {
reject(exc)
}
})
}
}
])], function(req, res) {
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({a: 12}));
});
request(app)
.get('/')
.set('Accept', 'applicatio3n/json; charset=utf-8')
.expect('Content-Type', 'application/json; charset=utf-8')
.expect('{"a":12,"dt":"2018-09-28T11:05:13.492Z"}')
.expect(456)
.end(done)
});
Web-minify доступна на github и в npm под лицензией MIT.
Спасибо за внимание! Критика, предложения и комментарии приветствуются!
Комментарии (18)
kolesoffac
05.10.2018 09:02Откройте для себя spa и ssr.
dmitriym09 Автор
05.10.2018 09:27Открыл уже давно — есть такие проекты у меня. Но считаю, что не серебрянная пуля и не панацея. Нужно исходить и задач и кейса. Обычно эти 3-х буквенные сокращения идут рядом с реактивностью, а она не всегда бывает нужна. Кроме того, не все серверные рендеры дают минифицированный html.
eugef
05.10.2018 09:11Вместо того, чтобы экономить на спичках и удалять из HTML и JSON пробелы и кавычки, лучше сжать ответ с помощью gzip. К тому же так можно сжимать потоки и не надо накапливать весь ответ (тем более 1ТБ в памяти).
Посмотрите например www.npmjs.com/package/compressiondmitriym09 Автор
05.10.2018 09:22«Экономия на спичках» дает в некоторых ситуациях сжатие до 10%.Cжатый html — это еще и плюс для SEO. Кроме того, одно другому не мешает, а дополняет: можно сначала пожать код, а потом gzip использовать. Я кстати так и делаю. Только я ставлю перед node приложением кеширующий проксер (nginx например), который и gzip-ает ответ клиенту. Кавычки из JSON не удаляются, а то это будет уже невалидный json)
eugef
05.10.2018 12:27Cжатый html — это еще и плюс для SEO
Можете предоставить ссылку на пруф, что именно сжатый HTML влияет на СЕО?
Где указано, что влияет именно HTML без лишних пробелов и кавычек, а не просто более оптимальный HTML без лишних тегов или более правильный семантически.
Кроме того, одно другому не мешает, а дополняет: можно сначала пожать код, а потом gzip использовать.
Давайте проведем эксперимент. Возьмем довольно большую страницу, например habr.com/post/425351 и сожмем ее по разному:
- Исходный размер HTML: 582KB
- Размер после html-minifier: 543KB (-7%)
- Размер после html-minifier и gzip: 78KB (-87%)
- Размер после одного gzip: 81KB (-86%)
Т.е. при условии, что HTML сжимается с помощь gzip, минификация дает крайне минимальный эффект (1% всего) — вот это я и называю «экономия на спичках».
Лучше процессорное время потратить на обработку другого запроса, чем минифицировать HTML (а тем более JSON)
я ставлю перед node приложением кеширующий проксер (nginx например)
Это, безусловно, правильное решение.dmitriym09 Автор
05.10.2018 12:58Можете предоставить ссылку на пруф, что именно сжатый HTML влияет на СЕО?
Где указано, что влияет именно HTML без лишних пробелов и кавычек, а не просто более оптимальный HTML без лишних тегов или более правильный семантически.
Не было такого утверждения. Было написано
Cжатый html — это еще и плюс для SEO.
Не раскрывалось в чем плюс.
… минифицировать HTML (а тем более JSON)
Где здесь минифицируется JSON??
Лучше процессорное время потратить на обработку другого запроса
И это лично ваше мнение. Очень сильно зависит от нагрузки и целей. Я думаю каждый разработчик сам должен принимать решение.
Т.е. при условии, что HTML сжимается с помощь gzip, минификация дает крайне минимальный эффект (1% всего) — вот это я и называю «экономия на спичках».
Опять все зависит от ситуации. Может кто-то хочет уместить ответ в минимальное количество tcp пакетов и 1% может повлиять на результат.
Вообще здесь приведены абстрактные решения в вакууме для демонстрации функциональности. Например, я воспользовался данной библиотекой для того, чтобы после после конструктора сайта, создающего довольно трешовый код, выдерать inline-стили и потом оптимизировать их. Естественно нужно думать головой когда и как что использовать, чтобы было лучше, а не хуже. Каждый разработчик принимает решение в зависимости от многих критериев для свое ситуации. И я считаю, что лишний инструмент, расширяющий возможности — это хорошо.eugef
05.10.2018 23:54Не было такого утверждения. Было написано
«Cжатый html — это еще и плюс для SEO.»
Ok, раскройте, в чем именно плюс. И желательно с ссылкой на пруф.
Koneru
Извините, за банальный вопрос, но зачем? Почему нельзя выполнить нужные операции один раз?
dmitriym09 Автор
Ну статику конечно можно и нужно пожать 1 раз и раздавать. Но есть же и динамический контент. Например html, созданный шаблонами. Конечно можно и его жать сразу в шаблонизаторе, но есть такой подход.
kalyukdo
Это должен делать какойнить дженкинс и складывать на сервер, зачем заставлять ноду это делать? зачем тянуть кучу лишних зависимостей в продакшин?
dmitriym09 Автор
Как какойнить дженкинс будет обрабатывать динамически создаваемый контент и складывать на сервер?
kalyukdo
Извините если я не понял вашу идею, но тогда давайте разберемся, если у Вас полная динамика css/html то минификацию нужно делать сразу на момент сохранения данных, но никак ни на момент каждого запроса к серверу, да я понимаю что у вас там возможно есть кэш, но при каждом запросе проверять данные из кеша не айс когда можно сразу nginx натравить на ваши динамические html/css и все.
Или же у вас css зависит от того какой пришел запрос?
dmitriym09 Автор
Есть статика, как, наверное, у любого сайта. Они собирается webpack. Сами html страницы генерятся при запросе с помощью шаблонов, в проекте о котором и шла в частности используется ejs. Можно конечно заморочиться и сделать так, чтобы после шаблонизатора получался уже минифицированный ответ. Но там есть нюансы и на мой вкус лучше обрабатывать уже полностью собранный ответ. Кеш конечно используется — я ставлю перед node-приложением nginx. Вообще эта библиотека не обязательно должна использоваться для минификации чего-то. Она просто позволяет хукнуть ответ так, чтобы было полное тело и что то можно сделать с ним перед отправкой. Можно, например, вместо webpack собирать бандлы прям на приложении при запросе, а потом кешировать их уже на проксире. Конечно тут куча сложностей и в большинстве случаев так делать не надо, но я вполне могу представить себе кейс где бы это было хорошо. Ну например код после какого-либо конструктора сайта. Или же нужно менять то, что генерит какая-нибудь 3rd-либа без возможности влезть в нее. Или же у вас есть кеча inline-стилей, которые можно собрать во внешний бандл. Естественно я решал не каждодневную задачу — она не стоит перед всеми, раз нет в стандартной реализации. В общем я решал проблему, не нашел удобного мне способа в стандартной реализации, навелосипедил и решил выложить в opensource. Может быть кому-нибудь поможет когда-нибудь. Естественно нужно думать головой когда и как ее использовать, чтобы было лучше, а не хуже.
Rahman
на практике пришли к выводу, что подобная оптимизация не входит в задачи приложения. остановились на PageSpeed, там оптимизация намного глубже чем просто пожать html/css/картинки.
dmitriym09 Автор
А что с динамическим контентом? Как его жмете?
Rahman
PageSpeed работает на уровне запросов, как прокси в nginx. Все равно как был получен контент, статический файл или на лету сгенерированный. Приложение отдает ответ на запрос, PageSpeed его обрабатывает, кеширует и т.д.
dmitriym09 Автор
Ну да, это удобно. А какие есть ограничения? Сколько запросов максимум? Платно\бесплатно?
Когда используешь PageSpeed для статистики сайта то он вроде кешируешь на какое-то время — сразу после внесения изменений не видит их. Как это решено через их API? На что он ориентируется для кеша?
Но нужно учитывать, что не все ответы можно отдавать 3-ей стороне. Бывает содержатся и приватные данные. Конечно их можно выдрать и отправлять отдельным запросом.
Например, я воспользовался данной библиотекой для того, чтобы после после конструктора сайта, создающего довольно трешовый код, выдерать inline-стили и потом оптимизировать их. Что-то уникальное часто руками приходится писать.
В общем любая проблема решается 100500 способами и каждый разработчик должен сам думать. А наличие большого разнообразия возможностей для решения — это хорошо.
Rahman
Несмотря на похожее название, между PageSpeed Insights (сервис оценки производительности страницы) и developers.google.com/speed/pagespeed/module достаточно большая разница. То, что я описываю, ставится на ваш локальный apache/nginx. Ни о api, ни о ограничении количества запросов речь не идет. Просто прокси + какая то собственная админка для управления (статистика, кеши и вот это все)
Относительно неплохо срабатывание не просто минимизация html, но и включение внешних javascript/css файлов непосредственно в страницу, сборка нескольких файлов в бандлы, оптимизация картинок и т.д. Более подробно лучше посмотреть в их документации.
Несомненно данное решение имеет право на жизнь. Модель middleware, встроенного в собственное приложение позволяет работать в любом окружении, где бы приложение не запускалось.
Моя мысль заключается в том, что подобная оптимизация для серьезного продукта более уместна вне приложения, а именно настройками окружения.