neutronium 2.9.9 → 3.0.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/README.md +21 -5
- package/cli/index.js +1 -1
- package/compiler/compiler.js +42 -57
- package/compiler/watcher.js +17 -0
- package/package.json +5 -5
- package/src/index.js +37 -19
- package/ts-neutronium/index.d.ts +82 -57
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# ⚛️ Neutronium
|
|
1
|
+
# ⚛️ Neutronium v3.0
|
|
2
2
|
|
|
3
3
|
**Ultra-dense JavaScript framework – maximum performance, minimal overhead**
|
|
4
4
|
|
|
@@ -8,7 +8,14 @@
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## 🎉 What's new?
|
|
12
|
+
- ⚡ Faster compilation for complex projects
|
|
13
|
+
- ✨ useState, and useEffect
|
|
14
|
+
- ⚛️ React like syntax for easier switching
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## ℹ️ About
|
|
12
19
|
|
|
13
20
|
**Neutronium** is a lightweight, efficient JavaScript framework designed for building modern web applications with **React-like simplicity** but **minimal overhead**.
|
|
14
21
|
|
|
@@ -33,12 +40,16 @@
|
|
|
33
40
|
npm i neutronium@latest -g
|
|
34
41
|
```
|
|
35
42
|
|
|
43
|
+
---
|
|
44
|
+
|
|
36
45
|
## 🛠️ Setup
|
|
37
46
|
|
|
38
47
|
```
|
|
39
48
|
neu-cli create-app my-app
|
|
40
49
|
```
|
|
41
50
|
|
|
51
|
+
---
|
|
52
|
+
|
|
42
53
|
## Usage Example
|
|
43
54
|
```jsx
|
|
44
55
|
// App.js
|
|
@@ -63,7 +74,12 @@ createApp(App).mount('body');
|
|
|
63
74
|
## Result:
|
|
64
75
|

|
|
65
76
|
|
|
66
|
-
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## NPM Packages using Neutronium
|
|
67
80
|
### [@neuhq/alert](https://www.npmjs.com/package/@neuhq/alert)
|
|
68
|
-
|
|
69
|
-
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Found a bug or a problem?
|
|
85
|
+
### Report [here.](https://github.com/PFMCODES/neutronium/issues/new)
|
package/cli/index.js
CHANGED
package/compiler/compiler.js
CHANGED
|
@@ -12,14 +12,19 @@ const { execSync } = require('child_process');
|
|
|
12
12
|
|
|
13
13
|
async function compileProject(projectDir = process.cwd()) {
|
|
14
14
|
const distDir = path.join(projectDir, 'dist');
|
|
15
|
-
const neutroniumPath = '
|
|
15
|
+
const neutroniumPath = path.join(projectDir, 'node_modules', 'neutronium', 'src', 'index.js');
|
|
16
16
|
const packageJson = JSON.parse(fs.readFileSync(path.join(projectDir, 'package.json')));
|
|
17
17
|
const entry = packageJson.main || 'App.js';
|
|
18
18
|
|
|
19
19
|
try {
|
|
20
20
|
log('📁 Scanning project files...');
|
|
21
21
|
const allFiles = fs.readdirSync(projectDir);
|
|
22
|
-
const jsFiles = allFiles.filter(f =>
|
|
22
|
+
const jsFiles = allFiles.filter(f =>
|
|
23
|
+
f.endsWith('.js') &&
|
|
24
|
+
f !== 'compiler.js' &&
|
|
25
|
+
!f.startsWith('.') &&
|
|
26
|
+
!f.startsWith('dist')
|
|
27
|
+
);
|
|
23
28
|
|
|
24
29
|
if (fs.existsSync(path.join(projectDir, 'tsconfig.json'))) {
|
|
25
30
|
try {
|
|
@@ -63,11 +68,11 @@ async function compileProject(projectDir = process.cwd()) {
|
|
|
63
68
|
|
|
64
69
|
// Ensure _neutronium is imported
|
|
65
70
|
if (!source.includes('_neutronium')) {
|
|
66
|
-
code = `import * as _neutronium from '${neutroniumPath}';\n\n${code}`;
|
|
71
|
+
code = `import * as _neutronium from '${neutroniumPath.replace(/\\/g, '/')}';\n\n${code}`;
|
|
67
72
|
}
|
|
68
73
|
|
|
69
|
-
// Replace imports to local
|
|
70
|
-
code = code.replace(/from\s+['"]neutronium['"]/g, `from '${neutroniumPath}'`);
|
|
74
|
+
// Replace imports to "neutronium" with local path
|
|
75
|
+
code = code.replace(/from\s+['"]neutronium['"]/g, `from '${neutroniumPath.replace(/\\/g, '/')}'`);
|
|
71
76
|
|
|
72
77
|
writeFile(outputPath, code);
|
|
73
78
|
}
|
|
@@ -88,32 +93,31 @@ async function compileProject(projectDir = process.cwd()) {
|
|
|
88
93
|
}
|
|
89
94
|
|
|
90
95
|
function compileProjectWatch(projectDir = process.cwd(), port = 3000) {
|
|
91
|
-
// Start server first
|
|
92
96
|
const server = serveProject(projectDir, port);
|
|
93
|
-
|
|
94
|
-
// Do initial compilation
|
|
95
97
|
compileProject(projectDir);
|
|
96
98
|
|
|
97
99
|
log('👀 Watching project for changes...');
|
|
98
100
|
log('✋ Press Ctrl+C to stop the development server');
|
|
99
|
-
|
|
101
|
+
|
|
100
102
|
let timeout;
|
|
101
103
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
+
const watcher = chokidar.watch([
|
|
105
|
+
path.join(projectDir, '**/*.js'),
|
|
106
|
+
path.join(projectDir, '**/*.ts'),
|
|
107
|
+
path.join(projectDir, '**/*.tsx'),
|
|
108
|
+
], {
|
|
104
109
|
ignoreInitial: true,
|
|
105
110
|
ignored: [
|
|
106
111
|
'**/node_modules/**',
|
|
107
112
|
'**/dist/**',
|
|
108
113
|
'**/.git/**',
|
|
109
|
-
'**/*',
|
|
110
114
|
'compiler.js'
|
|
111
115
|
],
|
|
112
116
|
persistent: true,
|
|
113
117
|
followSymlinks: false,
|
|
114
|
-
depth:
|
|
118
|
+
depth: 5,
|
|
115
119
|
awaitWriteFinish: {
|
|
116
|
-
stabilityThreshold:
|
|
120
|
+
stabilityThreshold: 300,
|
|
117
121
|
pollInterval: 100
|
|
118
122
|
}
|
|
119
123
|
});
|
|
@@ -123,58 +127,45 @@ function compileProjectWatch(projectDir = process.cwd(), port = 3000) {
|
|
|
123
127
|
});
|
|
124
128
|
|
|
125
129
|
watcher.on('change', filePath => {
|
|
126
|
-
|
|
127
|
-
if (!/\.(js|ts|tsx)$/.test(filePath)) {
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
+
if (!/\.(js|ts|tsx)$/.test(filePath)) return;
|
|
130
131
|
|
|
131
132
|
log(`🔍 Detected change in: ${path.relative(projectDir, filePath)}`);
|
|
132
|
-
|
|
133
|
+
|
|
133
134
|
clearTimeout(timeout);
|
|
134
135
|
timeout = setTimeout(() => {
|
|
135
136
|
log('🔨 Rebuilding project...');
|
|
136
|
-
|
|
137
137
|
try {
|
|
138
138
|
const success = compileProject(projectDir);
|
|
139
|
-
if (success) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (server && server.broadcastReload) {
|
|
143
|
-
server.broadcastReload();
|
|
144
|
-
log('🔄 Browser reload triggered');
|
|
145
|
-
}
|
|
139
|
+
if (success && server.broadcastReload) {
|
|
140
|
+
server.broadcastReload();
|
|
141
|
+
log('🔄 Browser reload triggered');
|
|
146
142
|
}
|
|
147
143
|
} catch (err) {
|
|
148
144
|
console.error('❌ Rebuild failed:', err.stack || err.message);
|
|
149
145
|
}
|
|
150
|
-
}, 300);
|
|
146
|
+
}, 300);
|
|
151
147
|
});
|
|
152
148
|
|
|
153
149
|
watcher.on('add', filePath => {
|
|
154
|
-
if (
|
|
155
|
-
|
|
150
|
+
if (/\.(js|ts|tsx)$/.test(filePath)) {
|
|
151
|
+
log(`📝 New file added: ${path.relative(projectDir, filePath)}`);
|
|
156
152
|
}
|
|
157
|
-
log(`📝 New file added: ${path.relative(projectDir, filePath)}`);
|
|
158
153
|
});
|
|
159
154
|
|
|
160
155
|
watcher.on('unlink', filePath => {
|
|
161
|
-
if (
|
|
162
|
-
|
|
156
|
+
if (/\.(js|ts|tsx)$/.test(filePath)) {
|
|
157
|
+
log(`🗑️ File removed: ${path.relative(projectDir, filePath)}`);
|
|
163
158
|
}
|
|
164
|
-
log(`🗑️ File removed: ${path.relative(projectDir, filePath)}`);
|
|
165
159
|
});
|
|
166
160
|
|
|
167
161
|
watcher.on('error', error => {
|
|
168
162
|
console.error('❌ Watcher error:', error);
|
|
169
163
|
});
|
|
170
164
|
|
|
171
|
-
// Cleanup function
|
|
172
165
|
const cleanup = () => {
|
|
173
166
|
log('🧹 Cleaning up...');
|
|
174
167
|
watcher.close();
|
|
175
|
-
if (server)
|
|
176
|
-
server.close();
|
|
177
|
-
}
|
|
168
|
+
if (server) server.close();
|
|
178
169
|
process.exit(0);
|
|
179
170
|
};
|
|
180
171
|
|
|
@@ -187,26 +178,23 @@ function compileProjectWatch(projectDir = process.cwd(), port = 3000) {
|
|
|
187
178
|
function serveProject(projectDir = process.cwd(), port = 3000) {
|
|
188
179
|
const server = http.createServer((req, res) => {
|
|
189
180
|
let reqPath = req.url;
|
|
190
|
-
|
|
191
|
-
// Handle root and index requests
|
|
181
|
+
|
|
192
182
|
if (reqPath === '/' || reqPath === '/index.html') {
|
|
193
183
|
reqPath = '/dist/index.html';
|
|
194
184
|
}
|
|
195
|
-
|
|
196
|
-
// Handle favicon requests
|
|
185
|
+
|
|
197
186
|
if (reqPath === '/favicon.ico') {
|
|
198
187
|
res.writeHead(204);
|
|
199
188
|
return res.end();
|
|
200
189
|
}
|
|
201
190
|
|
|
202
191
|
const filePath = path.join(projectDir, reqPath);
|
|
203
|
-
|
|
204
|
-
// Security check - ensure file is within project directory
|
|
192
|
+
|
|
205
193
|
if (!filePath.startsWith(projectDir)) {
|
|
206
194
|
res.writeHead(403);
|
|
207
195
|
return res.end('403 Forbidden');
|
|
208
196
|
}
|
|
209
|
-
|
|
197
|
+
|
|
210
198
|
if (!fs.existsSync(filePath)) {
|
|
211
199
|
res.writeHead(404);
|
|
212
200
|
return res.end('404 Not Found');
|
|
@@ -215,8 +203,8 @@ function serveProject(projectDir = process.cwd(), port = 3000) {
|
|
|
215
203
|
try {
|
|
216
204
|
const content = fs.readFileSync(filePath);
|
|
217
205
|
const mimeType = Mime.getType(filePath) || 'application/octet-stream';
|
|
218
|
-
|
|
219
|
-
res.writeHead(200, {
|
|
206
|
+
|
|
207
|
+
res.writeHead(200, {
|
|
220
208
|
'Content-Type': mimeType,
|
|
221
209
|
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
|
222
210
|
'Pragma': 'no-cache',
|
|
@@ -231,14 +219,10 @@ function serveProject(projectDir = process.cwd(), port = 3000) {
|
|
|
231
219
|
});
|
|
232
220
|
|
|
233
221
|
const wss = new WebSocket.Server({ server });
|
|
234
|
-
|
|
235
|
-
// Add WebSocket connection handling
|
|
222
|
+
|
|
236
223
|
wss.on('connection', (ws) => {
|
|
237
224
|
log('🔌 WebSocket client connected');
|
|
238
|
-
|
|
239
|
-
ws.on('close', () => {
|
|
240
|
-
log('🔌 WebSocket client disconnected');
|
|
241
|
-
});
|
|
225
|
+
ws.on('close', () => log('🔌 WebSocket client disconnected'));
|
|
242
226
|
});
|
|
243
227
|
|
|
244
228
|
server.broadcastReload = () => {
|
|
@@ -252,12 +236,13 @@ function serveProject(projectDir = process.cwd(), port = 3000) {
|
|
|
252
236
|
server.listen(port, () => {
|
|
253
237
|
log(`🚀 Server running at http://localhost:${port}`);
|
|
254
238
|
log(`🌐 Open your browser and navigate to: http://localhost:${port}`);
|
|
255
|
-
|
|
256
|
-
// Remove the open() call completely for now to avoid hanging
|
|
257
|
-
// User can manually open browser
|
|
258
239
|
});
|
|
259
240
|
|
|
260
241
|
return server;
|
|
261
242
|
}
|
|
262
243
|
|
|
263
|
-
module.exports = {
|
|
244
|
+
module.exports = {
|
|
245
|
+
compileProject,
|
|
246
|
+
compileProjectWatch,
|
|
247
|
+
serveProject,
|
|
248
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import chokidar from 'chokidar';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
const projectDir = process.cwd(); // or absolute path
|
|
5
|
+
|
|
6
|
+
const watcher = chokidar.watch(path.join(projectDir, '**/*.js'), {
|
|
7
|
+
ignoreInitial: true,
|
|
8
|
+
ignored: ['**/node_modules/**'],
|
|
9
|
+
awaitWriteFinish: {
|
|
10
|
+
stabilityThreshold: 300,
|
|
11
|
+
pollInterval: 100
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
watcher.on('all', (event, filePath) => {
|
|
16
|
+
console.log(`[${event}] ${filePath}`);
|
|
17
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neutronium",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "Ultra-dense JavaScript framework – maximum performance, minimal overhead",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"types": "./ts-neutronium/index.d.ts",
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
],
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|
|
25
|
-
"url": "git+https://github.com/
|
|
25
|
+
"url": "git+https://github.com/pfmcodes/neutronium.git"
|
|
26
26
|
},
|
|
27
27
|
"author": "PFMCODES",
|
|
28
28
|
"bugs": {
|
|
29
|
-
"url": "https://github.com/
|
|
29
|
+
"url": "https://github.com//pfmcodes/neutronium/issues"
|
|
30
30
|
},
|
|
31
|
-
"homepage": "https://github.com/
|
|
31
|
+
"homepage": "https://github.com/pfmcodes/neutronium#readme",
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@babel/core": "^7.28.0",
|
package/src/index.js
CHANGED
|
@@ -3,42 +3,60 @@
|
|
|
3
3
|
let globalState = [];
|
|
4
4
|
let stateIndex = 0;
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
export function resetStateIndex() {
|
|
6
|
+
function resetStateIndex() {
|
|
8
7
|
stateIndex = 0;
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
let currentEffect = null;
|
|
11
|
+
|
|
12
|
+
function useEffect(fn) {
|
|
13
|
+
currentEffect = fn;
|
|
14
|
+
fn(); // run once to collect dependencies
|
|
15
|
+
currentEffect = null;
|
|
16
|
+
}
|
|
17
|
+
|
|
12
18
|
function useState(initialValue) {
|
|
13
19
|
const index = stateIndex;
|
|
14
20
|
|
|
15
|
-
if (globalState[index]
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
if (!globalState[index]) {
|
|
22
|
+
let value = initialValue;
|
|
23
|
+
const subs = new Set();
|
|
18
24
|
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
function get() {
|
|
26
|
+
if (currentEffect) subs.add(currentEffect);
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
21
29
|
|
|
22
|
-
|
|
23
|
-
|
|
30
|
+
function set(newVal) {
|
|
31
|
+
if (value !== newVal) {
|
|
32
|
+
value = newVal;
|
|
33
|
+
subs.forEach(fn => fn()); // re-run effects
|
|
34
|
+
if (typeof window.__NEUTRONIUM_RENDER_FN__ === 'function') {
|
|
35
|
+
window.__NEUTRONIUM_RENDER_FN__();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
24
38
|
}
|
|
39
|
+
|
|
40
|
+
globalState[index] = [get, set];
|
|
25
41
|
}
|
|
26
42
|
|
|
43
|
+
const result = globalState[index];
|
|
27
44
|
stateIndex++;
|
|
28
|
-
return
|
|
45
|
+
return result;
|
|
29
46
|
}
|
|
30
47
|
|
|
31
48
|
function h(type, props = {}, ...children) {
|
|
49
|
+
props = props || {};
|
|
50
|
+
props.children = (props.children || []).concat(children).flat();
|
|
51
|
+
|
|
32
52
|
if (typeof type === 'function') {
|
|
33
|
-
// 🔧 Add children to props
|
|
34
|
-
props = props || {};
|
|
35
|
-
props.children = children.flat(); // ✅ critical fix
|
|
36
53
|
return type(props);
|
|
37
54
|
}
|
|
38
55
|
|
|
39
56
|
const el = document.createElement(type);
|
|
40
57
|
|
|
41
58
|
for (const [key, value] of Object.entries(props || {})) {
|
|
59
|
+
if (key === 'children') continue; // skip
|
|
42
60
|
if (key.startsWith('on') && typeof value === 'function') {
|
|
43
61
|
el.addEventListener(key.slice(2).toLowerCase(), value);
|
|
44
62
|
} else if (key === 'ref' && typeof value === 'function') {
|
|
@@ -48,7 +66,7 @@ function h(type, props = {}, ...children) {
|
|
|
48
66
|
}
|
|
49
67
|
}
|
|
50
68
|
|
|
51
|
-
children.
|
|
69
|
+
props.children.forEach(child => {
|
|
52
70
|
if (typeof child === 'string' || typeof child === 'number') {
|
|
53
71
|
el.appendChild(document.createTextNode(child));
|
|
54
72
|
} else if (child instanceof Node) {
|
|
@@ -71,14 +89,14 @@ function createApp(component) {
|
|
|
71
89
|
window.__NEUTRONIUM_ROOT__ = root;
|
|
72
90
|
|
|
73
91
|
function render() {
|
|
74
|
-
resetStateIndex();
|
|
92
|
+
resetStateIndex();
|
|
75
93
|
const vnode = component();
|
|
76
94
|
root.innerHTML = '';
|
|
77
95
|
root.appendChild(vnode);
|
|
78
96
|
}
|
|
79
97
|
|
|
80
|
-
window.__NEUTRONIUM_RENDER_FN__ = render;
|
|
81
|
-
render();
|
|
98
|
+
window.__NEUTRONIUM_RENDER_FN__ = render;
|
|
99
|
+
render();
|
|
82
100
|
|
|
83
101
|
return root;
|
|
84
102
|
}
|
|
@@ -100,4 +118,4 @@ function Fragment(props = {}) {
|
|
|
100
118
|
return frag;
|
|
101
119
|
}
|
|
102
120
|
|
|
103
|
-
export { h, createApp, Fragment, useState };
|
|
121
|
+
export { h, createApp, Fragment, useState,useEffect, resetStateIndex };
|
package/ts-neutronium/index.d.ts
CHANGED
|
@@ -1,58 +1,71 @@
|
|
|
1
|
-
//
|
|
1
|
+
// src/index.ts
|
|
2
2
|
|
|
3
|
-
type
|
|
4
|
-
type
|
|
3
|
+
type EffectFn = () => void;
|
|
4
|
+
type Setter<T> = (value: T) => void;
|
|
5
|
+
type Getter<T> = () => T;
|
|
6
|
+
type StateTuple<T> = [Getter<T>, Setter<T>];
|
|
5
7
|
|
|
6
|
-
let globalState: any
|
|
8
|
+
let globalState: Array<StateTuple<any>> = [];
|
|
7
9
|
let stateIndex = 0;
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
export function resetStateIndex(): void {
|
|
11
|
+
function resetStateIndex(): void {
|
|
11
12
|
stateIndex = 0;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
export function useState<T>(initialValue: T): [T, StateUpdater<T>] {
|
|
16
|
-
const currentIndex = stateIndex;
|
|
15
|
+
let currentEffect: EffectFn | null = null;
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
globalState[currentIndex] = newValue;
|
|
17
|
+
function useEffect(fn: EffectFn): void {
|
|
18
|
+
currentEffect = fn;
|
|
19
|
+
fn(); // run once to collect dependencies
|
|
20
|
+
currentEffect = null;
|
|
21
|
+
}
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
function useState<T>(initialValue: T): StateTuple<T> {
|
|
24
|
+
const index = stateIndex;
|
|
25
|
+
|
|
26
|
+
if (!globalState[index]) {
|
|
27
|
+
let value = initialValue;
|
|
28
|
+
const subs = new Set<EffectFn>();
|
|
29
|
+
|
|
30
|
+
const get: Getter<T> = () => {
|
|
31
|
+
if (currentEffect) subs.add(currentEffect);
|
|
32
|
+
return value;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const set: Setter<T> = (newVal: T) => {
|
|
36
|
+
if (value !== newVal) {
|
|
37
|
+
value = newVal;
|
|
38
|
+
subs.forEach(fn => fn()); // re-run effects
|
|
39
|
+
if (typeof (window as any).__NEUTRONIUM_RENDER_FN__ === 'function') {
|
|
40
|
+
(window as any).__NEUTRONIUM_RENDER_FN__();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
28
44
|
|
|
29
|
-
|
|
30
|
-
root.innerHTML = '';
|
|
31
|
-
resetStateIndex();
|
|
32
|
-
const newVNode = renderFn();
|
|
33
|
-
root.appendChild(newVNode);
|
|
34
|
-
}
|
|
45
|
+
globalState[index] = [get, set];
|
|
35
46
|
}
|
|
36
47
|
|
|
48
|
+
const result = globalState[index] as StateTuple<T>;
|
|
37
49
|
stateIndex++;
|
|
38
|
-
return
|
|
50
|
+
return result;
|
|
39
51
|
}
|
|
40
52
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
type Props = {
|
|
54
|
+
[key: string]: any;
|
|
55
|
+
children?: any;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function h(type: string | ((props: Props) => Node), props: Props = {}, ...children: any[]): Node {
|
|
59
|
+
props.children = (props.children || []).concat(children).flat();
|
|
60
|
+
|
|
47
61
|
if (typeof type === 'function') {
|
|
48
|
-
props = props || {};
|
|
49
|
-
props.children = children.flat();
|
|
50
62
|
return type(props);
|
|
51
63
|
}
|
|
52
64
|
|
|
53
65
|
const el = document.createElement(type);
|
|
54
66
|
|
|
55
67
|
for (const [key, value] of Object.entries(props)) {
|
|
68
|
+
if (key === 'children') continue;
|
|
56
69
|
if (key.startsWith('on') && typeof value === 'function') {
|
|
57
70
|
el.addEventListener(key.slice(2).toLowerCase(), value);
|
|
58
71
|
} else if (key === 'ref' && typeof value === 'function') {
|
|
@@ -62,56 +75,68 @@ export function h(
|
|
|
62
75
|
}
|
|
63
76
|
}
|
|
64
77
|
|
|
65
|
-
|
|
78
|
+
for (const child of props.children) {
|
|
66
79
|
if (typeof child === 'string' || typeof child === 'number') {
|
|
67
|
-
el.appendChild(document.createTextNode(child));
|
|
80
|
+
el.appendChild(document.createTextNode(String(child)));
|
|
68
81
|
} else if (child instanceof Node) {
|
|
69
82
|
el.appendChild(child);
|
|
70
83
|
}
|
|
71
|
-
}
|
|
84
|
+
}
|
|
72
85
|
|
|
73
86
|
return el;
|
|
74
87
|
}
|
|
75
88
|
|
|
76
|
-
|
|
77
|
-
export function createApp(component: () => Node) {
|
|
89
|
+
function createApp(component: () => Node) {
|
|
78
90
|
return {
|
|
79
|
-
mount(selector: string |
|
|
80
|
-
const root =
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
: selector;
|
|
91
|
+
mount(selector: string | Element): Element | null {
|
|
92
|
+
const root = typeof selector === 'string'
|
|
93
|
+
? document.querySelector(selector)
|
|
94
|
+
: selector;
|
|
84
95
|
|
|
85
96
|
if (!root) {
|
|
86
97
|
console.error(`❌ Root element '${selector}' not found`);
|
|
87
98
|
return null;
|
|
88
99
|
}
|
|
89
100
|
|
|
90
|
-
window.__NEUTRONIUM_ROOT__ = root;
|
|
91
|
-
|
|
101
|
+
(window as any).__NEUTRONIUM_ROOT__ = root;
|
|
102
|
+
|
|
103
|
+
function render() {
|
|
104
|
+
resetStateIndex();
|
|
105
|
+
const vnode = component();
|
|
106
|
+
root.innerHTML = '';
|
|
107
|
+
root.appendChild(vnode);
|
|
108
|
+
}
|
|
92
109
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
root.innerHTML = '';
|
|
96
|
-
root.appendChild(vnode);
|
|
110
|
+
(window as any).__NEUTRONIUM_RENDER_FN__ = render;
|
|
111
|
+
render();
|
|
97
112
|
|
|
98
|
-
return
|
|
113
|
+
return root;
|
|
99
114
|
}
|
|
100
115
|
};
|
|
101
116
|
}
|
|
102
117
|
|
|
103
|
-
|
|
104
|
-
export function Fragment(props: { children?: any[] }): DocumentFragment {
|
|
118
|
+
function Fragment(props: Props = {}): DocumentFragment {
|
|
105
119
|
const frag = document.createDocumentFragment();
|
|
106
|
-
const children = props.children
|
|
120
|
+
const children = props.children || [];
|
|
121
|
+
|
|
122
|
+
const childArray = Array.isArray(children) ? children : [children];
|
|
107
123
|
|
|
108
|
-
(
|
|
124
|
+
for (const child of childArray) {
|
|
109
125
|
if (typeof child === 'string' || typeof child === 'number') {
|
|
110
|
-
frag.appendChild(document.createTextNode(child));
|
|
126
|
+
frag.appendChild(document.createTextNode(String(child)));
|
|
111
127
|
} else if (child instanceof Node) {
|
|
112
128
|
frag.appendChild(child);
|
|
113
129
|
}
|
|
114
|
-
}
|
|
130
|
+
}
|
|
115
131
|
|
|
116
132
|
return frag;
|
|
117
|
-
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export {
|
|
136
|
+
h,
|
|
137
|
+
createApp,
|
|
138
|
+
Fragment,
|
|
139
|
+
useState,
|
|
140
|
+
useEffect,
|
|
141
|
+
resetStateIndex
|
|
142
|
+
};
|