neutronium 2.9.91 → 3.0.2
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 +82 -57
- package/compiler/template.js +2 -3
- package/compiler/watcher.js +17 -0
- package/package.json +3 -3
- 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
|
}
|
|
@@ -87,33 +92,71 @@ async function compileProject(projectDir = process.cwd()) {
|
|
|
87
92
|
}
|
|
88
93
|
}
|
|
89
94
|
|
|
95
|
+
async function compileAndReturnOutput(code) {
|
|
96
|
+
try {
|
|
97
|
+
log('📁 Scanning project files...');
|
|
98
|
+
log(`⚙️ Babel transforming`);
|
|
99
|
+
|
|
100
|
+
code = babel.transformSync(code, {
|
|
101
|
+
babelrc: false,
|
|
102
|
+
configFile: false,
|
|
103
|
+
presets: [],
|
|
104
|
+
plugins: [[
|
|
105
|
+
'@babel/plugin-transform-react-jsx',
|
|
106
|
+
{
|
|
107
|
+
pragma: '_neutronium.h',
|
|
108
|
+
pragmaFrag: '_neutronium.Fragment',
|
|
109
|
+
runtime: 'classic',
|
|
110
|
+
}
|
|
111
|
+
]]
|
|
112
|
+
});
|
|
113
|
+
code = code.code
|
|
114
|
+
// Ensure _neutronium is imported
|
|
115
|
+
if (!code.includes('_neutronium')) {
|
|
116
|
+
code = `import * as _neutronium from 'https://esm.sh/neutronium@3.0.2/es2022/neutronium.mjs`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Replace imports to "neutronium" with local path
|
|
120
|
+
code = code.replace(/from\s+['"]neutronium['"]/g, `from '${neutroniumPath.replace(/\\/g, '/')}'`);
|
|
121
|
+
|
|
122
|
+
console.log('🛠️ Generating index.html...');
|
|
123
|
+
const htmlContent = baseHtml(code);
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
log('✅ Compilation complete!');
|
|
127
|
+
return htmlContent;
|
|
128
|
+
} catch (e) {
|
|
129
|
+
console.error('❌ Compilation failed:', e.message);
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
90
134
|
function compileProjectWatch(projectDir = process.cwd(), port = 3000) {
|
|
91
|
-
// Start server first
|
|
92
135
|
const server = serveProject(projectDir, port);
|
|
93
|
-
|
|
94
|
-
// Do initial compilation
|
|
95
136
|
compileProject(projectDir);
|
|
96
137
|
|
|
97
138
|
log('👀 Watching project for changes...');
|
|
98
139
|
log('✋ Press Ctrl+C to stop the development server');
|
|
99
|
-
|
|
140
|
+
|
|
100
141
|
let timeout;
|
|
101
142
|
|
|
102
|
-
|
|
103
|
-
|
|
143
|
+
const watcher = chokidar.watch([
|
|
144
|
+
path.join(projectDir, '**/*.js'),
|
|
145
|
+
path.join(projectDir, '**/*.ts'),
|
|
146
|
+
path.join(projectDir, '**/*.tsx'),
|
|
147
|
+
], {
|
|
104
148
|
ignoreInitial: true,
|
|
105
149
|
ignored: [
|
|
106
150
|
'**/node_modules/**',
|
|
107
151
|
'**/dist/**',
|
|
108
152
|
'**/.git/**',
|
|
109
|
-
'**/*',
|
|
110
153
|
'compiler.js'
|
|
111
154
|
],
|
|
112
155
|
persistent: true,
|
|
113
156
|
followSymlinks: false,
|
|
114
|
-
depth:
|
|
157
|
+
depth: 5,
|
|
115
158
|
awaitWriteFinish: {
|
|
116
|
-
stabilityThreshold:
|
|
159
|
+
stabilityThreshold: 300,
|
|
117
160
|
pollInterval: 100
|
|
118
161
|
}
|
|
119
162
|
});
|
|
@@ -123,58 +166,45 @@ function compileProjectWatch(projectDir = process.cwd(), port = 3000) {
|
|
|
123
166
|
});
|
|
124
167
|
|
|
125
168
|
watcher.on('change', filePath => {
|
|
126
|
-
|
|
127
|
-
if (!/\.(js|ts|tsx)$/.test(filePath)) {
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
169
|
+
if (!/\.(js|ts|tsx)$/.test(filePath)) return;
|
|
130
170
|
|
|
131
171
|
log(`🔍 Detected change in: ${path.relative(projectDir, filePath)}`);
|
|
132
|
-
|
|
172
|
+
|
|
133
173
|
clearTimeout(timeout);
|
|
134
174
|
timeout = setTimeout(() => {
|
|
135
175
|
log('🔨 Rebuilding project...');
|
|
136
|
-
|
|
137
176
|
try {
|
|
138
177
|
const success = compileProject(projectDir);
|
|
139
|
-
if (success) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (server && server.broadcastReload) {
|
|
143
|
-
server.broadcastReload();
|
|
144
|
-
log('🔄 Browser reload triggered');
|
|
145
|
-
}
|
|
178
|
+
if (success && server.broadcastReload) {
|
|
179
|
+
server.broadcastReload();
|
|
180
|
+
log('🔄 Browser reload triggered');
|
|
146
181
|
}
|
|
147
182
|
} catch (err) {
|
|
148
183
|
console.error('❌ Rebuild failed:', err.stack || err.message);
|
|
149
184
|
}
|
|
150
|
-
}, 300);
|
|
185
|
+
}, 300);
|
|
151
186
|
});
|
|
152
187
|
|
|
153
188
|
watcher.on('add', filePath => {
|
|
154
|
-
if (
|
|
155
|
-
|
|
189
|
+
if (/\.(js|ts|tsx)$/.test(filePath)) {
|
|
190
|
+
log(`📝 New file added: ${path.relative(projectDir, filePath)}`);
|
|
156
191
|
}
|
|
157
|
-
log(`📝 New file added: ${path.relative(projectDir, filePath)}`);
|
|
158
192
|
});
|
|
159
193
|
|
|
160
194
|
watcher.on('unlink', filePath => {
|
|
161
|
-
if (
|
|
162
|
-
|
|
195
|
+
if (/\.(js|ts|tsx)$/.test(filePath)) {
|
|
196
|
+
log(`🗑️ File removed: ${path.relative(projectDir, filePath)}`);
|
|
163
197
|
}
|
|
164
|
-
log(`🗑️ File removed: ${path.relative(projectDir, filePath)}`);
|
|
165
198
|
});
|
|
166
199
|
|
|
167
200
|
watcher.on('error', error => {
|
|
168
201
|
console.error('❌ Watcher error:', error);
|
|
169
202
|
});
|
|
170
203
|
|
|
171
|
-
// Cleanup function
|
|
172
204
|
const cleanup = () => {
|
|
173
205
|
log('🧹 Cleaning up...');
|
|
174
206
|
watcher.close();
|
|
175
|
-
if (server)
|
|
176
|
-
server.close();
|
|
177
|
-
}
|
|
207
|
+
if (server) server.close();
|
|
178
208
|
process.exit(0);
|
|
179
209
|
};
|
|
180
210
|
|
|
@@ -187,26 +217,23 @@ function compileProjectWatch(projectDir = process.cwd(), port = 3000) {
|
|
|
187
217
|
function serveProject(projectDir = process.cwd(), port = 3000) {
|
|
188
218
|
const server = http.createServer((req, res) => {
|
|
189
219
|
let reqPath = req.url;
|
|
190
|
-
|
|
191
|
-
// Handle root and index requests
|
|
220
|
+
|
|
192
221
|
if (reqPath === '/' || reqPath === '/index.html') {
|
|
193
222
|
reqPath = '/dist/index.html';
|
|
194
223
|
}
|
|
195
|
-
|
|
196
|
-
// Handle favicon requests
|
|
224
|
+
|
|
197
225
|
if (reqPath === '/favicon.ico') {
|
|
198
226
|
res.writeHead(204);
|
|
199
227
|
return res.end();
|
|
200
228
|
}
|
|
201
229
|
|
|
202
230
|
const filePath = path.join(projectDir, reqPath);
|
|
203
|
-
|
|
204
|
-
// Security check - ensure file is within project directory
|
|
231
|
+
|
|
205
232
|
if (!filePath.startsWith(projectDir)) {
|
|
206
233
|
res.writeHead(403);
|
|
207
234
|
return res.end('403 Forbidden');
|
|
208
235
|
}
|
|
209
|
-
|
|
236
|
+
|
|
210
237
|
if (!fs.existsSync(filePath)) {
|
|
211
238
|
res.writeHead(404);
|
|
212
239
|
return res.end('404 Not Found');
|
|
@@ -215,8 +242,8 @@ function serveProject(projectDir = process.cwd(), port = 3000) {
|
|
|
215
242
|
try {
|
|
216
243
|
const content = fs.readFileSync(filePath);
|
|
217
244
|
const mimeType = Mime.getType(filePath) || 'application/octet-stream';
|
|
218
|
-
|
|
219
|
-
res.writeHead(200, {
|
|
245
|
+
|
|
246
|
+
res.writeHead(200, {
|
|
220
247
|
'Content-Type': mimeType,
|
|
221
248
|
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
|
222
249
|
'Pragma': 'no-cache',
|
|
@@ -231,14 +258,10 @@ function serveProject(projectDir = process.cwd(), port = 3000) {
|
|
|
231
258
|
});
|
|
232
259
|
|
|
233
260
|
const wss = new WebSocket.Server({ server });
|
|
234
|
-
|
|
235
|
-
// Add WebSocket connection handling
|
|
261
|
+
|
|
236
262
|
wss.on('connection', (ws) => {
|
|
237
263
|
log('🔌 WebSocket client connected');
|
|
238
|
-
|
|
239
|
-
ws.on('close', () => {
|
|
240
|
-
log('🔌 WebSocket client disconnected');
|
|
241
|
-
});
|
|
264
|
+
ws.on('close', () => log('🔌 WebSocket client disconnected'));
|
|
242
265
|
});
|
|
243
266
|
|
|
244
267
|
server.broadcastReload = () => {
|
|
@@ -252,12 +275,14 @@ function serveProject(projectDir = process.cwd(), port = 3000) {
|
|
|
252
275
|
server.listen(port, () => {
|
|
253
276
|
log(`🚀 Server running at http://localhost:${port}`);
|
|
254
277
|
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
278
|
});
|
|
259
279
|
|
|
260
280
|
return server;
|
|
261
281
|
}
|
|
262
282
|
|
|
263
|
-
module.exports = {
|
|
283
|
+
module.exports = {
|
|
284
|
+
compileProject,
|
|
285
|
+
compileProjectWatch,
|
|
286
|
+
serveProject,
|
|
287
|
+
compileAndReturnOutput
|
|
288
|
+
};
|
package/compiler/template.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
function baseHtml(
|
|
1
|
+
function baseHtml(script) {
|
|
2
2
|
return `
|
|
3
3
|
<!DOCTYPE html>
|
|
4
4
|
<html>
|
|
@@ -7,8 +7,7 @@ function baseHtml(appHtml, scriptName) {
|
|
|
7
7
|
<title>Neutronium App</title>
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
|
-
${
|
|
11
|
-
<script type="module" src="./${scriptName}"></script>
|
|
10
|
+
${script}
|
|
12
11
|
<script>
|
|
13
12
|
const ws = new WebSocket('ws://' + location.host);
|
|
14
13
|
ws.onmessage = (msg) => {
|
|
@@ -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.2",
|
|
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",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
".": "./src/index.js"
|
|
11
11
|
},
|
|
12
12
|
"bin": {
|
|
13
|
-
"neu-cli": "
|
|
13
|
+
"neu-cli": "cli/index.js"
|
|
14
14
|
},
|
|
15
15
|
"keywords": [
|
|
16
16
|
"framework",
|
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
|
+
};
|