@bgub/fig-vite 0.0.0-tegami-trusted-publish-setup → 0.1.0-alpha.2

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/CHANGELOG.md ADDED
@@ -0,0 +1,41 @@
1
+ ## @bgub/fig-vite@0.1.0-alpha.1
2
+
3
+ ### Refresh resolves the app's renderer runtime
4
+
5
+ `figRefresh` now imports `@bgub/fig-dom/refresh` through its bare specifier
6
+ rather than a resolved `/@fs/` path, so app-level aliases, dedupe, and
7
+ prebundling apply and the refresh scheduler cannot be instantiated twice.
8
+
9
+ ### TanStack Start's client graph is prebundled
10
+
11
+ The TanStack Start adapter now prebundles `@tanstack/start-client-core` in
12
+ development while leaving its application-bound router and Start imports as
13
+ external Vite modules. This reduces the module-request waterfall without
14
+ freezing generated app entries or the linked Fig adapter packages. Production
15
+ continues to use Vite's normal application bundling.
16
+
17
+ ### Use one data-resource API in every environment
18
+
19
+ `dataResource` now covers shared, browser, and server-only loaders without a
20
+ second API. Server-only loaders belong behind the framework's server module
21
+ boundary; browser code uses an explicit key-only resource when it needs the
22
+ same hydrated value.
23
+
24
+ The pass-through `serverDataResource` API, `@bgub/fig/server` entry point,
25
+ `figData` Vite transform, and generated browser resource stubs are removed.
26
+
27
+ ### TanStack Start gains state-preserving Fast Refresh
28
+
29
+ The TanStack Start Vite adapter now installs Fig Fast Refresh automatically.
30
+ Component edits update in place and preserve hook state in accepted modules.
31
+
32
+ `@bgub/fig-vite` is now a public package containing the reusable Fast Refresh
33
+ and server data-resource transforms.
34
+
35
+ ## @bgub/fig-vite@0.1.0-alpha.0 (alpha)
36
+
37
+ ### Initial alpha release
38
+
39
+ Fig Fast Refresh and server data-resource transforms for Vite.
40
+
41
+ # Changelog
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Ben Gubler
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,5 +1,18 @@
1
- # Placeholder package
1
+ # @bgub/fig-vite
2
2
 
3
- This empty package was published by [Tegami](https://tegami.fuma-nama.dev) to configure npm trusted publishing.
3
+ Vite integration for Fig. It provides state-preserving Fast Refresh.
4
4
 
5
- The real package contents will be published via CI with OIDC.
5
+ ```ts
6
+ import { figRefresh } from "@bgub/fig-vite";
7
+
8
+ const plugins = [figRefresh()];
9
+ ```
10
+
11
+ Framework adapters may install this plugin for you. In particular,
12
+ `@bgub/fig-tanstack-start/plugin/vite` includes `figRefresh()`.
13
+
14
+ Fig packages are ESM-only and require Node `^20.19.0 || >=22.12.0`.
15
+
16
+ ## License
17
+
18
+ MIT
@@ -0,0 +1,20 @@
1
+ //#region src/index.d.ts
2
+ interface FigRefreshOptions {
3
+ include?: RegExp;
4
+ }
5
+ interface FigRefreshPlugin {
6
+ apply: "serve";
7
+ enforce: "pre";
8
+ load(id: string): string | null;
9
+ name: string;
10
+ resolveId(id: string): string | null;
11
+ transform(code: string, id: string, options?: {
12
+ ssr?: boolean;
13
+ }): Promise<{
14
+ code: string;
15
+ map: unknown;
16
+ } | null>;
17
+ }
18
+ declare function figRefresh(options?: FigRefreshOptions): FigRefreshPlugin;
19
+ //#endregion
20
+ export { FigRefreshOptions, FigRefreshPlugin, figRefresh };
package/dist/index.js ADDED
@@ -0,0 +1,234 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import * as babel from "@babel/core";
3
+ import presetTypescript from "@babel/preset-typescript";
4
+ //#region src/transform.ts
5
+ function figRefreshBabelPlugin(api) {
6
+ const t = api.types;
7
+ const template = api.template;
8
+ return {
9
+ name: "fig-refresh",
10
+ visitor: { Program: { exit(path) {
11
+ const moduleId = this.opts?.moduleId;
12
+ if (moduleId === void 0) return;
13
+ const functions = /* @__PURE__ */ new Map();
14
+ for (const statement of path.get("body")) {
15
+ const declaration = statement.isExportNamedDeclaration() || statement.isExportDefaultDeclaration() ? statement.get("declaration") : statement;
16
+ if (!Array.isArray(declaration) && (declaration.isFunctionDeclaration() || declaration.isVariableDeclaration())) collectFunction(declaration, functions);
17
+ }
18
+ const components = [...functions.values()].filter((record) => record.isComponent);
19
+ if (components.length === 0) return;
20
+ const canSelfAccept = exportsOnlyComponents(path, functions);
21
+ for (const component of components) {
22
+ const result = signatureFor(component, [component.name]);
23
+ component.signature = result.signature;
24
+ component.forceReset = result.forceReset;
25
+ }
26
+ path.node.body.unshift(template.statement.ast(canSelfAccept ? `import { register as __figReg, setSignature as __figSig, enqueueRefresh as __figRefresh } from "virtual:fig-refresh";` : `import { register as __figReg, setSignature as __figSig } from "virtual:fig-refresh";`));
27
+ const tail = [];
28
+ for (const component of components) {
29
+ const signature = component.signature ?? "";
30
+ const forceReset = component.forceReset === true;
31
+ tail.push(template.statement.ast(`__figReg(${component.name}, ${JSON.stringify(`${moduleId}#${component.name}`)});`));
32
+ tail.push(template.statement.ast(forceReset ? `__figSig(${component.name}, ${JSON.stringify(signature)}, true);` : `__figSig(${component.name}, ${JSON.stringify(signature)});`));
33
+ }
34
+ if (canSelfAccept) tail.push(template.statement.ast(`if (import.meta.hot) { import.meta.hot.accept(); __figRefresh(); }`));
35
+ path.node.body.push(...tail);
36
+ function collectFunction(declaration, into) {
37
+ if (declaration.isFunctionDeclaration()) {
38
+ const id = declaration.node.id;
39
+ if (id != null) {
40
+ const isComponent = isComponentName(id.name);
41
+ const isCustomHook = isCustomHookName(id.name);
42
+ if (isComponent || isCustomHook) into.set(id.name, {
43
+ fnPath: declaration,
44
+ isComponent,
45
+ isCustomHook,
46
+ name: id.name
47
+ });
48
+ }
49
+ return;
50
+ }
51
+ if (declaration.isVariableDeclaration()) for (const declarator of declaration.get("declarations")) {
52
+ const id = declarator.node.id;
53
+ const init = declarator.node.init;
54
+ if (t.isIdentifier(id) && init != null && (t.isArrowFunctionExpression(init) || t.isFunctionExpression(init))) {
55
+ const isComponent = isComponentName(id.name);
56
+ const isCustomHook = isCustomHookName(id.name);
57
+ if (!isComponent && !isCustomHook) continue;
58
+ into.set(id.name, {
59
+ fnPath: declarator.get("init"),
60
+ isComponent,
61
+ isCustomHook,
62
+ name: id.name
63
+ });
64
+ }
65
+ }
66
+ }
67
+ function signatureFor(record, stack) {
68
+ const hookNames = [];
69
+ let forceReset = false;
70
+ record.fnPath.traverse({ CallExpression(call) {
71
+ if (call.getFunctionParent()?.node !== record.fnPath.node) return;
72
+ const callee = call.node.callee;
73
+ const hookName = hookCallName(callee);
74
+ if (hookName === null) return;
75
+ hookNames.push(hookName);
76
+ const hookRecord = functions.get(hookName);
77
+ if (hookRecord?.isCustomHook !== true) {
78
+ if (!isBuiltinFigHookName(hookName)) forceReset = true;
79
+ return;
80
+ }
81
+ if (stack.includes(hookName)) {
82
+ forceReset = true;
83
+ return;
84
+ }
85
+ const nested = signatureFor(hookRecord, [...stack, hookName]);
86
+ forceReset = forceReset || nested.forceReset;
87
+ if (nested.signature !== "") hookNames.push(...nested.signature.split("\n").map((line) => `>${line}`));
88
+ } });
89
+ return {
90
+ forceReset,
91
+ signature: hookNames.join("\n")
92
+ };
93
+ }
94
+ function hookCallName(callee) {
95
+ if (t.isIdentifier(callee)) return isCustomHookName(callee.name) ? callee.name : null;
96
+ if (t.isMemberExpression(callee) && !callee.computed && t.isIdentifier(callee.property) && isCustomHookName(callee.property.name)) return callee.property.name;
97
+ return null;
98
+ }
99
+ function exportsOnlyComponents(program, records) {
100
+ for (const statement of program.get("body")) {
101
+ if (statement.isExportNamedDeclaration()) {
102
+ if (statement.node.exportKind === "type") continue;
103
+ const declaration = statement.get("declaration");
104
+ if (!Array.isArray(declaration) && declaration.node != null) {
105
+ if (!declarationExportsOnlyComponents(declaration.node, records)) return false;
106
+ continue;
107
+ }
108
+ if (statement.node.source != null) {
109
+ for (const specifier of statement.node.specifiers) {
110
+ if ("exportKind" in specifier && specifier.exportKind === "type") continue;
111
+ return false;
112
+ }
113
+ continue;
114
+ }
115
+ for (const specifier of statement.get("specifiers")) {
116
+ if (specifier.isExportSpecifier() && specifier.node.exportKind === "type") continue;
117
+ if (!specifier.isExportSpecifier()) return false;
118
+ const local = specifier.node.local;
119
+ if (!t.isIdentifier(local)) return false;
120
+ if (records.get(local.name)?.isComponent !== true) return false;
121
+ }
122
+ continue;
123
+ }
124
+ if (statement.isExportDefaultDeclaration()) {
125
+ const declaration = statement.get("declaration");
126
+ if (Array.isArray(declaration)) return false;
127
+ if (declaration.isFunctionDeclaration()) {
128
+ const id = declaration.node.id;
129
+ if (id == null || !isComponentName(id.name)) return false;
130
+ continue;
131
+ }
132
+ if (declaration.isIdentifier() && records.get(declaration.node.name)?.isComponent === true) continue;
133
+ return false;
134
+ }
135
+ if (statement.isExportAllDeclaration()) return false;
136
+ }
137
+ return true;
138
+ }
139
+ function declarationExportsOnlyComponents(declaration, records) {
140
+ if (t.isFunctionDeclaration(declaration)) {
141
+ const id = declaration.id;
142
+ return id != null && records.get(id.name)?.isComponent === true;
143
+ }
144
+ if (!t.isVariableDeclaration(declaration)) return false;
145
+ for (const declarator of declaration.declarations) {
146
+ if (!t.isIdentifier(declarator.id)) return false;
147
+ if (records.get(declarator.id.name)?.isComponent !== true) return false;
148
+ }
149
+ return true;
150
+ }
151
+ } } }
152
+ };
153
+ }
154
+ function isComponentName(name) {
155
+ const first = name[0];
156
+ return first !== void 0 && first >= "A" && first <= "Z";
157
+ }
158
+ function isCustomHookName(name) {
159
+ return /^use[A-Z]/.test(name);
160
+ }
161
+ function isBuiltinFigHookName(name) {
162
+ return name === "useActionState" || name === "useBeforeLayout" || name === "useBeforePaint" || name === "useCallback" || name === "useSyncExternalStore" || name === "useId" || name === "useDeferredValue" || name === "useMemo" || name === "useReactive" || name === "useStableEvent" || name === "useState" || name === "useTransition";
163
+ }
164
+ async function transformModule(code, id) {
165
+ const result = await babel.transformAsync(code, {
166
+ babelrc: false,
167
+ configFile: false,
168
+ filename: id,
169
+ sourceMaps: true,
170
+ presets: [[presetTypescript, {
171
+ ignoreExtensions: true,
172
+ onlyRemoveTypeImports: true
173
+ }]],
174
+ parserOpts: { plugins: id.endsWith("x") ? ["jsx"] : [] },
175
+ plugins: [[figRefreshBabelPlugin, { moduleId: id }]]
176
+ });
177
+ if (result?.code == null || !result.code.includes("virtual:fig-refresh")) return null;
178
+ return {
179
+ code: result.code,
180
+ map: result.map
181
+ };
182
+ }
183
+ //#endregion
184
+ //#region src/index.ts
185
+ const VIRTUAL_ID = "virtual:fig-refresh";
186
+ const RESOLVED_VIRTUAL_ID = "\0virtual:fig-refresh";
187
+ const REFRESH_RUNTIME_IMPORT = viteFileImport(import.meta.resolve("@bgub/fig-refresh"));
188
+ const DOM_REFRESH_IMPORT = "@bgub/fig-dom/refresh";
189
+ function figRefresh(options = {}) {
190
+ const include = options.include ?? /\.[jt]sx?$/;
191
+ return {
192
+ apply: "serve",
193
+ enforce: "pre",
194
+ name: "fig:refresh",
195
+ resolveId(id) {
196
+ return id === VIRTUAL_ID ? RESOLVED_VIRTUAL_ID : null;
197
+ },
198
+ load(id) {
199
+ return id === RESOLVED_VIRTUAL_ID ? virtualModuleCode() : null;
200
+ },
201
+ async transform(code, id, transformOptions) {
202
+ if (transformOptions?.ssr === true) return null;
203
+ const clean = id.split("?")[0] ?? id;
204
+ if (clean.includes("/node_modules/") || !include.test(clean)) return null;
205
+ return transformModule(code, clean);
206
+ }
207
+ };
208
+ }
209
+ function virtualModuleCode() {
210
+ return `import { injectScheduleRefresh, performRefresh, register, setSignature } from ${JSON.stringify(REFRESH_RUNTIME_IMPORT)};
211
+ import { scheduleRefresh } from ${JSON.stringify(DOM_REFRESH_IMPORT)};
212
+
213
+ injectScheduleRefresh(scheduleRefresh);
214
+
215
+ let queued = false;
216
+ export function enqueueRefresh() {
217
+ if (queued) return;
218
+ queued = true;
219
+ queueMicrotask(() => {
220
+ queued = false;
221
+ performRefresh();
222
+ });
223
+ }
224
+
225
+ export { register, setSignature };
226
+ `;
227
+ }
228
+ function viteFileImport(url) {
229
+ return `/@fs/${fileURLToPath(url)}`;
230
+ }
231
+ //#endregion
232
+ export { figRefresh };
233
+
234
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/transform.ts","../src/index.ts"],"sourcesContent":["import * as babel from \"@babel/core\";\nimport type { NodePath, PluginObject } from \"@babel/core\";\nimport presetTypescript from \"@babel/preset-typescript\";\n\ninterface TransformResult {\n code: string;\n map: unknown;\n}\n\ninterface FunctionRecord {\n fnPath: NodePath;\n isComponent: boolean;\n isCustomHook: boolean;\n name: string;\n}\n\ninterface SignatureResult {\n forceReset: boolean;\n signature: string;\n}\n\n// Babel visitor: find top-level component declarations (PascalCase functions),\n// then inject calls to the Fig refresh runtime + a self-accepting HMR boundary.\n// Emits register/setSignature directly (no react-refresh global protocol).\nfunction figRefreshBabelPlugin(api: typeof babel): PluginObject {\n const t = api.types;\n const template = api.template;\n\n return {\n name: \"fig-refresh\",\n visitor: {\n Program: {\n exit(path) {\n const moduleId = (this as { opts?: { moduleId?: string } }).opts\n ?.moduleId;\n if (moduleId === undefined) return;\n\n const functions = new Map<string, FunctionRecord>();\n\n for (const statement of path.get(\"body\")) {\n const exported =\n statement.isExportNamedDeclaration() ||\n statement.isExportDefaultDeclaration();\n const declaration = exported\n ? statement.get(\"declaration\")\n : statement;\n if (\n !Array.isArray(declaration) &&\n (declaration.isFunctionDeclaration() ||\n declaration.isVariableDeclaration())\n ) {\n collectFunction(declaration, functions);\n }\n }\n\n const components = [...functions.values()].filter(\n (record) => record.isComponent,\n );\n if (components.length === 0) return;\n\n const canSelfAccept = exportsOnlyComponents(path, functions);\n for (const component of components) {\n const result = signatureFor(component, [component.name]);\n (component as FunctionRecord & SignatureResult).signature =\n result.signature;\n (component as FunctionRecord & SignatureResult).forceReset =\n result.forceReset;\n }\n\n path.node.body.unshift(\n template.statement.ast(\n canSelfAccept\n ? `import { register as __figReg, setSignature as __figSig, enqueueRefresh as __figRefresh } from \"virtual:fig-refresh\";`\n : `import { register as __figReg, setSignature as __figSig } from \"virtual:fig-refresh\";`,\n ),\n );\n\n const tail = [];\n for (const component of components) {\n const signature =\n (component as FunctionRecord & Partial<SignatureResult>)\n .signature ?? \"\";\n const forceReset =\n (component as FunctionRecord & Partial<SignatureResult>)\n .forceReset === true;\n tail.push(\n template.statement.ast(\n `__figReg(${component.name}, ${JSON.stringify(\n `${moduleId}#${component.name}`,\n )});`,\n ),\n );\n tail.push(\n template.statement.ast(\n forceReset\n ? `__figSig(${component.name}, ${JSON.stringify(\n signature,\n )}, true);`\n : `__figSig(${component.name}, ${JSON.stringify(\n signature,\n )});`,\n ),\n );\n }\n if (canSelfAccept) {\n tail.push(\n template.statement.ast(\n `if (import.meta.hot) { import.meta.hot.accept(); __figRefresh(); }`,\n ),\n );\n }\n path.node.body.push(...(tail as never[]));\n\n function collectFunction(\n declaration:\n | NodePath<babel.types.FunctionDeclaration>\n | NodePath<babel.types.VariableDeclaration>,\n into: Map<string, FunctionRecord>,\n ): void {\n if (declaration.isFunctionDeclaration()) {\n const id = declaration.node.id;\n if (id != null) {\n const isComponent = isComponentName(id.name);\n const isCustomHook = isCustomHookName(id.name);\n if (isComponent || isCustomHook) {\n into.set(id.name, {\n fnPath: declaration,\n isComponent,\n isCustomHook,\n name: id.name,\n });\n }\n }\n return;\n }\n\n if (declaration.isVariableDeclaration()) {\n for (const declarator of declaration.get(\"declarations\")) {\n const id = declarator.node.id;\n const init = declarator.node.init;\n if (\n t.isIdentifier(id) &&\n init != null &&\n (t.isArrowFunctionExpression(init) ||\n t.isFunctionExpression(init))\n ) {\n const isComponent = isComponentName(id.name);\n const isCustomHook = isCustomHookName(id.name);\n if (!isComponent && !isCustomHook) continue;\n into.set(id.name, {\n fnPath: declarator.get(\"init\") as NodePath,\n isComponent,\n isCustomHook,\n name: id.name,\n });\n }\n }\n }\n }\n\n function signatureFor(\n record: FunctionRecord,\n stack: string[],\n ): SignatureResult {\n const hookNames: string[] = [];\n let forceReset = false;\n\n record.fnPath.traverse({\n CallExpression(call) {\n if (call.getFunctionParent()?.node !== record.fnPath.node) {\n return;\n }\n\n const callee = call.node.callee;\n const hookName = hookCallName(callee);\n if (hookName === null) {\n return;\n }\n\n hookNames.push(hookName);\n const hookRecord = functions.get(hookName);\n if (hookRecord?.isCustomHook !== true) {\n if (!isBuiltinFigHookName(hookName)) forceReset = true;\n return;\n }\n\n if (stack.includes(hookName)) {\n forceReset = true;\n return;\n }\n\n const nested = signatureFor(hookRecord, [...stack, hookName]);\n forceReset = forceReset || nested.forceReset;\n if (nested.signature !== \"\") {\n hookNames.push(\n ...nested.signature.split(\"\\n\").map((line) => `>${line}`),\n );\n }\n },\n });\n\n return { forceReset, signature: hookNames.join(\"\\n\") };\n }\n\n function hookCallName(\n callee:\n | babel.types.Expression\n | babel.types.Import\n | babel.types.Super\n | babel.types.V8IntrinsicIdentifier,\n ): string | null {\n if (t.isIdentifier(callee)) {\n return isCustomHookName(callee.name) ? callee.name : null;\n }\n if (\n t.isMemberExpression(callee) &&\n !callee.computed &&\n t.isIdentifier(callee.property) &&\n isCustomHookName(callee.property.name)\n ) {\n return callee.property.name;\n }\n return null;\n }\n\n function exportsOnlyComponents(\n program: NodePath<babel.types.Program>,\n records: Map<string, FunctionRecord>,\n ): boolean {\n for (const statement of program.get(\"body\")) {\n if (statement.isExportNamedDeclaration()) {\n if (statement.node.exportKind === \"type\") continue;\n const declaration = statement.get(\"declaration\");\n if (!Array.isArray(declaration) && declaration.node != null) {\n if (\n !declarationExportsOnlyComponents(declaration.node, records)\n ) {\n return false;\n }\n continue;\n }\n\n if (statement.node.source != null) {\n for (const specifier of statement.node.specifiers) {\n if (\n \"exportKind\" in specifier &&\n specifier.exportKind === \"type\"\n ) {\n continue;\n }\n return false;\n }\n continue;\n }\n\n for (const specifier of statement.get(\"specifiers\")) {\n if (\n specifier.isExportSpecifier() &&\n specifier.node.exportKind === \"type\"\n ) {\n continue;\n }\n if (!specifier.isExportSpecifier()) return false;\n const local = specifier.node.local;\n if (!t.isIdentifier(local)) return false;\n if (records.get(local.name)?.isComponent !== true) {\n return false;\n }\n }\n continue;\n }\n\n if (statement.isExportDefaultDeclaration()) {\n const declaration = statement.get(\"declaration\");\n if (Array.isArray(declaration)) return false;\n if (declaration.isFunctionDeclaration()) {\n const id = declaration.node.id;\n if (id == null || !isComponentName(id.name)) return false;\n continue;\n }\n if (\n declaration.isIdentifier() &&\n records.get(declaration.node.name)?.isComponent === true\n ) {\n continue;\n }\n return false;\n }\n\n if (statement.isExportAllDeclaration()) return false;\n }\n\n return true;\n }\n\n function declarationExportsOnlyComponents(\n declaration: babel.types.Declaration,\n records: Map<string, FunctionRecord>,\n ): boolean {\n if (t.isFunctionDeclaration(declaration)) {\n const id = declaration.id;\n return id != null && records.get(id.name)?.isComponent === true;\n }\n\n if (!t.isVariableDeclaration(declaration)) return false;\n\n for (const declarator of declaration.declarations) {\n if (!t.isIdentifier(declarator.id)) return false;\n if (records.get(declarator.id.name)?.isComponent !== true) {\n return false;\n }\n }\n return true;\n }\n },\n },\n },\n };\n}\n\nfunction isComponentName(name: string): boolean {\n const first = name[0];\n return first !== undefined && first >= \"A\" && first <= \"Z\";\n}\n\nfunction isCustomHookName(name: string): boolean {\n return /^use[A-Z]/.test(name);\n}\n\nfunction isBuiltinFigHookName(name: string): boolean {\n return (\n name === \"useActionState\" ||\n name === \"useBeforeLayout\" ||\n name === \"useBeforePaint\" ||\n name === \"useCallback\" ||\n name === \"useSyncExternalStore\" ||\n name === \"useId\" ||\n name === \"useDeferredValue\" ||\n name === \"useMemo\" ||\n name === \"useReactive\" ||\n name === \"useStableEvent\" ||\n name === \"useState\" ||\n name === \"useTransition\"\n );\n}\n\n// Transform a single module. Returns null when it has no components (so the\n// caller leaves it for the regular pipeline).\nexport async function transformModule(\n code: string,\n id: string,\n): Promise<TransformResult | null> {\n const result = await babel.transformAsync(code, {\n babelrc: false,\n configFile: false,\n filename: id,\n sourceMaps: true,\n presets: [\n [\n presetTypescript,\n {\n ignoreExtensions: true,\n onlyRemoveTypeImports: true,\n },\n ],\n ],\n parserOpts: { plugins: id.endsWith(\"x\") ? [\"jsx\"] : [] },\n plugins: [[figRefreshBabelPlugin, { moduleId: id }]],\n });\n\n if (result?.code == null || !result.code.includes(\"virtual:fig-refresh\")) {\n return null;\n }\n return { code: result.code, map: result.map };\n}\n","import { fileURLToPath } from \"node:url\";\nimport { transformModule } from \"./transform.ts\";\n\nconst VIRTUAL_ID = \"virtual:fig-refresh\";\nconst RESOLVED_VIRTUAL_ID = \"\\0virtual:fig-refresh\";\nconst REFRESH_RUNTIME_IMPORT = viteFileImport(\n import.meta.resolve(\"@bgub/fig-refresh\"),\n);\n// A bare specifier so the app's resolve config (aliases, dedupe, prebundling)\n// applies; a file path would load a second copy of the scheduler state next to\n// the one `@bgub/fig-dom` wires up internally.\nconst DOM_REFRESH_IMPORT = \"@bgub/fig-dom/refresh\";\n\nexport interface FigRefreshOptions {\n // Files to consider for the refresh transform. Defaults to JS/TS(X).\n include?: RegExp;\n}\n\n// Minimal structural shape of a Vite plugin (avoids a hard dep on vite types).\nexport interface FigRefreshPlugin {\n apply: \"serve\";\n enforce: \"pre\";\n load(id: string): string | null;\n name: string;\n resolveId(id: string): string | null;\n transform(\n code: string,\n id: string,\n options?: { ssr?: boolean },\n ): Promise<{ code: string; map: unknown } | null>;\n}\n\nexport function figRefresh(options: FigRefreshOptions = {}): FigRefreshPlugin {\n const include = options.include ?? /\\.[jt]sx?$/;\n\n return {\n apply: \"serve\",\n enforce: \"pre\",\n name: \"fig:refresh\",\n resolveId(id) {\n return id === VIRTUAL_ID ? RESOLVED_VIRTUAL_ID : null;\n },\n load(id) {\n return id === RESOLVED_VIRTUAL_ID ? virtualModuleCode() : null;\n },\n async transform(code, id, transformOptions) {\n if (transformOptions?.ssr === true) return null;\n\n const clean = id.split(\"?\")[0] ?? id;\n if (clean.includes(\"/node_modules/\") || !include.test(clean)) return null;\n return transformModule(code, clean);\n },\n };\n}\n\n// The virtual runtime module: wires the renderer to the refresh runtime once,\n// and exposes register/setSignature plus a microtask-batched refresh trigger.\nfunction virtualModuleCode(): string {\n return `import { injectScheduleRefresh, performRefresh, register, setSignature } from ${JSON.stringify(\n REFRESH_RUNTIME_IMPORT,\n )};\nimport { scheduleRefresh } from ${JSON.stringify(DOM_REFRESH_IMPORT)};\n\ninjectScheduleRefresh(scheduleRefresh);\n\nlet queued = false;\nexport function enqueueRefresh() {\n if (queued) return;\n queued = true;\n queueMicrotask(() => {\n queued = false;\n performRefresh();\n });\n}\n\nexport { register, setSignature };\n`;\n}\n\nfunction viteFileImport(url: string): string {\n return `/@fs/${fileURLToPath(url)}`;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,5 +1,53 @@
1
1
  {
2
2
  "name": "@bgub/fig-vite",
3
- "version": "0.0.0-tegami-trusted-publish-setup",
4
- "description": "Placeholder published by Tegami for npm trusted publishing setup."
5
- }
3
+ "version": "0.1.0-alpha.2",
4
+ "description": "Fig Vite plugin for state-preserving Fast Refresh",
5
+ "keywords": [],
6
+ "homepage": "https://github.com/bgub/fig",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "types": "./dist/index.d.ts",
17
+ "sideEffects": false,
18
+ "files": [
19
+ "dist",
20
+ "CHANGELOG.md",
21
+ "LICENSE",
22
+ "README.md"
23
+ ],
24
+ "author": "Ben Gubler <nebrelbug@gmail.com>",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/bgub/fig",
28
+ "directory": "packages/fig-vite"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/bgub/fig/issues"
32
+ },
33
+ "license": "MIT",
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "dependencies": {
38
+ "@babel/core": "^8.0.1",
39
+ "@babel/preset-typescript": "^8.0.1",
40
+ "@bgub/fig-dom": "0.1.0-alpha.2",
41
+ "@bgub/fig-refresh": "0.1.0-alpha.2"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^20.19.0"
45
+ },
46
+ "engines": {
47
+ "node": "^20.19.0 || >=22.12.0"
48
+ },
49
+ "scripts": {
50
+ "build": "tsdown --config ../../tsdown.config.ts && node ../../scripts/strip-declaration-map-comments.mjs dist",
51
+ "test": "vitest run --config ../../vite.config.ts src"
52
+ }
53
+ }