diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a012c35 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# keep LF as-is even on Windows for consistent hash values across platforms +* text=auto eol=lf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b4b05b8..4261ad7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,6 +48,9 @@ jobs: - name: Install deps run: pnpm install + - name: Install playwright + run: pnpm --filter examples exec playwright install chromium --with-deps + - name: Build run: pnpm run build @@ -77,6 +80,9 @@ jobs: - name: Install deps run: pnpm install --no-frozen-lockfile + - name: Install playwright + run: pnpm --filter examples exec playwright install chromium --with-deps + - name: Install @babel/core v7 types run: pnpm add -Dw @types/babel__core@^7.20.5 diff --git a/.gitignore b/.gitignore index 2aa8c99..f13f414 100644 --- a/.gitignore +++ b/.gitignore @@ -142,3 +142,8 @@ dist vite.config.js.timestamp-* vite.config.ts.timestamp-* .vite/ + +.swc + +/examples-temp-* +output.swc.* diff --git a/.oxfmtrc.json b/.oxfmtrc.json index 282d97c..8b35b8c 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -2,5 +2,10 @@ "$schema": "./node_modules/oxfmt/configuration_schema.json", "semi": false, "singleQuote": true, - "ignorePatterns": ["**/dist/**", "packages/*/CHANGELOG.md"] + "ignorePatterns": [ + "**/dist/**", + "packages/*/CHANGELOG.md", + "**/fixtures/**", + "**/fixtures-labels/**" + ] } diff --git a/.oxlintrc.json b/.oxlintrc.json index 14244f4..8a7b1c5 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -4,5 +4,8 @@ "correctness": "error", "suspicious": "error" }, - "ignorePatterns": ["**/dist/**"] + "rules": { + "no-shadow": "off" + }, + "ignorePatterns": ["**/dist/**", "**/fixtures/**", "**/fixtures-labels/**", "**/benchmark/**"] } diff --git a/README.md b/README.md index cf3e43d..292b908 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,13 @@ Official Rolldown plugins ## Packages - [`@rolldown/plugin-babel`](https://github.com/rolldown/plugins/tree/main/packages/babel) ([![NPM version][badge-npm-version-babel]][url-npm-babel]): transform code with Babel +- [`@rolldown/plugin-emotion`](https://github.com/rolldown/plugins/tree/main/packages/emotion) ([![NPM version][badge-npm-version-emotion]][url-npm-emotion]): minification and optimization of Emotion styles ## License [MIT](https://github.com/rolldown/plugins/blob/main/LICENSE) [badge-npm-version-babel]: https://img.shields.io/npm/v/@rolldown/plugin-babel/latest?color=brightgreen +[badge-npm-version-emotion]: https://img.shields.io/npm/v/@rolldown/plugin-emotion/latest?color=brightgreen [url-npm-babel]: https://npmx.dev/package/@rolldown/plugin-babel/v/latest +[url-npm-emotion]: https://npmx.dev/package/@rolldown/plugin-emotion/v/latest diff --git a/examples/emotion/emotion.test.ts b/examples/emotion/emotion.test.ts new file mode 100644 index 0000000..3596caa --- /dev/null +++ b/examples/emotion/emotion.test.ts @@ -0,0 +1,28 @@ +import { expect, test } from 'vitest' +import { editFile, getBg, getColor, isServe, page } from '~utils' + +test('should render app', async () => { + expect(await page.textContent('.emotion-title')).toBe('Emotion Works!') +}) + +test('styled component should apply styles', async () => { + const title = page.locator('.emotion-title') + const color = await getColor(title) + expect(color).toBe('rgb(255, 105, 180)') // hotpink +}) + +test('css prop should work', async () => { + const button = page.locator('.emotion-button') + const bgColor = await getBg(button) + expect(bgColor).toBe('rgb(100, 108, 255)') // #646cff +}) + +test.runIf(isServe)('hmr works', async () => { + editFile('src/App.tsx', (code) => code.replace('hotpink', 'blue')) + await expect + .poll(async () => { + const title = page.locator('.emotion-title') + return getColor(title) + }) + .toBe('rgb(0, 0, 255)') // blue +}) diff --git a/examples/emotion/index.html b/examples/emotion/index.html new file mode 100644 index 0000000..be91380 --- /dev/null +++ b/examples/emotion/index.html @@ -0,0 +1,12 @@ + + + + + + Emotion Example + + +
+ + + diff --git a/examples/emotion/package.json b/examples/emotion/package.json new file mode 100644 index 0000000..077574a --- /dev/null +++ b/examples/emotion/package.json @@ -0,0 +1,23 @@ +{ + "name": "@rolldown/example-emotion", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "react": "^19.2.4", + "react-dom": "^19.2.4" + }, + "devDependencies": { + "@rolldown/plugin-emotion": "workspace:*", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite": "8.0.0" + } +} diff --git a/examples/emotion/src/App.tsx b/examples/emotion/src/App.tsx new file mode 100644 index 0000000..0e6f029 --- /dev/null +++ b/examples/emotion/src/App.tsx @@ -0,0 +1,42 @@ +/** @jsxImportSource @emotion/react */ +import { css } from '@emotion/react' +import styled from '@emotion/styled' + +const Title = styled.h1` + color: hotpink; + font-size: 2rem; +` + +const buttonStyle = css` + padding: 0.5rem 1rem; + background-color: #646cff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + + &:hover { + background-color: #535bf2; + } +` + +const Card = styled.div` + padding: 2rem; + border: 1px solid #ccc; + border-radius: 8px; + margin: 1rem; +` + +export default function App() { + return ( +
+ Emotion Works! + +

This is a styled card component.

+ +
+
+ ) +} diff --git a/examples/emotion/src/main.tsx b/examples/emotion/src/main.tsx new file mode 100644 index 0000000..feac8ee --- /dev/null +++ b/examples/emotion/src/main.tsx @@ -0,0 +1,9 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import App from './App' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/examples/emotion/vite.config.ts b/examples/emotion/vite.config.ts new file mode 100644 index 0000000..c5fc483 --- /dev/null +++ b/examples/emotion/vite.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import emotion from '@rolldown/plugin-emotion' + +export default defineConfig({ + plugins: [ + emotion({ + sourceMap: true, + autoLabel: 'always', + }), + react({ + jsxImportSource: '@emotion/react', + }), + ], +}) diff --git a/examples/package.json b/examples/package.json new file mode 100644 index 0000000..818dbee --- /dev/null +++ b/examples/package.json @@ -0,0 +1,14 @@ +{ + "name": "@rolldown/examples", + "private": true, + "type": "module", + "scripts": { + "test": "vitest --project=e2e:dev --project=e2e:build", + "test:dev": "vitest --project=e2e:dev", + "test:build": "vitest --project=e2e:build" + }, + "devDependencies": { + "playwright-chromium": "^1.58.2", + "vite": "^8.0.0" + } +} diff --git a/examples/test-utils.ts b/examples/test-utils.ts new file mode 100644 index 0000000..f61a52f --- /dev/null +++ b/examples/test-utils.ts @@ -0,0 +1,25 @@ +import fs from 'node:fs' +import path from 'node:path' +import type { Locator } from 'playwright-chromium' +import { inject } from 'vitest' +import { tempDir } from './vitestSetup' + +export { page } from './vitestSetup' + +export const isBuild = !!inject('isBuild') +export const isServe = !isBuild + +export function editFile(file: string, callback: (content: string) => string): void { + const filePath = path.resolve(tempDir, file) + const content = fs.readFileSync(filePath, 'utf-8') + const modified = callback(content) + fs.writeFileSync(filePath, modified) +} + +export async function getColor(locator: Locator): Promise { + return locator.evaluate((el) => getComputedStyle(el).color) +} + +export async function getBg(locator: Locator): Promise { + return locator.evaluate((el) => getComputedStyle(el).backgroundColor) +} diff --git a/examples/tsconfig.json b/examples/tsconfig.json new file mode 100644 index 0000000..a2357e4 --- /dev/null +++ b/examples/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "preserve", + "lib": ["es2023", "DOM"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "allowImportingTsExtensions": true, + "isolatedModules": true, + "declaration": true, + "jsx": "react-jsx", + "paths": { + "~utils": ["./test-utils.ts"] + } + } +} diff --git a/examples/vitest.config.ts b/examples/vitest.config.ts new file mode 100644 index 0000000..5133ec1 --- /dev/null +++ b/examples/vitest.config.ts @@ -0,0 +1,45 @@ +import { defineConfig } from 'vitest/config' +import { resolve } from 'node:path' + +const timeout = process.env.CI ? 6000 : 3000 + +export default defineConfig({ + resolve: { + alias: { + '~utils': resolve(import.meta.dirname, './test-utils.ts'), + }, + }, + test: { + setupFiles: ['./vitestSetup.ts'], + globalSetup: ['./vitestGlobalSetup.ts'], + testTimeout: timeout, + hookTimeout: timeout, + reporters: 'dot', + fileParallelism: false, + projects: [ + { + extends: true, + test: { + name: 'e2e:dev', + env: { + // set here to override Vitest's default `"test"` + NODE_ENV: 'development', + }, + }, + }, + { + extends: true, + test: { + name: 'e2e:build', + provide: { + isBuild: '1', + }, + env: { + // set here to override Vitest's default `"test"` + NODE_ENV: 'production', + }, + }, + }, + ], + }, +}) diff --git a/examples/vitestGlobalSetup.ts b/examples/vitestGlobalSetup.ts new file mode 100644 index 0000000..ca419c5 --- /dev/null +++ b/examples/vitestGlobalSetup.ts @@ -0,0 +1,31 @@ +import fs from 'node:fs' +import path from 'node:path' +import type { TestProject } from 'vitest/node' +import type { BrowserServer } from 'playwright-chromium' +import { chromium } from 'playwright-chromium' + +let tempBaseDir: string | undefined +let browserServer: BrowserServer | undefined + +export async function setup({ provide, config }: TestProject): Promise { + browserServer = await chromium.launchServer({ + headless: !process.env.VITE_DEBUG_SERVE, + args: process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : undefined, + }) + provide('wsEndpoint', browserServer.wsEndpoint()) + + const isBuild = !!config.provide.isBuild + tempBaseDir = path.join(import.meta.dirname, `../examples-temp-${isBuild ? 'build' : 'dev'}`) + + if (!fs.existsSync(tempBaseDir)) { + fs.mkdirSync(tempBaseDir, { recursive: true }) + } + provide('tempBaseDir', tempBaseDir) +} + +export async function teardown(): Promise { + await browserServer?.close() + if (fs.existsSync(tempBaseDir!)) { + fs.rmSync(tempBaseDir!, { recursive: true, force: true }) + } +} diff --git a/examples/vitestSetup.ts b/examples/vitestSetup.ts new file mode 100644 index 0000000..46e0468 --- /dev/null +++ b/examples/vitestSetup.ts @@ -0,0 +1,114 @@ +import fs from 'node:fs' +import path from 'node:path' +import type { Browser, Page } from 'playwright-chromium' +import { chromium } from 'playwright-chromium' +import type { InlineConfig, ViteDevServer } from 'vite' +import { build, createServer, preview } from 'vite' +import { beforeAll, afterAll, inject } from 'vitest' + +let server: ViteDevServer | { close: () => Promise; resolvedUrls: { local: string[] } } +export let tempDir: string +let browser: Browser +export let page: Page +let rootDir: string + +const isBuild = !!inject('isBuild') + +// oxlint-disable-next-line no-empty-pattern +beforeAll(async ({}, suite) => { + const wsEndpoint = inject('wsEndpoint') + if (!wsEndpoint) { + throw new Error('WS_ENDPOINT not found') + } + + browser = await chromium.connect(wsEndpoint) + page = await browser.newPage() + + // Get the test file path to determine which example to run + const testFile = suite.file?.filepath + if (!testFile) { + throw new Error('Could not determine test file path') + } + + // Extract example name from path: examples/{example}/xxx.spec.ts + const match = testFile.match(/examples[/\\]([^/\\]+)/) + if (!match) { + throw new Error(`Could not determine example from test file: ${testFile}`) + } + const exampleName = match[1] + + // Set up root directory (the example directory) + rootDir = path.resolve(import.meta.dirname, exampleName) + + // Create temp directory for this example to allow HMR edits + const tempBase = inject('tempBaseDir') + tempDir = path.join(tempBase, `${exampleName}-${Date.now()}`) + + // Copy example to temp directory + fs.cpSync(rootDir, tempDir, { + recursive: true, + filter(file) { + file = file.replace(/\\/g, '/') + return !file.includes('__tests__') && !/dist(?:\/|$)/.test(file) + }, + }) + + const testConfig: InlineConfig = { + root: tempDir, + logLevel: 'silent', + server: { + watch: { + usePolling: true, + interval: 100, + }, + }, + } + + if (isBuild) { + await build(testConfig) + const previewServer = await preview({ + ...testConfig, + preview: { + port: 0, + strictPort: false, + }, + }) + server = { + close: async () => { + previewServer.httpServer?.close() + }, + resolvedUrls: { + local: [ + // oxlint-disable-next-line no-unsafe-type-assertion -- address() returns AddressInfo for TCP server + `http://localhost:${(previewServer.httpServer?.address() as { port: number })?.port || 4173}/`, + ], + }, + } + } else { + server = await createServer(testConfig) + await server.listen() + } + + const url = server.resolvedUrls?.local[0] + if (!url) { + throw new Error('Could not get server URL') + } + + await page.goto(url) +}) + +afterAll(async () => { + await page?.close() + await server?.close() + if (tempDir && tempDir !== rootDir && fs.existsSync(tempDir)) { + fs.rmSync(tempDir, { recursive: true, force: true }) + } +}) + +declare module 'vitest' { + export interface ProvidedContext { + wsEndpoint: string + tempBaseDir: string + isBuild: string + } +} diff --git a/internal-packages/benchmark-utils/package.json b/internal-packages/benchmark-utils/package.json new file mode 100644 index 0000000..cdf9291 --- /dev/null +++ b/internal-packages/benchmark-utils/package.json @@ -0,0 +1,12 @@ +{ + "name": "@rolldown/benchmark-utils", + "version": "0.0.0", + "private": true, + "type": "module", + "exports": { + "./seeded-random": "./src/seeded-random.ts" + }, + "devDependencies": { + "@oxc-node/core": "^0.0.35" + } +} diff --git a/internal-packages/benchmark-utils/src/seeded-random.ts b/internal-packages/benchmark-utils/src/seeded-random.ts new file mode 100644 index 0000000..94eeeee --- /dev/null +++ b/internal-packages/benchmark-utils/src/seeded-random.ts @@ -0,0 +1,42 @@ +/** + * Seeded random number generator for deterministic output. + * All benchmark generators use seed=42. + */ +export class SeededRandom { + private seed: number + + constructor(seed: number) { + this.seed = seed + } + + next(): number { + this.seed = (this.seed * 1103515245 + 12345) & 0x7fffffff + return this.seed / 0x7fffffff + } + + nextInt(max: number): number { + return Math.floor(this.next() * max) + } + + pick(arr: T[]): T { + return arr[this.nextInt(arr.length)] + } + + shuffle(arr: T[]): T[] { + const result = [...arr] + for (let i = result.length - 1; i > 0; i--) { + const j = this.nextInt(i + 1) + ;[result[i], result[j]] = [result[j], result[i]] + } + return result + } + + pickN(arr: T[], n: number): T[] { + const shuffled = [...arr] + for (let i = shuffled.length - 1; i > 0; i--) { + const j = this.nextInt(i + 1) + ;[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]] + } + return shuffled.slice(0, n) + } +} diff --git a/internal-packages/oxc-unshadowed-visitor/README.md b/internal-packages/oxc-unshadowed-visitor/README.md new file mode 100644 index 0000000..fb150a7 --- /dev/null +++ b/internal-packages/oxc-unshadowed-visitor/README.md @@ -0,0 +1,32 @@ +# @rolldown/oxc-unshadowed-visitor + +Scope-aware AST visitor that tracks references to specified names, filtering out those shadowed by local bindings. Performs single-pass analysis using Rolldown's built-in AST visitor. + +> **Note:** This is a private internal package used by other `@rolldown` plugins. It is not published to npm. + +## Usage + +```ts +import { parseSync } from 'rolldown/utils' +import { ScopedVisitor } from '@rolldown/oxc-unshadowed-visitor' + +const program = parseSync('app.tsx', code).program + +const sv = new ScopedVisitor({ + trackedNames: ['React'], + visitor: { + Identifier(node, ctx) { + if (node.name === 'React') { + ctx.record({ name: node.name, node, data: 'jsx-ref' }) + } + }, + }, +}) + +const records = sv.walk(program) +// records contains only references where `React` is NOT shadowed +``` + +## How it works + +`ScopedVisitor` walks the AST once and maintains a scope stack internally. When your visitor calls `ctx.record()`, the record is kept only if the tracked name is not shadowed at that point. If a later `var` declaration hoists into scope, previously recorded references are retroactively invalidated. diff --git a/internal-packages/oxc-unshadowed-visitor/package.json b/internal-packages/oxc-unshadowed-visitor/package.json new file mode 100644 index 0000000..8ba1c6a --- /dev/null +++ b/internal-packages/oxc-unshadowed-visitor/package.json @@ -0,0 +1,17 @@ +{ + "name": "@rolldown/oxc-unshadowed-visitor", + "version": "0.0.0", + "private": true, + "type": "module", + "exports": "./src/index.ts", + "scripts": { + "bench": "vitest bench --project oxc-unshadowed-visitor", + "test": "vitest --project oxc-unshadowed-visitor" + }, + "devDependencies": { + "oxc-walker": "^0.7.0" + }, + "peerDependencies": { + "rolldown": "^1.0.0-rc.9" + } +} diff --git a/internal-packages/oxc-unshadowed-visitor/src/binding-names.ts b/internal-packages/oxc-unshadowed-visitor/src/binding-names.ts new file mode 100644 index 0000000..b90ff04 --- /dev/null +++ b/internal-packages/oxc-unshadowed-visitor/src/binding-names.ts @@ -0,0 +1,42 @@ +import type { ESTree } from 'rolldown/utils' + +/** + * Recursively extracts binding names from a pattern node. + */ +export function extractBindingNames( + pattern: ESTree.ParamPattern | ESTree.BindingPattern, + names: string[], +): void { + switch (pattern.type) { + case 'Identifier': + names.push(pattern.name) + break + + case 'ArrayPattern': + for (const element of pattern.elements) { + if (element != null) { + extractBindingNames(element, names) + } + } + break + + case 'ObjectPattern': + for (const prop of pattern.properties) { + if (prop.type === 'RestElement') { + extractBindingNames(prop, names) + } else { + // Property node — the value holds the binding pattern + extractBindingNames(prop.value, names) + } + } + break + + case 'AssignmentPattern': + extractBindingNames(pattern.left, names) + break + + case 'RestElement': + extractBindingNames(pattern.argument, names) + break + } +} diff --git a/internal-packages/oxc-unshadowed-visitor/src/index.test.ts b/internal-packages/oxc-unshadowed-visitor/src/index.test.ts new file mode 100644 index 0000000..5b19610 --- /dev/null +++ b/internal-packages/oxc-unshadowed-visitor/src/index.test.ts @@ -0,0 +1,322 @@ +import { describe, test, expect } from 'vitest' +import { parseSync } from 'rolldown/utils' +import { ScopedVisitor } from './index.ts' + +function parse(code: string) { + return parseSync('test.js', code).program +} + +function collectRecords(code: string, trackedNames = ['React']) { + const program = parse(code) + const sv = new ScopedVisitor({ + trackedNames, + visitor: { + Identifier(node, ctx) { + if (trackedNames.includes(node.name)) { + ctx.record({ name: node.name, node, data: 'ref' }) + } + }, + }, + }) + return sv.walk(program) +} + +describe('ScopedVisitor', () => { + test('collects records for unshadowed references', () => { + const records = collectRecords(`React.createElement('div');`) + expect(records.length).toBe(1) + expect(records[0].name).toBe('React') + expect(records[0].data).toBe('ref') + }) + + test('ignores records for untracked names', () => { + const code = `foo(); bar();` + const program = parse(code) + const sv = new ScopedVisitor({ + trackedNames: ['React'], + visitor: { + Identifier(node, ctx) { + ctx.record({ name: node.name, node, data: 'ref' }) + }, + }, + }) + const records = sv.walk(program) + expect(records.length).toBe(0) + }) + + test.each([ + { + name: 'let shadows tracked name in block scope', + code: ` + React.createElement('a'); + { let React = 'local'; React.createElement('b'); } + React.createElement('c'); + `, + expected: 2, + }, + { + name: 'const shadows tracked name in block scope', + code: ` + React.createElement('a'); + { const React = 'local'; React.createElement('b'); } + `, + expected: 1, + }, + { + name: 'var shadows tracked name in function scope', + code: ` + React.createElement('a'); + function foo() { var React = 'local'; React.createElement('b'); } + React.createElement('c'); + `, + expected: 2, + }, + { + name: 'function declaration shadows tracked name at block scope', + code: ` + React.createElement('x'); + { function React() {} React(); } + React.createElement('y'); + `, + expected: 2, + }, + { + name: 'class declaration shadows tracked name at block scope', + code: ` + React.createElement('x'); + { class React {} new React(); } + `, + expected: 1, + }, + { + name: 'function parameters shadow tracked name', + code: ` + React.createElement('outer'); + function foo(React) { React; } + `, + expected: 1, + }, + { + name: 'arrow function parameters shadow tracked name', + code: ` + React.createElement('outer'); + const fn = (React) => React; + `, + expected: 1, + }, + { + name: 'catch clause parameter shadows tracked name', + code: ` + React.createElement('outer'); + try {} catch (React) { React; } + React.createElement('after'); + `, + expected: 2, + }, + { + name: 'object destructuring shadows tracked name', + code: ` + React; + { const { React } = obj; React; } + `, + expected: 1, + }, + { + name: 'array destructuring shadows tracked name', + code: ` + React; + { const [React] = arr; React; } + `, + expected: 1, + }, + { + name: 'renamed destructuring shadows tracked name', + code: ` + React; + { const { other: React } = obj; React; } + `, + expected: 1, + }, + { + name: 'destructured function params shadow tracked name', + code: ` + React; + const fn = ({ React }) => React; + `, + expected: 1, + }, + { + name: 'rest parameter shadows tracked name', + code: ` + React; + function foo(...React) { React; } + `, + expected: 1, + }, + { + name: 'default parameter value shadows tracked name', + code: ` + React; + function foo(React = 'x') { React; } + `, + expected: 1, + }, + { + name: 'for-of loop variable shadows tracked name', + code: ` + React; + for (const React of items) { React; } + React; + `, + expected: 2, + }, + { + name: 'for-in loop variable shadows tracked name', + code: ` + React; + for (const React in obj) { React; } + React; + `, + expected: 2, + }, + { + name: 'for loop variable shadows tracked name', + code: ` + React; + for (let React = 0; React < 10; React++) { React; } + React; + `, + expected: 2, + }, + { + name: 'var declaration after reference retroactively invalidates records', + code: ` + function foo() { + React.createElement('a'); + var React = 'local'; + } + `, + expected: 0, + }, + { + name: 'var hoisting does not affect outer scope records', + code: ` + React.createElement('outer-a'); + function foo() { + React.createElement('inner'); + var React = 'local'; + } + React.createElement('outer-b'); + `, + expected: 2, + }, + { + name: 'var hoisting across nested blocks within function', + code: ` + function foo() { + { React; } + if (true) { var React = 'local'; } + } + `, + expected: 0, + }, + { + name: 'deeply nested shadowing only affects inner scope', + code: ` + React; + { React; { let React = 'local'; React; } React; } + `, + expected: 3, + }, + { + name: 'same name shadowed at multiple nesting levels', + code: ` + React; + { + let React = 'level-1'; + React; + { let React = 'level-2'; React; } + } + `, + expected: 1, + }, + ])('$name', ({ code, expected }) => { + expect(collectRecords(code).length).toBe(expected) + }) + + test('partial shadowing: only some tracked names shadowed', () => { + const code = ` + React; + process.env; + function foo() { + let React = 'local'; + React; + process.env; + } + ` + expect(collectRecords(code, ['React', 'process']).length).toBe(3) + }) + + test('var does not shadow at module level', () => { + const program = parseSync('test.js', `{ var React = 'still top-level'; }`, { + sourceType: 'script', + }).program + const sv = new ScopedVisitor({ + trackedNames: ['React'], + visitor: { + Identifier(node, ctx) { + if (node.name === 'React') { + ctx.record({ name: node.name, node, data: 'ref' }) + } + }, + }, + }) + const records = sv.walk(program) + expect(records.length).toBe(1) + }) + + test('user exit callbacks work correctly', () => { + const program = parse(`function foo() { React; }`) + const events: string[] = [] + const sv = new ScopedVisitor({ + trackedNames: ['React'], + visitor: { + FunctionDeclaration(node, _ctx) { + events.push('enter:' + node.id!.name) + }, + 'FunctionDeclaration:exit'(node, _ctx) { + events.push('exit:' + node.id!.name) + }, + Identifier(node, ctx) { + if (node.name === 'React') { + ctx.record({ name: node.name, node, data: 'ref' }) + } + }, + }, + }) + const records = sv.walk(program) + expect(events).toEqual(['enter:foo', 'exit:foo']) + expect(records.length).toBe(1) + }) + + test('user exit callbacks fire after scope is still active', () => { + const program = parse(`function foo() { let React = 'local'; React; }`) + const exitShadowState: boolean[] = [] + const sv = new ScopedVisitor({ + trackedNames: ['React'], + visitor: { + Identifier(node, ctx) { + if (node.name === 'React') { + ctx.record({ name: node.name, node, data: 'ref' }) + } + }, + 'FunctionDeclaration:exit'(_node, _ctx) { + exitShadowState.push(true) + }, + }, + }) + const records = sv.walk(program) + expect(records.length).toBe(0) + expect(exitShadowState.length).toBe(1) + }) +}) diff --git a/internal-packages/oxc-unshadowed-visitor/src/index.ts b/internal-packages/oxc-unshadowed-visitor/src/index.ts new file mode 100644 index 0000000..6f62f18 --- /dev/null +++ b/internal-packages/oxc-unshadowed-visitor/src/index.ts @@ -0,0 +1,7 @@ +export { ScopedVisitor } from './scoped-visitor.ts' +export type { + TransformRecord, + ScopedVisitorObject, + ScopedVisitorOptions, +} from './scoped-visitor.ts' +export type { VisitorContext } from './types.ts' diff --git a/internal-packages/oxc-unshadowed-visitor/src/merge-visitors.ts b/internal-packages/oxc-unshadowed-visitor/src/merge-visitors.ts new file mode 100644 index 0000000..ad64ee7 --- /dev/null +++ b/internal-packages/oxc-unshadowed-visitor/src/merge-visitors.ts @@ -0,0 +1,66 @@ +import type { ESTree } from 'rolldown/utils' +import type { VisitorContext } from './types' + +export type SimpleScopedVisitorObject = { + [key: string]: (node: ESTree.Node, ctx: VisitorContext) => void +} +export type SimpleVisitorObject = { + [key: string]: (node: ESTree.Node) => void +} + +/** + * Merge user visitors with internal scope-tracking visitors. + * Enter: internal runs FIRST, then user. + * Exit: user runs FIRST, then internal. + */ +export function mergeVisitors( + userVisitor: SimpleScopedVisitorObject, + ctx: VisitorContext, + internalEnter: SimpleVisitorObject, + internalExit: SimpleVisitorObject, +): SimpleVisitorObject { + const merged: SimpleVisitorObject = {} + + // Process user visitor keys + for (const key of Object.keys(userVisitor)) { + const userFn = userVisitor[key] + const isExit = key.endsWith(':exit') + const baseKey = isExit ? key.slice(0, -5) : key + + if (isExit) { + const internalExitFn = internalExit[key] + if (internalExitFn) { + merged[key] = (node) => { + userFn?.(node, ctx) + internalExitFn(node) + } + } else { + merged[key] = (node) => { + userFn?.(node, ctx) + } + } + } else { + const internalEnterFn = internalEnter[baseKey] + if (internalEnterFn) { + merged[key] = (node) => { + internalEnterFn(node) + userFn(node, ctx) + } + } else { + merged[key] = (node) => { + userFn(node, ctx) + } + } + } + } + + // Add internal-only visitors that the user didn't define + for (const [key, fn] of Object.entries(internalEnter)) { + if (!(key in merged)) merged[key] = fn + } + for (const [key, fn] of Object.entries(internalExit)) { + if (!(key in merged)) merged[key] = fn + } + + return merged +} diff --git a/internal-packages/oxc-unshadowed-visitor/src/scope-tracker.ts b/internal-packages/oxc-unshadowed-visitor/src/scope-tracker.ts new file mode 100644 index 0000000..c95cec0 --- /dev/null +++ b/internal-packages/oxc-unshadowed-visitor/src/scope-tracker.ts @@ -0,0 +1,94 @@ +interface ScopeFrame { + kind: 'function' | 'block' + /** per tracked name, does THIS scope shadow it? */ + shadows: boolean[] + /** index into records array when scope was pushed */ + recordsStartIdx: number +} + +export interface Invalidatable { + nameIdx: number + invalidated: boolean +} + +export class ScopeTracker { + /** per tracked name, 0 = not shadowed */ + private shadowDepth: number[] + private nameCount: number + private scopeStack: ScopeFrame[] + + constructor(nameCount: number) { + this.nameCount = nameCount + this.shadowDepth = Array.from({ length: nameCount }).fill(0) + this.scopeStack = [] + } + + pushScope(kind: 'function' | 'block', recordsLength: number): void { + this.scopeStack.push({ + kind, + shadows: Array.from({ length: this.nameCount }).fill(false), + recordsStartIdx: recordsLength, + }) + } + + popScope(): void { + const frame = this.scopeStack.pop() + if (!frame) return + + // For each name shadowed by this scope, decrement the depth counter + for (let i = 0; i < this.nameCount; i++) { + if (frame.shadows[i]) { + this.shadowDepth[i]-- + } + } + } + + /** + * Declare a block-scoped binding (let, const, class, catch param). + * Declares at the top of the scope stack. + * If the stack is empty (module level), returns without shadowing. + */ + declareBlock(nameIdx: number, records: Invalidatable[]): void { + if (this.scopeStack.length === 0) return // module level — not shadowing + const frame = this.scopeStack[this.scopeStack.length - 1] + this._declare(frame, nameIdx, records) + } + + /** + * Declare a var-scoped binding. + * Walks up the scope stack to find the nearest 'function' scope. + * If none found (module level), returns without shadowing. + */ + declareVar(nameIdx: number, records: Invalidatable[]): void { + // Walk up to find nearest function scope + for (let i = this.scopeStack.length - 1; i >= 0; i--) { + if (this.scopeStack[i].kind === 'function') { + this._declare(this.scopeStack[i], nameIdx, records) + return + } + } + // No function scope found — module level, no shadowing + } + + isShadowed(nameIdx: number): boolean { + return this.shadowDepth[nameIdx] > 0 + } + + private _declare(frame: ScopeFrame, nameIdx: number, records: Invalidatable[]): void { + if (frame.shadows[nameIdx]) return // already shadowed in this scope + + frame.shadows[nameIdx] = true + this.shadowDepth[nameIdx]++ + + // Retroactively invalidate records from this scope's start + this._retroactiveInvalidate(frame.recordsStartIdx, nameIdx, records) + } + + private _retroactiveInvalidate(fromIdx: number, nameIdx: number, records: Invalidatable[]): void { + for (let i = fromIdx; i < records.length; i++) { + if (records[i].nameIdx === nameIdx) { + records[i].invalidated = true + } + } + } +} diff --git a/internal-packages/oxc-unshadowed-visitor/src/scoped-visitor.ts b/internal-packages/oxc-unshadowed-visitor/src/scoped-visitor.ts new file mode 100644 index 0000000..7438879 --- /dev/null +++ b/internal-packages/oxc-unshadowed-visitor/src/scoped-visitor.ts @@ -0,0 +1,173 @@ +import { Visitor } from 'rolldown/utils' +import type { ESTree, VisitorObject } from 'rolldown/utils' +import { ScopeTracker, type Invalidatable } from './scope-tracker.ts' +import { extractBindingNames } from './binding-names.ts' +import type { VisitorContext } from './types.ts' +import { + mergeVisitors, + type SimpleScopedVisitorObject, + type SimpleVisitorObject, +} from './merge-visitors.ts' + +export interface TransformRecord { + name: string + node: object + data: T +} + +interface InternalRecord extends TransformRecord, Invalidatable { + nameIdx: number + invalidated: boolean +} + +type ScopedVisitorHandler = H extends (node: infer N) => void + ? (node: N, ctx: VisitorContext) => void + : H + +export type ScopedVisitorObject = { + [K in keyof VisitorObject]?: + | ScopedVisitorHandler, T> + | undefined +} + +export interface ScopedVisitorOptions { + trackedNames: string[] + visitor: ScopedVisitorObject +} + +export class ScopedVisitor { + private trackedNames: string[] + private userVisitor: ScopedVisitorObject + + constructor(options: ScopedVisitorOptions) { + this.trackedNames = options.trackedNames + this.userVisitor = options.visitor + } + + walk(program: ESTree.Program): TransformRecord[] { + const records: InternalRecord[] = [] + const trackedNames = this.trackedNames + const tracker = new ScopeTracker(trackedNames.length) + + const ctx: VisitorContext = { + record(opts) { + const nameIdx = trackedNames.indexOf(opts.name) + if (nameIdx === -1) return // not a tracked name + + records.push({ + name: opts.name, + node: opts.node, + data: opts.data, + nameIdx, + invalidated: tracker.isShadowed(nameIdx), + }) + }, + } + + // Reusable temp array to avoid allocations per declaration + const tempNames: string[] = [] + + /** + * Declare all binding names from a pattern for a given declaration style. + */ + const declarePattern = ( + pattern: ESTree.ParamPattern | ESTree.BindingPattern, + mode: 'block' | 'var', + ): void => { + tempNames.length = 0 + extractBindingNames(pattern, tempNames) + for (const name of tempNames) { + const idx = trackedNames.indexOf(name) + if (idx === -1) continue + if (mode === 'block') { + tracker.declareBlock(idx, records) + } else { + tracker.declareVar(idx, records) + } + } + } + /** + * Declare all function params as block-scoped bindings. + */ + const declareParams = (params: ESTree.ParamPattern[]): void => { + for (const param of params) declarePattern(param, 'block') + } + + const scopeEnter: VisitorObject = { + FunctionDeclaration(node) { + // Declare function name in OUTER block scope before pushing + if (node.id) declarePattern(node.id, 'block') + tracker.pushScope('function', records.length) + if (node.params) declareParams(node.params) + }, + FunctionExpression(node) { + tracker.pushScope('function', records.length) + // Declare function name inside function scope (named function expression) + if (node.id) declarePattern(node.id, 'block') + if (node.params) declareParams(node.params) + }, + ArrowFunctionExpression(node) { + tracker.pushScope('function', records.length) + if (node.params) declareParams(node.params) + }, + BlockStatement(_node) { + tracker.pushScope('block', records.length) + }, + ForStatement(_node) { + tracker.pushScope('block', records.length) + }, + ForInStatement(_node) { + tracker.pushScope('block', records.length) + }, + ForOfStatement(_node) { + tracker.pushScope('block', records.length) + }, + SwitchStatement(_node) { + tracker.pushScope('block', records.length) + }, + StaticBlock(_node) { + tracker.pushScope('block', records.length) + }, + CatchClause(node) { + tracker.pushScope('block', records.length) + if (node.param) { + declarePattern(node.param, 'block') + } + }, + } + const scopeExit: SimpleVisitorObject = {} + for (const key of Object.keys(scopeEnter)) { + scopeExit[`${key}:exit`] = () => tracker.popScope() + } + + const declarationOnlyEnter: VisitorObject = { + VariableDeclaration(node) { + const mode = node.kind === 'var' ? 'var' : 'block' + for (const declarator of node.declarations) { + declarePattern(declarator.id, mode) + } + }, + ClassDeclaration(node) { + if (node.id) { + declarePattern(node.id, 'block') + } + }, + } + + const oxcVisitor = mergeVisitors( + // oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion --- TypeScript cannot handle these complex types + this.userVisitor as SimpleScopedVisitorObject, + ctx, + // oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion --- TypeScript cannot handle these complex types + { ...(scopeEnter as SimpleVisitorObject), ...(declarationOnlyEnter as SimpleVisitorObject) }, + scopeExit, + ) + + const visitor = new Visitor(oxcVisitor) + visitor.visit(program) + + return records + .filter((r) => !r.invalidated) + .map(({ name, node, data }) => ({ name, node, data })) + } +} diff --git a/internal-packages/oxc-unshadowed-visitor/src/types.ts b/internal-packages/oxc-unshadowed-visitor/src/types.ts new file mode 100644 index 0000000..fa873f1 --- /dev/null +++ b/internal-packages/oxc-unshadowed-visitor/src/types.ts @@ -0,0 +1,3 @@ +export interface VisitorContext { + record(opts: { name: string; node: object; data: T }): void +} diff --git a/internal-packages/oxc-unshadowed-visitor/src/walk.bench.ts b/internal-packages/oxc-unshadowed-visitor/src/walk.bench.ts new file mode 100644 index 0000000..4044fc9 --- /dev/null +++ b/internal-packages/oxc-unshadowed-visitor/src/walk.bench.ts @@ -0,0 +1,133 @@ +import { bench, describe } from 'vitest' +import { parseSync } from 'rolldown/utils' +import { walk, ScopeTracker } from 'oxc-walker' +import { ScopedVisitor } from './index.js' + +// Generate a realistic fixture with shadowed and unshadowed references +function generateFixture(repetitions: number): string { + const blocks: string[] = [] + blocks.push(`import something from 'somewhere';`) + for (let i = 0; i < repetitions; i++) { + blocks.push(` + React.createElement('div-${i}'); + function fn${i}(x) { + const y = x + ${i}; + if (y > 0) { + let React = 'shadow'; + React.createElement('shadowed-${i}'); + } + return React.createElement('unshadowed-inner-${i}'); + } + { + const a${i} = React.createElement('block-${i}'); + } + `) + } + return blocks.join('\n') +} + +const smallProgram = parseSync('test.js', generateFixture(10)).program +const mediumProgram = parseSync('test.js', generateFixture(100)).program +const largeProgram = parseSync('test.js', generateFixture(500)).program + +describe('small (10)', () => { + bench('single-pass (ScopedVisitor)', () => { + const sv = new ScopedVisitor({ + trackedNames: ['React'], + visitor: { + Identifier(node, ctx) { + if (node.name === 'React') { + ctx.record({ name: 'React', node, data: 'ref' }) + } + }, + }, + }) + sv.walk(smallProgram) + }) + + bench('two-pass (oxc-walker)', () => { + const scopeTracker = new ScopeTracker({ preserveExitedScopes: true }) + walk(smallProgram, { scopeTracker, enter() {} }) + scopeTracker.freeze() + const results: { name: string; node: any }[] = [] + walk(smallProgram, { + scopeTracker, + enter(node) { + if (node.type === 'Identifier' && 'name' in node && node.name === 'React') { + const decl = scopeTracker.getDeclaration('React') + if (!decl) { + results.push({ name: 'React', node }) + } + } + }, + }) + }) +}) + +describe('medium (100)', () => { + bench('single-pass (ScopedVisitor)', () => { + const sv = new ScopedVisitor({ + trackedNames: ['React'], + visitor: { + Identifier(node, ctx) { + if (node.name === 'React') { + ctx.record({ name: 'React', node, data: 'ref' }) + } + }, + }, + }) + sv.walk(mediumProgram) + }) + + bench('two-pass (oxc-walker)', () => { + const scopeTracker = new ScopeTracker({ preserveExitedScopes: true }) + walk(mediumProgram, { scopeTracker, enter() {} }) + scopeTracker.freeze() + const results: { name: string; node: any }[] = [] + walk(mediumProgram, { + scopeTracker, + enter(node) { + if (node.type === 'Identifier' && 'name' in node && node.name === 'React') { + const decl = scopeTracker.getDeclaration('React') + if (!decl) { + results.push({ name: 'React', node }) + } + } + }, + }) + }) +}) + +describe('large (500)', () => { + bench('single-pass (ScopedVisitor)', () => { + const sv = new ScopedVisitor({ + trackedNames: ['React'], + visitor: { + Identifier(node, ctx) { + if (node.name === 'React') { + ctx.record({ name: 'React', node, data: 'ref' }) + } + }, + }, + }) + sv.walk(largeProgram) + }) + + bench('two-pass (oxc-walker)', () => { + const scopeTracker = new ScopeTracker({ preserveExitedScopes: true }) + walk(largeProgram, { scopeTracker, enter() {} }) + scopeTracker.freeze() + const results: { name: string; node: any }[] = [] + walk(largeProgram, { + scopeTracker, + enter(node) { + if (node.type === 'Identifier' && 'name' in node && node.name === 'React') { + const decl = scopeTracker.getDeclaration('React') + if (!decl) { + results.push({ name: 'React', node }) + } + } + }, + }) + }) +}) diff --git a/internal-packages/oxc-unshadowed-visitor/vitest.config.ts b/internal-packages/oxc-unshadowed-visitor/vitest.config.ts new file mode 100644 index 0000000..276ef99 --- /dev/null +++ b/internal-packages/oxc-unshadowed-visitor/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + name: 'oxc-unshadowed-visitor', + }, +}) diff --git a/internal-packages/swc-output-gen/package.json b/internal-packages/swc-output-gen/package.json new file mode 100644 index 0000000..527947e --- /dev/null +++ b/internal-packages/swc-output-gen/package.json @@ -0,0 +1,17 @@ +{ + "name": "@rolldown/swc-output-gen", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "generate": "oxnode src/index.ts" + }, + "dependencies": { + "@oxc-node/cli": "^0.0.35", + "@rollup/plugin-swc": "^0.4.0", + "@swc/core": "^1.15.18", + "@swc/plugin-emotion": "^14.7.0", + "rolldown": "^1.0.0-rc.9", + "tinyglobby": "^0.2.15" + } +} diff --git a/internal-packages/swc-output-gen/src/index.ts b/internal-packages/swc-output-gen/src/index.ts new file mode 100644 index 0000000..ff58070 --- /dev/null +++ b/internal-packages/swc-output-gen/src/index.ts @@ -0,0 +1,220 @@ +/** + * Generate SWC plugin outputs for comparison with ported plugins. + * + * Usage: + * pnpm generate:swc-outputs # All plugins + * pnpm generate:swc-outputs --plugins emotion # Specific plugin(s) + * pnpm generate:swc-outputs --dry-run # Preview only + */ + +import { readFile, writeFile } from 'node:fs/promises' +import { basename, dirname, join, relative } from 'node:path' +import { glob } from 'tinyglobby' +import { pluginRegistry, getPluginFromDirectory, getPluginNames } from './plugin-registry.js' +import { transformWithSwc } from './swc-transformer.js' + +interface CliOptions { + plugins: string[] + dryRun: boolean +} + +function parseArgs(): CliOptions { + const args = process.argv.slice(2) + const options: CliOptions = { + plugins: [], + dryRun: false, + } + + for (let i = 0; i < args.length; i++) { + const arg = args[i] + if (arg === '--dry-run') { + options.dryRun = true + } else if (arg === '--plugins') { + // Collect all following non-flag arguments as plugin names + i++ + while (i < args.length && !args[i].startsWith('--')) { + options.plugins.push(args[i]) + i++ + } + i-- // Back up one since the loop will increment + } + } + + // Validate plugin names + const validPlugins = getPluginNames() + for (const plugin of options.plugins) { + if (!validPlugins.includes(plugin)) { + console.error(`Unknown plugin: ${plugin}`) + console.error(`Available plugins: ${validPlugins.join(', ')}`) + process.exit(1) + } + } + + return options +} + +async function discoverFixtures(pluginFilter: string[]): Promise { + const packagesDir = join(import.meta.dirname, '..', '..', '..', 'packages') + + // Find all input files in both fixtures and fixtures-labels directories + const inputFiles = await glob( + [ + '*/tests/fixtures/**/input.{js,jsx,ts,tsx}', + '*/tests/fixtures-labels/*/input.{js,jsx,ts,tsx}', + ], + { + cwd: packagesDir, + absolute: true, + }, + ) + + // Filter by plugin if specified + if (pluginFilter.length > 0) { + return inputFiles.filter((file) => { + const relativePath = relative(packagesDir, file) + const pluginDir = relativePath.split('/')[0] + const pluginName = getPluginFromDirectory(pluginDir) + return pluginName && pluginFilter.includes(pluginName) + }) + } + + return inputFiles +} + +async function loadConfig(fixtureDir: string): Promise> { + const configPath = join(fixtureDir, 'config.json') + try { + const content = await readFile(configPath, 'utf-8') + return JSON.parse(content) + } catch { + // Config is optional, default to empty object + return {} + } +} + +interface ProcessResult { + fixture: string + status: 'success' | 'skipped' | 'error' + message?: string +} + +async function processFixture(inputPath: string, dryRun: boolean): Promise { + const fixtureDir = dirname(inputPath) + const packagesDir = join(import.meta.dirname, '..', '..', '..', 'packages') + const relativePath = relative(packagesDir, inputPath).replaceAll('\\', '/') + const pluginDir = relativePath.split('/')[0] + const pluginName = getPluginFromDirectory(pluginDir) + + if (!pluginName) { + return { + fixture: relativePath, + status: 'skipped', + message: 'Unknown plugin', + } + } + + const pluginConfig = pluginRegistry[pluginName] + const config = await loadConfig(fixtureDir) + + // Check if we should skip this fixture + if (pluginConfig.shouldSkip?.(config, { fixtureDir })) { + return { + fixture: relativePath, + status: 'skipped', + message: 'Fixture uses unsupported options', + } + } + + // Map config to SWC plugin options + const plugins = pluginConfig.mapOptions(config, { fixtureDir }) + + if (plugins.length === 0) { + return { + fixture: relativePath, + status: 'skipped', + message: 'No plugins to apply', + } + } + + if (dryRun) { + return { + fixture: relativePath, + status: 'success', + message: `Would transform with: ${plugins.map(([pkg]) => pkg).join(', ')}`, + } + } + + try { + // Read input file + const code = await readFile(inputPath, 'utf-8') + const filename = basename(inputPath) + + // Transform with SWC + const output = await transformWithSwc(code, { + filename, + plugins, + }) + + // Write output + const outputPath = join(fixtureDir, 'output.swc.js') + await writeFile(outputPath, output, 'utf-8') + + return { + fixture: relativePath, + status: 'success', + } + } catch (error) { + return { + fixture: relativePath, + status: 'error', + message: error instanceof Error ? error.message : String(error), + } + } +} + +async function main() { + const options = parseArgs() + + console.log('Discovering fixtures...') + const fixtures = await discoverFixtures(options.plugins) + + if (fixtures.length === 0) { + console.log('No fixtures found.') + return + } + + console.log(`Found ${fixtures.length} fixtures.`) + if (options.dryRun) { + console.log('(Dry run - no files will be written)\n') + } else { + console.log('') + } + + const results: ProcessResult[] = [] + + for (const fixture of fixtures) { + const result = await processFixture(fixture, options.dryRun) + results.push(result) + + // Print result + const icon = result.status === 'success' ? '✓' : result.status === 'skipped' ? '○' : '✗' + const message = result.message ? ` (${result.message})` : '' + console.log(`${icon} ${result.fixture}${message}`) + } + + // Summary + const success = results.filter((r) => r.status === 'success').length + const skipped = results.filter((r) => r.status === 'skipped').length + const errors = results.filter((r) => r.status === 'error').length + + console.log(`\nSummary: ${success} success, ${skipped} skipped, ${errors} errors`) + + if (errors > 0) { + process.exit(1) + } +} + +main().catch((error) => { + console.error('Fatal error:', error) + process.exit(1) +}) diff --git a/internal-packages/swc-output-gen/src/plugin-registry.ts b/internal-packages/swc-output-gen/src/plugin-registry.ts new file mode 100644 index 0000000..247b8cf --- /dev/null +++ b/internal-packages/swc-output-gen/src/plugin-registry.ts @@ -0,0 +1,47 @@ +/** + * Plugin registry that maps our plugin names to their original SWC packages + * and provides option mappers for transforming fixture configs to SWC plugin options. + */ + +export interface MapOptionsContext { + /** Path to the fixture directory */ + fixtureDir: string +} + +export interface PluginConfig { + /** Name of the original SWC plugin package(s) */ + packages: string[] + /** Map fixture config to SWC plugin options. Returns array of [package, options] tuples */ + mapOptions: ( + config: Record, + ctx: MapOptionsContext, + ) => Array<[string, Record]> + /** Whether to skip this fixture based on config and context */ + shouldSkip?: (config: Record, ctx: MapOptionsContext) => boolean +} + +export const pluginRegistry: Record = { + emotion: { + packages: ['@swc/plugin-emotion'], + mapOptions: (config) => [['@swc/plugin-emotion', config]], + shouldSkip: () => false, + }, +} + +/** Get list of all supported plugin names */ +export function getPluginNames(): string[] { + return Object.keys(pluginRegistry) +} + +/** Get plugin config by name, extracting from package directory name */ +export function getPluginFromDirectory(dirName: string): string | undefined { + // Directory name is the plugin name directly (e.g., "emotion") + if (pluginRegistry[dirName]) return dirName + + // Also support "plugin-*" naming convention + const match = dirName.match(/^plugin-(.+)$/) + if (!match) return undefined + + const pluginName = match[1] + return pluginRegistry[pluginName] ? pluginName : undefined +} diff --git a/internal-packages/swc-output-gen/src/swc-transformer.ts b/internal-packages/swc-output-gen/src/swc-transformer.ts new file mode 100644 index 0000000..f65cf23 --- /dev/null +++ b/internal-packages/swc-output-gen/src/swc-transformer.ts @@ -0,0 +1,111 @@ +import { rolldown } from 'rolldown' +import swc from '@rollup/plugin-swc' +import type { JscTarget } from '@swc/core' + +export interface TransformOptions { + filename: string + plugins: Array<[string, Record]> + /** Source code - used to detect JSX in .js files */ + code?: string +} + +/** + * Check if code likely contains JSX syntax + */ +function containsJsx(code: string): boolean { + // Look for JSX patterns like { + const parserConfig = getParserConfig(options.filename, code) + + // Use extension from original filename for virtual entry to ensure correct parsing + const ext = options.filename.match(/\.[jt]sx?$/)?.[0] ?? '.ts' + const virtualEntry = `virtual:entry${ext}` + + const build = await rolldown({ + input: virtualEntry, + plugins: [ + { + name: 'virtual', + resolveId(id) { + if (id === virtualEntry) return id + // Mark transformed imports as external + return { id, external: true } + }, + load(id) { + if (id === virtualEntry) return code + }, + }, + swc({ + swc: { + jsc: { + parser: parserConfig, + target: 'esnext' as JscTarget, + transform: { + react: { + // Preserve JSX - don't transform it + runtime: 'preserve' as const, + }, + }, + experimental: { + plugins: options.plugins, + }, + }, + // Don't add source maps + sourceMaps: false, + }, + }), + ], + }) + + const { output } = await build.generate({ format: 'esm' }) + return normalizeSourceMap(stripRolldownRuntime(output[0].code)) +} + +function stripRolldownRuntime(code: string): string { + // Replace rolldown runtime regions with a stable comment + return code.replace( + /\/\/#region \\0rolldown\/runtime\.js[\s\S]*?\/\/#endregion\n*/g, + '// [rolldown runtime elided]\n', + ) +} + +function normalizeSourceMap(code: string): string { + return code.replace( + /\/\*# sourceMappingURL=data:application\/json;charset=utf-8;base64,[^*]+ \*\//g, + '/*# sourceMappingURL=[sourcemap] */', + ) +} diff --git a/package.json b/package.json index da92f42..a87de24 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,12 @@ "format": "oxfmt .", "lint": "oxlint --type-aware --type-check .", "test": "vitest", + "test:e2e": "pnpm run --filter=./examples test", "dev": "pnpm -r --parallel --filter=\"./packages/*\" run dev", "build": "pnpm -r --filter=\"./packages/*\" run build", "release": "pnpm run --filter=./scripts release", - "ci-publish": "pnpm run --filter=./scripts ci-publish" + "ci-publish": "pnpm run --filter=./scripts ci-publish", + "generate:swc-outputs": "pnpm run --filter=@rolldown/swc-output-gen generate" }, "devDependencies": { "@typescript/native-preview": "7.0.0-dev.20260314.1", diff --git a/packages/babel/tsdown.config.ts b/packages/babel/tsdown.config.ts index 87d8936..a3a2720 100644 --- a/packages/babel/tsdown.config.ts +++ b/packages/babel/tsdown.config.ts @@ -3,6 +3,7 @@ import { defineConfig } from 'tsdown' export default defineConfig({ entry: './src/index.ts', dts: { + tsconfig: '../../tsconfig.common.json', tsgo: true, }, }) diff --git a/packages/emotion/LICENSE b/packages/emotion/LICENSE new file mode 120000 index 0000000..30cff74 --- /dev/null +++ b/packages/emotion/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/packages/emotion/README.md b/packages/emotion/README.md new file mode 100644 index 0000000..6514800 --- /dev/null +++ b/packages/emotion/README.md @@ -0,0 +1,129 @@ +# @rolldown/plugin-emotion [![npm](https://img.shields.io/npm/v/@rolldown/plugin-emotion.svg)](https://npmx.dev/package/@rolldown/plugin-emotion) + +Rolldown plugin for minification and optimization of [Emotion](https://emotion.sh/) styles. + +This plugin utilizes Rolldown's [native magic string API](https://rolldown.rs/in-depth/native-magic-string) instead of Babel and is more performant than using [`@emotion/babel-plugin`](https://emotion.sh/docs/@emotion/babel-plugin) with [`@rolldown/plugin-babel`](https://npmx.dev/package/@rolldown/plugin-babel). + +## Install + +```bash +pnpm add -D @rolldown/plugin-emotion +``` + +## Usage + +```js +import emotion from '@rolldown/plugin-emotion' + +export default { + plugins: [ + emotion({ + // options + }), + ], +} +``` + +### Supported Libraries + +The plugin handles imports from these Emotion packages out of the box: + +- `@emotion/css` +- `@emotion/styled` +- `@emotion/react` +- `@emotion/primitives` +- `@emotion/native` + +## Options + +### `sourceMap` + +- **Type:** `boolean` +- **Default:** `true` in development, `false` otherwise + +Generate source maps for Emotion CSS. Source maps help trace styles back to their original source in browser DevTools. + +### `autoLabel` + +- **Type:** `'never' | 'dev-only' | 'always'` +- **Default:** `'dev-only'` + +Controls when debug labels are added to styled components and `css` calls. + +- `'never'` — Never add labels +- `'dev-only'` — Only add labels in development mode +- `'always'` — Always add labels + +### `labelFormat` + +- **Type:** `string` +- **Default:** `"[local]"` + +Defines the format of generated debug labels. Only relevant when `autoLabel` is not `'never'`. + +Supports placeholders: + +- `[local]` — The variable name that the result of `css` or `styled` call is assigned to +- `[filename]` — The file name (without extension) +- `[dirname]` — The directory name of the file + +```js +emotion({ + autoLabel: 'always', + labelFormat: '[dirname]--[filename]--[local]', +}) +``` + +### `importMap` + +- **Type:** `Record` + +Custom import mappings for non-standard Emotion packages. Maps package names to their export configurations, allowing the plugin to transform custom libraries that re-export Emotion utilities. + +```js +emotion({ + importMap: { + 'my-emotion-lib': { + myStyled: { + canonicalImport: ['@emotion/styled', 'default'], + }, + myCss: { + canonicalImport: ['@emotion/react', 'css'], + }, + }, + }, +}) +``` + +Each entry maps an export name to its canonical Emotion equivalent via `canonicalImport: [packageName, exportName]`. + +## Benchmark + +Results of the benchmark that can be run by `pnpm bench` in `./benchmark` directory: + +``` + name hz min max mean p75 p99 p995 p999 rme samples +· @rolldown/plugin-emotion 9.7954 98.4954 108.83 102.09 103.34 108.83 108.83 108.83 ±2.23% 10 +· @rolldown/plugin-babel 3.7139 254.48 295.01 269.26 277.63 295.01 295.01 295.01 ±3.49% 10 +· @rollup/plugin-swc 7.5542 128.56 139.14 132.38 134.82 139.14 139.14 139.14 ±1.78% 10 + +@rolldown/plugin-emotion - bench/emotion.bench.ts > Emotion Benchmark + 1.30x faster than @rollup/plugin-swc + 2.64x faster than @rolldown/plugin-babel +``` + +The benchmark was ran on the following environment: + +``` +OS: macOS Tahoe 26.3 +CPU: Apple M4 +Memory: LPDDR5X-7500 32GB +``` + +## License + +MIT + +## Credits + +The implementation is based on [swc-project/plugins/packages/emotion](https://github.com/swc-project/plugins/tree/main/packages/emotion) ([Apache License 2.0](https://github.com/swc-project/plugins/blob/main/LICENSE)). Test cases are also adapted from it. diff --git a/packages/emotion/benchmark/.gitignore b/packages/emotion/benchmark/.gitignore new file mode 100644 index 0000000..8bab002 --- /dev/null +++ b/packages/emotion/benchmark/.gitignore @@ -0,0 +1,8 @@ +# Build outputs +dist/ + +# Generated components (regenerated with pnpm generate) +shared-app/src/components/ + +# SWC plugin cache +.swc/ diff --git a/packages/emotion/benchmark/bench/emotion.bench.ts b/packages/emotion/benchmark/bench/emotion.bench.ts new file mode 100644 index 0000000..373d192 --- /dev/null +++ b/packages/emotion/benchmark/bench/emotion.bench.ts @@ -0,0 +1,52 @@ +import { bench, describe } from 'vitest' +import { execSync } from 'node:child_process' +import { existsSync, rmSync } from 'node:fs' +import { resolve } from 'node:path' + +const baseDir = resolve(import.meta.dirname, '..') +const distBase = resolve(baseDir, 'dist') +const componentsDir = resolve(baseDir, 'shared-app/src/components') + +if (!existsSync(componentsDir)) { + execSync('pnpm generate', { cwd: baseDir, stdio: 'inherit' }) +} + +function cleanDist(name: string) { + const dir = resolve(distBase, name) + if (existsSync(dir)) { + rmSync(dir, { recursive: true }) + } +} + +function runBuild(name: string) { + execSync(`rolldown -c configs/${name}.ts`, { + cwd: baseDir, + stdio: 'pipe', + }) +} + +describe('Emotion Benchmark', () => { + bench( + '@rolldown/plugin-emotion', + () => { + runBuild('custom') + }, + { teardown: () => cleanDist('custom') }, + ) + + bench( + '@rolldown/plugin-babel', + () => { + runBuild('babel') + }, + { teardown: () => cleanDist('babel') }, + ) + + bench( + '@rollup/plugin-swc', + () => { + runBuild('swc') + }, + { teardown: () => cleanDist('swc') }, + ) +}) diff --git a/packages/emotion/benchmark/configs/babel.ts b/packages/emotion/benchmark/configs/babel.ts new file mode 100644 index 0000000..8969aaa --- /dev/null +++ b/packages/emotion/benchmark/configs/babel.ts @@ -0,0 +1,27 @@ +import { defineConfig } from 'rolldown' +import { resolve } from 'node:path' +import babel, { defineRolldownBabelPreset } from '@rolldown/plugin-babel' + +const emotionPreset = defineRolldownBabelPreset({ + preset: () => ({ + plugins: [['@emotion/babel-plugin', { autoLabel: 'always', sourceMap: false }]], + }), + rolldown: { + filter: { + id: /\.[jt]sx?$/, + code: '@emotion', + }, + }, +}) + +export default defineConfig({ + input: resolve(import.meta.dirname, '../shared-app/src/index.tsx'), + output: { + dir: resolve(import.meta.dirname, '../dist/babel'), + }, + plugins: [ + babel({ + presets: [emotionPreset], + }), + ], +}) diff --git a/packages/emotion/benchmark/configs/custom.ts b/packages/emotion/benchmark/configs/custom.ts new file mode 100644 index 0000000..4606523 --- /dev/null +++ b/packages/emotion/benchmark/configs/custom.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'rolldown' +import { resolve } from 'node:path' +import emotion from '@rolldown/plugin-emotion' + +export default defineConfig({ + input: resolve(import.meta.dirname, '../shared-app/src/index.tsx'), + output: { + dir: resolve(import.meta.dirname, '../dist/custom'), + }, + plugins: [emotion({ autoLabel: 'always', sourceMap: false })], +}) diff --git a/packages/emotion/benchmark/configs/swc.ts b/packages/emotion/benchmark/configs/swc.ts new file mode 100644 index 0000000..e05e1fc --- /dev/null +++ b/packages/emotion/benchmark/configs/swc.ts @@ -0,0 +1,39 @@ +import { defineConfig } from 'rolldown' +import { resolve } from 'node:path' +import { withFilter } from 'rolldown/filter' +import swc from '@rollup/plugin-swc' + +export default defineConfig({ + input: resolve(import.meta.dirname, '../shared-app/src/index.tsx'), + output: { + dir: resolve(import.meta.dirname, '../dist/swc'), + }, + plugins: [ + withFilter( + swc({ + swc: { + jsc: { + parser: { + syntax: 'typescript', + tsx: true, + }, + transform: { + react: { + runtime: 'preserve', + }, + }, + experimental: { + plugins: [['@swc/plugin-emotion', { autoLabel: 'always', sourceMap: false }]], + }, + }, + }, + }), + { + transform: { + id: { include: /\.[jt]sx?$/ }, + code: { include: '@emotion' }, + }, + }, + ), + ], +}) diff --git a/packages/emotion/benchmark/package.json b/packages/emotion/benchmark/package.json new file mode 100644 index 0000000..dde5f43 --- /dev/null +++ b/packages/emotion/benchmark/package.json @@ -0,0 +1,34 @@ +{ + "name": "@rolldown/benchmark-emotion", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "generate": "oxnode scripts/generate-app.ts", + "bench": "vitest bench --run", + "build:custom": "rolldown -c configs/custom.ts", + "build:babel": "rolldown -c configs/babel.ts", + "build:swc": "rolldown -c configs/swc.ts" + }, + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "react": "^19.2.4", + "react-dom": "^19.2.4" + }, + "devDependencies": { + "@babel/core": "^7.29.0", + "@emotion/babel-plugin": "^11.13.5", + "@oxc-node/cli": "^0.0.35", + "@rolldown/benchmark-utils": "workspace:*", + "@rolldown/plugin-babel": "file:../../babel", + "@rolldown/plugin-emotion": "workspace:*", + "@rollup/plugin-swc": "^0.4.0", + "@swc/core": "^1.15.18", + "@swc/plugin-emotion": "^14.7.0", + "@types/node": "^24.10.13", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "rolldown": "^1.0.0-rc.9" + } +} diff --git a/packages/emotion/benchmark/scripts/generate-app.ts b/packages/emotion/benchmark/scripts/generate-app.ts new file mode 100644 index 0000000..b4884c8 --- /dev/null +++ b/packages/emotion/benchmark/scripts/generate-app.ts @@ -0,0 +1,302 @@ +/** + * Component generator for Emotion benchmark. + * Generates ~100 React components with varied Emotion styled patterns. + * Uses seeded random (seed=42) for deterministic generation. + */ + +import { writeFileSync, mkdirSync, existsSync, rmSync } from 'node:fs' +import { join } from 'node:path' + +import { SeededRandom } from '@rolldown/benchmark-utils/seeded-random' + +const rng = new SeededRandom(42) + +type ComponentType = 'StyledButton' | 'StyledCard' | 'CssComponent' | 'AnimatedBox' | 'ThemedPanel' +const COMPONENT_TYPES: ComponentType[] = [ + 'StyledButton', + 'StyledCard', + 'CssComponent', + 'AnimatedBox', + 'ThemedPanel', +] + +const COLORS = [ + '#ff6b6b', + '#4ecdc4', + '#45b7d1', + '#96ceb4', + '#ffeaa7', + '#dfe6e9', + '#6c5ce7', + '#fd79a8', +] +const SIZES = ['4px', '8px', '12px', '16px', '24px', '32px'] +const FONTS = ["'Inter'", "'Roboto'", "'system-ui'", "'monospace'"] + +function generateStyledButton(index: number): string { + const bg = rng.pick(COLORS) + const radius = rng.pick(SIZES) + const padding = rng.pick(SIZES) + const hasHover = rng.next() > 0.3 + + return `import React from 'react' +import styled from '@emotion/styled' + +const Button = styled.button\` + background-color: ${bg}; + border: none; + border-radius: ${radius}; + padding: ${padding} \${parseInt('${padding}') * 2}px; + color: white; + font-size: 14px; + cursor: pointer; + transition: all 0.2s ease; +${ + hasHover + ? ` &:hover { + opacity: 0.8; + transform: scale(1.05); + }` + : '' +} +\` + +const Label = styled.span\` + font-weight: 600; + margin-right: 8px; +\` + +export function StyledButton${index}({ children, onClick }: { children: React.ReactNode; onClick?: () => void }) { + return ( + + ) +} +` +} + +function generateStyledCard(index: number): string { + const bg = rng.pick(COLORS) + const shadow = rng.next() > 0.5 + const border = rng.next() > 0.5 + + return `import React from 'react' +import styled from '@emotion/styled' + +const Card = styled.div\` + background: ${bg}20; + border-radius: 12px; + padding: 24px; +${shadow ? ' box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);' : ''} +${border ? ` border: 1px solid ${bg}40;` : ''} +\` + +const Title = styled.h3\` + margin: 0 0 12px; + font-size: 18px; + color: #333; +\` + +const Content = styled.p\` + margin: 0; + color: #666; + line-height: 1.6; +\` + +const Footer = styled.div\` + margin-top: 16px; + padding-top: 12px; + border-top: 1px solid #eee; + display: flex; + justify-content: space-between; +\` + +export function StyledCard${index}({ title, content }: { title: string; content: string }) { + return ( + + {title} + {content} +
+ Card ${index} +
+
+ ) +} +` +} + +function generateCssComponent(index: number): string { + const color = rng.pick(COLORS) + const font = rng.pick(FONTS) + + return `/** @jsxImportSource @emotion/react */ +import React from 'react' +import { css } from '@emotion/react' + +const containerStyle = css\` + padding: 16px; + font-family: ${font}; + color: ${color}; +\` + +const headingStyle = css\` + font-size: 24px; + font-weight: bold; + margin-bottom: 8px; +\` + +const textStyle = css\` + font-size: 14px; + line-height: 1.5; + opacity: 0.8; +\` + +export function CssComponent${index}({ heading, text }: { heading: string; text: string }) { + return ( +
+

{heading}

+

{text}

+
+ ) +} +` +} + +function generateAnimatedBox(index: number): string { + const color = rng.pick(COLORS) + const size = 40 + rng.nextInt(60) + + return `import React from 'react' +import styled from '@emotion/styled' +import { keyframes } from '@emotion/react' + +const pulse = keyframes\` + 0% { transform: scale(1); } + 50% { transform: scale(1.1); } + 100% { transform: scale(1); } +\` + +const rotate = keyframes\` + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +\` + +const Box = styled.div\` + width: ${size}px; + height: ${size}px; + background: ${color}; + border-radius: 8px; + animation: \${pulse} 2s ease-in-out infinite; +\` + +const Spinner = styled.div\` + width: 24px; + height: 24px; + border: 3px solid ${color}40; + border-top-color: ${color}; + border-radius: 50%; + animation: \${rotate} 1s linear infinite; +\` + +export function AnimatedBox${index}({ loading }: { loading?: boolean }) { + return loading ? : +} +` +} + +function generateThemedPanel(index: number): string { + const hasBorder = rng.next() > 0.5 + const hasIcon = rng.next() > 0.5 + + return `import React from 'react' +import styled from '@emotion/styled' + +interface PanelProps { + variant: 'primary' | 'secondary' | 'danger' +} + +const colorMap = { + primary: '#4ecdc4', + secondary: '#95a5a6', + danger: '#e74c3c', +} + +const Panel = styled.div\` + padding: 20px; + background: \${(props) => colorMap[props.variant]}15; + color: \${(props) => colorMap[props.variant]}; +${hasBorder ? ' border-left: 4px solid currentColor;' : ' border-radius: 8px;'} +\` + +const PanelTitle = styled.h4\` + margin: 0 0 8px; + font-size: 16px; +\` + +const PanelBody = styled.div\` + font-size: 14px; + opacity: 0.9; +\` +${ + hasIcon + ? ` +const Icon = styled.span\` + margin-right: 8px; + font-size: 18px; +\`` + : '' +} + +export function ThemedPanel${index}({ variant, title, children }: PanelProps & { title: string; children: React.ReactNode }) { + return ( + + ${hasIcon ? '*' : ''}{title} + {children} + + ) +} +` +} + +const GENERATORS: Record string> = { + StyledButton: generateStyledButton, + StyledCard: generateStyledCard, + CssComponent: generateCssComponent, + AnimatedBox: generateAnimatedBox, + ThemedPanel: generateThemedPanel, +} + +function main() { + const componentsDir = join(import.meta.dirname, '../shared-app/src/components') + if (existsSync(componentsDir)) rmSync(componentsDir, { recursive: true }) + mkdirSync(componentsDir, { recursive: true }) + + const components: Array<{ type: ComponentType; index: number }> = [] + const TOTAL = 100 + const perType = Math.floor(TOTAL / COMPONENT_TYPES.length) + const remainder = TOTAL % COMPONENT_TYPES.length + + for (let i = 0; i < COMPONENT_TYPES.length; i++) { + const type = COMPONENT_TYPES[i] + const count = perType + (i < remainder ? 1 : 0) + for (let j = 0; j < count; j++) { + const index = components.length + 1 + components.push({ type, index }) + writeFileSync(join(componentsDir, `${type}${index}.tsx`), GENERATORS[type](index)) + } + } + + const exports = components + .map(({ type, index }) => `export { ${type}${index} } from './${type}${index}.js'`) + .join('\n') + writeFileSync(join(componentsDir, 'index.ts'), exports + '\n') + + console.log(`Generated ${components.length} components in ${componentsDir}`) + for (const type of COMPONENT_TYPES) { + console.log(` ${type}: ${components.filter((c) => c.type === type).length}`) + } +} + +main() diff --git a/packages/emotion/benchmark/shared-app/src/App.tsx b/packages/emotion/benchmark/shared-app/src/App.tsx new file mode 100644 index 0000000..54c2397 --- /dev/null +++ b/packages/emotion/benchmark/shared-app/src/App.tsx @@ -0,0 +1,31 @@ +import React from 'react' +import * as Components from './components/index.js' + +type AnyComponent = React.ComponentType> +// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion +const componentEntries = Object.entries(Components) as unknown as [string, AnyComponent][] + +export function App() { + return ( +
+

Emotion Benchmark App

+

This app contains {componentEntries.length} components for benchmarking.

+
+ {componentEntries.map(([name, Component]) => ( +
+ {}} + title="Title" + content="Content text" + heading="Heading" + text="Body text" + loading={false} + variant="primary" + /> +
+ ))} +
+
+ ) +} diff --git a/packages/emotion/benchmark/shared-app/src/index.tsx b/packages/emotion/benchmark/shared-app/src/index.tsx new file mode 100644 index 0000000..07c809b --- /dev/null +++ b/packages/emotion/benchmark/shared-app/src/index.tsx @@ -0,0 +1,13 @@ +import React from 'react' +import { createRoot } from 'react-dom/client' +import { App } from './App.js' + +const container = document.getElementById('root') +if (container) { + const root = createRoot(container) + root.render( + + + , + ) +} diff --git a/packages/emotion/benchmark/vitest.config.ts b/packages/emotion/benchmark/vitest.config.ts new file mode 100644 index 0000000..f9cf58d --- /dev/null +++ b/packages/emotion/benchmark/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + name: 'benchmark-emotion', + }, +}) diff --git a/packages/emotion/package.json b/packages/emotion/package.json new file mode 100644 index 0000000..d253756 --- /dev/null +++ b/packages/emotion/package.json @@ -0,0 +1,63 @@ +{ + "name": "@rolldown/plugin-emotion", + "version": "0.1.0", + "description": "Rolldown plugin for Emotion CSS-in-JS", + "keywords": [ + "css-in-js", + "emotion", + "plugin", + "rolldown", + "rolldown-plugin" + ], + "homepage": "https://github.com/rolldown/plugins/tree/main/packages/emotion#readme", + "bugs": { + "url": "https://github.com/rolldown/plugins/issues" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/rolldown/plugins.git", + "directory": "packages/emotion" + }, + "files": [ + "dist" + ], + "type": "module", + "exports": "./dist/index.mjs", + "scripts": { + "dev": "tsdown --watch", + "build": "tsdown", + "test": "vitest --project emotion", + "prepublishOnly": "pnpm run build" + }, + "dependencies": { + "@emotion/hash": "^0.9.2", + "@jridgewell/gen-mapping": "^0.3.13", + "rolldown-string": "^0.3.0" + }, + "devDependencies": { + "@rolldown/oxc-unshadowed-visitor": "workspace:*", + "rolldown": "^1.0.0-rc.9", + "tinyglobby": "^0.2.15", + "vite": "^8.0.0" + }, + "peerDependencies": { + "rolldown": "^1.0.0-rc.9", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + }, + "engines": { + "node": ">=22.12.0 || ^24.0.0" + }, + "compatiblePackages": { + "schemaVersion": 1, + "rollup": { + "type": "incompatible", + "reason": "Uses Rolldown-specific APIs" + } + } +} diff --git a/packages/emotion/src/common.ts b/packages/emotion/src/common.ts new file mode 100644 index 0000000..7aa89e1 --- /dev/null +++ b/packages/emotion/src/common.ts @@ -0,0 +1,65 @@ +export const ExprKind = { + Css: 0, + Styled: 1, + GlobalJSX: 2, +} as const +export type ExprKind = (typeof ExprKind)[keyof typeof ExprKind] + +export function regexEscape(str: string): string { + return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +} + +/** + * Unescape template literal raw text to get the actual string value. + * Template literal raw text preserves escape sequences as written in source. + * We need to convert them to their actual characters. + */ +export function unescapeTemplateRaw(raw: string): string { + return raw + .replace(/\\`/g, '`') + .replace(/\\\$/g, '$') + .replace(/\\b/g, '\b') + .replace(/\\f/g, '\f') + .replace(/\\n/g, '\n') + .replace(/\\r/g, '\r') + .replace(/\\t/g, '\t') + .replace(/\\v/g, '\v') + .replace(/\\\\/g, '\\') +} + +/** + * Escape a string for use inside a JS double-quoted string literal. + */ +export function escapeJSString(str: string): string { + return ( + str + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + .replace(/\t/g, '\\t') + .replace(/\f/g, '\\f') + // oxlint-disable-next-line no-control-regex + .replace(/\u000b/g, '\\v') + .replace(/[\b]/g, '\\b') + ) +} + +const SPACE_REGEX = /\s/ + +export function checkTrailingCommaExistence(str: string, endIndex: number): boolean { + for (let i = endIndex - 1; i >= 0; i--) { + const char = str[i] + if (char === ',') { + return true + } + if (!SPACE_REGEX.test(char)) { + break + } + } + return false +} + +export function maybeComma(needed: boolean): string { + return needed ? ', ' : '' +} diff --git a/packages/emotion/src/css-minify.test.ts b/packages/emotion/src/css-minify.test.ts new file mode 100644 index 0000000..7690eb1 --- /dev/null +++ b/packages/emotion/src/css-minify.test.ts @@ -0,0 +1,37 @@ +import { expect, test } from 'vitest' +import { minifyCSSString } from './css-minify' + +const tests: Record< + string, + { input: { css: string; isFirst?: boolean; isLast?: boolean }; output: string } +> = { + 'should not trim end space in first item': { + input: { css: '\nbox-shadow: inset 0px 0px 0px ', isFirst: true, isLast: false }, + output: 'box-shadow:inset 0px 0px 0px ', + }, + 'should minify single line comment correctly': { + input: { css: '//comment;\ncolor: red;//comment\nbackground-image:url(http://dummy-url)' }, + output: 'color:red;background-image:url(http://dummy-url)', + }, + 'should remove comments': { + input: { + css: 'color: red;/*comment\ncomments*/background-image:url(http://dummy-url).foo{/*comments\n*/\n}', + }, + output: 'color:red;background-image:url(http://dummy-url).foo{}', + }, + 'issue 258 should preserve url starting with two slashes 1': { + input: { css: "background-image: url('//domain.com/image.png');" }, + output: "background-image:url('//domain.com/image.png');", + }, + 'issue 258 should preserve url starting with two slashes 2': { + input: { css: 'background-image: url("//domain.com/image.png");' }, + output: 'background-image:url("//domain.com/image.png");', + }, +} + +for (const [name, { input, output }] of Object.entries(tests)) { + test(name, () => { + const result = minifyCSSString(input.css, input.isFirst ?? true, input.isLast ?? false) + expect(result).toBe(output) + }) +} diff --git a/packages/emotion/src/css-minify.ts b/packages/emotion/src/css-minify.ts new file mode 100644 index 0000000..39c2022 --- /dev/null +++ b/packages/emotion/src/css-minify.ts @@ -0,0 +1,38 @@ +// Remove multi-line comments: /* ... */ +const MULTI_LINE_COMMENT = /\/\*[\s\S]*?\*\//g + +// Remove single-line comments: // ... +// Preserves URLs like url('//...') and url("//...") +// Group $1 captures the character before // (if any) +const SINGLE_LINE_COMMENT = /(^|[^:'^"]|\s)\/\/.*$/gm + +// Collapse whitespace around CSS punctuation: : ; , { } +const SPACE_AROUND_COLON = /\s*([:;,{}])\s*/g + +export function minifyCSSString(input: string, isFirst: boolean, isLast: boolean): string { + // Step 1: Remove multi-line comments + let result = input.replace(MULTI_LINE_COMMENT, '') + + // Step 2: Remove single-line comments (preserving preceding char) + result = result.replace(SINGLE_LINE_COMMENT, '$1') + + // Step 3: Trim leading/trailing whitespace + // First item: trim both spaces and newlines from start + // Middle items: only trim newlines from start/end + // Last item: trim both spaces and newlines from end + if (isFirst) { + result = result.replace(/^[\s]+/, '') + } else { + result = result.replace(/^\n+/, '') + } + if (isLast) { + result = result.replace(/[\s]+$/, '') + } else { + result = result.replace(/\n+$/, '') + } + + // Step 4: Collapse whitespace around CSS punctuation + result = result.replace(SPACE_AROUND_COLON, '$1') + + return result +} diff --git a/packages/emotion/src/import-map.ts b/packages/emotion/src/import-map.ts new file mode 100644 index 0000000..3ff3abf --- /dev/null +++ b/packages/emotion/src/import-map.ts @@ -0,0 +1,103 @@ +import type { ESTree } from 'rolldown/utils' +import { ExprKind } from './common' +import type { ImportMapConfig } from './types' + +export type EmotionImportMap = Record< + /* moduleName */ string, + Record +> + +const EMOTION_OFFICIAL_LIBRARIES: EmotionImportMap = { + '@emotion/css': { css: ExprKind.Css, default: ExprKind.Css }, + '@emotion/styled': { default: ExprKind.Styled }, + '@emotion/react': { css: ExprKind.Css, keyframes: ExprKind.Css, Global: ExprKind.GlobalJSX }, + '@emotion/primitives': { css: ExprKind.Css, default: ExprKind.Styled }, + '@emotion/native': { css: ExprKind.Css, default: ExprKind.Styled }, +} + +export function expandImportMap( + importMap: Record | undefined, +): EmotionImportMap { + const configs: EmotionImportMap = JSON.parse(JSON.stringify(EMOTION_OFFICIAL_LIBRARIES)) + if (!importMap) return configs + + for (const [importSource, exports] of Object.entries(importMap)) { + for (const [localExportName, entry] of Object.entries(exports)) { + const [packageName, exportName] = entry.canonicalImport + if (packageName === '@emotion/react' && exportName === 'jsx') continue + + const canonicalConfig = EMOTION_OFFICIAL_LIBRARIES[packageName] + if (canonicalConfig === undefined) { + throw new Error( + `Import map entry for "${importSource}" references unknown package "${packageName}". ` + + `Must be one of: ${Object.keys(EMOTION_OFFICIAL_LIBRARIES).join(', ')}`, + ) + } + const kind = canonicalConfig[exportName] + if (kind === undefined) { + throw new Error( + `Import map entry for "${importSource}" references unknown export "${exportName}" in package "${packageName}". ` + + `Must be one of: ${Object.keys(canonicalConfig).join(', ')}`, + ) + } + configs[importSource] ??= {} + configs[importSource][localExportName] = kind + } + } + return configs +} + +export type PackageMeta = + | { type: 'named'; kind: ExprKind } + | { type: 'namespace'; config: Record } + +export interface ImportMap { + addFromImportDecl(importDecl: ESTree.ImportDeclaration): void + get(importedName: string): PackageMeta | undefined + getTrackedNames(): string[] + isEmpty(): boolean +} + +export function createImportMap(registeredImports: EmotionImportMap): ImportMap { + const importPackages = new Map() + + return { + addFromImportDecl(importDecl) { + if (importDecl.importKind === 'type') return + + const src = importDecl.source.value + const config = registeredImports[src] + if (!config) return + + for (const spec of importDecl.specifiers) { + if (spec.type === 'ImportSpecifier') { + const importedName = + spec.imported.type === 'Identifier' ? spec.imported.name : spec.imported.value + const kind = config[importedName] + if (kind !== undefined) { + importPackages.set(spec.local.name, { type: 'named', kind }) + } + } else if (spec.type === 'ImportDefaultSpecifier') { + const kind = config.default + if (kind !== undefined) { + importPackages.set(spec.local.name, { type: 'named', kind }) + } + } else if (spec.type === 'ImportNamespaceSpecifier') { + importPackages.set(spec.local.name, { + type: 'namespace', + config, + }) + } + } + }, + get(importedName) { + return importPackages.get(importedName) + }, + getTrackedNames() { + return [...importPackages.keys()] + }, + isEmpty() { + return importPackages.size === 0 + }, + } +} diff --git a/packages/emotion/src/index.ts b/packages/emotion/src/index.ts new file mode 100644 index 0000000..20afa96 --- /dev/null +++ b/packages/emotion/src/index.ts @@ -0,0 +1,702 @@ +import { withMagicString } from 'rolldown-string' +import type { Plugin } from 'rolldown' +import type { ESTree } from 'rolldown/utils' +import { ScopedVisitor } from '@rolldown/oxc-unshadowed-visitor' +import type { EmotionPluginOptions } from './types.js' +import { minifyCSSString } from './css-minify.js' +import { createSourceMap, getPos } from './source-map.js' +import { + ExprKind, + regexEscape, + unescapeTemplateRaw, + escapeJSString, + checkTrailingCommaExistence, + maybeComma, +} from './common.js' +import { createImportMap, expandImportMap } from './import-map.js' +import { createLabelWithInfo } from './label.js' +import path from 'node:path' +import hashString from '@emotion/hash' + +export type { EmotionPluginOptions } from './types.js' + +interface RecordData { + nodeStart: number + nodeEnd: number + isFullReplace: boolean + apply: (getTarget: () => string) => void +} + +export default function emotionPlugin(options: EmotionPluginOptions = {}): Plugin { + const sourceMapEnabled = options.sourceMap + const autoLabel = options.autoLabel ?? 'dev-only' + const labelFormat = options.labelFormat ?? '[local]' + const registeredImports = expandImportMap(options.importMap) + + let isDev = false + + return { + name: 'rolldown-plugin-emotion', + // @ts-expect-error Vite-specific property + enforce: 'pre', + + // @ts-expect-error Vite-specific hook + configResolved(config) { + isDev = !config.isProduction + }, + + outputOptions() { + if ('viteVersion' in this.meta) return + isDev = process.env.NODE_ENV === 'development' + }, + + transform: { + filter: { + id: /\.[jt]sx?$/, + code: new RegExp(Object.keys(registeredImports).map(regexEscape).join('|')), + }, + + handler: withMagicString(function (this, s, id, meta) { + const lang = id.endsWith('.tsx') + ? 'tsx' + : id.endsWith('.ts') + ? 'ts' + : id.endsWith('.jsx') + ? 'jsx' + : 'js' + const program = meta?.ast ?? this.parse(s.original, { lang }) + + const sourceContent = s.original + const srcFileHash = hashString(sourceContent) + const fileStem = path.basename(id, path.extname(id)) + const dirName = path.basename(path.dirname(id)) + + let targetCount = 0 + const importMap = createImportMap(registeredImports) + + function shouldAddLabel(): boolean { + switch (autoLabel) { + case 'always': + return true + case 'never': + return false + case 'dev-only': + return isDev + default: + autoLabel satisfies never + return false + } + } + + function createLabel(context: string | null, withPrefix: boolean): string { + return createLabelWithInfo(labelFormat, context, fileStem, dirName ?? '', withPrefix) + } + + function makeSourceMap(offset: number): string | null { + if (!(sourceMapEnabled ?? isDev)) return null + const pos = getPos(sourceContent, offset) + return createSourceMap(sourceContent, id, pos) + } + + function buildTaggedTemplateArgs( + quasi: ESTree.TemplateLiteral, + inJsx: boolean, + labelContext: string | null, + sourceMapOffset: number, + withLabelPrefix: boolean, + includeLabel: boolean = true, + ): string { + const quasis = quasi.quasis + const expressions = quasi.expressions + const argsLen = quasis.length + expressions.length + const parts: string[] = [] + + for (let index = 0; index < argsLen; index++) { + const i = Math.floor(index / 2) + if (index % 2 === 0) { + // Template quasi (static text) + const raw = quasis[i].value.raw + const unescaped = unescapeTemplateRaw(raw) + const minified = minifyCSSString(unescaped, index === 0, index === argsLen - 1) + // Compress one more spaces into one space + if (minified.replaceAll(' ', '') === '') { + if (index !== 0 && index !== argsLen - 1) { + parts.push('" "') + } + } else { + parts.push(`"${escapeJSString(minified)}"`) + } + } else { + // Expression (interpolation) + const expr = expressions[i] + parts.push(s.slice(expr.start, expr.end)) + } + } + + // Add label and source map (unless in JSX element context) + if (!inJsx) { + if (includeLabel && shouldAddLabel()) { + const label = createLabel(labelContext, withLabelPrefix) + parts.push(`"${escapeJSString(label)}"`) + } + const sm = makeSourceMap(sourceMapOffset) + if (sm) { + parts.push(`"${escapeJSString(sm)}"`) + } + } + + return parts.join(', ') + } + + for (const node of program.body) { + if (node.type === 'ImportDeclaration') { + importMap.addFromImportDecl(node) + } + } + const trackedNames = importMap.getTrackedNames() + if (trackedNames.length === 0) return + + const labelContextStack: (string | null)[] = [null] + let inJsx = false + const sv = new ScopedVisitor({ + trackedNames, + visitor: { + VariableDeclarator(node) { + let ctx = null + if (node.id.type === 'Identifier') { + ctx = node.id.name + } + // Named function expression overrides variable name + if (node.init?.type === 'FunctionExpression' && node.init.id) { + ctx = node.init.id.name + } + labelContextStack.push(ctx) + }, + 'VariableDeclarator:exit'() { + labelContextStack.pop() + }, + + FunctionDeclaration(node) { + // Function declarations always have an id + labelContextStack.push(node.id!.name) + }, + 'FunctionDeclaration:exit'() { + labelContextStack.pop() + }, + + Property(node) { + let ctx = null + if (!node.computed) { + if (node.key.type === 'Identifier') ctx = node.key.name + else if (node.key.type === 'Literal' && typeof node.key.value === 'string') + ctx = node.key.value + } + labelContextStack.push(ctx) + }, + 'Property:exit'() { + labelContextStack.pop() + }, + + ClassDeclaration(node) { + const name = node.id?.name ?? labelContextStack[labelContextStack.length - 1] + labelContextStack.push(name) + }, + 'ClassDeclaration:exit'() { + labelContextStack.pop() + }, + + PropertyDefinition(node) { + let ctx = labelContextStack[labelContextStack.length - 1] + if (node.key.type === 'Identifier' && !node.computed) { + ctx = node.key.name + } + labelContextStack.push(ctx) + }, + 'PropertyDefinition:exit'() { + labelContextStack.pop() + }, + + JSXElement(node, ctx) { + const opening = node.openingElement + let isGlobal = false + let smOffset = node.start + let recordName: string | null = null + + // Check if this is a component + if (opening.name.type === 'JSXIdentifier') { + const meta = importMap.get(opening.name.name) + if (meta?.type === 'named' && meta.kind === ExprKind.GlobalJSX) { + isGlobal = true + smOffset = opening.name.start + recordName = opening.name.name + } + } + + // Check namespace: + if (!isGlobal && opening.name.type === 'JSXMemberExpression') { + const obj = opening.name.object + const prop = opening.name.property + if (obj.type === 'JSXIdentifier' && prop.type === 'JSXIdentifier') { + const meta = importMap.get(obj.name) + if (meta?.type === 'namespace' && meta.config[prop.name] === ExprKind.GlobalJSX) { + isGlobal = true + smOffset = obj.start + recordName = obj.name + } + } + } + + if (isGlobal && recordName) { + const stylesAttr = opening.attributes.find( + (a): a is ESTree.JSXAttribute => + a.type === 'JSXAttribute' && + a.name.type === 'JSXIdentifier' && + a.name.name === 'styles', + ) + if (stylesAttr?.value) { + inJsx = true + const attrValue = stylesAttr.value + let exprStart: number + let exprEnd: number + if (attrValue.type === 'JSXExpressionContainer') { + exprStart = attrValue.expression.start + exprEnd = attrValue.expression.end + } else { + exprStart = attrValue.start + exprEnd = attrValue.end + } + + const capturedSmOffset = smOffset + ctx.record({ + name: recordName, + node, + data: { + nodeStart: node.start, + nodeEnd: node.end, + isFullReplace: false, + apply: () => { + const sm = makeSourceMap(capturedSmOffset) + if (sm) { + s.appendLeft(exprStart, '[') + s.appendRight(exprEnd, `, "${escapeJSString(sm)}"]`) + } + }, + }, + }) + } + } + }, + 'JSXElement:exit'() { + inJsx = false + }, + + TaggedTemplateExpression(node, ctx) { + const tag = node.tag + const quasi = node.quasi + const labelContext = labelContextStack[labelContextStack.length - 1] + + // --- css`...` / keyframes`...` --- + if (tag.type === 'Identifier') { + const meta = importMap.get(tag.name) + if (meta?.type === 'named' && meta.kind === ExprKind.Css) { + let wasInJsx = inJsx + ctx.record({ + name: tag.name, + node, + data: { + nodeStart: node.start, + nodeEnd: node.end, + isFullReplace: true, + apply: () => { + const args = buildTaggedTemplateArgs( + quasi, + wasInJsx, + labelContext, + node.start, + false, + ) + const tagText = s.slice(tag.start, tag.end) + s.update(node.start, node.end, `${tagText}(${args})`) + if (!wasInJsx) { + s.appendLeft(node.start, '/* @__PURE__ */ ') + } + }, + }, + }) + return + } + } + + // --- styled.div`...` / namespace.css`...` --- + if ( + tag.type === 'MemberExpression' && + !tag.computed && + tag.object.type === 'Identifier' + ) { + const meta = importMap.get(tag.object.name) + if (meta?.type === 'named' && meta.kind === ExprKind.Styled) { + ctx.record({ + name: tag.object.name, + node, + data: { + nodeStart: node.start, + nodeEnd: node.end, + isFullReplace: true, + apply: (getTarget) => { + let labelObj = `target: "${getTarget()}"` + if (shouldAddLabel()) { + const label = createLabel(labelContext, false) + labelObj += `, label: "${escapeJSString(label)}"` + } + + const styledArgs = buildTaggedTemplateArgs( + quasi, + false, + labelContext, + node.start, + false, + false, + ) + const styledName = s.slice(tag.object.start, tag.object.end) + const propName = tag.property.name + s.update( + node.start, + node.end, + `${styledName}("${escapeJSString(propName)}", {\n${labelObj}\n})(${styledArgs})`, + ) + s.appendLeft(node.start, '/* @__PURE__ */ ') + }, + }, + }) + return + } + + // --- namespace.css`...` --- + if (meta?.type === 'namespace') { + const propName = tag.property.type === 'Identifier' ? tag.property.name : null + if (!propName || meta.config[propName] !== ExprKind.Css) return + + let wasInJsx = inJsx + ctx.record({ + name: tag.object.name, + node, + data: { + nodeStart: node.start, + nodeEnd: node.end, + isFullReplace: true, + apply: () => { + const tagText = s.slice(tag.start, tag.end) + const args = buildTaggedTemplateArgs( + quasi, + wasInJsx, + labelContext, + node.start, + true, + ) + s.update(node.start, node.end, `${tagText}(${args})`) + s.appendLeft(node.start, '/* @__PURE__ */ ') + }, + }, + }) + return + } + } + + // --- styled(Component)`...` --- + if (tag.type === 'CallExpression' && tag.callee.type === 'Identifier') { + const meta = importMap.get(tag.callee.name) + if (meta?.type === 'named' && meta.kind === ExprKind.Styled) { + ctx.record({ + name: tag.callee.name, + node, + data: { + nodeStart: node.start, + nodeEnd: node.end, + isFullReplace: true, + apply: (getTarget) => { + const styledName = s.slice(tag.callee.start, tag.callee.end) + const target = getTarget() + let labelObj = `target: "${target}"` + if (shouldAddLabel()) { + const label = createLabel(labelContext, false) + labelObj += `, label: "${escapeJSString(label)}"` + } + + // Extract existing args from styled(Component, ...) + const existingArgs = tag.arguments + const firstArgText = + existingArgs.length > 0 + ? s.slice(existingArgs[0].start, existingArgs[0].end) + : '' + + let innerCallText: string + if (existingArgs.length <= 1) { + // styled(Component) → styled(Component, { target, label }) + innerCallText = `${styledName}(${firstArgText}, {\n${labelObj}\n})` + } else { + // styled(Component, options) → need to merge options + const secondArg = existingArgs[1] + if (secondArg.type === 'ObjectExpression') { + // Merge target/label into existing object + const objText = s.slice(secondArg.start + 1, secondArg.end - 1) + const isEmpty = objText.trim() === '' + const hasTrailingComma = !isEmpty && objText.trimEnd().endsWith(',') + const prefix = isEmpty + ? '' + : `${objText}${maybeComma(!hasTrailingComma)} ` + innerCallText = `${styledName}(${firstArgText}, { ${prefix}${labelObj} })` + } else { + // Wrap with spread + const secondArgText = s.slice(secondArg.start, secondArg.end) + innerCallText = `${styledName}(${firstArgText}, {\n${labelObj},\n\t...${secondArgText}\n})` + } + } + + const styledArgs = buildTaggedTemplateArgs( + quasi, + false, + labelContext, + node.start, + false, + false, + ) + s.update(node.start, node.end, `${innerCallText}(${styledArgs})`) + s.appendLeft(node.start, '/* @__PURE__ */ ') + }, + }, + }) + return + } + } + }, + + CallExpression(node, ctx) { + const callee = node.callee + const args = node.arguments + const labelContext = labelContextStack[labelContextStack.length - 1] + + // --- css({...}) --- + if (callee.type === 'Identifier') { + const meta = importMap.get(callee.name) + if ( + meta?.type === 'named' && + meta.kind === ExprKind.Css && + args.length > 0 && + !inJsx + ) { + ctx.record({ + name: callee.name, + node, + data: { + nodeStart: node.start, + nodeEnd: node.end, + isFullReplace: false, + apply: () => { + s.appendLeft(node.start, '/* @__PURE__ */ ') + let hasTrailingComma = checkTrailingCommaExistence(s.original, node.end - 1) + if (shouldAddLabel()) { + const label = createLabel(labelContext, true) + s.appendRight( + node.end - 1, + `${maybeComma(!hasTrailingComma)}"${escapeJSString(label)}"`, + ) + hasTrailingComma = false + } + const sm = makeSourceMap(node.start) + if (sm) { + s.appendRight( + node.end - 1, + `${maybeComma(!hasTrailingComma)}"${escapeJSString(sm)}"`, + ) + } + }, + }, + }) + return + } + } + + // --- styled('div')({...}) --- + if (callee.type === 'CallExpression') { + const innerCallee = callee.callee + if (innerCallee.type === 'Identifier') { + const meta = importMap.get(innerCallee.name) + if ( + meta?.type === 'named' && + meta.kind === ExprKind.Styled && + callee.arguments.length > 0 + ) { + ctx.record({ + name: innerCallee.name, + node, + data: { + nodeStart: node.start, + nodeEnd: node.end, + isFullReplace: false, + apply: (getTarget) => { + s.appendLeft(node.start, '/* @__PURE__ */ ') + let labelObj = `target: "${getTarget()}"` + if (shouldAddLabel()) { + const label = createLabel(labelContext, false) + labelObj += `, label: "${escapeJSString(label)}"` + } + // Add { target, label } as second arg to inner call + if (callee.arguments.length === 1) { + // Insert before inner call's closing ) + s.appendLeft(callee.end - 1, `, {\n\t${labelObj}\n}`) + } else if (callee.arguments.length >= 2) { + const secondArg = callee.arguments[1] + if (secondArg.type === 'ObjectExpression') { + // Insert before the closing } of the object + const isEmpty = secondArg.properties.length === 0 + if (isEmpty) { + s.appendLeft(secondArg.end - 1, ` ${labelObj} `) + } else { + const hasTrailingComma = checkTrailingCommaExistence( + s.original, + secondArg.end - 1, + ) + s.appendLeft( + secondArg.end - 1, + `${maybeComma(!hasTrailingComma)} ${labelObj}`, + ) + } + } else { + // Wrap with spread + const secondArgText = s.slice(secondArg.start, secondArg.end) + s.update( + secondArg.start, + secondArg.end, + `{ ${labelObj}, ...${secondArgText} }`, + ) + } + } + const sm = makeSourceMap(node.start) + if (sm) { + const hasTrailingComma = checkTrailingCommaExistence( + s.original, + node.end - 1, + ) + s.appendLeft( + node.end - 1, + `${maybeComma(!hasTrailingComma)}"${escapeJSString(sm)}"`, + ) + } + }, + }, + }) + return + } + } + } + + // --- styled.div({...}) / namespace.css({...}) --- + if ( + callee.type === 'MemberExpression' && + !callee.computed && + callee.object.type === 'Identifier' + ) { + const meta = importMap.get(callee.object.name) + if (meta?.type === 'named' && meta.kind === ExprKind.Styled) { + let wasInJsx = inJsx + ctx.record({ + name: callee.object.name, + node, + data: { + nodeStart: node.start, + nodeEnd: node.end, + isFullReplace: false, + apply: (getTarget) => { + let labelObj = '' + if (!wasInJsx) { + labelObj += `target: "${getTarget()}"` + s.appendLeft(node.start, '/* @__PURE__ */ ') + if (shouldAddLabel()) { + const label = createLabel(labelContext, false) + labelObj += `, label: "${escapeJSString(label)}"` + } + } + const styledName = s.slice(callee.object.start, callee.object.end) + const propName = callee.property.name + // Replace callee styled.div with styled("div", { target, label }) + s.update( + callee.start, + callee.end, + `${styledName}("${escapeJSString(propName)}"${labelObj ? `, { ${labelObj} }` : ''})`, + ) + if (!wasInJsx) { + const sm = makeSourceMap(node.start) + if (sm) { + const hasTrailingComma = checkTrailingCommaExistence( + s.original, + node.end - 1, + ) + s.appendLeft( + node.end - 1, + `${maybeComma(!hasTrailingComma)}"${escapeJSString(sm)}"`, + ) + } + } + }, + }, + }) + return + } + + // --- namespace.css({...}) --- + if (meta?.type === 'namespace') { + const propName = + callee.property.type === 'Identifier' ? callee.property.name : null + if (!propName || meta.config[propName] !== ExprKind.Css) return + + ctx.record({ + name: callee.object.name, + node, + data: { + nodeStart: node.start, + nodeEnd: node.end, + isFullReplace: false, + apply: () => { + s.appendLeft(node.start, '/* @__PURE__ */ ') + let hasTrailingComma = checkTrailingCommaExistence(s.original, node.end - 1) + if (shouldAddLabel()) { + const label = createLabel(labelContext, true) + s.appendRight( + node.end - 1, + `${maybeComma(!hasTrailingComma)}"${escapeJSString(label)}"`, + ) + hasTrailingComma = false + } + const sm = makeSourceMap(node.start) + if (sm) { + s.appendRight( + node.end - 1, + `${maybeComma(!hasTrailingComma)}"${escapeJSString(sm)}"`, + ) + } + }, + }, + }) + return + } + } + }, + }, + }) + + const records = sv.walk(program) + if (records.length === 0) return + + const consumedRanges: [number, number][] = [] + for (const record of records) { + const { nodeStart, nodeEnd, isFullReplace, apply } = record.data + // Skip records fully contained within an already-consumed range + // (e.g., inner tagged template inside an outer one that was replaced) + if (consumedRanges.some(([cs, ce]) => nodeStart >= cs && nodeEnd <= ce)) continue + apply(() => `e${srcFileHash}${targetCount++}`) + if (isFullReplace) consumedRanges.push([nodeStart, nodeEnd]) + } + }), + }, + } +} diff --git a/packages/emotion/src/label.ts b/packages/emotion/src/label.ts new file mode 100644 index 0000000..1da25c0 --- /dev/null +++ b/packages/emotion/src/label.ts @@ -0,0 +1,32 @@ +const INVALID_LABEL_SPACES = /\s+/g + +const INVALID_CSS_CLASS_NAME_CHARS = /[!"#$%&'()*+,./:;<=>?@[\\\]^`|}~{]/g + +export function sanitizeLabelPart(part: string): string { + // Existing @emotion/babel-plugin behaviour is to replace all spaces + // with a single hyphen + return part.replace(INVALID_LABEL_SPACES, '-').replace(INVALID_CSS_CLASS_NAME_CHARS, '-') +} + +export function createLabelWithInfo( + labelFormat: string, + context: string | null, + fileStem: string, + dirName: string, + withPrefix: boolean, +): string { + // Existing @emotion/babel-plugin behaviour is to + // not provide a label if there is no available identifier + if (context == null) return '' + + const prefix = withPrefix ? 'label:' : '' + let label = `${prefix}${labelFormat}` + label = label.replace('[local]', sanitizeLabelPart(context)) + if (fileStem) { + label = label.replace('[filename]', sanitizeLabelPart(fileStem)) + } + if (dirName) { + label = label.replace('[dirname]', sanitizeLabelPart(dirName)) + } + return label +} diff --git a/packages/emotion/src/source-map.ts b/packages/emotion/src/source-map.ts new file mode 100644 index 0000000..b37d2df --- /dev/null +++ b/packages/emotion/src/source-map.ts @@ -0,0 +1,40 @@ +import { GenMapping, addSegment, setSourceContent, toEncodedMap } from '@jridgewell/gen-mapping' + +/** + * Create an inline source map comment string. + * + * @param sourceContent - The full original source code + * @param filename - The source filename (with extension stripped) + * @param pos - The 0-indexed line and column number of the expression in the original source + */ +export function createSourceMap( + sourceContent: string, + filename: string, + pos: { line: number; column: number }, +): string { + const map = new GenMapping({ file: filename }) + setSourceContent(map, filename, sourceContent) + addSegment(map, 0, 0, filename, pos.line, pos.column) + + const encoded = btoa(JSON.stringify(toEncodedMap(map))) + return `/*# sourceMappingURL=data:application/json;charset=utf-8;base64,${encoded} */` +} + +const LF = '\n'.charCodeAt(0) + +/** + * Get the 0-indexed line number and column for a character offset in the source. + */ +export function getPos(source: string, offset: number): { line: number; column: number } { + let line = 0 + let column = 0 + for (let i = 0; i < offset && i < source.length; i++) { + if (source.charCodeAt(i) === LF) { + line++ + column = 0 + } else { + column++ + } + } + return { line, column } +} diff --git a/packages/emotion/src/types.ts b/packages/emotion/src/types.ts new file mode 100644 index 0000000..33c521f --- /dev/null +++ b/packages/emotion/src/types.ts @@ -0,0 +1,76 @@ +/** + * Configuration for custom emotion-like packages + * Maps export names to their canonical emotion equivalents + */ +export interface ImportMapEntry { + /** + * The canonical emotion import this maps to + * @example ["@emotion/styled", "default"] + */ + canonicalImport: [packageName: string, exportName: string] + + /** + * The styled base import for this package + * @example ["package/base", "something"] + */ + styledBaseImport?: [packageName: string, exportName: string] +} + +export type ImportMapConfig = Record + +export interface EmotionPluginOptions { + /** + * Generate source maps for emotion CSS. + * @default true for development, otherwise false + */ + sourceMap?: boolean + + /** + * When to add debug labels to styled components. + * - 'never': Never add labels + * - 'dev-only': Only add labels in development mode (default) + * - 'always': Always add labels + * @default 'dev-only' + */ + autoLabel?: 'never' | 'dev-only' | 'always' + + /** + * Label format template. + * + * Defines the format of the generated debug labels. + * This option is only relevant if `autoLabel` is not set to 'never'. + * + * Supports placeholders: + * - [local]: The variable name that the result of `css` or `styled` call is assigned to + * - [filename]: The file name (without extension) that the `css` or `styled` call is in + * - [dirname]: The directory name of the file that the `css` or `styled` call is in + * + * @default "[local]" + * @example "[dirname]--[filename]--[local]" + */ + labelFormat?: string + + /** + * Custom import mappings for non-standard emotion packages. + * Maps package names to their export configurations. + * + * @example + * If you have a custom library "my-emotion-lib" that re-exports + * the default export of `@emotion/styled` as `myStyled` and + * the `css` export of `@emotion/react` as `myCss`, + * then you can configure it like this: + * ``` + * { + * "my-emotion-lib": { + * "myStyled": { + * canonicalImport: ["@emotion/styled", "default"] + * }, + * "myCss": { + * canonicalImport: ["@emotion/react", "css"] + * } + * } + * } + * ``` + */ + importMap?: Record +} diff --git a/packages/emotion/tests/fixtures-labels/basic/input.ts b/packages/emotion/tests/fixtures-labels/basic/input.ts new file mode 100644 index 0000000..0beca57 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/basic/input.ts @@ -0,0 +1,9 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/basic.js + +import { css } from '@emotion/react' + +export function doThing() { + return css` + display: flex; + ` +} diff --git a/packages/emotion/tests/fixtures-labels/basic/output.js b/packages/emotion/tests/fixtures-labels/basic/output.js new file mode 100644 index 0000000..316d77a --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/basic/output.js @@ -0,0 +1,7 @@ +import { css } from "@emotion/react"; +//#region virtual:entry.ts +function doThing() { + return /* @__PURE__ */ css("display:flex;", "doThing", "/*# sourceMappingURL=[sourcemap] */"); +} +//#endregion +export { doThing }; diff --git a/packages/emotion/tests/fixtures-labels/call-expression/input.ts b/packages/emotion/tests/fixtures-labels/call-expression/input.ts new file mode 100644 index 0000000..603ca7d --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/call-expression/input.ts @@ -0,0 +1,7 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/call-expression.js + +import { css } from '@emotion/react' + +export function doThing() { + return css({ color: 'hotpink' }) +} diff --git a/packages/emotion/tests/fixtures-labels/call-expression/output.js b/packages/emotion/tests/fixtures-labels/call-expression/output.js new file mode 100644 index 0000000..3974d32 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/call-expression/output.js @@ -0,0 +1,7 @@ +import { css } from "@emotion/react"; +//#region virtual:entry.ts +function doThing() { + return /* @__PURE__ */ css({ color: "hotpink" }, "label:doThing", "/*# sourceMappingURL=[sourcemap] */"); +} +//#endregion +export { doThing }; diff --git a/packages/emotion/tests/fixtures-labels/call-inside-call/input.ts b/packages/emotion/tests/fixtures-labels/call-inside-call/input.ts new file mode 100644 index 0000000..ceeea12 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/call-inside-call/input.ts @@ -0,0 +1,12 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/call-inside-call.js + +import { css } from '@emotion/react' + +export const thing = css` + display: flex; + &:hover { + ${css` + color: hotpink; + `}; + } +` diff --git a/packages/emotion/tests/fixtures-labels/call-inside-call/output.js b/packages/emotion/tests/fixtures-labels/call-inside-call/output.js new file mode 100644 index 0000000..aa18235 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/call-inside-call/output.js @@ -0,0 +1,7 @@ +import { css } from "@emotion/react"; +//#region virtual:entry.ts +const thing = /* @__PURE__ */ css("display:flex;&:hover{", css` + color: hotpink; + `, ";}", "thing", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { thing }; diff --git a/packages/emotion/tests/fixtures-labels/comment-with-interpolation/input.ts b/packages/emotion/tests/fixtures-labels/comment-with-interpolation/input.ts new file mode 100644 index 0000000..93529f5 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/comment-with-interpolation/input.ts @@ -0,0 +1,23 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/comment-with-interpolation.js + +import { css } from '@emotion/react' + +export const doThing = css` + // color: ${'green'}; + /* + + something: ${'something'}; + + */ + color: hotpink; +` + +export const doThing2 = css` + // color: ${'green'}; + /* + + something: ${'something'}; + + */ + color: ${'hotpink'}; +` diff --git a/packages/emotion/tests/fixtures-labels/comment-with-interpolation/output.js b/packages/emotion/tests/fixtures-labels/comment-with-interpolation/output.js new file mode 100644 index 0000000..d71639f --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/comment-with-interpolation/output.js @@ -0,0 +1,6 @@ +import { css } from "@emotion/react"; +//#region virtual:entry.ts +const doThing = /* @__PURE__ */ css("green", ";/*\n\n something:", "something", ";*/\n color:hotpink;", "doThing", "/*# sourceMappingURL=[sourcemap] */"); +const doThing2 = /* @__PURE__ */ css("green", ";/*\n\n something:", "something", ";*/\n color:", "hotpink", ";", "doThing2", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { doThing, doThing2 }; diff --git a/packages/emotion/tests/fixtures-labels/css-inside-function/input.ts b/packages/emotion/tests/fixtures-labels/css-inside-function/input.ts new file mode 100644 index 0000000..d22f460 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/css-inside-function/input.ts @@ -0,0 +1,13 @@ +import { css } from '@emotion/css' + +const wrapFunction = (cb) => { + return cb() +} + +export const classes = wrapFunction(() => { + const class1 = css({ color: 'red' }) + + return { + class1, + } +}) diff --git a/packages/emotion/tests/fixtures-labels/css-inside-function/output.js b/packages/emotion/tests/fixtures-labels/css-inside-function/output.js new file mode 100644 index 0000000..aa565c3 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/css-inside-function/output.js @@ -0,0 +1,10 @@ +import { css } from "@emotion/css"; +//#region virtual:entry.ts +const wrapFunction = (cb) => { + return cb(); +}; +const classes = wrapFunction(() => { + return { class1: /* @__PURE__ */ css({ color: "red" }, "label:class1", "/*# sourceMappingURL=[sourcemap] */") }; +}); +//#endregion +export { classes }; diff --git a/packages/emotion/tests/fixtures-labels/impure/input.ts b/packages/emotion/tests/fixtures-labels/impure/input.ts new file mode 100644 index 0000000..4806991 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/impure/input.ts @@ -0,0 +1,11 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/impure.js + +import { css } from '@emotion/react' + +function thing() {} + +export function doThing() { + return css` + display: ${thing()}; + ` +} diff --git a/packages/emotion/tests/fixtures-labels/impure/output.js b/packages/emotion/tests/fixtures-labels/impure/output.js new file mode 100644 index 0000000..635de43 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/impure/output.js @@ -0,0 +1,8 @@ +import { css } from "@emotion/react"; +//#region virtual:entry.ts +function thing() {} +function doThing() { + return /* @__PURE__ */ css("display:", thing(), ";", "doThing", "/*# sourceMappingURL=[sourcemap] */"); +} +//#endregion +export { doThing }; diff --git a/packages/emotion/tests/fixtures-labels/inside-anonymous-arrow-function/input.ts b/packages/emotion/tests/fixtures-labels/inside-anonymous-arrow-function/input.ts new file mode 100644 index 0000000..6bd8557 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/inside-anonymous-arrow-function/input.ts @@ -0,0 +1,9 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/inside-anonymous-arrow-function.js + +import { css } from '@emotion/react' + +export default () => { + return css` + color: hotpink; + ` +} diff --git a/packages/emotion/tests/fixtures-labels/inside-anonymous-arrow-function/output.js b/packages/emotion/tests/fixtures-labels/inside-anonymous-arrow-function/output.js new file mode 100644 index 0000000..1555018 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/inside-anonymous-arrow-function/output.js @@ -0,0 +1,7 @@ +import { css } from "@emotion/react"; +//#region virtual:entry.ts +var virtual_entry_default = () => { + return /* @__PURE__ */ css("color:hotpink;", "", "/*# sourceMappingURL=[sourcemap] */"); +}; +//#endregion +export { virtual_entry_default as default }; diff --git a/packages/emotion/tests/fixtures-labels/inside-anonymous-function/input.ts b/packages/emotion/tests/fixtures-labels/inside-anonymous-function/input.ts new file mode 100644 index 0000000..31b7aae --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/inside-anonymous-function/input.ts @@ -0,0 +1,9 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/inside-anonymous-function.js + +import { css } from '@emotion/react' + +export default () => { + return css` + color: hotpink; + ` +} diff --git a/packages/emotion/tests/fixtures-labels/inside-anonymous-function/output.js b/packages/emotion/tests/fixtures-labels/inside-anonymous-function/output.js new file mode 100644 index 0000000..1555018 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/inside-anonymous-function/output.js @@ -0,0 +1,7 @@ +import { css } from "@emotion/react"; +//#region virtual:entry.ts +var virtual_entry_default = () => { + return /* @__PURE__ */ css("color:hotpink;", "", "/*# sourceMappingURL=[sourcemap] */"); +}; +//#endregion +export { virtual_entry_default as default }; diff --git a/packages/emotion/tests/fixtures-labels/label-1/input.ts b/packages/emotion/tests/fixtures-labels/label-1/input.ts new file mode 100644 index 0000000..6de3951 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/label-1/input.ts @@ -0,0 +1,7 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/label-1.js + +import { css } from '@emotion/react' + +export const thing = css` + color: hotpink; +` diff --git a/packages/emotion/tests/fixtures-labels/label-1/output.js b/packages/emotion/tests/fixtures-labels/label-1/output.js new file mode 100644 index 0000000..77397a4 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/label-1/output.js @@ -0,0 +1,5 @@ +import { css } from "@emotion/react"; +//#region virtual:entry.ts +const thing = /* @__PURE__ */ css("color:hotpink;", "thing", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { thing }; diff --git a/packages/emotion/tests/fixtures-labels/label-arrow-as-obj-property/input.ts b/packages/emotion/tests/fixtures-labels/label-arrow-as-obj-property/input.ts new file mode 100644 index 0000000..dc349a1 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/label-arrow-as-obj-property/input.ts @@ -0,0 +1,9 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/label-arrow-as-obj-property.js + +import { css } from '@emotion/react' + +export const styles = { + colorFn1: () => css` + color: hotpink; + `, +} diff --git a/packages/emotion/tests/fixtures-labels/label-arrow-as-obj-property/output.js b/packages/emotion/tests/fixtures-labels/label-arrow-as-obj-property/output.js new file mode 100644 index 0000000..c331bc5 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/label-arrow-as-obj-property/output.js @@ -0,0 +1,5 @@ +import { css } from "@emotion/react"; +//#region virtual:entry.ts +const styles = { colorFn1: () => /* @__PURE__ */ css("color:hotpink;", "colorFn1", "/*# sourceMappingURL=[sourcemap] */") }; +//#endregion +export { styles }; diff --git a/packages/emotion/tests/fixtures-labels/label-function-expression-as-obj-property/input.ts b/packages/emotion/tests/fixtures-labels/label-function-expression-as-obj-property/input.ts new file mode 100644 index 0000000..54424fe --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/label-function-expression-as-obj-property/input.ts @@ -0,0 +1,11 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/label-function-expression-as-obj-property.js + +import { css } from '@emotion/react' + +export const styles = { + colorFn1: function () { + return css` + color: hotpink; + ` + }, +} diff --git a/packages/emotion/tests/fixtures-labels/label-function-expression-as-obj-property/output.js b/packages/emotion/tests/fixtures-labels/label-function-expression-as-obj-property/output.js new file mode 100644 index 0000000..cc2efbc --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/label-function-expression-as-obj-property/output.js @@ -0,0 +1,7 @@ +import { css } from "@emotion/react"; +//#region virtual:entry.ts +const styles = { colorFn1: function() { + return /* @__PURE__ */ css("color:hotpink;", "colorFn1", "/*# sourceMappingURL=[sourcemap] */"); +} }; +//#endregion +export { styles }; diff --git a/packages/emotion/tests/fixtures-labels/label-function-expression-named/input.ts b/packages/emotion/tests/fixtures-labels/label-function-expression-named/input.ts new file mode 100644 index 0000000..36837d6 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/label-function-expression-named/input.ts @@ -0,0 +1,9 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/label-function-expression-named.js + +import { css } from '@emotion/react' + +export const thing = function someName() { + return css` + color: hotpink; + ` +} diff --git a/packages/emotion/tests/fixtures-labels/label-function-expression-named/output.js b/packages/emotion/tests/fixtures-labels/label-function-expression-named/output.js new file mode 100644 index 0000000..47b4147 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/label-function-expression-named/output.js @@ -0,0 +1,7 @@ +import { css } from "@emotion/react"; +//#region virtual:entry.ts +const thing = function someName() { + return /* @__PURE__ */ css("color:hotpink;", "someName", "/*# sourceMappingURL=[sourcemap] */"); +}; +//#endregion +export { thing }; diff --git a/packages/emotion/tests/fixtures-labels/label-function-expression/input.ts b/packages/emotion/tests/fixtures-labels/label-function-expression/input.ts new file mode 100644 index 0000000..559261b --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/label-function-expression/input.ts @@ -0,0 +1,9 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/label-function-expression.js + +import { css } from '@emotion/react' + +export const thing = function () { + return css` + color: hotpink; + ` +} diff --git a/packages/emotion/tests/fixtures-labels/label-function-expression/output.js b/packages/emotion/tests/fixtures-labels/label-function-expression/output.js new file mode 100644 index 0000000..11243b7 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/label-function-expression/output.js @@ -0,0 +1,7 @@ +import { css } from "@emotion/react"; +//#region virtual:entry.ts +const thing = function() { + return /* @__PURE__ */ css("color:hotpink;", "thing", "/*# sourceMappingURL=[sourcemap] */"); +}; +//#endregion +export { thing }; diff --git a/packages/emotion/tests/fixtures-labels/label-no-final-semi/input.ts b/packages/emotion/tests/fixtures-labels/label-no-final-semi/input.ts new file mode 100644 index 0000000..c0ae7a7 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/label-no-final-semi/input.ts @@ -0,0 +1,8 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/label-no-final-semi.js + +import { css } from '@emotion/react' + +// prettier-ignore +export const thing = css` + color: hotpink +` diff --git a/packages/emotion/tests/fixtures-labels/label-no-final-semi/output.js b/packages/emotion/tests/fixtures-labels/label-no-final-semi/output.js new file mode 100644 index 0000000..7bdd3b7 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/label-no-final-semi/output.js @@ -0,0 +1,5 @@ +import { css } from "@emotion/react"; +//#region virtual:entry.ts +const thing = /* @__PURE__ */ css("color:hotpink", "thing", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { thing }; diff --git a/packages/emotion/tests/fixtures-labels/label-object/input.ts b/packages/emotion/tests/fixtures-labels/label-object/input.ts new file mode 100644 index 0000000..ef8da08 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/label-object/input.ts @@ -0,0 +1,13 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/label-object.js + +import { css } from '@emotion/react' + +export const thing = { + thisShouldBeTheLabel: css` + color: hotpink; + `, + // prettier-ignore + 'shouldBeAnotherLabel':css` + color:green; + `, +} diff --git a/packages/emotion/tests/fixtures-labels/label-object/output.js b/packages/emotion/tests/fixtures-labels/label-object/output.js new file mode 100644 index 0000000..a7712cb --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/label-object/output.js @@ -0,0 +1,8 @@ +import { css } from "@emotion/react"; +//#region virtual:entry.ts +const thing = { + thisShouldBeTheLabel: /* @__PURE__ */ css("color:hotpink;", "thisShouldBeTheLabel", "/*# sourceMappingURL=[sourcemap] */"), + "shouldBeAnotherLabel": /* @__PURE__ */ css("color:green;", "shouldBeAnotherLabel", "/*# sourceMappingURL=[sourcemap] */") +}; +//#endregion +export { thing }; diff --git a/packages/emotion/tests/fixtures-labels/multiple-calls/input.ts b/packages/emotion/tests/fixtures-labels/multiple-calls/input.ts new file mode 100644 index 0000000..6bee597 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/multiple-calls/input.ts @@ -0,0 +1,11 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/multiple-calls.js + +import { css } from '@emotion/react' + +export const thing = css` + color: hotpink; +` + +export const otherThing = css` + color: green; +` diff --git a/packages/emotion/tests/fixtures-labels/multiple-calls/output.js b/packages/emotion/tests/fixtures-labels/multiple-calls/output.js new file mode 100644 index 0000000..b1f3635 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/multiple-calls/output.js @@ -0,0 +1,6 @@ +import { css } from "@emotion/react"; +//#region virtual:entry.ts +const thing = /* @__PURE__ */ css("color:hotpink;", "thing", "/*# sourceMappingURL=[sourcemap] */"); +const otherThing = /* @__PURE__ */ css("color:green;", "otherThing", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { otherThing, thing }; diff --git a/packages/emotion/tests/fixtures-labels/no-actual-import/input.ts b/packages/emotion/tests/fixtures-labels/no-actual-import/input.ts new file mode 100644 index 0000000..a467480 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/no-actual-import/input.ts @@ -0,0 +1,3 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/no-actual-import.js + +import '@emotion/react' diff --git a/packages/emotion/tests/fixtures-labels/no-actual-import/output.js b/packages/emotion/tests/fixtures-labels/no-actual-import/output.js new file mode 100644 index 0000000..a35bc4b --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/no-actual-import/output.js @@ -0,0 +1 @@ +import "@emotion/react"; diff --git a/packages/emotion/tests/fixtures-labels/no-label-array-pattern/input.ts b/packages/emotion/tests/fixtures-labels/no-label-array-pattern/input.ts new file mode 100644 index 0000000..292404f --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/no-label-array-pattern/input.ts @@ -0,0 +1,11 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/no-label-array-pattern.js + +import { css } from '@emotion/react' + +const [weirdo] = [ + css` + color: hotpink; + `, +] + +export default weirdo diff --git a/packages/emotion/tests/fixtures-labels/no-label-array-pattern/output.js b/packages/emotion/tests/fixtures-labels/no-label-array-pattern/output.js new file mode 100644 index 0000000..c9f72cf --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/no-label-array-pattern/output.js @@ -0,0 +1,5 @@ +import { css } from "@emotion/react"; +//#region virtual:entry.ts +const [weirdo] = [/* @__PURE__ */ css("color:hotpink;", "", "/*# sourceMappingURL=[sourcemap] */")]; +//#endregion +export { weirdo as default }; diff --git a/packages/emotion/tests/fixtures-labels/no-label-obj-pattern-computed-property/input.ts b/packages/emotion/tests/fixtures-labels/no-label-obj-pattern-computed-property/input.ts new file mode 100644 index 0000000..13c8662 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/no-label-obj-pattern-computed-property/input.ts @@ -0,0 +1,13 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/no-label-obj-pattern-computed-property.js + +import { css } from '@emotion/react' + +const computed = 'weirdo' + +const { weirdo } = { + [computed]: css` + color: hotpink; + `, +} + +export default weirdo diff --git a/packages/emotion/tests/fixtures-labels/no-label-obj-pattern-computed-property/output.js b/packages/emotion/tests/fixtures-labels/no-label-obj-pattern-computed-property/output.js new file mode 100644 index 0000000..49c34af --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/no-label-obj-pattern-computed-property/output.js @@ -0,0 +1,5 @@ +import { css } from "@emotion/react"; +//#region virtual:entry.ts +const { weirdo } = { ["weirdo"]: /* @__PURE__ */ css("color:hotpink;", "", "/*# sourceMappingURL=[sourcemap] */") }; +//#endregion +export { weirdo as default }; diff --git a/packages/emotion/tests/fixtures-labels/object-dynamic-property/input.ts b/packages/emotion/tests/fixtures-labels/object-dynamic-property/input.ts new file mode 100644 index 0000000..4b13378 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/object-dynamic-property/input.ts @@ -0,0 +1,9 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/object-dynamic-property.js + +import { css } from '@emotion/react' + +export function doThing() { + return { + [css({ color: 'hotpink' })]: 'coldblue', + } +} diff --git a/packages/emotion/tests/fixtures-labels/object-dynamic-property/output.js b/packages/emotion/tests/fixtures-labels/object-dynamic-property/output.js new file mode 100644 index 0000000..55cb6ed --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/object-dynamic-property/output.js @@ -0,0 +1,7 @@ +import { css } from "@emotion/react"; +//#region virtual:entry.ts +function doThing() { + return { [/* @__PURE__ */ css({ color: "hotpink" }, "", "/*# sourceMappingURL=[sourcemap] */")]: "coldblue" }; +} +//#endregion +export { doThing }; diff --git a/packages/emotion/tests/fixtures-labels/options-dirname-filename-local/config.json b/packages/emotion/tests/fixtures-labels/options-dirname-filename-local/config.json new file mode 100644 index 0000000..fd3a9b2 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/options-dirname-filename-local/config.json @@ -0,0 +1 @@ +{ "labelFormat": "[dirname]-[filename]-[local]" } diff --git a/packages/emotion/tests/fixtures-labels/options-dirname-filename-local/input.tsx b/packages/emotion/tests/fixtures-labels/options-dirname-filename-local/input.tsx new file mode 100644 index 0000000..09c191e --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/options-dirname-filename-local/input.tsx @@ -0,0 +1,5 @@ +import styled from '@emotion/styled' + +export const StyledDiv = styled.div` + background-color: black; +` diff --git a/packages/emotion/tests/fixtures-labels/options-dirname-filename-local/output.js b/packages/emotion/tests/fixtures-labels/options-dirname-filename-local/output.js new file mode 100644 index 0000000..299f3f8 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/options-dirname-filename-local/output.js @@ -0,0 +1,8 @@ +import styled from "@emotion/styled"; +//#region virtual:entry.tsx +const StyledDiv = /* @__PURE__ */ styled("div", { + target: "ee6yw5a0", + label: "--virtual-entry-StyledDiv" +})("background-color:black;", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { StyledDiv }; diff --git a/packages/emotion/tests/fixtures-labels/options-dirname/config.json b/packages/emotion/tests/fixtures-labels/options-dirname/config.json new file mode 100644 index 0000000..e603c4d --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/options-dirname/config.json @@ -0,0 +1 @@ +{ "labelFormat": "[dirname]" } diff --git a/packages/emotion/tests/fixtures-labels/options-dirname/input.tsx b/packages/emotion/tests/fixtures-labels/options-dirname/input.tsx new file mode 100644 index 0000000..09c191e --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/options-dirname/input.tsx @@ -0,0 +1,5 @@ +import styled from '@emotion/styled' + +export const StyledDiv = styled.div` + background-color: black; +` diff --git a/packages/emotion/tests/fixtures-labels/options-dirname/output.js b/packages/emotion/tests/fixtures-labels/options-dirname/output.js new file mode 100644 index 0000000..779847a --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/options-dirname/output.js @@ -0,0 +1,8 @@ +import styled from "@emotion/styled"; +//#region virtual:entry.tsx +const StyledDiv = /* @__PURE__ */ styled("div", { + target: "ee6yw5a0", + label: "-" +})("background-color:black;", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { StyledDiv }; diff --git a/packages/emotion/tests/fixtures-labels/options-filename-local/config.json b/packages/emotion/tests/fixtures-labels/options-filename-local/config.json new file mode 100644 index 0000000..0294810 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/options-filename-local/config.json @@ -0,0 +1 @@ +{ "labelFormat": "[filename]-[local]" } diff --git a/packages/emotion/tests/fixtures-labels/options-filename-local/input.tsx b/packages/emotion/tests/fixtures-labels/options-filename-local/input.tsx new file mode 100644 index 0000000..09c191e --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/options-filename-local/input.tsx @@ -0,0 +1,5 @@ +import styled from '@emotion/styled' + +export const StyledDiv = styled.div` + background-color: black; +` diff --git a/packages/emotion/tests/fixtures-labels/options-filename-local/output.js b/packages/emotion/tests/fixtures-labels/options-filename-local/output.js new file mode 100644 index 0000000..0330cec --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/options-filename-local/output.js @@ -0,0 +1,8 @@ +import styled from "@emotion/styled"; +//#region virtual:entry.tsx +const StyledDiv = /* @__PURE__ */ styled("div", { + target: "ee6yw5a0", + label: "virtual-entry-StyledDiv" +})("background-color:black;", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { StyledDiv }; diff --git a/packages/emotion/tests/fixtures-labels/options-filename/config.json b/packages/emotion/tests/fixtures-labels/options-filename/config.json new file mode 100644 index 0000000..93ee80a --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/options-filename/config.json @@ -0,0 +1 @@ +{ "labelFormat": "[filename]" } diff --git a/packages/emotion/tests/fixtures-labels/options-filename/input.tsx b/packages/emotion/tests/fixtures-labels/options-filename/input.tsx new file mode 100644 index 0000000..09c191e --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/options-filename/input.tsx @@ -0,0 +1,5 @@ +import styled from '@emotion/styled' + +export const StyledDiv = styled.div` + background-color: black; +` diff --git a/packages/emotion/tests/fixtures-labels/options-filename/output.js b/packages/emotion/tests/fixtures-labels/options-filename/output.js new file mode 100644 index 0000000..16f6b63 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/options-filename/output.js @@ -0,0 +1,8 @@ +import styled from "@emotion/styled"; +//#region virtual:entry.tsx +const StyledDiv = /* @__PURE__ */ styled("div", { + target: "ee6yw5a0", + label: "virtual-entry" +})("background-color:black;", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { StyledDiv }; diff --git a/packages/emotion/tests/fixtures-labels/options-local/config.json b/packages/emotion/tests/fixtures-labels/options-local/config.json new file mode 100644 index 0000000..9d133c3 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/options-local/config.json @@ -0,0 +1 @@ +{ "labelFormat": "[local]" } diff --git a/packages/emotion/tests/fixtures-labels/options-local/input.tsx b/packages/emotion/tests/fixtures-labels/options-local/input.tsx new file mode 100644 index 0000000..09c191e --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/options-local/input.tsx @@ -0,0 +1,5 @@ +import styled from '@emotion/styled' + +export const StyledDiv = styled.div` + background-color: black; +` diff --git a/packages/emotion/tests/fixtures-labels/options-local/output.js b/packages/emotion/tests/fixtures-labels/options-local/output.js new file mode 100644 index 0000000..2917f19 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/options-local/output.js @@ -0,0 +1,8 @@ +import styled from "@emotion/styled"; +//#region virtual:entry.tsx +const StyledDiv = /* @__PURE__ */ styled("div", { + target: "ee6yw5a0", + label: "StyledDiv" +})("background-color:black;", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { StyledDiv }; diff --git a/packages/emotion/tests/fixtures-labels/other-imports/input.ts b/packages/emotion/tests/fixtures-labels/other-imports/input.ts new file mode 100644 index 0000000..81a73bc --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/other-imports/input.ts @@ -0,0 +1,5 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/other-imports.js + +import { nonExistantImport } from '@emotion/react' + +nonExistantImport() diff --git a/packages/emotion/tests/fixtures-labels/other-imports/output.js b/packages/emotion/tests/fixtures-labels/other-imports/output.js new file mode 100644 index 0000000..fddc701 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/other-imports/output.js @@ -0,0 +1,4 @@ +import { nonExistantImport } from "@emotion/react"; +//#region virtual:entry.ts +nonExistantImport(); +//#endregion diff --git a/packages/emotion/tests/fixtures-labels/remove-block-comments/input.ts b/packages/emotion/tests/fixtures-labels/remove-block-comments/input.ts new file mode 100644 index 0000000..5356570 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/remove-block-comments/input.ts @@ -0,0 +1,10 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/remove-block-comments.js + +import { css } from '@emotion/react' + +export const doThing = css` + /* color:green; + ddjfwjkng + */ + color: hotpink; +` diff --git a/packages/emotion/tests/fixtures-labels/remove-block-comments/output.js b/packages/emotion/tests/fixtures-labels/remove-block-comments/output.js new file mode 100644 index 0000000..cf38347 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/remove-block-comments/output.js @@ -0,0 +1,5 @@ +import { css } from "@emotion/react"; +//#region virtual:entry.ts +const doThing = /* @__PURE__ */ css("color:hotpink;", "doThing", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { doThing }; diff --git a/packages/emotion/tests/fixtures-labels/remove-line-comments/input.ts b/packages/emotion/tests/fixtures-labels/remove-line-comments/input.ts new file mode 100644 index 0000000..71ee998 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/remove-line-comments/input.ts @@ -0,0 +1,8 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/remove-line-comments.js + +import { css } from '@emotion/react' + +export const doThing = css` + // color: green; + color: hotpink; +` diff --git a/packages/emotion/tests/fixtures-labels/remove-line-comments/output.js b/packages/emotion/tests/fixtures-labels/remove-line-comments/output.js new file mode 100644 index 0000000..cf38347 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/remove-line-comments/output.js @@ -0,0 +1,5 @@ +import { css } from "@emotion/react"; +//#region virtual:entry.ts +const doThing = /* @__PURE__ */ css("color:hotpink;", "doThing", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { doThing }; diff --git a/packages/emotion/tests/fixtures-labels/sanitisation-bad.const.name/input.ts b/packages/emotion/tests/fixtures-labels/sanitisation-bad.const.name/input.ts new file mode 100644 index 0000000..0d2e352 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/sanitisation-bad.const.name/input.ts @@ -0,0 +1,5 @@ +import styled from '@emotion/styled' + +export const Dollar$Div = styled.div` + background-color: black; +` diff --git a/packages/emotion/tests/fixtures-labels/sanitisation-bad.const.name/output.js b/packages/emotion/tests/fixtures-labels/sanitisation-bad.const.name/output.js new file mode 100644 index 0000000..1ce42a8 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/sanitisation-bad.const.name/output.js @@ -0,0 +1,8 @@ +import styled from "@emotion/styled"; +//#region virtual:entry.ts +const Dollar$Div = /* @__PURE__ */ styled("div", { + target: "e1347i8e0", + label: "Dollar-Div" +})("background-color:black;", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { Dollar$Div }; diff --git a/packages/emotion/tests/fixtures-labels/sanitisation-bad.name.---------.----------/input.ts b/packages/emotion/tests/fixtures-labels/sanitisation-bad.name.---------.----------/input.ts new file mode 100644 index 0000000..09c191e --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/sanitisation-bad.name.---------.----------/input.ts @@ -0,0 +1,5 @@ +import styled from '@emotion/styled' + +export const StyledDiv = styled.div` + background-color: black; +` diff --git a/packages/emotion/tests/fixtures-labels/sanitisation-bad.name.---------.----------/output.js b/packages/emotion/tests/fixtures-labels/sanitisation-bad.name.---------.----------/output.js new file mode 100644 index 0000000..9059aa9 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/sanitisation-bad.name.---------.----------/output.js @@ -0,0 +1,8 @@ +import styled from "@emotion/styled"; +//#region virtual:entry.ts +const StyledDiv = /* @__PURE__ */ styled("div", { + target: "ee6yw5a0", + label: "StyledDiv" +})("background-color:black;", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { StyledDiv }; diff --git a/packages/emotion/tests/fixtures-labels/sanitisation-input.styles/input.ts b/packages/emotion/tests/fixtures-labels/sanitisation-input.styles/input.ts new file mode 100644 index 0000000..09c191e --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/sanitisation-input.styles/input.ts @@ -0,0 +1,5 @@ +import styled from '@emotion/styled' + +export const StyledDiv = styled.div` + background-color: black; +` diff --git a/packages/emotion/tests/fixtures-labels/sanitisation-input.styles/output.js b/packages/emotion/tests/fixtures-labels/sanitisation-input.styles/output.js new file mode 100644 index 0000000..9059aa9 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/sanitisation-input.styles/output.js @@ -0,0 +1,8 @@ +import styled from "@emotion/styled"; +//#region virtual:entry.ts +const StyledDiv = /* @__PURE__ */ styled("div", { + target: "ee6yw5a0", + label: "StyledDiv" +})("background-color:black;", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { StyledDiv }; diff --git a/packages/emotion/tests/fixtures-labels/tagged-template-args-forwarded/input.ts b/packages/emotion/tests/fixtures-labels/tagged-template-args-forwarded/input.ts new file mode 100644 index 0000000..862d016 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/tagged-template-args-forwarded/input.ts @@ -0,0 +1,15 @@ +// https://github.com/emotion-js/emotion/blob/main/packages/babel-plugin/__tests__/css-macro/__fixtures__/tagged-template-args-forwarded.js + +import { css } from '@emotion/react' + +function media(...args) { + return css` + @media (min-width: 100px) { + ${css(...args)}; + } + ` +} + +export const test = css` + ${media`color: red;`}; +` diff --git a/packages/emotion/tests/fixtures-labels/tagged-template-args-forwarded/output.js b/packages/emotion/tests/fixtures-labels/tagged-template-args-forwarded/output.js new file mode 100644 index 0000000..4f0eae6 --- /dev/null +++ b/packages/emotion/tests/fixtures-labels/tagged-template-args-forwarded/output.js @@ -0,0 +1,8 @@ +import { css } from "@emotion/react"; +//#region virtual:entry.ts +function media(...args) { + return /* @__PURE__ */ css("@media (min-width:100px){", css(...args), ";}", "media", "/*# sourceMappingURL=[sourcemap] */"); +} +const test = /* @__PURE__ */ css(media`color: red;`, ";", "test", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { test }; diff --git a/packages/emotion/tests/fixtures/comments/input.tsx b/packages/emotion/tests/fixtures/comments/input.tsx new file mode 100644 index 0000000..cc8315d --- /dev/null +++ b/packages/emotion/tests/fixtures/comments/input.tsx @@ -0,0 +1,13 @@ +import styled from "@emotion/styled"; + +export default styled.div` + color: red; + .foo { + color: blue; + /** + multi line comments + */ + } + /* /* */ + width: 10px; +`; diff --git a/packages/emotion/tests/fixtures/comments/output.js b/packages/emotion/tests/fixtures/comments/output.js new file mode 100644 index 0000000..f70a7de --- /dev/null +++ b/packages/emotion/tests/fixtures/comments/output.js @@ -0,0 +1,8 @@ +import styled from "@emotion/styled"; +//#region virtual:entry.tsx +var virtual_entry_default = /* @__PURE__ */ styled("div", { + target: "eluin830", + label: "" +})("color:red;.foo{color:blue;}width:10px;", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { virtual_entry_default as default }; diff --git a/packages/emotion/tests/fixtures/compress/input.tsx b/packages/emotion/tests/fixtures/compress/input.tsx new file mode 100644 index 0000000..10219cc --- /dev/null +++ b/packages/emotion/tests/fixtures/compress/input.tsx @@ -0,0 +1,24 @@ +import { css } from "@emotion/react"; +import styled from "@emotion/styled"; + +const unitNormal = "1rem"; +const unitLarge = "2rem"; + +export const Example = styled.div` + margin: ${unitNormal} ${unitLarge}; +`; + +export const Animated = styled.div` + & code { + background-color: linen; + } + animation: ${({ animation }) => animation} 0.2s infinite ease-in-out alternate; +`; + +export const shadowBorder = ({ width = "1px", color }) => css` + box-shadow: inset 0px 0px 0px ${width} ${color}; +`; + +export const StyledInput = styled.input` + ${shadowBorder({ color: "red", width: "4px" })} +`; diff --git a/packages/emotion/tests/fixtures/compress/output.js b/packages/emotion/tests/fixtures/compress/output.js new file mode 100644 index 0000000..3fcb404 --- /dev/null +++ b/packages/emotion/tests/fixtures/compress/output.js @@ -0,0 +1,20 @@ +import { css } from "@emotion/react"; +import styled from "@emotion/styled"; +const Example = /* @__PURE__ */ styled("div", { + target: "ejis9i80", + label: "Example" +})("margin:", "1rem", " ", "2rem", ";", "/*# sourceMappingURL=[sourcemap] */"); +const Animated = /* @__PURE__ */ styled("div", { + target: "ejis9i81", + label: "Animated" +})("& code{background-color:linen;}animation:", ({ animation }) => animation, " 0.2s infinite ease-in-out alternate;", "/*# sourceMappingURL=[sourcemap] */"); +const shadowBorder = ({ width = "1px", color }) => /* @__PURE__ */ css("box-shadow:inset 0px 0px 0px ", width, " ", color, ";", "shadowBorder", "/*# sourceMappingURL=[sourcemap] */"); +const StyledInput = /* @__PURE__ */ styled("input", { + target: "ejis9i82", + label: "StyledInput" +})(shadowBorder({ + color: "red", + width: "4px" +}), "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { Animated, Example, StyledInput, shadowBorder }; diff --git a/packages/emotion/tests/fixtures/css-default-export/input.tsx b/packages/emotion/tests/fixtures/css-default-export/input.tsx new file mode 100644 index 0000000..f07c3eb --- /dev/null +++ b/packages/emotion/tests/fixtures/css-default-export/input.tsx @@ -0,0 +1,5 @@ +import css from "@emotion/css"; + +export const styles = css` + color: red; +`; diff --git a/packages/emotion/tests/fixtures/css-default-export/output.js b/packages/emotion/tests/fixtures/css-default-export/output.js new file mode 100644 index 0000000..0973846 --- /dev/null +++ b/packages/emotion/tests/fixtures/css-default-export/output.js @@ -0,0 +1,5 @@ +import css from "@emotion/css"; +//#region virtual:entry.tsx +const styles = /* @__PURE__ */ css("color:red;", "styles", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { styles }; diff --git a/packages/emotion/tests/fixtures/css-in-callback/input.tsx b/packages/emotion/tests/fixtures/css-in-callback/input.tsx new file mode 100644 index 0000000..17b6b85 --- /dev/null +++ b/packages/emotion/tests/fixtures/css-in-callback/input.tsx @@ -0,0 +1,86 @@ +import { css, Global } from "@emotion/react"; +import styled from "@emotion/styled"; +import { PureComponent } from "react"; +import ReactDOM from "react-dom"; + +const stylesInCallback = (props: any) => + css({ + color: "red", + background: "yellow", + width: `${props.scale * 100}px`, + }); + +export const styles = css({ + color: "red", + width: "20px", +}); + +export const styles2 = css` + color: red; + width: 20px; +`; + +const DivContainer = styled.div({ + background: "red", +}); + +export const DivContainer2 = styled.div` + background: red; +`; + +export const ContainerWithOptions = styled("div", { + shouldForwardProp: (propertyName: string) => !propertyName.startsWith("$"), +})` + color: hotpink; +`; + +export const SpanContainer = styled("span")({ + background: "yellow", +}); + +export const DivContainerExtended = styled(DivContainer)``; +export const DivContainerExtended2 = styled(DivContainer)({}); + +const Container = styled("button")` + background: red; + ${stylesInCallback} + ${() => + css({ + background: "red", + })} + color: yellow; + font-size: 12px; +`; + +export const Container2 = styled.div` + background: red; +`; + +export class SimpleComponent extends PureComponent { + render() { + return ( + + + hello + + ); + } +} + +ReactDOM.render(, document.querySelector("#app")); diff --git a/packages/emotion/tests/fixtures/css-in-callback/output.js b/packages/emotion/tests/fixtures/css-in-callback/output.js new file mode 100644 index 0000000..4036c11 --- /dev/null +++ b/packages/emotion/tests/fixtures/css-in-callback/output.js @@ -0,0 +1,60 @@ +import { Global, css } from "@emotion/react"; +import styled from "@emotion/styled"; +import { PureComponent } from "react"; +import ReactDOM from "react-dom"; +import { jsx, jsxs } from "react/jsx-runtime"; +//#region virtual:entry.tsx +const stylesInCallback = (props) => /* @__PURE__ */ css({ + color: "red", + background: "yellow", + width: `${props.scale * 100}px` +}, "label:stylesInCallback", "/*# sourceMappingURL=[sourcemap] */"); +const styles = /* @__PURE__ */ css({ + color: "red", + width: "20px" +}, "label:styles", "/*# sourceMappingURL=[sourcemap] */"); +const styles2 = /* @__PURE__ */ css("color:red;width:20px;", "styles2", "/*# sourceMappingURL=[sourcemap] */"); +const DivContainer = /* @__PURE__ */ styled("div", { + target: "e1i4ntoh0", + label: "DivContainer" +})({ background: "red" }, "/*# sourceMappingURL=[sourcemap] */"); +const DivContainer2 = /* @__PURE__ */ styled("div", { + target: "e1i4ntoh1", + label: "DivContainer2" +})("background:red;", "/*# sourceMappingURL=[sourcemap] */"); +const ContainerWithOptions = /* @__PURE__ */ styled("div", { + shouldForwardProp: (propertyName) => !propertyName.startsWith("$"), + target: "e1i4ntoh2", + label: "ContainerWithOptions" +})("color:hotpink;", "/*# sourceMappingURL=[sourcemap] */"); +const SpanContainer = /* @__PURE__ */ styled("span", { + target: "e1i4ntoh3", + label: "SpanContainer" +})({ background: "yellow" }, "/*# sourceMappingURL=[sourcemap] */"); +const DivContainerExtended = /* @__PURE__ */ styled(DivContainer, { + target: "e1i4ntoh4", + label: "DivContainerExtended" +})("/*# sourceMappingURL=[sourcemap] */"); +const DivContainerExtended2 = /* @__PURE__ */ styled(DivContainer, { + target: "e1i4ntoh5", + label: "DivContainerExtended2" +})({}, "/*# sourceMappingURL=[sourcemap] */"); +const Container = /* @__PURE__ */ styled("button", { + target: "e1i4ntoh6", + label: "Container" +})("background:red;", stylesInCallback, " ", () => css({ background: "red" }), " color:yellow;font-size:12px;", "/*# sourceMappingURL=[sourcemap] */"); +const Container2 = /* @__PURE__ */ styled("div", { + target: "e1i4ntoh7", + label: "Container2" +})("background:red;", "/*# sourceMappingURL=[sourcemap] */"); +var SimpleComponent = class extends PureComponent { + render() { + return /* @__PURE__ */ jsxs(Container, { + css: /* @__PURE__ */ css("color:hotpink;", "SimpleComponent", "/*# sourceMappingURL=[sourcemap] */"), + children: [/* @__PURE__ */ jsx(Global, { styles: [css("html,body{padding:3rem 1rem;margin:0;background:papayawhip;min-height:100%;font-family:Helvetica,Arial,sans-serif;font-size:24px;}"), "/*# sourceMappingURL=[sourcemap] */"] }), /* @__PURE__ */ jsx("span", { children: "hello" })] + }); + } +}; +ReactDOM.render(/* @__PURE__ */ jsx(SimpleComponent, {}), document.querySelector("#app")); +//#endregion +export { Container2, ContainerWithOptions, DivContainer2, DivContainerExtended, DivContainerExtended2, SimpleComponent, SpanContainer, styles, styles2 }; diff --git a/packages/emotion/tests/fixtures/css-with-trailing-comma/input.tsx b/packages/emotion/tests/fixtures/css-with-trailing-comma/input.tsx new file mode 100644 index 0000000..06843fc --- /dev/null +++ b/packages/emotion/tests/fixtures/css-with-trailing-comma/input.tsx @@ -0,0 +1,5 @@ +import { css } from "@emotion/react"; + +export const contents = css( + { position: "absolute" }, +); diff --git a/packages/emotion/tests/fixtures/css-with-trailing-comma/output.js b/packages/emotion/tests/fixtures/css-with-trailing-comma/output.js new file mode 100644 index 0000000..a7a39ef --- /dev/null +++ b/packages/emotion/tests/fixtures/css-with-trailing-comma/output.js @@ -0,0 +1,5 @@ +import { css } from "@emotion/react"; +//#region virtual:entry.tsx +const contents = /* @__PURE__ */ css({ position: "absolute" }, "label:contents", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { contents }; diff --git a/packages/emotion/tests/fixtures/global-styles/input.tsx b/packages/emotion/tests/fixtures/global-styles/input.tsx new file mode 100644 index 0000000..c8f56a6 --- /dev/null +++ b/packages/emotion/tests/fixtures/global-styles/input.tsx @@ -0,0 +1,3 @@ +import { css, Global } from "@emotion/react"; + +export default () => ; diff --git a/packages/emotion/tests/fixtures/global-styles/output.js b/packages/emotion/tests/fixtures/global-styles/output.js new file mode 100644 index 0000000..628f259 --- /dev/null +++ b/packages/emotion/tests/fixtures/global-styles/output.js @@ -0,0 +1,6 @@ +import { Global, css } from "@emotion/react"; +import { jsx } from "react/jsx-runtime"; +//#region virtual:entry.tsx +var virtual_entry_default = () => /* @__PURE__ */ jsx(Global, { styles: [css("body{margin:0;}"), "/*# sourceMappingURL=[sourcemap] */"] }); +//#endregion +export { virtual_entry_default as default }; diff --git a/packages/emotion/tests/fixtures/import-map/global-needs-css/config.json b/packages/emotion/tests/fixtures/import-map/global-needs-css/config.json new file mode 100644 index 0000000..0ecce9e --- /dev/null +++ b/packages/emotion/tests/fixtures/import-map/global-needs-css/config.json @@ -0,0 +1,20 @@ +{ + "importMap": { + "package-one": { + "nonDefaultStyled": { "canonicalImport": ["@emotion/styled", "default"] } + }, + "package-two": { + "someCssFromCore": { "canonicalImport": ["@emotion/react", "css"] }, + "SomeGlobalFromCore": { "canonicalImport": ["@emotion/react", "Global"] } + }, + "package-three": { + "something": { "canonicalImport": ["@emotion/css", "css"] } + }, + "package-four": { + "nonDefaultStyled": { + "canonicalImport": ["@emotion/styled", "default"], + "styledBaseImport": ["package-four/base", "something"] + } + } + } +} diff --git a/packages/emotion/tests/fixtures/import-map/global-needs-css/input.tsx b/packages/emotion/tests/fixtures/import-map/global-needs-css/input.tsx new file mode 100644 index 0000000..a8f4a70 --- /dev/null +++ b/packages/emotion/tests/fixtures/import-map/global-needs-css/input.tsx @@ -0,0 +1,8 @@ +import * as React from "react"; +import { SomeGlobalFromCore } from "package-two"; + +const getBgColor = () => ({ backgroundColor: "#fff" }); + +export default () => ( + +); diff --git a/packages/emotion/tests/fixtures/import-map/global-needs-css/output.js b/packages/emotion/tests/fixtures/import-map/global-needs-css/output.js new file mode 100644 index 0000000..c0e75f8 --- /dev/null +++ b/packages/emotion/tests/fixtures/import-map/global-needs-css/output.js @@ -0,0 +1,11 @@ +import "react"; +import { SomeGlobalFromCore } from "package-two"; +import { jsx } from "react/jsx-runtime"; +//#region virtual:entry.tsx +const getBgColor = () => ({ backgroundColor: "#fff" }); +var virtual_entry_default = () => /* @__PURE__ */ jsx(SomeGlobalFromCore, { styles: [{ + color: "hotpink", + ...getBgColor() +}, "/*# sourceMappingURL=[sourcemap] */"] }); +//#endregion +export { virtual_entry_default as default }; diff --git a/packages/emotion/tests/fixtures/import-map/global/config.json b/packages/emotion/tests/fixtures/import-map/global/config.json new file mode 100644 index 0000000..0ecce9e --- /dev/null +++ b/packages/emotion/tests/fixtures/import-map/global/config.json @@ -0,0 +1,20 @@ +{ + "importMap": { + "package-one": { + "nonDefaultStyled": { "canonicalImport": ["@emotion/styled", "default"] } + }, + "package-two": { + "someCssFromCore": { "canonicalImport": ["@emotion/react", "css"] }, + "SomeGlobalFromCore": { "canonicalImport": ["@emotion/react", "Global"] } + }, + "package-three": { + "something": { "canonicalImport": ["@emotion/css", "css"] } + }, + "package-four": { + "nonDefaultStyled": { + "canonicalImport": ["@emotion/styled", "default"], + "styledBaseImport": ["package-four/base", "something"] + } + } + } +} diff --git a/packages/emotion/tests/fixtures/import-map/global/input.tsx b/packages/emotion/tests/fixtures/import-map/global/input.tsx new file mode 100644 index 0000000..93e282c --- /dev/null +++ b/packages/emotion/tests/fixtures/import-map/global/input.tsx @@ -0,0 +1,4 @@ +import * as React from "react"; +import { SomeGlobalFromCore } from "package-two"; + +export default () => ; diff --git a/packages/emotion/tests/fixtures/import-map/global/output.js b/packages/emotion/tests/fixtures/import-map/global/output.js new file mode 100644 index 0000000..16cfa47 --- /dev/null +++ b/packages/emotion/tests/fixtures/import-map/global/output.js @@ -0,0 +1,7 @@ +import "react"; +import { SomeGlobalFromCore } from "package-two"; +import { jsx } from "react/jsx-runtime"; +//#region virtual:entry.tsx +var virtual_entry_default = () => /* @__PURE__ */ jsx(SomeGlobalFromCore, { styles: [{ color: "hotpink" }, "/*# sourceMappingURL=[sourcemap] */"] }); +//#endregion +export { virtual_entry_default as default }; diff --git a/packages/emotion/tests/fixtures/import-map/non-default-styled-aliased/config.json b/packages/emotion/tests/fixtures/import-map/non-default-styled-aliased/config.json new file mode 100644 index 0000000..0ecce9e --- /dev/null +++ b/packages/emotion/tests/fixtures/import-map/non-default-styled-aliased/config.json @@ -0,0 +1,20 @@ +{ + "importMap": { + "package-one": { + "nonDefaultStyled": { "canonicalImport": ["@emotion/styled", "default"] } + }, + "package-two": { + "someCssFromCore": { "canonicalImport": ["@emotion/react", "css"] }, + "SomeGlobalFromCore": { "canonicalImport": ["@emotion/react", "Global"] } + }, + "package-three": { + "something": { "canonicalImport": ["@emotion/css", "css"] } + }, + "package-four": { + "nonDefaultStyled": { + "canonicalImport": ["@emotion/styled", "default"], + "styledBaseImport": ["package-four/base", "something"] + } + } + } +} diff --git a/packages/emotion/tests/fixtures/import-map/non-default-styled-aliased/input.tsx b/packages/emotion/tests/fixtures/import-map/non-default-styled-aliased/input.tsx new file mode 100644 index 0000000..5ae315e --- /dev/null +++ b/packages/emotion/tests/fixtures/import-map/non-default-styled-aliased/input.tsx @@ -0,0 +1,3 @@ +import { nonDefaultStyled as someAlias } from "package-one"; + +export let SomeComp = someAlias.div({ color: "hotpink" }); diff --git a/packages/emotion/tests/fixtures/import-map/non-default-styled-aliased/output.js b/packages/emotion/tests/fixtures/import-map/non-default-styled-aliased/output.js new file mode 100644 index 0000000..312b255 --- /dev/null +++ b/packages/emotion/tests/fixtures/import-map/non-default-styled-aliased/output.js @@ -0,0 +1,8 @@ +import { nonDefaultStyled } from "package-one"; +//#region virtual:entry.tsx +let SomeComp = /* @__PURE__ */ nonDefaultStyled("div", { + target: "e1v42pg20", + label: "SomeComp" +})({ color: "hotpink" }, "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { SomeComp }; diff --git a/packages/emotion/tests/fixtures/import-map/non-default-styled/config.json b/packages/emotion/tests/fixtures/import-map/non-default-styled/config.json new file mode 100644 index 0000000..0ecce9e --- /dev/null +++ b/packages/emotion/tests/fixtures/import-map/non-default-styled/config.json @@ -0,0 +1,20 @@ +{ + "importMap": { + "package-one": { + "nonDefaultStyled": { "canonicalImport": ["@emotion/styled", "default"] } + }, + "package-two": { + "someCssFromCore": { "canonicalImport": ["@emotion/react", "css"] }, + "SomeGlobalFromCore": { "canonicalImport": ["@emotion/react", "Global"] } + }, + "package-three": { + "something": { "canonicalImport": ["@emotion/css", "css"] } + }, + "package-four": { + "nonDefaultStyled": { + "canonicalImport": ["@emotion/styled", "default"], + "styledBaseImport": ["package-four/base", "something"] + } + } + } +} diff --git a/packages/emotion/tests/fixtures/import-map/non-default-styled/input.tsx b/packages/emotion/tests/fixtures/import-map/non-default-styled/input.tsx new file mode 100644 index 0000000..fc1d9c9 --- /dev/null +++ b/packages/emotion/tests/fixtures/import-map/non-default-styled/input.tsx @@ -0,0 +1,3 @@ +import { nonDefaultStyled } from "package-one"; + +export let SomeComp = nonDefaultStyled.div({ color: "hotpink" }); diff --git a/packages/emotion/tests/fixtures/import-map/non-default-styled/output.js b/packages/emotion/tests/fixtures/import-map/non-default-styled/output.js new file mode 100644 index 0000000..e7de109 --- /dev/null +++ b/packages/emotion/tests/fixtures/import-map/non-default-styled/output.js @@ -0,0 +1,8 @@ +import { nonDefaultStyled } from "package-one"; +//#region virtual:entry.tsx +let SomeComp = /* @__PURE__ */ nonDefaultStyled("div", { + target: "e9npfi30", + label: "SomeComp" +})({ color: "hotpink" }, "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { SomeComp }; diff --git a/packages/emotion/tests/fixtures/import-map/styled-with-base-specified/config.json b/packages/emotion/tests/fixtures/import-map/styled-with-base-specified/config.json new file mode 100644 index 0000000..0ecce9e --- /dev/null +++ b/packages/emotion/tests/fixtures/import-map/styled-with-base-specified/config.json @@ -0,0 +1,20 @@ +{ + "importMap": { + "package-one": { + "nonDefaultStyled": { "canonicalImport": ["@emotion/styled", "default"] } + }, + "package-two": { + "someCssFromCore": { "canonicalImport": ["@emotion/react", "css"] }, + "SomeGlobalFromCore": { "canonicalImport": ["@emotion/react", "Global"] } + }, + "package-three": { + "something": { "canonicalImport": ["@emotion/css", "css"] } + }, + "package-four": { + "nonDefaultStyled": { + "canonicalImport": ["@emotion/styled", "default"], + "styledBaseImport": ["package-four/base", "something"] + } + } + } +} diff --git a/packages/emotion/tests/fixtures/import-map/styled-with-base-specified/input.tsx b/packages/emotion/tests/fixtures/import-map/styled-with-base-specified/input.tsx new file mode 100644 index 0000000..5bc57bd --- /dev/null +++ b/packages/emotion/tests/fixtures/import-map/styled-with-base-specified/input.tsx @@ -0,0 +1,3 @@ +import { nonDefaultStyled } from "package-four"; + +export let SomeComp = nonDefaultStyled.div({ color: "hotpink" }); diff --git a/packages/emotion/tests/fixtures/import-map/styled-with-base-specified/output.js b/packages/emotion/tests/fixtures/import-map/styled-with-base-specified/output.js new file mode 100644 index 0000000..eb1cfe5 --- /dev/null +++ b/packages/emotion/tests/fixtures/import-map/styled-with-base-specified/output.js @@ -0,0 +1,8 @@ +import { nonDefaultStyled } from "package-four"; +//#region virtual:entry.tsx +let SomeComp = /* @__PURE__ */ nonDefaultStyled("div", { + target: "eivxod40", + label: "SomeComp" +})({ color: "hotpink" }, "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { SomeComp }; diff --git a/packages/emotion/tests/fixtures/import-map/vanilla/config.json b/packages/emotion/tests/fixtures/import-map/vanilla/config.json new file mode 100644 index 0000000..0ecce9e --- /dev/null +++ b/packages/emotion/tests/fixtures/import-map/vanilla/config.json @@ -0,0 +1,20 @@ +{ + "importMap": { + "package-one": { + "nonDefaultStyled": { "canonicalImport": ["@emotion/styled", "default"] } + }, + "package-two": { + "someCssFromCore": { "canonicalImport": ["@emotion/react", "css"] }, + "SomeGlobalFromCore": { "canonicalImport": ["@emotion/react", "Global"] } + }, + "package-three": { + "something": { "canonicalImport": ["@emotion/css", "css"] } + }, + "package-four": { + "nonDefaultStyled": { + "canonicalImport": ["@emotion/styled", "default"], + "styledBaseImport": ["package-four/base", "something"] + } + } + } +} diff --git a/packages/emotion/tests/fixtures/import-map/vanilla/input.tsx b/packages/emotion/tests/fixtures/import-map/vanilla/input.tsx new file mode 100644 index 0000000..abd2194 --- /dev/null +++ b/packages/emotion/tests/fixtures/import-map/vanilla/input.tsx @@ -0,0 +1,3 @@ +import { something } from "package-three"; + +export const foo = something({ color: "green" }); diff --git a/packages/emotion/tests/fixtures/import-map/vanilla/output.js b/packages/emotion/tests/fixtures/import-map/vanilla/output.js new file mode 100644 index 0000000..3e4308b --- /dev/null +++ b/packages/emotion/tests/fixtures/import-map/vanilla/output.js @@ -0,0 +1,5 @@ +import { something } from "package-three"; +//#region virtual:entry.tsx +const foo = /* @__PURE__ */ something({ color: "green" }, "label:foo", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { foo }; diff --git a/packages/emotion/tests/fixtures/issue-258/input.tsx b/packages/emotion/tests/fixtures/issue-258/input.tsx new file mode 100644 index 0000000..9d6cbd5 --- /dev/null +++ b/packages/emotion/tests/fixtures/issue-258/input.tsx @@ -0,0 +1,5 @@ +import styled from "@emotion/styled"; + +styled.div` + background-image: url("//domain.com/image.png"); +`; diff --git a/packages/emotion/tests/fixtures/issue-258/output.js b/packages/emotion/tests/fixtures/issue-258/output.js new file mode 100644 index 0000000..46c1e27 --- /dev/null +++ b/packages/emotion/tests/fixtures/issue-258/output.js @@ -0,0 +1 @@ +import "@emotion/styled"; diff --git a/packages/emotion/tests/fixtures/issues/180/input.tsx b/packages/emotion/tests/fixtures/issues/180/input.tsx new file mode 100644 index 0000000..e511466 --- /dev/null +++ b/packages/emotion/tests/fixtures/issues/180/input.tsx @@ -0,0 +1,39 @@ +import { css } from "@emotion/react"; +import styled from "@emotion/styled"; + +// Example 1 +function myStyled(Component) { + return styled(Component)` + background-color: red; + `; +} + +function myCss(color) { + return css` + background-color: ${color}; + `; +} + +const myStyles = myCss("red"); + +const Div = myStyled("div"); + +function App() { + return ( + <> +
one
+
two
+ + ); +} + +// Example 2 +const styles = { + keyA: css({ + padding: 0, + }), + keyB: css({ + margin: 0, + }), +}; +const App2 = () =>
hello world
; diff --git a/packages/emotion/tests/fixtures/issues/180/output.js b/packages/emotion/tests/fixtures/issues/180/output.js new file mode 100644 index 0000000..77d82f6 --- /dev/null +++ b/packages/emotion/tests/fixtures/issues/180/output.js @@ -0,0 +1,16 @@ +import { css } from "@emotion/react"; +import styled from "@emotion/styled"; +import "react/jsx-runtime"; +//#region virtual:entry.tsx +function myStyled(Component) { + return /* @__PURE__ */ styled(Component, { + target: "e16jl22g0", + label: "myStyled" + })("background-color:red;", "/*# sourceMappingURL=[sourcemap] */"); +} +function myCss(color) { + return /* @__PURE__ */ css("background-color:", color, ";", "myCss", "/*# sourceMappingURL=[sourcemap] */"); +} +myCss("red"); +myStyled("div"); +//#endregion diff --git a/packages/emotion/tests/fixtures/issues/201/input.tsx b/packages/emotion/tests/fixtures/issues/201/input.tsx new file mode 100644 index 0000000..f841c13 --- /dev/null +++ b/packages/emotion/tests/fixtures/issues/201/input.tsx @@ -0,0 +1,17 @@ +import styled from "@emotion/styled"; + +function makeOptions() { + return { + shouldForwardProp: (propertyName: string) => !propertyName.startsWith("$"), + }; +} +export const ContainerWithOptions = styled("div", makeOptions())` + color: hotpink; +`; + +export const ContainerWithOptions2 = styled( + "div", + makeOptions(), +)({ + color: "hotpink", +}); diff --git a/packages/emotion/tests/fixtures/issues/201/output.js b/packages/emotion/tests/fixtures/issues/201/output.js new file mode 100644 index 0000000..99aa3c4 --- /dev/null +++ b/packages/emotion/tests/fixtures/issues/201/output.js @@ -0,0 +1,17 @@ +import styled from "@emotion/styled"; +//#region virtual:entry.tsx +function makeOptions() { + return { shouldForwardProp: (propertyName) => !propertyName.startsWith("$") }; +} +const ContainerWithOptions = /* @__PURE__ */ styled("div", { + target: "e1mmxd2q0", + label: "ContainerWithOptions", + ...makeOptions() +})("color:hotpink;", "/*# sourceMappingURL=[sourcemap] */"); +const ContainerWithOptions2 = /* @__PURE__ */ styled("div", { + target: "e1mmxd2q1", + label: "ContainerWithOptions2", + ...makeOptions() +})({ color: "hotpink" }, "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { ContainerWithOptions, ContainerWithOptions2 }; diff --git a/packages/emotion/tests/fixtures/issues/39672/input.tsx b/packages/emotion/tests/fixtures/issues/39672/input.tsx new file mode 100644 index 0000000..9774f83 --- /dev/null +++ b/packages/emotion/tests/fixtures/issues/39672/input.tsx @@ -0,0 +1,9 @@ +import styled from "@emotion/styled"; + +const SelectedComponent = styled.p` + color: red; + + &:after { + content: " | "; + } +`; diff --git a/packages/emotion/tests/fixtures/issues/39672/output.js b/packages/emotion/tests/fixtures/issues/39672/output.js new file mode 100644 index 0000000..46c1e27 --- /dev/null +++ b/packages/emotion/tests/fixtures/issues/39672/output.js @@ -0,0 +1 @@ +import "@emotion/styled"; diff --git a/packages/emotion/tests/fixtures/keyframes/input.tsx b/packages/emotion/tests/fixtures/keyframes/input.tsx new file mode 100644 index 0000000..9cd1213 --- /dev/null +++ b/packages/emotion/tests/fixtures/keyframes/input.tsx @@ -0,0 +1,6 @@ +import { keyframes } from "@emotion/react"; + +export const fadeIn = keyframes` + from { opacity: 0; } + to { opacity: 1; } +`; diff --git a/packages/emotion/tests/fixtures/keyframes/output.js b/packages/emotion/tests/fixtures/keyframes/output.js new file mode 100644 index 0000000..7bdab7f --- /dev/null +++ b/packages/emotion/tests/fixtures/keyframes/output.js @@ -0,0 +1,5 @@ +import { keyframes } from "@emotion/react"; +//#region virtual:entry.tsx +const fadeIn = /* @__PURE__ */ keyframes("from{opacity:0;}to{opacity:1;}", "fadeIn", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { fadeIn }; diff --git a/packages/emotion/tests/fixtures/namespace-css-trailing-comma/input.tsx b/packages/emotion/tests/fixtures/namespace-css-trailing-comma/input.tsx new file mode 100644 index 0000000..71dddef --- /dev/null +++ b/packages/emotion/tests/fixtures/namespace-css-trailing-comma/input.tsx @@ -0,0 +1,5 @@ +import * as emotionReact from "@emotion/react"; + +export const styles = emotionReact.css( + { color: 'red' }, +); diff --git a/packages/emotion/tests/fixtures/namespace-css-trailing-comma/output.js b/packages/emotion/tests/fixtures/namespace-css-trailing-comma/output.js new file mode 100644 index 0000000..5c5a968 --- /dev/null +++ b/packages/emotion/tests/fixtures/namespace-css-trailing-comma/output.js @@ -0,0 +1,5 @@ +import * as emotionReact from "@emotion/react"; +//#region virtual:entry.tsx +const styles = /* @__PURE__ */ emotionReact.css({ color: "red" }, "label:styles", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { styles }; diff --git a/packages/emotion/tests/fixtures/namespace-import/input.tsx b/packages/emotion/tests/fixtures/namespace-import/input.tsx new file mode 100644 index 0000000..fc941c2 --- /dev/null +++ b/packages/emotion/tests/fixtures/namespace-import/input.tsx @@ -0,0 +1,32 @@ +import * as emotionReact from "@emotion/react"; +import { PureComponent } from "react"; +import ReactDOM from "react-dom"; + +export const stylesInCallback = (props: any) => + emotionReact.css({ + color: "red", + background: "yellow", + width: `${props.scale * 100}px`, + }); + +export const styles = emotionReact.css({ + color: "red", + width: "20px", +}); + +export const styles2 = emotionReact.css` + color: red; + width: 20px; +`; + +export class SimpleComponent extends PureComponent { + render() { + return ( +
+ hello +
+ ); + } +} + +ReactDOM.render(, document.querySelector("#app")); diff --git a/packages/emotion/tests/fixtures/namespace-import/output.js b/packages/emotion/tests/fixtures/namespace-import/output.js new file mode 100644 index 0000000..d516962 --- /dev/null +++ b/packages/emotion/tests/fixtures/namespace-import/output.js @@ -0,0 +1,26 @@ +import * as emotionReact from "@emotion/react"; +import { PureComponent } from "react"; +import ReactDOM from "react-dom"; +import { jsx } from "react/jsx-runtime"; +//#region virtual:entry.tsx +const stylesInCallback = (props) => /* @__PURE__ */ emotionReact.css({ + color: "red", + background: "yellow", + width: `${props.scale * 100}px` +}, "label:stylesInCallback", "/*# sourceMappingURL=[sourcemap] */"); +const styles = /* @__PURE__ */ emotionReact.css({ + color: "red", + width: "20px" +}, "label:styles", "/*# sourceMappingURL=[sourcemap] */"); +const styles2 = /* @__PURE__ */ emotionReact.css("color:red;width:20px;", "label:styles2", "/*# sourceMappingURL=[sourcemap] */"); +var SimpleComponent = class extends PureComponent { + render() { + return /* @__PURE__ */ jsx("div", { + className: styles, + children: /* @__PURE__ */ jsx("span", { children: "hello" }) + }); + } +}; +ReactDOM.render(/* @__PURE__ */ jsx(SimpleComponent, {}), document.querySelector("#app")); +//#endregion +export { SimpleComponent, styles, styles2, stylesInCallback }; diff --git a/packages/emotion/tests/fixtures/next/40385/1/input.tsx b/packages/emotion/tests/fixtures/next/40385/1/input.tsx new file mode 100644 index 0000000..38b1c66 --- /dev/null +++ b/packages/emotion/tests/fixtures/next/40385/1/input.tsx @@ -0,0 +1,26 @@ +import styled from "@emotion/styled"; + +export default function IndexPage() { + return ( + <> +

IndexPage

+ + + + ); +} + +const IconWrapper = styled.div` + &[class^="icon-"], + [class*=" icon-"] { + color: red; + } + + &.icon-chat:before { + content: "\\e904"; + } + + &.icon-check:before { + content: "\\e905"; + } +`; diff --git a/packages/emotion/tests/fixtures/next/40385/1/output.js b/packages/emotion/tests/fixtures/next/40385/1/output.js new file mode 100644 index 0000000..353ba1b --- /dev/null +++ b/packages/emotion/tests/fixtures/next/40385/1/output.js @@ -0,0 +1,16 @@ +import styled from "@emotion/styled"; +import { Fragment, jsx, jsxs } from "react/jsx-runtime"; +//#region virtual:entry.tsx +function IndexPage() { + return /* @__PURE__ */ jsxs(Fragment, { children: [ + /* @__PURE__ */ jsx("h1", { children: "IndexPage" }), + /* @__PURE__ */ jsx(IconWrapper, { className: "icon-chat" }), + /* @__PURE__ */ jsx(IconWrapper, { className: "icon-check" }) + ] }); +} +const IconWrapper = /* @__PURE__ */ styled("div", { + target: "eokgv1d0", + label: "IconWrapper" +})("&[class^=\"icon-\"],[class*=\" icon-\"]{color:red;}&.icon-chat:before{content:\"\\e904\";}&.icon-check:before{content:\"\\e905\";}", "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { IndexPage as default }; diff --git a/packages/emotion/tests/fixtures/shadowed/input.tsx b/packages/emotion/tests/fixtures/shadowed/input.tsx new file mode 100644 index 0000000..8bf793b --- /dev/null +++ b/packages/emotion/tests/fixtures/shadowed/input.tsx @@ -0,0 +1,18 @@ +import styled from "@emotion/styled"; + +export function foo() { + const a = styled.div` + color: red; + .foo { + color: blue; + /** + multi line comments + */ + } + /* /* */ + width: 10px; +`; + console.log(a); + + var styled; +} diff --git a/packages/emotion/tests/fixtures/shadowed/output.js b/packages/emotion/tests/fixtures/shadowed/output.js new file mode 100644 index 0000000..039c5e3 --- /dev/null +++ b/packages/emotion/tests/fixtures/shadowed/output.js @@ -0,0 +1,18 @@ +//#region virtual:entry.tsx +function foo() { + const a = styled.div` + color: red; + .foo { + color: blue; + /** + multi line comments + */ + } + /* /* */ + width: 10px; +`; + console.log(a); + var styled; +} +//#endregion +export { foo }; diff --git a/packages/emotion/tests/fixtures/styled-call-empty-object-no-label/config.json b/packages/emotion/tests/fixtures/styled-call-empty-object-no-label/config.json new file mode 100644 index 0000000..cd8fe2c --- /dev/null +++ b/packages/emotion/tests/fixtures/styled-call-empty-object-no-label/config.json @@ -0,0 +1 @@ +{ "autoLabel": "never" } diff --git a/packages/emotion/tests/fixtures/styled-call-empty-object-no-label/input.tsx b/packages/emotion/tests/fixtures/styled-call-empty-object-no-label/input.tsx new file mode 100644 index 0000000..9d9257f --- /dev/null +++ b/packages/emotion/tests/fixtures/styled-call-empty-object-no-label/input.tsx @@ -0,0 +1,9 @@ +import styled from "@emotion/styled"; + +// Tagged template with empty object, label disabled +export const TaggedEmpty = styled(Component, {})` + color: red; +`; + +// Call expression with empty object, label disabled +export const CallEmpty = styled(Component, {})({ color: 'red' }); diff --git a/packages/emotion/tests/fixtures/styled-call-empty-object-no-label/output.js b/packages/emotion/tests/fixtures/styled-call-empty-object-no-label/output.js new file mode 100644 index 0000000..3add18c --- /dev/null +++ b/packages/emotion/tests/fixtures/styled-call-empty-object-no-label/output.js @@ -0,0 +1,6 @@ +import styled from "@emotion/styled"; +//#region virtual:entry.tsx +const TaggedEmpty = /* @__PURE__ */ styled(Component, { target: "eeip7ub0" })("color:red;", "/*# sourceMappingURL=[sourcemap] */"); +const CallEmpty = /* @__PURE__ */ styled(Component, { target: "eeip7ub1" })({ color: "red" }, "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { CallEmpty, TaggedEmpty }; diff --git a/packages/emotion/tests/fixtures/styled-call-empty-object/input.tsx b/packages/emotion/tests/fixtures/styled-call-empty-object/input.tsx new file mode 100644 index 0000000..4547898 --- /dev/null +++ b/packages/emotion/tests/fixtures/styled-call-empty-object/input.tsx @@ -0,0 +1,9 @@ +import styled from "@emotion/styled"; + +// Tagged template with empty object +export const TaggedEmpty = styled(Component, {})` + color: red; +`; + +// Call expression with empty object +export const CallEmpty = styled(Component, {})({ color: 'red' }); diff --git a/packages/emotion/tests/fixtures/styled-call-empty-object/output.js b/packages/emotion/tests/fixtures/styled-call-empty-object/output.js new file mode 100644 index 0000000..272cc54 --- /dev/null +++ b/packages/emotion/tests/fixtures/styled-call-empty-object/output.js @@ -0,0 +1,12 @@ +import styled from "@emotion/styled"; +//#region virtual:entry.tsx +const TaggedEmpty = /* @__PURE__ */ styled(Component, { + target: "eh0js120", + label: "TaggedEmpty" +})("color:red;", "/*# sourceMappingURL=[sourcemap] */"); +const CallEmpty = /* @__PURE__ */ styled(Component, { + target: "eh0js121", + label: "CallEmpty" +})({ color: "red" }, "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { CallEmpty, TaggedEmpty }; diff --git a/packages/emotion/tests/fixtures/styled-call-trailing-comma/input.tsx b/packages/emotion/tests/fixtures/styled-call-trailing-comma/input.tsx new file mode 100644 index 0000000..aa42d0e --- /dev/null +++ b/packages/emotion/tests/fixtures/styled-call-trailing-comma/input.tsx @@ -0,0 +1,10 @@ +import styled from "@emotion/styled"; + +export const MyComponent = styled('div')( + { color: 'red' }, +); + +export const MyComponent2 = styled('div')( + { color: 'red' }, + { target: 'x', }, +); diff --git a/packages/emotion/tests/fixtures/styled-call-trailing-comma/output.js b/packages/emotion/tests/fixtures/styled-call-trailing-comma/output.js new file mode 100644 index 0000000..edcb6ea --- /dev/null +++ b/packages/emotion/tests/fixtures/styled-call-trailing-comma/output.js @@ -0,0 +1,12 @@ +import styled from "@emotion/styled"; +//#region virtual:entry.tsx +const MyComponent = /* @__PURE__ */ styled("div", { + target: "e1p2oxds0", + label: "MyComponent" +})({ color: "red" }, "/*# sourceMappingURL=[sourcemap] */"); +const MyComponent2 = /* @__PURE__ */ styled("div", { + target: "e1p2oxds1", + label: "MyComponent2" +})({ color: "red" }, { target: "x" }, "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { MyComponent, MyComponent2 }; diff --git a/packages/emotion/tests/fixtures/styled-member-trailing-comma/input.tsx b/packages/emotion/tests/fixtures/styled-member-trailing-comma/input.tsx new file mode 100644 index 0000000..1daa2ad --- /dev/null +++ b/packages/emotion/tests/fixtures/styled-member-trailing-comma/input.tsx @@ -0,0 +1,5 @@ +import styled from "@emotion/styled"; + +export const MyComponent = styled.div( + { color: 'red' }, +); diff --git a/packages/emotion/tests/fixtures/styled-member-trailing-comma/output.js b/packages/emotion/tests/fixtures/styled-member-trailing-comma/output.js new file mode 100644 index 0000000..ab6d12a --- /dev/null +++ b/packages/emotion/tests/fixtures/styled-member-trailing-comma/output.js @@ -0,0 +1,8 @@ +import styled from "@emotion/styled"; +//#region virtual:entry.tsx +const MyComponent = /* @__PURE__ */ styled("div", { + target: "e3bpr110", + label: "MyComponent" +})({ color: "red" }, "/*# sourceMappingURL=[sourcemap] */"); +//#endregion +export { MyComponent }; diff --git a/packages/emotion/tests/transform.test.ts b/packages/emotion/tests/transform.test.ts new file mode 100644 index 0000000..fe31127 --- /dev/null +++ b/packages/emotion/tests/transform.test.ts @@ -0,0 +1,107 @@ +import { describe, it, expect } from 'vitest' +import { rolldown } from 'rolldown' +import emotionPlugin from '../src/index.ts' +import { globSync } from 'tinyglobby' +import { readFileSync, existsSync } from 'node:fs' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import type { EmotionPluginOptions } from '../src/types.ts' + +const fixturesDir = join(dirname(fileURLToPath(import.meta.url)), 'fixtures') +const fixturesLabelsDir = join(dirname(fileURLToPath(import.meta.url)), 'fixtures-labels') + +// Get all fixture directories (input.tsx or input.js files) +const fixturePaths = globSync(['*/input.tsx', '*/input.js', '**/*/input.tsx', '**/*/input.js'], { + cwd: fixturesDir, +}) + +describe('fixtures', () => { + for (const inputPath of fixturePaths) { + const fixtureName = dirname(inputPath) + const fullInputPath = join(fixturesDir, inputPath) + const input = readFileSync(fullInputPath, 'utf-8') + + const configPath = join(fixturesDir, fixtureName, 'config.json') + const config: EmotionPluginOptions = existsSync(configPath) + ? JSON.parse(readFileSync(configPath, 'utf-8')) + : {} + + it(fixtureName, async () => { + const result = await transform(input, config, fullInputPath) + await expect(result).toMatchFileSnapshot(join(fixturesDir, fixtureName, 'output.js')) + }) + } +}) + +// Labels tests - test label extraction from various AST contexts +const labelPaths = globSync(['*/input.ts', '*/input.tsx'], { + cwd: fixturesLabelsDir, +}) + +describe('fixtures-labels', () => { + for (const inputPath of labelPaths) { + const fixtureName = dirname(inputPath) + const fullInputPath = join(fixturesLabelsDir, inputPath) + const input = readFileSync(fullInputPath, 'utf-8') + + const configPath = join(fixturesLabelsDir, fixtureName, 'config.json') + const config: EmotionPluginOptions = existsSync(configPath) + ? JSON.parse(readFileSync(configPath, 'utf-8')) + : {} + + it(fixtureName, async () => { + const result = await transform(input, config, fullInputPath) + await expect(result).toMatchFileSnapshot(join(fixturesLabelsDir, fixtureName, 'output.js')) + }) + } +}) + +async function transform( + code: string, + options: EmotionPluginOptions, + filename = 'virtual:entry.tsx', +): Promise { + // Use extension from original filename for virtual entry to ensure correct parsing + const ext = filename.match(/\.[jt]sx?$/)?.[0] ?? '.ts' + const virtualEntry = `virtual:entry${ext}` + + const build = await rolldown({ + input: virtualEntry, + plugins: [ + { + name: 'virtual', + resolveId(id) { + if (id === virtualEntry) return id + // Mark all other imports as external + return { id, external: true } + }, + load(id) { + if (id === virtualEntry) return code + }, + }, + emotionPlugin({ + sourceMap: true, + autoLabel: 'always', + ...options, + }), + ], + }) + + const { output } = await build.generate({ format: 'esm' }) + return normalizeSourceMap(stripRolldownRuntime(output[0].code)) +} + +function stripRolldownRuntime(code: string): string { + // Replace rolldown runtime regions with a stable comment + return code.replace( + /\/\/#region \\0rolldown\/runtime\.js[\s\S]*?\/\/#endregion\n*/g, + '// [rolldown runtime elided]\n', + ) +} + +function normalizeSourceMap(code: string): string { + return code.replace( + /\/\*# sourceMappingURL=data:application\/json;charset=utf-8;base64,[^*]+ \*\//g, + '/*# sourceMappingURL=[sourcemap] */', + ) +} diff --git a/packages/emotion/tsdown.config.ts b/packages/emotion/tsdown.config.ts new file mode 100644 index 0000000..a3a2720 --- /dev/null +++ b/packages/emotion/tsdown.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'tsdown' + +export default defineConfig({ + entry: './src/index.ts', + dts: { + tsconfig: '../../tsconfig.common.json', + tsgo: true, + }, +}) diff --git a/packages/emotion/vitest.config.ts b/packages/emotion/vitest.config.ts new file mode 100644 index 0000000..36f4696 --- /dev/null +++ b/packages/emotion/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + name: 'emotion', + }, +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ffa6f11..160eddb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,7 +30,84 @@ importers: version: 0.21.2(@typescript/native-preview@7.0.0-dev.20260314.1)(publint@0.3.17) vitest: specifier: ^4.1.0 - version: 4.1.0(@types/node@22.19.15)(vite@8.0.0(@types/node@22.19.15)(esbuild@0.27.3)) + version: 4.1.0(@types/node@24.12.0)(vite@8.0.0(@types/node@24.12.0)(esbuild@0.27.3)) + + examples: + devDependencies: + playwright-chromium: + specifier: ^1.58.2 + version: 1.58.2 + vite: + specifier: ^8.0.0 + version: 8.0.0(@types/node@24.12.0)(esbuild@0.27.3) + + examples/emotion: + dependencies: + '@emotion/react': + specifier: ^11.14.0 + version: 11.14.0(@types/react@19.2.14)(react@19.2.4) + '@emotion/styled': + specifier: ^11.14.1 + version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4) + react: + specifier: ^19.2.4 + version: 19.2.4 + react-dom: + specifier: ^19.2.4 + version: 19.2.4(react@19.2.4) + devDependencies: + '@rolldown/plugin-emotion': + specifier: workspace:* + version: link:../../packages/emotion + '@types/react': + specifier: ^19.2.14 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.1(@rolldown/plugin-babel@0.2.1(@babel/core@8.0.0-rc.2)(@babel/plugin-transform-runtime@8.0.0-rc.2(@babel/core@8.0.0-rc.2))(@babel/runtime@8.0.0-rc.2)(rolldown@1.0.0-rc.9)(vite@8.0.0(@types/node@24.12.0)(esbuild@0.27.3)))(vite@8.0.0(@types/node@24.12.0)(esbuild@0.27.3)) + vite: + specifier: 8.0.0 + version: 8.0.0(@types/node@24.12.0)(esbuild@0.27.3) + + internal-packages/benchmark-utils: + devDependencies: + '@oxc-node/core': + specifier: ^0.0.35 + version: 0.0.35 + + internal-packages/oxc-unshadowed-visitor: + dependencies: + rolldown: + specifier: ^1.0.0-rc.9 + version: 1.0.0-rc.9 + devDependencies: + oxc-walker: + specifier: ^0.7.0 + version: 0.7.0(oxc-parser@0.119.0) + + internal-packages/swc-output-gen: + dependencies: + '@oxc-node/cli': + specifier: ^0.0.35 + version: 0.0.35 + '@rollup/plugin-swc': + specifier: ^0.4.0 + version: 0.4.0(@swc/core@1.15.18) + '@swc/core': + specifier: ^1.15.18 + version: 1.15.18 + '@swc/plugin-emotion': + specifier: ^14.7.0 + version: 14.7.0 + rolldown: + specifier: ^1.0.0-rc.9 + version: 1.0.0-rc.9 + tinyglobby: + specifier: ^0.2.15 + version: 0.2.15 packages/babel: dependencies: @@ -63,6 +140,86 @@ importers: specifier: ^8.0.0 version: 8.0.0(@types/node@22.19.15)(esbuild@0.27.3) + packages/emotion: + dependencies: + '@emotion/hash': + specifier: ^0.9.2 + version: 0.9.2 + '@jridgewell/gen-mapping': + specifier: ^0.3.13 + version: 0.3.13 + rolldown-string: + specifier: ^0.3.0 + version: 0.3.0(rolldown@1.0.0-rc.9) + devDependencies: + '@rolldown/oxc-unshadowed-visitor': + specifier: workspace:* + version: link:../../internal-packages/oxc-unshadowed-visitor + rolldown: + specifier: ^1.0.0-rc.9 + version: 1.0.0-rc.9 + tinyglobby: + specifier: ^0.2.15 + version: 0.2.15 + vite: + specifier: ^8.0.0 + version: 8.0.0(@types/node@24.12.0)(esbuild@0.27.3) + + packages/emotion/benchmark: + dependencies: + '@emotion/react': + specifier: ^11.14.0 + version: 11.14.0(@types/react@19.2.14)(react@19.2.4) + '@emotion/styled': + specifier: ^11.14.1 + version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4) + react: + specifier: ^19.2.4 + version: 19.2.4 + react-dom: + specifier: ^19.2.4 + version: 19.2.4(react@19.2.4) + devDependencies: + '@babel/core': + specifier: ^7.29.0 + version: 7.29.0 + '@emotion/babel-plugin': + specifier: ^11.13.5 + version: 11.13.5 + '@oxc-node/cli': + specifier: ^0.0.35 + version: 0.0.35 + '@rolldown/benchmark-utils': + specifier: workspace:* + version: link:../../../internal-packages/benchmark-utils + '@rolldown/plugin-babel': + specifier: file:../../babel + version: file:packages/babel(@babel/core@7.29.0)(@babel/plugin-transform-runtime@8.0.0-rc.2(@babel/core@7.29.0))(@babel/runtime@8.0.0-rc.2)(rolldown@1.0.0-rc.9)(vite@8.0.0(@types/node@24.12.0)(esbuild@0.27.3)) + '@rolldown/plugin-emotion': + specifier: workspace:* + version: link:.. + '@rollup/plugin-swc': + specifier: ^0.4.0 + version: 0.4.0(@swc/core@1.15.18) + '@swc/core': + specifier: ^1.15.18 + version: 1.15.18 + '@swc/plugin-emotion': + specifier: ^14.7.0 + version: 14.7.0 + '@types/node': + specifier: ^24.10.13 + version: 24.12.0 + '@types/react': + specifier: ^19.2.14 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + rolldown: + specifier: ^1.0.0-rc.9 + version: 1.0.0-rc.9 + scripts: devDependencies: '@vitejs/release-scripts': @@ -77,14 +234,26 @@ importers: packages: + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + '@babel/code-frame@8.0.0-rc.2': resolution: {integrity: sha512-zbPFBDbQdChkGN02WRc/BcOvZLDTctFJZVeWkciVr82T5V0GVBXztq4/Wi4Ca+ZKx7U+Kdt5b862cpFJ4Cjf1A==} engines: {node: ^20.19.0 || >=22.12.0} + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + '@babel/compat-data@8.0.0-rc.2': resolution: {integrity: sha512-zDrQeMrDVCkisxxjZmP+xeAyGfZCVOwP+7VECgOvMXttb+1pTUMpeEYI0LaozIzeES/Uvu7OqhHLb3oN1qo6Wg==} engines: {node: ^20.19.0 || >=22.12.0} + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + '@babel/core@8.0.0-rc.2': resolution: {integrity: sha512-mlBJdKJJEZNGDE+w+P6B5w+FTMkht1liPkxtB4wk39EpGH01Am5tg1htaNlOU5rO9Ge3psMjAFycpc3ru5uaQw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -94,6 +263,10 @@ packages: '@babel/preset-typescript': optional: true + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + '@babel/generator@8.0.0-rc.2': resolution: {integrity: sha512-oCQ1IKPwkzCeJzAPb7Fv8rQ9k5+1sG8mf2uoHiMInPYvkRfrDJxbTIbH51U+jstlkghus0vAi3EBvkfvEsYNLQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -102,6 +275,10 @@ packages: resolution: {integrity: sha512-KbQiXXTEGDdQo6SHaVafQowpPT+Kksqnq20zRY23pZgd63buryBA0dciIHs/04a86SxIsl1Ggvn82cWgdeq5nQ==} engines: {node: ^20.19.0 || >=22.12.0} + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@8.0.0-rc.2': resolution: {integrity: sha512-oMIhKru9gl3mj0eKDyKW6wBDAvyWoZd28d6V/m4JTeeiFsJLfOYnqu+s+cnK4jSo87cg/oj4hsATgkmZ3AzsDQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -112,6 +289,10 @@ packages: peerDependencies: '@babel/core': ^8.0.0-rc.2 + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + '@babel/helper-globals@8.0.0-rc.2': resolution: {integrity: sha512-Q1AIOaW4EOxkI/8wYJKyLI59gfqTK3imFUfIqxuve0Q3GlOSrOTVmvHU6Gb3Y5GxtoS1hIzhO47k5GkfyGTQEQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -120,10 +301,20 @@ packages: resolution: {integrity: sha512-LbzA3y4YgiVC8TlnOucMWLdsag1KPYijUmm9hmaHcU7srGNYfH9Qe6Y5I3FlJ/PjmWEvIKX2MX+NFMuCMrpkHQ==} engines: {node: ^20.19.0 || >=22.12.0} + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@8.0.0-rc.2': resolution: {integrity: sha512-9xSWdt/w6bA9sEcdxIDoNvnKZSGcUqeLQSWu0bh69xRV2Skj5vXSYXbFdYE4M8g/stEeNQ/9zSAs2OlSF3ZOOw==} engines: {node: ^20.19.0 || >=22.12.0} + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-optimise-call-expression@8.0.0-rc.2': resolution: {integrity: sha512-y1H1DXTPIR0yi/T9ervuo4f0zD1zCEn7FXnFBFU8DtCr3SMA0oS50jgt1PMCX5hg44RxvgG+dhAwHCP9mneZqg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -144,22 +335,43 @@ packages: resolution: {integrity: sha512-NO8HTg+G9V5R7HGkVX5jwanxZcjcj0FZ6TwUFZJmGVOxpWBty+zStju0PTkhah0A/npmRGwJPy8+RCCUtX8Qug==} engines: {node: ^20.19.0 || >=22.12.0} + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@8.0.0-rc.2': resolution: {integrity: sha512-noLx87RwlBEMrTzncWd/FvTxoJ9+ycHNg0n8yyYydIoDsLZuxknKgWRJUqcrVkNrJ74uGyhWQzQaS3q8xfGAhQ==} engines: {node: ^20.19.0 || >=22.12.0} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@8.0.0-rc.2': resolution: {integrity: sha512-xExUBkuXWJjVuIbO7z6q7/BA9bgfJDEhVL0ggrggLMbg0IzCUWGT1hZGE8qUH7Il7/RD/a6cZ3AAFrrlp1LF/A==} engines: {node: ^20.19.0 || >=22.12.0} + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@8.0.0-rc.2': resolution: {integrity: sha512-EtxQopsocKue0ZdjnX5INoDiRN+RCBb1TDh3d0N8bM6aX0lyUhQfRNRQaKB+vCx+YvGjXWRf3JD6/YvTsf2qgQ==} engines: {node: ^20.19.0 || >=22.12.0} + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + '@babel/helpers@8.0.0-rc.2': resolution: {integrity: sha512-Cc2IpMRiu8PDBUxtQ6oSkML0etJ27kZGnf3XE+qqAJJFGtVl549kyfvDWLywCAFhq16kHUe2WMZMdFUtPz6kWw==} engines: {node: ^20.19.0 || >=22.12.0} + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/parser@8.0.0-rc.2': resolution: {integrity: sha512-29AhEtcq4x8Dp3T72qvUMZHx0OMXCj4Jy/TEReQa+KWLln524Cj1fWb3QFi0l/xSpptQBR6y9RNEXuxpFvwiUQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -183,18 +395,34 @@ packages: peerDependencies: '@babel/core': ^8.0.0-rc.2 + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + '@babel/runtime@8.0.0-rc.2': resolution: {integrity: sha512-wI+xpmXzz8HwQflyWnjgWqBNSwSCloGq+f5/8MpWQ9XE+w8PMPl/g8fjh6KHZa4ub5/WAMZKaNTThvRWJnzVYA==} engines: {node: ^20.19.0 || >=22.12.0} + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + '@babel/template@8.0.0-rc.2': resolution: {integrity: sha512-INp+KufeQpvU+V+gxR7xoiVzU6sRRQo8oOsCU/sTe0wtJ/Adrfgyet0i19qvXXSeuyiZ9+PV8IF/eEPzyJ527g==} engines: {node: ^20.19.0 || >=22.12.0} + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + '@babel/traverse@8.0.0-rc.2': resolution: {integrity: sha512-H9ZChE8gRy4fSloEQaT17ijcFNoayS9JIyE0IUWkjYgldU+Czkg2h5XtuJmfIk6cbuHfDK/FFJox+g/TlmXB7g==} engines: {node: ^20.19.0 || >=22.12.0} + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + '@babel/types@8.0.0-rc.2': resolution: {integrity: sha512-91gAaWRznDwSX4E2tZ1YjBuIfnQVOFDCQ2r0Toby0gu4XEbyF623kXLMA8d4ZbCu+fINcrudkmEcwSUHgDDkNw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -220,6 +448,60 @@ packages: '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@emotion/babel-plugin@11.13.5': + resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} + + '@emotion/cache@11.14.0': + resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==} + + '@emotion/hash@0.9.2': + resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} + + '@emotion/is-prop-valid@1.4.0': + resolution: {integrity: sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==} + + '@emotion/memoize@0.9.0': + resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} + + '@emotion/react@11.14.0': + resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==} + peerDependencies: + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/serialize@1.3.3': + resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==} + + '@emotion/sheet@1.4.0': + resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} + + '@emotion/styled@11.14.1': + resolution: {integrity: sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==} + peerDependencies: + '@emotion/react': ^11.0.0-rc.0 + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/unitless@0.10.0': + resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0': + resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==} + peerDependencies: + react: '>=16.8.0' + + '@emotion/utils@1.4.2': + resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==} + + '@emotion/weak-memoize@0.4.0': + resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} + '@esbuild/aix-ppc64@0.27.3': resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} engines: {node: '>=18'} @@ -395,6 +677,231 @@ packages: '@napi-rs/wasm-runtime@1.1.1': resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} + '@oxc-node/cli@0.0.35': + resolution: {integrity: sha512-FTsYtymv6E4tyV2kxQEFLjsi6ZLmtsBpirFnbv5l7JGmQj65TCGM/WJlkZW+mPAM/9QN+ZY6cIOpnTTl8Qc7uA==} + hasBin: true + + '@oxc-node/core-android-arm-eabi@0.0.35': + resolution: {integrity: sha512-Vgw/DtArB1fZJ7LX4FX7YDpRvWzwv80lFNngTcamS+9Kbd83HpZb6Tg38t6f7Ubyc/+cL0TTMo55Kg8gwnQGHQ==} + cpu: [arm] + os: [android] + + '@oxc-node/core-android-arm64@0.0.35': + resolution: {integrity: sha512-Z/2jKqkTybSDnx2lBb44K0TLD2eUgLMi0te0pp5p5GVnsOZ8A+qSnhZpsPAR8GAbGERdMNOWrDYqj0/VYQt7sQ==} + cpu: [arm64] + os: [android] + + '@oxc-node/core-darwin-arm64@0.0.35': + resolution: {integrity: sha512-aeEG/a1zj8pA6GC0P7NypzdDY0c6AbZOdbxaGl9UQlwGgHmw6sOtq5PJO+7rCvzxUKPxBH9VZOVg0laFcncIFw==} + cpu: [arm64] + os: [darwin] + + '@oxc-node/core-darwin-x64@0.0.35': + resolution: {integrity: sha512-MtxGaUR2LBcUmqINyxzSYdx5om9KlFjyvN8cx/NizH6U5nYs7Wh/XAIoGpcQmkK2snT1FsgJeGR9L01Q1oqmng==} + cpu: [x64] + os: [darwin] + + '@oxc-node/core-freebsd-x64@0.0.35': + resolution: {integrity: sha512-xsZNqMeavNxi/WTxaQMZj6walhj7skCu36yff5q0RjsV7Dzp3RQ/SYK1t3ydw3cwPz2oZCx0AukAYJAj4i9vdw==} + cpu: [x64] + os: [freebsd] + + '@oxc-node/core-linux-arm-gnueabihf@0.0.35': + resolution: {integrity: sha512-F+d948mEzQMvcv0BQO3NN4DP0jsJwvvD5VGCFcR2mFa/z6L7UuRkS8yOKnP7tUfZjri7BwxXn37Szj0ZY7pEPA==} + cpu: [arm] + os: [linux] + + '@oxc-node/core-linux-arm64-gnu@0.0.35': + resolution: {integrity: sha512-wKbAstp6Ztq5UVBf/csnMTPMef+wGsrNykJCAtJuO/l88Okm4jn6wnhudeD3hf/426Vw93txBS8veqN2JSb6fg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-node/core-linux-arm64-musl@0.0.35': + resolution: {integrity: sha512-IdLaYnFrDGRICQ86AoEQEv5Rfo//knhg4g9ABy7QE3C231C3YpbgwtY7YH7Qv+xHDuUHnTNo8Lo/iraSIY3tQA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-node/core-linux-ppc64-gnu@0.0.35': + resolution: {integrity: sha512-yxfWpG2as+al6G9epDvFk8AX1UWy76WlwCP3pUGtpEUGuoAO63JAHUMDSXv1sSd1YatJmRJ75ptexU6c/xMdXg==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-node/core-linux-s390x-gnu@0.0.35': + resolution: {integrity: sha512-nH3mnP6ger1i4LaroWhvtk3coquNYBJD9eqG3OEuJEFGo1Ao80irFcFoktQCLLq47uomYuNQxNJw5covYNHvLw==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-node/core-linux-x64-gnu@0.0.35': + resolution: {integrity: sha512-2VKErkkTxLViK/8xbdRoQ9+sid8ZGRROLkcmMtrggjQLU69EhL0wioUVztnDVjHfOPAN17lEAN7tUgxz+PAxCg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-node/core-linux-x64-musl@0.0.35': + resolution: {integrity: sha512-QDDZYWMbwB/1uyn0BPMYeqT6miWQBljzLCYESmsVcaHOps204yKHI1Ezp79n2BiYEghhu9RPWrOd4wZ7+Gqa7Q==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-node/core-openharmony-arm64@0.0.35': + resolution: {integrity: sha512-ihb0W8mc0iM9SpfFwj9xY/1gVAPv2y7fGuW2w4jWOICCY2enJ8GnY2N9eYloPkHd2/2+S87M63H998psVZQquQ==} + cpu: [arm64] + os: [openharmony] + + '@oxc-node/core-wasm32-wasi@0.0.35': + resolution: {integrity: sha512-GoT1X1Rw3MXbvU25rsqT6gLhl9AKBdLe1ss6pVHxzps0Va6qrSD/2H4alGglUX+qccKcw0kCgJbPKJphM/0CrQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-node/core-win32-arm64-msvc@0.0.35': + resolution: {integrity: sha512-ObSjUyRd5md+hKg4j8ufhjaeXHGm4f+9cz1y20mOHr/HOkBIY6CNoPM7x5JEzZNerVZ9Ye62G6t6HNQZttBjsg==} + cpu: [arm64] + os: [win32] + + '@oxc-node/core-win32-ia32-msvc@0.0.35': + resolution: {integrity: sha512-ZE7/di30tfhh/2ItgcZim4aPLw1ve+TQrp6oJSqMRyYjq0k1AwFrxIqICbaAG9sk79ap9Sy1icFMfFgSkhnABQ==} + cpu: [ia32] + os: [win32] + + '@oxc-node/core-win32-x64-msvc@0.0.35': + resolution: {integrity: sha512-9OyyjY/ECi1icwq32baG0Uct7RuAHbVxzGDffJzNhRtBABpQiIQauoaVuYiSlNecqnA8qFYxh2wxbKaVlsR1YA==} + cpu: [x64] + os: [win32] + + '@oxc-node/core@0.0.35': + resolution: {integrity: sha512-PV46QRDI2wCDdaPzppEh4UfzFmmpSt+1dX32ooq8RWb0BuWX24+LKYicAmSrsk1ls8JRSkAqiWrjrYFHIGozGg==} + + '@oxc-parser/binding-android-arm-eabi@0.119.0': + resolution: {integrity: sha512-e0ii/Tqwk5pAHZRY+ZyXOdKHNRNmE+dvTGQZ7xQ5XPH2Am59aktD30QvfcfwItGhNTLCj/5TYGH5RHvmvqaILQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.119.0': + resolution: {integrity: sha512-ha0xQpiStuoBv7HGazNKQWa6IRxri2+PpeojdAyBGnHGzfioA1GcStNGEGOyXvF+OxDfWvPuw5QiRYRUMtmgbQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.119.0': + resolution: {integrity: sha512-h/AIi5jfQz9WQUJJkkkHeXNYMhPtR72qnYZt0ZpM/LvlH/wpI5QkCPi7MWjjyY+m0JDorIXJyfOfccn8SbNSxQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.119.0': + resolution: {integrity: sha512-15RwS/AawrgognvWsonI2eLKI5BqO0FzrpYXnzROysSR0x5RYsCc3UMFBwB1ph0UFFQzJy3ZbHHxfxp8RGr5Xg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.119.0': + resolution: {integrity: sha512-iKaayTIDqEj0yyNPL+0t/spNAxMv7O32uY4eu/ir8BvFNgavoRmN8uqxRj8sxQDle89N/1Iw0dgRjS3tiWrqlA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.119.0': + resolution: {integrity: sha512-PDoOaOx8YWoxy19WNeMs6kOE0uFSb5EtA64Ye0wSp6sQpe+l8Gd+yjX2L+yNwd5MpDOvOy8KToa2bqCV4pf6iQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.119.0': + resolution: {integrity: sha512-AVxZ5Eo5squsUhpjnkCYuH20t5FCGV3HAP9UOKLxJQkmZW3kJvBGbfpH75ABxRrE2kGqmJW5FmS980u8v9Cepw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.119.0': + resolution: {integrity: sha512-9vfdyT9gczSeSwsEkBHVjigI8SWo3iB9zxEzL+YMBUrN0ftCUkKQr27DaDZK4/cQ80t6KRB+g9sUmT2T2AGnOQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-arm64-musl@0.119.0': + resolution: {integrity: sha512-7BOq/tjSrtnp/ihw615uGcxMY3iya2qvVtwm15h2NvBZ6Jje+PC1GSUBOLfqGKJbUr9riSVV//a4iNhHI48Qdw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-ppc64-gnu@0.119.0': + resolution: {integrity: sha512-PQIrLJwoAaNyNSWBF+2SSgv44Jp+xpKVUA+8+PuoMhyBQ6lFSbQdaxewdn11i3heTFMYd2xF339HWax4S6MPVA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-gnu@0.119.0': + resolution: {integrity: sha512-spNh4YhT9K+Ya5hr6NmI1MazKSKORD8u5/06hFbzTslLnmmxiGaLqJXhNKIYUH39ne/JD5rkoRkUcOB2/LpC3A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-musl@0.119.0': + resolution: {integrity: sha512-hFoCTRxSJAcrNBYVlgNDDQq6LyJLYyhnJDlPtK/mWrPYS3x5/fUR9jc6wo5VyxKIL/0dDJBBWp19v81q9heU/A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-s390x-gnu@0.119.0': + resolution: {integrity: sha512-bl7jHJZq3W5tYEvKG3yWZTUKTNb0/BtyYSnfMIrQ7t8hajCH4i1g0q+14s0KmQQl1UHxIX/Gx/Ps6e92qJQJmQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-gnu@0.119.0': + resolution: {integrity: sha512-m+DE7NhJIEGp4efSJnNfRf3swT25rbZ1FTIihV+pOLTI+k5yNguxvqT338mNu61OVSx0BfpV8QlO2nz43GR/Zg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-musl@0.119.0': + resolution: {integrity: sha512-pHhnXZHUfd5pYzFLQfvx1DH2HY+L8DPZeh6SsQrsmoaODm1+j8VPeWLwGSrXQSz5f1kfT/mnzm1iNLVOGIeuaw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-openharmony-arm64@0.119.0': + resolution: {integrity: sha512-8Fthv9nOec0hQLX16yjYyYIU+u8ZFuQojdQ3vNgXN+PcqI/bDohGgCATrxO69gLf0IzkyOmKmurXOQCYK8BYpQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-parser/binding-wasm32-wasi@0.119.0': + resolution: {integrity: sha512-KpOU6fLqevFDP6ndkgE4BPoceELM4bOsEsAXjpe+FKYuUyEzHssYPBmxouGpXDQeAeWTBIjosw5yElLMRPuccw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-parser/binding-win32-arm64-msvc@0.119.0': + resolution: {integrity: sha512-omtTgAKIl6GQ40nG+wAWN8xMJLNtfmTdd0+wMIcrw1shX9y5TntAVIuiay3Du0wvUK9sgMpL07HYNphgHeZS0A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-ia32-msvc@0.119.0': + resolution: {integrity: sha512-+0kqoCfv4WFP3e4BqcVEtf1moUuG9Zv5lo1aKcw1JakqJo008TGG+C2LnVM4QucGSZVQ/Ii/H5XCvrRbkeLQfA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.119.0': + resolution: {integrity: sha512-5kaKmBHD+OQjZzGAQQ9n8jWNvCRxu3MjElAjkCqsS3i2wiN3hqHlOPKwGDydYiB1gKdeYGlTjRYtuF4gBLDSxQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@oxc-project/runtime@0.115.0': resolution: {integrity: sha512-Rg8Wlt5dCbXhQnsXPrkOjL1DTSvXLgb2R/KYfnf1/K+R0k6UMLEmbQXPM+kwrWqSmWA2t0B1EtHy2/3zikQpvQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -402,6 +909,9 @@ packages: '@oxc-project/types@0.115.0': resolution: {integrity: sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw==} + '@oxc-project/types@0.119.0': + resolution: {integrity: sha512-9SCGhodOxEicD2kblitu34fGHcpmqgI3beYw/E22ehVLHzccHRFH91NmKt0MhZEaAwLpei6OOA9aB6Vuks9qAg==} + '@oxfmt/binding-android-arm-eabi@0.40.0': resolution: {integrity: sha512-S6zd5r1w/HmqR8t0CTnGjFTBLDq2QKORPwriCHxo4xFNuhmOTABGjPaNvCJJVnrKBLsohOeiDX3YqQfJPF+FXw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -778,9 +1288,65 @@ packages: cpu: [x64] os: [win32] + '@rolldown/plugin-babel@0.2.1': + resolution: {integrity: sha512-pHDVHqFv26JNC8I500JZ0H4h1kvSyiE3V9gjEO9pRAgD1KrIdJvcHCokV6f7gG7Rx4vMOD11V8VUOpqdyGbKBw==} + engines: {node: '>=22.12.0 || ^24.0.0'} + peerDependencies: + '@babel/core': ^7.29.0 || ^8.0.0-rc.1 + '@babel/plugin-transform-runtime': ^7.29.0 || ^8.0.0-rc.1 + '@babel/runtime': ^7.27.0 || ^8.0.0-rc.1 + rolldown: ^1.0.0-rc.5 + vite: ^8.0.0 + peerDependenciesMeta: + '@babel/plugin-transform-runtime': + optional: true + '@babel/runtime': + optional: true + vite: + optional: true + + '@rolldown/plugin-babel@file:packages/babel': + resolution: {directory: packages/babel, type: directory} + engines: {node: '>=22.12.0 || ^24.0.0'} + peerDependencies: + '@babel/core': ^7.29.0 || ^8.0.0-rc.1 + '@babel/plugin-transform-runtime': ^7.29.0 || ^8.0.0-rc.1 + '@babel/runtime': ^7.27.0 || ^8.0.0-rc.1 + rolldown: ^1.0.0-rc.5 + vite: ^8.0.0 + peerDependenciesMeta: + '@babel/plugin-transform-runtime': + optional: true + '@babel/runtime': + optional: true + vite: + optional: true + + '@rolldown/pluginutils@1.0.0-rc.7': + resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} + '@rolldown/pluginutils@1.0.0-rc.9': resolution: {integrity: sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw==} + '@rollup/plugin-swc@0.4.0': + resolution: {integrity: sha512-oAtqXa8rOl7BOK1Rz3rRxI+LIL53S9SqO2KSq2UUUzWgOgXg6492Jh5mL2mv/f9cpit8zFWdwILuVeozZ0C8mg==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@swc/core': ^1.3.0 + rollup: ^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} @@ -799,6 +1365,88 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@swc/core-darwin-arm64@1.15.18': + resolution: {integrity: sha512-+mIv7uBuSaywN3C9LNuWaX1jJJ3SKfiJuE6Lr3bd+/1Iv8oMU7oLBjYMluX1UrEPzwN2qCdY6Io0yVicABoCwQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.15.18': + resolution: {integrity: sha512-wZle0eaQhnzxWX5V/2kEOI6Z9vl/lTFEC6V4EWcn+5pDjhemCpQv9e/TDJ0GIoiClX8EDWRvuZwh+Z3dhL1NAg==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.15.18': + resolution: {integrity: sha512-ao61HGXVqrJFHAcPtF4/DegmwEkVCo4HApnotLU8ognfmU8x589z7+tcf3hU+qBiU1WOXV5fQX6W9Nzs6hjxDw==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.15.18': + resolution: {integrity: sha512-3xnctOBLIq3kj8PxOCgPrGjBLP/kNOddr6f5gukYt/1IZxsITQaU9TDyjeX6jG+FiCIHjCuWuffsyQDL5Ew1bg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@swc/core-linux-arm64-musl@1.15.18': + resolution: {integrity: sha512-0a+Lix+FSSHBSBOA0XznCcHo5/1nA6oLLjcnocvzXeqtdjnPb+SvchItHI+lfeiuj1sClYPDvPMLSLyXFaiIKw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@swc/core-linux-x64-gnu@1.15.18': + resolution: {integrity: sha512-wG9J8vReUlpaHz4KOD/5UE1AUgirimU4UFT9oZmupUDEofxJKYb1mTA/DrMj0s78bkBiNI+7Fo2EgPuvOJfuAA==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@swc/core-linux-x64-musl@1.15.18': + resolution: {integrity: sha512-4nwbVvCphKzicwNWRmvD5iBaZj8JYsRGa4xOxJmOyHlMDpsvvJ2OR2cODlvWyGFH6BYL1MfIAK3qph3hp0Az6g==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@swc/core-win32-arm64-msvc@1.15.18': + resolution: {integrity: sha512-zk0RYO+LjiBCat2RTMHzAWaMky0cra9loH4oRrLKLLNuL+jarxKLFDA8xTZWEkCPLjUTwlRN7d28eDLLMgtUcQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.15.18': + resolution: {integrity: sha512-yVuTrZ0RccD5+PEkpcLOBAuPbYBXS6rslENvIXfvJGXSdX5QGi1ehC4BjAMl5FkKLiam4kJECUI0l7Hq7T1vwg==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.15.18': + resolution: {integrity: sha512-7NRmE4hmUQNCbYU3Hn9Tz57mK9Qq4c97ZS+YlamlK6qG9Fb5g/BB3gPDe0iLlJkns/sYv2VWSkm8c3NmbEGjbg==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.15.18': + resolution: {integrity: sha512-z87aF9GphWp//fnkRsqvtY+inMVPgYW3zSlXH1kJFvRT5H/wiAn+G32qW5l3oEk63KSF1x3Ov0BfHCObAmT8RA==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '>=0.5.17' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/plugin-emotion@14.7.0': + resolution: {integrity: sha512-RwYrsxia8GKh2qLHWwymcfCeP6C5gAkssB2YtBRhP/qlKCxXYfv808buEXkCYvyGIY+bN3XziKXCuAi+waA5pQ==} + + '@swc/types@0.1.25': + resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==} + '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -820,12 +1468,26 @@ packages: '@types/node@22.19.15': resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==} + '@types/node@24.12.0': + resolution: {integrity: sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==} + '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + '@types/picomatch@4.0.2': resolution: {integrity: sha512-qHHxQ+P9PysNEGbALT8f8YOSHW0KJu6l2xU8DYY0fu/EmGxXdVnuTLvFUvBgPJMSqXq29SYHveejeAha+4AYgA==} + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260314.1': resolution: {integrity: sha512-PlDvhms3QsLOkXNnCoQGJXjcZG7G5QMnhpSOmtO5toZ93VhU1qaUdvDlxtbacCYxGXcs6qZiJnGUgcedfqn5cg==} cpu: [arm64] @@ -865,6 +1527,19 @@ packages: resolution: {integrity: sha512-XOVmR59UL6r2dmcdZD6SV4nhX5u4e34onMv7XSz9zKBp9mb8/v8kKPgGy9adp9at24FpAeBYNN5HKIViSeHtpQ==} hasBin: true + '@vitejs/plugin-react@6.0.1': + resolution: {integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + '@vitejs/release-scripts@1.6.0': resolution: {integrity: sha512-XV+w22Fvn+wqDtEkz8nQIJzvmRVSh90c2xvOO7cX9fkX8+39ZJpYRiXDIRJG1JRnF8khm1rHjulid+l+khc7TQ==} @@ -897,6 +1572,11 @@ packages: '@vitest/utils@4.1.0': resolution: {integrity: sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==} + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + ansis@4.2.0: resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} engines: {node: '>=14'} @@ -912,6 +1592,10 @@ packages: resolution: {integrity: sha512-trmleAnZ2PxN/loHWVhhx1qeOHSRXq4TDsBBxq3GqeJitfk3+jTQ+v/C1km/KYq9M7wKqCewMh+/NAvVH7m+bw==} engines: {node: '>=20.19.0'} + babel-plugin-macros@3.1.0: + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} + baseline-browser-mapping@2.10.0: resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} engines: {node: '>=6.0.0'} @@ -929,6 +1613,10 @@ packages: resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} engines: {node: '>=20.19.0'} + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + caniuse-lite@1.0.30001770: resolution: {integrity: sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==} @@ -939,6 +1627,9 @@ packages: compare-func@2.0.0: resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + conventional-changelog-conventionalcommits@9.1.0: resolution: {integrity: sha512-MnbEysR8wWa8dAEvbj5xcBgJKQlX/m0lhS8DsyAAWDHdfs2faDJxTgzRYlRYpXSe7UiKrIIlB4TrBKU9q9DgkA==} engines: {node: '>=18'} @@ -966,13 +1657,32 @@ packages: engines: {node: '>=18'} hasBin: true + convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} @@ -1000,6 +1710,9 @@ packages: resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} engines: {node: '>=14'} + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + es-module-lexer@2.0.0: resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} @@ -1012,6 +1725,13 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -1043,11 +1763,17 @@ packages: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} + find-root@1.1.0: + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -1068,6 +1794,13 @@ packages: engines: {node: '>=0.4.7'} hasBin: true + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + hookable@6.0.1: resolution: {integrity: sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw==} @@ -1083,6 +1816,10 @@ packages: resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} engines: {node: '>=18.18.0'} + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + import-meta-resolve@4.2.0: resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} @@ -1090,6 +1827,13 @@ packages: resolution: {integrity: sha512-B6Lc2s6yApwnD2/pMzFh/d5AVjdsDXjgkeJ766FmFuJELIGHNycKRj+l3A39yZPM4CchqNCB4RITEAYB1KUM6A==} engines: {node: '>=20.19.0'} + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + is-obj@2.0.0: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} @@ -1116,11 +1860,17 @@ packages: js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} hasBin: true + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -1204,13 +1954,22 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@7.18.3: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} + magic-regexp@0.10.0: + resolution: {integrity: sha512-Uly1Bu4lO1hwHUW0CQeSWuRtzCMNO00CmXtS8N6fyvB3B979GOEEeAkiTUDsmbYLAbvpUS/Kt5c4ibosAzVyVg==} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -1228,10 +1987,16 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -1262,6 +2027,15 @@ packages: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} + oxc-parser@0.119.0: + resolution: {integrity: sha512-fNiKvO0ZHSUmINQlVY2It+vGbHxCvhpqJi0rZYFFOESoOy3fs5E4erKYGZtB/J1aULkjtY06aWNil4JxMsKXGg==} + engines: {node: ^20.19.0 || >=22.12.0} + + oxc-walker@0.7.0: + resolution: {integrity: sha512-54B4KUhrzbzc4sKvKwVYm7E2PgeROpGba0/2nlNZMqfDyca+yOor5IMb4WLGBatGDT0nkzYdYuzylg7n3YfB7A==} + peerDependencies: + oxc-parser: '>=0.98.0' + oxfmt@0.40.0: resolution: {integrity: sha512-g0C3I7xUj4b4DcagevM9kgH6+pUHytikxUcn3/VUkvzTNaaXBeyZqb7IBsHwojeXm4mTBEC/aBjBTMVUkZwWUQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1284,6 +2058,14 @@ packages: package-manager-detector@1.6.0: resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + parse-ms@4.0.0: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} @@ -1296,6 +2078,13 @@ packages: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} engines: {node: '>=12'} + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -1306,6 +2095,23 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + playwright-chromium@1.58.2: + resolution: {integrity: sha512-SCoQ3hjBs7FfO46CoOtgAUg77BuYwCni1bzQgm47IUyLBTipnGkLxLnaUNRKXvPYO4hAyt8++Z6wVShVnhrzmw==} + engines: {node: '>=18'} + hasBin: true + + playwright-core@1.58.2: + resolution: {integrity: sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==} + engines: {node: '>=18'} + hasBin: true + postcss@8.5.8: resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} @@ -1326,9 +2132,34 @@ packages: quansync@1.0.0: resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + react-dom@19.2.4: + resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + peerDependencies: + react: ^19.2.4 + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react@19.2.4: + resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + engines: {node: '>=0.10.0'} + + regexp-tree@0.1.27: + resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} + hasBin: true + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + rolldown-plugin-dts@0.22.5: resolution: {integrity: sha512-M/HXfM4cboo+jONx9Z0X+CUf3B5tCi7ni+kR5fUW50Fp9AlZk0oVLesibGWgCXDKFp5lpgQ9yhKoImUFjl3VZw==} engines: {node: '>=20.19.0'} @@ -1348,6 +2179,15 @@ packages: vue-tsc: optional: true + rolldown-string@0.3.0: + resolution: {integrity: sha512-qGhBPNSv/27uzFBQdO+Cs4YAXC/1PKznD7Jz5Fl7NIAIlteuTPBTBWBhCpJ2AFTqLsoKvvUr7wSjqSUip0Fkpg==} + engines: {node: '>=20.19.0'} + peerDependencies: + rolldown: '*' + peerDependenciesMeta: + rolldown: + optional: true + rolldown@1.0.0-rc.9: resolution: {integrity: sha512-9EbgWge7ZH+yqb4d2EnELAntgPTWbfL8ajiTW+SyhJEC4qhBbkCKbqFV4Ge4zmu5ziQuVbWxb/XwLZ+RIO7E8Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1357,6 +2197,13 @@ packages: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + semver@7.7.4: resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} @@ -1380,10 +2227,18 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + smob@1.6.1: + resolution: {integrity: sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==} + engines: {node: '>=20.0.0'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -1414,6 +2269,13 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} + stylis@4.2.0: + resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -1468,6 +2330,12 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + type-level-regexp@0.1.17: + resolution: {integrity: sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg==} + + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + uglify-js@3.19.3: resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} engines: {node: '>=0.8.0'} @@ -1479,10 +2347,17 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + unicorn-magic@0.3.0: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} + engines: {node: '>=18.12.0'} + unrun@0.2.32: resolution: {integrity: sha512-opd3z6791rf281JdByf0RdRQrpcc7WyzqittqIXodM/5meNWdTwrVxeyzbaCp4/Rgls/um14oUaif1gomO8YGg==} engines: {node: '>=20.19.0'} @@ -1584,6 +2459,9 @@ packages: resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==} engines: {node: 20 || >=22} + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -1597,19 +2475,54 @@ packages: wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + yoctocolors@2.1.2: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} snapshots: + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/code-frame@8.0.0-rc.2': dependencies: '@babel/helper-validator-identifier': 8.0.0-rc.2 js-tokens: 10.0.0 + '@babel/compat-data@7.29.0': {} + '@babel/compat-data@8.0.0-rc.2': {} + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/core@8.0.0-rc.2': dependencies: '@babel/code-frame': 8.0.0-rc.2 @@ -1629,6 +2542,14 @@ snapshots: obug: 2.1.1 semver: 7.7.4 + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + '@babel/generator@8.0.0-rc.2': dependencies: '@babel/parser': 8.0.0-rc.2 @@ -1642,6 +2563,14 @@ snapshots: dependencies: '@babel/types': 8.0.0-rc.2 + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + '@babel/helper-compilation-targets@8.0.0-rc.2': dependencies: '@babel/compat-data': 8.0.0-rc.2 @@ -1661,6 +2590,8 @@ snapshots: '@babel/traverse': 8.0.0-rc.2 semver: 7.7.4 + '@babel/helper-globals@7.28.0': {} + '@babel/helper-globals@8.0.0-rc.2': {} '@babel/helper-member-expression-to-functions@8.0.0-rc.2': @@ -1668,15 +2599,36 @@ snapshots: '@babel/traverse': 8.0.0-rc.2 '@babel/types': 8.0.0-rc.2 + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + '@babel/helper-module-imports@8.0.0-rc.2': dependencies: '@babel/traverse': 8.0.0-rc.2 '@babel/types': 8.0.0-rc.2 + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + '@babel/helper-optimise-call-expression@8.0.0-rc.2': dependencies: '@babel/types': 8.0.0-rc.2 + '@babel/helper-plugin-utils@8.0.0-rc.2(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + optional: true + '@babel/helper-plugin-utils@8.0.0-rc.2(@babel/core@8.0.0-rc.2)': dependencies: '@babel/core': 8.0.0-rc.2 @@ -1693,17 +2645,32 @@ snapshots: '@babel/traverse': 8.0.0-rc.2 '@babel/types': 8.0.0-rc.2 + '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-string-parser@8.0.0-rc.2': {} + '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@8.0.0-rc.2': {} + '@babel/helper-validator-option@7.27.1': {} + '@babel/helper-validator-option@8.0.0-rc.2': {} + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + '@babel/helpers@8.0.0-rc.2': dependencies: '@babel/template': 8.0.0-rc.2 '@babel/types': 8.0.0-rc.2 + '@babel/parser@7.29.0': + dependencies: + '@babel/types': 7.29.0 + '@babel/parser@8.0.0-rc.2': dependencies: '@babel/types': 8.0.0-rc.2 @@ -1720,20 +2687,47 @@ snapshots: '@babel/core': 8.0.0-rc.2 '@babel/helper-plugin-utils': 8.0.0-rc.2(@babel/core@8.0.0-rc.2) + '@babel/plugin-transform-runtime@8.0.0-rc.2(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 8.0.0-rc.2 + '@babel/helper-plugin-utils': 8.0.0-rc.2(@babel/core@7.29.0) + optional: true + '@babel/plugin-transform-runtime@8.0.0-rc.2(@babel/core@8.0.0-rc.2)': dependencies: '@babel/core': 8.0.0-rc.2 '@babel/helper-module-imports': 8.0.0-rc.2 '@babel/helper-plugin-utils': 8.0.0-rc.2(@babel/core@8.0.0-rc.2) + '@babel/runtime@7.28.6': {} + '@babel/runtime@8.0.0-rc.2': {} + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@babel/template@8.0.0-rc.2': dependencies: '@babel/code-frame': 8.0.0-rc.2 '@babel/parser': 8.0.0-rc.2 '@babel/types': 8.0.0-rc.2 + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + '@babel/traverse@8.0.0-rc.2': dependencies: '@babel/code-frame': 8.0.0-rc.2 @@ -1744,6 +2738,11 @@ snapshots: '@babel/types': 8.0.0-rc.2 obug: 2.1.1 + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/types@8.0.0-rc.2': dependencies: '@babel/helper-string-parser': 8.0.0-rc.2 @@ -1755,24 +2754,107 @@ snapshots: '@simple-libs/stream-utils': 1.1.0 semver: 7.7.4 optionalDependencies: - conventional-commits-filter: 5.0.0 - conventional-commits-parser: 6.2.1 + conventional-commits-filter: 5.0.0 + conventional-commits-parser: 6.2.1 + + '@emnapi/core@1.8.1': + dependencies: + '@emnapi/wasi-threads': 1.1.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.1.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emotion/babel-plugin@11.13.5': + dependencies: + '@babel/helper-module-imports': 7.28.6 + '@babel/runtime': 7.28.6 + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/serialize': 1.3.3 + babel-plugin-macros: 3.1.0 + convert-source-map: 1.9.0 + escape-string-regexp: 4.0.0 + find-root: 1.1.0 + source-map: 0.5.7 + stylis: 4.2.0 + transitivePeerDependencies: + - supports-color + + '@emotion/cache@11.14.0': + dependencies: + '@emotion/memoize': 0.9.0 + '@emotion/sheet': 1.4.0 + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + stylis: 4.2.0 + + '@emotion/hash@0.9.2': {} + + '@emotion/is-prop-valid@1.4.0': + dependencies: + '@emotion/memoize': 0.9.0 + + '@emotion/memoize@0.9.0': {} + + '@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4)': + dependencies: + '@babel/runtime': 7.28.6 + '@emotion/babel-plugin': 11.13.5 + '@emotion/cache': 11.14.0 + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.4) + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + hoist-non-react-statics: 3.3.2 + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 + transitivePeerDependencies: + - supports-color - '@emnapi/core@1.8.1': + '@emotion/serialize@1.3.3': dependencies: - '@emnapi/wasi-threads': 1.1.0 - tslib: 2.8.1 - optional: true + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/unitless': 0.10.0 + '@emotion/utils': 1.4.2 + csstype: 3.2.3 - '@emnapi/runtime@1.8.1': + '@emotion/sheet@1.4.0': {} + + '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4)': dependencies: - tslib: 2.8.1 - optional: true + '@babel/runtime': 7.28.6 + '@emotion/babel-plugin': 11.13.5 + '@emotion/is-prop-valid': 1.4.0 + '@emotion/react': 11.14.0(@types/react@19.2.14)(react@19.2.4) + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.4) + '@emotion/utils': 1.4.2 + react: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 + transitivePeerDependencies: + - supports-color - '@emnapi/wasi-threads@1.1.0': + '@emotion/unitless@0.10.0': {} + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.2.4)': dependencies: - tslib: 2.8.1 - optional: true + react: 19.2.4 + + '@emotion/utils@1.4.2': {} + + '@emotion/weak-memoize@0.4.0': {} '@esbuild/aix-ppc64@0.27.3': optional: true @@ -1878,10 +2960,153 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true + '@oxc-node/cli@0.0.35': + dependencies: + '@oxc-node/core': 0.0.35 + + '@oxc-node/core-android-arm-eabi@0.0.35': + optional: true + + '@oxc-node/core-android-arm64@0.0.35': + optional: true + + '@oxc-node/core-darwin-arm64@0.0.35': + optional: true + + '@oxc-node/core-darwin-x64@0.0.35': + optional: true + + '@oxc-node/core-freebsd-x64@0.0.35': + optional: true + + '@oxc-node/core-linux-arm-gnueabihf@0.0.35': + optional: true + + '@oxc-node/core-linux-arm64-gnu@0.0.35': + optional: true + + '@oxc-node/core-linux-arm64-musl@0.0.35': + optional: true + + '@oxc-node/core-linux-ppc64-gnu@0.0.35': + optional: true + + '@oxc-node/core-linux-s390x-gnu@0.0.35': + optional: true + + '@oxc-node/core-linux-x64-gnu@0.0.35': + optional: true + + '@oxc-node/core-linux-x64-musl@0.0.35': + optional: true + + '@oxc-node/core-openharmony-arm64@0.0.35': + optional: true + + '@oxc-node/core-wasm32-wasi@0.0.35': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@oxc-node/core-win32-arm64-msvc@0.0.35': + optional: true + + '@oxc-node/core-win32-ia32-msvc@0.0.35': + optional: true + + '@oxc-node/core-win32-x64-msvc@0.0.35': + optional: true + + '@oxc-node/core@0.0.35': + dependencies: + pirates: 4.0.7 + optionalDependencies: + '@oxc-node/core-android-arm-eabi': 0.0.35 + '@oxc-node/core-android-arm64': 0.0.35 + '@oxc-node/core-darwin-arm64': 0.0.35 + '@oxc-node/core-darwin-x64': 0.0.35 + '@oxc-node/core-freebsd-x64': 0.0.35 + '@oxc-node/core-linux-arm-gnueabihf': 0.0.35 + '@oxc-node/core-linux-arm64-gnu': 0.0.35 + '@oxc-node/core-linux-arm64-musl': 0.0.35 + '@oxc-node/core-linux-ppc64-gnu': 0.0.35 + '@oxc-node/core-linux-s390x-gnu': 0.0.35 + '@oxc-node/core-linux-x64-gnu': 0.0.35 + '@oxc-node/core-linux-x64-musl': 0.0.35 + '@oxc-node/core-openharmony-arm64': 0.0.35 + '@oxc-node/core-wasm32-wasi': 0.0.35 + '@oxc-node/core-win32-arm64-msvc': 0.0.35 + '@oxc-node/core-win32-ia32-msvc': 0.0.35 + '@oxc-node/core-win32-x64-msvc': 0.0.35 + + '@oxc-parser/binding-android-arm-eabi@0.119.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.119.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.119.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.119.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.119.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.119.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.119.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.119.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.119.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.119.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.119.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.119.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.119.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.119.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.119.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.119.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.119.0': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.119.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.119.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.119.0': + optional: true + '@oxc-project/runtime@0.115.0': {} '@oxc-project/types@0.115.0': {} + '@oxc-project/types@0.119.0': {} + '@oxfmt/binding-android-arm-eabi@0.40.0': optional: true @@ -2067,8 +3292,43 @@ snapshots: '@rolldown/binding-win32-x64-msvc@1.0.0-rc.9': optional: true + '@rolldown/plugin-babel@0.2.1(@babel/core@8.0.0-rc.2)(@babel/plugin-transform-runtime@8.0.0-rc.2(@babel/core@8.0.0-rc.2))(@babel/runtime@8.0.0-rc.2)(rolldown@1.0.0-rc.9)(vite@8.0.0(@types/node@24.12.0)(esbuild@0.27.3))': + dependencies: + '@babel/core': 8.0.0-rc.2 + picomatch: 4.0.3 + rolldown: 1.0.0-rc.9 + optionalDependencies: + '@babel/plugin-transform-runtime': 8.0.0-rc.2(@babel/core@8.0.0-rc.2) + '@babel/runtime': 8.0.0-rc.2 + vite: 8.0.0(@types/node@24.12.0)(esbuild@0.27.3) + optional: true + + '@rolldown/plugin-babel@file:packages/babel(@babel/core@7.29.0)(@babel/plugin-transform-runtime@8.0.0-rc.2(@babel/core@7.29.0))(@babel/runtime@8.0.0-rc.2)(rolldown@1.0.0-rc.9)(vite@8.0.0(@types/node@24.12.0)(esbuild@0.27.3))': + dependencies: + '@babel/core': 7.29.0 + picomatch: 4.0.3 + rolldown: 1.0.0-rc.9 + optionalDependencies: + '@babel/plugin-transform-runtime': 8.0.0-rc.2(@babel/core@7.29.0) + '@babel/runtime': 8.0.0-rc.2 + vite: 8.0.0(@types/node@24.12.0)(esbuild@0.27.3) + + '@rolldown/pluginutils@1.0.0-rc.7': {} + '@rolldown/pluginutils@1.0.0-rc.9': {} + '@rollup/plugin-swc@0.4.0(@swc/core@1.15.18)': + dependencies: + '@rollup/pluginutils': 5.3.0 + '@swc/core': 1.15.18 + smob: 1.6.1 + + '@rollup/pluginutils@5.3.0': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + '@sec-ant/readable-stream@0.4.1': {} '@simple-libs/child-process-utils@1.0.1': @@ -2084,6 +3344,62 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@swc/core-darwin-arm64@1.15.18': + optional: true + + '@swc/core-darwin-x64@1.15.18': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.15.18': + optional: true + + '@swc/core-linux-arm64-gnu@1.15.18': + optional: true + + '@swc/core-linux-arm64-musl@1.15.18': + optional: true + + '@swc/core-linux-x64-gnu@1.15.18': + optional: true + + '@swc/core-linux-x64-musl@1.15.18': + optional: true + + '@swc/core-win32-arm64-msvc@1.15.18': + optional: true + + '@swc/core-win32-ia32-msvc@1.15.18': + optional: true + + '@swc/core-win32-x64-msvc@1.15.18': + optional: true + + '@swc/core@1.15.18': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.25 + optionalDependencies: + '@swc/core-darwin-arm64': 1.15.18 + '@swc/core-darwin-x64': 1.15.18 + '@swc/core-linux-arm-gnueabihf': 1.15.18 + '@swc/core-linux-arm64-gnu': 1.15.18 + '@swc/core-linux-arm64-musl': 1.15.18 + '@swc/core-linux-x64-gnu': 1.15.18 + '@swc/core-linux-x64-musl': 1.15.18 + '@swc/core-win32-arm64-msvc': 1.15.18 + '@swc/core-win32-ia32-msvc': 1.15.18 + '@swc/core-win32-x64-msvc': 1.15.18 + + '@swc/counter@0.1.3': {} + + '@swc/plugin-emotion@14.7.0': + dependencies: + '@swc/counter': 0.1.3 + + '@swc/types@0.1.25': + dependencies: + '@swc/counter': 0.1.3 + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 @@ -2106,10 +3422,24 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/node@24.12.0': + dependencies: + undici-types: 7.16.0 + '@types/normalize-package-data@2.4.4': {} + '@types/parse-json@4.0.2': {} + '@types/picomatch@4.0.2': {} + '@types/react-dom@19.2.3(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260314.1': optional: true @@ -2141,6 +3471,13 @@ snapshots: '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260314.1 '@typescript/native-preview-win32-x64': 7.0.0-dev.20260314.1 + '@vitejs/plugin-react@6.0.1(@rolldown/plugin-babel@0.2.1(@babel/core@8.0.0-rc.2)(@babel/plugin-transform-runtime@8.0.0-rc.2(@babel/core@8.0.0-rc.2))(@babel/runtime@8.0.0-rc.2)(rolldown@1.0.0-rc.9)(vite@8.0.0(@types/node@24.12.0)(esbuild@0.27.3)))(vite@8.0.0(@types/node@24.12.0)(esbuild@0.27.3))': + dependencies: + '@rolldown/pluginutils': 1.0.0-rc.7 + vite: 8.0.0(@types/node@24.12.0)(esbuild@0.27.3) + optionalDependencies: + '@rolldown/plugin-babel': 0.2.1(@babel/core@8.0.0-rc.2)(@babel/plugin-transform-runtime@8.0.0-rc.2(@babel/core@8.0.0-rc.2))(@babel/runtime@8.0.0-rc.2)(rolldown@1.0.0-rc.9)(vite@8.0.0(@types/node@24.12.0)(esbuild@0.27.3)) + '@vitejs/release-scripts@1.6.0(patch_hash=934d3ff41c551b1d73703bec99c2edbc3a91ec064c86cb21dad311a998354ced)(conventional-commits-filter@5.0.0)': dependencies: conventional-changelog: 7.1.1(conventional-commits-filter@5.0.0) @@ -2163,13 +3500,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.1.0(vite@8.0.0(@types/node@22.19.15)(esbuild@0.27.3))': + '@vitest/mocker@4.1.0(vite@8.0.0(@types/node@24.12.0)(esbuild@0.27.3))': dependencies: '@vitest/spy': 4.1.0 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.0(@types/node@22.19.15)(esbuild@0.27.3) + vite: 8.0.0(@types/node@24.12.0)(esbuild@0.27.3) '@vitest/pretty-format@4.1.0': dependencies: @@ -2195,6 +3532,8 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.0.3 + acorn@8.16.0: {} + ansis@4.2.0: {} array-ify@1.0.0: {} @@ -2207,6 +3546,12 @@ snapshots: estree-walker: 3.0.3 pathe: 2.0.3 + babel-plugin-macros@3.1.0: + dependencies: + '@babel/runtime': 7.28.6 + cosmiconfig: 7.1.0 + resolve: 1.22.11 + baseline-browser-mapping@2.10.0: {} birpc@4.0.0: {} @@ -2221,6 +3566,8 @@ snapshots: cac@7.0.0: {} + callsites@3.1.0: {} + caniuse-lite@1.0.30001770: {} chai@6.2.2: {} @@ -2230,6 +3577,8 @@ snapshots: array-ify: 1.0.0 dot-prop: 5.3.0 + confbox@0.1.8: {} + conventional-changelog-conventionalcommits@9.1.0: dependencies: compare-func: 2.0.0 @@ -2262,14 +3611,30 @@ snapshots: dependencies: meow: 13.2.0 + convert-source-map@1.9.0: {} + convert-source-map@2.0.0: {} + cosmiconfig@7.1.0: + dependencies: + '@types/parse-json': 4.0.2 + import-fresh: 3.3.1 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + defu@6.1.4: {} detect-libc@2.1.2: {} @@ -2284,6 +3649,10 @@ snapshots: empathic@2.0.0: {} + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + es-module-lexer@2.0.0: {} esbuild@0.27.3: @@ -2318,6 +3687,10 @@ snapshots: escalade@3.2.0: {} + escape-string-regexp@4.0.0: {} + + estree-walker@2.0.2: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 @@ -2363,9 +3736,13 @@ snapshots: dependencies: is-unicode-supported: 2.1.0 + find-root@1.1.0: {} + fsevents@2.3.3: optional: true + function-bind@1.1.2: {} + gensync@1.0.0-beta.2: {} get-stream@8.0.1: {} @@ -2388,6 +3765,14 @@ snapshots: optionalDependencies: uglify-js: 3.19.3 + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + hookable@6.0.1: {} hosted-git-info@8.1.0: @@ -2398,10 +3783,21 @@ snapshots: human-signals@8.0.1: {} + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + import-meta-resolve@4.2.0: {} import-without-cache@0.2.5: {} + is-arrayish@0.2.1: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + is-obj@2.0.0: {} is-plain-obj@4.1.0: {} @@ -2416,8 +3812,12 @@ snapshots: js-tokens@10.0.0: {} + js-tokens@4.0.0: {} + jsesc@3.1.0: {} + json-parse-even-better-errors@2.3.1: {} + json5@2.2.3: {} kleur@3.0.3: {} @@ -2471,10 +3871,26 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + lines-and-columns@1.2.4: {} + lru-cache@10.4.3: {} + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + lru-cache@7.18.3: {} + magic-regexp@0.10.0: + dependencies: + estree-walker: 3.0.3 + magic-string: 0.30.21 + mlly: 1.8.0 + regexp-tree: 0.1.27 + type-level-regexp: 0.1.17 + ufo: 1.6.3 + unplugin: 2.3.11 + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2487,8 +3903,17 @@ snapshots: minimist@1.2.8: {} + mlly@1.8.0: + dependencies: + acorn: 8.16.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.3 + mri@1.2.0: {} + ms@2.1.3: {} + nanoid@3.3.11: {} neo-async@2.6.2: {} @@ -2516,6 +3941,36 @@ snapshots: dependencies: mimic-fn: 4.0.0 + oxc-parser@0.119.0: + dependencies: + '@oxc-project/types': 0.119.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.119.0 + '@oxc-parser/binding-android-arm64': 0.119.0 + '@oxc-parser/binding-darwin-arm64': 0.119.0 + '@oxc-parser/binding-darwin-x64': 0.119.0 + '@oxc-parser/binding-freebsd-x64': 0.119.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.119.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.119.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.119.0 + '@oxc-parser/binding-linux-arm64-musl': 0.119.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.119.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.119.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.119.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.119.0 + '@oxc-parser/binding-linux-x64-gnu': 0.119.0 + '@oxc-parser/binding-linux-x64-musl': 0.119.0 + '@oxc-parser/binding-openharmony-arm64': 0.119.0 + '@oxc-parser/binding-wasm32-wasi': 0.119.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.119.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.119.0 + '@oxc-parser/binding-win32-x64-msvc': 0.119.0 + + oxc-walker@0.7.0(oxc-parser@0.119.0): + dependencies: + magic-regexp: 0.10.0 + oxc-parser: 0.119.0 + oxfmt@0.40.0: dependencies: tinypool: 2.1.0 @@ -2574,18 +4029,47 @@ snapshots: package-manager-detector@1.6.0: {} + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.0 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + parse-ms@4.0.0: {} path-key@3.1.1: {} path-key@4.0.0: {} + path-parse@1.0.7: {} + + path-type@4.0.0: {} + pathe@2.0.3: {} picocolors@1.1.1: {} picomatch@4.0.3: {} + pirates@4.0.7: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + playwright-chromium@1.58.2: + dependencies: + playwright-core: 1.58.2 + + playwright-core@1.58.2: {} + postcss@8.5.8: dependencies: nanoid: 3.3.11 @@ -2610,8 +4094,27 @@ snapshots: quansync@1.0.0: {} + react-dom@19.2.4(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + + react-is@16.13.1: {} + + react@19.2.4: {} + + regexp-tree@0.1.27: {} + + resolve-from@4.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + rolldown-plugin-dts@0.22.5(@typescript/native-preview@7.0.0-dev.20260314.1)(rolldown@1.0.0-rc.9): dependencies: '@babel/generator': 8.0.0-rc.2 @@ -2629,6 +4132,12 @@ snapshots: transitivePeerDependencies: - oxc-resolver + rolldown-string@0.3.0(rolldown@1.0.0-rc.9): + dependencies: + magic-string: 0.30.21 + optionalDependencies: + rolldown: 1.0.0-rc.9 + rolldown@1.0.0-rc.9: dependencies: '@oxc-project/types': 0.115.0 @@ -2654,6 +4163,10 @@ snapshots: dependencies: mri: 1.2.0 + scheduler@0.27.0: {} + + semver@6.3.1: {} + semver@7.7.4: {} shebang-command@2.0.0: @@ -2668,8 +4181,12 @@ snapshots: sisteransi@1.0.5: {} + smob@1.6.1: {} + source-map-js@1.2.1: {} + source-map@0.5.7: {} + source-map@0.6.1: {} spdx-correct@3.2.0: @@ -2694,6 +4211,10 @@ snapshots: strip-final-newline@4.0.0: {} + stylis@4.2.0: {} + + supports-preserve-symlinks-flag@1.0.0: {} + tinybench@2.9.0: {} tinyexec@1.0.2: {} @@ -2739,6 +4260,10 @@ snapshots: tslib@2.8.1: optional: true + type-level-regexp@0.1.17: {} + + ufo@1.6.3: {} + uglify-js@3.19.3: optional: true @@ -2749,8 +4274,17 @@ snapshots: undici-types@6.21.0: {} + undici-types@7.16.0: {} + unicorn-magic@0.3.0: {} + unplugin@2.3.11: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.16.0 + picomatch: 4.0.3 + webpack-virtual-modules: 0.6.2 + unrun@0.2.32: dependencies: rolldown: 1.0.0-rc.9 @@ -2779,10 +4313,23 @@ snapshots: esbuild: 0.27.3 fsevents: 2.3.3 - vitest@4.1.0(@types/node@22.19.15)(vite@8.0.0(@types/node@22.19.15)(esbuild@0.27.3)): + vite@8.0.0(@types/node@24.12.0)(esbuild@0.27.3): + dependencies: + '@oxc-project/runtime': 0.115.0 + lightningcss: 1.32.0 + picomatch: 4.0.3 + postcss: 8.5.8 + rolldown: 1.0.0-rc.9 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.12.0 + esbuild: 0.27.3 + fsevents: 2.3.3 + + vitest@4.1.0(@types/node@24.12.0)(vite@8.0.0(@types/node@24.12.0)(esbuild@0.27.3)): dependencies: '@vitest/expect': 4.1.0 - '@vitest/mocker': 4.1.0(vite@8.0.0(@types/node@22.19.15)(esbuild@0.27.3)) + '@vitest/mocker': 4.1.0(vite@8.0.0(@types/node@24.12.0)(esbuild@0.27.3)) '@vitest/pretty-format': 4.1.0 '@vitest/runner': 4.1.0 '@vitest/snapshot': 4.1.0 @@ -2799,15 +4346,17 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 8.0.0(@types/node@22.19.15)(esbuild@0.27.3) + vite: 8.0.0(@types/node@24.12.0)(esbuild@0.27.3) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.19.15 + '@types/node': 24.12.0 transitivePeerDependencies: - msw walk-up-path@4.0.0: {} + webpack-virtual-modules@0.6.2: {} + which@2.0.2: dependencies: isexe: 2.0.0 @@ -2819,4 +4368,8 @@ snapshots: wordwrap@1.0.0: {} + yallist@3.1.1: {} + + yaml@1.10.2: {} + yoctocolors@2.1.2: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 53f6789..751345b 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,9 @@ packages: - packages/* + - packages/*/benchmark + - internal-packages/* + - examples + - examples/* - scripts catalogs: diff --git a/tsconfig.common.json b/tsconfig.common.json new file mode 100644 index 0000000..7808718 --- /dev/null +++ b/tsconfig.common.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "preserve", + "lib": ["es2023"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "allowImportingTsExtensions": true, + "isolatedModules": true, + "declaration": true + }, + "exclude": ["./examples"] +} diff --git a/tsconfig.json b/tsconfig.json index bea07d8..b67e615 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,4 @@ { - "compilerOptions": { - "target": "esnext", - "module": "preserve", - "lib": ["es2023"], - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "allowImportingTsExtensions": true, - "isolatedModules": true, - "declaration": true - } + "references": [{ "path": "./tsconfig.common.json" }, { "path": "./examples/tsconfig.json" }], + "include": [] } diff --git a/vitest.config.ts b/vitest.config.ts index 45eba40..3d65014 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,6 +2,6 @@ import { defineConfig } from 'vitest/config' export default defineConfig({ test: { - projects: ['packages/*'], + projects: ['packages/*', 'packages/*/benchmark', 'internal-packages/*', './examples'], }, })