xpine 0.0.4 → 0.0.6
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/dist/index.js +658 -0
- package/dist/src/build/dev-server.js +10 -0
- package/dist/src/build/spa.js +152 -0
- package/dist/src/scripts/xpine-build.js +420 -0
- package/dist/src/scripts/xpine-dev.js +481 -0
- package/dist/src/static/dev-server.js +9 -0
- package/{src/static/spa.mjs → dist/src/static/spa.js} +34 -58
- package/package.json +1 -1
- package/src/auth.ts +0 -40
- package/src/build/typescript-builder.ts +0 -180
- package/src/express.ts +0 -120
- package/src/runDevServer.ts +0 -64
- package/src/scripts/build-module.ts +0 -36
- package/src/scripts/build.ts +0 -274
- package/src/scripts/xpine-build.ts +0 -5
- package/src/scripts/xpine-dev.ts +0 -5
- package/src/static/dev-server.mjs +0 -9
- package/src/util/env.ts +0 -7
- package/src/util/get-config.ts +0 -65
- package/src/util/html.ts +0 -32
- package/src/util/require.ts +0 -5
|
@@ -1,114 +1,102 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
1
|
+
// src/static/spa.mjs
|
|
3
2
|
async function replaceDocumentContentsWithLinkResponse() {
|
|
4
|
-
const links = [...document.querySelectorAll(
|
|
3
|
+
const links = [...document.querySelectorAll("[data-spa]")];
|
|
5
4
|
for (const link of links) {
|
|
6
|
-
link.addEventListener(
|
|
5
|
+
link.addEventListener("click", updatePageOnLinkClick);
|
|
7
6
|
}
|
|
8
|
-
|
|
9
7
|
}
|
|
10
|
-
|
|
11
8
|
async function updatePageOnLinkClick(e) {
|
|
12
|
-
const targetHref = e?.target?.getAttribute(
|
|
9
|
+
const targetHref = e?.target?.getAttribute("href");
|
|
13
10
|
if (!targetHref) return;
|
|
14
|
-
if (isExternalURL(targetHref) && !e?.target?.getAttribute(
|
|
11
|
+
if (isExternalURL(targetHref) && !e?.target?.getAttribute("data-spa-crossorigin")) return;
|
|
15
12
|
e.preventDefault();
|
|
16
13
|
try {
|
|
17
14
|
await getNewPageContent(targetHref);
|
|
18
15
|
if (targetHref === window.location.pathname) return;
|
|
19
16
|
window.history.pushState(
|
|
20
17
|
{
|
|
21
|
-
type:
|
|
22
|
-
targetHref
|
|
18
|
+
type: "link-click",
|
|
19
|
+
targetHref
|
|
23
20
|
},
|
|
24
|
-
|
|
21
|
+
"",
|
|
25
22
|
targetHref
|
|
26
23
|
);
|
|
27
|
-
const event = new CustomEvent(
|
|
24
|
+
const event = new CustomEvent("spa:updatePageURL", {
|
|
28
25
|
detail: {
|
|
29
|
-
href: targetHref
|
|
30
|
-
}
|
|
26
|
+
href: targetHref
|
|
27
|
+
}
|
|
31
28
|
});
|
|
32
29
|
window.dispatchEvent(event);
|
|
33
30
|
} catch (err) {
|
|
34
31
|
console.error(err);
|
|
35
32
|
}
|
|
36
33
|
}
|
|
37
|
-
|
|
38
34
|
async function getNewPageContent(href) {
|
|
39
35
|
try {
|
|
40
36
|
const res = await fetch(href);
|
|
41
37
|
const text = await res.text();
|
|
42
38
|
const parser = new DOMParser();
|
|
43
|
-
const dom = parser.parseFromString(text,
|
|
39
|
+
const dom = parser.parseFromString(text, "text/html");
|
|
44
40
|
diffHead(dom);
|
|
45
41
|
const newScripts = removeBodyScripts(dom);
|
|
46
42
|
const clonedPersistentNodes = getPersistentNodesFromDocument(dom);
|
|
47
43
|
const body = dom.body.innerHTML;
|
|
48
44
|
document.body.innerHTML = body;
|
|
49
45
|
for (const clonedNode of clonedPersistentNodes) {
|
|
50
|
-
|
|
51
|
-
const newNode = document.body.querySelector(`[data-persistent="${clonedNode?.getAttribute('data-persistent')}"]`);
|
|
46
|
+
const newNode = document.body.querySelector(`[data-persistent="${clonedNode?.getAttribute("data-persistent")}"]`);
|
|
52
47
|
newNode.replaceWith(clonedNode);
|
|
53
48
|
}
|
|
54
49
|
replaceAttributesOnDocumentBody(dom);
|
|
55
50
|
replaceDocumentContentsWithLinkResponse();
|
|
56
51
|
for (const script of newScripts) document.body.appendChild(script);
|
|
57
|
-
|
|
58
|
-
const event = new CustomEvent('spa:updatePageContent', {
|
|
52
|
+
const event = new CustomEvent("spa:updatePageContent", {
|
|
59
53
|
detail: {
|
|
60
|
-
href
|
|
61
|
-
}
|
|
54
|
+
href
|
|
55
|
+
}
|
|
62
56
|
});
|
|
63
57
|
window.dispatchEvent(event);
|
|
64
58
|
} catch (err) {
|
|
65
59
|
console.error(err);
|
|
66
|
-
console.error(
|
|
60
|
+
console.error("Error getting link", href);
|
|
67
61
|
}
|
|
68
62
|
}
|
|
69
|
-
|
|
70
63
|
function diffHead(dom) {
|
|
71
64
|
const domHeadChildren = [...dom.head.children];
|
|
72
65
|
const currentHeadChildren = [...document.head.children];
|
|
73
66
|
const result = {
|
|
74
67
|
childrenToAdd: [],
|
|
75
|
-
childrenToRemove: []
|
|
68
|
+
childrenToRemove: []
|
|
76
69
|
};
|
|
77
70
|
for (const domChild of domHeadChildren) {
|
|
78
|
-
const foundInCurrentChildren = currentHeadChildren.find(child => child.outerHTML === domChild.outerHTML);
|
|
71
|
+
const foundInCurrentChildren = currentHeadChildren.find((child) => child.outerHTML === domChild.outerHTML);
|
|
79
72
|
if (!foundInCurrentChildren) result.childrenToAdd.push(domChild);
|
|
80
73
|
}
|
|
81
74
|
for (const currentChild of currentHeadChildren) {
|
|
82
|
-
const foundInDomChildren = domHeadChildren.find(child => child.outerHTML === currentChild.outerHTML);
|
|
75
|
+
const foundInDomChildren = domHeadChildren.find((child) => child.outerHTML === currentChild.outerHTML);
|
|
83
76
|
if (!foundInDomChildren) result.childrenToRemove.push(currentChild);
|
|
84
77
|
}
|
|
85
78
|
applyHeadDiffsToDocument(result);
|
|
86
79
|
}
|
|
87
|
-
|
|
88
80
|
function applyHeadDiffsToDocument(diff) {
|
|
89
81
|
for (const child of diff.childrenToRemove) {
|
|
90
|
-
// Remove from head
|
|
91
82
|
document.head.removeChild(child);
|
|
92
83
|
}
|
|
93
84
|
for (const child of diff.childrenToAdd) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const scriptElement = document.createElement('script');
|
|
85
|
+
if (child?.nodeName?.toLowerCase() == "script") {
|
|
86
|
+
const scriptElement = document.createElement("script");
|
|
97
87
|
portAttributesToElement(child, scriptElement);
|
|
98
88
|
scriptElement.innerHTML = child.innerHTML;
|
|
99
89
|
document.head.appendChild(scriptElement);
|
|
100
90
|
continue;
|
|
101
91
|
}
|
|
102
|
-
// Add to the head
|
|
103
92
|
document.head.appendChild(child);
|
|
104
93
|
}
|
|
105
94
|
}
|
|
106
|
-
|
|
107
95
|
function removeBodyScripts(dom) {
|
|
108
|
-
const scripts = [...dom.body.querySelectorAll(
|
|
96
|
+
const scripts = [...dom.body.querySelectorAll("script")];
|
|
109
97
|
const newElements = [];
|
|
110
98
|
for (const script of scripts) {
|
|
111
|
-
const element = document.createElement(
|
|
99
|
+
const element = document.createElement("script");
|
|
112
100
|
portAttributesToElement(script, element);
|
|
113
101
|
element.innerHTML = script.innerHTML;
|
|
114
102
|
dom.body.removeChild(script);
|
|
@@ -116,60 +104,48 @@ function removeBodyScripts(dom) {
|
|
|
116
104
|
}
|
|
117
105
|
return newElements;
|
|
118
106
|
}
|
|
119
|
-
|
|
120
107
|
function portAttributesToElement(oldEl, newEl) {
|
|
121
108
|
for (const attribute of oldEl.attributes) {
|
|
122
109
|
newEl.setAttribute(attribute.name, attribute.nodeValue);
|
|
123
110
|
}
|
|
124
111
|
}
|
|
125
|
-
|
|
126
112
|
function getPersistentNodesFromDocument(dom) {
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
const validPersistentNodes = persistentNodesFromBody.filter(el => {
|
|
132
|
-
return persistentNodesFromDom.includes(el.getAttribute('data-persistent'));
|
|
113
|
+
const persistentNodesFromDom = [...dom.querySelectorAll("[data-persistent]")].map((el) => el.getAttribute("data-persistent"));
|
|
114
|
+
const persistentNodesFromBody = [...document.body.querySelectorAll("[data-persistent]")];
|
|
115
|
+
const validPersistentNodes = persistentNodesFromBody.filter((el) => {
|
|
116
|
+
return persistentNodesFromDom.includes(el.getAttribute("data-persistent"));
|
|
133
117
|
});
|
|
134
|
-
|
|
135
|
-
return validPersistentNodes.map(el => el.cloneNode(true));
|
|
118
|
+
return validPersistentNodes.map((el) => el.cloneNode(true));
|
|
136
119
|
}
|
|
137
|
-
|
|
138
120
|
function replaceAttributesOnDocumentBody(dom) {
|
|
139
121
|
while (document.body.attributes.length > 0) {
|
|
140
122
|
document.body.removeAttribute(document.body.attributes[0].name);
|
|
141
123
|
}
|
|
142
|
-
// Set attributes on the body
|
|
143
124
|
for (const attribute of dom.body.attributes) {
|
|
144
125
|
document.body.setAttribute(attribute.name, attribute.nodeValue);
|
|
145
126
|
}
|
|
146
127
|
}
|
|
147
|
-
|
|
148
128
|
async function handleBackButton() {
|
|
149
129
|
await getNewPageContent(window.history.state?.targetHref || window.location.href);
|
|
150
130
|
}
|
|
151
|
-
|
|
152
131
|
function isRelativeURL(url) {
|
|
153
|
-
if (typeof url !==
|
|
132
|
+
if (typeof url !== "string") return false;
|
|
154
133
|
try {
|
|
155
134
|
new URL(url);
|
|
156
135
|
return false;
|
|
157
136
|
} catch (err) {
|
|
158
|
-
if (url?.startsWith(
|
|
137
|
+
if (url?.startsWith("//")) return false;
|
|
159
138
|
return true;
|
|
160
139
|
}
|
|
161
140
|
}
|
|
162
|
-
|
|
163
141
|
function isExternalURL(url) {
|
|
164
142
|
if (isRelativeURL(url)) return false;
|
|
165
143
|
try {
|
|
166
144
|
const computedURL = new URL(url);
|
|
167
145
|
return computedURL?.origin !== window.location?.origin;
|
|
168
146
|
} catch {
|
|
169
|
-
return false;
|
|
147
|
+
return false;
|
|
170
148
|
}
|
|
171
149
|
}
|
|
172
|
-
|
|
173
|
-
window.addEventListener(
|
|
174
|
-
window.addEventListener('popstate', handleBackButton);
|
|
175
|
-
|
|
150
|
+
window.addEventListener("DOMContentLoaded", replaceDocumentContentsWithLinkResponse);
|
|
151
|
+
window.addEventListener("popstate", handleBackButton);
|
package/package.json
CHANGED
package/src/auth.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import jsonwebtoken from 'jsonwebtoken';
|
|
2
|
-
import { ServerRequest } from '../types';
|
|
3
|
-
const { verify, sign, } = jsonwebtoken;
|
|
4
|
-
|
|
5
|
-
export async function signUser(email: string, username: string) {
|
|
6
|
-
return new Promise((resolve, reject) => {
|
|
7
|
-
sign(
|
|
8
|
-
{
|
|
9
|
-
user: {
|
|
10
|
-
email,
|
|
11
|
-
username,
|
|
12
|
-
},
|
|
13
|
-
},
|
|
14
|
-
// @ts-ignore
|
|
15
|
-
process.env.JWT_PRIVATE_KEY,
|
|
16
|
-
{ expiresIn: '30d', },
|
|
17
|
-
(err, token) => {
|
|
18
|
-
if (err) reject(err);
|
|
19
|
-
resolve(token);
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export async function verifyUser(token: string): Promise<any> {
|
|
25
|
-
return new Promise((resolve, reject) => {
|
|
26
|
-
// @ts-ignore
|
|
27
|
-
verify(token, process.env.JWT_PRIVATE_KEY, (err, authorizedData) => {
|
|
28
|
-
if (err) reject(err);
|
|
29
|
-
resolve(authorizedData);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function getTokenFromRequest(req: ServerRequest) {
|
|
35
|
-
// @ts-ignore
|
|
36
|
-
const { authorization, } = req.headers;
|
|
37
|
-
if (!authorization) return null;
|
|
38
|
-
const token = authorization.split(' ').pop() || '';
|
|
39
|
-
return token || null;
|
|
40
|
-
}
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import ts from 'typescript';
|
|
2
|
-
import fs from 'fs-extra';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
|
|
5
|
-
type ImportDeclaration = {
|
|
6
|
-
node: ts.Node;
|
|
7
|
-
importPath: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// node arg should initially be the source node
|
|
11
|
-
export function getImportDeclarationNodes(node: ts.Node, sourceFile: ts.SourceFile, importDeclarations: ImportDeclaration[] = []) {
|
|
12
|
-
if (node.kind === ts.SyntaxKind.ImportDeclaration) {
|
|
13
|
-
let importPath = null;
|
|
14
|
-
node.forEachChild(child => {
|
|
15
|
-
if (child.kind === ts.SyntaxKind.StringLiteral) {
|
|
16
|
-
importPath = child.getText(sourceFile).replace(/[\"\']/g, '');
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
importDeclarations.push({
|
|
20
|
-
node,
|
|
21
|
-
importPath,
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
node.forEachChild(child => {
|
|
25
|
-
getImportDeclarationNodes(child, sourceFile, importDeclarations);
|
|
26
|
-
});
|
|
27
|
-
return importDeclarations;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function stripImportsNodesFromFile(importsToStrip: string[], content: string, importDeclarations: ImportDeclaration[]) {
|
|
31
|
-
let outputContent = content;
|
|
32
|
-
const filteredImports = importDeclarations.filter(item => importsToStrip.includes(item.importPath));
|
|
33
|
-
for (const importItem of filteredImports) {
|
|
34
|
-
outputContent = outputContent.slice(0, importItem.node.pos) + outputContent.slice(importItem.node.end);
|
|
35
|
-
}
|
|
36
|
-
return outputContent;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
type FoundAlpineFunction = {
|
|
40
|
-
name: string;
|
|
41
|
-
isDefaultExport: boolean;
|
|
42
|
-
hasExport: boolean;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function findDataAttributesAndFunctions(
|
|
46
|
-
node: ts.Node,
|
|
47
|
-
sourceFile: ts.SourceFile,
|
|
48
|
-
foundDataAttributes: string[] = [],
|
|
49
|
-
foundFunctions: FoundAlpineFunction[] = []
|
|
50
|
-
) {
|
|
51
|
-
if (node.kind === ts.SyntaxKind.JsxAttribute) {
|
|
52
|
-
const attribute = getAttributeValuePair(node, sourceFile);
|
|
53
|
-
if (attribute?.[0] === 'x-data' && attribute?.[1]) {
|
|
54
|
-
// We found a data attribute,
|
|
55
|
-
// so now we have to search throughout the file for a function declaration of the same name
|
|
56
|
-
foundDataAttributes.push(attribute[1].replace(/[^a-zA-Z0-9]/g, ''));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
if (node.kind === ts.SyntaxKind.SourceFile) {
|
|
60
|
-
node.forEachChild(child => {
|
|
61
|
-
if ([ts.SyntaxKind.FunctionDeclaration, ts.SyntaxKind.VariableDeclaration].includes(child.kind)) {
|
|
62
|
-
const functions = getFunctionDeclarationValue(child, sourceFile);
|
|
63
|
-
foundFunctions.push(functions);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
node.forEachChild(child => {
|
|
69
|
-
findDataAttributesAndFunctions(child, sourceFile, foundDataAttributes, foundFunctions);
|
|
70
|
-
});
|
|
71
|
-
return {
|
|
72
|
-
foundDataAttributes,
|
|
73
|
-
foundFunctions,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function getAttributeValuePair(node: ts.Node, sourceFile: ts.SourceFile): string[] {
|
|
78
|
-
const result = [];
|
|
79
|
-
node.forEachChild(child => {
|
|
80
|
-
result.push(child.getText(sourceFile));
|
|
81
|
-
});
|
|
82
|
-
return result;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function getFunctionDeclarationValue(node: ts.Node, sourceFile: ts.SourceFile): FoundAlpineFunction {
|
|
86
|
-
const result = {
|
|
87
|
-
name: null,
|
|
88
|
-
isDefaultExport: false,
|
|
89
|
-
hasExport: false,
|
|
90
|
-
};
|
|
91
|
-
node.forEachChild(child => {
|
|
92
|
-
if (child.kind === ts.SyntaxKind.ExportKeyword) {
|
|
93
|
-
result.hasExport = true;
|
|
94
|
-
}
|
|
95
|
-
if (child.kind === ts.SyntaxKind.DefaultKeyword) {
|
|
96
|
-
result.isDefaultExport = true;
|
|
97
|
-
}
|
|
98
|
-
if (child.kind === ts.SyntaxKind.Identifier) {
|
|
99
|
-
result.name = child.getText(sourceFile);
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
return result;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Convert entry point files into one big bundle file
|
|
106
|
-
export function convertEntryPointsToSingleFile(entryPoints: string[], tempWritePath: string) {
|
|
107
|
-
fs.writeFileSync(
|
|
108
|
-
tempWritePath,
|
|
109
|
-
entryPoints
|
|
110
|
-
.map(entry => `import "${entry}"`)
|
|
111
|
-
.join(';\n')
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export function removeClientScriptInTSXFile(pathName: string) {
|
|
116
|
-
const content = fs.readFileSync(pathName, 'utf-8');
|
|
117
|
-
const source = ts.createSourceFile(
|
|
118
|
-
pathName,
|
|
119
|
-
content,
|
|
120
|
-
ts.ScriptTarget.Latest
|
|
121
|
-
);
|
|
122
|
-
let toRemoveFrom: number;
|
|
123
|
-
let clientDataStart: number;
|
|
124
|
-
const clientImportsToHoist = [];
|
|
125
|
-
const clientImportsToReplace = [];
|
|
126
|
-
source.forEachChild((child) => {
|
|
127
|
-
if (child.kind === ts.SyntaxKind.ExpressionStatement) {
|
|
128
|
-
const text = child.getText(source);
|
|
129
|
-
// Remove from here
|
|
130
|
-
if (text.startsWith('<script')) {
|
|
131
|
-
toRemoveFrom = child.pos;
|
|
132
|
-
clientDataStart = child.end;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
// Hoist the client imports to above the <script /> tag
|
|
136
|
-
if (child.kind === ts.SyntaxKind.ImportDeclaration && child.pos >= clientDataStart) {
|
|
137
|
-
const text = child.getText(source);
|
|
138
|
-
child.forEachChild(child => {
|
|
139
|
-
if (child.kind === ts.SyntaxKind.StringLiteral) {
|
|
140
|
-
// Keep track of client imports needed for path replacement to be absolute
|
|
141
|
-
const text = child.getText(source);
|
|
142
|
-
const importPath = text.replace(/[\"\']/g, '');
|
|
143
|
-
const isRelativeImport = importPath.startsWith('.') || importPath.startsWith('/');
|
|
144
|
-
if (!isRelativeImport) return;
|
|
145
|
-
clientImportsToReplace.push({
|
|
146
|
-
old: importPath,
|
|
147
|
-
new: path.join(path.dirname(pathName), importPath),
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
clientImportsToHoist.push(text);
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
let clientContent = !isNaN(clientDataStart) ? content.slice(clientDataStart) : '';
|
|
155
|
-
for (const clientImport of clientImportsToReplace) {
|
|
156
|
-
clientContent = clientContent.replace(clientImport.old, clientImport.new);
|
|
157
|
-
}
|
|
158
|
-
return {
|
|
159
|
-
content: !isNaN(toRemoveFrom) ?
|
|
160
|
-
clientImportsToHoist.join('\n') + content.slice(0, toRemoveFrom) :
|
|
161
|
-
content,
|
|
162
|
-
clientContent,
|
|
163
|
-
fullContent: content,
|
|
164
|
-
toRemoveFrom,
|
|
165
|
-
clientDataStart,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export function printRecursiveFrom(
|
|
170
|
-
node: ts.Node, indentLevel: number, sourceFile: ts.SourceFile
|
|
171
|
-
) {
|
|
172
|
-
const indentation = '-'.repeat(indentLevel);
|
|
173
|
-
const syntaxKind = ts.SyntaxKind[node.kind];
|
|
174
|
-
const nodeText = node.getText(sourceFile);
|
|
175
|
-
console.log(`${indentation}${syntaxKind}: ${nodeText}`);
|
|
176
|
-
|
|
177
|
-
node.forEachChild(child =>
|
|
178
|
-
printRecursiveFrom(child, indentLevel + 1, sourceFile)
|
|
179
|
-
);
|
|
180
|
-
}
|
package/src/express.ts
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import express, { NextFunction, Express, Request, Response } from 'express';
|
|
2
|
-
import { globSync } from 'glob';
|
|
3
|
-
import { config } from './util/get-config';
|
|
4
|
-
import { verifyUser } from './auth';
|
|
5
|
-
import requestIP from 'request-ip';
|
|
6
|
-
import { ServerRequest } from '../types';
|
|
7
|
-
|
|
8
|
-
const doctypeHTML = '<!DOCTYPE html>';
|
|
9
|
-
|
|
10
|
-
export async function createRouter() {
|
|
11
|
-
const methods = ['get', 'post', 'put', 'patch', 'delete'];
|
|
12
|
-
const router = express.Router();
|
|
13
|
-
const routes = globSync(config.pagesDir + '/**/*.{tsx,ts}');
|
|
14
|
-
const routeMap = routes.map(route => {
|
|
15
|
-
return {
|
|
16
|
-
route: route.split(config.pagesDir).pop().replace('.tsx', '').replace('.js', '').replace('.ts', '').replace('/index', '/'),
|
|
17
|
-
path: route.replace(config.srcDir, config.distDir).replace('.tsx', '.js').replace('.ts', '.js'),
|
|
18
|
-
originalRoute: route,
|
|
19
|
-
};
|
|
20
|
-
});
|
|
21
|
-
const routeResults = [];
|
|
22
|
-
|
|
23
|
-
for (const route of routeMap) {
|
|
24
|
-
const isJSX = route.originalRoute.endsWith('.tsx') || route.originalRoute.endsWith('.jsx');
|
|
25
|
-
// Import route
|
|
26
|
-
const routeItem = process.env.NODE_ENV === 'development' ? null : (await import(route.path)).default;
|
|
27
|
-
// Configure result,methods for the route
|
|
28
|
-
const slugRoute = route.route.toLowerCase().replace(/[ ]/g, '');
|
|
29
|
-
const foundMethod = methods.find(method => slugRoute.endsWith(`.${method}`));
|
|
30
|
-
const isDynamicRoute = slugRoute.match(/\[(.*)\]/g);
|
|
31
|
-
let formattedRouteItem = slugRoute;
|
|
32
|
-
if (foundMethod) formattedRouteItem = formattedRouteItem.split('.').shift();
|
|
33
|
-
// Handle dynamic routing
|
|
34
|
-
if (isDynamicRoute) {
|
|
35
|
-
const result = [...formattedRouteItem.matchAll(/(\[)(.*?)(\])/g)];
|
|
36
|
-
for (const match of result) {
|
|
37
|
-
formattedRouteItem = formattedRouteItem.replace(match[0], ':' + match[2]);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
// Push to the route results array
|
|
41
|
-
routeResults.push({
|
|
42
|
-
formattedRouteItem,
|
|
43
|
-
foundMethod,
|
|
44
|
-
route,
|
|
45
|
-
});
|
|
46
|
-
router[foundMethod || 'get'](formattedRouteItem, async (req: Request, res: Response) => {
|
|
47
|
-
try {
|
|
48
|
-
// Check if it's a string response from the routeItem or is a different response
|
|
49
|
-
if (routeItem) {
|
|
50
|
-
if (isJSX) {
|
|
51
|
-
res.send(doctypeHTML + (await routeItem(req, res)));
|
|
52
|
-
} else {
|
|
53
|
-
await routeItem(req, res);
|
|
54
|
-
}
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
const defaultRouteImport = (await import(route.path + `?cache=${Date.now()}`)).default;
|
|
58
|
-
// Require every time only if in development mode
|
|
59
|
-
if (isJSX) {
|
|
60
|
-
res.send(doctypeHTML + (await defaultRouteImport(req, res)));
|
|
61
|
-
} else {
|
|
62
|
-
await defaultRouteImport(req, res);
|
|
63
|
-
}
|
|
64
|
-
} catch (err) {
|
|
65
|
-
console.error(err);
|
|
66
|
-
res.status(err?.status || 500).send(err?.message || 'Error');
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
return {
|
|
71
|
-
router,
|
|
72
|
-
routeResults,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function verifyUserMiddleware(req: ServerRequest, _res: Response, next: NextFunction) {
|
|
77
|
-
// @ts-ignore
|
|
78
|
-
const { usertoken, } = req.cookies;
|
|
79
|
-
if (!usertoken) {
|
|
80
|
-
req.user = null;
|
|
81
|
-
}
|
|
82
|
-
try {
|
|
83
|
-
const { user, } = await verifyUser(usertoken);
|
|
84
|
-
req.user = user;
|
|
85
|
-
} catch (err) {
|
|
86
|
-
req.user = null;
|
|
87
|
-
}
|
|
88
|
-
next();
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export async function createXPineRouter(app: any, beforeErrorRoute?: (app: Express) => void) {
|
|
92
|
-
app.use(express.static(config.distPublicDir));
|
|
93
|
-
app.use(verifyUserMiddleware);
|
|
94
|
-
app.use(requestIP.mw());
|
|
95
|
-
|
|
96
|
-
const { router, routeResults, } = await createRouter();
|
|
97
|
-
app.use(function replaceableRouter(req: Request, res: Response, next: NextFunction) {
|
|
98
|
-
router(req, res, next);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
const found404 = routeResults?.find(item => item?.formattedRouteItem === '/404');
|
|
102
|
-
const import404 = process.env.NODE_ENV === 'development' ? null : (await import(found404.route.path)).default;
|
|
103
|
-
|
|
104
|
-
if (beforeErrorRoute) beforeErrorRoute(app);
|
|
105
|
-
|
|
106
|
-
// error handler
|
|
107
|
-
app.use(async function (req: Request, res: Response) {
|
|
108
|
-
// render the error page
|
|
109
|
-
res.status(404);
|
|
110
|
-
|
|
111
|
-
if (import404) {
|
|
112
|
-
res.send(doctypeHTML + (await import404(req, res)));
|
|
113
|
-
} else if (found404 && process.env.NODE_ENV === 'development') {
|
|
114
|
-
const import404Item = (await import(found404.route.path + `?cache=${Date.now()}`)).default
|
|
115
|
-
res.send(doctypeHTML + (await import404Item(req, res)));
|
|
116
|
-
} else {
|
|
117
|
-
res.send('Error');
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
}
|
package/src/runDevServer.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import expressWs from 'express-ws';
|
|
2
|
-
import http from 'http';
|
|
3
|
-
import express from 'express';
|
|
4
|
-
import EventEmitter from 'events';
|
|
5
|
-
import chokidar from 'chokidar';
|
|
6
|
-
import { buildApp } from './scripts/build';
|
|
7
|
-
import path from 'path';
|
|
8
|
-
import { config } from './util/get-config';
|
|
9
|
-
import { setupEnv } from './util/env';
|
|
10
|
-
|
|
11
|
-
setupEnv();
|
|
12
|
-
|
|
13
|
-
export async function runDevServer() {
|
|
14
|
-
process.env.NODE_ENV = 'development';
|
|
15
|
-
const startServer = await import(config.serverDistAppPath + `?cache=${Date.now()}`);
|
|
16
|
-
|
|
17
|
-
// Initial server set up
|
|
18
|
-
await buildApp(true);
|
|
19
|
-
let appServer = await startServer.default();
|
|
20
|
-
|
|
21
|
-
// Watch files
|
|
22
|
-
const watcher = chokidar.watch(config.srcDir, {
|
|
23
|
-
ignoreInitial: true,
|
|
24
|
-
// Ignore map and prisma files
|
|
25
|
-
ignored: (pathName) => pathName.endsWith('.map') || pathName.startsWith(path.join(config.serverDir, './prisma')),
|
|
26
|
-
});
|
|
27
|
-
watcher.on('all', async (event, path) => {
|
|
28
|
-
const shouldReloadServer = (path.startsWith(config.serverDir) && !path.startsWith(config.runDir)) ||
|
|
29
|
-
['add', 'unlink'].includes(event) && path.startsWith(config.pagesDir);
|
|
30
|
-
if (shouldReloadServer) {
|
|
31
|
-
// We modified files in the server, restart the server
|
|
32
|
-
appServer.server.close();
|
|
33
|
-
await buildApp(true);
|
|
34
|
-
const startServer = await import(config.serverDistAppPath + `?cache=${Date.now()}`);
|
|
35
|
-
appServer = await startServer.default();
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
await buildApp(true);
|
|
39
|
-
refreshEmitter.emit('refresh');
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
// Create web socket sever
|
|
43
|
-
const wsApp = express();
|
|
44
|
-
const wsServer = http.createServer(wsApp);
|
|
45
|
-
expressWs(wsApp, wsServer);
|
|
46
|
-
class RefreshEmitter extends EventEmitter { };
|
|
47
|
-
const refreshEmitter = new RefreshEmitter();
|
|
48
|
-
|
|
49
|
-
// @ts-ignore
|
|
50
|
-
wsApp.ws('/dev-server', function (ws) {
|
|
51
|
-
refreshEmitter.removeAllListeners();
|
|
52
|
-
refreshEmitter.on('refresh', () => {
|
|
53
|
-
ws.send('refresh:client');
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
const wsPort = 3001;
|
|
58
|
-
wsServer.listen(wsPort);
|
|
59
|
-
wsServer.on('listening', () => {
|
|
60
|
-
console.info(`Dev server listening on port ${wsPort}`);
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import { build } from 'esbuild';
|
|
3
|
-
import { config } from '../util/get-config';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
|
|
6
|
-
// Extensions to look for in the bundle
|
|
7
|
-
const packageJson = JSON.parse(fs.readFileSync(config.packageJsonPath, 'utf-8'));
|
|
8
|
-
const allPackages = Object.keys(packageJson.devDependencies).concat(Object.keys(packageJson.dependencies));
|
|
9
|
-
const dirname = import.meta.dirname;
|
|
10
|
-
|
|
11
|
-
export async function buildXPine() {
|
|
12
|
-
await build({
|
|
13
|
-
entryPoints: [
|
|
14
|
-
// The contents of XPine itself
|
|
15
|
-
path.join(dirname, '../../index.ts'),
|
|
16
|
-
// The script for xpine-build
|
|
17
|
-
path.join(dirname, './xpine-build.ts'),
|
|
18
|
-
// The script for xpine-dev
|
|
19
|
-
path.join(dirname, './xpine-dev.ts'),
|
|
20
|
-
// Static files
|
|
21
|
-
path.join(dirname, '../static/dev-server.mjs'),
|
|
22
|
-
path.join(dirname, '../static/spa.mjs'),
|
|
23
|
-
],
|
|
24
|
-
format: 'esm',
|
|
25
|
-
platform: 'node',
|
|
26
|
-
outdir: path.join(dirname, '../../dist'),
|
|
27
|
-
bundle: true,
|
|
28
|
-
sourcemap: 'inline',
|
|
29
|
-
external: allPackages,
|
|
30
|
-
jsx: 'transform',
|
|
31
|
-
minify: false,
|
|
32
|
-
plugins: [],
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
buildXPine();
|