@base44/vite-plugin 1.0.28 → 1.0.31
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/dist/capabilities/inline-edit/controller.d.ts.map +1 -1
- package/dist/capabilities/inline-edit/controller.js +2 -5
- package/dist/capabilities/inline-edit/controller.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -2
- package/dist/index.js.map +1 -1
- package/dist/injections/unhandled-errors-handlers.d.ts +2 -1
- package/dist/injections/unhandled-errors-handlers.d.ts.map +1 -1
- package/dist/injections/unhandled-errors-handlers.js +34 -19
- package/dist/injections/unhandled-errors-handlers.js.map +1 -1
- package/dist/injections/utils.d.ts +6 -0
- package/dist/injections/utils.d.ts.map +1 -1
- package/dist/injections/utils.js +8 -0
- package/dist/injections/utils.js.map +1 -1
- package/dist/injections/visual-edit-agent.d.ts.map +1 -1
- package/dist/injections/visual-edit-agent.js +2 -5
- package/dist/injections/visual-edit-agent.js.map +1 -1
- package/dist/processors/collection-item-field-processor.d.ts.map +1 -1
- package/dist/processors/collection-item-field-processor.js +11 -0
- package/dist/processors/collection-item-field-processor.js.map +1 -1
- package/dist/statics/index.mjs +7 -7
- package/dist/statics/index.mjs.map +1 -1
- package/dist/visual-edit-plugin.d.ts.map +1 -1
- package/dist/visual-edit-plugin.js +20 -22
- package/dist/visual-edit-plugin.js.map +1 -1
- package/package.json +1 -1
- package/src/capabilities/inline-edit/controller.ts +2 -6
- package/src/index.ts +21 -2
- package/src/injections/unhandled-errors-handlers.ts +36 -19
- package/src/injections/utils.ts +9 -0
- package/src/injections/visual-edit-agent.ts +2 -6
- package/src/processors/collection-item-field-processor.ts +12 -0
- package/src/visual-edit-plugin.md +13 -13
- package/src/visual-edit-plugin.ts +22 -26
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"visual-edit-plugin.d.ts","sourceRoot":"","sources":["../src/visual-edit-plugin.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"visual-edit-plugin.d.ts","sourceRoot":"","sources":["../src/visual-edit-plugin.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAa,MAAM,EAAc,MAAM,MAAM,CAAC;AAI1D,wBAAgB,gBAAgB,IA+EzB,MAAM,CACZ"}
|
|
@@ -7,7 +7,13 @@ import { JSXUtils } from "./jsx-utils.js";
|
|
|
7
7
|
export function visualEditPlugin() {
|
|
8
8
|
return {
|
|
9
9
|
name: "visual-edit-transform",
|
|
10
|
-
|
|
10
|
+
// Dev SERVER only: the injected data-* markers are consumed by the
|
|
11
|
+
// visual-edit agent in the live preview, which vite serves — never by
|
|
12
|
+
// build output. Gating on mode alone also caught `vite build --mode
|
|
13
|
+
// development` (the platform's validation build), where instrumenting
|
|
14
|
+
// user code is pure risk: an injection bug there fails the build on
|
|
15
|
+
// code that doesn't exist on disk.
|
|
16
|
+
apply: (config, env) => env.command === "serve" && config.mode === "development",
|
|
11
17
|
enforce: "pre",
|
|
12
18
|
order: "pre",
|
|
13
19
|
transformIndexHtml(html) {
|
|
@@ -73,28 +79,20 @@ export function visualEditPlugin() {
|
|
|
73
79
|
},
|
|
74
80
|
};
|
|
75
81
|
}
|
|
82
|
+
// Emit the canonical real path (src/pages/Home.jsx) the backend and sandbox use, NOT a
|
|
83
|
+
// legacy alias (pages/Home). The path is anchored on the last "src" segment with the
|
|
84
|
+
// extension retained, so consumers get the file's true sandbox path directly.
|
|
76
85
|
function extractFilename(id) {
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
86
|
+
// Drop any Vite query suffix (e.g. "?v=...") and normalize separators.
|
|
87
|
+
const cleanId = id.split("?")[0].replace(/\\/g, "/");
|
|
88
|
+
const parts = cleanId.split("/");
|
|
89
|
+
// Anchor on the LAST "src" segment so a "src" elsewhere in the absolute path
|
|
90
|
+
// (e.g. a user's home dir) can't mis-anchor. base44 apps keep all source under src/.
|
|
91
|
+
const srcIndex = parts.lastIndexOf("src");
|
|
92
|
+
if (srcIndex >= 0) {
|
|
93
|
+
return parts.slice(srcIndex).join("/"); // src/pages/Home.jsx, src/Layout.jsx, src/components/ui/Card.tsx
|
|
84
94
|
}
|
|
85
|
-
|
|
86
|
-
return
|
|
87
|
-
}
|
|
88
|
-
function findSegmentIndex(parts, segments) {
|
|
89
|
-
for (const segment of segments) {
|
|
90
|
-
const idx = parts.findIndex((part) => part === segment);
|
|
91
|
-
if (idx >= 0)
|
|
92
|
-
return idx;
|
|
93
|
-
}
|
|
94
|
-
return -1;
|
|
95
|
-
}
|
|
96
|
-
function stripExtension(filename) {
|
|
97
|
-
const dotIndex = filename.indexOf(".");
|
|
98
|
-
return dotIndex >= 0 ? filename.substring(0, dotIndex) : filename;
|
|
95
|
+
// Fallback: file not under src/ — use the bare filename (extension kept).
|
|
96
|
+
return parts[parts.length - 1] ?? "";
|
|
99
97
|
}
|
|
100
98
|
//# sourceMappingURL=visual-edit-plugin.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"visual-edit-plugin.js","sourceRoot":"","sources":["../src/visual-edit-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,CAAC,MAAM,cAAc,CAAC;AAElC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,MAAM,UAAU,gBAAgB;IAC9B,OAAO;QACL,IAAI,EAAE,uBAAuB;QAC7B,KAAK,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"visual-edit-plugin.js","sourceRoot":"","sources":["../src/visual-edit-plugin.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
shouldEnterInlineEditingMode,
|
|
7
7
|
isStaticArrayTextElement,
|
|
8
8
|
} from "./dom-utils.js";
|
|
9
|
-
import { PLUGIN_ELEMENT_ATTR } from "../../injections/utils.js";
|
|
9
|
+
import { PLUGIN_ELEMENT_ATTR, readElementClasses } from "../../injections/utils.js";
|
|
10
10
|
|
|
11
11
|
const DEBOUNCE_MS = 500;
|
|
12
12
|
|
|
@@ -36,17 +36,13 @@ export function createInlineEditController(
|
|
|
36
36
|
const originalContent = element.dataset.originalTextContent;
|
|
37
37
|
const newContent = element.textContent;
|
|
38
38
|
|
|
39
|
-
const svgElement = element as unknown as SVGElement;
|
|
40
39
|
const rect = element.getBoundingClientRect();
|
|
41
40
|
|
|
42
41
|
const message: Record<string, unknown> = {
|
|
43
42
|
type: "inline-edit",
|
|
44
43
|
elementInfo: {
|
|
45
44
|
tagName: element.tagName,
|
|
46
|
-
classes:
|
|
47
|
-
(svgElement.className as unknown as SVGAnimatedString)?.baseVal ||
|
|
48
|
-
element.className ||
|
|
49
|
-
"",
|
|
45
|
+
classes: readElementClasses(element),
|
|
50
46
|
visualSelectorId: host.getSelectedElementId(),
|
|
51
47
|
content: newContent,
|
|
52
48
|
dataSourceLocation: element.dataset.sourceLocation,
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,19 @@ import { buildStatusPlugin } from "./build-status-plugin.js";
|
|
|
8
8
|
|
|
9
9
|
const isRunningInSandbox = !!process.env.MODAL_SANDBOX_ID;
|
|
10
10
|
|
|
11
|
+
const ENV_UNSET_GUIDANCE = [
|
|
12
|
+
"[base44] No Base44 backend configured — VITE_BASE44_APP_BASE_URL is not set, so /api requests have nowhere to go.",
|
|
13
|
+
"[base44] Run the app through the Base44 CLI instead:",
|
|
14
|
+
"[base44] base44 dev app + local backend (throwaway local data)",
|
|
15
|
+
"[base44] base44 dev --remote app + your app's real backend (production data)",
|
|
16
|
+
].join("\n");
|
|
17
|
+
|
|
18
|
+
const BUILD_WITHOUT_APP_ID_WARNING = [
|
|
19
|
+
"[base44] Warning: VITE_BASE44_APP_ID is not set — this build",
|
|
20
|
+
"[base44] will not know its app id and its API calls will fail.",
|
|
21
|
+
"[base44] Build with: base44 build (or base44 deploy --build)",
|
|
22
|
+
].join("\n");
|
|
23
|
+
|
|
11
24
|
export default function vitePlugin(
|
|
12
25
|
opts: {
|
|
13
26
|
legacySDKImports?: boolean;
|
|
@@ -28,9 +41,13 @@ export default function vitePlugin(
|
|
|
28
41
|
return [
|
|
29
42
|
{
|
|
30
43
|
name: "base44",
|
|
31
|
-
config: ({ mode, root = process.cwd() }) => {
|
|
44
|
+
config: ({ mode, root = process.cwd() }, { command }) => {
|
|
32
45
|
const env = loadEnv(mode ?? "development", root, "");
|
|
33
46
|
|
|
47
|
+
if (!isRunningInSandbox && command === "build" && !env.VITE_BASE44_APP_ID) {
|
|
48
|
+
console.log(BUILD_WITHOUT_APP_ID_WARNING);
|
|
49
|
+
}
|
|
50
|
+
|
|
34
51
|
return {
|
|
35
52
|
resolve: {
|
|
36
53
|
alias: {
|
|
@@ -110,7 +127,9 @@ export default function vitePlugin(
|
|
|
110
127
|
};
|
|
111
128
|
}
|
|
112
129
|
console.log(
|
|
113
|
-
|
|
130
|
+
command === "serve"
|
|
131
|
+
? ENV_UNSET_GUIDANCE
|
|
132
|
+
: "[base44] Proxy not enabled (VITE_BASE44_APP_BASE_URL not set)"
|
|
114
133
|
);
|
|
115
134
|
return {};
|
|
116
135
|
})()),
|
|
@@ -56,7 +56,7 @@ function onAppError({
|
|
|
56
56
|
}: {
|
|
57
57
|
title: string;
|
|
58
58
|
details: string;
|
|
59
|
-
componentName: string;
|
|
59
|
+
componentName: string | undefined;
|
|
60
60
|
originalError: any;
|
|
61
61
|
}) {
|
|
62
62
|
if (originalError?.response?.status === 402) {
|
|
@@ -80,35 +80,52 @@ function onAppError({
|
|
|
80
80
|
);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
function
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
// extract function name from "at X (eval" where X is the function name
|
|
84
|
+
function extractFunctionName(stack: unknown): string | undefined {
|
|
85
|
+
if (typeof stack !== "string") return undefined;
|
|
86
|
+
return stack.match(/at\s+(\w+)\s+\(eval/)?.[1];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// A rejection reason or error can be any value — undefined, a string, a
|
|
90
|
+
// null-prototype object. Throwing here masks the real error entirely.
|
|
91
|
+
function describeErrorValue(value: unknown): string {
|
|
92
|
+
try {
|
|
93
|
+
return String(value);
|
|
94
|
+
} catch {
|
|
95
|
+
return Object.prototype.toString.call(value);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function handleUnhandledRejection(event: any) {
|
|
100
|
+
const reason = event?.reason;
|
|
101
|
+
const functionName = extractFunctionName(reason?.stack);
|
|
102
|
+
const text = describeErrorValue(reason);
|
|
103
|
+
const msg = functionName ? `Error in ${functionName}: ${text}` : text;
|
|
90
104
|
onAppError({
|
|
91
105
|
title: msg,
|
|
92
|
-
details:
|
|
106
|
+
details: text,
|
|
93
107
|
componentName: functionName,
|
|
94
|
-
originalError:
|
|
108
|
+
originalError: reason,
|
|
95
109
|
});
|
|
96
110
|
}
|
|
97
111
|
|
|
98
|
-
function handleWindowError(event: any) {
|
|
99
|
-
const
|
|
100
|
-
let functionName = stack
|
|
112
|
+
export function handleWindowError(event: any) {
|
|
113
|
+
const error = event?.error;
|
|
114
|
+
let functionName = extractFunctionName(error?.stack);
|
|
101
115
|
if (functionName === "eval") {
|
|
102
|
-
functionName =
|
|
116
|
+
functionName = undefined;
|
|
103
117
|
}
|
|
104
118
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
119
|
+
// window.onerror fires with a null error for cross-origin script failures
|
|
120
|
+
const text =
|
|
121
|
+
error == null && typeof event?.message === "string"
|
|
122
|
+
? event.message
|
|
123
|
+
: describeErrorValue(error);
|
|
124
|
+
const msg = functionName ? `in ${functionName}: ${text}` : text;
|
|
108
125
|
onAppError({
|
|
109
126
|
title: msg,
|
|
110
|
-
details:
|
|
127
|
+
details: text,
|
|
111
128
|
componentName: functionName,
|
|
112
|
-
originalError:
|
|
129
|
+
originalError: error,
|
|
113
130
|
});
|
|
114
131
|
}
|
package/src/injections/utils.ts
CHANGED
|
@@ -98,6 +98,15 @@ export function updateElementAttribute(elements: Element[], attribute: string, v
|
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Read an element's classes as a plain string, safe to postMessage.
|
|
103
|
+
* `element.className` is an SVGAnimatedString on SVG elements, which is not
|
|
104
|
+
* structured-cloneable — posting it throws DataCloneError and the preview hangs.
|
|
105
|
+
*/
|
|
106
|
+
export function readElementClasses(element: Element): string {
|
|
107
|
+
return element.getAttribute("class") ?? "";
|
|
108
|
+
}
|
|
109
|
+
|
|
101
110
|
/** Collect attribute values from an element for a given allowlist. */
|
|
102
111
|
export function collectAllowedAttributes(element: Element, allowedAttributes: string[]): Record<string, string> {
|
|
103
112
|
const attributes: Record<string, string> = {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { findElementsById, updateElementClasses, updateElementAttribute, collectAllowedAttributes, ALLOWED_ATTRIBUTES, getElementSelectorId, stopAnimations, resumeAnimations, findInstrumentedElement, resolveHoverTarget, positionLabel, injectFontFaceCss } from "./utils.js";
|
|
1
|
+
import { findElementsById, updateElementClasses, updateElementAttribute, collectAllowedAttributes, readElementClasses, ALLOWED_ATTRIBUTES, getElementSelectorId, stopAnimations, resumeAnimations, findInstrumentedElement, resolveHoverTarget, positionLabel, injectFontFaceCss } from "./utils.js";
|
|
2
2
|
import { createLayerController } from "./layer-dropdown/controller.js";
|
|
3
3
|
import { LAYER_DROPDOWN_ATTR } from "./layer-dropdown/consts.js";
|
|
4
4
|
import { createInlineEditController } from "../capabilities/inline-edit/index.js";
|
|
@@ -137,7 +137,6 @@ export function setupVisualEditAgent() {
|
|
|
137
137
|
const notifyElementSelected = (element: Element) => {
|
|
138
138
|
const htmlElement = element as HTMLElement;
|
|
139
139
|
const rect = element.getBoundingClientRect();
|
|
140
|
-
const svgElement = element as SVGElement;
|
|
141
140
|
const isTextElement = TEXT_TAGS.includes(element.tagName?.toLowerCase());
|
|
142
141
|
|
|
143
142
|
const arrEl = htmlElement.closest("[data-arr-variable-name]") as HTMLElement | null;
|
|
@@ -153,10 +152,7 @@ export function setupVisualEditAgent() {
|
|
|
153
152
|
window.parent.postMessage({
|
|
154
153
|
type: "element-selected",
|
|
155
154
|
tagName: element.tagName,
|
|
156
|
-
classes:
|
|
157
|
-
(svgElement.className as unknown as SVGAnimatedString)?.baseVal ||
|
|
158
|
-
element.className ||
|
|
159
|
-
"",
|
|
155
|
+
classes: readElementClasses(element),
|
|
160
156
|
visualSelectorId: getElementSelectorId(element),
|
|
161
157
|
content: isTextElement ? htmlElement.innerText : undefined,
|
|
162
158
|
dataSourceLocation: htmlElement.dataset.sourceLocation,
|
|
@@ -382,6 +382,18 @@ export class DataItemFieldProcessor {
|
|
|
382
382
|
}
|
|
383
383
|
|
|
384
384
|
const fieldToInject = idField ?? "id";
|
|
385
|
+
|
|
386
|
+
// Never inject a param whose name is already bound where the JSX
|
|
387
|
+
// element sits. A function-scope binding (e.g. `const { id } =
|
|
388
|
+
// useParams()`) makes the output fail to compile — "The symbol 'id'
|
|
389
|
+
// has already been declared" — an outer-scope binding would be
|
|
390
|
+
// silently shadowed by an undefined prop, and a block-scope binding
|
|
391
|
+
// between the element and the function would make the emitted
|
|
392
|
+
// attribute read the wrong value. Anchoring on the element's scope
|
|
393
|
+
// chain covers all three; skipping just degrades visual-edit tracing
|
|
394
|
+
// for this element.
|
|
395
|
+
if (path.scope.hasBinding(fieldToInject)) return;
|
|
396
|
+
|
|
385
397
|
const newProp = this.types.objectProperty(
|
|
386
398
|
this.types.identifier(fieldToInject),
|
|
387
399
|
this.types.identifier(fieldToInject),
|
|
@@ -14,7 +14,7 @@ It adds two HTML `data-*` attributes to every JSX element:
|
|
|
14
14
|
|
|
15
15
|
// AFTER (served to browser):
|
|
16
16
|
<Button
|
|
17
|
-
data-source-location="components/Button:42:8"
|
|
17
|
+
data-source-location="src/components/Button.jsx:42:8"
|
|
18
18
|
data-dynamic-content="false"
|
|
19
19
|
className="px-4"
|
|
20
20
|
>Click me</Button>
|
|
@@ -29,7 +29,7 @@ It adds two HTML `data-*` attributes to every JSX element:
|
|
|
29
29
|
| Condition | Where | Why |
|
|
30
30
|
|-----------|-------|-----|
|
|
31
31
|
| `MODAL_SANDBOX_ID` env var is set | `index.ts:8` | Only runs inside Modal sandbox containers |
|
|
32
|
-
|
|
|
32
|
+
| Dev **server** in `"development"` mode | `apply: (config, env) => env.command === "serve" && config.mode === "development"` | The injected `data-*` markers are only consumed by the visual-edit agent in the live preview, which the dev server serves. Builds never use them — including `vite build --mode development` (the platform's validation build), where instrumenting user code once produced compile errors on code that wasn't on disk |
|
|
33
33
|
| File is `.js`, `.jsx`, `.ts`, or `.tsx` | `id.match(/\.(jsx?\|tsx?)$/)` | Only JSX-capable files |
|
|
34
34
|
| File is NOT in `node_modules` | `id.includes("node_modules")` | Skip third-party code |
|
|
35
35
|
| File is NOT `visual-edit-agent` | `id.includes("visual-edit-agent")` | Don't instrument the agent itself |
|
|
@@ -37,7 +37,8 @@ It adds two HTML `data-*` attributes to every JSX element:
|
|
|
37
37
|
### Execution Timeline
|
|
38
38
|
|
|
39
39
|
```
|
|
40
|
-
1. npm run dev
|
|
40
|
+
1. npm run dev (dev SERVER only — `vite build` never registers this plugin,
|
|
41
|
+
regardless of mode)
|
|
41
42
|
2. Vite starts, loads plugin config
|
|
42
43
|
3. index.ts checks: isRunningInSandbox = !!process.env.MODAL_SANDBOX_ID
|
|
43
44
|
4. If true → registers visualEditPlugin() in the plugin pipeline
|
|
@@ -65,19 +66,18 @@ It adds two HTML `data-*` attributes to every JSX element:
|
|
|
65
66
|
|
|
66
67
|
### Step 1: Filename Extraction (lines 123-173)
|
|
67
68
|
|
|
68
|
-
Builds
|
|
69
|
+
Builds the canonical real source path (the path the backend and sandbox use) from the full file path:
|
|
69
70
|
|
|
70
71
|
```
|
|
71
|
-
/Users/dev/project/src/pages/About/index.tsx → "pages/About/index"
|
|
72
|
-
/Users/dev/project/src/components/ui/Button.tsx → "components/ui/Button"
|
|
73
|
-
/Users/dev/project/src/Layout.tsx → "Layout"
|
|
72
|
+
/Users/dev/project/src/pages/About/index.tsx → "src/pages/About/index.tsx"
|
|
73
|
+
/Users/dev/project/src/components/ui/Button.tsx → "src/components/ui/Button.tsx"
|
|
74
|
+
/Users/dev/project/src/Layout.tsx → "src/Layout.tsx"
|
|
74
75
|
```
|
|
75
76
|
|
|
76
77
|
**Rules:**
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
- File extensions are always stripped
|
|
78
|
+
- Anchors on the last `src/` segment and keeps everything from there, preserving nested structure
|
|
79
|
+
- The file extension is retained (`.jsx`/`.tsx`/`.js`/`.ts`)
|
|
80
|
+
- Files not under `src/` → just the filename (extension kept)
|
|
81
81
|
|
|
82
82
|
### Step 2: AST Parsing (lines 177-196)
|
|
83
83
|
|
|
@@ -260,7 +260,7 @@ The parent window (editor UI) and the sandbox iframe can't share a DOM. The `dat
|
|
|
260
260
|
```
|
|
261
261
|
Parent Window (Editor UI) iframe Sandbox (React App)
|
|
262
262
|
───────────────────────── ──────────────────────────
|
|
263
|
-
"Select element components/Button:42:8" → querySelector('[data-source-location="components/Button:42:8"]')
|
|
263
|
+
"Select element src/components/Button.jsx:42:8" → querySelector('[data-source-location="src/components/Button.jsx:42:8"]')
|
|
264
264
|
← "Element found: classes='px-4', isDynamic=false"
|
|
265
265
|
"Update classes to 'px-6 py-3'" → element.setAttribute("class", "px-6 py-3")
|
|
266
266
|
```
|
|
@@ -305,7 +305,7 @@ The `transformIndexHtml` hook injects the Tailwind CSS CDN, enabling visual edit
|
|
|
305
305
|
│ │ React App (DOM) │ │
|
|
306
306
|
│ │ │ │
|
|
307
307
|
│ │ <div data-source-location= │ │
|
|
308
|
-
│ │ "pages/Home:10:4"
|
|
308
|
+
│ │ "src/pages/Home.jsx:10:4" │ │
|
|
309
309
|
│ │ data-dynamic-content="true"> │ │
|
|
310
310
|
│ │ {greeting} │ │
|
|
311
311
|
│ │ </div> │ │
|
|
@@ -2,14 +2,21 @@ import { parse } from "@babel/parser";
|
|
|
2
2
|
import { default as traverse } from "@babel/traverse";
|
|
3
3
|
import { default as generate } from "@babel/generator";
|
|
4
4
|
import * as t from "@babel/types";
|
|
5
|
-
import type { Plugin } from "vite";
|
|
5
|
+
import type { ConfigEnv, Plugin, UserConfig } from "vite";
|
|
6
6
|
import { JSXProcessor } from "./jsx-processor.js";
|
|
7
7
|
import { JSXUtils } from "./jsx-utils.js";
|
|
8
8
|
|
|
9
9
|
export function visualEditPlugin() {
|
|
10
10
|
return {
|
|
11
11
|
name: "visual-edit-transform",
|
|
12
|
-
|
|
12
|
+
// Dev SERVER only: the injected data-* markers are consumed by the
|
|
13
|
+
// visual-edit agent in the live preview, which vite serves — never by
|
|
14
|
+
// build output. Gating on mode alone also caught `vite build --mode
|
|
15
|
+
// development` (the platform's validation build), where instrumenting
|
|
16
|
+
// user code is pure risk: an injection bug there fails the build on
|
|
17
|
+
// code that doesn't exist on disk.
|
|
18
|
+
apply: (config: UserConfig, env: ConfigEnv) =>
|
|
19
|
+
env.command === "serve" && config.mode === "development",
|
|
13
20
|
enforce: "pre",
|
|
14
21
|
order: "pre",
|
|
15
22
|
transformIndexHtml(html: any) {
|
|
@@ -81,30 +88,19 @@ export function visualEditPlugin() {
|
|
|
81
88
|
} as Plugin;
|
|
82
89
|
}
|
|
83
90
|
|
|
91
|
+
// Emit the canonical real path (src/pages/Home.jsx) the backend and sandbox use, NOT a
|
|
92
|
+
// legacy alias (pages/Home). The path is anchored on the last "src" segment with the
|
|
93
|
+
// extension retained, so consumers get the file's true sandbox path directly.
|
|
84
94
|
function extractFilename(id: string): string {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return
|
|
95
|
+
// Drop any Vite query suffix (e.g. "?v=...") and normalize separators.
|
|
96
|
+
const cleanId = id.split("?")[0]!.replace(/\\/g, "/");
|
|
97
|
+
const parts = cleanId.split("/");
|
|
98
|
+
// Anchor on the LAST "src" segment so a "src" elsewhere in the absolute path
|
|
99
|
+
// (e.g. a user's home dir) can't mis-anchor. base44 apps keep all source under src/.
|
|
100
|
+
const srcIndex = parts.lastIndexOf("src");
|
|
101
|
+
if (srcIndex >= 0) {
|
|
102
|
+
return parts.slice(srcIndex).join("/"); // src/pages/Home.jsx, src/Layout.jsx, src/components/ui/Card.tsx
|
|
93
103
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return stripExtension(lastPart);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function findSegmentIndex(parts: string[], segments: string[]): number {
|
|
100
|
-
for (const segment of segments) {
|
|
101
|
-
const idx = parts.findIndex((part) => part === segment);
|
|
102
|
-
if (idx >= 0) return idx;
|
|
103
|
-
}
|
|
104
|
-
return -1;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function stripExtension(filename: string): string {
|
|
108
|
-
const dotIndex = filename.indexOf(".");
|
|
109
|
-
return dotIndex >= 0 ? filename.substring(0, dotIndex) : filename;
|
|
104
|
+
// Fallback: file not under src/ — use the bare filename (extension kept).
|
|
105
|
+
return parts[parts.length - 1] ?? "";
|
|
110
106
|
}
|