mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
refactor(compiler)!: consolidate jsx surface
This commit is contained in:
@@ -16,20 +16,20 @@
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
"./vite": {
|
||||
"types": "./dist/plugins/vite.d.ts",
|
||||
"default": "./dist/plugins/vite.js"
|
||||
"types": "./dist/bundlers/vite.d.ts",
|
||||
"default": "./dist/bundlers/vite.js"
|
||||
},
|
||||
"./ast": {
|
||||
"types": "./dist/ast/index.d.ts",
|
||||
"default": "./dist/ast/index.js"
|
||||
"./jsx": {
|
||||
"types": "./dist/jsx/index.d.ts",
|
||||
"default": "./dist/jsx/index.js"
|
||||
},
|
||||
"./matchers": {
|
||||
"types": "./dist/matchers/index.d.ts",
|
||||
"default": "./dist/matchers/index.js"
|
||||
"./diagnostics": {
|
||||
"types": "./dist/diagnostics.d.ts",
|
||||
"default": "./dist/diagnostics.js"
|
||||
},
|
||||
"./react": {
|
||||
"types": "./dist/react/index.d.ts",
|
||||
"default": "./dist/react/index.js"
|
||||
"./transforms": {
|
||||
"types": "./dist/transforms/index.d.ts",
|
||||
"default": "./dist/transforms/index.js"
|
||||
},
|
||||
"./styles": {
|
||||
"types": "./dist/styles/index.d.ts",
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export { type ParseOptions, type ParseResult, parse } from '../parse';
|
||||
export { type AddImportContext, type AddImportRef, addNamedImport } from '../transforms/add-import';
|
||||
export { type ImportRef, type ImportRewriteOptions, type ImportRule, transformImports } from '../transforms/imports';
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
type CompilerDiagnostic,
|
||||
type CompilerPipelineStep,
|
||||
type CompilerTransform,
|
||||
react,
|
||||
jsx,
|
||||
} from './config';
|
||||
import { fatalDiagnosticFromError, withDiagnosticSource } from './diagnostics';
|
||||
import { parse } from './parse';
|
||||
@@ -59,7 +59,7 @@ const printer = ts.createPrinter({
|
||||
export async function compile(source: string, options: CompileOptions = {}): Promise<CompileResult> {
|
||||
const filename = options.filename ?? 'input.tsx';
|
||||
const config = options.config ?? {};
|
||||
const target = config.target ?? react();
|
||||
const target = config.target ?? jsx();
|
||||
const assets: CompilerAsset[] = [];
|
||||
const diagnostics: CompilerDiagnostic[] = [];
|
||||
const context: CompilerContext = {
|
||||
|
||||
@@ -41,11 +41,8 @@ export interface StylePipeline {
|
||||
setup(context: CompilerContext): CompilerPipelineStep | Promise<CompilerPipelineStep>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-target compile configuration. Currently only `react` is shipped, but
|
||||
* the shape is extensible for `html`/etc.
|
||||
*/
|
||||
export interface ReactTargetOptions {
|
||||
/** Per-target compile configuration for JSX transforms. */
|
||||
export interface JsxTargetOptions {
|
||||
/** Per-source-module rewrite rules. */
|
||||
imports?: Record<string, ImportRule> | undefined;
|
||||
/** Transforms applied in order after `transformImports`. */
|
||||
@@ -53,7 +50,7 @@ export interface ReactTargetOptions {
|
||||
}
|
||||
|
||||
export interface CompilerTarget {
|
||||
name: 'react' | 'html';
|
||||
name: 'jsx';
|
||||
imports?: Record<string, ImportRule> | undefined;
|
||||
transforms?: readonly CompilerTransform[] | undefined;
|
||||
}
|
||||
@@ -68,9 +65,9 @@ export function defineConfig<const Config extends CompilerConfig>(config: Config
|
||||
return config;
|
||||
}
|
||||
|
||||
export function react(options: ReactTargetOptions = {}): CompilerTarget {
|
||||
export function jsx(options: JsxTargetOptions = {}): CompilerTarget {
|
||||
return {
|
||||
name: 'react',
|
||||
name: 'jsx',
|
||||
...(options.imports ? { imports: options.imports } : {}),
|
||||
...(options.transforms ? { transforms: options.transforms } : {}),
|
||||
};
|
||||
|
||||
@@ -8,37 +8,5 @@ export {
|
||||
type CompilerTarget,
|
||||
type CompilerTransform,
|
||||
defineConfig,
|
||||
type ReactTargetOptions,
|
||||
react,
|
||||
type StylePipeline,
|
||||
} from './config';
|
||||
export {
|
||||
compilerDiagnosticToJsonEvent,
|
||||
type DiagnosticFormat,
|
||||
type DiagnosticJsonEvent,
|
||||
type DiagnosticJsonFrameLine,
|
||||
type DiagnosticLocation,
|
||||
type DiagnosticSummaryJsonEvent,
|
||||
diagnosticLocationFromNode,
|
||||
diagnosticSummaryToJsonEvent,
|
||||
type FormatDiagnosticOptions,
|
||||
formatCompilerDiagnostic,
|
||||
formatCompilerDiagnosticJsonLine,
|
||||
formatDiagnosticSummaryJsonLine,
|
||||
LogLevel,
|
||||
type LogLevelName,
|
||||
mapLogLevelStringToNumber,
|
||||
mapLogLevelToString,
|
||||
shouldUseColor,
|
||||
} from './diagnostics';
|
||||
export { type TailwindMode, type TailwindOptions, tailwind } from './tailwind';
|
||||
export type { ImportRef, ImportRule } from './transforms/imports';
|
||||
export {
|
||||
accessPath,
|
||||
type JsxChildReplacement,
|
||||
jsxExpression,
|
||||
propertyAccess,
|
||||
type ReplaceJsxChildOptions,
|
||||
readStringAttribute,
|
||||
replaceJsxChild,
|
||||
} from './transforms/jsx';
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
export { type JsxTargetOptions, jsx } from '../config';
|
||||
export type { ImportRef } from '../transforms/imports';
|
||||
export { hasChild } from './matchers/has-child';
|
||||
export { anyTag, byTag, type JsxElementLike, type Matcher, tagName } from './matchers/tag';
|
||||
export { type AddPropImportRef, type AddPropOptions, addProp } from './transforms/add-prop';
|
||||
export { type ChildAsPropOptions, childAsProp } from './transforms/child-as-prop';
|
||||
export {
|
||||
accessPath,
|
||||
type JsxChildReplacement,
|
||||
jsxExpression,
|
||||
propertyAccess,
|
||||
type ReplaceJsxChildOptions,
|
||||
readStringAttribute,
|
||||
replaceJsxChild,
|
||||
} from './transforms/jsx';
|
||||
export { type ReplaceOptions, replace } from './transforms/replace';
|
||||
export { type WrapOptions, wrap } from './transforms/wrap';
|
||||
@@ -1,6 +1,6 @@
|
||||
import ts from 'typescript';
|
||||
|
||||
/** A JSX element that helpers can transform — either an open/close pair or self-closing. */
|
||||
/** A JSX element that helpers can transform: either an open/close pair or self-closing. */
|
||||
export type JsxElementLike = ts.JsxElement | ts.JsxSelfClosingElement;
|
||||
|
||||
/** Predicate over a JSX element. Single shape across `replace`, `wrap`, `childAsProp`, `addProp`. */
|
||||
@@ -16,7 +16,7 @@ function readTag(name: ts.JsxTagNameExpression): string {
|
||||
if (ts.isIdentifier(name)) return name.text;
|
||||
if (ts.isPropertyAccessExpression(name))
|
||||
return `${readTag(name.expression as ts.JsxTagNameExpression)}.${name.name.text}`;
|
||||
// ThisExpression / JsxNamespacedName — uncommon in our skins; fall back to source text.
|
||||
// ThisExpression / JsxNamespacedName: uncommon in our skins; fall back to source text.
|
||||
return name.getText();
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import ts from 'typescript';
|
||||
import { type AddImportContext, addNamedImport } from '../../transforms/add-import';
|
||||
import type { JsxElementLike, Matcher } from '../matchers';
|
||||
import { type AddImportContext, addNamedImport } from '../transforms/add-import';
|
||||
|
||||
export interface AddPropImportRef {
|
||||
source: string;
|
||||
+1
-1
@@ -11,7 +11,7 @@ export interface ChildAsPropOptions {
|
||||
* named prop (turning the element into a self-closing form):
|
||||
*
|
||||
* <Tooltip.Trigger><PlayButton/></Tooltip.Trigger>
|
||||
* → <Tooltip.Trigger render={<PlayButton/>}/>
|
||||
* -> <Tooltip.Trigger render={<PlayButton/>}/>
|
||||
*
|
||||
* Skips no-op cases:
|
||||
* - element is already self-closing
|
||||
+3
-2
@@ -1,10 +1,11 @@
|
||||
import ts from 'typescript';
|
||||
import { type AddImportContext, addNamedImport } from '../../transforms/add-import';
|
||||
import type { ImportRef } from '../../transforms/imports';
|
||||
import type { JsxElementLike, Matcher } from '../matchers';
|
||||
import { type AddImportContext, type AddImportRef, addNamedImport } from './add-import';
|
||||
|
||||
export interface ReplaceOptions {
|
||||
match: Matcher;
|
||||
with: AddImportRef;
|
||||
with: ImportRef;
|
||||
/** Reshape the new element's attributes from the original's. Defaults to passthrough. */
|
||||
mapProps?: (original: ts.JsxAttributes, factory: ts.NodeFactory) => ts.JsxAttributes;
|
||||
/** Reshape the new element's children from the original's. Defaults to passthrough (open form only). */
|
||||
+4
-3
@@ -1,15 +1,16 @@
|
||||
import ts from 'typescript';
|
||||
import { type AddImportContext, addNamedImport } from '../../transforms/add-import';
|
||||
import type { ImportRef } from '../../transforms/imports';
|
||||
import type { JsxElementLike, Matcher } from '../matchers';
|
||||
import { type AddImportContext, type AddImportRef, addNamedImport } from './add-import';
|
||||
|
||||
export interface WrapOptions {
|
||||
match: Matcher;
|
||||
with: AddImportRef;
|
||||
with: ImportRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a matched JSX subtree with another component:
|
||||
* <Match/> → <Wrapper><Match/></Wrapper>
|
||||
* <Match/> -> <Wrapper><Match/></Wrapper>
|
||||
* The wrapper's import is added if missing.
|
||||
*/
|
||||
export function wrap(opts: WrapOptions, ctx: AddImportContext = {}): ts.TransformerFactory<ts.SourceFile> {
|
||||
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* React-target plugins for `@videojs/compiler`. Houses the framework-pattern
|
||||
* helpers that lower constrained JSX into React-friendly component shapes.
|
||||
* Re-exports `replace` and `wrap` for convenience so a config can import
|
||||
* everything from one subpath.
|
||||
*/
|
||||
|
||||
export { type ReplaceOptions, replace } from '../transforms/replace';
|
||||
export { type WrapOptions, wrap } from '../transforms/wrap';
|
||||
export { type AddPropImportRef, type AddPropOptions, addProp } from './add-prop';
|
||||
export { type ChildAsPropOptions, childAsProp } from './child-as-prop';
|
||||
@@ -1,5 +1,5 @@
|
||||
import ts from 'typescript';
|
||||
import type { JsxElementLike } from '../matchers';
|
||||
import type { JsxElementLike } from '../jsx';
|
||||
|
||||
/**
|
||||
* A single segment within a `className` attribute. Either a literal class
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import ts from 'typescript';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { compile } from '../../compile';
|
||||
import { react } from '../../config';
|
||||
import { jsx } from '../../config';
|
||||
import type { StyleAttributeInfo, StyleAttributeSegmentsInfo, StyleSegment } from '../analyze';
|
||||
import { analyzeStyles } from '../analyze';
|
||||
|
||||
@@ -9,7 +9,7 @@ async function collectSegments(source: string): Promise<StyleAttributeInfo[]> {
|
||||
const collected: StyleAttributeInfo[] = [];
|
||||
await compile(source, {
|
||||
config: {
|
||||
target: react({
|
||||
target: jsx({
|
||||
transforms: [
|
||||
analyzeStyles({
|
||||
visit: (info) => {
|
||||
@@ -25,7 +25,7 @@ async function collectSegments(source: string): Promise<StyleAttributeInfo[]> {
|
||||
}
|
||||
|
||||
const compileWithTransform = (source: string, transform: ReturnType<typeof analyzeStyles>) =>
|
||||
compile(source, { config: { target: react({ transforms: [transform] }) } });
|
||||
compile(source, { config: { target: jsx({ transforms: [transform] }) } });
|
||||
|
||||
const collapse = (s: string): string => s.replace(/\s+/g, '');
|
||||
|
||||
@@ -109,7 +109,7 @@ describe('analyzeStyles — decomposition', () => {
|
||||
const infos: StyleAttributeInfo[] = [];
|
||||
await compile(source, {
|
||||
config: {
|
||||
target: react({
|
||||
target: jsx({
|
||||
transforms: [
|
||||
analyzeStyles({
|
||||
mergeFn: 'twMerge',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { kebabCase } from '@videojs/utils/string';
|
||||
import { type DiagnosticLocation, diagnosticLocationFromNode } from '../diagnostics';
|
||||
import type { JsxElementLike } from '../matchers';
|
||||
import { tagName } from '../matchers';
|
||||
import { type JsxElementLike, tagName } from '../jsx';
|
||||
import type { StyleSegment } from '../styles';
|
||||
|
||||
/** Result of deriving a CSS class name for a JSX element. */
|
||||
|
||||
@@ -3,7 +3,7 @@ import { basename, dirname, extname, isAbsolute, join, resolve as resolvePath }
|
||||
import ts from 'typescript';
|
||||
import type { CompilerContext, StylePipeline } from '../config';
|
||||
import { diagnosticLocationFromNode } from '../diagnostics';
|
||||
import { tagName } from '../matchers';
|
||||
import { tagName } from '../jsx';
|
||||
import { analyzeStyles, type StyleSegment, type StyleVisitor } from '../styles';
|
||||
import { decompose, type UtilityCss } from './decompose';
|
||||
import { type DesignSystem, loadDesignSystem } from './design-system';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import ts from 'typescript';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { JsxElementLike } from '../../matchers';
|
||||
import type { JsxElementLike } from '../../jsx';
|
||||
import { parse } from '../../parse';
|
||||
import type { StyleSegment } from '../../styles';
|
||||
import { DiagnosticError, deriveClassName } from '../naming';
|
||||
|
||||
@@ -3,7 +3,7 @@ import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { compile as compileSource } from '../../compile';
|
||||
import { type CompilerTransform, react } from '../../config';
|
||||
import { type CompilerTransform, jsx } from '../../config';
|
||||
import type { DesignSystem } from '../design-system';
|
||||
import { loadDesignSystem } from '../design-system';
|
||||
import type { CompiledRule } from '../emit';
|
||||
@@ -50,10 +50,10 @@ const compile = (
|
||||
source: string,
|
||||
options: {
|
||||
filename?: string | undefined;
|
||||
target?: 'react' | undefined;
|
||||
target?: 'jsx' | undefined;
|
||||
plugins?: readonly CompilerTransform[] | undefined;
|
||||
} = {}
|
||||
) => compileSource(source, { filename: options.filename, config: { target: react({ transforms: options.plugins }) } });
|
||||
) => compileSource(source, { filename: options.filename, config: { target: jsx({ transforms: options.plugins }) } });
|
||||
|
||||
const compileTailwind = (source: string, options: Parameters<typeof tailwind>[0], filename?: string) =>
|
||||
compileSource(source, { filename, config: { styles: tailwind(options) } });
|
||||
@@ -62,7 +62,7 @@ describe('tailwindPlugin — mode: preserve', () => {
|
||||
it('preserves static className values', async () => {
|
||||
const source = `function App(){ return <Foo className="flex items-center"/>; }`;
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [tailwindPlugin({ design, mode: 'preserve' })],
|
||||
});
|
||||
expect(code).toContain('"flex items-center"');
|
||||
@@ -72,7 +72,7 @@ describe('tailwindPlugin — mode: preserve', () => {
|
||||
const source = `function App(){ return <Foo className="flex"/>; }`;
|
||||
let called = 0;
|
||||
await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [
|
||||
tailwindPlugin({
|
||||
design,
|
||||
@@ -91,7 +91,7 @@ describe('tailwindPlugin — mode: inline', () => {
|
||||
it('preserves static className values', async () => {
|
||||
const source = `function App(){ return <Foo className="flex items-center"/>; }`;
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [tailwindPlugin({ design, mode: 'inline' })],
|
||||
});
|
||||
expect(code).toContain('"flex items-center"');
|
||||
@@ -100,7 +100,7 @@ describe('tailwindPlugin — mode: inline', () => {
|
||||
it('folds static cn calls', async () => {
|
||||
const source = `function App(){ return <Foo className={cn('flex', 'items-center', 'gap-2')}/>; }`;
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [tailwindPlugin({ design, mode: 'inline' })],
|
||||
});
|
||||
expect(code).toContain('"flex items-center gap-2"');
|
||||
@@ -119,7 +119,7 @@ function App(){ return <Foo className={cn('flex', styles.button.base)}/>; }`;
|
||||
const sourcePath = writeFixture('skin.tsx', source);
|
||||
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
filename: sourcePath,
|
||||
plugins: [tailwindPlugin({ design, mode: 'inline', sourcePath })],
|
||||
});
|
||||
@@ -131,7 +131,7 @@ function App(){ return <Foo className={cn('flex', styles.button.base)}/>; }`;
|
||||
function App(){ return <Foo className={cn('flex', styles.unknown)}/>; }`;
|
||||
const sourcePath = writeFixture('skin.tsx', source);
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
filename: sourcePath,
|
||||
plugins: [tailwindPlugin({ design, mode: 'inline', sourcePath })],
|
||||
});
|
||||
@@ -141,7 +141,7 @@ function App(){ return <Foo className={cn('flex', styles.unknown)}/>; }`;
|
||||
it('leaves dynamic cn calls untouched', async () => {
|
||||
const source = `function App(){ return <Foo className={cn('flex', isOn && 'on')}/>; }`;
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [tailwindPlugin({ design, mode: 'inline' })],
|
||||
});
|
||||
expect(code).toMatch(/cn\(/);
|
||||
@@ -152,7 +152,7 @@ describe('tailwindPlugin — mode: extract', () => {
|
||||
it('replaces static utilities with component class names', async () => {
|
||||
const source = `function App(){ return <PlayButton className="flex items-center"/>; }`;
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [tailwindPlugin({ design, mode: 'extract' })],
|
||||
});
|
||||
expect(code).toContain('"play-button"');
|
||||
@@ -162,7 +162,7 @@ describe('tailwindPlugin — mode: extract', () => {
|
||||
it('preserves group marker classes', async () => {
|
||||
const source = `function App(){ return <PlayButton className="group"/>; }`;
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [tailwindPlugin({ design, mode: 'extract' })],
|
||||
});
|
||||
// `group` produces no declarations but is required by descendant
|
||||
@@ -174,7 +174,7 @@ describe('tailwindPlugin — mode: extract', () => {
|
||||
const source = `function App(){ return <PlayButton className={cn('flex', 'group')}/>; }`;
|
||||
let captured: readonly CompiledRule[] | undefined;
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [
|
||||
tailwindPlugin({
|
||||
design,
|
||||
@@ -194,7 +194,7 @@ describe('tailwindPlugin — mode: extract', () => {
|
||||
it('keeps dynamic cn expressions', async () => {
|
||||
const source = `function App(){ return <PlayButton className={cn('group', extra)}/>; }`;
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [tailwindPlugin({ design, mode: 'extract' })],
|
||||
});
|
||||
expect(code).toMatch(/cn\("play-button group",\s*extra\)/);
|
||||
@@ -204,7 +204,7 @@ describe('tailwindPlugin — mode: extract', () => {
|
||||
const source = `function App(){ return <div><SeekIcon className="flex"/><SeekIcon className="block"/></div>; }`;
|
||||
await expect(
|
||||
compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [tailwindPlugin({ design, mode: 'extract' })],
|
||||
})
|
||||
).rejects.toThrow(/class name 'seek-icon' is derived from elements with different styles/);
|
||||
@@ -213,7 +213,7 @@ describe('tailwindPlugin — mode: extract', () => {
|
||||
it('allows preserved marker classes next to matching generated styles', async () => {
|
||||
const source = `function App(){ return <div><Menu.Item className={cn('flex', 'legacy-submenu')}/><Menu.Item className="flex"/></div>; }`;
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [tailwindPlugin({ design, mode: 'extract' })],
|
||||
});
|
||||
expect(code).toContain('"menu-item legacy-submenu"');
|
||||
@@ -223,7 +223,7 @@ describe('tailwindPlugin — mode: extract', () => {
|
||||
it('handles duplicate component styles', async () => {
|
||||
const source = `function App(){ return <div><PlayButton className="flex"/><PlayButton className="flex"/></div>; }`;
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [tailwindPlugin({ design, mode: 'extract' })],
|
||||
});
|
||||
expect(code).toContain('"play-button"');
|
||||
@@ -232,7 +232,7 @@ describe('tailwindPlugin — mode: extract', () => {
|
||||
it('derives class names from style member expressions', async () => {
|
||||
const source = `function App(){ return <div className={styles.bufferingIndicator}/>; }`;
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [tailwindPlugin({ design, mode: 'extract' })],
|
||||
});
|
||||
expect(code).toContain('"buffering-indicator"');
|
||||
@@ -250,7 +250,7 @@ function App(){ return <div className={slider.root}/>; }`;
|
||||
|
||||
const { code } = await compile(source, {
|
||||
filename: sourcePath,
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [tailwindPlugin({ design, mode: 'extract', sourcePath })],
|
||||
});
|
||||
expect(code).toContain('"slider-root"');
|
||||
@@ -270,7 +270,7 @@ function App(){ return <div><ChevronIcon className={cn(icon, menu.chevron)}/><Ch
|
||||
|
||||
const { code } = await compile(source, {
|
||||
filename: sourcePath,
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [tailwindPlugin({ design, mode: 'extract', sourcePath })],
|
||||
});
|
||||
expect(code).toContain('"menu-chevron"');
|
||||
@@ -280,7 +280,7 @@ function App(){ return <div><ChevronIcon className={cn(icon, menu.chevron)}/><Ch
|
||||
it('derives class names from single imported token identifiers', async () => {
|
||||
const source = `function App(){ return <div className={buttonGroupStart}/>; }`;
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [tailwindPlugin({ design, mode: 'extract' })],
|
||||
});
|
||||
expect(code).toContain('"button-group-start"');
|
||||
@@ -289,7 +289,7 @@ function App(){ return <div><ChevronIcon className={cn(icon, menu.chevron)}/><Ch
|
||||
it('prefers style token names over reusable component tag names', async () => {
|
||||
const source = `function App(){ return <Menu.Trigger className={styles.menu.item}/>; }`;
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [tailwindPlugin({ design, mode: 'extract' })],
|
||||
});
|
||||
expect(code).toContain('"menu-item"');
|
||||
@@ -306,7 +306,7 @@ function App(){ return <span className={cn(styles.seek.label, styles.seek.labelB
|
||||
const sourcePath = writeFixture('skin.tsx', source);
|
||||
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
filename: sourcePath,
|
||||
plugins: [tailwindPlugin({ design, mode: 'extract', sourcePath })],
|
||||
});
|
||||
@@ -327,7 +327,7 @@ function App({ type, className }){
|
||||
const sourcePath = writeFixture('skin.tsx', source);
|
||||
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
filename: sourcePath,
|
||||
plugins: [tailwindPlugin({ design, mode: 'extract', sourcePath })],
|
||||
});
|
||||
@@ -338,7 +338,7 @@ function App({ type, className }){
|
||||
it('keeps a single simple literal utility as the class name for bare HTML', async () => {
|
||||
const source = `function App(){ return <div className="grow"/>; }`;
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [tailwindPlugin({ design, mode: 'extract' })],
|
||||
});
|
||||
|
||||
@@ -348,7 +348,7 @@ function App({ type, className }){
|
||||
it('applies component class overrides', async () => {
|
||||
const source = `function App(){ return <PlayButton className="flex"/>; }`;
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [
|
||||
tailwindPlugin({
|
||||
design,
|
||||
@@ -363,7 +363,7 @@ function App({ type, className }){
|
||||
it('applies transformed generated class names', async () => {
|
||||
const source = `function App(){ return <PlayButton className="flex"/>; }`;
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [
|
||||
tailwindPlugin({
|
||||
design,
|
||||
@@ -379,7 +379,7 @@ function App({ type, className }){
|
||||
const source = `function App(){ return <Foo className="flex"/>; }`;
|
||||
let captured: readonly CompiledRule[] | undefined;
|
||||
await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [
|
||||
tailwindPlugin({
|
||||
design,
|
||||
@@ -400,7 +400,7 @@ function App({ type, className }){
|
||||
const source = `function App(){ return <Foo className={cn('flex', 'opacity-50')}/>; }`;
|
||||
let captured: readonly CompiledRule[] | undefined;
|
||||
await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [
|
||||
tailwindPlugin({
|
||||
design,
|
||||
@@ -429,7 +429,7 @@ function App(){ return <Foo className={styles.button}/>; }`;
|
||||
|
||||
let captured: readonly CompiledRule[] | undefined;
|
||||
await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
filename: sourcePath,
|
||||
plugins: [
|
||||
tailwindPlugin({
|
||||
@@ -459,7 +459,7 @@ function App(){ return <Foo className={styles.button}/>; }`;
|
||||
|
||||
let captured: readonly CompiledRule[] | undefined;
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
filename: sourcePath,
|
||||
plugins: [
|
||||
tailwindPlugin({
|
||||
@@ -482,7 +482,7 @@ function App(){ return <Foo className={styles.button}/>; }`;
|
||||
const source = `function App(){ return <PlayButton className="flex"/>; }`;
|
||||
let captured: readonly CompiledRule[] | undefined;
|
||||
await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [
|
||||
tailwindPlugin({
|
||||
design,
|
||||
@@ -501,7 +501,7 @@ function App(){ return <Foo className={styles.button}/>; }`;
|
||||
const source = `function App(){ return <Foo className={isOn ? 'a' : 'b'}/>; }`;
|
||||
let captured: readonly CompiledRule[] | undefined;
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [
|
||||
tailwindPlugin({
|
||||
design,
|
||||
@@ -531,7 +531,7 @@ function App(){ return <PlayButton className={iconButton}/>; }`;
|
||||
|
||||
let captured: readonly CompiledRule[] | undefined;
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
filename: sourcePath,
|
||||
plugins: [
|
||||
tailwindPlugin({
|
||||
@@ -553,7 +553,7 @@ function App(){ return <PlayButton className={iconButton}/>; }`;
|
||||
it('preserves dynamic cn suffixes after extraction', async () => {
|
||||
const source = `function App({ extra }){ return <PlayButton className={cn('flex', extra)}/>; }`;
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [tailwindPlugin({ design, mode: 'extract' })],
|
||||
});
|
||||
expect(code).toMatch(/cn\("play-button",\s*extra\)/);
|
||||
@@ -565,7 +565,7 @@ function App(){ return <PlayButton className={iconButton}/>; }`;
|
||||
}`;
|
||||
let captured: readonly CompiledRule[] | undefined;
|
||||
const { code } = await compile(source, {
|
||||
target: 'react',
|
||||
target: 'jsx',
|
||||
plugins: [
|
||||
tailwindPlugin({
|
||||
design,
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { accessPath, compile, jsxExpression, type ReactTargetOptions, react, replaceJsxChild } from '..';
|
||||
import { parse } from '../ast';
|
||||
import { anyTag, byTag, hasChild } from '../matchers';
|
||||
import { addProp, childAsProp, replace, wrap } from '../react';
|
||||
import { compile } from '..';
|
||||
import {
|
||||
accessPath,
|
||||
addProp,
|
||||
anyTag,
|
||||
byTag,
|
||||
childAsProp,
|
||||
hasChild,
|
||||
type JsxElementLike,
|
||||
type JsxTargetOptions,
|
||||
jsx,
|
||||
jsxExpression,
|
||||
replace,
|
||||
replaceJsxChild,
|
||||
wrap,
|
||||
} from '../jsx';
|
||||
import { parse } from '../parse';
|
||||
|
||||
/**
|
||||
* The TS printer emits `<A />` (space before slash) — collapse all whitespace
|
||||
@@ -10,8 +23,8 @@ import { addProp, childAsProp, replace, wrap } from '../react';
|
||||
*/
|
||||
const collapse = (s: string): string => s.replace(/\s+/g, '');
|
||||
|
||||
const compileReact = (source: string, options: ReactTargetOptions = {}) =>
|
||||
compile(source, { config: { target: react(options) } });
|
||||
const compileJsx = (source: string, options: JsxTargetOptions = {}) =>
|
||||
compile(source, { config: { target: jsx(options) } });
|
||||
|
||||
describe('parse', () => {
|
||||
it('produces a TSX SourceFile with parent pointers set', () => {
|
||||
@@ -24,7 +37,7 @@ describe('parse', () => {
|
||||
describe('compile (no transforms)', () => {
|
||||
it('round-trips a simple TSX module', async () => {
|
||||
const source = `import { Foo } from 'bar';\nexport function App() { return <Foo/>; }\n`;
|
||||
const { code } = await compileReact(source);
|
||||
const { code } = await compileJsx(source);
|
||||
// Identifier and JSX preserved; quote/whitespace style is whatever the printer decides.
|
||||
expect(code).toContain('Foo');
|
||||
expect(code).toContain('bar');
|
||||
@@ -35,16 +48,16 @@ describe('compile (no transforms)', () => {
|
||||
describe('compile (transformImports — bare-string rule)', () => {
|
||||
it('rewrites the module specifier and leaves identifiers untouched', async () => {
|
||||
const source = `import { PlayIcon } from '@videojs/icons/components';\nconst _x = PlayIcon;`;
|
||||
const { code } = await compileReact(source, {
|
||||
imports: { '@videojs/icons/components': '@videojs/icons/react' },
|
||||
const { code } = await compileJsx(source, {
|
||||
imports: { '@videojs/icons/components': '@videojs/icons/jsx' },
|
||||
});
|
||||
expect(code).toContain(`import { PlayIcon } from "@videojs/icons/react"`);
|
||||
expect(code).toContain(`import { PlayIcon } from "@videojs/icons/jsx"`);
|
||||
});
|
||||
|
||||
it('leaves unrelated imports untouched', async () => {
|
||||
const source = `import { Other } from 'unrelated';\nimport { PlayIcon } from '@videojs/icons/components';\nconst _ = [Other, PlayIcon];`;
|
||||
const { code } = await compileReact(source, {
|
||||
imports: { '@videojs/icons/components': '@videojs/icons/react' },
|
||||
const { code } = await compileJsx(source, {
|
||||
imports: { '@videojs/icons/components': '@videojs/icons/jsx' },
|
||||
});
|
||||
expect(code).toMatch(/from ['"]unrelated['"]/);
|
||||
expect(code).toContain('PlayIcon');
|
||||
@@ -54,7 +67,7 @@ describe('compile (transformImports — bare-string rule)', () => {
|
||||
describe('compile (transformImports — function rule)', () => {
|
||||
it('rewrites per-identifier source and bucket-merges by resolved target', async () => {
|
||||
const source = `import { Alpha, Beta } from '@fixture/components';\nconst _ = [Alpha, Beta];`;
|
||||
const { code } = await compileReact(source, {
|
||||
const { code } = await compileJsx(source, {
|
||||
imports: {
|
||||
'@fixture/components': (name) => ({ source: `./ui/${name.toLowerCase()}`, name }),
|
||||
},
|
||||
@@ -65,7 +78,7 @@ describe('compile (transformImports — function rule)', () => {
|
||||
|
||||
it('renames identifiers when the rule returns a different `name`', async () => {
|
||||
const source = `import { OldName } from 'src';\nconst _ = OldName;`;
|
||||
const { code } = await compileReact(source, {
|
||||
const { code } = await compileJsx(source, {
|
||||
imports: { src: (_name) => ({ source: 'dst', name: 'NewName' }) },
|
||||
});
|
||||
expect(code).toContain(`import { NewName as OldName } from "dst"`);
|
||||
@@ -75,7 +88,7 @@ describe('compile (transformImports — function rule)', () => {
|
||||
describe('replace', () => {
|
||||
it('substitutes a matched element with a new tag and adds the import', async () => {
|
||||
const source = `function App(){ return <Old foo="bar"/>; }`;
|
||||
const { code } = await compileReact(source, {
|
||||
const { code } = await compileJsx(source, {
|
||||
transforms: [replace({ match: byTag('Old'), with: { source: 'pkg', name: 'New' } })],
|
||||
});
|
||||
expect(code).toContain(`<New foo="bar"`);
|
||||
@@ -84,7 +97,7 @@ describe('replace', () => {
|
||||
|
||||
it('preserves children when matching an open element', async () => {
|
||||
const source = `function App(){ return <Old><span/></Old>; }`;
|
||||
const { code } = await compileReact(source, {
|
||||
const { code } = await compileJsx(source, {
|
||||
transforms: [replace({ match: byTag('Old'), with: { source: 'pkg', name: 'New' } })],
|
||||
});
|
||||
expect(collapse(code)).toContain(collapse(`<New><span/></New>`));
|
||||
@@ -94,7 +107,7 @@ describe('replace', () => {
|
||||
describe('wrap', () => {
|
||||
it('wraps a matched element with a new tag and adds the import', async () => {
|
||||
const source = `function App(){ return <Inner/>; }`;
|
||||
const { code } = await compileReact(source, {
|
||||
const { code } = await compileJsx(source, {
|
||||
transforms: [wrap({ match: byTag('Inner'), with: { source: 'pkg', name: 'Outer' } })],
|
||||
});
|
||||
expect(collapse(code)).toContain(collapse(`<Outer><Inner/></Outer>`));
|
||||
@@ -105,7 +118,7 @@ describe('wrap', () => {
|
||||
describe('childAsProp', () => {
|
||||
it('lifts a single JSX-element child into the named prop', async () => {
|
||||
const source = `function App(){ return <T><B/></T>; }`;
|
||||
const { code } = await compileReact(source, {
|
||||
const { code } = await compileJsx(source, {
|
||||
transforms: [childAsProp({ match: byTag('T'), prop: 'render' })],
|
||||
});
|
||||
expect(collapse(code)).toContain(collapse(`<T render={<B/>}/>`));
|
||||
@@ -113,7 +126,7 @@ describe('childAsProp', () => {
|
||||
|
||||
it('skips when prop is already set', async () => {
|
||||
const source = `function App(){ return <T render={<X/>}><B/></T>; }`;
|
||||
const { code } = await compileReact(source, {
|
||||
const { code } = await compileJsx(source, {
|
||||
transforms: [childAsProp({ match: byTag('T'), prop: 'render' })],
|
||||
});
|
||||
expect(collapse(code)).toContain(collapse(`<X/>`));
|
||||
@@ -122,7 +135,7 @@ describe('childAsProp', () => {
|
||||
|
||||
it('skips when there are multiple JSX-element children', async () => {
|
||||
const source = `function App(){ return <T><A/><B/></T>; }`;
|
||||
const { code } = await compileReact(source, {
|
||||
const { code } = await compileJsx(source, {
|
||||
transforms: [childAsProp({ match: byTag('T'), prop: 'render' })],
|
||||
});
|
||||
expect(collapse(code)).toContain(collapse(`<T><A/><B/></T>`));
|
||||
@@ -130,7 +143,7 @@ describe('childAsProp', () => {
|
||||
|
||||
it('matches an array of tags via anyTag', async () => {
|
||||
const source = `function App(){ return <><T1><A/></T1><T2><B/></T2></>; }`;
|
||||
const { code } = await compileReact(source, {
|
||||
const { code } = await compileJsx(source, {
|
||||
transforms: [childAsProp({ match: anyTag(['T1', 'T2']), prop: 'render' })],
|
||||
});
|
||||
const trimmed = collapse(code);
|
||||
@@ -142,7 +155,7 @@ describe('childAsProp', () => {
|
||||
describe('replaceJsxChild', () => {
|
||||
it('replaces matched JSX children with expression helpers', async () => {
|
||||
const source = `function App({ values }){ return <Container><Token name="poster-image"/></Container>; }`;
|
||||
const { code } = await compileReact(source, {
|
||||
const { code } = await compileJsx(source, {
|
||||
transforms: [
|
||||
replaceJsxChild({
|
||||
match: byTag('Token'),
|
||||
@@ -158,7 +171,7 @@ describe('replaceJsxChild', () => {
|
||||
describe('addProp', () => {
|
||||
it('emits a JSX value by default and adds the import', async () => {
|
||||
const source = `function App(){ return <PlayButton/>; }`;
|
||||
const { code } = await compileReact(source, {
|
||||
const { code } = await compileJsx(source, {
|
||||
transforms: [
|
||||
addProp({ match: byTag('PlayButton'), prop: 'render', value: { source: './button', name: 'Button' } }),
|
||||
],
|
||||
@@ -169,7 +182,7 @@ describe('addProp', () => {
|
||||
|
||||
it('emits a bare reference when kind is "ref"', async () => {
|
||||
const source = `function App(){ return <PlayButton/>; }`;
|
||||
const { code } = await compileReact(source, {
|
||||
const { code } = await compileJsx(source, {
|
||||
transforms: [
|
||||
addProp({
|
||||
match: byTag('PlayButton'),
|
||||
@@ -183,7 +196,7 @@ describe('addProp', () => {
|
||||
|
||||
it('skips elements where the prop is already set', async () => {
|
||||
const source = `function App(){ return <PlayButton render={<X/>}/>; }`;
|
||||
const { code } = await compileReact(source, {
|
||||
const { code } = await compileJsx(source, {
|
||||
transforms: [
|
||||
addProp({ match: byTag('PlayButton'), prop: 'render', value: { source: './button', name: 'Button' } }),
|
||||
],
|
||||
@@ -194,7 +207,7 @@ describe('addProp', () => {
|
||||
|
||||
it('overwrites the existing prop when overwrite is true', async () => {
|
||||
const source = `function App(){ return <PlayButton render={<X/>}/>; }`;
|
||||
const { code } = await compileReact(source, {
|
||||
const { code } = await compileJsx(source, {
|
||||
transforms: [
|
||||
addProp({
|
||||
match: byTag('PlayButton'),
|
||||
@@ -211,7 +224,7 @@ describe('addProp', () => {
|
||||
describe('matchers', () => {
|
||||
it('byTag supports dotted tags', async () => {
|
||||
const source = `function App(){ return <Popover.Root foo="bar"/>; }`;
|
||||
const { code } = await compileReact(source, {
|
||||
const { code } = await compileJsx(source, {
|
||||
transforms: [replace({ match: byTag('Popover.Root'), with: { source: 'pkg', name: 'NewRoot' } })],
|
||||
});
|
||||
expect(code).toContain(`<NewRoot`);
|
||||
@@ -219,12 +232,12 @@ describe('matchers', () => {
|
||||
|
||||
it('byTag honours `when` refinement', async () => {
|
||||
const source = `function App(){ return <><Foo a="1"/><Foo a="2"/></>; }`;
|
||||
const isA1 = (node: import('../matchers').JsxElementLike) => {
|
||||
const isA1 = (node: JsxElementLike) => {
|
||||
const attrs = 'attributes' in node ? node.attributes : (node as never);
|
||||
const props = (attrs as { properties?: ReadonlyArray<{ initializer?: { text?: string } }> }).properties ?? [];
|
||||
return props.some((p) => p.initializer?.text === '1');
|
||||
};
|
||||
const { code } = await compileReact(source, {
|
||||
const { code } = await compileJsx(source, {
|
||||
transforms: [replace({ match: byTag('Foo', { when: isA1 }), with: { source: 'pkg', name: 'Bar' } })],
|
||||
});
|
||||
expect(code).toContain(`<Bar a="1"`);
|
||||
@@ -233,7 +246,7 @@ describe('matchers', () => {
|
||||
|
||||
it('hasChild matches direct children only by default', async () => {
|
||||
const source = `function App(){ return <><A><B/></A><A><div><B/></div></A></>; }`;
|
||||
const { code } = await compileReact(source, {
|
||||
const { code } = await compileJsx(source, {
|
||||
transforms: [replace({ match: byTag('A', { when: hasChild(byTag('B')) }), with: { source: 'p', name: 'Z' } })],
|
||||
});
|
||||
// First <A> has direct <B/> child → replaced; second <A> has only a nested <B/> → not replaced.
|
||||
@@ -243,7 +256,7 @@ describe('matchers', () => {
|
||||
|
||||
it('hasChild with deep:true matches descendants', async () => {
|
||||
const source = `function App(){ return <A><div><B/></div></A>; }`;
|
||||
const { code } = await compileReact(source, {
|
||||
const { code } = await compileJsx(source, {
|
||||
transforms: [
|
||||
replace({
|
||||
match: byTag('A', { when: hasChild(byTag('B'), { deep: true }) }),
|
||||
@@ -258,7 +271,7 @@ describe('matchers', () => {
|
||||
const source = `function App(){
|
||||
return <><Outer><Inner><Target/></Inner></Outer><Outer><Inner><Other/></Inner></Outer></>;
|
||||
}`;
|
||||
const { code } = await compileReact(source, {
|
||||
const { code } = await compileJsx(source, {
|
||||
transforms: [
|
||||
replace({
|
||||
match: byTag('Outer', { when: hasChild(byTag('Inner', { when: hasChild(byTag('Target')) })) }),
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import type ts from 'typescript';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { CompilerError, compile, react } from '..';
|
||||
import { CompilerError, compile } from '..';
|
||||
import {
|
||||
diagnosticLocationFromNode,
|
||||
formatCompilerDiagnostic,
|
||||
formatCompilerDiagnosticJsonLine,
|
||||
formatDiagnosticSummaryJsonLine,
|
||||
} from '../diagnostics';
|
||||
import { jsx } from '../jsx';
|
||||
import { DiagnosticError } from '../tailwind';
|
||||
|
||||
describe('formatCompilerDiagnostic', () => {
|
||||
@@ -96,7 +97,7 @@ describe('CompilerError diagnostics', () => {
|
||||
try {
|
||||
await compile(`export function App(){ return <Foo/>; }`, {
|
||||
filename: '/workspace/skin.tsx',
|
||||
config: { target: react({ transforms: [transform()] }) },
|
||||
config: { target: jsx({ transforms: [transform()] }) },
|
||||
});
|
||||
throw new Error('Expected compile to fail');
|
||||
} catch (error) {
|
||||
|
||||
@@ -2,9 +2,9 @@ import { readFileSync } from 'node:fs';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { beforeAll, describe, expect, it } from 'vitest';
|
||||
import { compile, type ImportRule, react } from '..';
|
||||
import { anyTag, byTag, hasChild } from '../matchers';
|
||||
import { childAsProp, replace } from '../react';
|
||||
import { compile } from '..';
|
||||
import { anyTag, byTag, childAsProp, hasChild, jsx, replace } from '../jsx';
|
||||
import type { ImportRule } from '../transforms';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const skinSource = resolve(__dirname, 'fixtures/video-skin.tsx');
|
||||
@@ -12,12 +12,12 @@ const skinSource = resolve(__dirname, 'fixtures/video-skin.tsx');
|
||||
/**
|
||||
* End-to-end smoke test: feed a representative constrained-JSX video skin
|
||||
* (vendored under `fixtures/`) through `compile()` with the same shape
|
||||
* a React package build hook uses, and sanity-check the output's structural
|
||||
* a package build hook uses, and sanity-check the output's structural
|
||||
* shape. Snapshot-style assertions intentionally use `.toContain` over a full
|
||||
* snapshot to keep the test resilient to incidental whitespace differences
|
||||
* from the TS printer.
|
||||
*/
|
||||
describe('integration: default/video skin → React', () => {
|
||||
describe('integration: default/video skin → JSX', () => {
|
||||
const source = readFileSync(skinSource, 'utf8');
|
||||
let code = '';
|
||||
|
||||
@@ -26,14 +26,14 @@ describe('integration: default/video skin → React', () => {
|
||||
source: `./src/ui/${name.replace(/^[A-Z]/, (m) => m.toLowerCase()).replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)}`,
|
||||
name,
|
||||
}),
|
||||
'@fixture/icons/components': '@fixture/icons/react',
|
||||
'@fixture/icons/components': '@fixture/icons/jsx',
|
||||
'../tailwind': '@videojs/skins/default/tailwind',
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
const result = await compile(source, {
|
||||
config: {
|
||||
target: react({
|
||||
target: jsx({
|
||||
imports,
|
||||
transforms: [
|
||||
replace({
|
||||
@@ -60,7 +60,7 @@ describe('integration: default/video skin → React', () => {
|
||||
});
|
||||
|
||||
it('rewrites icon component imports', () => {
|
||||
expect(code).toContain('@fixture/icons/react');
|
||||
expect(code).toContain('@fixture/icons/jsx');
|
||||
expect(code).not.toContain('@fixture/icons/components');
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import ts from 'typescript';
|
||||
|
||||
/**
|
||||
* Per-identifier rewrite target. `source` may be either a bare specifier
|
||||
* (`@videojs/icons/react`) or a relative path. Relative paths are resolved
|
||||
* (`@fixture/icons/jsx`) or a relative path. Relative paths are resolved
|
||||
* against the configured `configDir` and re-projected as a relative path from
|
||||
* the output file at print time.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
export { type AddImportContext, type AddImportRef, addNamedImport } from './add-import';
|
||||
export { dropUnusedImports } from './drop-unused-imports';
|
||||
export { dropUnusedLocals } from './drop-unused-locals';
|
||||
export {
|
||||
type ImportRef,
|
||||
type ImportRewriteOptions,
|
||||
type ImportRule,
|
||||
resolveRelative,
|
||||
transformImports,
|
||||
} from './imports';
|
||||
@@ -1,10 +1,10 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { compile } from '../../compile';
|
||||
import { react } from '../../config';
|
||||
import { jsx } from '../../config';
|
||||
import { dropUnusedImports } from '../drop-unused-imports';
|
||||
|
||||
const wrap = async (source: string): Promise<string> =>
|
||||
(await compile(source, { config: { target: react({ transforms: [dropUnusedImports()] }) } })).code;
|
||||
(await compile(source, { config: { target: jsx({ transforms: [dropUnusedImports()] }) } })).code;
|
||||
|
||||
describe('dropUnusedImports', () => {
|
||||
it('does not count intrinsic JSX tag names as import references', async () => {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { compile } from '../../compile';
|
||||
import { react } from '../../config';
|
||||
import { jsx } from '../../config';
|
||||
import { dropUnusedLocals } from '../drop-unused-locals';
|
||||
|
||||
const wrap = async (source: string): Promise<string> =>
|
||||
(await compile(source, { config: { target: react({ transforms: [dropUnusedLocals()] }) } })).code;
|
||||
(await compile(source, { config: { target: jsx({ transforms: [dropUnusedLocals()] }) } })).code;
|
||||
|
||||
describe('dropUnusedLocals', () => {
|
||||
it('drops an unused cn() local', async () => {
|
||||
|
||||
@@ -4,12 +4,12 @@ export default defineConfig({
|
||||
entry: {
|
||||
index: './src/index.ts',
|
||||
cli: './src/cli.ts',
|
||||
'plugins/vite': './src/plugins/vite.ts',
|
||||
'ast/index': './src/ast/index.ts',
|
||||
'matchers/index': './src/matchers/index.ts',
|
||||
'react/index': './src/react/index.ts',
|
||||
'bundlers/vite': './src/bundlers/vite.ts',
|
||||
diagnostics: './src/diagnostics.ts',
|
||||
'jsx/index': './src/jsx/index.ts',
|
||||
'styles/index': './src/styles/index.ts',
|
||||
'tailwind/index': './src/tailwind/index.ts',
|
||||
'transforms/index': './src/transforms/index.ts',
|
||||
},
|
||||
platform: 'neutral',
|
||||
format: 'es',
|
||||
|
||||
Reference in New Issue
Block a user