From 4b3f3506d224d7b1a2ca1489e44455e6d001879a Mon Sep 17 00:00:00 2001 From: hikerpig Date: Wed, 28 Feb 2024 21:49:51 +0800 Subject: [PATCH] optimize(cli): should cleanup global pollution after render --- .changeset/real-badgers-complain.md | 5 ++ .../pintora-cli/src/__tests__/render.spec.ts | 14 +++++ packages/pintora-cli/src/render.ts | 51 ++++++++++++++++--- 3 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 .changeset/real-badgers-complain.md diff --git a/.changeset/real-badgers-complain.md b/.changeset/real-badgers-complain.md new file mode 100644 index 00000000..a62e169d --- /dev/null +++ b/.changeset/real-badgers-complain.md @@ -0,0 +1,5 @@ +--- +'@pintora/cli': patch +--- + +should cleanup global pollution after render diff --git a/packages/pintora-cli/src/__tests__/render.spec.ts b/packages/pintora-cli/src/__tests__/render.spec.ts index 789b042e..575ff5c4 100644 --- a/packages/pintora-cli/src/__tests__/render.spec.ts +++ b/packages/pintora-cli/src/__tests__/render.spec.ts @@ -38,4 +38,18 @@ describe('render', () => { expect(config.themeConfig.theme).toBe(oldTheme) expect(config.core.useMaxWidth).toBe(oldUseMaxWidth) }) + + it('should cleanup global pollution after render', async () => { + const fakeDom = {} + ;(globalThis as any).document = fakeDom + + await render({ + code: EXAMPLES.er.code, + mimeType: SVG_MIME_TYPE, + }) + expect(global.window).toBeUndefined() + expect((globalThis as any).document).toBe(fakeDom) // should not mess with existing globals + + delete (globalThis as any).document + }) }) diff --git a/packages/pintora-cli/src/render.ts b/packages/pintora-cli/src/render.ts index 891a69b0..a3baf051 100644 --- a/packages/pintora-cli/src/render.ts +++ b/packages/pintora-cli/src/render.ts @@ -23,6 +23,31 @@ export type CLIRenderOptions = { width?: number } +/** + * records how many globals we have patched, + * need to restore them later to prevent polluting the global environment + */ +class GlobalPatcher { + private records: any = {} + set(k: K, v: any) { + const prevValue = globalThis[k] + this.records[k] = { + prevValue, + value: v, + } + + globalThis[k] = v + } + + restore() { + for (const k in this.records) { + if ((globalThis as any)[k] === this.records[k].value) { + ;(globalThis as any)[k] = this.records[k].prevValue + } + } + } +} + function renderPrepare(opts: CLIRenderOptions) { const { code, backgroundColor, pintoraConfig } = opts const devicePixelRatio = opts.devicePixelRatio || 2 @@ -33,10 +58,11 @@ function renderPrepare(opts: CLIRenderOptions) { container.id = 'pintora-container' // setup the env for renderer - global.window = dom.window as any - global.document = document + const patcher = new GlobalPatcher() + patcher.set('window', dom.window) + patcher.set('document', document) + patcher.set('CanvasPattern', CanvasPattern) ;(dom.window as any).devicePixelRatio = devicePixelRatio - ;(global as any).CanvasPattern = CanvasPattern return { container, @@ -51,7 +77,7 @@ function renderPrepare(opts: CLIRenderOptions) { config = pintoraStandalone.configApi.gnernateNewConfig({ core: { useMaxWidth: true } }) } - return new Promise((resolve, reject) => { + return new Promise<{ renderer: IRenderer; cleanup(): void }>((resolve, reject) => { pintoraStandalone.renderTo(code, { container, renderer: renderOpts.renderer || 'canvas', @@ -69,10 +95,16 @@ function renderPrepare(opts: CLIRenderOptions) { return ir }, onRender(renderer) { - resolve(renderer) + resolve({ + renderer, + cleanup() { + patcher.restore() + }, + }) }, onError(e) { console.error('onError', e) + patcher.restore() reject(e) }, }) @@ -96,10 +128,12 @@ export function render(opts: CLIRenderOptions) { function renderToSvg() { return new Promise((resolve, reject) => { pintorRender({ renderer: 'svg' }) - .then(renderer => { + .then(({ renderer, cleanup }) => { const rootElement = renderer.getRootElement() as SVGSVGElement rootElement.setAttribute('xmlns', 'http://www.w3.org/2000/svg') - resolve(rootElement.outerHTML) + const html = rootElement.outerHTML + cleanup() + resolve(html) }) .catch(reject) }) @@ -109,9 +143,10 @@ export function render(opts: CLIRenderOptions) { function renderToImageBuffer() { return new Promise((resolve, reject) => { pintorRender({ renderer: 'canvas' }) - .then(renderer => { + .then(({ renderer, cleanup }) => { setTimeout(() => { const buf = getBuf(renderer.getRootElement() as HTMLCanvasElement) + cleanup() resolve(buf) }, 20) })