diff --git a/packages/docusaurus-plugin-svgr/src/__tests__/svgrLoader.test.ts b/packages/docusaurus-plugin-svgr/src/__tests__/svgrLoader.test.ts index b022e076eecf..7e19bb3f5f54 100644 --- a/packages/docusaurus-plugin-svgr/src/__tests__/svgrLoader.test.ts +++ b/packages/docusaurus-plugin-svgr/src/__tests__/svgrLoader.test.ts @@ -6,6 +6,8 @@ */ import {describe, expect, it} from 'vitest'; +import {mkdir, mkdtempDisposable, realpath, writeFile} from 'node:fs/promises'; +import {tmpdir} from 'node:os'; import path from 'node:path'; import {transform} from '@svgr/core'; import svgo from '@svgr/plugin-svgo'; @@ -15,6 +17,8 @@ import type {SVGRConfig} from '../options'; const siteDir = path.join(__dirname, 'site'); +const typescriptSvgrConfig = 'module.exports = {typescript: true};\n'; + function gradientSvg(color: string) { return ` Gradient @@ -53,6 +57,35 @@ function getIds(code: string): string[] { ); } +// SVGR searches from the SVG file upward, so each case needs its own directory. +// Cosmiconfig caches a search path for the process lifetime. +async function withSvgrConfigFile( + configSource: string, + location: 'parent' | 'site', +): Promise<{siteDirectory: string; filePath: string} & AsyncDisposable> { + const parent = await mkdtempDisposable( + path.join(await realpath(tmpdir()), 'docusaurus-svgr-'), + ); + const siteDirectory = path.join(parent.path, 'site'); + const filePath = 'src/icon.svg'; + await mkdir(path.join(siteDirectory, 'src'), {recursive: true}); + await writeFile(path.join(siteDirectory, filePath), gradientSvg('red')); + const configDirectory = location === 'parent' ? parent.path : siteDirectory; + await writeFile(path.join(configDirectory, 'svgr.config.js'), configSource); + return { + siteDirectory, + filePath, + [Symbol.asyncDispose]: parent[Symbol.asyncDispose], + }; +} + +function expectPlainReactSvg(code: string) { + expect(code).toContain('export default'); + expect(code).toContain('viewBox="0 0 10 10"'); + expect(code).not.toContain('interface SVGRProps'); + expect(code).not.toContain('import type { SVGProps }'); +} + describe('createSVGRConfig', () => { it('prefixes SVG ids to avoid clashes between inline SVGs', async () => { const codeA = await transformSvg({ @@ -122,4 +155,49 @@ describe('createSVGRConfig', () => { expect(getIds(code)).toEqual(['a']); expect(code).toContain('fill="url(#a)"'); }); + + it('ignores a parent svgr config that enables TypeScript output', async () => { + await using site = await withSvgrConfigFile(typescriptSvgrConfig, 'parent'); + const code = await transformSvg({ + svg: gradientSvg('red'), + filePath: site.filePath, + siteDirectory: site.siteDirectory, + }); + expectPlainReactSvg(code); + }); + + it('loads a parent svgr config when runtimeConfig is enabled', async () => { + await using site = await withSvgrConfigFile(typescriptSvgrConfig, 'parent'); + const code = await transformSvg({ + svg: gradientSvg('red'), + filePath: site.filePath, + siteDirectory: site.siteDirectory, + svgrConfig: {runtimeConfig: true}, + }); + expect(code).toContain('interface SVGRProps'); + expect(code).toContain('import type { SVGProps }'); + }); + + it('ignores an svgr config in the site directory', async () => { + await using site = await withSvgrConfigFile(typescriptSvgrConfig, 'site'); + const code = await transformSvg({ + svg: gradientSvg('red'), + filePath: site.filePath, + siteDirectory: site.siteDirectory, + }); + expectPlainReactSvg(code); + }); + + it('ignores a malformed svgr config discovered from a parent directory', async () => { + await using site = await withSvgrConfigFile( + 'module.exports = {\n', + 'parent', + ); + const code = await transformSvg({ + svg: gradientSvg('red'), + filePath: site.filePath, + siteDirectory: site.siteDirectory, + }); + expectPlainReactSvg(code); + }); }); diff --git a/packages/docusaurus-plugin-svgr/src/svgrLoader.ts b/packages/docusaurus-plugin-svgr/src/svgrLoader.ts index c9363db21e7a..a3ac0dd0ce41 100644 --- a/packages/docusaurus-plugin-svgr/src/svgrLoader.ts +++ b/packages/docusaurus-plugin-svgr/src/svgrLoader.ts @@ -68,6 +68,9 @@ function createDefaultSVGRConfig({siteDir}: {siteDir: string}): SVGRConfig { svgo: true, svgoConfig: createDefaultSVGOConfig({siteDir}), titleProp: true, + // Skip svgr.config.js discovered in the site directory or an ancestor. + // See https://github.com/meta/docusaurus/issues/8646 + runtimeConfig: false, }; } diff --git a/website/docs/api/plugins/plugin-svgr.mdx b/website/docs/api/plugins/plugin-svgr.mdx index 53a11bed7ad2..6dca55b54376 100644 --- a/website/docs/api/plugins/plugin-svgr.mdx +++ b/website/docs/api/plugins/plugin-svgr.mdx @@ -61,6 +61,7 @@ const defaultSvgrConfig = { prettier: false, svgo: true, titleProp: true, + runtimeConfig: false, svgoConfig: { plugins: [ { @@ -88,4 +89,6 @@ const defaultSvgrConfig = { The `prefixIds` plugin prefixes SVG ids (for example, `id="a"` becomes `id="svg-3f2a1c-a"`) to avoid clashes between multiple SVGs inlined in the same page. Class names are not prefixed. +`runtimeConfig: false` disables discovery of [SVGR configuration files](https://react-svgr.com/docs/configuration-files/) (for example `svgr.config.js`) in the site directory and ancestor directories. Configure SVGR through the `svgrConfig` option. + Providing `svgoConfig` replaces the default [SVGO](https://svgo.dev/) config entirely. Use `svgoConfig: undefined` to read your own [`svgo.config.js` file](https://svgo.dev/docs/introduction/#configuration) instead.