@base44/vite-plugin 0.1.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/compat/agents.cjs +13 -0
- package/compat/base44Client.cjs +6 -0
- package/compat/entities.cjs +25 -0
- package/compat/functions.cjs +9 -0
- package/compat/integrations.cjs +9 -0
- package/dist/ErrorOverlay.d.ts +12 -0
- package/dist/ErrorOverlay.d.ts.map +1 -0
- package/dist/ErrorOverlay.js +51 -0
- package/dist/ErrorOverlay.js.map +1 -0
- package/dist/error-overlay-plugin.d.ts +3 -0
- package/dist/error-overlay-plugin.d.ts.map +1 -0
- package/dist/error-overlay-plugin.js +15 -0
- package/dist/error-overlay-plugin.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +105 -0
- package/dist/index.js.map +1 -0
- package/dist/visual-edit-plugin.d.ts +3 -0
- package/dist/visual-edit-plugin.d.ts.map +1 -0
- package/dist/visual-edit-plugin.js +229 -0
- package/dist/visual-edit-plugin.js.map +1 -0
- package/package.json +32 -0
- package/src/ErrorOverlay.ts +71 -0
- package/src/error-overlay-plugin.ts +19 -0
- package/src/index.ts +139 -0
- package/src/visual-edit-plugin.ts +268 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const { base44 } = require('./base44Client.cjs');
|
|
2
|
+
|
|
3
|
+
module.exports = new Proxy({}, {
|
|
4
|
+
get: (_, entityName) => {
|
|
5
|
+
return new Proxy({}, {
|
|
6
|
+
get: (_, prop) => {
|
|
7
|
+
if (entityName === 'User') {
|
|
8
|
+
if (prop === 'me') {
|
|
9
|
+
return base44.auth.me;
|
|
10
|
+
}
|
|
11
|
+
if (prop === 'loginWithRedirect' || prop === 'login') {
|
|
12
|
+
return base44.auth.loginWithRedirect;
|
|
13
|
+
}
|
|
14
|
+
if (prop === 'logout') {
|
|
15
|
+
return base44.auth.logout;
|
|
16
|
+
}
|
|
17
|
+
if (prop === 'updateMyUserData') {
|
|
18
|
+
return base44.auth.updateMe;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return base44.entities[entityName][prop];
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
declare const HTMLElement: {
|
|
2
|
+
new (): {};
|
|
3
|
+
};
|
|
4
|
+
export declare class ErrorOverlay extends HTMLElement {
|
|
5
|
+
static getOverlayHTML(): string;
|
|
6
|
+
close(): void;
|
|
7
|
+
static sendErrorToParent(error: Error, title: string, details: string | undefined, componentName: string | undefined): void;
|
|
8
|
+
constructor(error: Error);
|
|
9
|
+
}
|
|
10
|
+
export declare const errorOverlayCode: string;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=ErrorOverlay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ErrorOverlay.d.ts","sourceRoot":"","sources":["../src/ErrorOverlay.ts"],"names":[],"mappings":"AAGA,QAAA,MAAQ,WAAW;;CAA0B,CAAC;AAE9C,qBAAa,YAAa,SAAQ,WAAW;IAC3C,MAAM,CAAC,cAAc;IAMrB,KAAK;IAIL,MAAM,CAAC,iBAAiB,CACtB,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,aAAa,EAAE,MAAM,GAAG,SAAS;gBAqBvB,KAAK,EAAE,KAAK;CAuBzB;AAGD,eAAO,MAAM,gBAAgB,QAG5B,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
// Make HTMLElement available in non-browser environments
|
|
3
|
+
const { HTMLElement = class {
|
|
4
|
+
} } = globalThis;
|
|
5
|
+
export class ErrorOverlay extends HTMLElement {
|
|
6
|
+
static getOverlayHTML() {
|
|
7
|
+
return `
|
|
8
|
+
<div>
|
|
9
|
+
</div>
|
|
10
|
+
`;
|
|
11
|
+
}
|
|
12
|
+
close() {
|
|
13
|
+
this.parentNode?.removeChild(this);
|
|
14
|
+
}
|
|
15
|
+
static sendErrorToParent(error, title, details, componentName) {
|
|
16
|
+
// Send error to parent using framewire
|
|
17
|
+
if (globalThis.window?.parent) {
|
|
18
|
+
try {
|
|
19
|
+
globalThis.window.parent?.postMessage({
|
|
20
|
+
type: "app_error",
|
|
21
|
+
error: { title, details, componentName, originalError: error },
|
|
22
|
+
}, "*");
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
console.warn("Failed to send error to iframe parent:", error?.message);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
constructor(error) {
|
|
30
|
+
super();
|
|
31
|
+
const stack = error?.stack;
|
|
32
|
+
let componentName = stack?.match(/at\s+(\w+)\s+\(eval/)?.[1];
|
|
33
|
+
if (componentName === "eval") {
|
|
34
|
+
componentName = undefined;
|
|
35
|
+
}
|
|
36
|
+
const title = componentName
|
|
37
|
+
? `in ${componentName}: ${error.message?.toString()}`
|
|
38
|
+
: error.message?.toString();
|
|
39
|
+
const details = error?.stack;
|
|
40
|
+
// Call editor frame with the error (via post message)
|
|
41
|
+
ErrorOverlay.sendErrorToParent(error, title, details, componentName);
|
|
42
|
+
// Create the overlay element using HTML template
|
|
43
|
+
const overlay = document.createElement("div");
|
|
44
|
+
overlay.innerHTML = ErrorOverlay.getOverlayHTML();
|
|
45
|
+
// Add to DOM
|
|
46
|
+
document.body.appendChild(overlay);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// vite/react-plugin transpiles classes with _SomeClass, so we need to replace all _ErrorOverlay with ErrorOverlay
|
|
50
|
+
export const errorOverlayCode = ErrorOverlay.toString().replaceAll("_ErrorOverlay", "ErrorOverlay");
|
|
51
|
+
//# sourceMappingURL=ErrorOverlay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ErrorOverlay.js","sourceRoot":"","sources":["../src/ErrorOverlay.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAE3B,yDAAyD;AACzD,MAAM,EAAE,WAAW,GAAG;CAAQ,EAAE,GAAG,UAAU,CAAC;AAE9C,MAAM,OAAO,YAAa,SAAQ,WAAW;IAC3C,MAAM,CAAC,cAAc;QACnB,OAAO;;;KAGN,CAAC;IACJ,CAAC;IACD,KAAK;QACF,IAAY,CAAC,UAAU,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,CAAC,iBAAiB,CACtB,KAAY,EACZ,KAAa,EACb,OAA2B,EAC3B,aAAiC;QAEjC,uCAAuC;QACvC,IAAI,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;YAC9B,IAAI,CAAC;gBACH,UAAU,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CACnC;oBACE,IAAI,EAAE,WAAW;oBACjB,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,KAAK,EAAE;iBAC/D,EACD,GAAG,CACJ,CAAC;YACJ,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,IAAI,CACV,wCAAwC,EACvC,KAAe,EAAE,OAAO,CAC1B,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,YAAY,KAAY;QACtB,KAAK,EAAE,CAAC;QAER,MAAM,KAAK,GAAG,KAAK,EAAE,KAAK,CAAC;QAC3B,IAAI,aAAa,GAAG,KAAK,EAAE,KAAK,CAAC,qBAAqB,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7D,IAAI,aAAa,KAAK,MAAM,EAAE,CAAC;YAC7B,aAAa,GAAG,SAAS,CAAC;QAC5B,CAAC;QACD,MAAM,KAAK,GAAG,aAAa;YACzB,CAAC,CAAC,MAAM,aAAa,KAAK,KAAK,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE;YACrD,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,KAAK,EAAE,KAAK,CAAC;QAE7B,sDAAsD;QACtD,YAAY,CAAC,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;QAErE,iDAAiD;QACjD,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9C,OAAO,CAAC,SAAS,GAAG,YAAY,CAAC,cAAc,EAAE,CAAC;QAElD,aAAa;QACb,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;CACF;AAED,kHAAkH;AAClH,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC,QAAQ,EAAE,CAAC,UAAU,CAChE,eAAe,EACf,cAAc,CACf,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-overlay-plugin.d.ts","sourceRoot":"","sources":["../src/error-overlay-plugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,wBAAgB,kBAAkB,IAc3B,MAAM,CACZ"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { errorOverlayCode } from "./ErrorOverlay.js";
|
|
2
|
+
export function errorOverlayPlugin() {
|
|
3
|
+
return {
|
|
4
|
+
name: "error-overlay",
|
|
5
|
+
apply: (config) => config.mode === "development",
|
|
6
|
+
transform(code, id, opts = {}) {
|
|
7
|
+
if (opts?.ssr)
|
|
8
|
+
return;
|
|
9
|
+
if (!id.includes("vite/dist/client/client.mjs"))
|
|
10
|
+
return;
|
|
11
|
+
return code.replace("class ErrorOverlay", errorOverlayCode + "\nclass OldErrorOverlay");
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=error-overlay-plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-overlay-plugin.js","sourceRoot":"","sources":["../src/error-overlay-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGrD,MAAM,UAAU,kBAAkB;IAChC,OAAO;QACL,IAAI,EAAE,eAAe;QACrB,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,aAAa;QAChD,SAAS,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,GAAG,EAAE;YAC3B,IAAI,IAAI,EAAE,GAAG;gBAAE,OAAO;YAEtB,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,6BAA6B,CAAC;gBAAE,OAAO;YAExD,OAAO,IAAI,CAAC,OAAO,CACjB,oBAAoB,EACpB,gBAAgB,GAAG,yBAAyB,CAC7C,CAAC;QACJ,CAAC;KACQ,CAAC;AACd,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,MAAM,CAAC;AAO/C,MAAM,CAAC,OAAO,UAAU,UAAU,CAChC,IAAI,GAAE;IACJ,gBAAgB,CAAC,EAAE,OAAO,CAAC;CACvB,iBAgIP"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { loadEnv } from "vite";
|
|
2
|
+
import { errorOverlayPlugin } from "./error-overlay-plugin.js";
|
|
3
|
+
import { visualEditPlugin } from "./visual-edit-plugin.js";
|
|
4
|
+
const isRunningInSandbox = !!process.env.MODAL_SANDBOX_ID;
|
|
5
|
+
export default function vitePlugin(opts = {}) {
|
|
6
|
+
const { legacySDKImports = false } = opts;
|
|
7
|
+
return [
|
|
8
|
+
{
|
|
9
|
+
name: "base44",
|
|
10
|
+
config: ({ mode }) => {
|
|
11
|
+
const env = loadEnv(mode ?? "development", process.cwd(), "");
|
|
12
|
+
return {
|
|
13
|
+
resolve: {
|
|
14
|
+
alias: {
|
|
15
|
+
"@/": "/src/",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
...(isRunningInSandbox
|
|
19
|
+
? {
|
|
20
|
+
server: {
|
|
21
|
+
host: "0.0.0.0", // Bind to all interfaces for container access
|
|
22
|
+
port: 5173,
|
|
23
|
+
strictPort: true,
|
|
24
|
+
// Allow all hosts - essential for Modal tunnel URLs
|
|
25
|
+
allowedHosts: true,
|
|
26
|
+
watch: {
|
|
27
|
+
// Enable polling for better file change detection in containers
|
|
28
|
+
usePolling: true,
|
|
29
|
+
interval: 100, // Check every 100ms for responsive HMR
|
|
30
|
+
},
|
|
31
|
+
hmr: {
|
|
32
|
+
protocol: "wss",
|
|
33
|
+
clientPort: 443,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
build: {
|
|
37
|
+
rollupOptions: {
|
|
38
|
+
onwarn(warning, warn) {
|
|
39
|
+
// Treat import errors as fatal errors
|
|
40
|
+
if (warning.code === "UNRESOLVED_IMPORT" ||
|
|
41
|
+
warning.code === "MISSING_EXPORT") {
|
|
42
|
+
throw new Error(`Build failed: ${warning.message}`);
|
|
43
|
+
}
|
|
44
|
+
// Use default for other warnings
|
|
45
|
+
warn(warning);
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
: {}),
|
|
51
|
+
optimizeDeps: {
|
|
52
|
+
esbuildOptions: {
|
|
53
|
+
loader: {
|
|
54
|
+
".js": "jsx",
|
|
55
|
+
},
|
|
56
|
+
...(legacySDKImports
|
|
57
|
+
? {
|
|
58
|
+
define: {
|
|
59
|
+
"process.env.VITE_BASE44_APP_ID": JSON.stringify(env.VITE_BASE44_APP_ID),
|
|
60
|
+
"process.env.VITE_BASE44_BACKEND_URL": JSON.stringify(env.VITE_BASE44_BACKEND_URL),
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
: {}),
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
resolveId(source, importer, options) {
|
|
69
|
+
if (legacySDKImports) {
|
|
70
|
+
if (source.includes("/entities")) {
|
|
71
|
+
return this.resolve("@base44/vite-plugin/compat/entities.cjs", importer, options);
|
|
72
|
+
}
|
|
73
|
+
if (source.includes("/functions")) {
|
|
74
|
+
return this.resolve("@base44/vite-plugin/compat/functions.cjs", importer, options);
|
|
75
|
+
}
|
|
76
|
+
if (source.includes("/integrations")) {
|
|
77
|
+
return this.resolve("@base44/vite-plugin/compat/integrations.cjs", importer, options);
|
|
78
|
+
}
|
|
79
|
+
if (source.includes("@/agents")) {
|
|
80
|
+
return this.resolve("@base44/vite-plugin/compat/agents.cjs", importer, options);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
...(isRunningInSandbox
|
|
87
|
+
? [
|
|
88
|
+
{
|
|
89
|
+
name: "iframe-hmr",
|
|
90
|
+
configureServer(server) {
|
|
91
|
+
server.middlewares.use((req, res, next) => {
|
|
92
|
+
// Allow iframe embedding
|
|
93
|
+
res.setHeader("X-Frame-Options", "ALLOWALL");
|
|
94
|
+
res.setHeader("Content-Security-Policy", "frame-ancestors *;");
|
|
95
|
+
next();
|
|
96
|
+
});
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
errorOverlayPlugin(),
|
|
100
|
+
visualEditPlugin(),
|
|
101
|
+
]
|
|
102
|
+
: []),
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-edit-plugin.d.ts","sourceRoot":"","sources":["../src/visual-edit-plugin.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AA+FnC,wBAAgB,gBAAgB,IAuKzB,MAAM,CACZ"}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { parse } from "@babel/parser";
|
|
2
|
+
import { default as traverse } from "@babel/traverse";
|
|
3
|
+
import { default as generate } from "@babel/generator";
|
|
4
|
+
import * as t from "@babel/types";
|
|
5
|
+
// Helper function to check if JSX element contains dynamic content
|
|
6
|
+
function checkIfElementHasDynamicContent(jsxElement) {
|
|
7
|
+
let hasDynamicContent = false;
|
|
8
|
+
// Helper function to check if any node contains dynamic patterns
|
|
9
|
+
function checkNodeForDynamicContent(node) {
|
|
10
|
+
// JSX expressions like {variable}, {func()}, {obj.prop}
|
|
11
|
+
if (t.isJSXExpressionContainer(node)) {
|
|
12
|
+
const expression = node.expression;
|
|
13
|
+
// Skip empty expressions {}
|
|
14
|
+
if (t.isJSXEmptyExpression(expression)) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
// Any non-literal expression is considered dynamic
|
|
18
|
+
if (!t.isLiteral(expression)) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Template literals with expressions `Hello ${name}`
|
|
23
|
+
if (t.isTemplateLiteral(node) && node.expressions.length > 0) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
// Member expressions like props.title, state.value
|
|
27
|
+
if (t.isMemberExpression(node)) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
// Function calls like getData(), format()
|
|
31
|
+
if (t.isCallExpression(node)) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
// Conditional expressions like condition ? "yes" : "no"
|
|
35
|
+
if (t.isConditionalExpression(node)) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
// Identifier references (could be props, state, variables)
|
|
39
|
+
if (t.isIdentifier(node)) {
|
|
40
|
+
// Common dynamic identifiers
|
|
41
|
+
const dynamicNames = [
|
|
42
|
+
"props",
|
|
43
|
+
"state",
|
|
44
|
+
"data",
|
|
45
|
+
"item",
|
|
46
|
+
"value",
|
|
47
|
+
"text",
|
|
48
|
+
"content",
|
|
49
|
+
];
|
|
50
|
+
if (dynamicNames.some((name) => node.name.includes(name))) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
// Recursively traverse all child nodes
|
|
57
|
+
function traverseNode(node) {
|
|
58
|
+
if (checkNodeForDynamicContent(node)) {
|
|
59
|
+
hasDynamicContent = true;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// Recursively check child nodes
|
|
63
|
+
Object.keys(node).forEach((key) => {
|
|
64
|
+
const value = node[key];
|
|
65
|
+
if (Array.isArray(value)) {
|
|
66
|
+
value.forEach((child) => {
|
|
67
|
+
if (child && typeof child === "object" && child.type) {
|
|
68
|
+
traverseNode(child);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
else if (value && typeof value === "object" && value.type) {
|
|
73
|
+
traverseNode(value);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
// Check all children of the JSX element
|
|
78
|
+
jsxElement.children.forEach((child) => {
|
|
79
|
+
if (hasDynamicContent)
|
|
80
|
+
return; // Early exit if already found dynamic content
|
|
81
|
+
traverseNode(child);
|
|
82
|
+
});
|
|
83
|
+
return hasDynamicContent;
|
|
84
|
+
}
|
|
85
|
+
export function visualEditPlugin() {
|
|
86
|
+
return {
|
|
87
|
+
name: "visual-edit-transform",
|
|
88
|
+
apply: (config) => config.mode === "development",
|
|
89
|
+
enforce: "pre",
|
|
90
|
+
order: "pre",
|
|
91
|
+
// Inject Tailwind CDN for visual editing capabilities
|
|
92
|
+
transformIndexHtml(html) {
|
|
93
|
+
// Inject the Tailwind CSS CDN script right before the closing </head> tag
|
|
94
|
+
const tailwindScript = ` <!-- Tailwind CSS CDN for visual editing -->\n <script src="https://cdn.tailwindcss.com"></script>\n `;
|
|
95
|
+
return html.replace("</head>", tailwindScript + "</head>");
|
|
96
|
+
},
|
|
97
|
+
transform(code, id) {
|
|
98
|
+
// Skip node_modules and visual-edit-agent itself
|
|
99
|
+
if (id.includes("node_modules") || id.includes("visual-edit-agent")) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
// Process JS/JSX/TS/TSX files
|
|
103
|
+
if (!id.match(/\.(jsx?|tsx?)$/)) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
// Extract filename from path, preserving pages/ or components/ structure
|
|
107
|
+
const pathParts = id.split("/");
|
|
108
|
+
let filename;
|
|
109
|
+
// Check if this is a pages or components file
|
|
110
|
+
if (id.includes("/pages/")) {
|
|
111
|
+
const pagesIndex = pathParts.findIndex((part) => part === "pages");
|
|
112
|
+
if (pagesIndex >= 0 && pagesIndex < pathParts.length - 1) {
|
|
113
|
+
// Get all parts from 'pages' to the file, preserving nested structure
|
|
114
|
+
const relevantParts = pathParts.slice(pagesIndex, pathParts.length);
|
|
115
|
+
const lastPart = relevantParts[relevantParts.length - 1];
|
|
116
|
+
// Remove file extension from the last part
|
|
117
|
+
relevantParts[relevantParts.length - 1] = lastPart.includes(".")
|
|
118
|
+
? lastPart.split(".")[0]
|
|
119
|
+
: lastPart;
|
|
120
|
+
filename = relevantParts.join("/");
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
filename = pathParts[pathParts.length - 1];
|
|
124
|
+
if (filename.includes(".")) {
|
|
125
|
+
filename = filename.split(".")[0];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else if (id.includes("/components/")) {
|
|
130
|
+
const componentsIndex = pathParts.findIndex((part) => part === "components");
|
|
131
|
+
if (componentsIndex >= 0 && componentsIndex < pathParts.length - 1) {
|
|
132
|
+
// Get all parts from 'components' to the file, preserving nested structure
|
|
133
|
+
const relevantParts = pathParts.slice(componentsIndex, pathParts.length);
|
|
134
|
+
const lastPart = relevantParts[relevantParts.length - 1];
|
|
135
|
+
// Remove file extension from the last part
|
|
136
|
+
relevantParts[relevantParts.length - 1] = lastPart.includes(".")
|
|
137
|
+
? lastPart.split(".")[0]
|
|
138
|
+
: lastPart;
|
|
139
|
+
filename = relevantParts.join("/");
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
filename = pathParts[pathParts.length - 1];
|
|
143
|
+
if (filename.includes(".")) {
|
|
144
|
+
filename = filename.split(".")[0];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
// For other files (like layout), just use the filename
|
|
150
|
+
filename = pathParts[pathParts.length - 1];
|
|
151
|
+
if (filename.includes(".")) {
|
|
152
|
+
filename = filename.split(".")[0];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
// Parse the code into an AST
|
|
157
|
+
const ast = parse(code, {
|
|
158
|
+
sourceType: "module",
|
|
159
|
+
plugins: [
|
|
160
|
+
"jsx",
|
|
161
|
+
"typescript",
|
|
162
|
+
"decorators-legacy",
|
|
163
|
+
"classProperties",
|
|
164
|
+
"objectRestSpread",
|
|
165
|
+
"functionBind",
|
|
166
|
+
"exportDefaultFrom",
|
|
167
|
+
"exportNamespaceFrom",
|
|
168
|
+
"dynamicImport",
|
|
169
|
+
"nullishCoalescingOperator",
|
|
170
|
+
"optionalChaining",
|
|
171
|
+
"asyncGenerators",
|
|
172
|
+
"bigInt",
|
|
173
|
+
"optionalCatchBinding",
|
|
174
|
+
"throwExpressions",
|
|
175
|
+
],
|
|
176
|
+
});
|
|
177
|
+
// Traverse the AST and add source location and dynamic content attributes to JSX elements
|
|
178
|
+
let elementsProcessed = 0;
|
|
179
|
+
traverse.default(ast, {
|
|
180
|
+
JSXElement(path) {
|
|
181
|
+
const jsxElement = path.node;
|
|
182
|
+
const openingElement = jsxElement.openingElement;
|
|
183
|
+
// Skip fragments
|
|
184
|
+
if (t.isJSXFragment(jsxElement))
|
|
185
|
+
return;
|
|
186
|
+
// Skip if already has source location attribute
|
|
187
|
+
const hasSourceLocation = openingElement.attributes.some((attr) => t.isJSXAttribute(attr) &&
|
|
188
|
+
t.isJSXIdentifier(attr.name) &&
|
|
189
|
+
attr.name.name === "data-source-location");
|
|
190
|
+
if (hasSourceLocation)
|
|
191
|
+
return;
|
|
192
|
+
// Get line and column from AST node location
|
|
193
|
+
const { line, column } = openingElement.loc?.start || {
|
|
194
|
+
line: 1,
|
|
195
|
+
column: 0,
|
|
196
|
+
};
|
|
197
|
+
// Create the source location attribute
|
|
198
|
+
const sourceLocationAttr = t.jsxAttribute(t.jsxIdentifier("data-source-location"), t.stringLiteral(`${filename}:${line}:${column}`));
|
|
199
|
+
// Check if element has dynamic content
|
|
200
|
+
const isDynamic = checkIfElementHasDynamicContent(jsxElement);
|
|
201
|
+
// Create the dynamic content attribute
|
|
202
|
+
const dynamicContentAttr = t.jsxAttribute(t.jsxIdentifier("data-dynamic-content"), t.stringLiteral(isDynamic ? "true" : "false"));
|
|
203
|
+
// Add both attributes to the beginning of the attributes array
|
|
204
|
+
openingElement.attributes.unshift(sourceLocationAttr, dynamicContentAttr);
|
|
205
|
+
elementsProcessed++;
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
// Generate the code back from the AST
|
|
209
|
+
const result = generate.default(ast, {
|
|
210
|
+
compact: false,
|
|
211
|
+
concise: false,
|
|
212
|
+
retainLines: true,
|
|
213
|
+
});
|
|
214
|
+
return {
|
|
215
|
+
code: result.code,
|
|
216
|
+
map: null,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
console.error("Failed to add source location to JSX:", error);
|
|
221
|
+
return {
|
|
222
|
+
code: code, // Return original code on failure
|
|
223
|
+
map: null,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=visual-edit-plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-edit-plugin.js","sourceRoot":"","sources":["../src/visual-edit-plugin.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@base44/vite-plugin",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The Vite plugin for base44 based applications",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/index.js",
|
|
9
|
+
"./compat/*": "./compat/*"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"src",
|
|
14
|
+
"compat"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/babel__generator": "^7.27.0",
|
|
21
|
+
"@types/babel__traverse": "^7.28.0",
|
|
22
|
+
"@types/node": "^24.6.2",
|
|
23
|
+
"typescript": "^5.9.3",
|
|
24
|
+
"vite": "^7.1.9"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@babel/generator": "^7.28.5",
|
|
28
|
+
"@babel/parser": "^7.28.5",
|
|
29
|
+
"@babel/traverse": "^7.28.5",
|
|
30
|
+
"@babel/types": "^7.28.5"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
|
|
3
|
+
// Make HTMLElement available in non-browser environments
|
|
4
|
+
const { HTMLElement = class {} } = globalThis;
|
|
5
|
+
|
|
6
|
+
export class ErrorOverlay extends HTMLElement {
|
|
7
|
+
static getOverlayHTML() {
|
|
8
|
+
return `
|
|
9
|
+
<div>
|
|
10
|
+
</div>
|
|
11
|
+
`;
|
|
12
|
+
}
|
|
13
|
+
close() {
|
|
14
|
+
(this as any).parentNode?.removeChild(this);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static sendErrorToParent(
|
|
18
|
+
error: Error,
|
|
19
|
+
title: string,
|
|
20
|
+
details: string | undefined,
|
|
21
|
+
componentName: string | undefined
|
|
22
|
+
) {
|
|
23
|
+
// Send error to parent using framewire
|
|
24
|
+
if (globalThis.window?.parent) {
|
|
25
|
+
try {
|
|
26
|
+
globalThis.window.parent?.postMessage(
|
|
27
|
+
{
|
|
28
|
+
type: "app_error",
|
|
29
|
+
error: { title, details, componentName, originalError: error },
|
|
30
|
+
},
|
|
31
|
+
"*"
|
|
32
|
+
);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.warn(
|
|
35
|
+
"Failed to send error to iframe parent:",
|
|
36
|
+
(error as Error)?.message
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
constructor(error: Error) {
|
|
43
|
+
super();
|
|
44
|
+
|
|
45
|
+
const stack = error?.stack;
|
|
46
|
+
let componentName = stack?.match(/at\s+(\w+)\s+\(eval/)?.[1];
|
|
47
|
+
if (componentName === "eval") {
|
|
48
|
+
componentName = undefined;
|
|
49
|
+
}
|
|
50
|
+
const title = componentName
|
|
51
|
+
? `in ${componentName}: ${error.message?.toString()}`
|
|
52
|
+
: error.message?.toString();
|
|
53
|
+
const details = error?.stack;
|
|
54
|
+
|
|
55
|
+
// Call editor frame with the error (via post message)
|
|
56
|
+
ErrorOverlay.sendErrorToParent(error, title, details, componentName);
|
|
57
|
+
|
|
58
|
+
// Create the overlay element using HTML template
|
|
59
|
+
const overlay = document.createElement("div");
|
|
60
|
+
overlay.innerHTML = ErrorOverlay.getOverlayHTML();
|
|
61
|
+
|
|
62
|
+
// Add to DOM
|
|
63
|
+
document.body.appendChild(overlay);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// vite/react-plugin transpiles classes with _SomeClass, so we need to replace all _ErrorOverlay with ErrorOverlay
|
|
68
|
+
export const errorOverlayCode = ErrorOverlay.toString().replaceAll(
|
|
69
|
+
"_ErrorOverlay",
|
|
70
|
+
"ErrorOverlay"
|
|
71
|
+
);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { errorOverlayCode } from "./ErrorOverlay.js";
|
|
2
|
+
import type { Plugin } from "vite";
|
|
3
|
+
|
|
4
|
+
export function errorOverlayPlugin() {
|
|
5
|
+
return {
|
|
6
|
+
name: "error-overlay",
|
|
7
|
+
apply: (config) => config.mode === "development",
|
|
8
|
+
transform(code, id, opts = {}) {
|
|
9
|
+
if (opts?.ssr) return;
|
|
10
|
+
|
|
11
|
+
if (!id.includes("vite/dist/client/client.mjs")) return;
|
|
12
|
+
|
|
13
|
+
return code.replace(
|
|
14
|
+
"class ErrorOverlay",
|
|
15
|
+
errorOverlayCode + "\nclass OldErrorOverlay"
|
|
16
|
+
);
|
|
17
|
+
},
|
|
18
|
+
} as Plugin;
|
|
19
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { Plugin, UserConfig } from "vite";
|
|
2
|
+
import { loadEnv } from "vite";
|
|
3
|
+
import { errorOverlayPlugin } from "./error-overlay-plugin.js";
|
|
4
|
+
import { visualEditPlugin } from "./visual-edit-plugin.js";
|
|
5
|
+
|
|
6
|
+
const isRunningInSandbox = !!process.env.MODAL_SANDBOX_ID;
|
|
7
|
+
|
|
8
|
+
export default function vitePlugin(
|
|
9
|
+
opts: {
|
|
10
|
+
legacySDKImports?: boolean;
|
|
11
|
+
} = {}
|
|
12
|
+
) {
|
|
13
|
+
const { legacySDKImports = false } = opts;
|
|
14
|
+
|
|
15
|
+
return [
|
|
16
|
+
{
|
|
17
|
+
name: "base44",
|
|
18
|
+
config: ({ mode }) => {
|
|
19
|
+
const env = loadEnv(mode ?? "development", process.cwd(), "");
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
resolve: {
|
|
23
|
+
alias: {
|
|
24
|
+
"@/": "/src/",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
...(isRunningInSandbox
|
|
28
|
+
? ({
|
|
29
|
+
server: {
|
|
30
|
+
host: "0.0.0.0", // Bind to all interfaces for container access
|
|
31
|
+
port: 5173,
|
|
32
|
+
strictPort: true,
|
|
33
|
+
// Allow all hosts - essential for Modal tunnel URLs
|
|
34
|
+
allowedHosts: true,
|
|
35
|
+
watch: {
|
|
36
|
+
// Enable polling for better file change detection in containers
|
|
37
|
+
usePolling: true,
|
|
38
|
+
interval: 100, // Check every 100ms for responsive HMR
|
|
39
|
+
},
|
|
40
|
+
hmr: {
|
|
41
|
+
protocol: "wss",
|
|
42
|
+
clientPort: 443,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
build: {
|
|
46
|
+
rollupOptions: {
|
|
47
|
+
onwarn(warning, warn) {
|
|
48
|
+
// Treat import errors as fatal errors
|
|
49
|
+
if (
|
|
50
|
+
warning.code === "UNRESOLVED_IMPORT" ||
|
|
51
|
+
warning.code === "MISSING_EXPORT"
|
|
52
|
+
) {
|
|
53
|
+
throw new Error(`Build failed: ${warning.message}`);
|
|
54
|
+
}
|
|
55
|
+
// Use default for other warnings
|
|
56
|
+
warn(warning);
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
} as Partial<UserConfig>)
|
|
61
|
+
: {}),
|
|
62
|
+
optimizeDeps: {
|
|
63
|
+
esbuildOptions: {
|
|
64
|
+
loader: {
|
|
65
|
+
".js": "jsx",
|
|
66
|
+
},
|
|
67
|
+
...(legacySDKImports
|
|
68
|
+
? {
|
|
69
|
+
define: {
|
|
70
|
+
"process.env.VITE_BASE44_APP_ID": JSON.stringify(
|
|
71
|
+
env.VITE_BASE44_APP_ID
|
|
72
|
+
),
|
|
73
|
+
"process.env.VITE_BASE44_BACKEND_URL": JSON.stringify(
|
|
74
|
+
env.VITE_BASE44_BACKEND_URL
|
|
75
|
+
),
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
: {}),
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
resolveId(source, importer, options) {
|
|
84
|
+
if (legacySDKImports) {
|
|
85
|
+
if (source.includes("/entities")) {
|
|
86
|
+
return this.resolve(
|
|
87
|
+
"@base44/vite-plugin/compat/entities.cjs",
|
|
88
|
+
importer,
|
|
89
|
+
options
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (source.includes("/functions")) {
|
|
94
|
+
return this.resolve(
|
|
95
|
+
"@base44/vite-plugin/compat/functions.cjs",
|
|
96
|
+
importer,
|
|
97
|
+
options
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (source.includes("/integrations")) {
|
|
102
|
+
return this.resolve(
|
|
103
|
+
"@base44/vite-plugin/compat/integrations.cjs",
|
|
104
|
+
importer,
|
|
105
|
+
options
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (source.includes("@/agents")) {
|
|
110
|
+
return this.resolve(
|
|
111
|
+
"@base44/vite-plugin/compat/agents.cjs",
|
|
112
|
+
importer,
|
|
113
|
+
options
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return null;
|
|
119
|
+
},
|
|
120
|
+
} as Plugin,
|
|
121
|
+
...(isRunningInSandbox
|
|
122
|
+
? [
|
|
123
|
+
{
|
|
124
|
+
name: "iframe-hmr",
|
|
125
|
+
configureServer(server) {
|
|
126
|
+
server.middlewares.use((req, res, next) => {
|
|
127
|
+
// Allow iframe embedding
|
|
128
|
+
res.setHeader("X-Frame-Options", "ALLOWALL");
|
|
129
|
+
res.setHeader("Content-Security-Policy", "frame-ancestors *;");
|
|
130
|
+
next();
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
} as Plugin,
|
|
134
|
+
errorOverlayPlugin(),
|
|
135
|
+
visualEditPlugin(),
|
|
136
|
+
]
|
|
137
|
+
: []),
|
|
138
|
+
];
|
|
139
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { parse } from "@babel/parser";
|
|
2
|
+
import { default as traverse } from "@babel/traverse";
|
|
3
|
+
import { default as generate } from "@babel/generator";
|
|
4
|
+
import * as t from "@babel/types";
|
|
5
|
+
import type { Plugin } from "vite";
|
|
6
|
+
|
|
7
|
+
// Helper function to check if JSX element contains dynamic content
|
|
8
|
+
function checkIfElementHasDynamicContent(jsxElement: any) {
|
|
9
|
+
let hasDynamicContent = false;
|
|
10
|
+
|
|
11
|
+
// Helper function to check if any node contains dynamic patterns
|
|
12
|
+
function checkNodeForDynamicContent(node: any) {
|
|
13
|
+
// JSX expressions like {variable}, {func()}, {obj.prop}
|
|
14
|
+
if (t.isJSXExpressionContainer(node)) {
|
|
15
|
+
const expression = node.expression;
|
|
16
|
+
|
|
17
|
+
// Skip empty expressions {}
|
|
18
|
+
if (t.isJSXEmptyExpression(expression)) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Any non-literal expression is considered dynamic
|
|
23
|
+
if (!t.isLiteral(expression)) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Template literals with expressions `Hello ${name}`
|
|
29
|
+
if (t.isTemplateLiteral(node) && node.expressions.length > 0) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Member expressions like props.title, state.value
|
|
34
|
+
if (t.isMemberExpression(node)) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Function calls like getData(), format()
|
|
39
|
+
if (t.isCallExpression(node)) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Conditional expressions like condition ? "yes" : "no"
|
|
44
|
+
if (t.isConditionalExpression(node)) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Identifier references (could be props, state, variables)
|
|
49
|
+
if (t.isIdentifier(node)) {
|
|
50
|
+
// Common dynamic identifiers
|
|
51
|
+
const dynamicNames = [
|
|
52
|
+
"props",
|
|
53
|
+
"state",
|
|
54
|
+
"data",
|
|
55
|
+
"item",
|
|
56
|
+
"value",
|
|
57
|
+
"text",
|
|
58
|
+
"content",
|
|
59
|
+
];
|
|
60
|
+
if (dynamicNames.some((name) => node.name.includes(name))) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Recursively traverse all child nodes
|
|
69
|
+
function traverseNode(node: any) {
|
|
70
|
+
if (checkNodeForDynamicContent(node)) {
|
|
71
|
+
hasDynamicContent = true;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Recursively check child nodes
|
|
76
|
+
Object.keys(node).forEach((key) => {
|
|
77
|
+
const value = node[key];
|
|
78
|
+
|
|
79
|
+
if (Array.isArray(value)) {
|
|
80
|
+
value.forEach((child) => {
|
|
81
|
+
if (child && typeof child === "object" && child.type) {
|
|
82
|
+
traverseNode(child);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
} else if (value && typeof value === "object" && value.type) {
|
|
86
|
+
traverseNode(value);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Check all children of the JSX element
|
|
92
|
+
jsxElement.children.forEach((child: any) => {
|
|
93
|
+
if (hasDynamicContent) return; // Early exit if already found dynamic content
|
|
94
|
+
traverseNode(child);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return hasDynamicContent;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function visualEditPlugin() {
|
|
101
|
+
return {
|
|
102
|
+
name: "visual-edit-transform",
|
|
103
|
+
apply: (config) => config.mode === "development",
|
|
104
|
+
enforce: "pre",
|
|
105
|
+
order: "pre",
|
|
106
|
+
// Inject Tailwind CDN for visual editing capabilities
|
|
107
|
+
transformIndexHtml(html: any) {
|
|
108
|
+
// Inject the Tailwind CSS CDN script right before the closing </head> tag
|
|
109
|
+
const tailwindScript = ` <!-- Tailwind CSS CDN for visual editing -->\n <script src="https://cdn.tailwindcss.com"></script>\n `;
|
|
110
|
+
return html.replace("</head>", tailwindScript + "</head>");
|
|
111
|
+
},
|
|
112
|
+
transform(code: any, id: any) {
|
|
113
|
+
// Skip node_modules and visual-edit-agent itself
|
|
114
|
+
if (id.includes("node_modules") || id.includes("visual-edit-agent")) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Process JS/JSX/TS/TSX files
|
|
119
|
+
if (!id.match(/\.(jsx?|tsx?)$/)) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Extract filename from path, preserving pages/ or components/ structure
|
|
124
|
+
const pathParts = id.split("/");
|
|
125
|
+
let filename;
|
|
126
|
+
|
|
127
|
+
// Check if this is a pages or components file
|
|
128
|
+
if (id.includes("/pages/")) {
|
|
129
|
+
const pagesIndex = pathParts.findIndex((part: any) => part === "pages");
|
|
130
|
+
if (pagesIndex >= 0 && pagesIndex < pathParts.length - 1) {
|
|
131
|
+
// Get all parts from 'pages' to the file, preserving nested structure
|
|
132
|
+
const relevantParts = pathParts.slice(pagesIndex, pathParts.length);
|
|
133
|
+
const lastPart = relevantParts[relevantParts.length - 1];
|
|
134
|
+
// Remove file extension from the last part
|
|
135
|
+
relevantParts[relevantParts.length - 1] = lastPart.includes(".")
|
|
136
|
+
? lastPart.split(".")[0]
|
|
137
|
+
: lastPart;
|
|
138
|
+
filename = relevantParts.join("/");
|
|
139
|
+
} else {
|
|
140
|
+
filename = pathParts[pathParts.length - 1];
|
|
141
|
+
if (filename.includes(".")) {
|
|
142
|
+
filename = filename.split(".")[0];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} else if (id.includes("/components/")) {
|
|
146
|
+
const componentsIndex = pathParts.findIndex(
|
|
147
|
+
(part: any) => part === "components"
|
|
148
|
+
);
|
|
149
|
+
if (componentsIndex >= 0 && componentsIndex < pathParts.length - 1) {
|
|
150
|
+
// Get all parts from 'components' to the file, preserving nested structure
|
|
151
|
+
const relevantParts = pathParts.slice(
|
|
152
|
+
componentsIndex,
|
|
153
|
+
pathParts.length
|
|
154
|
+
);
|
|
155
|
+
const lastPart = relevantParts[relevantParts.length - 1];
|
|
156
|
+
// Remove file extension from the last part
|
|
157
|
+
relevantParts[relevantParts.length - 1] = lastPart.includes(".")
|
|
158
|
+
? lastPart.split(".")[0]
|
|
159
|
+
: lastPart;
|
|
160
|
+
filename = relevantParts.join("/");
|
|
161
|
+
} else {
|
|
162
|
+
filename = pathParts[pathParts.length - 1];
|
|
163
|
+
if (filename.includes(".")) {
|
|
164
|
+
filename = filename.split(".")[0];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
// For other files (like layout), just use the filename
|
|
169
|
+
filename = pathParts[pathParts.length - 1];
|
|
170
|
+
if (filename.includes(".")) {
|
|
171
|
+
filename = filename.split(".")[0];
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
// Parse the code into an AST
|
|
177
|
+
const ast = parse(code, {
|
|
178
|
+
sourceType: "module",
|
|
179
|
+
plugins: [
|
|
180
|
+
"jsx",
|
|
181
|
+
"typescript",
|
|
182
|
+
"decorators-legacy",
|
|
183
|
+
"classProperties",
|
|
184
|
+
"objectRestSpread",
|
|
185
|
+
"functionBind",
|
|
186
|
+
"exportDefaultFrom",
|
|
187
|
+
"exportNamespaceFrom",
|
|
188
|
+
"dynamicImport",
|
|
189
|
+
"nullishCoalescingOperator",
|
|
190
|
+
"optionalChaining",
|
|
191
|
+
"asyncGenerators",
|
|
192
|
+
"bigInt",
|
|
193
|
+
"optionalCatchBinding",
|
|
194
|
+
"throwExpressions",
|
|
195
|
+
],
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Traverse the AST and add source location and dynamic content attributes to JSX elements
|
|
199
|
+
let elementsProcessed = 0;
|
|
200
|
+
traverse.default(ast, {
|
|
201
|
+
JSXElement(path) {
|
|
202
|
+
const jsxElement = path.node;
|
|
203
|
+
const openingElement = jsxElement.openingElement;
|
|
204
|
+
|
|
205
|
+
// Skip fragments
|
|
206
|
+
if (t.isJSXFragment(jsxElement)) return;
|
|
207
|
+
|
|
208
|
+
// Skip if already has source location attribute
|
|
209
|
+
const hasSourceLocation = openingElement.attributes.some(
|
|
210
|
+
(attr) =>
|
|
211
|
+
t.isJSXAttribute(attr) &&
|
|
212
|
+
t.isJSXIdentifier(attr.name) &&
|
|
213
|
+
attr.name.name === "data-source-location"
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
if (hasSourceLocation) return;
|
|
217
|
+
|
|
218
|
+
// Get line and column from AST node location
|
|
219
|
+
const { line, column } = openingElement.loc?.start || {
|
|
220
|
+
line: 1,
|
|
221
|
+
column: 0,
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// Create the source location attribute
|
|
225
|
+
const sourceLocationAttr = t.jsxAttribute(
|
|
226
|
+
t.jsxIdentifier("data-source-location"),
|
|
227
|
+
t.stringLiteral(`${filename}:${line}:${column}`)
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
// Check if element has dynamic content
|
|
231
|
+
const isDynamic = checkIfElementHasDynamicContent(jsxElement);
|
|
232
|
+
|
|
233
|
+
// Create the dynamic content attribute
|
|
234
|
+
const dynamicContentAttr = t.jsxAttribute(
|
|
235
|
+
t.jsxIdentifier("data-dynamic-content"),
|
|
236
|
+
t.stringLiteral(isDynamic ? "true" : "false")
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
// Add both attributes to the beginning of the attributes array
|
|
240
|
+
openingElement.attributes.unshift(
|
|
241
|
+
sourceLocationAttr,
|
|
242
|
+
dynamicContentAttr
|
|
243
|
+
);
|
|
244
|
+
elementsProcessed++;
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Generate the code back from the AST
|
|
249
|
+
const result = generate.default(ast, {
|
|
250
|
+
compact: false,
|
|
251
|
+
concise: false,
|
|
252
|
+
retainLines: true,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
code: result.code,
|
|
257
|
+
map: null,
|
|
258
|
+
};
|
|
259
|
+
} catch (error) {
|
|
260
|
+
console.error("Failed to add source location to JSX:", error);
|
|
261
|
+
return {
|
|
262
|
+
code: code, // Return original code on failure
|
|
263
|
+
map: null,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
} as Plugin;
|
|
268
|
+
}
|