diff --git a/.changeset/ninety-hotels-hear.md b/.changeset/ninety-hotels-hear.md new file mode 100644 index 000000000..c23530b4f --- /dev/null +++ b/.changeset/ninety-hotels-hear.md @@ -0,0 +1,5 @@ +--- +'@vanilla-extract/integration': major +--- + +Support serialisation of exotic react components diff --git a/packages/integration/src/processVanillaFile.test.ts b/packages/integration/src/processVanillaFile.test.ts index e336ec248..c9193f79f 100644 --- a/packages/integration/src/processVanillaFile.test.ts +++ b/packages/integration/src/processVanillaFile.test.ts @@ -138,6 +138,30 @@ describe('serializeVanillaModule', () => { `); }); + test('should handle function serialization', () => { + const component = { render: () => null }; + + Object.defineProperty(component, '__function_serializer__', { + value: { + importPath: 'my-package', + importName: 'myFunction', + args: ['arg1', 'arg2'], + }, + writable: false, + }); + + const exports = { + component, + }; + + expect(serializeVanillaModule(['import "./styles.css"'], exports, null)) + .toMatchInlineSnapshot(` + "import "./styles.css" + import { myFunction as _86bce } from 'my-package'; + export var component = _86bce('arg1','arg2');" + `); + }); + test('should re-use exports in handle serialized function args', () => { const complexExport = { my: { diff --git a/packages/integration/src/processVanillaFile.ts b/packages/integration/src/processVanillaFile.ts index 5ded311ab..174e25daa 100644 --- a/packages/integration/src/processVanillaFile.ts +++ b/packages/integration/src/processVanillaFile.ts @@ -11,7 +11,7 @@ import type { IdentifierOption } from './types'; const originalNodeEnv = process.env.NODE_ENV; // Copied from https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore/blob/51f83bd3db728fd7ee177de1ffc253fdb99c537f/README.md#_isplainobject -function isPlainObject(value: unknown) { +function isPlainObject(value: unknown): value is object { if (typeof value !== 'object' || value === null) { return false; } @@ -36,6 +36,16 @@ function isPlainObject(value: unknown) { ); } +function isExoticReactComponent( + value: unknown, +): value is React.ExoticComponent { + return ( + isPlainObject(value) && + 'render' in value && + typeof value.render === 'function' + ); +} + export function stringifyFileScope({ packageName, filePath, @@ -186,7 +196,7 @@ export async function processVanillaFile({ function stringifyExports( functionSerializationImports: Set, - value: any, + value: unknown, unusedCompositionRegex: RegExp | null, key: string, exportLookup: Map, @@ -206,31 +216,8 @@ function stringifyExports( return next(value); } - if (Array.isArray(value) || isPlainObject(value)) { - const reusedExport = exportLookup.get(value); - - if (reusedExport && reusedExport !== key) { - exportDependencyGraph.addDependency(key, reusedExport); - return reusedExport; - } - return next(value); - } - - if (Symbol.toStringTag in Object(value)) { - const { [Symbol.toStringTag]: _tag, ...valueWithoutTag } = value; - return next(valueWithoutTag); - } - - if (valueType === 'string') { - return next( - unusedCompositionRegex - ? value.replace(unusedCompositionRegex, '') - : value, - ); - } - if ( - valueType === 'function' && + (valueType === 'function' || isExoticReactComponent(value)) && (value.__function_serializer__ || value.__recipe__) ) { const { importPath, importName, args } = @@ -273,6 +260,29 @@ function stringifyExports( } } + if (Array.isArray(value) || isPlainObject(value)) { + const reusedExport = exportLookup.get(value); + + if (reusedExport && reusedExport !== key) { + exportDependencyGraph.addDependency(key, reusedExport); + return reusedExport; + } + return next(value); + } + + if (Symbol.toStringTag in Object(value)) { + const { [Symbol.toStringTag]: _tag, ...valueWithoutTag } = value; + return next(valueWithoutTag); + } + + if (valueType === 'string') { + return next( + unusedCompositionRegex + ? value.replace(unusedCompositionRegex, '') + : value, + ); + } + throw new Error(dedent` Invalid exports.