diff --git a/napi/transform/index.d.ts b/napi/transform/index.d.ts index c1ce33cb8b26a..a8bddd337876b 100644 --- a/napi/transform/index.d.ts +++ b/napi/transform/index.d.ts @@ -323,6 +323,10 @@ export interface ModuleRunnerTransformResult { errors: Array } +export interface PluginsOptions { + styledComponents?: StyledComponentsOptions +} + export interface ReactRefreshOptions { /** * Specify the identifier of the refresh registration variable. @@ -339,6 +343,85 @@ export interface ReactRefreshOptions { emitFullSignatures?: boolean } +/** + * Configure how styled-components are transformed. + * + * @see {@link https://styled-components.com/docs/tooling#babel-plugin} + */ +export interface StyledComponentsOptions { + /** + * Enhances the attached CSS class name on each component with richer output to help + * identify your components in the DOM without React DevTools. + * + * @default true + */ + displayName?: boolean + /** + * Controls whether the `displayName` of a component will be prefixed with the filename + * to make the component name as unique as possible. + * + * @default true + */ + fileName?: boolean + /** + * Adds a unique identifier to every styled component to avoid checksum mismatches + * due to different class generation on the client and server during server-side rendering. + * + * @default true + */ + ssr?: boolean + /** + * Transpiles styled-components tagged template literals to a smaller representation + * than what Babel normally creates, helping to reduce bundle size. + * + * @default true + */ + transpileTemplateLiterals?: boolean + /** + * Minifies CSS content by removing all whitespace and comments from your CSS, + * keeping valuable bytes out of your bundles. + * + * @default true + */ + minify?: boolean + /** + * Enables transformation of JSX `css` prop when using styled-components. + * + * **Note: This feature is not yet implemented in oxc.** + * + * @default true + */ + cssProp?: boolean + /** + * Enables "pure annotation" to aid dead code elimination by bundlers. + * + * @default false + */ + pure?: boolean + /** + * Adds a namespace prefix to component identifiers to ensure class names are unique. + * + * Example: With `namespace: "my-app"`, generates `componentId: "my-app__sc-3rfj0a-1"` + */ + namespace?: string + /** + * List of file names that are considered meaningless for component naming purposes. + * + * When the `fileName` option is enabled and a component is in a file with a name + * from this list, the directory name will be used instead of the file name for + * the component's display name. + * + * @default ["index"] + */ + meaninglessFileNames?: Array + /** + * Import paths to be considered as styled-components imports at the top level. + * + * **Note: This feature is not yet implemented in oxc.** + */ + topLevelImportPaths?: Array +} + /** * Transpile a JavaScript or TypeScript into a target ECMAScript version. * @@ -407,6 +490,8 @@ export interface TransformOptions { inject?: Record /** Decorator plugin */ decorator?: DecoratorOptions + /** Third-party plugins to use. */ + plugins?: PluginsOptions } export interface TransformResult { diff --git a/napi/transform/src/transformer.rs b/napi/transform/src/transformer.rs index 9a6036e91887d..f9db860a91321 100644 --- a/napi/transform/src/transformer.rs +++ b/napi/transform/src/transformer.rs @@ -19,8 +19,8 @@ use oxc::{ semantic::{SemanticBuilder, SemanticBuilderReturn}, span::SourceType, transformer::{ - EnvOptions, HelperLoaderMode, HelperLoaderOptions, JsxRuntime, PluginsOptions, - ProposalOptions, RewriteExtensionsMode, + EnvOptions, HelperLoaderMode, HelperLoaderOptions, JsxRuntime, ProposalOptions, + RewriteExtensionsMode, }, transformer_plugins::{ InjectGlobalVariablesConfig, InjectImport, ModuleRunnerTransform, @@ -144,6 +144,9 @@ pub struct TransformOptions { /// Decorator plugin pub decorator: Option, + + /// Third-party plugins to use. + pub plugins: Option, } impl TryFrom for oxc::transformer::TransformOptions { @@ -182,7 +185,10 @@ impl TryFrom for oxc::transformer::TransformOptions { helper_loader: options .helpers .map_or_else(HelperLoaderOptions::default, HelperLoaderOptions::from), - plugins: PluginsOptions::default(), + plugins: options + .plugins + .map(oxc::transformer::PluginsOptions::from) + .unwrap_or_default(), }) } } @@ -389,6 +395,114 @@ impl From for oxc::transformer::DecoratorOptions { } } +/// Configure how styled-components are transformed. +/// +/// @see {@link https://styled-components.com/docs/tooling#babel-plugin} +#[napi(object)] +#[derive(Default)] +pub struct StyledComponentsOptions { + /// Enhances the attached CSS class name on each component with richer output to help + /// identify your components in the DOM without React DevTools. + /// + /// @default true + pub display_name: Option, + + /// Controls whether the `displayName` of a component will be prefixed with the filename + /// to make the component name as unique as possible. + /// + /// @default true + pub file_name: Option, + + /// Adds a unique identifier to every styled component to avoid checksum mismatches + /// due to different class generation on the client and server during server-side rendering. + /// + /// @default true + pub ssr: Option, + + /// Transpiles styled-components tagged template literals to a smaller representation + /// than what Babel normally creates, helping to reduce bundle size. + /// + /// @default true + pub transpile_template_literals: Option, + + /// Minifies CSS content by removing all whitespace and comments from your CSS, + /// keeping valuable bytes out of your bundles. + /// + /// @default true + pub minify: Option, + + /// Enables transformation of JSX `css` prop when using styled-components. + /// + /// **Note: This feature is not yet implemented in oxc.** + /// + /// @default true + pub css_prop: Option, + + /// Enables "pure annotation" to aid dead code elimination by bundlers. + /// + /// @default false + pub pure: Option, + + /// Adds a namespace prefix to component identifiers to ensure class names are unique. + /// + /// Example: With `namespace: "my-app"`, generates `componentId: "my-app__sc-3rfj0a-1"` + pub namespace: Option, + + /// List of file names that are considered meaningless for component naming purposes. + /// + /// When the `fileName` option is enabled and a component is in a file with a name + /// from this list, the directory name will be used instead of the file name for + /// the component's display name. + /// + /// @default ["index"] + pub meaningless_file_names: Option>, + + /// Import paths to be considered as styled-components imports at the top level. + /// + /// **Note: This feature is not yet implemented in oxc.** + pub top_level_import_paths: Option>, +} + +#[napi(object)] +#[derive(Default)] +pub struct PluginsOptions { + pub styled_components: Option, +} + +impl From for oxc::transformer::PluginsOptions { + fn from(options: PluginsOptions) -> Self { + oxc::transformer::PluginsOptions { + styled_components: options + .styled_components + .map(oxc::transformer::StyledComponentsOptions::from), + } + } +} + +impl From for oxc::transformer::StyledComponentsOptions { + fn from(options: StyledComponentsOptions) -> Self { + let ops = oxc::transformer::StyledComponentsOptions::default(); + oxc::transformer::StyledComponentsOptions { + display_name: options.display_name.unwrap_or(ops.display_name), + file_name: options.file_name.unwrap_or(ops.file_name), + ssr: options.ssr.unwrap_or(ops.ssr), + transpile_template_literals: options + .transpile_template_literals + .unwrap_or(ops.transpile_template_literals), + minify: options.minify.unwrap_or(ops.minify), + css_prop: options.css_prop.unwrap_or(ops.css_prop), + pure: options.pure.unwrap_or(ops.pure), + namespace: options.namespace, + meaningless_file_names: options + .meaningless_file_names + .unwrap_or(ops.meaningless_file_names), + top_level_import_paths: options + .top_level_import_paths + .unwrap_or(ops.top_level_import_paths), + } + } +} + /// Configure how TSX and JSX are transformed. /// /// @see {@link https://babeljs.io/docs/babel-plugin-transform-react-jsx#options} diff --git a/napi/transform/test/transform.test.ts b/napi/transform/test/transform.test.ts index 977df2b956e92..78b2eccee25ca 100644 --- a/napi/transform/test/transform.test.ts +++ b/napi/transform/test/transform.test.ts @@ -418,3 +418,30 @@ describe('typescript', () => { }); }); }); + +describe('styled-components', () => { + test('matches output', () => { + const code = ` + import styled, { css } from 'styled-components'; + + styled.div\`color: red;\`; + const v = css(["color: red;"]); + `; + const ret = transform('test.js', code, { + plugins: { + styledComponents: { + pure: true, + }, + }, + }); + expect(ret.code).toMatchInlineSnapshot(` + "import styled, { css } from "styled-components"; + styled.div.withConfig({ + displayName: "test", + componentId: "sc-ziiqn7-0" + })(["color:red;"]); + const v = /* @__PURE__ */ css(["color: red;"]); + " + `); + }); +});