Fix SVG exports in Next.js (#3446)
Next.js bans the use of react-dom/server APIs on the client. React's docs recommend against using these too: https://react.dev/reference/react-dom/server/renderToString#removing-rendertostring-from-the-client-code In this diff, we switch from using `ReactDOMServer.renderToStaticMarkup` to `ReactDOMClient.createRoot`, fixing SVG exports in next.js apps. `getSvg` remains deprecated, but we've introduced a new `getSvgElement` method with a similar API to `getSvgString` - it returns an `{svg, width, height}` object. ### Change Type - [x] `sdk` — Changes the tldraw SDK - [x] `bugfix` — Bug fix
This commit is contained in:
parent
84dbf2df20
commit
a18525ea78
5 changed files with 153 additions and 16 deletions
|
@ -767,6 +767,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
getStyleForNextShape<T>(style: StyleProp<T>): T;
|
||||
// @deprecated (undocumented)
|
||||
getSvg(shapes: TLShape[] | TLShapeId[], opts?: Partial<TLSvgOptions>): Promise<SVGSVGElement | undefined>;
|
||||
getSvgElement(shapes: TLShape[] | TLShapeId[], opts?: Partial<TLSvgOptions>): Promise<{
|
||||
svg: SVGSVGElement;
|
||||
width: number;
|
||||
height: number;
|
||||
} | undefined>;
|
||||
getSvgString(shapes: TLShape[] | TLShapeId[], opts?: Partial<TLSvgOptions>): Promise<{
|
||||
svg: string;
|
||||
width: number;
|
||||
|
|
|
@ -13888,7 +13888,7 @@
|
|||
{
|
||||
"kind": "Method",
|
||||
"canonicalReference": "@tldraw/editor!Editor#getSvg:member(1)",
|
||||
"docComment": "/**\n * @deprecated\n *\n * Use {@link Editor.getSvgString} instead\n */\n",
|
||||
"docComment": "/**\n * @deprecated\n *\n * Use {@link Editor.getSvgString} or {@link Editor.getSvgElement} instead.\n */\n",
|
||||
"excerptTokens": [
|
||||
{
|
||||
"kind": "Content",
|
||||
|
@ -13991,6 +13991,112 @@
|
|||
"isAbstract": false,
|
||||
"name": "getSvg"
|
||||
},
|
||||
{
|
||||
"kind": "Method",
|
||||
"canonicalReference": "@tldraw/editor!Editor#getSvgElement:member(1)",
|
||||
"docComment": "/**\n * Get an exported SVG element of the given shapes.\n *\n * @param ids - The shapes (or shape ids) to export.\n *\n * @param opts - Options for the export.\n *\n * @returns The SVG element.\n *\n * @public\n */\n",
|
||||
"excerptTokens": [
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "getSvgElement(shapes: "
|
||||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
"text": "TLShape",
|
||||
"canonicalReference": "@tldraw/tlschema!TLShape:type"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "[] | "
|
||||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
"text": "TLShapeId",
|
||||
"canonicalReference": "@tldraw/tlschema!TLShapeId:type"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "[]"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": ", opts?: "
|
||||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
"text": "Partial",
|
||||
"canonicalReference": "!Partial:type"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "<"
|
||||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
"text": "TLSvgOptions",
|
||||
"canonicalReference": "@tldraw/editor!TLSvgOptions:type"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": ">"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "): "
|
||||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
"text": "Promise",
|
||||
"canonicalReference": "!Promise:interface"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "<{\n svg: "
|
||||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
"text": "SVGSVGElement",
|
||||
"canonicalReference": "!SVGSVGElement:interface"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": ";\n width: number;\n height: number;\n } | undefined>"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": ";"
|
||||
}
|
||||
],
|
||||
"isStatic": false,
|
||||
"returnTypeTokenRange": {
|
||||
"startIndex": 11,
|
||||
"endIndex": 15
|
||||
},
|
||||
"releaseTag": "Public",
|
||||
"isProtected": false,
|
||||
"overloadIndex": 1,
|
||||
"parameters": [
|
||||
{
|
||||
"parameterName": "shapes",
|
||||
"parameterTypeTokenRange": {
|
||||
"startIndex": 1,
|
||||
"endIndex": 5
|
||||
},
|
||||
"isOptional": false
|
||||
},
|
||||
{
|
||||
"parameterName": "opts",
|
||||
"parameterTypeTokenRange": {
|
||||
"startIndex": 6,
|
||||
"endIndex": 10
|
||||
},
|
||||
"isOptional": true
|
||||
}
|
||||
],
|
||||
"isOptional": false,
|
||||
"isAbstract": false,
|
||||
"name": "getSvgElement"
|
||||
},
|
||||
{
|
||||
"kind": "Method",
|
||||
"canonicalReference": "@tldraw/editor!Editor#getSvgString:member(1)",
|
||||
|
|
|
@ -61,7 +61,6 @@ import {
|
|||
import { EventEmitter } from 'eventemitter3'
|
||||
import { flushSync } from 'react-dom'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { TLUser, createTLUser } from '../config/createTLUser'
|
||||
import { checkShapesAndAddCore } from '../config/defaultShapes'
|
||||
import {
|
||||
|
@ -8070,6 +8069,33 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an exported SVG element of the given shapes.
|
||||
*
|
||||
* @param ids - The shapes (or shape ids) to export.
|
||||
* @param opts - Options for the export.
|
||||
*
|
||||
* @returns The SVG element.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
async getSvgElement(shapes: TLShapeId[] | TLShape[], opts = {} as Partial<TLSvgOptions>) {
|
||||
const result = await getSvgJsx(this, shapes, opts)
|
||||
if (!result) return undefined
|
||||
|
||||
const fragment = document.createDocumentFragment()
|
||||
const root = createRoot(fragment)
|
||||
flushSync(() => {
|
||||
root.render(result.jsx)
|
||||
})
|
||||
|
||||
const svg = fragment.firstElementChild
|
||||
assert(svg instanceof SVGSVGElement, 'Expected an SVG element')
|
||||
|
||||
root.unmount()
|
||||
return { svg, width: result.width, height: result.height }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an exported SVG string of the given shapes.
|
||||
*
|
||||
|
@ -8081,21 +8107,22 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
* @public
|
||||
*/
|
||||
async getSvgString(shapes: TLShapeId[] | TLShape[], opts = {} as Partial<TLSvgOptions>) {
|
||||
const svg = await getSvgJsx(this, shapes, opts)
|
||||
if (!svg) return undefined
|
||||
return { svg: renderToStaticMarkup(svg.jsx), width: svg.width, height: svg.height }
|
||||
const result = await this.getSvgElement(shapes, opts)
|
||||
if (!result) return undefined
|
||||
|
||||
const serializer = new XMLSerializer()
|
||||
return {
|
||||
svg: serializer.serializeToString(result.svg),
|
||||
width: result.width,
|
||||
height: result.height,
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Editor.getSvgString} instead */
|
||||
/** @deprecated Use {@link Editor.getSvgString} or {@link Editor.getSvgElement} instead. */
|
||||
async getSvg(shapes: TLShapeId[] | TLShape[], opts = {} as Partial<TLSvgOptions>) {
|
||||
const svg = await getSvgJsx(this, shapes, opts)
|
||||
if (!svg) return undefined
|
||||
const fragment = new DocumentFragment()
|
||||
const root = createRoot(fragment)
|
||||
flushSync(() => root.render(svg.jsx))
|
||||
const rendered = fragment.firstElementChild
|
||||
root.unmount()
|
||||
return rendered as SVGSVGElement
|
||||
const result = await this.getSvgElement(shapes, opts)
|
||||
if (!result) return undefined
|
||||
return result.svg
|
||||
}
|
||||
|
||||
/* --------------------- Events --------------------- */
|
||||
|
|
|
@ -184,7 +184,6 @@ export async function getSvgJsx(
|
|||
const svg = (
|
||||
<SvgExportContextProvider editor={editor} context={exportContext}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
preserveAspectRatio={preserveAspectRatio ? preserveAspectRatio : undefined}
|
||||
direction="ltr"
|
||||
width={w}
|
||||
|
|
|
@ -7,7 +7,7 @@ exports[`Matches a snapshot: Basic SVG 1`] = `
|
|||
height="564"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
style="background-color:transparent"
|
||||
style="background-color: transparent;"
|
||||
viewBox="-32 -32 564 564"
|
||||
width="564"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
|
Loading…
Reference in a new issue