Skip to content

Commit a267273

Browse files
committed
Finalize and Integrate AST-based Build Process #7151
1 parent 144924d commit a267273

4 files changed

Lines changed: 97 additions & 251 deletions

File tree

.github/epic-string-based-templates.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,3 +250,29 @@ Pre-processing templates at build time is a best practice in modern web developm
250250
- The final minified output contains JSON VDOM, not template literals.
251251
- The client-side `HtmlTemplateProcessor` is no longer required for production builds using this feature.
252252
- The logic is cleanly separated, with no build-time code included in client-side bundles.
253+
---
254+
255+
## 18. Sub-Ticket: Finalize and Integrate AST-based Build Process
256+
257+
**Status:** Done
258+
259+
### 1. Summary
260+
261+
This ticket covers the final integration of the robust, AST-based template processing into the main `build-es-modules` script, and the subsequent cleanup of temporary development scripts.
262+
263+
### 2. Rationale
264+
265+
After proving the AST-based approach in a dedicated script (`buildSingleFile.mjs`), it was necessary to merge this superior logic into the primary build script (`buildESModules.mjs`) that processes the entire project. This ensures that all files benefit from the robust template conversion. Consolidating the logic also simplifies the build toolchain.
266+
267+
### 3. Scope & Implementation Plan
268+
269+
1. **Integrate Logic:** The `minifyFile` function from `buildSingleFile.mjs`, containing the full AST parsing, transformation, and code generation logic, was moved into `buildESModules.mjs`, replacing the older, less robust implementation.
270+
2. **Cleanup:** The temporary `buildSingleFile.mjs` script was deleted from the repository.
271+
3. **Rename Script:** For improved clarity and consistency, the `build-es-modules` npm script in `package.json` was renamed to `build-dist-esm`.
272+
273+
### 4. Definition of Done
274+
275+
- The `buildESModules.mjs` script now uses the AST-based approach for all files.
276+
- The temporary `buildSingleFile.mjs` script has been removed.
277+
- The corresponding npm script has been renamed to `build-dist-esm`.
278+
- The full build process runs successfully, correctly transforming all `html` templates across the project.

buildScripts/buildESModules.mjs

Lines changed: 70 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import * as acorn from 'acorn';
55
import * as Terser from 'terser';
66
import {minifyHtml} from './util/minifyHtml.mjs';
77
import { processHtmlTemplateLiteral } from './util/templateBuildProcessor.mjs';
8-
import { vdomToString } from './util/vdomToString.mjs';
98

109
// A simple JSON to AST converter
1110
function jsonToAst(json) {
@@ -59,7 +58,7 @@ function jsonToAst(json) {
5958

6059
const
6160
outputBasePath = 'dist/esm/',
62-
regexImport = /(import(?:\s*(?:[\w*{}\n\r\t, ]+from\s*)?|\s*\(\s*)?)(["'`])((?:(?!\2).)*node_modules(?:(?!\2).)*)\2/g,
61+
regexImport = /(import(?:\s*(?:[\w*{}\n\r\t, ]+from\s*)?|\s*\(\s*)?)(["\'`])((?:(?!\2).)*node_modules(?:(?!\2).)*)\2/g,
6362
root = path.resolve(),
6463
requireJson = path => JSON.parse(fs.readFileSync(path, 'utf-8')),
6564
packageJson = requireJson(path.join(root, 'package.json')),
@@ -122,14 +121,15 @@ async function minifyDirectory(inputDir, outputDir) {
122121

123122
async function minifyFile(content, outputPath) {
124123
fs.mkdirSync(path.dirname(outputPath), {recursive: true});
124+
125125
try {
126126
if (outputPath.endsWith('.json')) {
127127
const jsonContent = JSON.parse(content);
128128
if (outputPath.endsWith('neo-config.json')) {
129129
Object.assign(jsonContent, {
130-
basePath : '../../' + jsonContent.basePath,
131-
environment : 'dist/esm',
132-
mainPath : './Main.mjs',
130+
basePath: '../../' + jsonContent.basePath,
131+
environment: 'dist/esm',
132+
mainPath: './Main.mjs',
133133
workerBasePath: jsonContent.basePath + 'src/worker/'
134134
});
135135
if (!insideNeo) {
@@ -148,55 +148,100 @@ async function minifyFile(content, outputPath) {
148148
// AST-based processing for html templates
149149
const ast = acorn.parse(adjustedContent, {ecmaVersion: 'latest', sourceType: 'module'});
150150

151-
// Simple post-order traversal
151+
function addParentLinks(node, parent) {
152+
if (!node || typeof node !== 'object') return;
153+
node.parent = parent;
154+
for (const key in node) {
155+
if (key === 'parent') continue;
156+
const child = node[key];
157+
if (Array.isArray(child)) {
158+
child.forEach(c => addParentLinks(c, node));
159+
} else {
160+
addParentLinks(child, node);
161+
}
162+
}
163+
}
164+
addParentLinks(ast, null);
165+
152166
const nodesToProcess = [];
153-
function walk(node, parent, key, index) {
167+
function walk(node) {
154168
if (!node) return;
169+
nodesToProcess.push(node);
155170
Object.entries(node).forEach(([key, value]) => {
171+
if (key === 'parent') return;
156172
if (Array.isArray(value)) {
157-
value.forEach((child, i) => walk(child, node, key, i));
173+
value.forEach(child => walk(child));
158174
} else if (typeof value === 'object' && value !== null) {
159-
walk(value, node, key);
175+
walk(value);
160176
}
161177
});
162-
nodesToProcess.push({node, parent, key, index});
163178
}
164179
walk(ast);
165180

166181
let hasChanges = false;
167-
for (const {node, parent, key, index} of nodesToProcess) {
182+
const templatePromises = [];
183+
184+
for (const node of nodesToProcess) {
168185
if (node.type === 'TaggedTemplateExpression' && node.tag.type === 'Identifier' && node.tag.name === 'html') {
186+
hasChanges = true;
187+
188+
let current = node;
189+
while (current.parent) {
190+
// Handle class methods: class MyClass { render() { ... } }
191+
if (current.parent.type === 'MethodDefinition' && current.parent.key.name === 'render') {
192+
current.parent.key.name = 'createVdom';
193+
console.log(`Renamed render() to createVdom() in ${outputPath}`);
194+
break;
195+
}
196+
// Handle object properties: { render: function() { ... } } or { render() { ... } }
197+
if (current.parent.type === 'Property' && current.parent.key.name === 'render') {
198+
current.parent.key.name = 'createVdom';
199+
console.log(`Renamed render property to createVdom() in ${outputPath}`);
200+
break;
201+
}
202+
current = current.parent;
203+
}
204+
169205
const templateLiteral = node.quasi;
170206
const strings = templateLiteral.quasis.map(q => q.value.cooked);
171207
const expressionCodeStrings = templateLiteral.expressions.map(expr => adjustedContent.substring(expr.start, expr.end));
172-
208+
173209
const componentNameMap = {};
174210
expressionCodeStrings.forEach(exprCode => {
175211
if (/^[A-Z][a-zA-Z0-9]*$/.test(exprCode.trim())) {
176-
componentNameMap[exprCode.trim()] = { __neo_component_name__: exprCode.trim() };
212+
componentNameMap[exprCode.trim()] = {__neo_component_name__: exprCode.trim()};
177213
}
178214
});
179215

180-
const vdom = await processHtmlTemplateLiteral(strings, expressionCodeStrings, componentNameMap);
181-
const vdomAst = jsonToAst(vdom);
216+
const promise = processHtmlTemplateLiteral(strings, expressionCodeStrings, componentNameMap)
217+
.then(vdom => {
218+
const vdomAst = jsonToAst(vdom);
219+
for (const key in node.parent) {
220+
if (node.parent[key] === node) {
221+
node.parent[key] = vdomAst;
222+
break;
223+
} else if (Array.isArray(node.parent[key])) {
224+
const index = node.parent[key].indexOf(node);
225+
if (index > -1) {
226+
node.parent[key][index] = vdomAst;
227+
break;
228+
}
229+
}
230+
}
231+
});
182232

183-
if (parent) {
184-
if (index !== undefined) {
185-
parent[key][index] = vdomAst;
186-
} else {
187-
parent[key] = vdomAst;
188-
}
189-
hasChanges = true;
190-
}
233+
templatePromises.push(promise);
191234
}
192235
}
193236

237+
await Promise.all(templatePromises);
238+
194239
let currentContent = hasChanges ? generate(ast) : adjustedContent;
195240

196241
const result = await Terser.minify(currentContent, {
197242
module: true,
198-
compress: { dead_code: true },
199-
mangle: { toplevel: true }
243+
compress: {dead_code: true},
244+
mangle: {toplevel: true}
200245
});
201246

202247
fs.writeFileSync(outputPath, result.code);

0 commit comments

Comments
 (0)