@superblocksteam/sdk 2.0.141 → 2.0.142-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/dev-utils/dev-server.optimize-deps.test.mjs +242 -2
- package/dist/dev-utils/dev-server.optimize-deps.test.mjs.map +1 -1
- package/dist/dev-utils/optimize-deps-config.d.mts +14 -0
- package/dist/dev-utils/optimize-deps-config.d.mts.map +1 -1
- package/dist/dev-utils/optimize-deps-config.mjs +104 -8
- package/dist/dev-utils/optimize-deps-config.mjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/telemetry/startup-context.d.ts +17 -0
- package/dist/telemetry/startup-context.d.ts.map +1 -0
- package/dist/telemetry/startup-context.js +43 -0
- package/dist/telemetry/startup-context.js.map +1 -0
- package/dist/telemetry/startup-context.test.d.ts +2 -0
- package/dist/telemetry/startup-context.test.d.ts.map +1 -0
- package/dist/telemetry/startup-context.test.js +70 -0
- package/dist/telemetry/startup-context.test.js.map +1 -0
- package/dist/vite-plugin-optimize-lucide-imports.d.ts +20 -0
- package/dist/vite-plugin-optimize-lucide-imports.d.ts.map +1 -1
- package/dist/vite-plugin-optimize-lucide-imports.js +19 -4
- package/dist/vite-plugin-optimize-lucide-imports.js.map +1 -1
- package/package.json +6 -6
- package/src/dev-utils/dev-server.optimize-deps.test.mts +334 -2
- package/src/dev-utils/optimize-deps-config.mts +106 -8
- package/src/index.ts +7 -0
- package/src/telemetry/startup-context.test.ts +102 -0
- package/src/telemetry/startup-context.ts +77 -0
- package/src/vite-plugin-optimize-lucide-imports.ts +21 -4
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
import type { UserConfig } from "vite";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Deep-path prefix that `vite-plugin-optimize-lucide-imports` rewrites every
|
|
5
|
+
* `import { X } from "lucide-react"` into (`lucide-react/dist/esm/icons/x.js`).
|
|
6
|
+
* Shared between the rewrite plugin and the optimizeDeps include glob below so
|
|
7
|
+
* the pre-bundled paths can never drift from the paths the plugin emits.
|
|
8
|
+
*/
|
|
9
|
+
export const LUCIDE_ICON_DEEP_PATH_PREFIX = "lucide-react/dist/esm/icons/";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Selection rule for the include list below: every dependency declared by BOTH
|
|
13
|
+
* live-edit templates is force-included, EXCEPT the packages listed here. Each
|
|
14
|
+
* exclusion needs a reason; the contract is enforced in both directions by
|
|
15
|
+
* `dev-server.optimize-deps.test.mts`.
|
|
16
|
+
*/
|
|
17
|
+
export const NOT_PRE_OPTIMIZED_TEMPLATE_DEPS: readonly string[] = [
|
|
18
|
+
// The hot-swapped Superblocks runtime: generated apps receive it through the
|
|
19
|
+
// SDK plugin pipeline, and a prebundled duplicate splits React context/hook
|
|
20
|
+
// state between the two copies. Only its `/jsx-dev-runtime` subpath (which
|
|
21
|
+
// the dep scan structurally cannot discover) is force-included.
|
|
22
|
+
"@superblocksteam/library",
|
|
23
|
+
// Node-side Vite build plugin, never imported by browser code. Bundling it
|
|
24
|
+
// drags in lightningcss/oxide native binaries and crashes the optimizer —
|
|
25
|
+
// the same reason `*.config.*` files are excluded from scan entries above.
|
|
26
|
+
"@tailwindcss/vite",
|
|
27
|
+
];
|
|
28
|
+
|
|
3
29
|
/**
|
|
4
30
|
* Shared `optimizeDeps` config for both the pre-warm and activation Vite
|
|
5
31
|
* servers (see `dev-server.mts`).
|
|
@@ -59,14 +85,86 @@ export const OPTIMIZE_DEPS_CONFIG: UserConfig["optimizeDeps"] = {
|
|
|
59
85
|
// EE warm-pool box: without this, a freshly-generated app still reloads once
|
|
60
86
|
// on first render for the JSX runtime — APPS-4576).
|
|
61
87
|
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
// `@superblocksteam/library/jsx-dev-runtime
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
|
|
88
|
+
// Two kinds of entries live here:
|
|
89
|
+
//
|
|
90
|
+
// 1. `@superblocksteam/library/jsx-dev-runtime`: the scan's esbuild uses the
|
|
91
|
+
// DEFAULT (react) JSX runtime, so it emits `react/jsx-runtime` imports and
|
|
92
|
+
// pre-bundles those — but never the library runtime, even though every
|
|
93
|
+
// generated app sets `jsxImportSource: "@superblocksteam/library"` and
|
|
94
|
+
// pulls that runtime in at serve time. The scan can never see it.
|
|
95
|
+
//
|
|
96
|
+
// 2. The common live-edit template deps: the `entries` scan only pre-bundles
|
|
97
|
+
// deps that *existing* app source imports, but a warm-pool box pre-warms
|
|
98
|
+
// against the seeded template, and Clark's generated code routinely
|
|
99
|
+
// imports template-declared deps the seed never touches (an unused Radix
|
|
100
|
+
// primitive, `recharts` for the first dashboard). Each such first-time
|
|
101
|
+
// import is a runtime discovery — a mid-generation re-optimize, worst
|
|
102
|
+
// case the "optimized dependencies changed" reload. Force-including every
|
|
103
|
+
// dep the templates declare (minus NOT_PRE_OPTIMIZED_TEMPLATE_DEPS above)
|
|
104
|
+
// closes that gap.
|
|
105
|
+
//
|
|
106
|
+
// The list is deliberately STATIC (not derived from the app's package.json):
|
|
107
|
+
// `include` IS part of Vite's dep-cache config hash, so pre-warm and
|
|
108
|
+
// activation must see the exact same value or activation discards the
|
|
109
|
+
// pre-warmed cache and re-optimizes — the very reload this config removes.
|
|
110
|
+
// The two-way template contract (include ⊆ shared template deps, and every
|
|
111
|
+
// shared template dep is included or explicitly excluded above) is pinned in
|
|
112
|
+
// `dev-server.optimize-deps.test.mts`. An unresolvable `include` entry is a
|
|
113
|
+
// no-op (Vite warns and skips), so apps lacking one of these deps (e.g.
|
|
114
|
+
// older 2.0 apps) never fail cold-start over this.
|
|
115
|
+
//
|
|
116
|
+
// Keep sorted.
|
|
117
|
+
include: [
|
|
118
|
+
"@radix-ui/react-accordion",
|
|
119
|
+
"@radix-ui/react-aspect-ratio",
|
|
120
|
+
"@radix-ui/react-avatar",
|
|
121
|
+
"@radix-ui/react-checkbox",
|
|
122
|
+
"@radix-ui/react-dialog",
|
|
123
|
+
"@radix-ui/react-dropdown-menu",
|
|
124
|
+
"@radix-ui/react-hover-card",
|
|
125
|
+
"@radix-ui/react-label",
|
|
126
|
+
"@radix-ui/react-navigation-menu",
|
|
127
|
+
"@radix-ui/react-popover",
|
|
128
|
+
"@radix-ui/react-progress",
|
|
129
|
+
"@radix-ui/react-scroll-area",
|
|
130
|
+
"@radix-ui/react-select",
|
|
131
|
+
"@radix-ui/react-separator",
|
|
132
|
+
"@radix-ui/react-slider",
|
|
133
|
+
"@radix-ui/react-slot",
|
|
134
|
+
"@radix-ui/react-switch",
|
|
135
|
+
"@radix-ui/react-tabs",
|
|
136
|
+
"@radix-ui/react-toggle",
|
|
137
|
+
"@radix-ui/react-toggle-group",
|
|
138
|
+
"@radix-ui/react-tooltip",
|
|
139
|
+
"@superblocksteam/library/jsx-dev-runtime",
|
|
140
|
+
"class-variance-authority",
|
|
141
|
+
"clsx",
|
|
142
|
+
"date-fns",
|
|
143
|
+
"lucide-react",
|
|
144
|
+
// The icon deep paths, NOT just the bare package: the lucide import
|
|
145
|
+
// optimizer plugin rewrites every named lucide import into a per-icon deep
|
|
146
|
+
// path, so the dep optimizer never sees the bare specifier at serve time —
|
|
147
|
+
// a bare include alone leaves every first-time icon import as a runtime
|
|
148
|
+
// discovery. Vite expands this glob (`expandGlobIds`) against the installed
|
|
149
|
+
// package: ~1,850 icons, measured at ~+1.4s cold-start optimize on the real
|
|
150
|
+
// template — inside the warm-standby budget, and it covers any icon Clark
|
|
151
|
+
// picks instead of a top-N guess. On apps without lucide-react the glob
|
|
152
|
+
// matches nothing and is a no-op. Expanded entries are keyed with the `.js`
|
|
153
|
+
// extension, which is why the rewrite plugin emits `.js` ids (see
|
|
154
|
+
// `lucideIconModuleId`) — Vite matches optimized deps by exact id.
|
|
155
|
+
`${LUCIDE_ICON_DEEP_PATH_PREFIX}*`,
|
|
156
|
+
"react",
|
|
157
|
+
"react-day-picker",
|
|
158
|
+
"react-dom",
|
|
159
|
+
"react-dom/client",
|
|
160
|
+
"react-dropzone",
|
|
161
|
+
"react-router",
|
|
162
|
+
"react-syntax-highlighter",
|
|
163
|
+
"react/jsx-runtime",
|
|
164
|
+
"recharts",
|
|
165
|
+
"sonner",
|
|
166
|
+
"tailwind-merge",
|
|
167
|
+
],
|
|
70
168
|
// `@superblocksteam/sdk-api` is a VIRTUAL module the `sb-sdk-api-entry-point`
|
|
71
169
|
// plugin serves (not a real package), imported by generated app code. Left to
|
|
72
170
|
// the optimizer it gets discovered as a "new dependency" on first import and
|
package/src/index.ts
CHANGED
|
@@ -133,3 +133,10 @@ export {
|
|
|
133
133
|
getTracer,
|
|
134
134
|
isCloudPrem,
|
|
135
135
|
} from "./telemetry/index.js";
|
|
136
|
+
|
|
137
|
+
export {
|
|
138
|
+
applyStartupTraceCarrier,
|
|
139
|
+
startupTraceCarrierFromHeaders,
|
|
140
|
+
type StartupTraceCarrier,
|
|
141
|
+
withStartupTraceContext,
|
|
142
|
+
} from "./telemetry/startup-context.js";
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { ROOT_CONTEXT, trace } from "@opentelemetry/api";
|
|
2
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
getTestTelemetryInstance,
|
|
6
|
+
getTestTracer,
|
|
7
|
+
initTestTelemetry,
|
|
8
|
+
resetTestTelemetry,
|
|
9
|
+
} from "@superblocksteam/telemetry/testing";
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
applyStartupTraceCarrier,
|
|
13
|
+
extractStartupTraceContext,
|
|
14
|
+
startupTraceCarrierFromHeaders,
|
|
15
|
+
type StartupTraceCarrier,
|
|
16
|
+
withStartupTraceContext,
|
|
17
|
+
} from "./startup-context.js";
|
|
18
|
+
|
|
19
|
+
const TRACE_ID = "4bf92f3577b34da6a3ce929d0e0e4736";
|
|
20
|
+
const TRACEPARENT = `00-${TRACE_ID}-00f067aa0ba902b7-01`;
|
|
21
|
+
|
|
22
|
+
describe("sdk-dev-server startup trace context", () => {
|
|
23
|
+
beforeAll(() => {
|
|
24
|
+
initTestTelemetry();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterAll(async () => {
|
|
28
|
+
await resetTestTelemetry();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("extracts the SABS parent from cold-start environment values", () => {
|
|
32
|
+
const parentContext = extractStartupTraceContext({
|
|
33
|
+
TRACEPARENT,
|
|
34
|
+
TRACESTATE: "vendor=value",
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
expect(trace.getSpanContext(parentContext)).toMatchObject({
|
|
38
|
+
isRemote: true,
|
|
39
|
+
traceId: TRACE_ID,
|
|
40
|
+
traceFlags: 1,
|
|
41
|
+
traceState: expect.objectContaining({
|
|
42
|
+
serialize: expect.any(Function),
|
|
43
|
+
}),
|
|
44
|
+
});
|
|
45
|
+
expect(trace.getSpanContext(parentContext)?.traceState?.serialize()).toBe(
|
|
46
|
+
"vendor=value",
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("uses the root context when startup propagation is absent", () => {
|
|
51
|
+
expect(extractStartupTraceContext({})).toBe(ROOT_CONTEXT);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("stores warm activation headers for the startup path without stale tracestate", () => {
|
|
55
|
+
const env: NodeJS.ProcessEnv = {
|
|
56
|
+
TRACEPARENT: "stale",
|
|
57
|
+
TRACESTATE: "stale=value",
|
|
58
|
+
};
|
|
59
|
+
const carrier: StartupTraceCarrier = { traceparent: TRACEPARENT };
|
|
60
|
+
|
|
61
|
+
applyStartupTraceCarrier(carrier, env);
|
|
62
|
+
|
|
63
|
+
expect(env.TRACEPARENT).toBe(TRACEPARENT);
|
|
64
|
+
expect(env.TRACESTATE).toBeUndefined();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("captures inbound warm activation trace headers", () => {
|
|
68
|
+
expect(
|
|
69
|
+
startupTraceCarrierFromHeaders({
|
|
70
|
+
traceparent: TRACEPARENT,
|
|
71
|
+
tracestate: "vendor=value",
|
|
72
|
+
}),
|
|
73
|
+
).toEqual({
|
|
74
|
+
traceparent: TRACEPARENT,
|
|
75
|
+
tracestate: "vendor=value",
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("parents the dev-server startup span to the propagated SABS context", async () => {
|
|
80
|
+
const environment = {
|
|
81
|
+
TRACEPARENT,
|
|
82
|
+
TRACESTATE: "vendor=value",
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
await withStartupTraceContext(environment, async () => {
|
|
86
|
+
await getTestTracer().startActiveSpan(
|
|
87
|
+
"devServerStartup",
|
|
88
|
+
async (span) => {
|
|
89
|
+
span.end();
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const [startupSpan] = getTestTelemetryInstance()
|
|
95
|
+
.spanExporter.getSpans()
|
|
96
|
+
.filter((span) => span.name === "devServerStartup");
|
|
97
|
+
expect(startupSpan.spanContext().traceId).toBe(TRACE_ID);
|
|
98
|
+
expect(startupSpan.parentSpanContext?.spanId).toBe("00f067aa0ba902b7");
|
|
99
|
+
expect(environment.TRACEPARENT).toBeUndefined();
|
|
100
|
+
expect(environment.TRACESTATE).toBeUndefined();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Context,
|
|
3
|
+
ROOT_CONTEXT,
|
|
4
|
+
context,
|
|
5
|
+
propagation,
|
|
6
|
+
trace,
|
|
7
|
+
} from "@opentelemetry/api";
|
|
8
|
+
|
|
9
|
+
export type StartupTraceCarrier = {
|
|
10
|
+
traceparent?: string;
|
|
11
|
+
tracestate?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type StartupTraceEnvironment = {
|
|
15
|
+
[key: string]: string | undefined;
|
|
16
|
+
TRACEPARENT?: string;
|
|
17
|
+
TRACESTATE?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function startupTraceCarrierFromHeaders(
|
|
21
|
+
headers: Record<string, string | string[] | undefined>,
|
|
22
|
+
): StartupTraceCarrier {
|
|
23
|
+
const traceparent = headers.traceparent;
|
|
24
|
+
const tracestate = headers.tracestate;
|
|
25
|
+
return {
|
|
26
|
+
traceparent: typeof traceparent === "string" ? traceparent : undefined,
|
|
27
|
+
tracestate: typeof tracestate === "string" ? tracestate : undefined,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function extractStartupTraceContext(
|
|
32
|
+
environment: StartupTraceEnvironment = process.env,
|
|
33
|
+
): Context {
|
|
34
|
+
if (!environment.TRACEPARENT) {
|
|
35
|
+
return ROOT_CONTEXT;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const extracted = propagation.extract(ROOT_CONTEXT, {
|
|
39
|
+
traceparent: environment.TRACEPARENT,
|
|
40
|
+
...(environment.TRACESTATE ? { tracestate: environment.TRACESTATE } : {}),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return trace.getSpanContext(extracted)?.isRemote ? extracted : ROOT_CONTEXT;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function applyStartupTraceCarrier(
|
|
47
|
+
carrier: StartupTraceCarrier,
|
|
48
|
+
environment: NodeJS.ProcessEnv = process.env,
|
|
49
|
+
): void {
|
|
50
|
+
if (carrier.traceparent) {
|
|
51
|
+
environment.TRACEPARENT = carrier.traceparent;
|
|
52
|
+
} else {
|
|
53
|
+
delete environment.TRACEPARENT;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (carrier.tracestate) {
|
|
57
|
+
environment.TRACESTATE = carrier.tracestate;
|
|
58
|
+
} else {
|
|
59
|
+
delete environment.TRACESTATE;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function consumeStartupTraceContext(
|
|
64
|
+
environment: StartupTraceEnvironment,
|
|
65
|
+
): Context {
|
|
66
|
+
const parentContext = extractStartupTraceContext(environment);
|
|
67
|
+
delete environment.TRACEPARENT;
|
|
68
|
+
delete environment.TRACESTATE;
|
|
69
|
+
return parentContext;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function withStartupTraceContext<T>(
|
|
73
|
+
environment: StartupTraceEnvironment,
|
|
74
|
+
callback: () => T,
|
|
75
|
+
): T {
|
|
76
|
+
return context.with(consumeStartupTraceContext(environment), callback);
|
|
77
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { TransformResult, Plugin } from "vite";
|
|
2
2
|
|
|
3
|
+
import { LUCIDE_ICON_DEEP_PATH_PREFIX } from "./dev-utils/optimize-deps-config.mjs";
|
|
4
|
+
|
|
3
5
|
// Pre-compile regex for better performance
|
|
4
6
|
const LUCIDE_IMPORT_PATTERN =
|
|
5
7
|
/([ \t]*)import\s+\{\s*([^}]+)\s*\}\s+from\s+['"]lucide-react['"]/g;
|
|
@@ -48,9 +50,11 @@ function isValidInput(code: string, id: string): boolean {
|
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
/**
|
|
51
|
-
* Transforms lucide-react imports from destructured to individual imports
|
|
53
|
+
* Transforms lucide-react imports from destructured to individual imports.
|
|
54
|
+
* Exported so tests can pin the emitted import specifiers against the ids the
|
|
55
|
+
* dep optimizer registers.
|
|
52
56
|
*/
|
|
53
|
-
function transformLucideImports(sourceCode: string): {
|
|
57
|
+
export function transformLucideImports(sourceCode: string): {
|
|
54
58
|
transformedCode: string;
|
|
55
59
|
hasChanges: boolean;
|
|
56
60
|
} {
|
|
@@ -80,6 +84,20 @@ function transformLucideImports(sourceCode: string): {
|
|
|
80
84
|
return { transformedCode, hasChanges };
|
|
81
85
|
}
|
|
82
86
|
|
|
87
|
+
/**
|
|
88
|
+
* The module id this plugin emits for a named lucide-react import. The `.js`
|
|
89
|
+
* extension is load-bearing: the dep optimizer's include glob registers each
|
|
90
|
+
* icon under its on-disk id (`…/icons/check.js`), and Vite's
|
|
91
|
+
* `tryOptimizedResolve` matches optimized deps by EXACT id — an extensionless
|
|
92
|
+
* import misses the optimized entry and is re-registered as a newly discovered
|
|
93
|
+
* dependency, re-running the optimizer (the reload this plugin + config
|
|
94
|
+
* combination exists to prevent). Exported so tests can assert the emitted ids
|
|
95
|
+
* and the optimized ids stay identical.
|
|
96
|
+
*/
|
|
97
|
+
export function lucideIconModuleId(importName: string): string {
|
|
98
|
+
return `${LUCIDE_ICON_DEEP_PATH_PREFIX}${convertToKebabCase(importName)}.js`;
|
|
99
|
+
}
|
|
100
|
+
|
|
83
101
|
/**
|
|
84
102
|
* Converts a comma-separated list of imports to individual import statements
|
|
85
103
|
*/
|
|
@@ -93,9 +111,8 @@ function convertToIndividualImports(
|
|
|
93
111
|
.map((name) => name.trim())
|
|
94
112
|
.filter(Boolean)
|
|
95
113
|
.map((name) => {
|
|
96
|
-
const kebabCasePath = convertToKebabCase(name);
|
|
97
114
|
const semicolon = withSemicolon ? ";" : "";
|
|
98
|
-
return `${indentation}import ${name} from "
|
|
115
|
+
return `${indentation}import ${name} from "${lucideIconModuleId(name)}"${semicolon}`;
|
|
99
116
|
})
|
|
100
117
|
.join("\n");
|
|
101
118
|
}
|