📜 ⬆️ ⬇️

SVG sprite with webpack in one line

A couple of days ago a fully updated version of the svg-sprite-loader was released - a webpack loader for creating SVG sprites. Inside, I will talk in detail about how it works and how it makes life easier for the developer.


Image as a module


For webpack, any file is a module. Following this concept, the svg-sprite-loader, configured to handle SVG, when importing a view:


import twitterLogo from './logos/twitter.svg'; 

will do the following: the contents of the image will be transformed into <symbol> and transferred to the module, which will generate client code for working with the symbol and sprite. This code will be called during the execution of the program as a normal module. The resulting code looks like this:


 //  ,          <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; 

Thus, the SVG file, being initially simple text, turns into an object with the id , viewBox and content fields, which can be further referred to by the <svg><use xlink:href="#id"></svg> markup. However, for this link to work, the sprite with all the characters must be part of the page. Remember the singleton object globalSprite, which stores all the characters in it? Its code looks like this:


 import BrowserSprite from '…'; const sprite = new BrowserSprite(); document.addEventListener('DOMContentLoaded', () => { sprite.mount(document.body); }); export default sprite; 

That is, the sprite will automatically be inserted into the page as soon as the DOMContentLoaded event DOMContentLoaded . With one line of image import, we converted it into an object and created a sprite, which itself will be drawn when and where necessary. Conveniently. However, you still have to refer to the symbol with your hands.


You can solve this problem using a template engine. Using the React example, let's create a component that renders the sprite symbol:


 // 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> ); } 

In combination with a loader, its use will look like this:


 import Icon from './icon.jsx'; import twitterLogo from './logos/twitter.svg'; <Icon glyph={twitterLogo.id} viewBox={twitterLogo.viewBox} /> 

Conveniently. However, you still have to specify the id of the symbol and the viewBox. But what if, when importing SVG, you return not a symbol object, but a component using this symbol for drawing? This is possible thanks to the runtimeGenerator option, which indicates the path to the Node.js module that generates the binding above the SVG image. An example of such a generator can be found here . It will produce the following code:


 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} />; } 

And then the image import already returns the React-component with the predefined properties:


 import TwitterLogo from './logos/twitter.svg'; render( <div> <TwitterLogo width="100" /> <TwitterLogo fill="red" /> <TwitterLogo fill="blue" style={{width: 600}} /> </div>, document.querySelector('.app') ); 

Conveniently. And all this is a single import line.


Server side rendering


Creating a sprite on the fly in a browser is certainly convenient, but what if the code that renders ui runs on the server? There are no DOM and browser events, how then the sprite will draw itself? For these purposes, svg-sprite-loader proposes to use an isomorphic version of the sprite that works in any execution environment. It does not know anything about the environment, so you need to manually draw the drawing. Example of page rendering on the server:


 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 }); 

In order to draw the sprite, you need to output the sprite variable, and to insert a symbol, use the symbols object:


 // layout.twig <body> {{ sprite }} // pages/about.twig <svg viewBox="{{ symbols.twitter.viewBox }}"> <use xlink:href="#{{ symbols.twitter.id }}" /> </svg> </body> 

In the case of React, everything will work just like in the browser, with the ability to compile the component on import.


Sprite as a separate file


But what if the use of the <svg><use … /></svg> construction in the markup is not possible or requires large rework? Everyone is already accustomed to using SVG as background-image, because if you don’t write SPA, then it’s more logical to import interface images from styles than from markup.


For such a case, a special mode is provided, which works quite differently and requires an additional plug-in (supplied with the loader). Suppose there is such an import:


 .logo { backgroung-image: url(./logos/twitter.svg); } 

Loader in combination with the plugin will do the following:


  1. All images will be converted to <symbol> and placed in a sprite, which will be created as a separate file (sprite.svg by default).


  2. All image imports will be replaced with the sprite path with the id of the character at the end:

 .logo { backgroung-image: url(sprite.svg#twitter); } 

This approach allows using the SVG stack technology, which is supported by all browsers except Safari (mobile and desktop) and the Android browser up to 4.4.4. But as always there is a polyfill .


Autoconfiguration


Loader has sufficient knowledge of the environment to understand the main thing:



This means that in most cases the standard configuration of the loader should suffice:


 //  webpack 1 module: { loaders: [ { test: /\.svg$/, loader: 'svg-sprite-loader' } ] } //  webpack 2 module: { rules: [ { test: /\.svg$/, loader: 'svg-sprite-loader' } ] } 

What else



Links



')

Source: https://habr.com/ru/post/327700/


All Articles