Пару дней назад вышла полностью обновлённая версия svg-sprite-loader — webpack лоадера для создания SVG спрайтов. Внутри я подробно рассскажу о том как он работает и чем облегчает жизнь разработчику.
Изображение как модуль
Для webpack любой файл — модуль. Следуя этой концепции svg-sprite-loader, настроенный на обработку SVG, при импорте вида:
import twitterLogo from './logos/twitter.svg';
сделает следующее: содержимое изображения будет трансформировано в <symbol> и передано в модуль, который сгенерирует клиентский код для работы с символом и спрайтом. Этот код будет вызван во время исполнения программы как обычный модуль. Полученный код выглядит примерно так:
// Импорт класса, в который будет обёрнуто содержимое изображения после преобразования в <symbol> import SpriteSymbol from 'svg-sprite-loader/runtime/symbol'; // Импорт объекта-синглтона, который представляет собой SVG спрайт и играет роль глобального хранилища символов import globalSprite from 'svg-sprite-loader/runtime/browser-sprite'; // Создание экземпляра класса символа const symbol = new SpriteSymbol({ /* symbol data */ }); // Добавление символа в спрайт globalSprite.add(symbol); // Модуль возвращает созданный экземпляр класса SpriteSymbol export default symbol;
Таким образом SVG файл, будучи изначально простым текстом, превращается в объект с полями id, viewBox и content, на который в дальнейшем можно сослаться разметкой <svg><use xlink:href="#id"></svg>. Однако, чтобы такая ссылка работала, спрайт со всеми символами должен быть частью страницы. Помните объект-синглтон globalSprite, который хранит в себе все символы? Его код выглядит примерно так:
import BrowserSprite from '…'; const sprite = new BrowserSprite(); document.addEventListener('DOMContentLoaded', () => { sprite.mount(document.body); }); export default sprite;
То есть спрайт автоматически вставится в страницу как только произойдёт событие DOMContentLoaded. Одной строкой импорта изображения мы преобразовали его в объект и создали спрайт, который сам отрисуется когда и куда надо. Удобно. Однако ссылаться на символ всё ещё приходится руками.
Решить эту проблему можно используя шаблонизатор. На примере React создадим компонент, который отрисовывает символ спрайта:
// icon.jsx export default function Icon({glyph, viewBox = '0 0 16 16', className = 'icon', …props}){ return ( <svg className={className} viewBox={viewBox} {…props}> <use xlinkHref={`#${glyph}`} /> </svg> ); }
В сочетании с лоадером его использование будет выглядеть так:
import Icon from './icon.jsx'; import twitterLogo from './logos/twitter.svg'; <Icon glyph={twitterLogo.id} viewBox={twitterLogo.viewBox} />
Удобно. Однако всё ещё приходится указывать id символа и viewBox. А что если при импорте SVG возвращать не объект символа, а компонент использующий этот символ для отрисовки? Это возможно благодаря опции runtimeGenerator, которая указывает путь к Node.js модулю генерирующему обвязку над SVG изображением. Пример такого генератора можно посмотреть тут. Он произведёт следующий код:
import React from 'react'; import SpriteSymbol from 'runtime/symbol'; import globalSprite from 'runtime/global-browser-sprite'; import Icon from './icon.jsx'; const symbol = new SpriteSymbol({ /* symbol data */ }); globalSprite.add(symbol); export default function TwitterIcon({…props}) { return <Icon glyph={symbol.id} viewBox={symbol.viewBox} {…props} />; }
И тогда импорт изображения уже вернёт React-компонент с предустановленными свойствами:
import TwitterLogo from './logos/twitter.svg'; render( <div> <TwitterLogo width="100" /> <TwitterLogo fill="red" /> <TwitterLogo fill="blue" style={{width: 600}} /> </div>, document.querySelector('.app') );
Удобно. И всё это одной строкой импорта.
Server side rendering
Создавать спрайт на лету в браузере конечно удобно, но что делать если код отрисовывающий ui работает на сервере? Там нет DOM и браузерных событий, как тогда спрайт отрисует сам себя? Для этих целей svg-sprite-loader предлагает использовать изоморфный вариант спрайта, работающий в любой среде исполнения. Он ничего не знает про окружение, поэтому вызывать отрисовку нужно вручную. Пример отрисовки страницы на сервере:
import template from 'page-view.twig'; import globalSprite from 'svg-sprite-loader/runtime/sprite'; // Конвертируем массив символов спрайта в объект где ключами выступают id символов const symbols = globalSprite.symbols.reduce((acc, s) => { acc[s.id] = s; return acc; }, {}); // Конвертируем спрайт в строку вида '<svg><symbol id="…">…</symbol>…</svg>' const sprite = sprite.stringify(); const content = template.render({ sprite, symbols });
В для отрисовки спрайта нужно вывести переменную sprite, а для вставки символа использовать объект symbols:
// layout.twig <body> {{ sprite }} // pages/about.twig <svg viewBox="{{ symbols.twitter.viewBox }}"> <use xlink:href="#{{ symbols.twitter.id }}" /> </svg> </body>
В случае с React всё будет работать также как в браузере, с возможностью скомпилировать компонент при импорте.
Спрайт как отдельный файл
Но что если использование конструкции <svg><use … /></svg> в разметке не возможно или требует больших переделок? Все уже привыкли к использованию SVG в качестве background-image, ведь если вы не пишете SPA, то интерфейсные изображения логичнее импортировать из стилей, чем из разметки.
Для такого случая предусмотрен специальный режим, который работает совсем по-другому и требует наличия дополнительного плагина (идущего в поставке с лоадером). Предположим имеется такой импорт:
.logo { backgroung-image: url(./logos/twitter.svg); }
Лоадер в комбинации с плагином сделают следующее:
- Все изображения будут конвертированы в
<symbol>и помещены в спрайт, который будет создан в виде отдельного файла (sprite.svg по умолчанию). - Все импорты изображений будут заменены на путь к спрайту с id символа на конце:
.logo { backgroung-image: url(sprite.svg#twitter); }
Такой подход позволяет применить технику SVG стеков, которая поддерживается всеми браузерами кроме Safari (мобильным и десктопным) и Android browser вплоть до 4.4.4. Но как всегда есть полифилл.
Автоконфигурирование
Лоадер обладает достаточными знаниями об окружении, чтобы понять главное:
- Extract-режим — если SVG импортируется из CSS/SCSS/LESS/Stylus/HTML — спрайт будет создаваться в виде отдельного файла.
- Окружение — если код собирается webpack’ом под браузер (target: browser), будет использован модуль спрайта для браузера. В противном случае — его изоморфный вариант.
- Формат экспорта модуля — для webpack 1 лоадер генерирует такой экспорт
module.exports = …, для webpack 2 и старшеexport default ….
Это значит что в большинстве случаев стандартной настройки лоадера должно хватить:
// для webpack 1 module: { loaders: [ { test: /\.svg$/, loader: 'svg-sprite-loader' } ] } // для webpack 2 module: { rules: [ { test: /\.svg$/, loader: 'svg-sprite-loader' } ] }
Что ещё
- Модуль спрайта для браузера решает многие проблемы работы с SVG спрайтами из коробки. Смотрите его конфиг и исходный код.
- В extract режиме лоадера есть возможность создать сколько угодно спрайтов или по спрайту для каждого чанка.
Ссылки
ссылка на оригинал статьи https://habrahabr.ru/post/327700/
Добавить комментарий