Skip to content

Commit db222d1

Browse files
committed
feat(*): add lightweight version pinning packages
Introduces @clerk/clerk-js-pinned and @clerk/ui-pinned packages for dependency-managed version pinning without heavy transitive dependencies. - Add @clerk/clerk-js-pinned: exports clerkJs branded object and version - Add @clerk/ui-pinned: exports ui branded object with full Appearance types - Add ClerkJs and Ui branded types to @clerk/shared - Add clerkJs prop to IsomorphicClerkOptions - Update loadClerkJsScript to handle clerkJs object - Use dts-bundle-generator to bundle all types inline (zero type deps) Both packages are <1KB gzipped with zero runtime dependencies.
1 parent a9c9ee3 commit db222d1

File tree

14 files changed

+440
-36
lines changed

14 files changed

+440
-36
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"name": "@clerk/clerk-js-pinned",
3+
"version": "5.114.0",
4+
"description": "Lightweight package for pinning @clerk/clerk-js version via dependency management",
5+
"keywords": [
6+
"clerk",
7+
"auth",
8+
"authentication",
9+
"version",
10+
"pinning"
11+
],
12+
"homepage": "https://clerk.com/",
13+
"bugs": {
14+
"url": "https://github.com/clerk/javascript/issues"
15+
},
16+
"repository": {
17+
"type": "git",
18+
"url": "git+https://github.com/clerk/javascript.git",
19+
"directory": "packages/clerk-js-pinned"
20+
},
21+
"license": "MIT",
22+
"author": "Clerk",
23+
"sideEffects": false,
24+
"type": "module",
25+
"exports": {
26+
".": {
27+
"import": {
28+
"types": "./dist/index.d.ts",
29+
"default": "./dist/index.js"
30+
},
31+
"require": {
32+
"types": "./dist/index.d.cts",
33+
"default": "./dist/index.cjs"
34+
}
35+
},
36+
"./package.json": "./package.json"
37+
},
38+
"main": "./dist/index.cjs",
39+
"module": "./dist/index.js",
40+
"types": "./dist/index.d.ts",
41+
"files": [
42+
"dist"
43+
],
44+
"scripts": {
45+
"build": "tsdown && pnpm build:dts",
46+
"build:dts": "dts-bundle-generator -o dist/index.d.ts src/index.ts --no-check && cp dist/index.d.ts dist/index.d.cts",
47+
"clean": "rimraf ./dist",
48+
"format": "node ../../scripts/format-package.mjs",
49+
"format:check": "node ../../scripts/format-package.mjs --check",
50+
"lint": "eslint src",
51+
"lint:attw": "attw --pack . --profile node16",
52+
"lint:publint": "publint"
53+
},
54+
"devDependencies": {
55+
"@clerk/shared": "workspace:^",
56+
"dts-bundle-generator": "^9.5.1",
57+
"tsdown": "catalog:repo"
58+
},
59+
"engines": {
60+
"node": ">=20.9.0"
61+
},
62+
"publishConfig": {
63+
"access": "public"
64+
}
65+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { ClerkJs } from '@clerk/shared/types';
2+
3+
declare const PACKAGE_VERSION: string;
4+
5+
/**
6+
* The version of @clerk/clerk-js that this package pins to.
7+
* Use this with the `clerkJSVersion` prop for string-based pinning.
8+
*
9+
* @example
10+
* ```tsx
11+
* import { version } from '@clerk/clerk-js-pinned';
12+
* <ClerkProvider clerkJSVersion={version} />
13+
* ```
14+
*/
15+
export const version = PACKAGE_VERSION;
16+
17+
/**
18+
* Branded object for pinning @clerk/clerk-js version.
19+
* Use this with the `clerkJs` prop in ClerkProvider for dependency-managed pinning.
20+
*
21+
* @example
22+
* ```tsx
23+
* import { clerkJs } from '@clerk/clerk-js-pinned';
24+
* <ClerkProvider clerkJs={clerkJs} />
25+
* ```
26+
*/
27+
export const clerkJs = {
28+
version: PACKAGE_VERSION,
29+
} as ClerkJs;
30+
31+
// Re-export the type for consumers who need it
32+
export type { ClerkJs } from '@clerk/shared/types';
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"compilerOptions": {
3+
"rootDir": "src",
4+
"verbatimModuleSyntax": true,
5+
"target": "es2022",
6+
"strict": true,
7+
"skipLibCheck": true,
8+
"resolveJsonModule": true,
9+
"outDir": "dist",
10+
"noUnusedLocals": true,
11+
"moduleResolution": "bundler",
12+
"moduleDetection": "force",
13+
"module": "preserve",
14+
"lib": ["ES2023"],
15+
"isolatedModules": true,
16+
"forceConsistentCasingInFileNames": true,
17+
"esModuleInterop": true,
18+
"emitDeclarationOnly": true,
19+
"declaration": true,
20+
"declarationMap": true
21+
},
22+
"exclude": ["node_modules", "dist"],
23+
"include": ["src"]
24+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { defineConfig } from 'tsdown';
2+
3+
import packageJson from './package.json' with { type: 'json' };
4+
5+
export default defineConfig({
6+
entry: ['./src/index.ts'],
7+
outDir: './dist',
8+
dts: false, // We use dts-bundle-generator for bundled types
9+
sourcemap: true,
10+
clean: true,
11+
target: 'es2022',
12+
platform: 'neutral',
13+
format: ['cjs', 'esm'],
14+
minify: false,
15+
external: [],
16+
define: {
17+
PACKAGE_NAME: `"${packageJson.name}"`,
18+
PACKAGE_VERSION: `"${packageJson.version}"`,
19+
},
20+
});

packages/shared/src/loadClerkJsScript.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ export type LoadClerkJsScriptOptions = {
1515
clerkJSUrl?: string;
1616
clerkJSVariant?: 'headless' | '';
1717
clerkJSVersion?: string;
18+
/**
19+
* Branded object for pinning @clerk/clerk-js version.
20+
* Takes precedence over clerkJSVersion if both are provided.
21+
*/
22+
clerkJs?: { version: string };
1823
sdkMetadata?: SDKMetadata;
1924
proxyUrl?: string;
2025
domain?: string;
@@ -217,15 +222,16 @@ export const loadClerkUiScript = async (opts?: LoadClerkUiScriptOptions): Promis
217222
};
218223

219224
export const clerkJsScriptUrl = (opts: LoadClerkJsScriptOptions) => {
220-
const { clerkJSUrl, clerkJSVariant, clerkJSVersion, proxyUrl, domain, publishableKey } = opts;
225+
const { clerkJSUrl, clerkJSVariant, clerkJSVersion, clerkJs, proxyUrl, domain, publishableKey } = opts;
221226

222227
if (clerkJSUrl) {
223228
return clerkJSUrl;
224229
}
225230

226231
const scriptHost = buildScriptHost({ publishableKey, proxyUrl, domain });
227232
const variant = clerkJSVariant ? `${clerkJSVariant.replace(/\.+$/, '')}.` : '';
228-
const version = versionSelector(clerkJSVersion);
233+
// clerkJs object takes precedence over clerkJSVersion string
234+
const version = versionSelector(clerkJs?.version ?? clerkJSVersion);
229235
return `https://${scriptHost}/npm/@clerk/clerk-js@${version}/dist/clerk.${variant}browser.js`;
230236
};
231237

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Branded/Tagged type utilities for creating nominal types.
3+
* These are used to ensure only official Clerk objects can be passed to certain props.
4+
*/
5+
6+
declare const Tags: unique symbol;
7+
8+
/**
9+
* Creates a branded/tagged type that prevents arbitrary objects from being assigned.
10+
* The tag exists only at the type level and has no runtime overhead.
11+
*
12+
* @example
13+
* ```typescript
14+
* type UserId = Tagged<string, 'UserId'>;
15+
* const userId: UserId = 'user_123' as UserId;
16+
* ```
17+
*/
18+
export type Tagged<BaseType, Tag extends PropertyKey> = BaseType & { [Tags]: { [K in Tag]: void } };
19+
20+
/**
21+
* Branded type for Clerk JS version pinning objects.
22+
* Used with the `clerkJs` prop in ClerkProvider.
23+
*
24+
* @example
25+
* ```typescript
26+
* import { clerkJs } from '@clerk/clerk-js-pinned';
27+
* <ClerkProvider clerkJs={clerkJs} />
28+
* ```
29+
*/
30+
export type ClerkJs = Tagged<
31+
{
32+
/**
33+
* The version string of @clerk/clerk-js to load from CDN.
34+
*/
35+
version: string;
36+
},
37+
'ClerkJs'
38+
>;
39+
40+
/**
41+
* Branded type for Clerk UI version pinning objects.
42+
* Carries appearance type information via phantom property for type-safe customization.
43+
*
44+
* @example
45+
* ```typescript
46+
* import { ui } from '@clerk/ui-pinned';
47+
* <ClerkProvider ui={ui} appearance={{ ... }} />
48+
* ```
49+
*/
50+
export type Ui<A = any> = Tagged<
51+
{
52+
/**
53+
* The version string of @clerk/ui to load from CDN.
54+
*/
55+
version: string;
56+
/**
57+
* Optional custom URL to load @clerk/ui from.
58+
*/
59+
url?: string;
60+
/**
61+
* Phantom property for type-level appearance inference.
62+
* This property never exists at runtime.
63+
* @internal
64+
*/
65+
__appearanceType?: A;
66+
},
67+
'ClerkUi'
68+
>;
69+
70+
/**
71+
* Extracts the appearance type from a Ui object. We have 3 cases:
72+
* - If the Ui type has __appearanceType with a specific type, extract it
73+
* - If __appearanceType is 'any', fallback to base Appearance type
74+
* - Otherwise, fallback to the base Appearance type
75+
*/
76+
export type ExtractAppearanceType<T, Default> = T extends { __appearanceType?: infer A }
77+
? 0 extends 1 & A // Check if A is 'any' (this trick works because 1 & any = any, and 0 extends any)
78+
? Default
79+
: A
80+
: Default;

packages/shared/src/types/clerk.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2423,6 +2423,17 @@ export type IsomorphicClerkOptions = Without<ClerkOptions, 'isSatellite'> & {
24232423
* The npm version for `@clerk/clerk-js`.
24242424
*/
24252425
clerkJSVersion?: string;
2426+
/**
2427+
* Branded object for pinning @clerk/clerk-js version.
2428+
* Import from `@clerk/clerk-js-pinned` for dependency-managed version pinning.
2429+
*
2430+
* @example
2431+
* ```tsx
2432+
* import { clerkJs } from '@clerk/clerk-js-pinned';
2433+
* <ClerkProvider clerkJs={clerkJs} />
2434+
* ```
2435+
*/
2436+
clerkJs?: { version: string };
24262437
/**
24272438
* The URL that `@clerk/ui` should be hot-loaded from.
24282439
*/
@@ -2441,9 +2452,14 @@ export type IsomorphicClerkOptions = Without<ClerkOptions, 'isSatellite'> & {
24412452
*/
24422453
nonce?: string;
24432454
/**
2444-
* @internal
2445-
* This is a structural-only type for the `ui` object that can be passed
2446-
* to Clerk.load() and ClerkProvider
2455+
* Branded object for pinning @clerk/ui version with full appearance type support.
2456+
* Import from `@clerk/ui-pinned` for dependency-managed version pinning.
2457+
*
2458+
* @example
2459+
* ```tsx
2460+
* import { ui } from '@clerk/ui-pinned';
2461+
* <ClerkProvider ui={ui} appearance={{ ... }} />
2462+
* ```
24472463
*/
24482464
ui?: { version: string; url?: string };
24492465
} & MultiDomainAndOrProxy;

packages/shared/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export type * from './apiKeys';
2+
export type * from './branded';
23
export type * from './apiKeysSettings';
34
export type * from './attributes';
45
export type * from './authConfig';

packages/ui-pinned/package.json

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
{
2+
"name": "@clerk/ui-pinned",
3+
"version": "0.0.1",
4+
"description": "Lightweight package for pinning @clerk/ui version via dependency management with full type support",
5+
"keywords": [
6+
"clerk",
7+
"ui",
8+
"version",
9+
"pinning",
10+
"theming"
11+
],
12+
"homepage": "https://clerk.com/",
13+
"bugs": {
14+
"url": "https://github.com/clerk/javascript/issues"
15+
},
16+
"repository": {
17+
"type": "git",
18+
"url": "git+https://github.com/clerk/javascript.git",
19+
"directory": "packages/ui-pinned"
20+
},
21+
"license": "MIT",
22+
"author": "Clerk",
23+
"sideEffects": false,
24+
"type": "module",
25+
"exports": {
26+
".": {
27+
"import": {
28+
"types": "./dist/index.d.ts",
29+
"default": "./dist/index.js"
30+
},
31+
"require": {
32+
"types": "./dist/index.d.cts",
33+
"default": "./dist/index.cjs"
34+
}
35+
},
36+
"./package.json": "./package.json"
37+
},
38+
"main": "./dist/index.cjs",
39+
"module": "./dist/index.js",
40+
"types": "./dist/index.d.ts",
41+
"files": [
42+
"dist"
43+
],
44+
"scripts": {
45+
"build": "tsdown && pnpm build:dts",
46+
"build:dts": "dts-bundle-generator -o dist/index.d.ts src/index.ts --no-check && cp dist/index.d.ts dist/index.d.cts",
47+
"clean": "rimraf ./dist",
48+
"format": "node ../../scripts/format-package.mjs",
49+
"format:check": "node ../../scripts/format-package.mjs --check",
50+
"lint": "eslint src",
51+
"lint:attw": "attw --pack . --profile node16",
52+
"lint:publint": "publint"
53+
},
54+
"devDependencies": {
55+
"@clerk/shared": "workspace:^",
56+
"@clerk/ui": "workspace:^",
57+
"dts-bundle-generator": "^9.5.1",
58+
"tsdown": "catalog:repo"
59+
},
60+
"engines": {
61+
"node": ">=20.9.0"
62+
},
63+
"publishConfig": {
64+
"access": "public"
65+
}
66+
}

0 commit comments

Comments
 (0)