@decocms/start 2.13.0 → 2.15.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/.agents/skills/deco-to-tanstack-migration/references/post-migration-cleanup.md +132 -6
- package/CLAUDE.md +1 -1
- package/package.json +1 -1
- package/scripts/migrate/post-cleanup/rules.ts +196 -7
- package/scripts/migrate/post-cleanup/runner.test.ts +225 -2
- package/scripts/migrate/post-cleanup/shim-classify.test.ts +352 -0
- package/scripts/migrate/post-cleanup/shim-classify.ts +246 -0
- package/.cursor/skills/deco-to-tanstack-migration/SKILL.md +0 -655
- package/.cursor/skills/deco-to-tanstack-migration/references/codemod-commands.md +0 -174
- package/.cursor/skills/deco-to-tanstack-migration/references/commerce/README.md +0 -78
- package/.cursor/skills/deco-to-tanstack-migration/references/deco-framework/README.md +0 -174
- package/.cursor/skills/deco-to-tanstack-migration/references/gotchas.md +0 -834
- package/.cursor/skills/deco-to-tanstack-migration/references/imports/README.md +0 -70
- package/.cursor/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +0 -121
- package/.cursor/skills/deco-to-tanstack-migration/references/post-migration-cleanup.md +0 -231
- package/.cursor/skills/deco-to-tanstack-migration/references/signals/README.md +0 -220
- package/.cursor/skills/deco-to-tanstack-migration/references/vite-config/README.md +0 -103
- package/.cursor/skills/deco-to-tanstack-migration/templates/package-json.md +0 -75
- package/.cursor/skills/deco-to-tanstack-migration/templates/root-route.md +0 -127
- package/.cursor/skills/deco-to-tanstack-migration/templates/router.md +0 -96
- package/.cursor/skills/deco-to-tanstack-migration/templates/setup-ts.md +0 -148
- package/.cursor/skills/deco-to-tanstack-migration/templates/vite-config.md +0 -197
- package/.cursor/skills/deco-to-tanstack-migration/templates/worker-entry.md +0 -67
- /package/{.cursor → .agents}/skills/deco-to-tanstack-migration/references/server-functions/README.md +0 -0
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
# Preact -> React Import Migration
|
|
2
|
-
|
|
3
|
-
Mechanical find-and-replace. Safe to automate with `sed`.
|
|
4
|
-
|
|
5
|
-
## Replacements
|
|
6
|
-
|
|
7
|
-
| Find | Replace |
|
|
8
|
-
|------|---------|
|
|
9
|
-
| `from "preact/hooks"` | `from "react"` |
|
|
10
|
-
| `from "preact/compat"` | `from "react"` |
|
|
11
|
-
| `from "preact"` | `from "react"` |
|
|
12
|
-
|
|
13
|
-
## Special Cases
|
|
14
|
-
|
|
15
|
-
### ComponentChildren -> ReactNode
|
|
16
|
-
|
|
17
|
-
Preact's `ComponentChildren` maps to React's `ReactNode`:
|
|
18
|
-
|
|
19
|
-
```typescript
|
|
20
|
-
// OLD
|
|
21
|
-
import type { ComponentChildren } from "preact";
|
|
22
|
-
|
|
23
|
-
// NEW
|
|
24
|
-
import type { ReactNode as ComponentChildren } from "react";
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
If you want to modernize fully, rename `ComponentChildren` to `ReactNode` across the codebase.
|
|
28
|
-
|
|
29
|
-
### JSX type
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
// OLD
|
|
33
|
-
import type { JSX } from "preact";
|
|
34
|
-
|
|
35
|
-
// NEW (works unchanged)
|
|
36
|
-
import type { JSX } from "react";
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### FunctionalComponent -> FC
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
// OLD
|
|
43
|
-
const Foo: preact.FunctionalComponent<Props> = ...
|
|
44
|
-
|
|
45
|
-
// NEW
|
|
46
|
-
import React from "react";
|
|
47
|
-
const Foo: React.FC<Props> = ...
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## Automation
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
# Bulk replace (macOS sed)
|
|
54
|
-
find src/ -name '*.ts' -o -name '*.tsx' | xargs sed -i '' \
|
|
55
|
-
-e 's|from "preact/hooks"|from "react"|g' \
|
|
56
|
-
-e 's|from "preact/compat"|from "react"|g'
|
|
57
|
-
|
|
58
|
-
# Bare preact requires care (don't match preact/hooks or preact/compat)
|
|
59
|
-
find src/ -name '*.ts' -o -name '*.tsx' | xargs sed -i '' \
|
|
60
|
-
's|from "preact"|from "react"|g'
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Then handle `ComponentChildren` files individually.
|
|
64
|
-
|
|
65
|
-
## Verification
|
|
66
|
-
|
|
67
|
-
```bash
|
|
68
|
-
grep -r 'from "preact' src/ --include='*.ts' --include='*.tsx'
|
|
69
|
-
# Should return ZERO matches
|
|
70
|
-
```
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
# Platform Hooks Migration
|
|
2
|
-
|
|
3
|
-
Platform hooks (useCart, useUser, useWishlist) are the most complex migration target because they have real business logic.
|
|
4
|
-
|
|
5
|
-
## Strategy
|
|
6
|
-
|
|
7
|
-
All hooks are **site-local**. No Vite alias tricks. No compat layers.
|
|
8
|
-
|
|
9
|
-
- Active platform hooks (VTEX for this store) -> `~/hooks/useCart.ts` with real implementation
|
|
10
|
-
- Inactive platform hooks (Wake, Shopify, etc.) -> `~/hooks/platform/{name}.ts` with no-op stubs
|
|
11
|
-
- Auth hooks -> `~/hooks/useUser.ts`, `~/hooks/useWishlist.ts`
|
|
12
|
-
|
|
13
|
-
## VTEX useCart (Real Implementation)
|
|
14
|
-
|
|
15
|
-
### Why Server Functions Are Required
|
|
16
|
-
|
|
17
|
-
The storefront domain (e.g., `my-store.deco.site`) differs from the VTEX checkout domain (`account.vtexcommercestable.com.br`). Direct browser `fetch()` calls are blocked by CORS. Additionally, VTEX API credentials (`AppKey`/`AppToken`) must stay server-side.
|
|
18
|
-
|
|
19
|
-
Use TanStack Start `createServerFn` to create server-side proxy functions that the client hook calls transparently.
|
|
20
|
-
|
|
21
|
-
> See `references/server-functions/README.md` for the full pattern and all TypeScript pitfalls.
|
|
22
|
-
|
|
23
|
-
### Server Functions (`src/server/invoke.ts`)
|
|
24
|
-
|
|
25
|
-
Wrap `@decocms/apps` VTEX actions in `createServerFn`. Always use `.inputValidator()` (not `.validator()`) and return `Promise<any>` to bypass `ValidateSerializable`:
|
|
26
|
-
|
|
27
|
-
```typescript
|
|
28
|
-
import { createServerFn } from "@tanstack/react-start";
|
|
29
|
-
import { getOrCreateCart } from "@decocms/apps/vtex/actions/checkout";
|
|
30
|
-
|
|
31
|
-
// Preferred: wrap @decocms/apps actions (handles auth + API details internally)
|
|
32
|
-
const _getOrCreateCart = createServerFn({ method: "POST" })
|
|
33
|
-
.inputValidator((data: { orderFormId?: string }) => data)
|
|
34
|
-
.handler(async ({ data }): Promise<any> => {
|
|
35
|
-
const result = await getOrCreateCart(data.orderFormId);
|
|
36
|
-
return (result as any).data;
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
export const invoke = {
|
|
40
|
-
vtex: { actions: { getOrCreateCart: _getOrCreateCart } },
|
|
41
|
-
} as const;
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### Hook (~/hooks/useCart.ts)
|
|
45
|
-
|
|
46
|
-
Key design decisions:
|
|
47
|
-
- **Module-level singleton state** shared across all component instances
|
|
48
|
-
- **Pub/sub pattern** (`_listeners` Set) for notifying React components of changes
|
|
49
|
-
- **Cookie-based session**: reads/writes `checkout.vtex.com__orderFormId` on the **client** side (not VTEX's domain cookie)
|
|
50
|
-
- Returns `cart` and `loading` with `.value` getter/setter for backward compat with Preact-era components
|
|
51
|
-
- Lazy initialization: cart is fetched on first component mount, not on module load
|
|
52
|
-
- Exports `itemToAnalyticsItem` for cart-specific analytics mapping
|
|
53
|
-
|
|
54
|
-
### Cross-Domain Checkout
|
|
55
|
-
|
|
56
|
-
The minicart's "Finalizar Compra" button must link to the VTEX checkout domain with the `orderFormId` as a query parameter — the VTEX domain can't read the storefront's cookies:
|
|
57
|
-
|
|
58
|
-
```typescript
|
|
59
|
-
const checkoutUrl = `https://secure.${STORE_DOMAIN}/checkout/?orderFormId=${orderFormId}`;
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### VTEX Types (~/types/vtex.ts)
|
|
63
|
-
|
|
64
|
-
Site-local types for VTEX-specific structures:
|
|
65
|
-
- `OrderFormItem`, `SimulationOrderForm`, `Sla`, `SKU`, `VtexProduct`
|
|
66
|
-
|
|
67
|
-
## Inactive Platform Stubs
|
|
68
|
-
|
|
69
|
-
For non-VTEX platforms, create minimal no-op files:
|
|
70
|
-
|
|
71
|
-
```typescript
|
|
72
|
-
// ~/hooks/platform/wake.ts
|
|
73
|
-
export function useCart() {
|
|
74
|
-
return {
|
|
75
|
-
cart: { value: null },
|
|
76
|
-
loading: { value: false },
|
|
77
|
-
addItem: async (_params: any) => {},
|
|
78
|
-
updateItems: async (_params: any) => {},
|
|
79
|
-
removeItem: async (_index: any) => {},
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function useUser() {
|
|
84
|
-
return {
|
|
85
|
-
user: { value: null as { email?: string; name?: string } | null },
|
|
86
|
-
loading: { value: false },
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function useWishlist() {
|
|
91
|
-
return {
|
|
92
|
-
loading: { value: false },
|
|
93
|
-
addItem: async (_props: any) => {},
|
|
94
|
-
removeItem: async (_props: any) => {},
|
|
95
|
-
getItem: (_props: any) => undefined as any,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
Create similar stubs for: `shopify.ts`, `linx.ts`, `vnda.ts`, `nuvemshop.ts`.
|
|
101
|
-
|
|
102
|
-
Match the return shape to what each platform's AddToCartButton expects (some use `addItem`, others `addItems`).
|
|
103
|
-
|
|
104
|
-
## Import Rewrites
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
sed -i '' 's|from "apps/vtex/hooks/useCart.ts"|from "~/hooks/useCart"|g'
|
|
108
|
-
sed -i '' 's|from "apps/vtex/hooks/useUser.ts"|from "~/hooks/useUser"|g'
|
|
109
|
-
sed -i '' 's|from "apps/vtex/hooks/useWishlist.ts"|from "~/hooks/useWishlist"|g'
|
|
110
|
-
sed -i '' 's|from "apps/vtex/utils/types.ts"|from "~/types/vtex"|g'
|
|
111
|
-
sed -i '' 's|from "apps/shopify/hooks/useCart.ts"|from "~/hooks/platform/shopify"|g'
|
|
112
|
-
sed -i '' 's|from "apps/wake/hooks/useCart.ts"|from "~/hooks/platform/wake"|g'
|
|
113
|
-
# etc. for all platforms
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
## Verification
|
|
117
|
-
|
|
118
|
-
```bash
|
|
119
|
-
grep -r 'from "apps/' src/ --include='*.ts' --include='*.tsx'
|
|
120
|
-
# Should return ZERO matches
|
|
121
|
-
```
|
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
# Post-Migration Cleanup
|
|
2
|
-
|
|
3
|
-
After the migration script runs and the site builds + boots, there's a
|
|
4
|
-
recurring set of dead-code and boilerplate cleanup that every migrated
|
|
5
|
-
site benefits from. Run this checklist before the first PR review, not
|
|
6
|
-
after the site has been shipping for weeks.
|
|
7
|
-
|
|
8
|
-
## Run the audit first
|
|
9
|
-
|
|
10
|
-
This whole checklist is now automated by the **`deco-post-cleanup`**
|
|
11
|
-
audit script (added in `@decocms/start >= 2.11.0`, `--fix` mode in
|
|
12
|
-
`>= 2.12.0`). Run it from the site repo to get a structured report
|
|
13
|
-
of which sections below actually apply to your codebase:
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
# Pretty text output, exits 0 unless --strict is passed
|
|
17
|
-
npx -p @decocms/start deco-post-cleanup
|
|
18
|
-
|
|
19
|
-
# Auto-apply mechanical fixes for the safe rules, then report what's left.
|
|
20
|
-
# Safe rules: dead-lib-shims, dead-runtime-shim, local-widgets-types.
|
|
21
|
-
# Other rules stay detect-only — they require human judgment.
|
|
22
|
-
npx -p @decocms/start deco-post-cleanup --fix
|
|
23
|
-
|
|
24
|
-
# Combine for CI: auto-fix safe rules, fail (exit 2) if warnings remain.
|
|
25
|
-
npx -p @decocms/start deco-post-cleanup --fix --strict
|
|
26
|
-
|
|
27
|
-
# Machine-readable JSON for dashboards
|
|
28
|
-
npx -p @decocms/start deco-post-cleanup --json
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
The audit covers all 7 rules below and prints the exact file path +
|
|
32
|
-
suggested fix for each finding. With `--fix`, the three safe rules
|
|
33
|
-
auto-apply (`rm` for dead files, regex-anchored import rewrites for
|
|
34
|
-
shadowed shims). The output explicitly tags rules that require manual
|
|
35
|
-
work as `(0 fixed, manual)`, so you always know what's left after
|
|
36
|
-
auto-fix runs.
|
|
37
|
-
|
|
38
|
-
Real-world signal: on baggagio, `--fix` produced a byte-identical
|
|
39
|
-
diff to the manual cleanup PR a human had just made (45 files,
|
|
40
|
-
+45/-53). On casaevideo-storefront (production), the audit caught
|
|
41
|
-
six silent VTEX shim regressions that no `tsc --noEmit` run can
|
|
42
|
-
detect — those still require manual cleanup until rule 5 gains a
|
|
43
|
-
per-shim mapping table.
|
|
44
|
-
|
|
45
|
-
## 1. Delete unused `src/lib/*` shims
|
|
46
|
-
|
|
47
|
-
The migration script's `templates/lib-utils.ts` generates 11 shim files
|
|
48
|
-
under `src/lib/`. Most of them are NO-OP stubs intended as defensive
|
|
49
|
-
bridges for signature mismatches. In practice many sites use zero of them
|
|
50
|
-
because their loaders import directly from `@decocms/apps/vtex/utils/*`.
|
|
51
|
-
|
|
52
|
-
### How to detect
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
# From repo root.
|
|
56
|
-
for f in src/lib/*.ts; do
|
|
57
|
-
base=$(basename "$f" .ts)
|
|
58
|
-
symbols=$(grep -oE "^export (function|const|interface|type|class) [A-Za-z_][A-Za-z0-9_]*" "$f" | awk '{print $NF}')
|
|
59
|
-
for s in $symbols; do
|
|
60
|
-
# Search for the symbol anywhere in src/ outside src/lib/
|
|
61
|
-
hits=$(rg -l "\\b$s\\b" src/ --type ts --type tsx -g '!src/lib/**')
|
|
62
|
-
if [ -z "$hits" ]; then
|
|
63
|
-
echo "DEAD: $f exports $s with zero external imports"
|
|
64
|
-
fi
|
|
65
|
-
done
|
|
66
|
-
done
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
If every export in a file is dead, delete the file. If `src/lib/` ends
|
|
70
|
-
up empty, delete the directory too.
|
|
71
|
-
|
|
72
|
-
### Real-world data
|
|
73
|
-
|
|
74
|
-
| Site | Files generated | Files used |
|
|
75
|
-
|------|-----------------|-----------|
|
|
76
|
-
| baggagio-tanstack | 11 | 0 (all dead) |
|
|
77
|
-
| casaevideo-storefront | 11 | 1 (wrapped manually) |
|
|
78
|
-
|
|
79
|
-
The files that tend to be dead in every site:
|
|
80
|
-
|
|
81
|
-
- `vtex-client.ts` — type-only export, sites usually grab it from `@decocms/apps`
|
|
82
|
-
- `vtex-fetch.ts` — `fetchSafe` wrapper, supplanted by `@decocms/apps/vtex/utils/fetch`
|
|
83
|
-
- `vtex-id.ts` — manual `parseCookie`, usually shadowed by `~/sdk/orderForm`'s real one
|
|
84
|
-
- `vtex-segment.ts` — NO-OP stubs returning empty; never useful
|
|
85
|
-
- `vtex-intelligent-search.ts` — stubs returning `{}`; supplanted by apps
|
|
86
|
-
- `vtex-transform.ts` — re-exports from `@decocms/apps/vtex/utils/transform` directly
|
|
87
|
-
- `vtex-send-event.ts` — claims to mirror an unreleased apps export; almost never imported
|
|
88
|
-
|
|
89
|
-
The files that occasionally stay used:
|
|
90
|
-
|
|
91
|
-
- `http-utils.ts` — `createHttpClient` proxy bridge for sites with custom
|
|
92
|
-
HTTP clients
|
|
93
|
-
- `graphql-utils.ts` — same shape for GraphQL
|
|
94
|
-
- `fetch-utils.ts` — single `STALE` cache header constant (very small)
|
|
95
|
-
- `filter-navigate.ts` — VTEX filter URL string transformer
|
|
96
|
-
|
|
97
|
-
If `apps/utils/*` imports never appear in your Fresh source, ALL FOUR of
|
|
98
|
-
the latter are also dead.
|
|
99
|
-
|
|
100
|
-
## 2. Drop inline vite plugins that are now framework-provided
|
|
101
|
-
|
|
102
|
-
Two plugins that older site templates inline are obsolete on
|
|
103
|
-
`@decocms/start >= 2.5.0`:
|
|
104
|
-
|
|
105
|
-
```ts
|
|
106
|
-
// site-manual-chunks — overrides framework default chunking
|
|
107
|
-
{ name: "site-manual-chunks", config(_cfg, { command }) { ... ~25 lines ... } }
|
|
108
|
-
|
|
109
|
-
// deco-stub-meta-gen — stubs admin schema on client
|
|
110
|
-
{ name: "deco-stub-meta-gen", enforce: "pre", resolveId(...), load(...) }
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
The framework's `decoVitePlugin()` now handles both:
|
|
114
|
-
- `manualChunks` no longer splits `@decocms/start` / `@decocms/apps` (the
|
|
115
|
-
old split caused circular-dep load-order crashes — every site overrode it)
|
|
116
|
-
- `meta.gen.{json,ts}` is stubbed on the client by default
|
|
117
|
-
|
|
118
|
-
Delete both inline plugins from the site's `vite.config.ts`. Verify the
|
|
119
|
-
production build still succeeds (`vite build` in the site repo).
|
|
120
|
-
|
|
121
|
-
## 3. Drop the `runtime.ts` `invoke` shim
|
|
122
|
-
|
|
123
|
-
Older migrations create `src/runtime.ts` with a manual `createNestedInvokeProxy`
|
|
124
|
-
implementation (~45 lines). The framework's `@decocms/start/sdk` now exports
|
|
125
|
-
both `invoke` (default singleton) and `createAppInvoke` (for typed app-scoped
|
|
126
|
-
proxies). Replace the file with a re-export, or delete it and update import
|
|
127
|
-
sites:
|
|
128
|
-
|
|
129
|
-
```diff
|
|
130
|
-
- import { invoke } from "~/runtime";
|
|
131
|
-
+ import { invoke } from "@decocms/start/sdk";
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
If `~/runtime` was only used for `invoke`, delete the file entirely. If
|
|
135
|
-
it had additional helpers, keep it but trim it down to those.
|
|
136
|
-
|
|
137
|
-
## 4. Drop site-local `withSiteGlobals` workaround
|
|
138
|
-
|
|
139
|
-
If your site's `cmsRouteConfig` has a `cmsRouteWithGlobals` wrapper that
|
|
140
|
-
manually merges `site.global` sections into the page sections list,
|
|
141
|
-
delete it and use `@decocms/start/routes`'s opt-in helper:
|
|
142
|
-
|
|
143
|
-
```ts
|
|
144
|
-
import { cmsRouteConfig, withSiteGlobals } from "@decocms/start/routes";
|
|
145
|
-
|
|
146
|
-
export const Route = createFileRoute(...)({
|
|
147
|
-
...cmsRouteConfig({
|
|
148
|
-
...withSiteGlobals,
|
|
149
|
-
// your route options
|
|
150
|
-
}),
|
|
151
|
-
});
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
The site-side wrapper is typically ~390 LOC; the framework helper is
|
|
155
|
-
opt-in and tested.
|
|
156
|
-
|
|
157
|
-
## 5. Verify `vtex-* shim regression` is not still happening
|
|
158
|
-
|
|
159
|
-
Older versions of the migration script's `phase-cleanup` had a bug where
|
|
160
|
-
it actively rewrote valid `@decocms/apps/vtex/utils/*` and
|
|
161
|
-
`@decocms/apps/vtex/client` imports back to the dead `~/lib/vtex-*` shims.
|
|
162
|
-
Confirm your loaders import direct from `@decocms/apps`:
|
|
163
|
-
|
|
164
|
-
```bash
|
|
165
|
-
rg "from ['\"]~/lib/vtex-" src/
|
|
166
|
-
# Expected: 0 hits (or only site-specific reasons you can articulate)
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
If you see hits, update the imports to point at `@decocms/apps/vtex/...`
|
|
170
|
-
directly (or the corresponding `commerce/utils/*` if it's a generic
|
|
171
|
-
utility). Your runtime behavior gets MUCH better — segment cookies, IS
|
|
172
|
-
cookies, VTEX session auth all start working again instead of being
|
|
173
|
-
silently stubbed to `{}` / `null`.
|
|
174
|
-
|
|
175
|
-
## 6. Drop `src/types/widgets.ts` — framework owns it
|
|
176
|
-
|
|
177
|
-
Older migrations scaffold a local `src/types/widgets.ts` containing 8
|
|
178
|
-
string-aliased widget types (`ImageWidget`, `HTMLWidget`, …). The
|
|
179
|
-
framework now exports the same set (plus `TextArea`) at
|
|
180
|
-
`@decocms/start/types/widgets`, and the schema generator detects the
|
|
181
|
-
widgets via type-text matching, so the local file is purely
|
|
182
|
-
duplicated boilerplate.
|
|
183
|
-
|
|
184
|
-
```bash
|
|
185
|
-
# Quick check
|
|
186
|
-
rg -n "from ['\"]~/types/widgets['\"]" src/ | wc -l # >0 → cleanup applies
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
Replace all imports in one pass:
|
|
190
|
-
|
|
191
|
-
```bash
|
|
192
|
-
# macOS / BSD sed: drop the empty quotes after -i
|
|
193
|
-
rg -l "from ['\"]~/types/widgets['\"]" src/ \
|
|
194
|
-
| xargs sed -i '' "s|from ['\"]~/types/widgets['\"]|from \"@decocms/start/types/widgets\"|g"
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
Then delete the now-orphan local file:
|
|
198
|
-
|
|
199
|
-
```bash
|
|
200
|
-
rm src/types/widgets.ts
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
Confirm `tsc --noEmit` is still clean — the framework version is a
|
|
204
|
-
strict superset of what the migration script generated.
|
|
205
|
-
|
|
206
|
-
## 7. Search for orphan `TODO: move into framework` comments
|
|
207
|
-
|
|
208
|
-
Real sites accumulate `TODO` comments like `// TODO: move into decoVitePlugin
|
|
209
|
-
in next @decocms/start release`. These are roadmap items the framework
|
|
210
|
-
team should pick up, but they often go stale.
|
|
211
|
-
|
|
212
|
-
```bash
|
|
213
|
-
rg -n "TODO.*deco|TODO.*framework|TODO.*move into" src/ vite.config.ts
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
For each hit, decide:
|
|
217
|
-
- Has the framework feature shipped? → migrate to it now and delete the comment
|
|
218
|
-
- Is it deferred indefinitely? → file a tracking issue and link from the comment
|
|
219
|
-
- Is it obsolete? → delete the comment
|
|
220
|
-
|
|
221
|
-
## Verification checklist
|
|
222
|
-
|
|
223
|
-
After completing 1-7:
|
|
224
|
-
|
|
225
|
-
- [ ] `npm run typecheck` baseline matches pre-cleanup count (no new errors)
|
|
226
|
-
- [ ] `npm run dev` starts and `/`, `/some-pdp/p`, `/s?q=foo` all render
|
|
227
|
-
- [ ] `npm run build` succeeds with no chunk-load crashes
|
|
228
|
-
- [ ] Smoke a logged-in PDP to confirm session cookies and segment auth
|
|
229
|
-
work (this is what the `~/lib/vtex-*` regression silently broke)
|
|
230
|
-
- [ ] `git diff --stat` shows only deletions or framework-helper substitutions
|
|
231
|
-
— no new site-local logic added
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
# @preact/signals -> TanStack Store Migration
|
|
2
|
-
|
|
3
|
-
Two distinct patterns need different handling.
|
|
4
|
-
|
|
5
|
-
## Pattern A: Component Hooks (useSignal, useComputed)
|
|
6
|
-
|
|
7
|
-
These are component-local state. Replace with React hooks directly.
|
|
8
|
-
|
|
9
|
-
### useSignal -> useState
|
|
10
|
-
|
|
11
|
-
```typescript
|
|
12
|
-
// OLD
|
|
13
|
-
import { useSignal } from "@preact/signals";
|
|
14
|
-
const loading = useSignal(false);
|
|
15
|
-
loading.value = true; // write
|
|
16
|
-
if (loading.value) { ... } // read
|
|
17
|
-
|
|
18
|
-
// NEW
|
|
19
|
-
import { useState } from "react";
|
|
20
|
-
const [loading, setLoading] = useState(false);
|
|
21
|
-
setLoading(true); // write
|
|
22
|
-
if (loading) { ... } // read
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Setter naming convention: `set` + capitalized variable name.
|
|
26
|
-
|
|
27
|
-
### useComputed -> useMemo
|
|
28
|
-
|
|
29
|
-
```typescript
|
|
30
|
-
// OLD
|
|
31
|
-
import { useComputed } from "@preact/signals";
|
|
32
|
-
const isValid = useComputed(() => name.value.length > 0);
|
|
33
|
-
return <div>{isValid.value}</div>;
|
|
34
|
-
|
|
35
|
-
// NEW
|
|
36
|
-
import { useMemo } from "react";
|
|
37
|
-
const isValid = useMemo(() => name.length > 0, [name]);
|
|
38
|
-
return <div>{isValid}</div>;
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### Automation Tips
|
|
42
|
-
|
|
43
|
-
- `useSignal`/`useComputed` changes are NOT safe for bulk sed (variable names, setter names, `.value` removal all differ per file)
|
|
44
|
-
- Process each file individually: read, identify variable names, transform
|
|
45
|
-
- Watch for: toggle patterns (`x.value = !x.value` -> `setX(prev => !prev)`), object state, conditional assignments
|
|
46
|
-
|
|
47
|
-
## Pattern B: Module-Level Signals (Global State)
|
|
48
|
-
|
|
49
|
-
These create shared state across components. Use `@tanstack/store`.
|
|
50
|
-
|
|
51
|
-
### Create ~/sdk/signal.ts
|
|
52
|
-
|
|
53
|
-
```typescript
|
|
54
|
-
import { Store } from "@tanstack/store";
|
|
55
|
-
import { useSyncExternalStore, useMemo } from "react";
|
|
56
|
-
|
|
57
|
-
export interface Signal<T> {
|
|
58
|
-
readonly store: Store<T>;
|
|
59
|
-
value: T;
|
|
60
|
-
peek(): T;
|
|
61
|
-
subscribe(fn: () => void): () => void;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function signal<T>(initialValue: T): Signal<T> {
|
|
65
|
-
const store = new Store<T>(initialValue);
|
|
66
|
-
return {
|
|
67
|
-
store,
|
|
68
|
-
get value() { return store.state; },
|
|
69
|
-
set value(v: T) { store.setState(() => v); },
|
|
70
|
-
peek() { return store.state; },
|
|
71
|
-
subscribe(fn) {
|
|
72
|
-
// CRITICAL: @tanstack/store@0.9.x returns { unsubscribe: Function },
|
|
73
|
-
// NOT a plain function. React's useSyncExternalStore and useEffect
|
|
74
|
-
// cleanup both expect a bare function. Passing the object causes
|
|
75
|
-
// "TypeError: destroy_ is not a function" at runtime.
|
|
76
|
-
const sub = store.subscribe(() => fn());
|
|
77
|
-
return typeof sub === "function" ? sub : sub.unsubscribe;
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function useSignal<T>(initialValue: T): Signal<T> {
|
|
83
|
-
const sig = useMemo(() => signal(initialValue), []);
|
|
84
|
-
useSyncExternalStore(
|
|
85
|
-
(cb) => sig.subscribe(cb),
|
|
86
|
-
() => sig.value,
|
|
87
|
-
() => sig.value,
|
|
88
|
-
);
|
|
89
|
-
return sig;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function useComputed<T>(fn: () => T): Signal<T> {
|
|
93
|
-
const sig = useMemo(() => signal(fn()), []);
|
|
94
|
-
return sig;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export function computed<T>(fn: () => T): Signal<T> {
|
|
98
|
-
return signal(fn());
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export function effect(fn: () => void | (() => void)): () => void {
|
|
102
|
-
const cleanup = fn();
|
|
103
|
-
return typeof cleanup === "function" ? cleanup : () => {};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function batch(fn: () => void): void {
|
|
107
|
-
fn();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export function useSignalEffect(fn: () => void | (() => void)): void {
|
|
111
|
-
fn();
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export type { Signal as ReadonlySignal };
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
> **WARNING**: The `subscribe()` unwrapping is the single most critical line in
|
|
118
|
-
> this file. Without it, every component using `useSignal` will crash with
|
|
119
|
-
> "TypeError: J is not a function" (minified) or "TypeError: destroy_ is not a
|
|
120
|
-
> function" (non-minified), which then cascades into React #419 hydration
|
|
121
|
-
> failures and #130 undefined component errors across the entire page.
|
|
122
|
-
|
|
123
|
-
### Replace Imports
|
|
124
|
-
|
|
125
|
-
```bash
|
|
126
|
-
# Safe for bulk sed
|
|
127
|
-
sed -i '' 's|from "@preact/signals"|from "~/sdk/signal"|g'
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### React Subscriptions
|
|
131
|
-
|
|
132
|
-
The signal shim's `.value` getter/setter does NOT create automatic React subscriptions. Reading `signal.value` during render won't re-render when the signal changes. This is the #1 source of "it works in Preact but not React" bugs.
|
|
133
|
-
|
|
134
|
-
Replace manual useEffect+subscribe boilerplate with `useStore`:
|
|
135
|
-
|
|
136
|
-
```typescript
|
|
137
|
-
// OLD (manual subscription -- works but verbose)
|
|
138
|
-
const { displayCart } = useUI();
|
|
139
|
-
const [open, setOpen] = useState(false);
|
|
140
|
-
useEffect(() => {
|
|
141
|
-
setOpen(displayCart.value);
|
|
142
|
-
return displayCart.subscribe(() => setOpen(displayCart.value));
|
|
143
|
-
}, []);
|
|
144
|
-
|
|
145
|
-
// NEW (useStore from @tanstack/react-store -- recommended)
|
|
146
|
-
import { useStore } from "@tanstack/react-store";
|
|
147
|
-
const { displayCart } = useUI();
|
|
148
|
-
const open = useStore(displayCart.store);
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
Write-only consumers (event handlers) don't need `useStore`:
|
|
152
|
-
```typescript
|
|
153
|
-
// This still works -- .value setter backed by TanStack Store
|
|
154
|
-
onClick={() => { displayCart.value = true; }}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### DaisyUI Drawer Workaround
|
|
158
|
-
|
|
159
|
-
DaisyUI drawers use a hidden checkbox to toggle visibility. Since signal changes don't trigger React re-renders, the checkbox `checked` attribute never updates. Pragmatic workaround: directly toggle the DOM checkbox alongside the signal:
|
|
160
|
-
|
|
161
|
-
```typescript
|
|
162
|
-
// After setting the signal, also toggle the drawer checkbox
|
|
163
|
-
displayCart.value = true;
|
|
164
|
-
const checkbox = document.getElementById("cart-drawer") as HTMLInputElement;
|
|
165
|
-
if (checkbox) checkbox.checked = true;
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
This is an interim pattern until all signal consumers use `useStore`. The `useStore` approach is the proper fix because it makes the Drawer component re-render, which updates the checkbox `checked` prop through React.
|
|
169
|
-
|
|
170
|
-
### Global State Hook Pattern (useCart, useUI)
|
|
171
|
-
|
|
172
|
-
Hooks that manage global state (cart, UI drawers) need module-level singleton state with a subscription mechanism. The pattern:
|
|
173
|
-
|
|
174
|
-
```typescript
|
|
175
|
-
let _state: CartData | null = null;
|
|
176
|
-
const _listeners = new Set<() => void>();
|
|
177
|
-
|
|
178
|
-
function notify() { _listeners.forEach((fn) => fn()); }
|
|
179
|
-
|
|
180
|
-
export function useCart() {
|
|
181
|
-
const [, forceUpdate] = useState(0);
|
|
182
|
-
|
|
183
|
-
useEffect(() => {
|
|
184
|
-
const listener = () => forceUpdate((n) => n + 1);
|
|
185
|
-
_listeners.add(listener);
|
|
186
|
-
return () => { _listeners.delete(listener); };
|
|
187
|
-
}, []);
|
|
188
|
-
|
|
189
|
-
return {
|
|
190
|
-
cart: {
|
|
191
|
-
get value() { return _state; },
|
|
192
|
-
set value(v) { _state = v; notify(); },
|
|
193
|
-
},
|
|
194
|
-
// ... methods that update _state and call notify()
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
Every component calling `useCart()` subscribes to changes, and state updates trigger re-renders across all subscribers. This replaces the Preact signals global reactivity.
|
|
200
|
-
|
|
201
|
-
## Signal Type References
|
|
202
|
-
|
|
203
|
-
If components use `Signal<T>` as a prop type:
|
|
204
|
-
|
|
205
|
-
```typescript
|
|
206
|
-
// OLD
|
|
207
|
-
import { Signal } from "@preact/signals";
|
|
208
|
-
interface Props { quantity: Signal<number>; }
|
|
209
|
-
|
|
210
|
-
// NEW
|
|
211
|
-
import type { ReactiveSignal } from "~/sdk/signal";
|
|
212
|
-
interface Props { quantity: ReactiveSignal<number>; }
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
## Verification
|
|
216
|
-
|
|
217
|
-
```bash
|
|
218
|
-
grep -r '@preact/signals' src/ --include='*.ts' --include='*.tsx'
|
|
219
|
-
# Should return ZERO matches
|
|
220
|
-
```
|