@makano/rew 1.2.53 → 1.2.55

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,498 @@
1
+
2
+ const emitter = require("../functions/emitter");
3
+ const { compile } = require("../modules/compiler");
4
+ const { wait } = require("../functions/wait");
5
+ const { generateRandomID } = require("../functions/id");
6
+
7
+
8
+ const selfClosingElements = new Set([
9
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'source', 'track', 'wbr'
10
+ ]);
11
+
12
+ class Node { }
13
+
14
+ class State {
15
+ _target = emitter();
16
+ id = generateRandomID();
17
+ constructor(value) {
18
+ this._value = value;
19
+ }
20
+ get value() {
21
+ return this._value;
22
+ }
23
+ set value(value) {
24
+ const oldValue = this._value;
25
+ this._value = value;
26
+ this._target.emit('change', { old: oldValue, new: this._value })
27
+ }
28
+
29
+ set(value){
30
+ this.value = value;
31
+ return this;
32
+ }
33
+
34
+ subscribe(renderCallback) {
35
+ this._target.on('change', renderCallback);
36
+ }
37
+ }
38
+
39
+ const nodeType = (name) => class extends Node {
40
+ [name] = "";
41
+ constructor(value) {
42
+ super();
43
+ this[name] = value;
44
+ }
45
+ }
46
+
47
+ const node = (typeNode) => class extends Node {
48
+ constructor(value) {
49
+ super();
50
+ if (!value.type instanceof typeNode) throw new TypeError('Node does not match it\'s type');
51
+ for (let i in value) this[i] = value[i];
52
+
53
+ this.props.children.forEach(child => parentify(child, this));
54
+ }
55
+ parent = null;
56
+ _target = emitter();
57
+ find(prop, value = null) {
58
+ return findChild(this, prop, value, true);
59
+ }
60
+ add(...children) {
61
+ for (let child of children) addChildren(this, child);
62
+ return this;
63
+ }
64
+ remove() {
65
+ this.parent?.children.splice(this.parent.children.indexOf(this), 1);
66
+ return this;
67
+ }
68
+ setProp(key, val) {
69
+ if (key == 'children') return;
70
+ this.props[key] = val;
71
+ return this;
72
+ }
73
+ render() {
74
+ renderToString(this, true);
75
+ this._target.emit('render', () => { });
76
+ return this;
77
+ }
78
+ }
79
+
80
+
81
+ class TextTypeNode extends nodeType('text') { };
82
+ class TextNode extends node(TextTypeNode) { };
83
+ function createTextNode(text) {
84
+ let t = text instanceof State ? text.value : text;
85
+ const node = new TextNode({
86
+ type: new TextTypeNode(t),
87
+ props: {
88
+ children: []
89
+ }
90
+ });
91
+ if (text instanceof State) {
92
+ node.props.children.push(text);
93
+ text.subscribe(() => node.parent?.render());
94
+ node.states = {
95
+ ':text': text
96
+ };
97
+ }
98
+ return node;
99
+ }
100
+
101
+ class ElementTypeNode extends nodeType('element') { };
102
+ class ElementNode extends node(ElementTypeNode) { };
103
+ function createElement(type, props, ...children) {
104
+ const flattenChildren = (childrenArray) =>
105
+ childrenArray.flatMap(child => Array.isArray(child) ? flattenChildren(child) : child).map(child => {
106
+ if (typeof child !== 'object') return createTextNode(child.toString());
107
+
108
+ if (child instanceof State) {
109
+ const textNode = createTextNode(child);
110
+ child.subscribe(() => {
111
+ textNode.parent?.render();
112
+ });
113
+ return textNode;
114
+ }
115
+
116
+ return child;
117
+ });
118
+
119
+ if (type instanceof State) {
120
+ return createTextNode(child);
121
+ }
122
+
123
+ if (typeof type === 'function') {
124
+ return type({ ...props, children: flattenChildren(children) })
125
+ }
126
+
127
+ const newChildren = flattenChildren(children);
128
+
129
+
130
+ const resolvedProps = {};
131
+ const attributeStates = {};
132
+ for (const key in props) {
133
+ if (props[key] instanceof State) {
134
+ resolvedProps[key] = props[key].value;
135
+ props[key].subscribe(() => {
136
+ if (props[key].value !== resolvedProps[key]) {
137
+ resolvedProps[key] = props[key].value;
138
+ if (elementInstance) elementInstance.render();
139
+ }
140
+ });
141
+ attributeStates[key] = props[key];
142
+ } else {
143
+ resolvedProps[key] = props[key];
144
+ }
145
+ }
146
+
147
+
148
+ return new ElementNode({
149
+ type: new ElementTypeNode(type),
150
+ props: {
151
+ ...resolvedProps,
152
+ children: newChildren
153
+ },
154
+ states: attributeStates
155
+ });
156
+ }
157
+
158
+ function parentify(child, parent) {
159
+ child.parent = parent;
160
+ }
161
+
162
+ function addChildren(nest, child) {
163
+ nest.props.children.push(child);
164
+ parentify(child, nest);
165
+ }
166
+
167
+ function findChild(nest, prop, value, recurse = false) {
168
+ let child = nest.props.children.find(element => element instanceof State ? false : value ? element.props[prop] == value : element.type.element == prop);
169
+ if (recurse && !child) return nest.props.children.find(element => element instanceof State ? false : findChild(element, prop, value, recurse));
170
+ return child;
171
+ }
172
+
173
+ function cloneNest(node) {
174
+ const clonedNode = new ElementNode({
175
+ type: node.type,
176
+ props: { ...node.props },
177
+ });
178
+
179
+ clonedNode.props.children = node.props.children.map(child => {
180
+ if (child instanceof ElementNode) {
181
+ return cloneNest(child);
182
+ } else if (child instanceof TextNode) {
183
+ return new TextNode({ type: child.type, props: { ...child.props } });
184
+ } else {
185
+ return child;
186
+ }
187
+ });
188
+
189
+ return clonedNode;
190
+ }
191
+
192
+ function assessKey(key){
193
+ if(key.startsWith('on')) return key.toLowerCase();
194
+ return key;
195
+ }
196
+
197
+ function assessValue(value, key){
198
+ if(key.startsWith('on')) return value = `(${value})()`;
199
+ return value;
200
+ }
201
+
202
+ function renderToString(element, js = false) {
203
+ if (typeof element === 'string') {
204
+ return element
205
+ }
206
+
207
+ const { type, props = {} } = element
208
+
209
+ if (typeof type === 'function') {
210
+ return renderToString(type(props))
211
+ }
212
+
213
+ const children = element instanceof TextNode ? [] : props?.children || []
214
+ const childrenParsed = Array.isArray(children) ? children.map((c) => renderToString(c, js ? 'raw' : false)) : renderToString(children, js ? 'raw' : false)
215
+ const childrenHTML = Array.isArray(childrenParsed) ? childrenParsed.join('') : childrenParsed;
216
+
217
+ const propsString = Object.entries(props)
218
+ .filter(([key]) => key !== 'children')
219
+ .map(([key, value]) => ` ${assessKey(key)}="${assessValue(value, key)}"`)
220
+ .join('')
221
+
222
+ const eltJSON = {
223
+ ...element,
224
+ nodeType: element instanceof TextNode ? 'text' : 'element',
225
+ props: {
226
+ ...props,
227
+ children: childrenParsed,
228
+ },
229
+ states: element.states || {}
230
+ };
231
+ for(let i in eltJSON.props) {
232
+ if(typeof eltJSON.props[i] == "function"){
233
+ eltJSON.props[assessKey(i)] = assessValue(eltJSON.props[i].toString(), i);
234
+ }
235
+ }
236
+ delete eltJSON.parent;
237
+ delete eltJSON._target;
238
+
239
+ return js ? js === 'raw' ? eltJSON : JSON.stringify(eltJSON) : element instanceof TextNode ? `${type.text}` : `<${type.element}${propsString}>${selfClosingElements.has(type.element) ? '' : childrenHTML}${selfClosingElements.has(type.element) ? '' : `</${type.element}>`}`;
240
+ }
241
+
242
+ class Page extends Node {
243
+ constructor() {
244
+ super();
245
+ }
246
+ /** @type{ElementNode} */
247
+ root = null;
248
+ /** @type{ElementNode} */
249
+ head = null;
250
+ /** @type{ElementNode} */
251
+ body = null;
252
+ find(key, value = null) {
253
+ return this.root.find(key, value);
254
+ }
255
+ add(...children) {
256
+ return this.body.add(...children);
257
+ }
258
+ script(scriptString) {
259
+ if (typeof scriptString == "object" && scriptString.src)
260
+ return this.add(createElement('script', { src: scriptString.src }));
261
+ else
262
+ return this.add(createElement('script', null, createTextNode(scriptString)));
263
+ }
264
+ style(styleString) {
265
+ if (typeof styleString == "object" && styleString.href)
266
+ return this.head.add(createElement('link', { href: styleString.href, rel: 'stylesheet' }));
267
+ else
268
+ return this.head.add(createElement('style', null, createTextNode(styleString)));
269
+ }
270
+ link(rel, href){
271
+ return this.head.add(createElement('link', { href, rel }));
272
+ }
273
+ serializeState() {
274
+ const states = {};
275
+ function extractStates(node) {
276
+ if (node instanceof ElementNode) {
277
+ if (node.props && node.props.children) {
278
+ node.props.children.forEach(child => extractStates(child));
279
+ }
280
+ }
281
+ if(node.states){
282
+ for(let i in node.states) states[node.states[i].id] = node.states[i];
283
+ }
284
+ }
285
+ extractStates(this.root);
286
+ this.initialState = states;
287
+ return JSON.stringify(states);
288
+ }
289
+ render(staticRender = false) {
290
+ return staticRender ? renderToString(this.root) : `<script>
291
+ const __INITIAL_STATE__ = ${this.serializeState()};
292
+ const DOMObject = ${renderToString(this.root, true)};
293
+ const generateRandomID = ${generateRandomID}
294
+ const emitter = ${emitter}
295
+ ${State}
296
+ const states = [];
297
+
298
+ function setAttribute(el, key, value){
299
+ let defVal = value;
300
+ if(key == 'style'){
301
+ for(let i in value){
302
+ const v = value[i];
303
+ el.style.setProperty(i, value[i]);
304
+ }
305
+ return;
306
+ }
307
+ el.setAttribute(key, defVal);
308
+ }
309
+
310
+ function rehydrate() {
311
+ const initialState = __INITIAL_STATE__;
312
+
313
+ function updateDOM(node, state) {
314
+ const elt = node.DOMELEMENT ? node.DOMELEMENT : node.nodeType == 'text' ? document.createTextNode(node.type.text) : document.createElement(node.type.element);
315
+ node.DOMELEMENT = elt;
316
+
317
+ if (node.nodeType == 'text') {
318
+ if(node.states?.[':text']){
319
+ const state = node.states[':text'];
320
+ if(state) elt.textContent = getState(state.id)?.value || state._value;
321
+ }
322
+ } else if (node.nodeType !== 'text' && node.props.children) {
323
+ const nodeState = node.states || {};
324
+ node.props.children.forEach(child => {
325
+ child.parent = node;
326
+ updateDOM(child, state);
327
+ });
328
+ Object.keys(node.props).forEach(key => {
329
+ if (key !== 'children') {
330
+ if(key in nodeState){
331
+ setAttribute(elt, key, getState(nodeState[key].id)?.value ?? nodeState[key]._value);
332
+ } else setAttribute(elt, key, node.props[key]);
333
+ }
334
+ });
335
+ if('data-only-if' in node.props){
336
+ if(elt.getAttribute('data-only-if') == 'true'){
337
+ elt.hidden = false;
338
+ } else {
339
+ elt.hidden = true;
340
+ }
341
+ }
342
+ }
343
+ if(node.parent && !elt.parentNode){
344
+ node.parent.DOMELEMENT.appendChild(elt);
345
+ }
346
+ return node;
347
+ }
348
+
349
+ function createState(inState, val, key){
350
+ const state = new State(inState._value);
351
+ state.id = inState.id;
352
+ states.push(state);
353
+ state.subscribe(() => updateDOM(DOMObject, { [key]: Array.isArray(val) ? [...val.filter(i => i.id !== state.id), state] : state }));
354
+ }
355
+
356
+ Object.keys(initialState).forEach(key => {
357
+ if(Array.isArray(initialState[key])) initialState[key].forEach((i) => createState(i, initialState[key], key));
358
+ else createState(initialState[key], initialState[key], key);
359
+ });
360
+
361
+ document.body.parentNode.remove();
362
+ document.appendChild(updateDOM(DOMObject, initialState).DOMELEMENT);
363
+ }
364
+ window.getState = (id) => states.find(s => s.id == id);
365
+ if (document.readyState === 'loading') {
366
+ document.addEventListener('DOMContentLoaded', rehydrate);
367
+ } else {
368
+ rehydrate();
369
+ }
370
+ </script>`;
371
+ }
372
+
373
+ toString() {
374
+ return this.render();
375
+ }
376
+
377
+ clone(){
378
+ const page = new Page();
379
+ page.root = cloneNest(this.root);
380
+ page.body = page.root.find('body');
381
+ page.head = page.root.find('head');
382
+ page.body.parent = page.root;
383
+ page.head.parent = page.root;
384
+ return page;
385
+ }
386
+ }
387
+ function createPage(options) {
388
+ const page = new Page;
389
+ const root = createElement('html');
390
+ page.root = root;
391
+
392
+ const head = createElement('head');
393
+ page.head = head;
394
+
395
+ if (options.viewportMeta) {
396
+ head
397
+ .add(createElement('meta', { charset: 'UTF-8' }))
398
+ .add(createElement('meta', { name: 'viewport', content: typeof options.viewportMeta == 'string' ? options.basicMeta : 'width=device-width, initial-scale=1.0' }));
399
+ }
400
+
401
+ const title = createElement('title', null, 'Document');
402
+
403
+ if (options.title) title.props.children = [createTextNode(options.title)];
404
+
405
+ if (options.title !== false) {
406
+ head.add(title);
407
+ page.title = title;
408
+ }
409
+
410
+ const body = createElement('body');
411
+ page.body = body;
412
+
413
+ root.add(head);
414
+ root.add(body);
415
+
416
+ return page;
417
+ }
418
+
419
+ module.exports = (context, importOptions) => {
420
+
421
+ const { build } = wait(async () => await import('vite'));
422
+
423
+ class Web {
424
+ create(options) {
425
+ return createPage(options);
426
+ }
427
+ isNode(node) {
428
+ return node instanceof Node;
429
+ }
430
+ isTextNode(node) {
431
+ return node instanceof TextNode;
432
+ }
433
+ isElementNode(node) {
434
+ return node instanceof ElementNode;
435
+ }
436
+ createText(text) {
437
+ return createTextNode(text);
438
+ }
439
+ createElement(...args) {
440
+ return createElement(...args);
441
+ }
442
+ state(value) {
443
+ return new State(value);
444
+ }
445
+ useState(states, callback){
446
+ const statesMapped = states.map(i => `getState('${i.id}')`);
447
+ return `((${callback})(...[${statesMapped}]))`;
448
+ }
449
+ async bundle(filepath, options = {}) {
450
+ const virtualModuleId = `virtual:${filepath}`;
451
+ const result = await build({
452
+ build: {
453
+ rollupOptions: {
454
+ input: options.code ? virtualModuleId : filepath,
455
+ output: {
456
+ format: 'iife', // Immediately Invoked Function Expression for the browser
457
+ entryFileNames: '[name].js',
458
+ },
459
+ },
460
+ write: false, // Do not write to file system, get the output as string
461
+ },
462
+ logLevel: 'silent',
463
+ plugins: [
464
+ (function rew() {
465
+ return {
466
+ name: 'rew',
467
+ resolveId(id) {
468
+ if (options.code && id === virtualModuleId) {
469
+ return virtualModuleId;
470
+ }
471
+ return null;
472
+ },
473
+ load(id) {
474
+ if (options.code && id === virtualModuleId) {
475
+ return options.code;
476
+ }
477
+ return null;
478
+ },
479
+ transform(code, id) {
480
+ if (id.endsWith('.coffee')) {
481
+ const result = compile({ content: code, path: filepath }, { jsx: true, keepImports: true });
482
+ return {
483
+ code: result,
484
+ map: null,
485
+ };
486
+ }
487
+ },
488
+ };
489
+ })(),
490
+ ...(options.plugins ?? [])
491
+ ],
492
+ });
493
+ return result.output[0].code;
494
+ }
495
+ }
496
+
497
+ return importOptions.instance ? new Web : Web;
498
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makano/rew",
3
- "version": "1.2.53",
3
+ "version": "1.2.55",
4
4
  "description": "A simple coffescript runtime and app manager",
5
5
  "main": "main.js",
6
6
  "directories": {
@@ -32,6 +32,7 @@
32
32
  "license": "ISC",
33
33
  "dependencies": {
34
34
  "@babel/core": "^7.24.6",
35
+ "@babel/preset-env": "^7.24.7",
35
36
  "@babel/preset-react": "^7.24.6",
36
37
  "@babel/preset-typescript": "^7.24.7",
37
38
  "axios": "^1.7.2",
@@ -39,15 +40,14 @@
39
40
  "chokidar": "^3.6.0",
40
41
  "colors": "^1.4.0",
41
42
  "deasync": "^0.1.30",
43
+ "itty-router": "^5.0.17",
42
44
  "js-yaml": "^4.1.0",
43
45
  "loading-cli": "^1.1.2",
44
46
  "tiny-msgpack": "^2.2.0",
45
47
  "uuid": "^9.0.1",
48
+ "vite": "^5.2.13",
46
49
  "vm": "^0.1.0",
47
50
  "yargs": "^17.7.2"
48
51
  },
49
- "devDependencies": {
50
- "pkg": "^5.8.1",
51
- "vitepress": "^1.2.2"
52
- }
52
+ "devDependencies": {}
53
53
  }