@bananacool467/authtics-host 0.0.2-beta.3 → 0.1.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 +28 -6
- package/dist/cli.js +3 -4
- package/dist/devPanel.js +41 -19
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/renderEngine.d.ts +1 -1
- package/dist/renderEngine.js +11 -17
- package/dist/route.d.ts +4 -1
- package/dist/route.js +47 -22
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# Authtics Host
|
|
2
2
|
|
|
3
|
-
Version 0.0
|
|
3
|
+
Version 0.1.0
|
|
4
4
|
License: MIT
|
|
5
5
|
|
|
6
|
-
**(NOTE:
|
|
6
|
+
**(NOTE: THIS IS AUTHTICS HOST'S FIRST RELEASE 🎉🎉🎉)**
|
|
7
7
|
|
|
8
8
|
Authtics Host is a framework inspired by Next.js. Authtics Host supports SSR (Server-Side Rendering) and uses esbuild.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
**(NOTE: The short name for Authtics Host is HostJS or Host.js)**
|
|
11
|
+
|
|
12
|
+
## Setting Up
|
|
11
13
|
|
|
12
14
|
To get **Authtics Host** use:
|
|
13
15
|
```bash
|
|
@@ -98,17 +100,23 @@ export default defineConfig({
|
|
|
98
100
|
|
|
99
101
|
`client/app.tsx` should look like:
|
|
100
102
|
```tsx
|
|
101
|
-
import { Route } from "@bananacool467/authtics-host";
|
|
103
|
+
import { Route, RouteMap } from "@bananacool467/authtics-host";
|
|
102
104
|
import Index from "./pages/index";
|
|
103
105
|
|
|
104
106
|
export default function App() {
|
|
105
107
|
return (
|
|
106
|
-
|
|
108
|
+
<RouteMap>
|
|
107
109
|
<Route at="/" statusCode={200} page={Index} />
|
|
108
|
-
|
|
110
|
+
<RouteMap/>
|
|
109
111
|
)
|
|
110
112
|
}
|
|
111
113
|
```
|
|
114
|
+
**(NOTE: You can change the `statusCode` to anything you want, the property is optional, it either gives 404 or 200 if the `statusCode` is missing.)**
|
|
115
|
+
**(NOTE: To make an 404 page you can use:**
|
|
116
|
+
```tsx
|
|
117
|
+
<Route statusCode={404} page={NotFound} />
|
|
118
|
+
```
|
|
119
|
+
**)**
|
|
112
120
|
|
|
113
121
|
`client/pages/index.tsx` should look like:
|
|
114
122
|
```tsx
|
|
@@ -140,6 +148,20 @@ node --experimental-strip-types index.ts
|
|
|
140
148
|
|
|
141
149
|
Then when you run your project, you will get a page just saying **"Hello, World!"** and you should see a clickable Developer Panel at the bottom-right.
|
|
142
150
|
|
|
151
|
+
## About The Developer Panel
|
|
152
|
+
|
|
153
|
+
The Developer Panel is what you could use to help you debug your code. The Developer Panel has **7 tabs**:
|
|
154
|
+
|
|
155
|
+
**Console** — Shows current logs
|
|
156
|
+
**Route** — Shows information about the current route
|
|
157
|
+
**Webpage Info** — Shows information about the website/webpage
|
|
158
|
+
**Network** — Shows the current ping and shows sent fetches from the client and shows received from the server after fetching
|
|
159
|
+
**Client Info** — Shows information about your client's User Agent, OS and FPS
|
|
160
|
+
**About Host** — Shows the current version of Authtics Host
|
|
161
|
+
**Server Info** — Shows how much heap was used and how much CPU was used on the server and how much requests there were and how much users are currently on the site and how long the site has been up
|
|
162
|
+
|
|
163
|
+
**(NOTE: If you do not set DAT restriction (Which will be explained on how to set it up in the next section), you will only get access to the Console and Network tabs)**
|
|
164
|
+
|
|
143
165
|
## Developer Panel Notes And Warnings And DAT (Developer Access Token)
|
|
144
166
|
|
|
145
167
|
A DAT (Developer Access Token) is your token that is used to access the Developer Panel.
|
package/dist/cli.js
CHANGED
|
@@ -331,7 +331,6 @@ async function start() {
|
|
|
331
331
|
config.autoPage = true;
|
|
332
332
|
if (config.autoFile == null)
|
|
333
333
|
config.autoFile = true;
|
|
334
|
-
console.log(JSON.stringify(config));
|
|
335
334
|
const PORT = config.port ?? 3000;
|
|
336
335
|
const showPanel = config.devMode !== false;
|
|
337
336
|
const autoPaging = config.autoPage !== false;
|
|
@@ -411,7 +410,7 @@ async function start() {
|
|
|
411
410
|
const htmlPath = pagesDir ? path.join(pagesDir, `${urlPath}.html`) : undefined;
|
|
412
411
|
if (tsxPath && fs.existsSync(tsxPath)) {
|
|
413
412
|
try {
|
|
414
|
-
const html = await renderPage(tsxPath, url, actuallyShowPanel, compiler, hotReload, autoPaging);
|
|
413
|
+
const html = await renderPage(tsxPath, url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""));
|
|
415
414
|
res.setHeader("Content-Type", "text/html");
|
|
416
415
|
return res.end(html);
|
|
417
416
|
}
|
|
@@ -430,7 +429,7 @@ async function start() {
|
|
|
430
429
|
const appPath = path.join(process.cwd(), appTsxPath);
|
|
431
430
|
if (fs.existsSync(appPath)) {
|
|
432
431
|
try {
|
|
433
|
-
const html = await renderPage(appPath, url, actuallyShowPanel, compiler, hotReload, autoPaging);
|
|
432
|
+
const html = await renderPage(appPath, url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""));
|
|
434
433
|
res.setHeader("Content-Type", "text/html");
|
|
435
434
|
return res.end(html);
|
|
436
435
|
}
|
|
@@ -444,7 +443,7 @@ async function start() {
|
|
|
444
443
|
const notFoundPath = path.join(__dirname, '404.js');
|
|
445
444
|
if (fs.existsSync(notFoundPath)) {
|
|
446
445
|
try {
|
|
447
|
-
let html = await renderPage(notFoundPath, url, actuallyShowPanel, compiler, hotReload, autoPaging);
|
|
446
|
+
let html = await renderPage(notFoundPath, url, actuallyShowPanel, compiler, hotReload, autoPaging, config.autoPageDir, (routes.get("APP_TSX") || ""));
|
|
448
447
|
res.setHeader("Content-Type", "text/html");
|
|
449
448
|
res.writeHead(404);
|
|
450
449
|
html = html.replace("<script>", "<script>\nwindow.__status_code__ = 404;");
|
package/dist/devPanel.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import React, { useState, useEffect, useRef } from 'react';
|
|
3
|
-
import { ShieldCheck, CircleX, ChevronDown, ChevronUp, Bug, AlertTriangle, Info, ArrowLeft, Wifi, Terminal, Route, Globe, ChevronRight, ChevronLeft, Server, TriangleAlert } from 'lucide-react';
|
|
3
|
+
import { ShieldCheck, CircleX, ChevronDown, ChevronUp, Bug, AlertTriangle, Info, ArrowLeft, Wifi, Terminal, Route, Globe, ChevronRight, ChevronLeft, Server, TriangleAlert, Eye } from 'lucide-react';
|
|
4
4
|
import Prism from 'prismjs';
|
|
5
5
|
import 'prismjs/components/prism-javascript';
|
|
6
6
|
Prism.languages.javascript['builtin'] = /\b(window|document|console|navigator|localStorage|sessionStorage|fetch|Promise|setTimeout|setInterval|clearTimeout|clearInterval|requestAnimationFrame|Math|JSON|Object|Array|String|Number|Boolean|Date|RegExp|Error|Map|Set|Symbol|Proxy|Reflect|performance|history|location|screen|crypto|indexedDB|alert|confirm|prompt)\b/;
|
|
@@ -178,6 +178,7 @@ export default function DevPanel() {
|
|
|
178
178
|
const [protocol, setProtocol] = useState(undefined);
|
|
179
179
|
const [path, setPath] = useState(undefined);
|
|
180
180
|
const [isSsr, setIsSsr] = useState(false);
|
|
181
|
+
const datRestrictionMissingRef = useRef(true);
|
|
181
182
|
const cpuUsage = useRef(0);
|
|
182
183
|
const heapUsage = useRef(0);
|
|
183
184
|
const [datMatch, setDatMatch] = useState(false);
|
|
@@ -191,6 +192,7 @@ export default function DevPanel() {
|
|
|
191
192
|
const [users, setUsers] = useState(0);
|
|
192
193
|
const [token, setToken] = useState(null);
|
|
193
194
|
const [tokenLength, setTokenLength] = useState(0);
|
|
195
|
+
const [tokenHidden, setTokenHidden] = useState(true);
|
|
194
196
|
const [consoleInputFocused, setConsoleInputFocused] = useState(false);
|
|
195
197
|
const consoleInputRef = useRef(null);
|
|
196
198
|
const overlayRef = useRef(null);
|
|
@@ -251,7 +253,6 @@ export default function DevPanel() {
|
|
|
251
253
|
setDatMatch(data.isMatching);
|
|
252
254
|
if (!data.isMatching) {
|
|
253
255
|
doc?.getElementById('authtics-dev-panel-root')?.remove();
|
|
254
|
-
return;
|
|
255
256
|
}
|
|
256
257
|
const resp2 = await fetch('/__authtics_host_apis__/get-dat-restriction-missing', {
|
|
257
258
|
method: 'POST',
|
|
@@ -261,8 +262,11 @@ export default function DevPanel() {
|
|
|
261
262
|
if (resp2.status === 200) {
|
|
262
263
|
const data = await resp2.json();
|
|
263
264
|
setDatRestrictionMissing(!data.enabled);
|
|
265
|
+
datRestrictionMissingRef.current = !data.enabled;
|
|
266
|
+
return "";
|
|
264
267
|
}
|
|
265
268
|
}
|
|
269
|
+
return "";
|
|
266
270
|
};
|
|
267
271
|
const isSSR = async () => {
|
|
268
272
|
const resp = await fetch('/__authtics_host_apis__/get-is-ssr', {
|
|
@@ -354,20 +358,22 @@ export default function DevPanel() {
|
|
|
354
358
|
};
|
|
355
359
|
setTimeout(async () => {
|
|
356
360
|
await getDatMatching();
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
361
|
+
setTimeout(() => {
|
|
362
|
+
if (datRestrictionMissingRef.current === false) {
|
|
363
|
+
getPath();
|
|
364
|
+
getCompilerType();
|
|
365
|
+
getHttpVersion();
|
|
366
|
+
isSSR();
|
|
367
|
+
userSettings();
|
|
368
|
+
getHostVersion();
|
|
369
|
+
setInterval(() => {
|
|
370
|
+
getUsage();
|
|
371
|
+
}, 250);
|
|
372
|
+
setInterval(() => {
|
|
373
|
+
update();
|
|
374
|
+
}, 500);
|
|
375
|
+
}
|
|
376
|
+
}, 250);
|
|
371
377
|
}, 250);
|
|
372
378
|
}, []);
|
|
373
379
|
const restart = async () => {
|
|
@@ -491,7 +497,7 @@ export default function DevPanel() {
|
|
|
491
497
|
catch (error) {
|
|
492
498
|
}
|
|
493
499
|
window.addEventListener('authtics:log', handler);
|
|
494
|
-
setStatus(window.__status_code__
|
|
500
|
+
setInterval(() => setStatus(window.__status_code__ || 200), 1000);
|
|
495
501
|
setPathName(window.location.pathname);
|
|
496
502
|
setHostName(window.location.hostname);
|
|
497
503
|
setDoc(document);
|
|
@@ -932,7 +938,6 @@ export default function DevPanel() {
|
|
|
932
938
|
maxHeight: '260px',
|
|
933
939
|
flexDirection: 'column',
|
|
934
940
|
}, children: [_jsxs("div", { style: {
|
|
935
|
-
display: 'flex',
|
|
936
941
|
justifyContent: 'space-between',
|
|
937
942
|
padding: '6px 10px',
|
|
938
943
|
background: '#fff',
|
|
@@ -940,7 +945,24 @@ export default function DevPanel() {
|
|
|
940
945
|
border: '1px solid #eee',
|
|
941
946
|
fontSize: '12px',
|
|
942
947
|
color: '#333',
|
|
943
|
-
|
|
948
|
+
display: 'flex'
|
|
949
|
+
}, children: [_jsx("span", { style: { fontSize: '12px', color: '#333', position: 'relative' }, children: "DAT (Developer Access Token)" }), _jsxs("div", { style: { position: 'relative',
|
|
950
|
+
display: 'inline-flex' }, children: [_jsx("span", { style: { fontSize: '12px', color: 'blue' }, children: token }), tokenHidden && (_jsxs("div", { style: {
|
|
951
|
+
position: 'absolute',
|
|
952
|
+
top: 0, left: 0,
|
|
953
|
+
marginTop: '-2.5px',
|
|
954
|
+
width: '100%', height: '100%',
|
|
955
|
+
backdropFilter: 'blur(10px)',
|
|
956
|
+
backgroundColor: 'rgba(255,255,255,0.3)',
|
|
957
|
+
cursor: 'pointer',
|
|
958
|
+
padding: '2.5px 2.5px',
|
|
959
|
+
borderRadius: '8px',
|
|
960
|
+
textAlign: 'center',
|
|
961
|
+
display: 'flex',
|
|
962
|
+
justifyContent: 'center',
|
|
963
|
+
alignItems: 'center',
|
|
964
|
+
gap: '4px'
|
|
965
|
+
}, onClick: () => setTokenHidden(false), children: [_jsx(Eye, { size: 12 }), "Click to reveal"] }))] })] }), tokenLength !== 16 ? (_jsx(_Fragment, { children: _jsxs("div", { style: {
|
|
944
966
|
display: 'flex',
|
|
945
967
|
background: '#ba4e4e',
|
|
946
968
|
borderRadius: '10px',
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineConfig } from './config.js';
|
|
2
2
|
import App from './cli.js';
|
|
3
|
-
import Route from './route.js';
|
|
4
|
-
|
|
3
|
+
import { Route } from './route.js';
|
|
4
|
+
import { RouteMap } from './route.js';
|
|
5
|
+
export { App, Route, RouteMap, defineConfig };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineConfig } from './config.js';
|
|
2
2
|
import App from './cli.js';
|
|
3
|
-
import Route from './route.js';
|
|
4
|
-
|
|
3
|
+
import { Route } from './route.js';
|
|
4
|
+
import { RouteMap } from './route.js';
|
|
5
|
+
export { App, Route, RouteMap, defineConfig };
|
package/dist/renderEngine.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export type CompilerType = 'esbuild' | 'babel';
|
|
2
2
|
export declare function findFileUpwards(startDir: string, relativePath: string): string | null;
|
|
3
|
-
export declare function renderPage(tsxPath: string, reqUrl?: string, showPanel?: boolean, compiler?: CompilerType, hotReload?: boolean, autoPaging?: boolean): Promise<string>;
|
|
3
|
+
export declare function renderPage(tsxPath: string, reqUrl?: string, showPanel?: boolean, compiler?: CompilerType, hotReload?: boolean, autoPaging?: boolean, autoPageDir?: string, appDir?: string): Promise<string>;
|
package/dist/renderEngine.js
CHANGED
|
@@ -75,26 +75,20 @@ function resolveDevPanelPath() {
|
|
|
75
75
|
return null;
|
|
76
76
|
}
|
|
77
77
|
const devPanelPath = resolveDevPanelPath();
|
|
78
|
-
function resolveTemplate(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const candidate = path.join(dir, "_template.html");
|
|
83
|
-
if (fs.existsSync(candidate))
|
|
84
|
-
return candidate;
|
|
85
|
-
if (dir === pagesDir || dir === path.dirname(pagesDir))
|
|
86
|
-
break;
|
|
87
|
-
const parent = path.dirname(dir);
|
|
88
|
-
if (parent === dir)
|
|
89
|
-
break;
|
|
90
|
-
dir = parent;
|
|
78
|
+
function resolveTemplate(appDir, autoPageDir, autoPaging) {
|
|
79
|
+
let nextToCli;
|
|
80
|
+
if (autoPaging) {
|
|
81
|
+
nextToCli = path.join(process.cwd(), autoPageDir || "", "_template.html");
|
|
91
82
|
}
|
|
92
|
-
|
|
83
|
+
else {
|
|
84
|
+
nextToCli = path.join(process.cwd(), appDir || "", "../", "_template.html");
|
|
85
|
+
}
|
|
86
|
+
return nextToCli;
|
|
93
87
|
}
|
|
94
88
|
function buildHtml(opts) {
|
|
95
89
|
const { pageHtml, clientCode, devPanelContainer, templatePath, hotReload } = opts;
|
|
96
90
|
const hmrScript = hotReload ? HMR_SCRIPT : '';
|
|
97
|
-
if (templatePath) {
|
|
91
|
+
if (templatePath && fs.existsSync(templatePath)) {
|
|
98
92
|
let template = fs.readFileSync(templatePath, "utf-8");
|
|
99
93
|
const contentTag = /<authtics_host_content\s*\/>/i;
|
|
100
94
|
if (contentTag.test(template)) {
|
|
@@ -146,13 +140,13 @@ const HMR_SCRIPT = `
|
|
|
146
140
|
})();
|
|
147
141
|
</script>
|
|
148
142
|
`;
|
|
149
|
-
export async function renderPage(tsxPath, reqUrl, showPanel = true, compiler = 'esbuild', hotReload = false, autoPaging = true) {
|
|
143
|
+
export async function renderPage(tsxPath, reqUrl, showPanel = true, compiler = 'esbuild', hotReload = false, autoPaging = true, autoPageDir, appDir) {
|
|
150
144
|
ensureTempDirs();
|
|
151
145
|
if (!fs.existsSync(tsxPath)) {
|
|
152
146
|
throw new Error(`renderPage: source file not found: ${tsxPath}`);
|
|
153
147
|
}
|
|
154
148
|
const usesDevPanel = showPanel && devPanelPath !== null;
|
|
155
|
-
const templatePath = resolveTemplate(
|
|
149
|
+
const templatePath = resolveTemplate(appDir, autoPageDir, autoPaging);
|
|
156
150
|
const tempPath = tempFileForTsx(tsxPath);
|
|
157
151
|
const serverCjsPath = tempPath.replace(/\.json$/, ".server.cjs");
|
|
158
152
|
let clientCode;
|
package/dist/route.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export
|
|
1
|
+
export declare function RouteMap({ children }: {
|
|
2
|
+
children?: React.ReactNode;
|
|
3
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
4
|
+
export declare function Route({ page, statusCode, at }: {
|
|
2
5
|
page: Function;
|
|
3
6
|
statusCode?: number;
|
|
4
7
|
at?: string;
|
package/dist/route.js
CHANGED
|
@@ -1,30 +1,55 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const [
|
|
7
|
-
const
|
|
2
|
+
import { useEffect, useState, useRef } from "react";
|
|
3
|
+
import NotFound from "./404.js";
|
|
4
|
+
export function RouteMap({ children }) {
|
|
5
|
+
const childArray = useRef(children);
|
|
6
|
+
const [path, setPath] = useState('');
|
|
7
|
+
const matched = useRef(null);
|
|
8
|
+
const [MatchedEl, setMatchedEl] = useState(null);
|
|
9
|
+
const [currentlyAwaiting, setCurrentlyAwaiting] = useState(true);
|
|
10
|
+
const [At404, setAt404] = useState(false);
|
|
8
11
|
useEffect(() => {
|
|
9
|
-
|
|
12
|
+
setPath(window.location.pathname);
|
|
10
13
|
}, []);
|
|
11
14
|
useEffect(() => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
const childrenToProcess = Array.isArray(childArray.current)
|
|
16
|
+
? childArray.current
|
|
17
|
+
: [childArray.current];
|
|
18
|
+
childrenToProcess.forEach((child) => {
|
|
19
|
+
if (child?.props?.at === path) {
|
|
20
|
+
matched.current = child.props;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
if (!matched.current) {
|
|
24
|
+
childrenToProcess.forEach((child) => {
|
|
25
|
+
if (child?.props?.at === undefined) {
|
|
26
|
+
matched.current = child.props;
|
|
27
|
+
setAt404(true);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
setCurrentlyAwaiting(false);
|
|
32
|
+
}, [path]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (matched.current && !At404) {
|
|
35
|
+
setMatchedEl(() => matched.current.page);
|
|
36
|
+
window.__status_code__ = matched.current.statusCode || 200;
|
|
37
|
+
}
|
|
38
|
+
else if (At404) {
|
|
39
|
+
setMatchedEl(() => matched.current.page);
|
|
40
|
+
window.__status_code__ = matched.current.statusCode || 404;
|
|
17
41
|
}
|
|
18
42
|
else {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
43
|
+
setMatchedEl(() => NotFound);
|
|
44
|
+
window.__status_code__ = 404;
|
|
25
45
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
46
|
+
}, [path]);
|
|
47
|
+
if (matched.current && MatchedEl)
|
|
48
|
+
return _jsx(MatchedEl, {});
|
|
49
|
+
if (currentlyAwaiting)
|
|
50
|
+
return _jsx(_Fragment, {});
|
|
51
|
+
return _jsx(NotFound, {});
|
|
52
|
+
}
|
|
53
|
+
export function Route({ page, statusCode, at }) {
|
|
54
|
+
return (_jsx(_Fragment, {}));
|
|
30
55
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananacool467/authtics-host",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "A Next.js-inspired SSR framework for Node.js with TypeScript and JSX support.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
"lightweight",
|
|
36
36
|
"hot-reload",
|
|
37
37
|
"dev",
|
|
38
|
-
"developer"
|
|
38
|
+
"developer",
|
|
39
|
+
"HostJS"
|
|
39
40
|
],
|
|
40
41
|
"author": "bananacool467",
|
|
41
42
|
"license": "MIT",
|