diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml deleted file mode 100644 index f52fe20..0000000 --- a/.github/workflows/npm-publish.yml +++ /dev/null @@ -1,47 +0,0 @@ -# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created -# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages - -name: Node.js Package - -on: - release: - types: [created] - -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 - - publish-npm: - needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: - node-version: 12 - registry-url: https://registry.npmjs.org/ - - run: yarn --pure-lockfile - - run: npm publish - env: - NODE_AUTH_TOKEN: ${{secrets.npm_token}} - - publish-gpr: - needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: - node-version: 12 - registry-url: https://npm.pkg.github.com/ - - run: yarn --pure-lockfile - - run: npm publish - env: - NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..73471bf --- /dev/null +++ b/.github/workflows/test.yml @@ -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 diff --git a/example/src/server.tsx b/example/src/server.tsx index 00ff360..a14593b 100644 --- a/example/src/server.tsx +++ b/example/src/server.tsx @@ -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'; @@ -25,9 +25,11 @@ server.get('/*', async (_, res) => { const markup = renderToString( - + , ); + const spriteSheet = await createSpriteSheetString(sessionIcons); + if (context.url) { res.redirect(context.url); } else { @@ -51,18 +53,11 @@ server.get('/*', async (_, res) => {
${markup}
+ ${spriteSheet} `; - const extended = await renderSpriteSheetToString( - renderedHtml, - sessionIcons - ); - - res - .type('text/html') - .status(200) - .send(extended); + res.type('text/html').status(200).send(renderedHtml); } }); diff --git a/package.json b/package.json index d315178..c397c93 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/index.tsx b/src/index.tsx index e131be5..6583f32 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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 = ``; +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( a != null)} />, ); +}; + +export const renderSpriteSheetToString = async ( + markupString: string, + knownIcons: IconsCache, + spriteSheetId = internalSpriteSheetId, +) => { + const spriteSheet = await createSpriteSheetString(knownIcons); + const ssrEmptySpriteSheet = ``; return markupString.replace(ssrEmptySpriteSheet, spriteSheet); }; @@ -135,12 +142,14 @@ export interface SpriteContext { */ loadSVG: (url: string) => Promise; knownIcons?: IconsCache; + embeddedSSR?: boolean; } export const SpriteContextProvider: FC = ({ children, loadSVG, knownIcons = globalIconsCache, + embeddedSSR = false, }) => { const icons = useIcons(); @@ -162,7 +171,7 @@ export const SpriteContextProvider: FC = ({ return ( {children} - + {(!isSSR || embeddedSSR) && } ); }; @@ -173,7 +182,7 @@ export const Icon: FC<{ url: string } & React.SVGProps> = ({ }) => { const { registerSVG } = useContext(spriteContext); - if (typeof document === 'undefined') { + if (isSSR) { registerSVG(url); } else { useEffect(() => { @@ -189,31 +198,39 @@ export const Icon: FC<{ url: string } & React.SVGProps> = ({ }; 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 ( + + ); + }, + ); + + if (spriteSheetContainer.current) { + return createPortal(renderedIcons, spriteSheetContainer.current); + } + return ( - {icons.map( - ({ - id, - svgString, - attributes: { - width, - height, - ['xmlns:xlink']: xmlnsXlink, - ...attributes - }, - }) => { - return ( - - ); - }, - )} + {renderedIcons} ); }; @@ -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); diff --git a/test/ssr.test.tsx b/test/ssr.test.tsx index dc60cf7..b9eee79 100644 --- a/test/ssr.test.tsx +++ b/test/ssr.test.tsx @@ -7,6 +7,7 @@ import { SpriteContextProvider, IconsCache, renderSpriteSheetToString, + createSpriteSheetString, } from '../src/index'; import { renderToString } from 'react-dom/server'; @@ -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( - + , ); @@ -45,3 +46,36 @@ test('render loaded svgs to a svg sprite sheet string', async () => { `""`, ); }); + +test('should not render an embedded sprite sheet when not explicitly asked for', async () => { + const cache: IconsCache = new Map(); + const renderedString = renderToString( + + + , + ); + + const renderedSpriteSheet = await renderSpriteSheetToString( + renderedString, + cache, + ); + + expect(renderedSpriteSheet).toMatchInlineSnapshot( + `""`, + ); +}); + +test('render loaded svgs to a svg sprite sheet string', async () => { + const cache: IconsCache = new Map(); + renderToString( + + + , + ); + + const renderedSpriteSheet = await createSpriteSheetString(cache); + + expect(renderedSpriteSheet).toMatchInlineSnapshot( + `""`, + ); +});