@makano/rew 1.2.52 → 1.2.54

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,476 @@
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 => value ? element.props[prop] == value : element.type.element == prop);
169
+ if (recurse && !child) return nest.props.children.find(element => 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 = 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.scr)
260
+ return this.add(createElement('script', { src: scriptString.src }));
261
+ else
262
+ return this.add(createElement('script', null, createTextNode(scriptString)));
263
+ }
264
+ serializeState() {
265
+ const states = {};
266
+ function extractStates(node) {
267
+ if (node instanceof ElementNode) {
268
+ if (node.props && node.props.children) {
269
+ node.props.children.forEach(child => extractStates(child));
270
+ }
271
+ }
272
+ if(node.states){
273
+ for(let i in node.states) states[node.states[i].id] = node.states[i];
274
+ }
275
+ }
276
+ extractStates(this.root);
277
+ this.initialState = states;
278
+ return JSON.stringify(states);
279
+ }
280
+ render(staticRender = false) {
281
+ return staticRender ? renderToString(this.root) : `<script>
282
+ const __INITIAL_STATE__ = ${this.serializeState()};
283
+ const DOMObject = ${renderToString(this.root, true)};
284
+ const generateRandomID = ${generateRandomID}
285
+ const emitter = ${emitter}
286
+ ${State}
287
+ const states = [];
288
+ function rehydrate() {
289
+ const initialState = __INITIAL_STATE__;
290
+
291
+ function updateDOM(node, state) {
292
+ const elt = node.DOMELEMENT ? node.DOMELEMENT : node.nodeType == 'text' ? document.createTextNode(node.type.text) : document.createElement(node.type.element);
293
+ node.DOMELEMENT = elt;
294
+
295
+ if (node.nodeType == 'text') {
296
+ if(node.states?.[':text']){
297
+ const state = node.states[':text'];
298
+ if(state) elt.textContent = getState(state.id)?.value || state._value;
299
+ }
300
+ } else if (node.nodeType !== 'text' && node.props.children) {
301
+ const nodeState = node.states || {};
302
+ node.props.children.forEach(child => {
303
+ child.parent = node;
304
+ updateDOM(child, state);
305
+ });
306
+ Object.keys(node.props).forEach(key => {
307
+ if (key !== 'children') {
308
+ if(key in nodeState){
309
+ elt.setAttribute(key, getState(nodeState[key].id)?.value ?? nodeState[key]._value);
310
+ } else elt.setAttribute(key, node.props[key]);
311
+ }
312
+ });
313
+ if('data-only-if' in node.props){
314
+ if(elt.getAttribute('data-only-if') == 'true'){
315
+ elt.hidden = false;
316
+ } else {
317
+ elt.hidden = true;
318
+ }
319
+ }
320
+ }
321
+ if(node.parent && !elt.parentNode){
322
+ node.parent.DOMELEMENT.appendChild(elt);
323
+ }
324
+ return node;
325
+ }
326
+
327
+ function createState(inState, val, key){
328
+ const state = new State(inState._value);
329
+ state.id = inState.id;
330
+ states.push(state);
331
+ state.subscribe(() => updateDOM(DOMObject, { [key]: Array.isArray(val) ? [...val.filter(i => i.id !== state.id), state] : state }));
332
+ }
333
+
334
+ Object.keys(initialState).forEach(key => {
335
+ if(Array.isArray(initialState[key])) initialState[key].forEach((i) => createState(i, initialState[key], key));
336
+ else createState(initialState[key], initialState[key], key);
337
+ });
338
+
339
+ document.body.parentNode.remove();
340
+ document.appendChild(updateDOM(DOMObject, initialState).DOMELEMENT);
341
+ }
342
+ window.getState = (id) => states.find(s => s.id == id);
343
+ if (document.readyState === 'loading') {
344
+ document.addEventListener('DOMContentLoaded', rehydrate);
345
+ } else {
346
+ rehydrate();
347
+ }
348
+ </script>`;
349
+ }
350
+
351
+ toString() {
352
+ return this.render();
353
+ }
354
+
355
+ clone(){
356
+ const page = new Page();
357
+ page.root = cloneNest(this.root);
358
+ page.body = page.root.find('body');
359
+ page.head = page.root.find('head');
360
+ page.body.parent = page.root;
361
+ page.head.parent = page.root;
362
+ return page;
363
+ }
364
+ }
365
+ function createPage(options) {
366
+ const page = new Page;
367
+ const root = createElement('html');
368
+ page.root = root;
369
+
370
+ const head = createElement('head');
371
+ page.head = head;
372
+
373
+ if (options.viewportMeta) {
374
+ head
375
+ .add(createElement('meta', { charset: 'UTF-8' }))
376
+ .add(createElement('meta', { name: 'viewport', content: typeof options.viewportMeta == 'string' ? options.basicMeta : 'width=device-width, initial-scale=1.0' }));
377
+ }
378
+
379
+ const title = createElement('title', null, 'Document');
380
+
381
+ if (options.title) title.props.children = [createTextNode(options.title)];
382
+
383
+ if (options.title !== false) {
384
+ head.add(title);
385
+ page.title = title;
386
+ }
387
+
388
+ const body = createElement('body');
389
+ page.body = body;
390
+
391
+ root.add(head);
392
+ root.add(body);
393
+
394
+ return page;
395
+ }
396
+
397
+ module.exports = (context, importOptions) => {
398
+
399
+ const { build } = wait(async () => await import('vite'));
400
+
401
+ class Web {
402
+ create(options) {
403
+ return createPage(options);
404
+ }
405
+ isNode(node) {
406
+ return node instanceof Node;
407
+ }
408
+ isTextNode(node) {
409
+ return node instanceof TextNode;
410
+ }
411
+ isElementNode(node) {
412
+ return node instanceof ElementNode;
413
+ }
414
+ createText(text) {
415
+ return createTextNode(text);
416
+ }
417
+ createElement(...args) {
418
+ return createElement(...args);
419
+ }
420
+ state(value) {
421
+ return new State(value);
422
+ }
423
+ useState(states, callback){
424
+ const statesMapped = states.map(i => `getState('${i.id}')`);
425
+ return `((${callback})(...[${statesMapped}]))`;
426
+ }
427
+ async bundle(filepath, options = {}) {
428
+ const virtualModuleId = `virtual:${filepath}`;
429
+ const result = await build({
430
+ build: {
431
+ rollupOptions: {
432
+ input: options.code ? virtualModuleId : filepath,
433
+ output: {
434
+ format: 'iife', // Immediately Invoked Function Expression for the browser
435
+ entryFileNames: '[name].js',
436
+ },
437
+ },
438
+ write: false, // Do not write to file system, get the output as string
439
+ },
440
+ logLevel: 'silent',
441
+ plugins: [
442
+ (function rew() {
443
+ return {
444
+ name: 'rew',
445
+ resolveId(id) {
446
+ if (options.code && id === virtualModuleId) {
447
+ return virtualModuleId;
448
+ }
449
+ return null;
450
+ },
451
+ load(id) {
452
+ if (options.code && id === virtualModuleId) {
453
+ return options.code;
454
+ }
455
+ return null;
456
+ },
457
+ transform(code, id) {
458
+ if (id.endsWith('.coffee')) {
459
+ const result = compile({ content: code, path: filepath }, { jsx: true, keepImports: true });
460
+ return {
461
+ code: result,
462
+ map: null,
463
+ };
464
+ }
465
+ },
466
+ };
467
+ })(),
468
+ ...(options.plugins ?? [])
469
+ ],
470
+ });
471
+ return result.output[0].code;
472
+ }
473
+ }
474
+
475
+ return importOptions.instance ? new Web : Web;
476
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makano/rew",
3
- "version": "1.2.52",
3
+ "version": "1.2.54",
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
  }