Skip to content

Commit

Permalink
feat: enable spritesheet creation outside of the rendered app
Browse files Browse the repository at this point in the history
  • Loading branch information
Kalle Ott committed Jun 30, 2020
1 parent 2a9ad73 commit c900765
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 94 deletions.
47 changes: 0 additions & 47 deletions .github/workflows/npm-publish.yml

This file was deleted.

14 changes: 14 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: Node.js Package

on: push

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
- run: yarn --pure-lockfile
- run: yarn test
17 changes: 6 additions & 11 deletions example/src/server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import fastify from 'fastify';
import { renderToString } from 'react-dom/server';
import fastifyStatic from 'fastify-static';
import {
renderSpriteSheetToString,
SpriteContextProvider,
IconsCache,
createSpriteSheetString,
} from 'react-lazy-svg';
import { readSvg } from './serverLoadSvg';

Expand All @@ -25,9 +25,11 @@ server.get('/*', async (_, res) => {
const markup = renderToString(
<SpriteContextProvider loadSVG={readSvg} knownIcons={sessionIcons}>
<App />
</SpriteContextProvider>
</SpriteContextProvider>,
);

const spriteSheet = await createSpriteSheetString(sessionIcons);

if (context.url) {
res.redirect(context.url);
} else {
Expand All @@ -51,18 +53,11 @@ server.get('/*', async (_, res) => {
</head>
<body>
<div id="root">${markup}</div>
${spriteSheet}
</body>
</html>`;

const extended = await renderSpriteSheetToString(
renderedHtml,
sessionIcons
);

res
.type('text/html')
.status(200)
.send(extended);
res.type('text/html').status(200).send(renderedHtml);
}
});

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"url": "https://github.com/kaoDev/"
},
"description": "react-lazy-svg is a simple way to use SVGs with the performance benefits of a sprite-sheet and svg css styling possibilities. Without bloating the bundle. It automatically creates a sprite-sheet for all used SVGs on the client but also provides a function to create a server side rendered sprite-sheet for icons used in the first paint.",
"version": "1.0.1",
"version": "1.1.0",
"main": "dist/index.js",
"module": "dist/react-lazy-svg.esm.js",
"typings": "dist/index.d.ts",
Expand Down
88 changes: 54 additions & 34 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,32 @@ import React, {
useEffect,
useCallback,
useMemo,
useRef,
} from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { createPortal } from 'react-dom';

const spriteSheetId = '__SVG_SPRITE_SHEET__';

const ssrEmptySpriteSheet = `<svg id="${spriteSheetId}" style="display:none"></svg>`;
const internalSpriteSheetId = '__SVG_SPRITE_SHEET__';
const isSSR = typeof document === 'undefined';

const globalIconsCache: IconsCache = new Map();

export const renderSpriteSheetToString = async (
markupString: string,
knownIcons: IconsCache,
) => {
export const createSpriteSheetString = async (knownIcons: IconsCache) => {
const arr = await Promise.all(Array.from(knownIcons.values()));

const spriteSheet = renderToStaticMarkup(
return renderToStaticMarkup(
<SpriteSheet icons={arr.filter((a): a is IconData => a != null)} />,
);
};

export const renderSpriteSheetToString = async (
markupString: string,
knownIcons: IconsCache,
spriteSheetId = internalSpriteSheetId,
) => {
const spriteSheet = await createSpriteSheetString(knownIcons);

const ssrEmptySpriteSheet = `<svg id="${spriteSheetId}" style="display:none"></svg>`;
return markupString.replace(ssrEmptySpriteSheet, spriteSheet);
};

Expand Down Expand Up @@ -135,12 +142,14 @@ export interface SpriteContext {
*/
loadSVG: (url: string) => Promise<string | undefined>;
knownIcons?: IconsCache;
embeddedSSR?: boolean;
}

export const SpriteContextProvider: FC<SpriteContext> = ({
children,
loadSVG,
knownIcons = globalIconsCache,
embeddedSSR = false,
}) => {
const icons = useIcons();

Expand All @@ -162,7 +171,7 @@ export const SpriteContextProvider: FC<SpriteContext> = ({
return (
<spriteContext.Provider value={contextValue}>
{children}
<SpriteSheet icons={icons}></SpriteSheet>
{(!isSSR || embeddedSSR) && <SpriteSheet icons={icons}></SpriteSheet>}
</spriteContext.Provider>
);
};
Expand All @@ -173,7 +182,7 @@ export const Icon: FC<{ url: string } & React.SVGProps<SVGSVGElement>> = ({
}) => {
const { registerSVG } = useContext(spriteContext);

if (typeof document === 'undefined') {
if (isSSR) {
registerSVG(url);
} else {
useEffect(() => {
Expand All @@ -189,31 +198,39 @@ export const Icon: FC<{ url: string } & React.SVGProps<SVGSVGElement>> = ({
};

const hidden = { display: 'none' };
const SpriteSheet: FC<{ icons: IconData[] }> = ({ icons }) => {
const SpriteSheet: FC<{
icons: IconData[];
spriteSheetId?: string;
}> = ({ icons, spriteSheetId = internalSpriteSheetId }) => {
const spriteSheetContainer = useRef(
!isSSR ? document.getElementById(spriteSheetId) : null,
);

const renderedIcons = icons.map(
({
id,
svgString,
attributes: { width, height, ['xmlns:xlink']: xmlnsXlink, ...attributes },
}) => {
return (
<symbol
key={id}
id={id}
xmlnsXlink={xmlnsXlink}
{...attributes}
dangerouslySetInnerHTML={svgString}
/>
);
},
);

if (spriteSheetContainer.current) {
return createPortal(renderedIcons, spriteSheetContainer.current);
}

return (
<svg id={spriteSheetId} style={hidden}>
{icons.map(
({
id,
svgString,
attributes: {
width,
height,
['xmlns:xlink']: xmlnsXlink,
...attributes
},
}) => {
return (
<symbol
key={id}
id={id}
xmlnsXlink={xmlnsXlink}
{...attributes}
dangerouslySetInnerHTML={svgString}
/>
);
},
)}
{renderedIcons}
</svg>
);
};
Expand All @@ -228,7 +245,10 @@ const mapNodeAttributes = (rawAttributes: NamedNodeMap) =>
{},
);

export const initOnClient = (knownIcons: IconsCache = globalIconsCache) => {
export const initOnClient = (
knownIcons: IconsCache = globalIconsCache,
spriteSheetId = internalSpriteSheetId,
) => {
knownIcons.clear();
const spriteSheet = document.getElementById(spriteSheetId);

Expand Down
36 changes: 35 additions & 1 deletion test/ssr.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
SpriteContextProvider,
IconsCache,
renderSpriteSheetToString,
createSpriteSheetString,
} from '../src/index';
import { renderToString } from 'react-dom/server';

Expand All @@ -31,7 +32,7 @@ const loadSVG = async (url: string) => {
test('render loaded svgs to a svg sprite sheet string', async () => {
const cache: IconsCache = new Map();
const renderedString = renderToString(
<SpriteContextProvider knownIcons={cache} loadSVG={loadSVG}>
<SpriteContextProvider embeddedSSR knownIcons={cache} loadSVG={loadSVG}>
<Icon url={'1'}></Icon>
</SpriteContextProvider>,
);
Expand All @@ -45,3 +46,36 @@ test('render loaded svgs to a svg sprite sheet string', async () => {
`"<svg><use xlink:href=\\"#1\\"></use></svg><svg id=\\"__SVG_SPRITE_SHEET__\\" style=\\"display:none\\"><symbol id=\\"1\\" xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 24 24\\"><path d=\\"M0 0h24v24H0z\\" fill=\\"none\\"/></symbol></svg>"`,
);
});

test('should not render an embedded sprite sheet when not explicitly asked for', async () => {
const cache: IconsCache = new Map();
const renderedString = renderToString(
<SpriteContextProvider knownIcons={cache} loadSVG={loadSVG}>
<Icon url={'1'}></Icon>
</SpriteContextProvider>,
);

const renderedSpriteSheet = await renderSpriteSheetToString(
renderedString,
cache,
);

expect(renderedSpriteSheet).toMatchInlineSnapshot(
`"<svg><use xlink:href=\\"#1\\"></use></svg>"`,
);
});

test('render loaded svgs to a svg sprite sheet string', async () => {
const cache: IconsCache = new Map();
renderToString(
<SpriteContextProvider embeddedSSR knownIcons={cache} loadSVG={loadSVG}>
<Icon url={'1'}></Icon>
</SpriteContextProvider>,
);

const renderedSpriteSheet = await createSpriteSheetString(cache);

expect(renderedSpriteSheet).toMatchInlineSnapshot(
`"<svg id=\\"__SVG_SPRITE_SHEET__\\" style=\\"display:none\\"><symbol id=\\"1\\" xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 24 24\\"><path d=\\"M0 0h24v24H0z\\" fill=\\"none\\"/></symbol></svg>"`,
);
});

0 comments on commit c900765

Please sign in to comment.