@package-verse/esmpack 1.0.2 → 1.0.4
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/.vscode/settings.json +1 -0
- package/.vscode/tasks.json +4 -8
- package/README.md +13 -0
- package/init.js +30 -0
- package/package.json +22 -2
- package/postcss.config.cjs +18 -0
- package/serve.js +1 -0
- package/src/ProcessArgs.ts +17 -0
- package/src/core/RegExpExtra.ts +14 -0
- package/src/core/tokenizeAll.ts +40 -0
- package/src/pack/FilePacker.ts +20 -0
- package/src/pack/pack.ts +0 -0
- package/src/serve/WebServer.ts +63 -0
- package/src/serve/send/packageInfo.ts +7 -0
- package/src/serve/send/sendCSSJS.ts +22 -0
- package/src/serve/send/sendJS.ts +55 -0
- package/src/serve/send/sendJSHost.ts +26 -0
- package/src/serve/send/sendList.ts +99 -0
- package/src/serve/send/sendLocalFile.ts +36 -0
- package/src/serve/serve.ts +146 -0
- package/src/test/TestView.global.css +4 -0
- package/src/test/TestView.local.css +3 -0
- package/src/test/TestView.ts +35 -0
- package/tsconfig.json +2 -1
package/.vscode/settings.json
CHANGED
package/.vscode/tasks.json
CHANGED
|
@@ -34,15 +34,11 @@
|
|
|
34
34
|
{
|
|
35
35
|
"label": "Web Atoms Dev Server",
|
|
36
36
|
"type": "shell",
|
|
37
|
-
"command": "
|
|
37
|
+
"command": "npm",
|
|
38
38
|
"args": [
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"https://v2025-test.tas800.com"
|
|
43
|
-
//"http://localhost:9991"
|
|
44
|
-
],
|
|
45
|
-
"problemMatcher": []
|
|
39
|
+
"run",
|
|
40
|
+
"test-server"
|
|
41
|
+
]
|
|
46
42
|
}
|
|
47
43
|
]
|
|
48
44
|
}
|
package/README.md
CHANGED
|
@@ -8,3 +8,16 @@ Because, there is no simple packer that just rewrites module paths. After ES6 an
|
|
|
8
8
|
1. Creates a single pack JS file which only has import definition of all the imports.
|
|
9
9
|
2. Packed file strips css and delivers separate css as combined CSS.
|
|
10
10
|
3. Retains ESM source code as it is except import path, import path is rewritten to fully qualified CDN url. So caching is preserved over different main module versions but same dependencies.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Dev Packer
|
|
14
|
+
|
|
15
|
+
1. Development time packer will generate HTML file along with the pack that will generate import maps along with the loading of control and hosting it.
|
|
16
|
+
2. Dev Packer will generate `let cs = document.currentScript;import("imported-path").then((r) => ESMPack.render(r, cs))` script inside html for every JS's corresponding html.
|
|
17
|
+
3. Library author must implement `ESMPack.render` method which will accept exports from imported method and `currentScript`.
|
|
18
|
+
4. Every `js` file's imports will be changed to fully qualified references for external imports.
|
|
19
|
+
|
|
20
|
+
## Release Packer
|
|
21
|
+
|
|
22
|
+
1. A single script with `.pack.js` will be generated that will import every nested imports along with fully qualified path for every nested imported external references.
|
|
23
|
+
2. Every `js` file's imports will be changed to fully qualified references for external imports.
|
package/init.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const ESMPack = window.ESMPack ||= {};
|
|
2
|
+
ESMPack.installed ||= new Set();
|
|
3
|
+
|
|
4
|
+
ESMPack.markAsInstalled ||= (url) => ESMPack.installed.add(url);
|
|
5
|
+
|
|
6
|
+
ESMPack.installStyleSheet ||= (url) => {
|
|
7
|
+
|
|
8
|
+
const installCss = (url) => {
|
|
9
|
+
|
|
10
|
+
const link = document.createElement("link");
|
|
11
|
+
link.rel = "stylesheet";
|
|
12
|
+
link.href = url;
|
|
13
|
+
document.head.insertAdjacentElement(
|
|
14
|
+
/\.global\./i.test(url)
|
|
15
|
+
? "afterbegin"
|
|
16
|
+
: "beforeend", link);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
if (ESMPack.installed.has(url)) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
ESMPack.installed.add(url);
|
|
23
|
+
if(document.readyState === "complete") {
|
|
24
|
+
installCss(url);
|
|
25
|
+
} else {
|
|
26
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
27
|
+
installCss(url);
|
|
28
|
+
}, { once: true });
|
|
29
|
+
}
|
|
30
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@package-verse/esmpack",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "ESM Pack Packer and Web Server with PostCSS and ESM Loader",
|
|
5
5
|
"homepage": "https://github.com/package-verse/esmpack#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -16,9 +16,29 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"test": "exit 0",
|
|
18
18
|
"push": "git add -A && git commit -m \"dep vb\" && npm version patch",
|
|
19
|
+
"build-css-watch": "npx postcss \"./src/**/*.css\" --base src --dir dist --map --verbose -w",
|
|
20
|
+
"test-server": "node --enable-source-maps ./serve.js",
|
|
19
21
|
"postversion": "git push --follow-tags"
|
|
20
22
|
},
|
|
21
23
|
"devDependencies": {
|
|
22
|
-
"@types/node": "^25.5.0"
|
|
24
|
+
"@types/node": "^25.5.0",
|
|
25
|
+
"@types/ws": "^8.18.1",
|
|
26
|
+
"cssnano": "^7.0.6",
|
|
27
|
+
"postcss": "^8.5.3",
|
|
28
|
+
"postcss-cli": "^11.0.1",
|
|
29
|
+
"postcss-import": "^16.1.0",
|
|
30
|
+
"postcss-import-ext-glob": "^2.1.1",
|
|
31
|
+
"postcss-import-styled-js": "^1.0.23",
|
|
32
|
+
"postcss-nested": "^7.0.2",
|
|
33
|
+
"postcss-preset-env": "^10.1.5",
|
|
34
|
+
"postcss-url": "^10.1.3"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@web-atoms/date-time": "^3.0.3",
|
|
38
|
+
"colors": "^1.4.0",
|
|
39
|
+
"http-proxy-middleware": "^3.0.5",
|
|
40
|
+
"mime": "^4.1.0",
|
|
41
|
+
"portfinder": "^1.0.38",
|
|
42
|
+
"ws": "^8.19.0"
|
|
23
43
|
}
|
|
24
44
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module.exports = (ctx) => ({
|
|
2
|
+
map: { ... ctx.options.map, sourcesContent: false },
|
|
3
|
+
plugins: [
|
|
4
|
+
require("postcss-import-styled-js")(),
|
|
5
|
+
require('postcss-preset-env')({
|
|
6
|
+
stage: 4
|
|
7
|
+
}),
|
|
8
|
+
require("postcss-import-ext-glob")(),
|
|
9
|
+
require("postcss-import")(),
|
|
10
|
+
require("postcss-url")({
|
|
11
|
+
url: "rebase",
|
|
12
|
+
basePath: ctx.options.base,
|
|
13
|
+
assetsPath: ctx.options.dist
|
|
14
|
+
}),
|
|
15
|
+
require("postcss-nested")(),
|
|
16
|
+
require("cssnano")()
|
|
17
|
+
]
|
|
18
|
+
});
|
package/serve.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "./dist/serve/serve.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const ProcessOptions = {
|
|
2
|
+
cwd: ""
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
const { argv } = process;
|
|
6
|
+
const { length } = argv;
|
|
7
|
+
for(let i=0; i< length;i++) {
|
|
8
|
+
const arg = argv[i];
|
|
9
|
+
if (arg.startsWith("--")) {
|
|
10
|
+
ProcessOptions[arg] = argv[i+1] || void 0;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const last = argv[argv.length-1];
|
|
15
|
+
export const fileArgument = last.startsWith("--") ? void 0 : last;
|
|
16
|
+
|
|
17
|
+
ProcessOptions.cwd ||= process.cwd();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { tokenizeRegexAll } from "./tokenizeAll.js";
|
|
2
|
+
|
|
3
|
+
export type regExpTokens = { text: string, match: RegExpMatchArray };
|
|
4
|
+
|
|
5
|
+
export const RegExpExtra = {
|
|
6
|
+
|
|
7
|
+
replaceAll(text: string, regExp: RegExp, transformer: (item: regExpTokens) => string) {
|
|
8
|
+
let r = "";
|
|
9
|
+
for(const token of tokenizeRegexAll(text, regExp)) {
|
|
10
|
+
r += transformer(token as any);
|
|
11
|
+
}
|
|
12
|
+
return r;
|
|
13
|
+
},
|
|
14
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export function *tokenizeRegexAll(text: string, regex: RegExp) {
|
|
2
|
+
if (!text) {
|
|
3
|
+
return;
|
|
4
|
+
}
|
|
5
|
+
regex.lastIndex = 0;
|
|
6
|
+
let start = 0;
|
|
7
|
+
for(const m of text.matchAll(regex)) {
|
|
8
|
+
if (start < m.index) {
|
|
9
|
+
yield { text: text.substring(start, m.index)};
|
|
10
|
+
}
|
|
11
|
+
yield { match: m };
|
|
12
|
+
start = m.index + m[0].length;
|
|
13
|
+
}
|
|
14
|
+
if (start < text.length) {
|
|
15
|
+
if (start) {
|
|
16
|
+
yield { text: text.substring(start) };
|
|
17
|
+
} else {
|
|
18
|
+
yield { text };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// let m;
|
|
22
|
+
// let start = 0;
|
|
23
|
+
// while((m = regex.exec(text)) !== null) {
|
|
24
|
+
// const { lastIndex } = regex;
|
|
25
|
+
// if (m.index === lastIndex) {
|
|
26
|
+
// regex.lastIndex++;
|
|
27
|
+
// }
|
|
28
|
+
// if (!m?.length) {
|
|
29
|
+
// break;
|
|
30
|
+
// }
|
|
31
|
+
// if (start < m.index) {
|
|
32
|
+
// yield { text: text.substring(start, m.index)};
|
|
33
|
+
// }
|
|
34
|
+
// yield { match: m as RegExpExecArray };
|
|
35
|
+
// start = m.index + m[0].length;
|
|
36
|
+
// }
|
|
37
|
+
// if (start < text.length) {
|
|
38
|
+
// yield { text: text.substring(start) };
|
|
39
|
+
// }
|
|
40
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Packer must do following tasks...
|
|
3
|
+
* 1. Visit every JavaScript file
|
|
4
|
+
* 2. Change all imports, redirect CSS imports as css.js file that will include CSS on the page ...
|
|
5
|
+
* 3. Create .pack.js with all nested imports if file imports `@web-atoms/core/dist/Pack`
|
|
6
|
+
* 4. Packed file must set ignore for all imported CSS so dynamically loaded CSS can still be imported
|
|
7
|
+
*/
|
|
8
|
+
export default class FilePacker {
|
|
9
|
+
|
|
10
|
+
constructor(
|
|
11
|
+
public readonly root: string
|
|
12
|
+
) {
|
|
13
|
+
// empty
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async pack() {
|
|
17
|
+
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
}
|
package/src/pack/pack.ts
ADDED
|
File without changes
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from "http";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileArgument, ProcessOptions } from "../ProcessArgs.js";
|
|
4
|
+
import { existsSync } from "fs";
|
|
5
|
+
import sendLocalFile from "./send/sendLocalFile.js";
|
|
6
|
+
import { createProxyMiddleware, fixRequestBody } from "http-proxy-middleware";
|
|
7
|
+
import colors from "colors";
|
|
8
|
+
import sendJSHost from "./send/sendJSHost.js";
|
|
9
|
+
import sendCSSJS from "./send/sendCSSJS.js";
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
let middleware;
|
|
13
|
+
|
|
14
|
+
export default function WebServer(req: IncomingMessage, res: ServerResponse) {
|
|
15
|
+
const pathname = new URL(req.url, "http://a").pathname.substring(1);
|
|
16
|
+
|
|
17
|
+
// check if path exists...
|
|
18
|
+
const fullPath = path.resolve(ProcessOptions.cwd, pathname);
|
|
19
|
+
if (existsSync(fullPath)) {
|
|
20
|
+
// server local..
|
|
21
|
+
sendLocalFile(pathname, fullPath, req, res);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const css = fullPath.replace(/\.js$/, "");
|
|
26
|
+
if (existsSync(css)) {
|
|
27
|
+
sendCSSJS(pathname.substring(0, pathname.length - 3), req, res);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const js = fullPath.replace(/\.html$/,".js");
|
|
32
|
+
if(existsSync(js)) {
|
|
33
|
+
sendJSHost(pathname.replace(/\.html$/, ".js"), req, res);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// proxy...
|
|
38
|
+
middleware ??= createProxyMiddleware({
|
|
39
|
+
target: fileArgument,
|
|
40
|
+
changeOrigin: true,
|
|
41
|
+
ws: true,
|
|
42
|
+
secure: false,
|
|
43
|
+
cookieDomainRewrite: "",
|
|
44
|
+
on: {
|
|
45
|
+
proxyReq: fixRequestBody,
|
|
46
|
+
proxyRes:(proxyReq, req, res) => {
|
|
47
|
+
if (proxyReq.statusCode >= 400) {
|
|
48
|
+
console.error(colors.red(`HTTP STATUS ${proxyReq.statusCode} for ${fileArgument}${req.url}`));
|
|
49
|
+
} else if (proxyReq.statusCode >= 300) {
|
|
50
|
+
console.warn(colors.yellow(
|
|
51
|
+
`HTTP STATUS ${proxyReq.statusCode} for ${fileArgument}${req.url}`));
|
|
52
|
+
}
|
|
53
|
+
let cookie = proxyReq.headers["set-cookie"];
|
|
54
|
+
if (cookie) {
|
|
55
|
+
cookie = cookie.map((s) => s.split(";").filter((c) => c.trim().toLocaleLowerCase() !== "secure").join(";"));
|
|
56
|
+
proxyReq.headers["set-cookie"] = cookie;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
middleware(req, res);
|
|
63
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { IncomingMessage, ServerResponse } from "node:http";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
export default function sendCSSJS(path: string, req: IncomingMessage, res: ServerResponse) {
|
|
6
|
+
|
|
7
|
+
const init = fileURLToPath(import.meta.resolve("../../../init.js"));
|
|
8
|
+
const initText = readFileSync(init, "utf-8");
|
|
9
|
+
|
|
10
|
+
const text = `
|
|
11
|
+
(function (link) {
|
|
12
|
+
${initText};
|
|
13
|
+
ESMPack.installStyleSheet(link);
|
|
14
|
+
}("/${path}"))
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
res.writeHead(200, {
|
|
18
|
+
"content-type": "text/javascript",
|
|
19
|
+
"cache-control": "no-cache"
|
|
20
|
+
});
|
|
21
|
+
res.end(text);
|
|
22
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { IncomingMessage, ServerResponse } from "node:http";
|
|
3
|
+
import { RegExpExtra } from "../../core/RegExpExtra.js";
|
|
4
|
+
|
|
5
|
+
export default function sendJS(path: string, req: IncomingMessage, res: ServerResponse) {
|
|
6
|
+
let text = readFileSync(path, "utf-8");
|
|
7
|
+
|
|
8
|
+
// change references....
|
|
9
|
+
// text = text.replace(/from\s*\"([^\.][^\"]+)\"/gm, `from "/node_modules/$1"`);
|
|
10
|
+
|
|
11
|
+
// remap CSS
|
|
12
|
+
text = RegExpExtra.replaceAll(text, /from\s*\"([^\"]+)\"/gm, (
|
|
13
|
+
{ text, match }
|
|
14
|
+
) => {
|
|
15
|
+
if (text) {
|
|
16
|
+
return text;
|
|
17
|
+
}
|
|
18
|
+
const [matched, g] = match;
|
|
19
|
+
if (g.endsWith(".css")) {
|
|
20
|
+
// we need to find source...
|
|
21
|
+
return `from "/node_modules/${g}.js"`;
|
|
22
|
+
}
|
|
23
|
+
if (g.startsWith(".")) {
|
|
24
|
+
return matched;
|
|
25
|
+
}
|
|
26
|
+
return `from "/node_modules/${g}"`;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
text = RegExpExtra.replaceAll(text, /import\s*\"([^\"]+)\"/gm, (
|
|
30
|
+
{ text, match }
|
|
31
|
+
) => {
|
|
32
|
+
if (text) {
|
|
33
|
+
return text;
|
|
34
|
+
}
|
|
35
|
+
const [matched, g] = match;
|
|
36
|
+
if (g.endsWith(".css")) {
|
|
37
|
+
// we need to find source...
|
|
38
|
+
let cssPath = g;
|
|
39
|
+
if (!cssPath.startsWith(".")) {
|
|
40
|
+
cssPath = "/node_modules/" + cssPath;
|
|
41
|
+
}
|
|
42
|
+
return `import "${cssPath}.js"`;
|
|
43
|
+
}
|
|
44
|
+
if (g.startsWith(".")) {
|
|
45
|
+
return matched;
|
|
46
|
+
}
|
|
47
|
+
return `import "/node_modules/${g}"`;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
res.writeHead(200, {
|
|
51
|
+
"content-type": "text/javascript",
|
|
52
|
+
"cache-control": "no-cache"
|
|
53
|
+
});
|
|
54
|
+
res.end(text);
|
|
55
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
|
|
3
|
+
export default function sendJSHost(path: string, req: IncomingMessage, res: ServerResponse) {
|
|
4
|
+
const text = `
|
|
5
|
+
<!DOCTYPE html>
|
|
6
|
+
<html lang="en">
|
|
7
|
+
<head>
|
|
8
|
+
<meta charset="UTF-8" />
|
|
9
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
10
|
+
<title>Index of ${path}</title>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<script>
|
|
14
|
+
const cs = document.currentScript;
|
|
15
|
+
import("/${path}").then((r) => ESMPack.render(r, cs), (error) => cs.replaceWith(document.createTextNode(error.stack || error)));
|
|
16
|
+
</script>
|
|
17
|
+
</body>
|
|
18
|
+
</html>
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
res.writeHead(200, {
|
|
22
|
+
"content-type": "text/html",
|
|
23
|
+
"cache-control": "no-cache"
|
|
24
|
+
});
|
|
25
|
+
res.end(text);
|
|
26
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { IncomingMessage, ServerResponse } from "node:http";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
export default function sendList(reqPath, path: string, req: IncomingMessage, res: ServerResponse) {
|
|
6
|
+
|
|
7
|
+
const entries = readdirSync(path, { withFileTypes: true})
|
|
8
|
+
.filter((d) => !d.name.startsWith(".") && d.name !== "node_modules")
|
|
9
|
+
.map((d) => {
|
|
10
|
+
const fullPath = join(d.parentPath, d.name);
|
|
11
|
+
let size = 0, mtime = new Date();
|
|
12
|
+
try {
|
|
13
|
+
const s = statSync(fullPath);
|
|
14
|
+
size = s.size;
|
|
15
|
+
mtime = s.mtime;
|
|
16
|
+
} catch {}
|
|
17
|
+
return { name: d.name, isDir: d.isDirectory(), size, mtime };
|
|
18
|
+
})
|
|
19
|
+
.sort((a, b) => {
|
|
20
|
+
if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
|
|
21
|
+
return a.name.localeCompare(b.name);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
const text = renderDirectoryListing(reqPath, entries);
|
|
26
|
+
|
|
27
|
+
res.writeHead(200, {
|
|
28
|
+
"Content-Type": "text/html",
|
|
29
|
+
"cache-control": "no-cache"
|
|
30
|
+
});
|
|
31
|
+
res.end(text);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function formatSize(bytes) {
|
|
35
|
+
if (bytes === 0) return "0 B";
|
|
36
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
37
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
38
|
+
return `${(bytes / Math.pow(1024, i)).toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function renderDirectoryListing(reqPath, entries: { isDir: boolean, name: string, size: number, mtime: Date } []) {
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
const isRoot = reqPath === "/";
|
|
45
|
+
const rows = entries
|
|
46
|
+
.map((e) => {
|
|
47
|
+
let href = reqPath.replace(/\/$/, "") + "/" + encodeURIComponent(e.name) + (e.isDir ? "/" : "");
|
|
48
|
+
const icon = e.isDir ? "📁" : (/\.(js|html)$/i.test(e.name) ? "🌐" : "📄");
|
|
49
|
+
const size = e.isDir ? "—" : formatSize(e.size);
|
|
50
|
+
const modified = e.mtime.toISOString().replace("T", " ").slice(0, 19);
|
|
51
|
+
|
|
52
|
+
if (e.name.endsWith(".js")) {
|
|
53
|
+
href = href.replace(/\.js$/, ".html");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return `<tr>
|
|
57
|
+
<td>${icon} <a href="${href.startsWith("/") ? href : "/" + href}">${e.name}${e.isDir ? "/" : ""}</a></td>
|
|
58
|
+
<td>${size}</td>
|
|
59
|
+
<td>${modified}</td>
|
|
60
|
+
</tr>`;
|
|
61
|
+
})
|
|
62
|
+
.join("\n");
|
|
63
|
+
|
|
64
|
+
const parentRow = isRoot
|
|
65
|
+
? ""
|
|
66
|
+
: `<tr><td>⬆️ <a href="../">../</a></td><td>—</td><td>—</td></tr>`;
|
|
67
|
+
|
|
68
|
+
return `<!DOCTYPE html>
|
|
69
|
+
<html lang="en">
|
|
70
|
+
<head>
|
|
71
|
+
<meta charset="UTF-8" />
|
|
72
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
73
|
+
<title>Index of ${reqPath}</title>
|
|
74
|
+
<style>
|
|
75
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
76
|
+
body { font-family: ui-monospace, 'Cascadia Code', monospace; background: #0f1117; color: #e2e8f0; min-height: 100vh; padding: 2rem; }
|
|
77
|
+
h1 { font-size: 1.25rem; color: #7dd3fc; margin-bottom: 1.5rem; border-bottom: 1px solid #1e293b; padding-bottom: .75rem; }
|
|
78
|
+
table { width: 100%; border-collapse: collapse; }
|
|
79
|
+
th { text-align: left; padding: .5rem .75rem; color: #94a3b8; font-weight: 600; font-size: .8rem; text-transform: uppercase; letter-spacing: .05em; border-bottom: 1px solid #1e293b; }
|
|
80
|
+
td { padding: .45rem .75rem; font-size: .9rem; border-bottom: 1px solid #0d1520; }
|
|
81
|
+
td:nth-child(2), td:nth-child(3) { color: #64748b; font-size: .8rem; white-space: nowrap; }
|
|
82
|
+
tr:hover td { background: #1e293b; }
|
|
83
|
+
a { color: #7dd3fc; text-decoration: none; }
|
|
84
|
+
a:hover { color: #bae6fd; text-decoration: underline; }
|
|
85
|
+
footer { margin-top: 2rem; color: #334155; font-size: .75rem; }
|
|
86
|
+
</style>
|
|
87
|
+
</head>
|
|
88
|
+
<body>
|
|
89
|
+
<h1>📂 Index of ${reqPath}</h1>
|
|
90
|
+
<table>
|
|
91
|
+
<thead><tr><th>Name</th><th>Size</th><th>Modified</th></tr></thead>
|
|
92
|
+
<tbody>
|
|
93
|
+
${parentRow}
|
|
94
|
+
${rows}
|
|
95
|
+
</tbody>
|
|
96
|
+
</table>
|
|
97
|
+
</body>
|
|
98
|
+
</html>`;
|
|
99
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createReadStream, statSync } from "node:fs";
|
|
2
|
+
import { IncomingMessage, ServerResponse } from "node:http";
|
|
3
|
+
import mime from "mime";
|
|
4
|
+
import sendList from "./sendList.js";
|
|
5
|
+
import sendJS from "./sendJS.js";
|
|
6
|
+
export default function sendLocalFile(reqPath: string, path: string, req: IncomingMessage, res: ServerResponse) {
|
|
7
|
+
|
|
8
|
+
if (path.endsWith(".js")) {
|
|
9
|
+
// send JS
|
|
10
|
+
return sendJS(path, req, res);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// check if it is a folder...
|
|
14
|
+
const stat = statSync(path);
|
|
15
|
+
if (stat.isDirectory()) {
|
|
16
|
+
// list...
|
|
17
|
+
return sendList(reqPath, path, req, res);
|
|
18
|
+
}
|
|
19
|
+
// just pipe the file...
|
|
20
|
+
sendFile(path, res);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function sendFile(filePath, res) {
|
|
24
|
+
const stream = createReadStream(filePath);
|
|
25
|
+
stream.on("error", (err) => {
|
|
26
|
+
if (!res.headersSent) {
|
|
27
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
28
|
+
res.end(`500 Internal Server Error\n\n${err.message}`);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
res.writeHead(200, {
|
|
32
|
+
"Content-Type": mime.getType(filePath),
|
|
33
|
+
"cache-control": "no-cache"
|
|
34
|
+
});
|
|
35
|
+
stream.pipe(res);
|
|
36
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import app from "./WebServer.js";
|
|
2
|
+
import portfinder from "portfinder";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import colors from "colors";
|
|
5
|
+
import http from "http";
|
|
6
|
+
import https from "https";
|
|
7
|
+
import url from 'url';
|
|
8
|
+
import { WebSocketServer } from "ws";
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
var netFaces = os.networkInterfaces();
|
|
11
|
+
|
|
12
|
+
function createCert() {
|
|
13
|
+
|
|
14
|
+
var certPath = "./generated-cert-1";
|
|
15
|
+
|
|
16
|
+
if(fs.existsSync(certPath)) {
|
|
17
|
+
return JSON.parse(fs.readFileSync(certPath, { encoding: "utf8", flag: "r" }));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
var forge = require('node-forge');
|
|
21
|
+
var pki = forge.pki;
|
|
22
|
+
|
|
23
|
+
// generate a key pair or use one you have already
|
|
24
|
+
var keys = pki.rsa.generateKeyPair(2048);
|
|
25
|
+
|
|
26
|
+
// create a new certificate
|
|
27
|
+
var cert = pki.createCertificate();
|
|
28
|
+
|
|
29
|
+
// fill the required fields
|
|
30
|
+
cert.publicKey = keys.publicKey;
|
|
31
|
+
cert.serialNumber = '01';
|
|
32
|
+
cert.validity.notBefore = new Date();
|
|
33
|
+
cert.validity.notAfter = new Date();
|
|
34
|
+
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 20);
|
|
35
|
+
|
|
36
|
+
const altNames = [{ type: 7, ip: "127.0.0.1"}];
|
|
37
|
+
|
|
38
|
+
for(let i=1;i<255;i++) {
|
|
39
|
+
altNames.push({ type: 7, ip: `192.168.0.${i}`});
|
|
40
|
+
altNames.push({ type: 7, ip: `192.168.1.${i}`});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// use your own attributes here, or supply a csr (check the docs)
|
|
44
|
+
var attrs = [{
|
|
45
|
+
name: 'commonName',
|
|
46
|
+
value: 'dev.web-atoms.in'
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'countryName',
|
|
50
|
+
value: 'IN'
|
|
51
|
+
}, {
|
|
52
|
+
shortName: 'ST',
|
|
53
|
+
value: 'Maharashtra'
|
|
54
|
+
}, {
|
|
55
|
+
name: 'localityName',
|
|
56
|
+
value: 'Navi Mumbai'
|
|
57
|
+
}, {
|
|
58
|
+
name: 'organizationName',
|
|
59
|
+
value: 'NeuroSpeech Technologies Pvt Ltd'
|
|
60
|
+
}, {
|
|
61
|
+
shortName: 'OU',
|
|
62
|
+
value: 'Test'
|
|
63
|
+
}];
|
|
64
|
+
|
|
65
|
+
// here we set subject and issuer as the same one
|
|
66
|
+
cert.setSubject(attrs);
|
|
67
|
+
cert.setIssuer(attrs);
|
|
68
|
+
cert.setExtensions([{
|
|
69
|
+
name: "subjectAltName",
|
|
70
|
+
altNames
|
|
71
|
+
}]);
|
|
72
|
+
|
|
73
|
+
// the actual certificate signing
|
|
74
|
+
cert.sign(keys.privateKey);
|
|
75
|
+
|
|
76
|
+
// now convert the Forge certificate to PEM format
|
|
77
|
+
var pem = pki.certificateToPem(cert);
|
|
78
|
+
var pkey = pki.privateKeyToPem(keys.privateKey)
|
|
79
|
+
var c = { key: pkey, cert: pem };
|
|
80
|
+
|
|
81
|
+
fs.writeFileSync( certPath, JSON.stringify(c), "utf8");
|
|
82
|
+
return c;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function listen(port, ssl?) {
|
|
86
|
+
|
|
87
|
+
var server = ssl ? https.createServer(createCert(), app) : http.createServer(app);
|
|
88
|
+
|
|
89
|
+
var wss = new WebSocketServer({ noServer: true });
|
|
90
|
+
|
|
91
|
+
const dss = new WebSocketServer({ noServer: true });
|
|
92
|
+
|
|
93
|
+
server.on("error", (error) => {
|
|
94
|
+
console.error(error);
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
server.on("upgrade", function upgrade(request, socket, head) {
|
|
98
|
+
const pathname = url.parse(request.url).pathname;
|
|
99
|
+
|
|
100
|
+
if (pathname === "/__debug" || pathname.startsWith('/__debug/')) {
|
|
101
|
+
dss.handleUpgrade(request, socket, head, function done(ws) {
|
|
102
|
+
dss.emit('connection', ws, request);
|
|
103
|
+
});
|
|
104
|
+
} else if (pathname === '/__listen') {
|
|
105
|
+
wss.handleUpgrade(request, socket, head, function done(ws) {
|
|
106
|
+
wss.emit('connection', ws, request);
|
|
107
|
+
});
|
|
108
|
+
} else {
|
|
109
|
+
console.error("Forwarding further.. " + pathname);
|
|
110
|
+
// socket.destroy();
|
|
111
|
+
socket.on("error", (error) => console.error(error));
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
server.listen(port,() => {
|
|
116
|
+
Object.keys(netFaces).forEach(function (dev) {
|
|
117
|
+
netFaces[dev].forEach(function (details) {
|
|
118
|
+
if (details.family === 'IPv4') {
|
|
119
|
+
if (ssl) {
|
|
120
|
+
console.log((' https://' + details.address + ':' + colors.cyan(port.toString())));
|
|
121
|
+
} else {
|
|
122
|
+
console.log((' http://' + details.address + ':' + colors.cyan(port.toString())));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
return console.log(colors.green("Server has started "));
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
portfinder.basePort = 8080;
|
|
134
|
+
portfinder.getPort(function (err, port) {
|
|
135
|
+
if (err) {
|
|
136
|
+
throw err;
|
|
137
|
+
}
|
|
138
|
+
listen(port);
|
|
139
|
+
});
|
|
140
|
+
portfinder.getPort(function (err, port) {
|
|
141
|
+
if (err) {
|
|
142
|
+
throw err;
|
|
143
|
+
}
|
|
144
|
+
listen(port, true);
|
|
145
|
+
});
|
|
146
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import DateTime from "@web-atoms/date-time/dist/DateTime.js";
|
|
2
|
+
|
|
3
|
+
// irrespective of loading order
|
|
4
|
+
// global must be loaded first and
|
|
5
|
+
// local must override the global style
|
|
6
|
+
import "./TestView.local.css";
|
|
7
|
+
|
|
8
|
+
import "./TestView.global.css";
|
|
9
|
+
|
|
10
|
+
export default class DateView extends HTMLElement {
|
|
11
|
+
|
|
12
|
+
connectedCallback() {
|
|
13
|
+
setInterval(() => {
|
|
14
|
+
const now = DateTime.now;
|
|
15
|
+
this.textContent = now.toJSON();
|
|
16
|
+
}, 1000);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
customElements.define("date-view", DateView);
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
(window as any).ESMPack = {
|
|
25
|
+
async render(imports: { default: any }, cs: HTMLScriptElement) {
|
|
26
|
+
const view = new imports.default();
|
|
27
|
+
cs.replaceWith(view);
|
|
28
|
+
},
|
|
29
|
+
installStyleSheet(src) {
|
|
30
|
+
const link = document.createElement("link");
|
|
31
|
+
link.rel = "stylesheet";
|
|
32
|
+
link.href = src;
|
|
33
|
+
document.head.appendChild(link);
|
|
34
|
+
}
|
|
35
|
+
};
|