pulse-js-framework 1.4.10 → 1.5.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/cli/analyze.js +8 -7
- package/cli/build.js +14 -13
- package/cli/dev.js +28 -7
- package/cli/format.js +13 -12
- package/cli/index.js +20 -1
- package/cli/lint.js +8 -7
- package/cli/release.js +493 -0
- package/compiler/parser.js +41 -20
- package/compiler/transformer/constants.js +54 -0
- package/compiler/transformer/export.js +33 -0
- package/compiler/transformer/expressions.js +273 -0
- package/compiler/transformer/imports.js +101 -0
- package/compiler/transformer/index.js +319 -0
- package/compiler/transformer/router.js +95 -0
- package/compiler/transformer/state.js +118 -0
- package/compiler/transformer/store.js +97 -0
- package/compiler/transformer/style.js +130 -0
- package/compiler/transformer/view.js +428 -0
- package/compiler/transformer.js +17 -1310
- package/core/errors.js +300 -0
- package/package.json +7 -2
- package/runtime/dom.js +61 -10
- package/runtime/lru-cache.js +145 -0
- package/runtime/native.js +6 -1
- package/runtime/pulse.js +46 -2
- package/runtime/router.js +4 -1
- package/runtime/store.js +35 -1
- package/runtime/utils.js +348 -0
- package/types/index.d.ts +19 -0
- package/types/lru-cache.d.ts +118 -0
- package/types/utils.d.ts +255 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse Transformer - Code generator
|
|
3
|
+
*
|
|
4
|
+
* Transforms AST into JavaScript code
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Import statement support
|
|
8
|
+
* - Slot-based component composition
|
|
9
|
+
* - CSS scoping with unique class prefixes
|
|
10
|
+
* - Source map generation
|
|
11
|
+
*
|
|
12
|
+
* @module pulse-js-framework/compiler/transformer
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { SourceMapGenerator } from '../sourcemap.js';
|
|
16
|
+
import { generateScopeId } from './constants.js';
|
|
17
|
+
import { extractImportedComponents, generateImports } from './imports.js';
|
|
18
|
+
import {
|
|
19
|
+
extractPropVars,
|
|
20
|
+
extractStateVars,
|
|
21
|
+
extractActionNames,
|
|
22
|
+
transformState,
|
|
23
|
+
transformActions,
|
|
24
|
+
transformValue
|
|
25
|
+
} from './state.js';
|
|
26
|
+
import { transformRouter } from './router.js';
|
|
27
|
+
import { transformStore } from './store.js';
|
|
28
|
+
import { transformExpression, transformExpressionString, transformFunctionBody } from './expressions.js';
|
|
29
|
+
import { transformView, transformViewNode, VIEW_NODE_HANDLERS, addScopeToSelector } from './view.js';
|
|
30
|
+
import { transformStyle, transformStyleRule, scopeStyleSelector } from './style.js';
|
|
31
|
+
import { generateExport } from './export.js';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Transformer class
|
|
35
|
+
*/
|
|
36
|
+
export class Transformer {
|
|
37
|
+
constructor(ast, options = {}) {
|
|
38
|
+
this.ast = ast;
|
|
39
|
+
// Default to source maps enabled in development mode
|
|
40
|
+
const isDev = typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production';
|
|
41
|
+
this.options = {
|
|
42
|
+
runtime: 'pulse-js-framework/runtime',
|
|
43
|
+
minify: false,
|
|
44
|
+
scopeStyles: true,
|
|
45
|
+
sourceMap: isDev, // Enable source map generation (default: true in dev)
|
|
46
|
+
sourceFileName: null, // Original .pulse file name
|
|
47
|
+
sourceContent: null, // Original source content (for inline source maps)
|
|
48
|
+
...options
|
|
49
|
+
};
|
|
50
|
+
this.stateVars = new Set();
|
|
51
|
+
this.propVars = new Set();
|
|
52
|
+
this.propDefaults = new Map();
|
|
53
|
+
this.actionNames = new Set();
|
|
54
|
+
this.importedComponents = new Map();
|
|
55
|
+
this.scopeId = this.options.scopeStyles ? generateScopeId() : null;
|
|
56
|
+
|
|
57
|
+
// Source map tracking
|
|
58
|
+
this.sourceMap = null;
|
|
59
|
+
this._currentLine = 0;
|
|
60
|
+
this._currentColumn = 0;
|
|
61
|
+
|
|
62
|
+
// Initialize source map generator if enabled
|
|
63
|
+
if (this.options.sourceMap) {
|
|
64
|
+
this.sourceMap = new SourceMapGenerator({
|
|
65
|
+
file: this.options.sourceFileName?.replace('.pulse', '.js') || 'output.js'
|
|
66
|
+
});
|
|
67
|
+
if (this.options.sourceFileName) {
|
|
68
|
+
this.sourceMap.addSource(
|
|
69
|
+
this.options.sourceFileName,
|
|
70
|
+
this.options.sourceContent
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Add a mapping to the source map
|
|
78
|
+
* @param {Object} original - Original position {line, column} (1-based)
|
|
79
|
+
* @param {string} name - Optional identifier name
|
|
80
|
+
*/
|
|
81
|
+
_addMapping(original, name = null) {
|
|
82
|
+
if (!this.sourceMap || !original) return;
|
|
83
|
+
|
|
84
|
+
this.sourceMap.addMapping({
|
|
85
|
+
generated: {
|
|
86
|
+
line: this._currentLine,
|
|
87
|
+
column: this._currentColumn
|
|
88
|
+
},
|
|
89
|
+
original: {
|
|
90
|
+
line: original.line - 1, // Convert to 0-based
|
|
91
|
+
column: original.column - 1
|
|
92
|
+
},
|
|
93
|
+
source: this.options.sourceFileName,
|
|
94
|
+
name
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Track output position when writing code
|
|
100
|
+
* @param {string} code - Generated code
|
|
101
|
+
* @returns {string} The same code (for chaining)
|
|
102
|
+
*/
|
|
103
|
+
_trackCode(code) {
|
|
104
|
+
for (const char of code) {
|
|
105
|
+
if (char === '\n') {
|
|
106
|
+
this._currentLine++;
|
|
107
|
+
this._currentColumn = 0;
|
|
108
|
+
} else {
|
|
109
|
+
this._currentColumn++;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return code;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Write code with optional source mapping
|
|
117
|
+
* @param {string} code - Code to write
|
|
118
|
+
* @param {Object} original - Original position {line, column}
|
|
119
|
+
* @param {string} name - Optional identifier name
|
|
120
|
+
* @returns {string} The code
|
|
121
|
+
*/
|
|
122
|
+
_emit(code, original = null, name = null) {
|
|
123
|
+
if (original) {
|
|
124
|
+
this._addMapping(original, name);
|
|
125
|
+
}
|
|
126
|
+
return this._trackCode(code);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Transform AST to JavaScript code
|
|
131
|
+
*/
|
|
132
|
+
transform() {
|
|
133
|
+
const parts = [];
|
|
134
|
+
|
|
135
|
+
// Extract imported components first
|
|
136
|
+
if (this.ast.imports) {
|
|
137
|
+
extractImportedComponents(this, this.ast.imports);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Imports (runtime + user imports)
|
|
141
|
+
parts.push(generateImports(this));
|
|
142
|
+
|
|
143
|
+
// Extract prop variables
|
|
144
|
+
if (this.ast.props) {
|
|
145
|
+
extractPropVars(this, this.ast.props);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Extract state variables
|
|
149
|
+
if (this.ast.state) {
|
|
150
|
+
extractStateVars(this, this.ast.state);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Extract action names
|
|
154
|
+
if (this.ast.actions) {
|
|
155
|
+
extractActionNames(this, this.ast.actions);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Store (must come before router so $store is available to guards)
|
|
159
|
+
if (this.ast.store) {
|
|
160
|
+
parts.push(transformStore(this, this.ast.store, transformValue));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Router (after store so guards can access $store)
|
|
164
|
+
if (this.ast.router) {
|
|
165
|
+
parts.push(transformRouter(this, this.ast.router));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// State
|
|
169
|
+
if (this.ast.state) {
|
|
170
|
+
parts.push(transformState(this, this.ast.state));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Actions
|
|
174
|
+
if (this.ast.actions) {
|
|
175
|
+
parts.push(transformActions(this, this.ast.actions, transformFunctionBody));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// View
|
|
179
|
+
if (this.ast.view) {
|
|
180
|
+
parts.push(transformView(this, this.ast.view));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Style
|
|
184
|
+
if (this.ast.style) {
|
|
185
|
+
parts.push(transformStyle(this, this.ast.style));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Component export
|
|
189
|
+
parts.push(generateExport(this));
|
|
190
|
+
|
|
191
|
+
const code = parts.filter(Boolean).join('\n\n');
|
|
192
|
+
|
|
193
|
+
// Track the generated code for source map positions
|
|
194
|
+
if (this.sourceMap) {
|
|
195
|
+
this._trackCode(code);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return code;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Transform AST and return result with optional source map
|
|
203
|
+
* @returns {Object} Result with code and optional sourceMap
|
|
204
|
+
*/
|
|
205
|
+
transformWithSourceMap() {
|
|
206
|
+
const code = this.transform();
|
|
207
|
+
|
|
208
|
+
if (!this.sourceMap) {
|
|
209
|
+
return { code, sourceMap: null };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
code,
|
|
214
|
+
sourceMap: this.sourceMap.toJSON(),
|
|
215
|
+
sourceMapComment: this.sourceMap.toComment()
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// =============================================================================
|
|
220
|
+
// Instance methods that delegate to module functions
|
|
221
|
+
// These are kept for backward compatibility
|
|
222
|
+
// =============================================================================
|
|
223
|
+
|
|
224
|
+
extractImportedComponents(imports) {
|
|
225
|
+
return extractImportedComponents(this, imports);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
generateImports() {
|
|
229
|
+
return generateImports(this);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
extractPropVars(propsBlock) {
|
|
233
|
+
return extractPropVars(this, propsBlock);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
extractStateVars(stateBlock) {
|
|
237
|
+
return extractStateVars(this, stateBlock);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
extractActionNames(actionsBlock) {
|
|
241
|
+
return extractActionNames(this, actionsBlock);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
transformState(stateBlock) {
|
|
245
|
+
return transformState(this, stateBlock);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
transformRouter(routerBlock) {
|
|
249
|
+
return transformRouter(this, routerBlock);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
transformStore(storeBlock) {
|
|
253
|
+
return transformStore(this, storeBlock, transformValue);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
transformValue(node) {
|
|
257
|
+
return transformValue(this, node);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
transformActions(actionsBlock) {
|
|
261
|
+
return transformActions(this, actionsBlock, transformFunctionBody);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
transformFunctionBody(tokens) {
|
|
265
|
+
return transformFunctionBody(this, tokens);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
transformView(viewBlock) {
|
|
269
|
+
return transformView(this, viewBlock);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
transformViewNode(node, indent = 0) {
|
|
273
|
+
return transformViewNode(this, node, indent);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
transformExpression(node) {
|
|
277
|
+
return transformExpression(this, node);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
transformExpressionString(exprStr) {
|
|
281
|
+
return transformExpressionString(this, exprStr);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
transformStyle(styleBlock) {
|
|
285
|
+
return transformStyle(this, styleBlock);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
transformStyleRule(rule, indent) {
|
|
289
|
+
return transformStyleRule(this, rule, indent);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
scopeStyleSelector(selector) {
|
|
293
|
+
return scopeStyleSelector(this, selector);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
addScopeToSelector(selector) {
|
|
297
|
+
return addScopeToSelector(this, selector);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
generateExport() {
|
|
301
|
+
return generateExport(this);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Static property for VIEW_NODE_HANDLERS (backward compatibility)
|
|
306
|
+
Transformer.VIEW_NODE_HANDLERS = VIEW_NODE_HANDLERS;
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Transform AST to JavaScript code
|
|
310
|
+
*/
|
|
311
|
+
export function transform(ast, options = {}) {
|
|
312
|
+
const transformer = new Transformer(ast, options);
|
|
313
|
+
return transformer.transform();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export default {
|
|
317
|
+
Transformer,
|
|
318
|
+
transform
|
|
319
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transformer Router Module
|
|
3
|
+
* Handles router block transformation
|
|
4
|
+
* @module pulse-js-framework/compiler/transformer/router
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { PUNCT_NO_SPACE_BEFORE, PUNCT_NO_SPACE_AFTER } from './constants.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Helper to emit token value with proper string/template handling
|
|
11
|
+
* @param {Object} token - Token to emit
|
|
12
|
+
* @returns {string} Token value
|
|
13
|
+
*/
|
|
14
|
+
export function emitToken(token) {
|
|
15
|
+
if (token.type === 'STRING') return token.raw || JSON.stringify(token.value);
|
|
16
|
+
if (token.type === 'TEMPLATE') return token.raw || ('`' + token.value + '`');
|
|
17
|
+
return token.value;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Helper to check if space needed between tokens
|
|
22
|
+
* @param {Object} token - Current token
|
|
23
|
+
* @param {Object} nextToken - Next token
|
|
24
|
+
* @returns {boolean} Whether space is needed
|
|
25
|
+
*/
|
|
26
|
+
export function needsSpace(token, nextToken) {
|
|
27
|
+
if (!nextToken) return false;
|
|
28
|
+
return !PUNCT_NO_SPACE_BEFORE.includes(nextToken.type) &&
|
|
29
|
+
!PUNCT_NO_SPACE_AFTER.includes(token.type);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Transform router guard body - handles store references
|
|
34
|
+
* @param {Array} tokens - Body tokens
|
|
35
|
+
* @returns {string} JavaScript code
|
|
36
|
+
*/
|
|
37
|
+
export function transformRouterGuardBody(tokens) {
|
|
38
|
+
let code = '';
|
|
39
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
40
|
+
const token = tokens[i];
|
|
41
|
+
if (token.value === 'store' && tokens[i + 1]?.type === 'DOT') {
|
|
42
|
+
code += '$store';
|
|
43
|
+
} else {
|
|
44
|
+
code += emitToken(token);
|
|
45
|
+
}
|
|
46
|
+
if (needsSpace(token, tokens[i + 1])) code += ' ';
|
|
47
|
+
}
|
|
48
|
+
return code.trim();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Transform router block to createRouter() call
|
|
53
|
+
* @param {Object} transformer - Transformer instance
|
|
54
|
+
* @param {Object} routerBlock - Router block from AST
|
|
55
|
+
* @returns {string} JavaScript code
|
|
56
|
+
*/
|
|
57
|
+
export function transformRouter(transformer, routerBlock) {
|
|
58
|
+
const lines = ['// Router'];
|
|
59
|
+
|
|
60
|
+
// Build routes object
|
|
61
|
+
const routesCode = [];
|
|
62
|
+
for (const route of routerBlock.routes) {
|
|
63
|
+
routesCode.push(` '${route.path}': ${route.handler}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
lines.push('const router = createRouter({');
|
|
67
|
+
lines.push(` mode: '${routerBlock.mode}',`);
|
|
68
|
+
if (routerBlock.base) {
|
|
69
|
+
lines.push(` base: '${routerBlock.base}',`);
|
|
70
|
+
}
|
|
71
|
+
lines.push(' routes: {');
|
|
72
|
+
lines.push(routesCode.join(',\n'));
|
|
73
|
+
lines.push(' }');
|
|
74
|
+
lines.push('});');
|
|
75
|
+
lines.push('');
|
|
76
|
+
|
|
77
|
+
// Add global guards
|
|
78
|
+
if (routerBlock.beforeEach) {
|
|
79
|
+
const params = routerBlock.beforeEach.params.join(', ');
|
|
80
|
+
const body = transformRouterGuardBody(routerBlock.beforeEach.body);
|
|
81
|
+
lines.push(`router.beforeEach((${params}) => { ${body} });`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (routerBlock.afterEach) {
|
|
85
|
+
const params = routerBlock.afterEach.params.join(', ');
|
|
86
|
+
const body = transformRouterGuardBody(routerBlock.afterEach.body);
|
|
87
|
+
lines.push(`router.afterEach((${params}) => { ${body} });`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
lines.push('');
|
|
91
|
+
lines.push('// Start router');
|
|
92
|
+
lines.push('router.start();');
|
|
93
|
+
|
|
94
|
+
return lines.join('\n');
|
|
95
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transformer State Module
|
|
3
|
+
* Handles state, props, and actions extraction and transformation
|
|
4
|
+
* @module pulse-js-framework/compiler/transformer/state
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { NodeType } from '../parser.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Extract prop variable names and defaults
|
|
11
|
+
* @param {Object} transformer - Transformer instance
|
|
12
|
+
* @param {Object} propsBlock - Props block from AST
|
|
13
|
+
*/
|
|
14
|
+
export function extractPropVars(transformer, propsBlock) {
|
|
15
|
+
for (const prop of propsBlock.properties) {
|
|
16
|
+
transformer.propVars.add(prop.name);
|
|
17
|
+
transformer.propDefaults.set(prop.name, prop.value);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Extract state variable names
|
|
23
|
+
* @param {Object} transformer - Transformer instance
|
|
24
|
+
* @param {Object} stateBlock - State block from AST
|
|
25
|
+
*/
|
|
26
|
+
export function extractStateVars(transformer, stateBlock) {
|
|
27
|
+
for (const prop of stateBlock.properties) {
|
|
28
|
+
transformer.stateVars.add(prop.name);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Extract action names
|
|
34
|
+
* @param {Object} transformer - Transformer instance
|
|
35
|
+
* @param {Object} actionsBlock - Actions block from AST
|
|
36
|
+
*/
|
|
37
|
+
export function extractActionNames(transformer, actionsBlock) {
|
|
38
|
+
for (const fn of actionsBlock.functions) {
|
|
39
|
+
transformer.actionNames.add(fn.name);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Transform a value node to JavaScript code
|
|
45
|
+
* @param {Object} transformer - Transformer instance
|
|
46
|
+
* @param {Object} node - AST node to transform
|
|
47
|
+
* @returns {string} JavaScript code
|
|
48
|
+
*/
|
|
49
|
+
export function transformValue(transformer, node) {
|
|
50
|
+
if (!node) return 'undefined';
|
|
51
|
+
|
|
52
|
+
switch (node.type) {
|
|
53
|
+
case NodeType.Literal:
|
|
54
|
+
if (typeof node.value === 'string') {
|
|
55
|
+
return JSON.stringify(node.value);
|
|
56
|
+
}
|
|
57
|
+
return String(node.value);
|
|
58
|
+
|
|
59
|
+
case NodeType.ObjectLiteral: {
|
|
60
|
+
const props = node.properties.map(p =>
|
|
61
|
+
`${p.name}: ${transformValue(transformer, p.value)}`
|
|
62
|
+
);
|
|
63
|
+
return `{ ${props.join(', ')} }`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
case NodeType.ArrayLiteral: {
|
|
67
|
+
const elements = node.elements.map(e => transformValue(transformer, e));
|
|
68
|
+
return `[${elements.join(', ')}]`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
case NodeType.Identifier:
|
|
72
|
+
return node.name;
|
|
73
|
+
|
|
74
|
+
default:
|
|
75
|
+
return 'undefined';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Transform state block to pulse declarations
|
|
81
|
+
* @param {Object} transformer - Transformer instance
|
|
82
|
+
* @param {Object} stateBlock - State block from AST
|
|
83
|
+
* @returns {string} JavaScript code
|
|
84
|
+
*/
|
|
85
|
+
export function transformState(transformer, stateBlock) {
|
|
86
|
+
const lines = ['// State'];
|
|
87
|
+
|
|
88
|
+
for (const prop of stateBlock.properties) {
|
|
89
|
+
const value = transformValue(transformer, prop.value);
|
|
90
|
+
lines.push(`const ${prop.name} = pulse(${value});`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return lines.join('\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Transform actions block to function declarations
|
|
98
|
+
* @param {Object} transformer - Transformer instance
|
|
99
|
+
* @param {Object} actionsBlock - Actions block from AST
|
|
100
|
+
* @param {Function} transformFunctionBody - Function to transform body tokens
|
|
101
|
+
* @returns {string} JavaScript code
|
|
102
|
+
*/
|
|
103
|
+
export function transformActions(transformer, actionsBlock, transformFunctionBody) {
|
|
104
|
+
const lines = ['// Actions'];
|
|
105
|
+
|
|
106
|
+
for (const fn of actionsBlock.functions) {
|
|
107
|
+
const asyncKeyword = fn.async ? 'async ' : '';
|
|
108
|
+
const params = fn.params.join(', ');
|
|
109
|
+
const body = transformFunctionBody(transformer, fn.body);
|
|
110
|
+
|
|
111
|
+
lines.push(`${asyncKeyword}function ${fn.name}(${params}) {`);
|
|
112
|
+
lines.push(` ${body}`);
|
|
113
|
+
lines.push('}');
|
|
114
|
+
lines.push('');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return lines.join('\n');
|
|
118
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transformer Store Module
|
|
3
|
+
* Handles store block transformation
|
|
4
|
+
* @module pulse-js-framework/compiler/transformer/store
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { emitToken, needsSpace } from './router.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Transform store action body (this.x = y -> store.x.set(y))
|
|
11
|
+
* @param {Array} tokens - Body tokens
|
|
12
|
+
* @returns {string} JavaScript code
|
|
13
|
+
*/
|
|
14
|
+
export function transformStoreActionBody(tokens) {
|
|
15
|
+
let code = '';
|
|
16
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
17
|
+
const token = tokens[i];
|
|
18
|
+
if (token.value === 'this') code += 'store';
|
|
19
|
+
else if (token.type === 'COLON') code += ' : ';
|
|
20
|
+
else code += emitToken(token);
|
|
21
|
+
if (needsSpace(token, tokens[i + 1])) code += ' ';
|
|
22
|
+
}
|
|
23
|
+
return code.replace(/store\.(\w+)\s*=\s*([^;]+)/g, 'store.$1.set($2)').trim();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Transform store getter body (this.x -> store.x.get())
|
|
28
|
+
* @param {Array} tokens - Body tokens
|
|
29
|
+
* @returns {string} JavaScript code
|
|
30
|
+
*/
|
|
31
|
+
export function transformStoreGetterBody(tokens) {
|
|
32
|
+
return transformStoreActionBody(tokens)
|
|
33
|
+
.replace(/store\.(\w+)(?!\.(?:get|set)\()/g, 'store.$1.get()');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Transform store block to createStore(), createActions(), createGetters() calls
|
|
38
|
+
* @param {Object} transformer - Transformer instance
|
|
39
|
+
* @param {Object} storeBlock - Store block from AST
|
|
40
|
+
* @param {Function} transformValue - Function to transform values
|
|
41
|
+
* @returns {string} JavaScript code
|
|
42
|
+
*/
|
|
43
|
+
export function transformStore(transformer, storeBlock, transformValue) {
|
|
44
|
+
const lines = ['// Store'];
|
|
45
|
+
|
|
46
|
+
// Transform state
|
|
47
|
+
if (storeBlock.state) {
|
|
48
|
+
const stateProps = storeBlock.state.properties.map(p =>
|
|
49
|
+
` ${p.name}: ${transformValue(transformer, p.value)}`
|
|
50
|
+
).join(',\n');
|
|
51
|
+
|
|
52
|
+
lines.push('const store = createStore({');
|
|
53
|
+
lines.push(stateProps);
|
|
54
|
+
lines.push('}, {');
|
|
55
|
+
lines.push(` persist: ${storeBlock.persist},`);
|
|
56
|
+
lines.push(` storageKey: '${storeBlock.storageKey}'`);
|
|
57
|
+
lines.push('});');
|
|
58
|
+
lines.push('');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Transform actions
|
|
62
|
+
if (storeBlock.actions) {
|
|
63
|
+
lines.push('const storeActions = createActions(store, {');
|
|
64
|
+
for (const fn of storeBlock.actions.functions) {
|
|
65
|
+
const params = fn.params.length > 0 ? ', ' + fn.params.join(', ') : '';
|
|
66
|
+
const body = transformStoreActionBody(fn.body);
|
|
67
|
+
lines.push(` ${fn.name}: (store${params}) => { ${body} },`);
|
|
68
|
+
}
|
|
69
|
+
lines.push('});');
|
|
70
|
+
lines.push('');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Transform getters
|
|
74
|
+
if (storeBlock.getters) {
|
|
75
|
+
lines.push('const storeGetters = createGetters(store, {');
|
|
76
|
+
for (const getter of storeBlock.getters.getters) {
|
|
77
|
+
const body = transformStoreGetterBody(getter.body);
|
|
78
|
+
lines.push(` ${getter.name}: (store) => { ${body} },`);
|
|
79
|
+
}
|
|
80
|
+
lines.push('});');
|
|
81
|
+
lines.push('');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Create combined $store object for easy access
|
|
85
|
+
lines.push('// Combined store with actions and getters');
|
|
86
|
+
lines.push('const $store = {');
|
|
87
|
+
lines.push(' ...store,');
|
|
88
|
+
if (storeBlock.actions) {
|
|
89
|
+
lines.push(' ...storeActions,');
|
|
90
|
+
}
|
|
91
|
+
if (storeBlock.getters) {
|
|
92
|
+
lines.push(' ...storeGetters,');
|
|
93
|
+
}
|
|
94
|
+
lines.push('};');
|
|
95
|
+
|
|
96
|
+
return lines.join('\n');
|
|
97
|
+
}
|