From 81dcec33f4b38b9b81d7735a149df1ed8e37cb8f Mon Sep 17 00:00:00 2001 From: Ygor Perez Date: Wed, 3 Jun 2026 13:18:24 -0300 Subject: [PATCH 1/2] fix(build): Unblock builds and typecheck under TypeScript 6 Two fallouts from the typescript@^6.0.3 bump: - tsup's dts worker injects baseUrl into generated compiler options, which TS 6 rejects with TS5101 (baseUrl deprecated). Every package build failed in the DTS step. Tolerate it via ignoreDeprecations. - TS 6 enforces the inferred rootDir strictly, so the paths mapping to ../core/src fails typecheck with TS6059 in react/vue/solid. Set an explicit monorepo rootDir; with noEmit this has no output effect. --- packages/core/tsconfig.json | 1 + packages/react/tsconfig.json | 2 ++ packages/solid/tsconfig.json | 2 ++ packages/vue/tsconfig.json | 2 ++ tsconfig.json | 1 + 5 files changed, 8 insertions(+) diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 0c94ac8..dd01524 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "ignoreDeprecations": "6.0", "target": "ES2020", "module": "ESNext", "lib": ["ES2020"], diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json index 74cd92b..a801f99 100644 --- a/packages/react/tsconfig.json +++ b/packages/react/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "ignoreDeprecations": "6.0", "target": "ES2020", "module": "ESNext", "lib": ["ES2020", "DOM", "DOM.Iterable"], @@ -15,6 +16,7 @@ "skipLibCheck": true, "moduleResolution": "bundler", "baseUrl": ".", + "rootDir": "../..", "paths": { "@sankyu/circle-flags-core": ["../core/src"], "@sankyu/circle-flags-core/*": ["../core/src/*"] diff --git a/packages/solid/tsconfig.json b/packages/solid/tsconfig.json index 814ea99..80646fe 100644 --- a/packages/solid/tsconfig.json +++ b/packages/solid/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "ignoreDeprecations": "6.0", "target": "ES2020", "module": "ESNext", "lib": ["ES2020", "DOM"], @@ -8,6 +9,7 @@ "strict": true, "moduleResolution": "bundler", "baseUrl": ".", + "rootDir": "../..", "paths": { "@sankyu/circle-flags-core": ["../core/src"], "@sankyu/circle-flags-core/*": ["../core/src/*"], diff --git a/packages/vue/tsconfig.json b/packages/vue/tsconfig.json index 3e8a34e..7dccf0b 100644 --- a/packages/vue/tsconfig.json +++ b/packages/vue/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "ignoreDeprecations": "6.0", "target": "ES2020", "module": "ESNext", "lib": ["ES2020", "DOM", "DOM.Iterable"], @@ -14,6 +15,7 @@ "skipLibCheck": true, "moduleResolution": "bundler", "baseUrl": ".", + "rootDir": "../..", "paths": { "@sankyu/circle-flags-core": ["../core/src"], "@sankyu/circle-flags-core/*": ["../core/src/*"] diff --git a/tsconfig.json b/tsconfig.json index a8af58b..cf6ecdf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "ignoreDeprecations": "6.0", "target": "ES2020", "module": "ESNext", "lib": ["ES2020"], From 618af1353a9f5221e67caaa97aebe8665d558fee Mon Sep 17 00:00:00 2001 From: Ygor Perez Date: Wed, 3 Jun 2026 13:18:38 -0300 Subject: [PATCH 2/2] feat(react): Expose per-flag subpath exports with typed declarations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a ./flags/* entry to the exports map so each flag is importable as its own module: import { FlagBr } from '@sankyu/react-circle-flags/flags/br' The per-flag bundles already existed in dist/flags/ but were not reachable through the exports map. Subpath imports guarantee that only the imported flag lands in the consumer bundle even when the bundler cannot tree-shake the root barrel — Turbopack (Next.js 16 default) currently bundles all ~430 flags (~400 kB minified) for a single named import from the package root. The postbuild step now generates a .d.ts per flag from the generated sources (instead of deleting tsup's broken dts chunks): component flags get a typed declaration mirroring the source signature, alias flags re-export their target so TypeScript resolves through the sibling declaration. Verified: 430 declarations generated; ESM import and CJS require of both a component flag (flags/br) and an alias flag (flags/uk) resolve through the exports map; react test suite passes. --- packages/react/README.md | 17 ++++++++++++ packages/react/package.json | 5 ++++ scripts/tasks/postbuild-react.mjs | 45 +++++++++++++++++++++++++------ 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/packages/react/README.md b/packages/react/README.md index ec712cd..960f651 100644 --- a/packages/react/README.md +++ b/packages/react/README.md @@ -103,6 +103,23 @@ export default function App() { } ``` +### Per-flag subpath imports + +Each flag is also exposed as its own module under `./flags/` (ISO code, +lowercase). This guarantees only the imported flag lands in your bundle even +when your bundler can't tree-shake the barrel (e.g. Turbopack as of Next.js 16): + +```tsx +import { FlagUs } from '@sankyu/react-circle-flags/flags/us' +import { FlagBr } from '@sankyu/react-circle-flags/flags/br' +``` + +> [!NOTE] +> Named imports from the package root are equivalent under bundlers with +> whole-barrel tree-shaking (webpack, Rollup, esbuild). Prefer subpaths when +> you need the guarantee, or when measuring shows barrel imports bloating +> your chunks. + ### Other usage examples > [!TIP] diff --git a/packages/react/package.json b/packages/react/package.json index 60a0a44..5d87483 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -11,6 +11,11 @@ "types": "./dist/index.d.ts", "import": "./dist/index.mjs", "require": "./dist/index.cjs" + }, + "./flags/*": { + "types": "./dist/flags/*.d.ts", + "import": "./dist/flags/*.mjs", + "require": "./dist/flags/*.cjs" } }, "files": [ diff --git a/scripts/tasks/postbuild-react.mjs b/scripts/tasks/postbuild-react.mjs index 8366ead..eb47cb6 100644 --- a/scripts/tasks/postbuild-react.mjs +++ b/scripts/tasks/postbuild-react.mjs @@ -26,19 +26,48 @@ export const postbuildReact = () => { process.exit(1) } + // Generate per-flag .d.ts files so the `./flags/*` subpath exports are + // fully typed. tsup's dts rollup produces broken chunks for the per-flag + // entries (see packages/react/tsup.config.ts), so declarations are emitted + // here from the generated sources instead. Any stray tsup-emitted .d.ts is + // overwritten in the process. try { - const files = readdirSync(flagsDir) - let removedCount = 0 + const generatedDir = join(process.cwd(), 'generated/flags') + const sources = readdirSync(generatedDir).filter(f => f.endsWith('.tsx')) + let declared = 0 - for (const file of files) { - if (!file.endsWith('.d.ts')) continue - unlinkSync(join(flagsDir, file)) - removedCount++ + for (const file of sources) { + const code = file.replace(/\.tsx$/, '') + const source = readFileSync(join(generatedDir, file), 'utf-8') + + // Alias flags re-export their target: keep the same shape in the .d.ts + // so TypeScript resolves through the sibling declaration. + const alias = source.match(/^export \{ (\w+) as (\w+) \} from '\.\/([\w-]+)'/) + + let dts + if (alias) { + dts = `export { ${alias[1]} as ${alias[2]} } from './${alias[3]}'\n` + } else { + const name = source.match(/export const (Flag\w+)/)?.[1] + if (!name) { + throw new Error(`Could not find flag export in generated/flags/${file}`) + } + dts = `import type { ReactElement, SVGProps } from 'react' +import type { FlagComponentProps } from '@sankyu/circle-flags-core' +export declare const ${name}: ( + props: SVGProps & FlagComponentProps +) => ReactElement +` + } + + writeFileSync(join(flagsDir, `${code}.d.ts`), dts, 'utf-8') + declared++ } - console.log(`✅ Removed ${removedCount} redundant .d.ts files in dist/flags/`) + console.log(`✅ Generated ${declared} per-flag .d.ts files in dist/flags/`) } catch (error) { - console.error('⚠️ Failed to cleanup dist/flags/*.d.ts:', error) + console.error('❌ Failed to generate dist/flags/*.d.ts:', error) + process.exit(1) } const dctsPath = join(process.cwd(), 'dist/index.d.cts')