diff --git a/packages/core/lib/bundler/helpers/internal.ts b/packages/core/lib/bundler/helpers/internal.ts index 42b019a3..8d141acd 100644 --- a/packages/core/lib/bundler/helpers/internal.ts +++ b/packages/core/lib/bundler/helpers/internal.ts @@ -3,6 +3,7 @@ import { getPreludeScript } from '@react-native-esbuild/internal'; import { stripFlowWithSucrase, minifyWithSwc, + swcPresets, } from '@react-native-esbuild/transformer'; import type { BundleOptions } from '@react-native-esbuild/config'; @@ -22,11 +23,7 @@ export const getTransformedPreludeScript = async ( .trim(); return bundleOptions.minify - ? minifyWithSwc(strippedScript, context, { - compress: true, - mangle: true, - sourceMap: false, - }) + ? minifyWithSwc(strippedScript, context, swcPresets.getMinifyPreset()) : strippedScript; }; diff --git a/packages/jest/lib/transformer/createTransformer.ts b/packages/jest/lib/transformer/createTransformer.ts index 36f33c9b..f6ea087d 100644 --- a/packages/jest/lib/transformer/createTransformer.ts +++ b/packages/jest/lib/transformer/createTransformer.ts @@ -34,13 +34,13 @@ export const createTransformer = (config: TransformerConfig): Transformer => { const syncTransformPipeline = new SyncTransformPipeline.builder( ROOT, DUMMY_ENTRY, - { - swc: swcPresets.getJestOptions({ + ) + .setSwcPreset( + swcPresets.getJestPreset({ module: 'cjs', experimental: swcExperimentalOptions, }), - }, - ) + ) .setInjectScripts([getReactNativeInitializeCore(ROOT)]) .setFullyTransformPackages(transformer?.fullyTransformPackageNames ?? []) .setStripFlowPackages(transformer?.stripFlowPackageNames ?? []) @@ -55,14 +55,14 @@ export const createTransformer = (config: TransformerConfig): Transformer => { const asyncTransformPipeline = new AsyncTransformPipeline.builder( ROOT, DUMMY_ENTRY, - { + ) + .setSwcPreset( // Async transform is always ESM. - swc: swcPresets.getJestOptions({ + swcPresets.getJestPreset({ module: 'esm', experimental: swcExperimentalOptions, }), - }, - ) + ) .setInjectScripts([getReactNativeInitializeCore(ROOT)]) .setFullyTransformPackages(transformer?.fullyTransformPackageNames ?? []) .setStripFlowPackages(transformer?.stripFlowPackageNames ?? []) diff --git a/packages/plugins/lib/reactNativeRuntimeTransformPlugin/reactNativeRuntimeTransformPlugin.ts b/packages/plugins/lib/reactNativeRuntimeTransformPlugin/reactNativeRuntimeTransformPlugin.ts index c0739199..bde58c85 100644 --- a/packages/plugins/lib/reactNativeRuntimeTransformPlugin/reactNativeRuntimeTransformPlugin.ts +++ b/packages/plugins/lib/reactNativeRuntimeTransformPlugin/reactNativeRuntimeTransformPlugin.ts @@ -8,6 +8,7 @@ import { import { getReactNativeInitializeCore } from '@react-native-esbuild/internal'; import { AsyncTransformPipeline, + swcPresets, type AsyncTransformStep, } from '@react-native-esbuild/transformer'; import { logger } from '../shared'; @@ -121,6 +122,7 @@ export const createReactNativeRuntimeTransformPlugin: ReactNativeEsbuildPluginCr context.root, context.entry, ) + .setSwcPreset(swcPresets.getReactNativeRuntimePreset()) .setInjectScripts(injectScriptPaths) .setFullyTransformPackages(fullyTransformPackageNames) .setStripFlowPackages(stripFlowPackageNames) diff --git a/packages/transformer/lib/helpers/transformer.ts b/packages/transformer/lib/helpers/transformer.ts index b583b3c3..4e4877b3 100644 --- a/packages/transformer/lib/helpers/transformer.ts +++ b/packages/transformer/lib/helpers/transformer.ts @@ -9,14 +9,16 @@ import type { TransformerContext, SwcTransformRule, BabelTransformRule, + TransformerOptionsPreset, } from '../types'; -const getOptions = ( +const ruleOptionsToPreset = ( options: TransformRuleBase['options'], code: string, - context: TransformerContext, -): T => { - return options instanceof Function ? options(context.path, code) : options; +): TransformerOptionsPreset => { + return options instanceof Function + ? (context) => options(context.path, code) + : () => options; }; export const transformByBabelRule = ( @@ -25,7 +27,7 @@ export const transformByBabelRule = ( context: TransformerContext, ): Promise => { return rule.test(context.path, code) - ? transformWithBabel(code, context, getOptions(rule.options, code, context)) + ? transformWithBabel(code, context, ruleOptionsToPreset(rule.options, code)) : Promise.resolve(null); }; @@ -38,7 +40,7 @@ export const transformSyncByBabelRule = ( ? transformSyncWithBabel( code, context, - getOptions(rule.options, code, context), + ruleOptionsToPreset(rule.options, code), ) : null; }; @@ -49,7 +51,7 @@ export const transformBySwcRule = ( context: TransformerContext, ): Promise => { return rule.test(context.path, code) - ? transformWithSwc(code, context, getOptions(rule.options, code, context)) + ? transformWithSwc(code, context, ruleOptionsToPreset(rule.options, code)) : Promise.resolve(null); }; @@ -62,7 +64,7 @@ export const transformSyncBySwcRule = ( ? transformSyncWithSwc( code, context, - getOptions(rule.options, code, context), + ruleOptionsToPreset(rule.options, code), ) : null; }; diff --git a/packages/transformer/lib/pipelines/AsyncTransformPipeline.ts b/packages/transformer/lib/pipelines/AsyncTransformPipeline.ts index e38caa3d..1e07f7fd 100644 --- a/packages/transformer/lib/pipelines/AsyncTransformPipeline.ts +++ b/packages/transformer/lib/pipelines/AsyncTransformPipeline.ts @@ -45,7 +45,7 @@ export class AsyncTransformPipelineBuilder extends TransformPipelineBuilder< code: await transformWithBabel( code, this.getTransformContext(args), - { root: this.root, babelrc: true }, + this.presets.babelFullyTransform, ), // skip other transformations when fully transformed done: true, @@ -103,7 +103,7 @@ export class AsyncTransformPipelineBuilder extends TransformPipelineBuilder< code: await transformWithSwc( code, this.getTransformContext(args), - this.transformerOptions.swc, + this.swcPreset, ), done: true, }; diff --git a/packages/transformer/lib/pipelines/SyncTransformPipeline.ts b/packages/transformer/lib/pipelines/SyncTransformPipeline.ts index 1f46f736..7b2585a3 100644 --- a/packages/transformer/lib/pipelines/SyncTransformPipeline.ts +++ b/packages/transformer/lib/pipelines/SyncTransformPipeline.ts @@ -42,10 +42,11 @@ export class SyncTransformPipelineBuilder extends TransformPipelineBuilder< pipeline.addStep((code, args) => { if (fullyTransformPackagesRegExp.test(args.path)) { return { - code: transformSyncWithBabel(code, this.getTransformContext(args), { - root: this.root, - babelrc: true, - }), + code: transformSyncWithBabel( + code, + this.getTransformContext(args), + this.presets.babelFullyTransform, + ), // skip other transformations when fully transformed done: true, }; @@ -102,7 +103,7 @@ export class SyncTransformPipelineBuilder extends TransformPipelineBuilder< code: transformSyncWithSwc( code, this.getTransformContext(args), - this.transformerOptions.swc, + this.swcPreset, ), done: true, }; diff --git a/packages/transformer/lib/pipelines/builder.ts b/packages/transformer/lib/pipelines/builder.ts index f62859b8..e37ec3d9 100644 --- a/packages/transformer/lib/pipelines/builder.ts +++ b/packages/transformer/lib/pipelines/builder.ts @@ -1,12 +1,12 @@ import type { OnLoadArgs } from 'esbuild'; import type { Options as SwcTransformOptions } from '@swc/core'; -import type { TransformOptions as BabelTransformOptions } from '@babel/core'; import type { BabelTransformRule, SwcTransformRule, TransformStep, + TransformerOptionsPreset, } from '../types'; -import { babelPresets, swcPresets } from '../transformer'; +import { babelPresets } from '../transformer'; import type { TransformPipeline } from './pipeline'; const FLOW_SYMBOL = ['@flow', '@noflow'] as const; @@ -15,12 +15,12 @@ export abstract class TransformPipelineBuilder< Step extends TransformStep, Pipeline extends TransformPipeline, > { + protected presets = { + babelFullyTransform: babelPresets.getFullyTransformPreset(), + }; protected onBefore?: Step; protected onAfter?: Step; - protected transformerOptions: { - swc?: SwcTransformOptions; - babel?: BabelTransformOptions; - } = {}; + protected swcPreset?: TransformerOptionsPreset; protected injectScriptPaths: string[] = []; protected fullyTransformPackageNames: string[] = []; protected stripFlowPackageNames: string[] = []; @@ -30,16 +30,7 @@ export abstract class TransformPipelineBuilder< constructor( protected root: string, protected entry: string, - transformerOptions?: { - swc?: SwcTransformOptions; - babel?: BabelTransformOptions; - }, - ) { - this.transformerOptions.swc = - transformerOptions?.swc ?? swcPresets.getReactNativeRuntimeOptions(); - this.transformerOptions.babel = - transformerOptions?.babel ?? babelPresets.getCommon(); - } + ) {} protected getNodePackageRegExp(packageNames: string[]): RegExp | null { return packageNames.length @@ -106,5 +97,12 @@ export abstract class TransformPipelineBuilder< return this; } + public setSwcPreset( + preset: TransformerOptionsPreset, + ): this { + this.swcPreset = preset; + return this; + } + abstract build(): Pipeline; } diff --git a/packages/transformer/lib/transformer/babel/babel.ts b/packages/transformer/lib/transformer/babel/babel.ts index 4eefdf0b..95261d9c 100644 --- a/packages/transformer/lib/transformer/babel/babel.ts +++ b/packages/transformer/lib/transformer/babel/babel.ts @@ -1,7 +1,7 @@ import type { TransformOptions } from '@babel/core'; import { loadOptions, transformAsync, transformSync } from '@babel/core'; import type { - Transformer, + AsyncTransformer, SyncTransformer, TransformerContext, } from '../../types'; @@ -17,12 +17,12 @@ const loadBabelOptions = ( }); }; -export const transformWithBabel: Transformer = async ( +export const transformWithBabel: AsyncTransformer = async ( code: string, context, - options, + preset, ) => { - const babelOptions = loadBabelOptions(context, options); + const babelOptions = loadBabelOptions(context, preset?.(context)); if (!babelOptions) { throw new Error('cannot load babel options'); } @@ -38,9 +38,9 @@ export const transformWithBabel: Transformer = async ( export const transformSyncWithBabel: SyncTransformer = ( code: string, context, - options, + preset, ) => { - const babelOptions = loadBabelOptions(context, options); + const babelOptions = loadBabelOptions(context, preset?.(context)); if (!babelOptions) { throw new Error('cannot load babel options'); } diff --git a/packages/transformer/lib/transformer/babel/presets.ts b/packages/transformer/lib/transformer/babel/presets.ts index ced45702..1002980d 100644 --- a/packages/transformer/lib/transformer/babel/presets.ts +++ b/packages/transformer/lib/transformer/babel/presets.ts @@ -1,11 +1,22 @@ import type { TransformOptions } from '@babel/core'; +import type { TransformerOptionsPreset } from '../../types'; -const getCommon = (): TransformOptions => ({ - minified: false, - compact: false, - sourceMaps: false, - babelrc: false, - highlightCode: !process.stdin.isTTY, -}); +const getCommonPreset = (): TransformerOptionsPreset => { + return (_context) => ({ + minified: false, + compact: false, + sourceMaps: false, + babelrc: false, + highlightCode: !process.stdin.isTTY, + }); +}; -export { getCommon }; +const getFullyTransformPreset = + (): TransformerOptionsPreset => { + return (context) => ({ + root: context.root, + babelrc: true, + }); + }; + +export { getCommonPreset, getFullyTransformPreset }; diff --git a/packages/transformer/lib/transformer/swc/presets.ts b/packages/transformer/lib/transformer/swc/presets.ts index 2b79f320..a380e7f0 100644 --- a/packages/transformer/lib/transformer/swc/presets.ts +++ b/packages/transformer/lib/transformer/swc/presets.ts @@ -1,58 +1,91 @@ -import type { Options } from '@swc/core'; -import type { SwcJestPresetOptions } from '../../types'; +import type { Options, TsParserConfig, EsParserConfig } from '@swc/core'; +import type { + TransformerOptionsPreset, + SwcJestPresetOptions, +} from '../../types'; + +const getParserOptions = (path: string): TsParserConfig | EsParserConfig => { + return /\.tsx?$/.test(path) + ? ({ + syntax: 'typescript', + tsx: true, + dynamicImport: true, + } as TsParserConfig) + : ({ + syntax: 'ecmascript', + jsx: true, + exportDefaultFrom: true, + } as EsParserConfig); +}; /** * swc transform options preset for react-native runtime. */ -const getReactNativeRuntimeOptions = (): Options => ({ - minify: false, - sourceMaps: false, - isModule: true, - inputSourceMap: false, - inlineSourcesContent: false, - jsc: { - target: 'es5', - loose: false, - externalHelpers: true, - keepClassNames: true, - }, -}); +const getReactNativeRuntimePreset = (): TransformerOptionsPreset => { + return (context) => ({ + minify: false, + sourceMaps: false, + isModule: true, + inputSourceMap: false, + inlineSourcesContent: false, + jsc: { + parser: getParserOptions(context.path), + target: 'es5', + loose: false, + externalHelpers: true, + keepClassNames: true, + }, + filename: context.path, + root: context.root, + }); +}; -/** - * swc transform options preset for jest. - */ -const getJestOptions = (options: SwcJestPresetOptions): Options => ({ - sourceMaps: 'inline', - jsc: { - target: 'es2022', - transform: { - /** - * @see {@link https://github.com/swc-project/jest/blob/v0.2.29/index.ts#L119} - */ - // @ts-expect-error -- swc sugar code - hidden: { - jest: true, +const getJestPreset = ( + options: SwcJestPresetOptions, +): TransformerOptionsPreset => { + return (context) => ({ + sourceMaps: 'inline', + jsc: { + parser: getParserOptions(context.path), + target: 'es2022', + transform: { + /** + * @see {@link https://github.com/swc-project/jest/blob/v0.2.29/index.ts#L119} + */ + hidden: { + jest: true, + }, + react: { + runtime: 'automatic', + }, }, - react: { - runtime: 'automatic', + experimental: { + ...(options.experimental?.customCoverageInstrumentation + ? { + plugins: [ + [ + 'swc-plugin-coverage-instrument', + options.experimental.customCoverageInstrumentation, + ], + ], + } + : null), }, }, - experimental: { - ...(options.experimental?.customCoverageInstrumentation - ? { - plugins: [ - [ - 'swc-plugin-coverage-instrument', - options.experimental.customCoverageInstrumentation, - ], - ], - } - : null), + module: { + type: options.module === 'esm' ? 'es6' : 'commonjs', }, - }, - module: { - type: options.module === 'esm' ? 'es6' : 'commonjs', - }, -}); + filename: context.path, + root: context.root, + }); +}; + +const getMinifyPreset = () => { + return () => ({ + compress: true, + mangle: true, + sourceMap: false, + }); +}; -export { getReactNativeRuntimeOptions, getJestOptions }; +export { getReactNativeRuntimePreset, getJestPreset, getMinifyPreset }; diff --git a/packages/transformer/lib/transformer/swc/swc.ts b/packages/transformer/lib/transformer/swc/swc.ts index 7d8875e6..9fe79f90 100644 --- a/packages/transformer/lib/transformer/swc/swc.ts +++ b/packages/transformer/lib/transformer/swc/swc.ts @@ -4,53 +4,15 @@ import { minify, type Options, type JsMinifyOptions, - type TsParserConfig, - type EsParserConfig, } from '@swc/core'; -import type { - Transformer, - SyncTransformer, - TransformerContext, -} from '../../types'; +import type { AsyncTransformer, SyncTransformer } from '../../types'; -const getParserOptions = (path: string): TsParserConfig | EsParserConfig => { - return /\.tsx?$/.test(path) - ? ({ - syntax: 'typescript', - tsx: true, - dynamicImport: true, - } as TsParserConfig) - : ({ - syntax: 'ecmascript', - jsx: true, - exportDefaultFrom: true, - } as EsParserConfig); -}; - -const getSwcOptions = ( - context: TransformerContext, - options?: Options, -): Options => { - return { - ...options, - jsc: { - parser: getParserOptions(context.path), - ...options?.jsc, - }, - filename: context.path, - root: context.root, - }; -}; - -export const transformWithSwc: Transformer = async ( +export const transformWithSwc: AsyncTransformer = async ( code, context, - options, + preset, ) => { - const { code: transformedCode } = await transform( - code, - getSwcOptions(context, options), - ); + const { code: transformedCode } = await transform(code, preset?.(context)); if (typeof transformedCode !== 'string') { throw new Error('swc transformed source is empty'); @@ -62,12 +24,9 @@ export const transformWithSwc: Transformer = async ( export const transformSyncWithSwc: SyncTransformer = ( code, context, - options, + preset, ) => { - const { code: transformedCode } = transformSync( - code, - getSwcOptions(context, options), - ); + const { code: transformedCode } = transformSync(code, preset?.(context)); if (typeof transformedCode !== 'string') { throw new Error('swc transformed source is empty'); @@ -76,12 +35,12 @@ export const transformSyncWithSwc: SyncTransformer = ( return transformedCode; }; -export const minifyWithSwc: Transformer = async ( +export const minifyWithSwc: AsyncTransformer = async ( code, - _context, - options, + context, + preset, ) => { - const { code: minifiedCode } = await minify(code, options); + const { code: minifiedCode } = await minify(code, preset?.(context)); if (typeof minifiedCode !== 'string') { throw new Error('swc minified source is empty'); diff --git a/packages/transformer/lib/types.ts b/packages/transformer/lib/types.ts index 034a50db..08ff93d8 100644 --- a/packages/transformer/lib/types.ts +++ b/packages/transformer/lib/types.ts @@ -2,16 +2,16 @@ import type { OnLoadArgs } from 'esbuild'; import type { TransformOptions as BabelTransformOptions } from '@babel/core'; import type { Options as SwcTransformOptions } from '@swc/core'; -export type Transformer = ( +export type AsyncTransformer = ( code: string, context: TransformerContext, - options?: Options, + preset?: TransformerOptionsPreset, ) => Promise; -export type SyncTransformer = ( +export type SyncTransformer = ( code: string, context: TransformerContext, - options?: Options, + preset?: TransformerOptionsPreset, ) => string; export interface TransformerContext { @@ -19,7 +19,15 @@ export interface TransformerContext { root: string; } -// swc presets +export type TransformerOptionsPreset = ( + context: TransformerContext, +) => TransformerOptions; + +// swc preset options +export interface SwcReactNativeRuntimePresetOptions { + reactRefresh?: { moduleId: string }; +} + export interface SwcJestPresetOptions { module?: 'cjs' | 'esm'; experimental?: {