Skip to content

Commit f30ddd1

Browse files
committed
fixed product bug
1 parent 672af33 commit f30ddd1

24 files changed

Lines changed: 597 additions & 1928 deletions

bin/create-ojs-app

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ async function createProject(projectName, template = "basic") {
8585
preview: "vite preview",
8686
},
8787
dependencies: {
88-
"modular-openscriptjs": "^1.0.0",
88+
"modular-openscriptjs": "latest",
8989
},
9090
devDependencies: {
9191
vite: "^5.0.7",

build/vite-plugin-openscript.js

Lines changed: 161 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,10 @@ import MagicString from "magic-string";
88
* Automatically discovers components and provides IDE autocomplete + bundling
99
*/
1010
export function openScriptComponentPlugin(options = {}) {
11-
const {
12-
componentsDir = "src/components",
13-
autoRegister = true,
14-
generateTypes = true,
15-
} = options;
11+
const { componentsDir = "src/components", generateTypes = true } = options;
1612

1713
let config;
1814
let components = [];
19-
const virtualModuleId = "virtual:openscript-components";
20-
const resolvedVirtualModuleId = "\0" + virtualModuleId;
2115

2216
return {
2317
name: "openscript-component-plugin",
@@ -32,7 +26,7 @@ export function openScriptComponentPlugin(options = {}) {
3226

3327
if (!fs.existsSync(componentsPath)) {
3428
console.warn(
35-
`[OpenScript] Components directory not found: ${componentsPath}`
29+
`[OpenScript] Components directory not found: ${componentsPath}`,
3630
);
3731
return;
3832
}
@@ -42,7 +36,7 @@ export function openScriptComponentPlugin(options = {}) {
4236

4337
console.log(
4438
`[OpenScript] Found ${components.length} components:`,
45-
components.map((c) => c.name).join(", ")
39+
components.map((c) => c.name).join(", "),
4640
);
4741

4842
// Generate TypeScript definitions if enabled
@@ -51,83 +45,185 @@ export function openScriptComponentPlugin(options = {}) {
5145
}
5246
},
5347

54-
resolveId(id) {
55-
if (id === virtualModuleId) {
56-
return resolvedVirtualModuleId;
57-
}
58-
},
59-
60-
load(id) {
61-
if (id === resolvedVirtualModuleId) {
62-
// Generate virtual module that imports all components
63-
return generateVirtualModule(
64-
config.root,
65-
componentsDir,
66-
components,
67-
autoRegister
68-
);
69-
}
70-
},
71-
7248
transform(code, id) {
49+
// Normalize path for Windows compatibility
50+
const normalizedId = normalizePath(id);
51+
7352
// Only transform files in components directory
74-
if (!id.includes(componentsDir) || !id.endsWith(".js")) return;
53+
if (
54+
!normalizedId.includes(componentsDir) ||
55+
!normalizedId.endsWith(".js")
56+
)
57+
return;
7558

76-
// Find class definition
77-
const classMatch = code.match(/class\s+(\w+)\s+extends\s+Component/);
78-
if (!classMatch) return;
59+
const s = new MagicString(code);
60+
let hasChanged = false;
61+
62+
// 1. Check for Functional Components: function MyComp(...) { ... }
63+
// Regex matches: 1=export_modifier, 2=Name, 3=Args
64+
const funcRegex =
65+
/(export\s+default\s+|export\s+)?function\s+([A-Z]\w*)\s*\(([^)]*)\)\s*\{/g;
66+
let match;
67+
68+
// We need to handle multiple components in one file, though rare for default exports
69+
// But let's loop to be safe and use a while loop with exec
70+
while ((match = funcRegex.exec(code)) !== null) {
71+
const [fullMatch, exportModifier = "", name, args] = match;
72+
const start = match.index;
73+
const bodyStart = start + fullMatch.length - 1; // pointing to {
74+
75+
// Find closing brace
76+
let braceCount = 1;
77+
let end = -1;
78+
let i = bodyStart + 1;
79+
80+
while (i < code.length) {
81+
if (code[i] === "{") braceCount++;
82+
else if (code[i] === "}") braceCount--;
83+
84+
if (braceCount === 0) {
85+
end = i;
86+
break;
87+
}
88+
i++;
89+
}
7990

80-
const className = classMatch[1];
91+
if (end !== -1) {
92+
// Found the component body
93+
hasChanged = true;
94+
95+
// Replace header
96+
// "export default function Name(args) {"
97+
// ->
98+
// "export default class Name extends Component { constructor() { super(); this.name = "Name"; } render(args) {"
99+
const replacement = `${exportModifier}class ${name} extends Component {
100+
constructor() {
101+
super();
102+
this.name = "${name}";
103+
}
81104
82-
// If code already sets this.name explicitly, skip (simple check)
83-
if (code.includes(`this.name = "${className}"`)) return;
105+
render(${args}) {`;
84106

85-
const s = new MagicString(code);
107+
s.overwrite(start, bodyStart + 1, replacement);
86108

87-
if (code.includes("constructor")) {
88-
// Inject after super()
89-
const superMatch = code.match(/(super\s*\([^)]*\)\s*;?)/);
90-
if (superMatch) {
91-
const index = superMatch.index + superMatch[0].length;
92-
s.appendRight(
93-
index,
94-
`\n if (!this.name) this.name = "${className}";`
95-
);
109+
// Append closing brace for the class
110+
s.appendRight(end + 1, "\n}");
96111
}
97-
} else {
98-
// No constructor, inject one
99-
const classDef = classMatch[0];
100-
const openBraceIndex = code.indexOf("{", code.indexOf(classDef));
112+
}
101113

102-
if (openBraceIndex !== -1) {
114+
// 2. Existing logic: Class Component name injection
115+
// Only run this if we haven't just converted it (though regex below checks for class)
116+
// If we converted, we already injected the name.
117+
// But there might be other classes in the file.
118+
119+
// Note: If we just converted, s.toString() inside loop?
120+
// MagicString handles edits on original string.
121+
// But our regex 'code.match' works on original code.
122+
// If we have mixed content, we should be careful.
123+
// The original logic scanned for "class ... extends Component".
124+
// Our new logic creates that string in output, but original code didn't have it.
125+
// So checks against 'code' for class will only find ORIGINAL classes.
126+
127+
const classRegex = /class\s+(\w+)\s+extends\s+Component/g;
128+
while ((match = classRegex.exec(code)) !== null) {
129+
const className = match[1];
130+
131+
// Check if this class already has name set in ORIGINAL code
132+
// (If we synthesized it, we don't need to do this, and regex won't find synthesized one)
133+
const classStart = match.index;
134+
// Simple check range for existing name assignment is hard with regex loop
135+
// But we can check globally if the code has it for this class
136+
if (code.includes(`this.name = "${className}"`)) continue;
137+
138+
// Find constructor or inject it
139+
// We need to look inside the class body.
140+
// Find the opening brace for this class
141+
const openBraceIndex = code.indexOf("{", classStart);
142+
if (openBraceIndex === -1) continue;
143+
144+
// Check if constructor exists within reasonable distance/scope?
145+
// This is tricky with simple string searching on the whole file.
146+
// But let's assume standard structure or rely on previous logic's robustness
147+
// The previous logic used 'if (code.includes("constructor"))' which is global!
148+
// That was capable of false positives if multiple classes existed or comments.
149+
// But let's stick to the previous logic style for consistency but apply it carefully.
150+
151+
// Actually, since I'm rewriting the transform function, I can try to improve it slightly
152+
// or just keep it as is for existing classes.
153+
154+
// Given constraints, I will preserve the logic's INTENT:
155+
// Ensure `this.name` is set.
156+
157+
// For the *functional* transformation I just added, I *know* I added the constructor.
158+
// So I don't need to process those.
159+
// So I only process matches from `classRegex` on the `code` string.
160+
// Since `code` is original source, it won't include my functional-to-class transforms.
161+
// So I am only processing originally-written classes. Perfect.
162+
163+
// Re-implementing existing logic for original classes:
164+
165+
// Try to find constructor in the class body?
166+
// We'll search for `constructor` keyword after class def.
167+
const nextClassIndex = code.indexOf("class ", openBraceIndex);
168+
const searchEnd = nextClassIndex === -1 ? code.length : nextClassIndex;
169+
const constructorMatch = code
170+
.substring(openBraceIndex, searchEnd)
171+
.match(/constructor\s*\(/);
172+
173+
if (constructorMatch) {
174+
// Constructor exists
175+
// Find super() call relative to class start
176+
const constructorAbsIndex = openBraceIndex + constructorMatch.index;
177+
// Search for super() after constructor
178+
const superMatch = code
179+
.substring(constructorAbsIndex, searchEnd)
180+
.match(/super\s*\([^)]*\)\s*;?/);
181+
if (superMatch) {
182+
const insertAt =
183+
constructorAbsIndex + superMatch.index + superMatch[0].length;
184+
s.appendRight(
185+
insertAt,
186+
`\n if (!this.name) this.name = "${className}";`,
187+
);
188+
hasChanged = true;
189+
}
190+
} else {
191+
// No constructor, inject one at start of class
103192
s.appendRight(
104193
openBraceIndex + 1,
105-
`\n constructor() { super(); this.name = "${className}"; }`
194+
`\n constructor() { super(); this.name = "${className}"; }`,
106195
);
196+
hasChanged = true;
107197
}
108198
}
109199

110-
if (s.hasChanged()) {
200+
// 3. Inject Component import if needed
201+
// If we did any transformation (functional or existing class), we might need Component.
202+
// Especially for functional -> class.
203+
if (hasChanged) {
204+
// Check if Component is imported
205+
// Matches: import { Component } ... or import ... Component ...
206+
// Simple check:
207+
if (!code.includes("import") || !code.match(/import\s+.*Component/)) {
208+
// Need to import Component.
209+
// Check if existing import from "modular-openscriptjs" exists
210+
if (code.includes("modular-openscriptjs")) {
211+
// Try to add to existing import? Hard with regex.
212+
// Easier to just add a new line.
213+
s.prepend(`import { Component } from "modular-openscriptjs";\n`);
214+
} else {
215+
s.prepend(`import { Component } from "modular-openscriptjs";\n`);
216+
}
217+
}
218+
}
219+
220+
if (hasChanged) {
111221
return {
112222
code: s.toString(),
113223
map: s.generateMap({ source: id, includeContent: true }),
114224
};
115225
}
116226
},
117-
118-
// HMR support
119-
handleHotUpdate({ file, server }) {
120-
if (file.includes(componentsDir)) {
121-
// Reload virtual module when components change
122-
const module = server.moduleGraph.getModuleById(
123-
resolvedVirtualModuleId
124-
);
125-
if (module) {
126-
server.moduleGraph.invalidateModule(module);
127-
return [module];
128-
}
129-
}
130-
},
131227
};
132228
}
133229

@@ -217,45 +313,3 @@ export {};
217313
fs.writeFileSync(dtsPath, content, "utf-8");
218314
console.log(`[OpenScript] Generated type definitions: ${dtsPath}`);
219315
}
220-
221-
/**
222-
* Generate virtual module content that imports and registers all components
223-
*/
224-
function generateVirtualModule(root, componentsDir, components, autoRegister) {
225-
const imports = components
226-
.map((c) => {
227-
const absolutePath = normalizePath(
228-
path.resolve(root, componentsDir, c.path)
229-
);
230-
return `import ${c.name} from '${absolutePath}';`;
231-
})
232-
.join("\n");
233-
234-
const exports = components.map((c) => c.name).join(", ");
235-
236-
const registration = autoRegister
237-
? `
238-
// Auto-register all components
239-
const components = { ${exports} };
240-
241-
export async function registerAllComponents() {
242-
for (const [name, Component] of Object.entries(components)) {
243-
const instance = new Component();
244-
await instance.mount();
245-
}
246-
}
247-
`
248-
: "";
249-
250-
return `// Auto-generated by OpenScript Component Plugin
251-
${imports}
252-
253-
${registration}
254-
255-
// Export all components for manual use
256-
export { ${exports} };
257-
258-
// Export component registry
259-
export default { ${exports} };
260-
`;
261-
}

0 commit comments

Comments
 (0)