diff --git a/.changeset/wide-dodos-film.md b/.changeset/wide-dodos-film.md new file mode 100644 index 00000000..0967562d --- /dev/null +++ b/.changeset/wide-dodos-film.md @@ -0,0 +1,8 @@ +--- +'@granite-js/react-native': patch +'@granite-js/plugin-core': patch +'@granite-js/native': patch +'@granite-js/mpack': patch +--- + +fix: supports unicode property escape step diff --git a/packages/mpack/package.json b/packages/mpack/package.json index 72f7e30c..96d1e1a4 100644 --- a/packages/mpack/package.json +++ b/packages/mpack/package.json @@ -81,6 +81,7 @@ "@babel/plugin-proposal-private-methods": "7.18.6", "@babel/plugin-proposal-private-property-in-object": "7.21.11", "@babel/plugin-transform-flow-strip-types": "7.27.1", + "@babel/plugin-transform-unicode-regex": "7.27.1", "@babel/preset-env": "7.28.5", "@babel/preset-react": "7.28.5", "@babel/preset-typescript": "7.28.5", diff --git a/packages/mpack/src/bundler/internal/presets.ts b/packages/mpack/src/bundler/internal/presets.ts index 3ccceb23..8f8e464f 100644 --- a/packages/mpack/src/bundler/internal/presets.ts +++ b/packages/mpack/src/bundler/internal/presets.ts @@ -58,18 +58,6 @@ export function combineWithBaseBuildConfig( ].join('\n'), }, }, - babel: { - conditions: [ - /** - * @TODO - * We're using a RegExp in Zod that's not supported by Hermes, - * so we're switching to Babel for transpilation since there's no compatible SWC config or plugin available. - * - * @see zod {@link https://github.com/colinhacks/zod/issues/2302} - */ - (_code: string, path: string) => path.includes('node_modules/zod'), - ], - }, }, config.buildConfig ); diff --git a/packages/mpack/src/bundler/plugins/transformPlugin/steps/createFullyTransformStep.ts b/packages/mpack/src/bundler/plugins/transformPlugin/steps/createFullyTransformStep.ts deleted file mode 100644 index 38a4acbb..00000000 --- a/packages/mpack/src/bundler/plugins/transformPlugin/steps/createFullyTransformStep.ts +++ /dev/null @@ -1,98 +0,0 @@ -import * as path from 'path'; -import * as babel from '@babel/core'; -import { AsyncTransformStep } from '../../../../transformer/TransformPipeline'; -import { defineStepName } from '../../../../utils/defineStepName'; - -interface FullyTransformStepConfig { - dev: boolean; - additionalBabelOptions?: babel.TransformOptions; -} - -export function createFullyTransformStep({ - dev, - additionalBabelOptions, -}: FullyTransformStepConfig): AsyncTransformStep { - const baseOptions: babel.TransformOptions = { - configFile: additionalBabelOptions?.configFile || false, - presets: [ - [ - /** - * React Native Hermes 대응을 위해 - * 최대한 낮은 버전의 JS 엔진을 지원하도록 IE 11로 설정 - * 추후 정확한 target으로 설정 필요 - */ - require.resolve('@babel/preset-env'), - { - targets: { - ie: 11, - }, - /** - * supportsStaticESM 이 true 이면 modules 가 false 로 처리되어야 하는데, - * 기본값('auto')이 적용되어 안내 로그가 찍히고 있어 직접 값 지정 - * - * @see source {@link https://github.com/babel/babel/blob/v7.23.10/packages/babel-preset-env/src/index.ts#L398-L403} - */ - modules: false, - }, - ], - /** - * react-native-reanimated 등 TypeScript를 직접 export하는 라이브러리를 다루기 위해 - * @babel/preset-typescript 포함 필요 - */ - require.resolve('@babel/preset-typescript'), - /** - * react-native-reanimated 등 .tsx 직접 export하는 라이브러리를 다루기 위해 - * @babel/preset-react 포함 필요 - */ - [require.resolve('@babel/preset-react'), { runtime: 'automatic' }], - ...(additionalBabelOptions?.presets ?? []), - ], - plugins: [ - /** - * react-native에서 직접 export 하는 flow 파일 대응을 위해 strip types 추가 필요 - */ - require.resolve('@babel/plugin-transform-flow-strip-types'), - [require.resolve('@babel/plugin-proposal-class-properties'), { loose: true }], - [require.resolve('@babel/plugin-proposal-private-property-in-object'), { loose: true }], - [require.resolve('@babel/plugin-proposal-private-methods'), { loose: true }], - ...(additionalBabelOptions?.plugins ?? []), - ], - }; - - const fullyTransformStep: AsyncTransformStep = async function fullyTransform(code, args) { - const babelOptions = babel.loadOptions({ - minified: false, - compact: false, - babelrc: false, - configFile: false, - envName: dev ? 'development' : 'production', - ...baseOptions, - sourceMaps: 'inline', - filename: path.basename(args.path), - caller: { - name: 'mpack-fully-transform-plugin', - supportsStaticESM: true, - }, - }) as babel.TransformOptions | null; - - if (!babelOptions) { - return { code }; - } - - if (babelOptions.sourceMaps) { - babelOptions.sourceFileName = path.basename(args.path); - } - - const result = await babel.transformAsync(code, babelOptions); - - if (result?.code != null) { - return { code: result.code }; - } - - throw new Error('babel transform result is null'); - }; - - defineStepName(fullyTransformStep, 'fully-transform'); - - return fullyTransformStep; -} diff --git a/packages/mpack/src/bundler/plugins/transformPlugin/steps/createTransformUnicodePropertyEscapeStep.ts b/packages/mpack/src/bundler/plugins/transformPlugin/steps/createTransformUnicodePropertyEscapeStep.ts new file mode 100644 index 00000000..ab11bfe1 --- /dev/null +++ b/packages/mpack/src/bundler/plugins/transformPlugin/steps/createTransformUnicodePropertyEscapeStep.ts @@ -0,0 +1,32 @@ +import { transformSync } from '@babel/core'; +import { AsyncTransformStep } from '../../../../transformer/TransformPipeline'; +import { defineStepName } from '../../../../utils/defineStepName'; + +export function createTransformUnicodePropertyEscapeStep(): AsyncTransformStep { + const unicodePropertyEscapeRegExp = /\\p\{[^}]+\}/u; + + const transformUnicodePropertyEscapeStep: AsyncTransformStep = async (code, args) => { + if (!unicodePropertyEscapeRegExp.test(code)) { + return { code }; + } + + const result = transformSync(code, { + presets: [require.resolve('@babel/preset-typescript')], + plugins: [require.resolve('@babel/plugin-transform-unicode-regex')], + filename: args.path, + sourceMaps: false, + babelrc: false, + configFile: false, + }); + + if (result?.code != null) { + return { code: result.code }; + } + + return { code }; + }; + + defineStepName(transformUnicodePropertyEscapeStep, 'transform-unicode-property-escape'); + + return transformUnicodePropertyEscapeStep; +} diff --git a/packages/mpack/src/bundler/plugins/transformPlugin/transformPlugin.ts b/packages/mpack/src/bundler/plugins/transformPlugin/transformPlugin.ts index 86b0f6f7..b24c1d27 100644 --- a/packages/mpack/src/bundler/plugins/transformPlugin/transformPlugin.ts +++ b/packages/mpack/src/bundler/plugins/transformPlugin/transformPlugin.ts @@ -3,9 +3,9 @@ import * as fs from 'fs/promises'; import { Plugin } from 'esbuild'; import * as preludeScript from './helpers/preludeScript'; import { createCacheSteps } from './steps/createCacheSteps'; -import { createFullyTransformStep } from './steps/createFullyTransformStep'; import { createStripFlowStep } from './steps/createStripFlowStep'; import { createTransformToHermesSyntaxStep } from './steps/createTransformToHermesSyntaxStep'; +import { createTransformUnicodePropertyEscapeStep } from './steps/createTransformUnicodePropertyEscapeStep'; import { Performance } from '../../../performance'; import { AsyncTransformPipeline } from '../../../transformer'; import { PluginOptions } from '../types'; @@ -23,7 +23,7 @@ export function transformPlugin({ context, ...options }: PluginOptions boolean>; configFile?: string; presets?: string[]; plugins?: (string | [string, any])[]; diff --git a/packages/plugin-core/src/utils/mergeBabel.spec.ts b/packages/plugin-core/src/utils/mergeBabel.spec.ts index 72516f81..9cba1922 100644 --- a/packages/plugin-core/src/utils/mergeBabel.spec.ts +++ b/packages/plugin-core/src/utils/mergeBabel.spec.ts @@ -35,19 +35,6 @@ describe('mergeBabel', () => { expect(result?.plugins).toEqual(['plugin1', 'plugin2']); }); - it('merges conditions arrays', () => { - const condition1 = (code: string) => code.includes('test1'); - const condition2 = (code: string) => code.includes('test2'); - - const source = { conditions: [condition1] }; - const target = { conditions: [condition2] }; - const result = mergeBabel(source, target); - - expect(result?.conditions).toHaveLength(2); - expect(result?.conditions).toContain(condition1); - expect(result?.conditions).toContain(condition2); - }); - it('handles missing arrays gracefully', () => { const source = { presets: ['preset1'] }; const target = { plugins: ['plugin1'] }; @@ -66,21 +53,16 @@ describe('mergeBabel', () => { }); it('performs complete merge with all properties', () => { - const condition1 = (code: string) => code.includes('test1'); - const condition2 = (code: string) => code.includes('test2'); - const source = { configFile: 'babel.config.js', presets: ['preset1'], plugins: ['plugin1'], - conditions: [condition1], }; const target = { configFile: 'babel.config.json', presets: ['preset2'], plugins: ['plugin2'], - conditions: [condition2], }; const result = mergeBabel(source, target); @@ -89,7 +71,6 @@ describe('mergeBabel', () => { configFile: 'babel.config.json', presets: ['preset1', 'preset2'], plugins: ['plugin1', 'plugin2'], - conditions: [condition1, condition2], }); }); }); diff --git a/packages/plugin-core/src/utils/mergeBabel.ts b/packages/plugin-core/src/utils/mergeBabel.ts index 6b8db716..591607b6 100644 --- a/packages/plugin-core/src/utils/mergeBabel.ts +++ b/packages/plugin-core/src/utils/mergeBabel.ts @@ -18,6 +18,5 @@ export function mergeBabel(source: BuildConfig['babel'], target: BuildConfig['ba ...target, presets: [...(source.presets ?? []), ...(target.presets ?? [])], plugins: [...(source.plugins ?? []), ...(target.plugins ?? [])], - conditions: [...(source.conditions ?? []), ...(target.conditions ?? [])], }; } diff --git a/packages/plugin-core/src/utils/mergeConfig.spec.ts b/packages/plugin-core/src/utils/mergeConfig.spec.ts index cafbc42a..308d2bb9 100644 --- a/packages/plugin-core/src/utils/mergeConfig.spec.ts +++ b/packages/plugin-core/src/utils/mergeConfig.spec.ts @@ -8,8 +8,6 @@ describe('mergeConfig', () => { const mockLoad2 = vitest.fn(); const mockPluginOption = {}; - const mockCondition1 = vitest.fn(); - const mockCondition2 = vitest.fn(); const mockMiddleware1 = vitest.fn(); const mockMiddleware2 = vitest.fn(); @@ -32,7 +30,6 @@ describe('mergeConfig', () => { babel: { presets: ['preset1'], plugins: [['plugin1', mockPluginOption], 'plugin2'], - conditions: [mockCondition1], }, esbuild: { minify: true, @@ -70,7 +67,6 @@ describe('mergeConfig', () => { babel: { presets: ['preset2'], plugins: ['plugin3'], - conditions: [mockCondition2], }, esbuild: { sourcemap: true, @@ -122,7 +118,6 @@ describe('mergeConfig', () => { }, babel: { presets: ['preset1', 'preset2'], - conditions: [mockCondition1, mockCondition2], plugins: [['plugin1', mockPluginOption], 'plugin2', 'plugin3'], }, esbuild: { @@ -264,8 +259,6 @@ describe('mergeConfig', () => { const mockLoad2 = vitest.fn(); const mockPluginOption = {}; - const mockCondition1 = vitest.fn(); - const mockCondition2 = vitest.fn(); const mockMiddleware1 = vitest.fn(); const mockMiddleware2 = vitest.fn(); @@ -337,7 +330,6 @@ describe('mergeConfig', () => { const target2: GranitePluginCore['config'] = { babel: { plugins: ['plugin3'], - conditions: [mockCondition1], }, swc: { plugins: ['swc-plugin' as any], @@ -356,7 +348,6 @@ describe('mergeConfig', () => { }, babel: { plugins: ['plugin4'], - conditions: [mockCondition2], }, esbuild: { prelude: ['source-2.js'], @@ -409,7 +400,6 @@ describe('mergeConfig', () => { babel: { presets: ['preset1', 'preset2'], plugins: [['plugin1', {}], 'plugin2', 'plugin3', 'plugin4'], - conditions: [mockCondition1, mockCondition2], }, metro: { middlewares: [mockMiddleware1, mockMiddleware2], diff --git a/services/counter/src/router.gen.ts b/services/counter/src/router.gen.ts index 81e66613..1cbf474e 100644 --- a/services/counter/src/router.gen.ts +++ b/services/counter/src/router.gen.ts @@ -1,4 +1,3 @@ -/* eslint-disable */ // This file is auto-generated by @granite-js/react-native. DO NOT EDIT. import { Route as _IndexRoute } from '../pages/'; diff --git a/services/showcase/package.json b/services/showcase/package.json index 9131d530..4853b7fa 100644 --- a/services/showcase/package.json +++ b/services/showcase/package.json @@ -13,8 +13,8 @@ "@granite-js/react-native": "workspace:*", "react": "18.2.0", "react-native": "0.72.6", - "valibot": "^1.1.0", - "zod": "^4.1.12" + "valibot": "^1.2.0", + "zod": "^4.1.13" }, "devDependencies": { "@babel/core": "7.28.5", diff --git a/services/showcase/src/pages/about.tsx b/services/showcase/src/pages/about.tsx index 30bdf943..81ad688f 100644 --- a/services/showcase/src/pages/about.tsx +++ b/services/showcase/src/pages/about.tsx @@ -1,12 +1,17 @@ import { createRoute, Stack } from '@granite-js/react-native'; import { StyleSheet, Text } from 'react-native'; +import * as v from 'valibot'; import { Button } from '../components/Button'; export const Route = createRoute('/about', { component: Page, + validateParams: v.object({ + name: v.string(), + }), }); function Page() { + const params = Route.useParams(); const navigation = Route.useNavigation(); const handleGoShowcase = () => { @@ -19,7 +24,7 @@ function Page() { return ( - About Granite + About {params.name} Granite is a powerful and flexible React Native Framework 🚀