@rettangoli/fe 0.0.7-rc8 → 0.0.8
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/package.json +2 -2
- package/src/cli/blank/blank.handlers.js +2 -2
- package/src/cli/blank/blank.store.js +3 -3
- package/src/cli/build.js +12 -7
- package/src/cli/watch.js +8 -2
- package/src/createComponent.js +31 -11
- package/src/parser.js +15 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rettangoli/fe",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "Frontend framework for building reactive web components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"esbuild": "^0.25.5",
|
|
31
31
|
"immer": "^10.1.1",
|
|
32
|
-
"jempl": "0.
|
|
32
|
+
"jempl": "0.3.2-rc2",
|
|
33
33
|
"js-yaml": "^4.1.0",
|
|
34
34
|
"rxjs": "^7.8.2",
|
|
35
35
|
"snabbdom": "^3.6.2",
|
package/src/cli/build.js
CHANGED
|
@@ -18,7 +18,7 @@ function capitalize(word) {
|
|
|
18
18
|
// Function to process view files - loads YAML and creates temporary JS file
|
|
19
19
|
export const writeViewFile = (view, category, component) => {
|
|
20
20
|
// const { category, component } = extractCategoryAndComponent(filePath);
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
const dir = `./.temp/${category}`;
|
|
23
23
|
if (!existsSync(dir)) {
|
|
24
24
|
mkdirSync(dir, { recursive: true });
|
|
@@ -30,12 +30,12 @@ export const writeViewFile = (view, category, component) => {
|
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
export const bundleFile = async (options) => {
|
|
33
|
-
const { outfile = "./vt/static/main.js" } = options;
|
|
33
|
+
const { outfile = "./vt/static/main.js", development = false } = options;
|
|
34
34
|
await esbuild.build({
|
|
35
35
|
entryPoints: ["./.temp/dynamicImport.js"],
|
|
36
36
|
bundle: true,
|
|
37
|
-
minify:
|
|
38
|
-
sourcemap:
|
|
37
|
+
minify: !development,
|
|
38
|
+
sourcemap: !!development,
|
|
39
39
|
outfile: outfile,
|
|
40
40
|
format: "esm",
|
|
41
41
|
loader: {
|
|
@@ -47,7 +47,7 @@ export const bundleFile = async (options) => {
|
|
|
47
47
|
const buildRettangoliFrontend = async (options) => {
|
|
48
48
|
console.log("running build with options", options);
|
|
49
49
|
|
|
50
|
-
const { dirs = ["./example"], outfile = "./vt/static/main.js", setup = "setup.js" } = options;
|
|
50
|
+
const { dirs = ["./example"], outfile = "./vt/static/main.js", setup = "setup.js", development = false } = options;
|
|
51
51
|
|
|
52
52
|
const allFiles = getAllFiles(dirs).filter((filePath) => {
|
|
53
53
|
return (
|
|
@@ -93,7 +93,12 @@ const buildRettangoliFrontend = async (options) => {
|
|
|
93
93
|
count++;
|
|
94
94
|
} else if (["view"].includes(fileType)) {
|
|
95
95
|
const view = loadYaml(readFileSync(filePath, "utf8"));
|
|
96
|
-
|
|
96
|
+
try {
|
|
97
|
+
view.template = parse(view.template);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error(`Error parsing template in file: ${filePath}`);
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
97
102
|
writeViewFile(view, category, component);
|
|
98
103
|
output += `import ${component}${capitalize(
|
|
99
104
|
fileType,
|
|
@@ -125,7 +130,7 @@ Object.keys(imports).forEach(category => {
|
|
|
125
130
|
|
|
126
131
|
writeFileSync("./.temp/dynamicImport.js", output);
|
|
127
132
|
|
|
128
|
-
await bundleFile({ outfile });
|
|
133
|
+
await bundleFile({ outfile, development });
|
|
129
134
|
|
|
130
135
|
console.log(`Build complete. Output file: ${outfile}`);
|
|
131
136
|
};
|
package/src/cli/watch.js
CHANGED
|
@@ -64,13 +64,19 @@ async function startViteServer(options) {
|
|
|
64
64
|
const startWatching = async (options) => {
|
|
65
65
|
const { dirs = ['src'], port = 3001 } = options;
|
|
66
66
|
|
|
67
|
+
// Set development mode for all builds in watch mode
|
|
68
|
+
const watchOptions = {
|
|
69
|
+
development: true,
|
|
70
|
+
...options
|
|
71
|
+
};
|
|
72
|
+
|
|
67
73
|
// Do initial build with all directories
|
|
68
74
|
console.log('Starting initial build...');
|
|
69
|
-
await buildRettangoliFrontend(
|
|
75
|
+
await buildRettangoliFrontend(watchOptions);
|
|
70
76
|
console.log('Initial build complete');
|
|
71
77
|
|
|
72
78
|
dirs.forEach(dir => {
|
|
73
|
-
setupWatcher(dir,
|
|
79
|
+
setupWatcher(dir, watchOptions);
|
|
74
80
|
});
|
|
75
81
|
|
|
76
82
|
startViteServer({ port, outfile: options.outfile });
|
package/src/createComponent.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { produce } from "immer";
|
|
2
2
|
import { parseView } from "./parser.js";
|
|
3
|
+
import { parseAndRender } from "jempl";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* covert this format of json into raw css strings
|
|
@@ -261,8 +262,10 @@ class BaseComponent extends HTMLElement {
|
|
|
261
262
|
}
|
|
262
263
|
|
|
263
264
|
get viewData() {
|
|
264
|
-
|
|
265
|
-
|
|
265
|
+
let data = {};
|
|
266
|
+
if (this.store.selectViewData) {
|
|
267
|
+
data = this.store.selectViewData();
|
|
268
|
+
}
|
|
266
269
|
return data;
|
|
267
270
|
}
|
|
268
271
|
|
|
@@ -295,7 +298,6 @@ class BaseComponent extends HTMLElement {
|
|
|
295
298
|
this.renderTarget = document.createElement("div");
|
|
296
299
|
this.renderTarget.style.cssText = "display: contents;";
|
|
297
300
|
this.shadow.appendChild(this.renderTarget);
|
|
298
|
-
this.transformedHandlers = {};
|
|
299
301
|
if (!this.renderTarget.parentNode) {
|
|
300
302
|
this.appendChild(this.renderTarget);
|
|
301
303
|
}
|
|
@@ -307,10 +309,24 @@ class BaseComponent extends HTMLElement {
|
|
|
307
309
|
dispatchEvent: this.dispatchEvent.bind(this),
|
|
308
310
|
};
|
|
309
311
|
|
|
312
|
+
|
|
313
|
+
this.transformedHandlers = {
|
|
314
|
+
handleCallStoreAction: (event, payload) => {
|
|
315
|
+
const { render, store } = deps;
|
|
316
|
+
const context = parseAndRender(payload, {
|
|
317
|
+
event: {
|
|
318
|
+
target: event.target,
|
|
319
|
+
detail: event.detail
|
|
320
|
+
}
|
|
321
|
+
})
|
|
322
|
+
store[payload.action](context);
|
|
323
|
+
render();
|
|
324
|
+
}
|
|
325
|
+
};
|
|
310
326
|
// TODO don't include onmount, subscriptions, etc in transformedHandlers
|
|
311
327
|
Object.keys(this.handlers || {}).forEach((key) => {
|
|
312
|
-
this.transformedHandlers[key] = (payload) => {
|
|
313
|
-
const result = this.handlers[key](
|
|
328
|
+
this.transformedHandlers[key] = (event, payload) => {
|
|
329
|
+
const result = this.handlers[key](deps, event, payload);
|
|
314
330
|
return result;
|
|
315
331
|
};
|
|
316
332
|
});
|
|
@@ -348,7 +364,10 @@ class BaseComponent extends HTMLElement {
|
|
|
348
364
|
if (oldValue !== newValue && this.render) {
|
|
349
365
|
// Call handleOnUpdate if it exists
|
|
350
366
|
if (this.handlers?.handleOnUpdate) {
|
|
351
|
-
const changes = {
|
|
367
|
+
const changes = {
|
|
368
|
+
oldAttrs: { [name]: oldValue },
|
|
369
|
+
newAttrs: { [name]: newValue }
|
|
370
|
+
};
|
|
352
371
|
const deps = {
|
|
353
372
|
...this.deps,
|
|
354
373
|
refIds: this.refIds,
|
|
@@ -421,17 +440,19 @@ class BaseComponent extends HTMLElement {
|
|
|
421
440
|
/**
|
|
422
441
|
* Binds store functions with actual framework data flow
|
|
423
442
|
* Makes state changes immutable with immer
|
|
424
|
-
* Passes props to selectors
|
|
443
|
+
* Passes props to selectors
|
|
425
444
|
* @param {*} store
|
|
426
445
|
* @param {*} props
|
|
427
446
|
* @returns
|
|
428
447
|
*/
|
|
429
448
|
const bindStore = (store, props, attrs) => {
|
|
430
|
-
const {
|
|
449
|
+
const { createInitialState, ...selectorsAndActions } = store;
|
|
431
450
|
const selectors = {};
|
|
432
451
|
const actions = {};
|
|
433
|
-
let currentState =
|
|
434
|
-
|
|
452
|
+
let currentState = {};
|
|
453
|
+
if (createInitialState) {
|
|
454
|
+
currentState = createInitialState();
|
|
455
|
+
}
|
|
435
456
|
Object.entries(selectorsAndActions).forEach(([key, fn]) => {
|
|
436
457
|
if (key.startsWith("select")) {
|
|
437
458
|
selectors[key] = (...args) => {
|
|
@@ -448,7 +469,6 @@ const bindStore = (store, props, attrs) => {
|
|
|
448
469
|
});
|
|
449
470
|
|
|
450
471
|
return {
|
|
451
|
-
toViewData: () => toViewData({ state: currentState, props, attrs }),
|
|
452
472
|
getState: () => currentState,
|
|
453
473
|
...actions,
|
|
454
474
|
...selectors,
|
package/src/parser.js
CHANGED
|
@@ -164,7 +164,7 @@ export const createVirtualDom = ({
|
|
|
164
164
|
const attrRegex = /(\S+?)=(?:\"([^\"]*)\"|\'([^\']*)\'|([^\s]+))/g;
|
|
165
165
|
let match;
|
|
166
166
|
const processedAttrs = new Set();
|
|
167
|
-
|
|
167
|
+
|
|
168
168
|
while ((match = attrRegex.exec(attrsString)) !== null) {
|
|
169
169
|
processedAttrs.add(match[1]);
|
|
170
170
|
if (match[1].startsWith(".")) {
|
|
@@ -175,7 +175,7 @@ export const createVirtualDom = ({
|
|
|
175
175
|
// Handle conditional boolean attributes
|
|
176
176
|
const attrName = match[1].substring(1);
|
|
177
177
|
const attrValue = match[2] || match[3] || match[4];
|
|
178
|
-
|
|
178
|
+
|
|
179
179
|
// Convert string values to boolean
|
|
180
180
|
let evalValue;
|
|
181
181
|
if (attrValue === "true") {
|
|
@@ -186,7 +186,7 @@ export const createVirtualDom = ({
|
|
|
186
186
|
// Try to get from viewData if it's not a literal boolean
|
|
187
187
|
evalValue = lodashGet(viewData, attrValue);
|
|
188
188
|
}
|
|
189
|
-
|
|
189
|
+
|
|
190
190
|
// Only add attribute if value is truthy
|
|
191
191
|
if (evalValue) {
|
|
192
192
|
attrs[attrName] = "";
|
|
@@ -195,7 +195,7 @@ export const createVirtualDom = ({
|
|
|
195
195
|
attrs[match[1]] = match[2] || match[3] || match[4];
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
|
-
|
|
198
|
+
|
|
199
199
|
// Then, handle boolean attributes without values
|
|
200
200
|
// Remove all processed attribute-value pairs from the string first
|
|
201
201
|
let remainingAttrsString = attrsString;
|
|
@@ -209,7 +209,7 @@ export const createVirtualDom = ({
|
|
|
209
209
|
processedMatches.forEach(match => {
|
|
210
210
|
remainingAttrsString = remainingAttrsString.replace(match, ' ');
|
|
211
211
|
});
|
|
212
|
-
|
|
212
|
+
|
|
213
213
|
const booleanAttrRegex = /\b(\S+?)(?=\s|$)/g;
|
|
214
214
|
let boolMatch;
|
|
215
215
|
while ((boolMatch = booleanAttrRegex.exec(remainingAttrsString)) !== null) {
|
|
@@ -327,9 +327,18 @@ export const createVirtualDom = ({
|
|
|
327
327
|
const eventListeners = refs[bestMatchRefKey].eventListeners;
|
|
328
328
|
Object.entries(eventListeners).forEach(
|
|
329
329
|
([eventType, eventConfig]) => {
|
|
330
|
+
if (eventConfig.handler && eventConfig.action) {
|
|
331
|
+
throw new Error('Each listener can have hanlder or action but not both')
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (eventConfig.action) {
|
|
335
|
+
handlers.handleCallStoreAction(event, eventConfig.payload)
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
330
339
|
if (eventConfig.handler && handlers[eventConfig.handler]) {
|
|
331
340
|
eventHandlers[eventType] = (event) => {
|
|
332
|
-
handlers[eventConfig.handler](event);
|
|
341
|
+
handlers[eventConfig.handler](event, eventConfig.payload);
|
|
333
342
|
};
|
|
334
343
|
} else if (eventConfig.handler) {
|
|
335
344
|
// Keep this warning for missing handlers
|