Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resvg error(by Next.js 14) #315

Open
front-zero opened this issue Mar 14, 2024 · 5 comments
Open

resvg error(by Next.js 14) #315

front-zero opened this issue Mar 14, 2024 · 5 comments

Comments

@front-zero
Copy link

front-zero commented Mar 14, 2024

hi!

I wrote logic to convert svg to png using resvg in next.js, but the following error occurred. Could I possibly get some help?

  • Next.js api route code
import {NextResponse} from "next/server";
import satori from 'satori';
import sharp from "sharp";
import fs from 'fs';
import path from 'path';
import {convertSvgToPngByResvg, convertSvgToPngBySharp} from "@/utils/svgToPngUtil";

/**
 * Modified version of https://unpkg.com/[email protected]/dist/twemoji.esm.js.
 */

/*! Copyright Twitter Inc. and other contributors. Licensed under MIT */

const U200D = String.fromCharCode(8205)
const UFE0Fg = /\uFE0F/g

function getIconCode(char: string) {
    return toCodePoint(char.indexOf(U200D) < 0 ? char.replace(UFE0Fg, '') : char)
}

function toCodePoint(unicodeSurrogates: string) {
    const r = []
    let c = 0,
        p = 0,
        i = 0

    while (i < unicodeSurrogates.length) {
        c = unicodeSurrogates.charCodeAt(i++)
        if (p) {
            r.push((65536 + ((p - 55296) << 10) + (c - 56320)).toString(16))
            p = 0
        } else if (55296 <= c && c <= 56319) {
            p = c
        } else {
            r.push(c.toString(16))
        }
    }
    return r.join('-')
}

export const apis = {
    twemoji: (code: string) =>
        'https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/svg/' +
        code.toLowerCase() +
        '.svg',
    openmoji: 'https://cdn.jsdelivr.net/npm/@svgmoji/[email protected]/svg/',
    blobmoji: 'https://cdn.jsdelivr.net/npm/@svgmoji/[email protected]/svg/',
    noto: 'https://cdn.jsdelivr.net/gh/svgmoji/svgmoji/packages/svgmoji__noto/svg/',
    fluent: (code: string) =>
        'https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/' +
        code.toLowerCase() +
        '_color.svg',
    fluentFlat: (code: string) =>
        'https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/' +
        code.toLowerCase() +
        '_flat.svg',
}

function loadEmoji(type: keyof typeof apis, code: string) {
    const key = type + ':' + code

    if (!type || !apis[type]) {
        type = 'twemoji'
    }

    const api = apis[type]
    if (typeof api === 'function') {
        return fetch(api(code)).then((r) => r.text());
    }
    return fetch(`${api}${code.toUpperCase()}.svg`).then((r) =>
        r.text()
    )
}


export async function GET(request: Request) {

    const notoSansFontBuffer = fs.readFileSync(path.join(process.cwd(), 'public', 'fonts', 'NotoSansKR-SemiBold.ttf'));

    const svg = await satori((<div
        style={{
            display: 'flex',
            height: '100%',
            width: '100%',
            alignItems: 'center',
            justifyContent: 'center',
            flexDirection: 'column',
            backgroundImage: 'linear-gradient(to bottom, #dbf4ff, #fff1f1)',
            fontSize: 60,
            letterSpacing: -2,
            fontWeight: 700,
            textAlign: 'center',
        }}
    >
        <svg
            height={80}
            viewBox="0 0 75 65"
            fill="black"
            style={{margin: '0 75px'}}
        >
            <path d="M37.59.25l36.95 64H.64l36.95-64z"></path>
        </svg>
        <div
            style={{
                backgroundImage: 'linear-gradient(90deg, rgb(0, 124, 240), rgb(0, 223, 216))',
                backgroundClip: 'text',
                '-webkit-background-clip': 'text',
                color: 'transparent',
            }}
        >
            Develop
        </div>
        <div
            style={{
                backgroundImage: 'linear-gradient(90deg, rgb(121, 40, 202), rgb(255, 0, 128))',
                backgroundClip: 'text',
                '-webkit-background-clip': 'text',
                color: 'transparent',
            }}
        >
            Preview
        </div>
        <div
            style={{
                backgroundImage: 'linear-gradient(90deg, rgb(255, 77, 77), rgb(249, 203, 40))',
                backgroundClip: 'text',
                '-webkit-background-clip': 'text',
                color: 'transparent',
            }}
        >
            Ship ❤️
        </div>
        <img src="https://picsum.photos/150" width={150} height={150}/>
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
            <path fill="#DD2E44"
                  d="M35.885 11.833c0-5.45-4.418-9.868-9.867-9.868-3.308 0-6.227 1.633-8.018 4.129-1.791-2.496-4.71-4.129-8.017-4.129-5.45 0-9.868 4.417-9.868 9.868 0 .772.098 1.52.266 2.241C1.751 22.587 11.216 31.568 18 34.034c6.783-2.466 16.249-11.447 17.617-19.959.17-.721.268-1.469.268-2.242z"/>
        </svg>
    </div>), {
        width: 1200,
        height: 627,
        fonts: [
            {
                style: "normal",
                name: "Noto Sans KR",
                data: notoSansFontBuffer,
                weight: 600,
            },
        ],
        loadAdditionalAsset: async (code: string, segment: string) => {
            // console.log(code, segment, Buffer.from(segment).toString('base64'));
            // console.log(getIconCode(segment));
            // console.log(await loadEmoji('twemoji', getIconCode(segment)));
            // return loadDynamicAsset('twemoji', code, segment);
            if (code === 'emoji') {
                return `data:image/svg+xml;base64,` +
                    btoa(await loadEmoji('twemoji', getIconCode(segment)));
            }

            return code;
        }
    },);

    const pngBuffer = convertSvgToPngByResvg(svg);

    // const pngBuffer2 = convertSvgToPng(svg);

    // return new NextResponse(pngBuffer, {
    //     status: 200,
    //     headers: {
    //         'Content-Type': 'image/png',
    //         'Content-Length': String(pngBuffer.length),
    //     }
    // });

    return new NextResponse(svg, {
        status: 200,
        headers: {
            'Content-Type': 'image/svg+xml',
        }
    });
}
  • svgToPngUtil.ts
import sharp from "sharp";
import {Resvg} from "@resvg/resvg-js";

export function convertSvgToPngByResvg(targetSvg: Buffer | string) {
    const resvg = new Resvg(targetSvg, {});
    const pngData = resvg.render();
    return pngData.asPng();
}

export async function convertSvgToPngBySharp(targetSvg: string) {
    return sharp(Buffer.from(targetSvg)).png().toBuffer();
}

⨯ ./node_modules/@resvg/resvg-js-win32-x64-msvc/resvgjs.win32-x64-msvc.node
Module parse failed: Unexpected character '�' (1:2)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
(Source code omitted for this binary file)

Import trace for requested module:
./node_modules/@resvg/resvg-js-win32-x64-msvc/resvgjs.win32-x64-msvc.node
./node_modules/@resvg/resvg-js/js-binding.js
./node_modules/@resvg/resvg-js/index.js
./src/utils/svgToPngUtil.ts
./src/app/api/satori/route.tsx
○ Compiling /not-found ...
⨯ ./node_modules/@resvg/resvg-js-win32-x64-msvc/resvgjs.win32-x64-msvc.node
Module parse failed: Unexpected character '�' (1:2)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
(Source code omitted for this binary file)

image
image

@front-zero front-zero changed the title resvg error(bu Next.js 14) resvg error(by Next.js 14) Mar 14, 2024
@mathiasprisfeldt
Copy link

mathiasprisfeldt commented Apr 3, 2024

@udatny
Copy link

udatny commented Sep 10, 2024

@mathiasprisfeldt do you have an example how you use it ?

@mathiasprisfeldt
Copy link

@udatny
try this

npm install nextjs-node-loader --save-dev

// next.config.mjs
export default () => {

  const nextConfig = {
    webpack: (config) => {
      config.module.rules.push(
        {
          test: /\.node$/,
          use: [
            {
              loader: 'nextjs-node-loader',
            },
          ],
        },
      );

      return config;
    }
  };

  return nextConfig;
};

@joshxfi
Copy link

joshxfi commented Oct 6, 2024

you need to add @resvg/resvg-js in serverComponentsExternalPackages

const nextConfig = {
  experimental: {
    serverComponentsExternalPackages: ["@resvg/resvg-js"],
  },
};

EDIT:
As of Next 15, see https://nextjs.org/docs/app/api-reference/next-config-js/serverExternalPackages

const nextConfig = {
  serverExternalPackages: ["@resvg/resvg-js"],
};

@udatny
Copy link

udatny commented Oct 15, 2024

@joshxfi Your hint helped me solve a tricky issue.
eisberg-labs/nextjs-node-loader#45

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants