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
package/dist/index.js
ADDED
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
// src/runDevServer.ts
|
|
2
|
+
import expressWs from "express-ws";
|
|
3
|
+
import http from "http";
|
|
4
|
+
import express from "express";
|
|
5
|
+
import EventEmitter from "events";
|
|
6
|
+
import chokidar from "chokidar";
|
|
7
|
+
|
|
8
|
+
// src/scripts/build.ts
|
|
9
|
+
import path3 from "path";
|
|
10
|
+
import fs2 from "fs-extra";
|
|
11
|
+
import { build } from "esbuild";
|
|
12
|
+
import builtinModules from "builtin-modules";
|
|
13
|
+
import ts2 from "typescript";
|
|
14
|
+
|
|
15
|
+
// src/build/typescript-builder.ts
|
|
16
|
+
import ts from "typescript";
|
|
17
|
+
import fs from "fs-extra";
|
|
18
|
+
import path from "path";
|
|
19
|
+
function findDataAttributesAndFunctions(node, sourceFile, foundDataAttributes = [], foundFunctions = []) {
|
|
20
|
+
if (node.kind === ts.SyntaxKind.JsxAttribute) {
|
|
21
|
+
const attribute = getAttributeValuePair(node, sourceFile);
|
|
22
|
+
if (attribute?.[0] === "x-data" && attribute?.[1]) {
|
|
23
|
+
foundDataAttributes.push(attribute[1].replace(/[^a-zA-Z0-9]/g, ""));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (node.kind === ts.SyntaxKind.SourceFile) {
|
|
27
|
+
node.forEachChild((child) => {
|
|
28
|
+
if ([ts.SyntaxKind.FunctionDeclaration, ts.SyntaxKind.VariableDeclaration].includes(child.kind)) {
|
|
29
|
+
const functions = getFunctionDeclarationValue(child, sourceFile);
|
|
30
|
+
foundFunctions.push(functions);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
node.forEachChild((child) => {
|
|
35
|
+
findDataAttributesAndFunctions(child, sourceFile, foundDataAttributes, foundFunctions);
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
foundDataAttributes,
|
|
39
|
+
foundFunctions
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function getAttributeValuePair(node, sourceFile) {
|
|
43
|
+
const result = [];
|
|
44
|
+
node.forEachChild((child) => {
|
|
45
|
+
result.push(child.getText(sourceFile));
|
|
46
|
+
});
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
function getFunctionDeclarationValue(node, sourceFile) {
|
|
50
|
+
const result = {
|
|
51
|
+
name: null,
|
|
52
|
+
isDefaultExport: false,
|
|
53
|
+
hasExport: false
|
|
54
|
+
};
|
|
55
|
+
node.forEachChild((child) => {
|
|
56
|
+
if (child.kind === ts.SyntaxKind.ExportKeyword) {
|
|
57
|
+
result.hasExport = true;
|
|
58
|
+
}
|
|
59
|
+
if (child.kind === ts.SyntaxKind.DefaultKeyword) {
|
|
60
|
+
result.isDefaultExport = true;
|
|
61
|
+
}
|
|
62
|
+
if (child.kind === ts.SyntaxKind.Identifier) {
|
|
63
|
+
result.name = child.getText(sourceFile);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
function convertEntryPointsToSingleFile(entryPoints, tempWritePath) {
|
|
69
|
+
fs.writeFileSync(
|
|
70
|
+
tempWritePath,
|
|
71
|
+
entryPoints.map((entry) => `import "${entry}"`).join(";\n")
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
function removeClientScriptInTSXFile(pathName) {
|
|
75
|
+
const content = fs.readFileSync(pathName, "utf-8");
|
|
76
|
+
const source = ts.createSourceFile(
|
|
77
|
+
pathName,
|
|
78
|
+
content,
|
|
79
|
+
ts.ScriptTarget.Latest
|
|
80
|
+
);
|
|
81
|
+
let toRemoveFrom;
|
|
82
|
+
let clientDataStart;
|
|
83
|
+
const clientImportsToHoist = [];
|
|
84
|
+
const clientImportsToReplace = [];
|
|
85
|
+
source.forEachChild((child) => {
|
|
86
|
+
if (child.kind === ts.SyntaxKind.ExpressionStatement) {
|
|
87
|
+
const text = child.getText(source);
|
|
88
|
+
if (text.startsWith("<script")) {
|
|
89
|
+
toRemoveFrom = child.pos;
|
|
90
|
+
clientDataStart = child.end;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (child.kind === ts.SyntaxKind.ImportDeclaration && child.pos >= clientDataStart) {
|
|
94
|
+
const text = child.getText(source);
|
|
95
|
+
child.forEachChild((child2) => {
|
|
96
|
+
if (child2.kind === ts.SyntaxKind.StringLiteral) {
|
|
97
|
+
const text2 = child2.getText(source);
|
|
98
|
+
const importPath = text2.replace(/[\"\']/g, "");
|
|
99
|
+
const isRelativeImport = importPath.startsWith(".") || importPath.startsWith("/");
|
|
100
|
+
if (!isRelativeImport) return;
|
|
101
|
+
clientImportsToReplace.push({
|
|
102
|
+
old: importPath,
|
|
103
|
+
new: path.join(path.dirname(pathName), importPath)
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
clientImportsToHoist.push(text);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
let clientContent = !isNaN(clientDataStart) ? content.slice(clientDataStart) : "";
|
|
111
|
+
for (const clientImport of clientImportsToReplace) {
|
|
112
|
+
clientContent = clientContent.replace(clientImport.old, clientImport.new);
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
content: !isNaN(toRemoveFrom) ? clientImportsToHoist.join("\n") + content.slice(0, toRemoveFrom) : content,
|
|
116
|
+
clientContent,
|
|
117
|
+
fullContent: content,
|
|
118
|
+
toRemoveFrom,
|
|
119
|
+
clientDataStart
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/scripts/build.ts
|
|
124
|
+
import { globSync } from "glob";
|
|
125
|
+
import postcss from "postcss";
|
|
126
|
+
import tailwindPostcss from "@tailwindcss/postcss";
|
|
127
|
+
|
|
128
|
+
// src/util/get-config.ts
|
|
129
|
+
import path2 from "path";
|
|
130
|
+
|
|
131
|
+
// src/util/require.ts
|
|
132
|
+
import { createRequire } from "module";
|
|
133
|
+
var require2 = createRequire(import.meta.url);
|
|
134
|
+
var require_default = require2;
|
|
135
|
+
|
|
136
|
+
// src/util/get-config.ts
|
|
137
|
+
var rootDir = process.cwd();
|
|
138
|
+
function fromRoot(pathName) {
|
|
139
|
+
if (!pathName) return rootDir;
|
|
140
|
+
return path2.join(rootDir, pathName);
|
|
141
|
+
}
|
|
142
|
+
var userConfig = require_default(path2.join(process.cwd(), "./xpine.config.mjs")).default;
|
|
143
|
+
var configDefaults = {
|
|
144
|
+
rootDir: fromRoot(),
|
|
145
|
+
srcDir: fromRoot("./src"),
|
|
146
|
+
distDir: fromRoot("./dist"),
|
|
147
|
+
packageJsonPath: fromRoot("package.json"),
|
|
148
|
+
// We need to use getters here in the event someone wants to change folders, such as the dist folder
|
|
149
|
+
get distPublicDir() {
|
|
150
|
+
return path2.join(this.distDir, "./public");
|
|
151
|
+
},
|
|
152
|
+
get distPublicScriptsDir() {
|
|
153
|
+
return path2.join(this.distPublicDir, "./scripts");
|
|
154
|
+
},
|
|
155
|
+
get distTempFolder() {
|
|
156
|
+
return path2.join(this.distDir, "./temp");
|
|
157
|
+
},
|
|
158
|
+
get clientJSBundlePath() {
|
|
159
|
+
return path2.join(this.distPublicScriptsDir, "./app.js");
|
|
160
|
+
},
|
|
161
|
+
get alpineDataPath() {
|
|
162
|
+
return path2.join(this.distTempFolder, "./alpine-data.ts");
|
|
163
|
+
},
|
|
164
|
+
get serverDistDir() {
|
|
165
|
+
return path2.join(this.distDir, "./server");
|
|
166
|
+
},
|
|
167
|
+
get serverDistAppPath() {
|
|
168
|
+
return path2.join(this.serverDistDir, "./app.js");
|
|
169
|
+
},
|
|
170
|
+
// Important dirs/paths
|
|
171
|
+
get pagesDir() {
|
|
172
|
+
return path2.join(this.srcDir, "./pages");
|
|
173
|
+
},
|
|
174
|
+
get publicDir() {
|
|
175
|
+
return path2.join(this.srcDir, "./public");
|
|
176
|
+
},
|
|
177
|
+
get serverDir() {
|
|
178
|
+
return path2.join(this.srcDir, "./server");
|
|
179
|
+
},
|
|
180
|
+
get runDir() {
|
|
181
|
+
return path2.join(this.serverDir, "./run");
|
|
182
|
+
},
|
|
183
|
+
get serverAppPath() {
|
|
184
|
+
return path2.join(this.serverDir, "./app.ts");
|
|
185
|
+
},
|
|
186
|
+
get globalCSSFile() {
|
|
187
|
+
return path2.join(this.publicDir, "./styles/global.css");
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
var config = {
|
|
191
|
+
...configDefaults,
|
|
192
|
+
...userConfig
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// src/scripts/build.ts
|
|
196
|
+
var extensions = [".ts", ".tsx"];
|
|
197
|
+
var packageJson = JSON.parse(fs2.readFileSync(config.packageJsonPath, "utf-8"));
|
|
198
|
+
var allPackages = Object.keys(packageJson.devDependencies).concat(Object.keys(packageJson.dependencies));
|
|
199
|
+
var allPackagesIncludingNode = allPackages.concat(builtinModules);
|
|
200
|
+
async function buildApp(isDev = false) {
|
|
201
|
+
try {
|
|
202
|
+
const srcDirFiles = globSync(config.srcDir + "/**/*.{js,ts,tsx,jsx}");
|
|
203
|
+
const { componentData, dataFiles } = await buildAppFiles(srcDirFiles, isDev);
|
|
204
|
+
const alpineDataFile = await buildAlpineDataFile(componentData, dataFiles);
|
|
205
|
+
await buildClientSideFiles([alpineDataFile], isDev);
|
|
206
|
+
fs2.removeSync(config.distTempFolder);
|
|
207
|
+
await buildCSS();
|
|
208
|
+
await buildPublicFolderSymlinks();
|
|
209
|
+
} catch (err) {
|
|
210
|
+
console.error("Build failed");
|
|
211
|
+
console.error(err);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async function buildAppFiles(files, isDev) {
|
|
215
|
+
const componentData = [];
|
|
216
|
+
const dataFiles = [];
|
|
217
|
+
const backendFiles = files.filter((file) => extensions.find((ext) => file.endsWith(ext))).filter((file) => !file.startsWith(config.publicDir));
|
|
218
|
+
fs2.ensureDirSync(config.distDir);
|
|
219
|
+
await build({
|
|
220
|
+
entryPoints: backendFiles,
|
|
221
|
+
format: "esm",
|
|
222
|
+
platform: "node",
|
|
223
|
+
outdir: config.distDir,
|
|
224
|
+
bundle: true,
|
|
225
|
+
sourcemap: isDev ? "inline" : false,
|
|
226
|
+
external: allPackages,
|
|
227
|
+
jsx: "transform",
|
|
228
|
+
minify: !isDev,
|
|
229
|
+
plugins: [
|
|
230
|
+
{
|
|
231
|
+
name: "add-dot-js",
|
|
232
|
+
setup(build2) {
|
|
233
|
+
build2.onResolve({ filter: /.*/ }, (args) => {
|
|
234
|
+
const hasAtSign = args.path.startsWith("@");
|
|
235
|
+
const isPackage = hasAtSign ? allPackagesIncludingNode.includes(args.path) : allPackagesIncludingNode.includes(args.path.split("/").shift());
|
|
236
|
+
if (args.importer && !isPackage) {
|
|
237
|
+
const calculatedDir = path3.join(args.resolveDir, args.path);
|
|
238
|
+
let existsAsFile = false;
|
|
239
|
+
for (const extension of extensions) {
|
|
240
|
+
const asFile = calculatedDir + extension;
|
|
241
|
+
const exists = fs2.existsSync(asFile);
|
|
242
|
+
if (exists) existsAsFile = true;
|
|
243
|
+
}
|
|
244
|
+
let outputPath = args.path + (existsAsFile ? "" : "/index") + ".js";
|
|
245
|
+
outputPath = args.path.endsWith(".js") || args.path.endsWith(".mjs") ? args.path : outputPath;
|
|
246
|
+
return { path: outputPath, external: true };
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
name: "insert-html-banner-and-remove-client-scripts",
|
|
253
|
+
setup(build2) {
|
|
254
|
+
build2.onLoad({ filter: /.tsx/ }, (args) => {
|
|
255
|
+
const cleanedContent = removeClientScriptInTSXFile(args.path);
|
|
256
|
+
const htmlImportStart = "import { html } from 'xpine';\n";
|
|
257
|
+
const newContent = `${htmlImportStart}${cleanedContent.content}`;
|
|
258
|
+
componentData.push({
|
|
259
|
+
...args,
|
|
260
|
+
contents: `${htmlImportStart}${cleanedContent.fullContent}`,
|
|
261
|
+
clientContent: cleanedContent.clientContent
|
|
262
|
+
});
|
|
263
|
+
return {
|
|
264
|
+
contents: newContent,
|
|
265
|
+
loader: "tsx"
|
|
266
|
+
};
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
name: "get-data-files",
|
|
272
|
+
setup(build2) {
|
|
273
|
+
build2.onLoad({ filter: /\.data\.(js|mjs|ts)$/ }, (args) => {
|
|
274
|
+
const contents = fs2.readFileSync(args.path, "utf-8");
|
|
275
|
+
dataFiles.push({
|
|
276
|
+
...args,
|
|
277
|
+
contents
|
|
278
|
+
});
|
|
279
|
+
return {
|
|
280
|
+
contents,
|
|
281
|
+
loader: "ts"
|
|
282
|
+
};
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
]
|
|
287
|
+
});
|
|
288
|
+
await logSize(config.distDir, "app");
|
|
289
|
+
return {
|
|
290
|
+
componentData,
|
|
291
|
+
dataFiles
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
async function buildClientSideFiles(alpineDataFiles = [], isDev) {
|
|
295
|
+
const tempFilePath = path3.join(config.distTempFolder, "./app.ts");
|
|
296
|
+
fs2.ensureFileSync(tempFilePath);
|
|
297
|
+
const pagesScriptsGlob = config.publicDir + "/scripts/pages/**/*.{js,ts}";
|
|
298
|
+
const clientFiles = globSync(
|
|
299
|
+
config.publicDir + "/**/*.{js,ts}",
|
|
300
|
+
{
|
|
301
|
+
ignore: pagesScriptsGlob
|
|
302
|
+
}
|
|
303
|
+
);
|
|
304
|
+
convertEntryPointsToSingleFile(alpineDataFiles.concat(clientFiles), tempFilePath);
|
|
305
|
+
if (isDev) writeDevServerClientSideCode(tempFilePath);
|
|
306
|
+
writeSpaClientSideCode(tempFilePath);
|
|
307
|
+
await build({
|
|
308
|
+
entryPoints: [tempFilePath],
|
|
309
|
+
bundle: true,
|
|
310
|
+
outdir: config.distPublicScriptsDir,
|
|
311
|
+
minify: !isDev,
|
|
312
|
+
sourcemap: isDev ? "inline" : false
|
|
313
|
+
});
|
|
314
|
+
const pagesFiles = globSync(pagesScriptsGlob);
|
|
315
|
+
await build({
|
|
316
|
+
entryPoints: pagesFiles || [],
|
|
317
|
+
bundle: true,
|
|
318
|
+
outdir: config.distPublicScriptsDir + "/pages",
|
|
319
|
+
minify: !isDev,
|
|
320
|
+
sourcemap: isDev ? "inline" : false
|
|
321
|
+
});
|
|
322
|
+
await logSize(config.distPublicDir, "client");
|
|
323
|
+
}
|
|
324
|
+
function writeDevServerClientSideCode(tempFilePath) {
|
|
325
|
+
const devServerPath = path3.join(import.meta.dirname, "../static/dev-server.js");
|
|
326
|
+
const content = fs2.readFileSync(devServerPath, "utf-8");
|
|
327
|
+
fs2.appendFileSync(tempFilePath, `
|
|
328
|
+
` + content);
|
|
329
|
+
}
|
|
330
|
+
function writeSpaClientSideCode(tempFilePath) {
|
|
331
|
+
const spaPath = path3.join(import.meta.dirname, "../static/spa.js");
|
|
332
|
+
const content = fs2.readFileSync(spaPath, "utf-8");
|
|
333
|
+
fs2.appendFileSync(tempFilePath, `
|
|
334
|
+
` + content);
|
|
335
|
+
}
|
|
336
|
+
async function buildAlpineDataFile(componentData, dataFiles) {
|
|
337
|
+
const output = {
|
|
338
|
+
imports: [
|
|
339
|
+
"import Alpine from 'alpinejs';"
|
|
340
|
+
],
|
|
341
|
+
code: [],
|
|
342
|
+
end: [
|
|
343
|
+
"window.Alpine = Alpine;"
|
|
344
|
+
]
|
|
345
|
+
};
|
|
346
|
+
const dataFunctionResults = {
|
|
347
|
+
foundDataAttributes: [],
|
|
348
|
+
foundFunctions: [],
|
|
349
|
+
foundImports: []
|
|
350
|
+
};
|
|
351
|
+
const componentsAndDataFiles = componentData.concat(dataFiles);
|
|
352
|
+
for (const component of componentsAndDataFiles) {
|
|
353
|
+
const sourceFile = ts2.createSourceFile(
|
|
354
|
+
component.path,
|
|
355
|
+
component.contents,
|
|
356
|
+
ts2.ScriptTarget.Latest
|
|
357
|
+
);
|
|
358
|
+
const dataFunctionResult = findDataAttributesAndFunctions(sourceFile, sourceFile);
|
|
359
|
+
dataFunctionResults.foundDataAttributes.push(...dataFunctionResult.foundDataAttributes);
|
|
360
|
+
const foundFunctionsWithPath = dataFunctionResult.foundFunctions.map((item) => {
|
|
361
|
+
return {
|
|
362
|
+
...item,
|
|
363
|
+
path: component.path,
|
|
364
|
+
content: component.clientContent
|
|
365
|
+
};
|
|
366
|
+
});
|
|
367
|
+
dataFunctionResults.foundFunctions.push(...foundFunctionsWithPath);
|
|
368
|
+
}
|
|
369
|
+
const validDataFunctions = dataFunctionResults.foundFunctions.filter((item) => {
|
|
370
|
+
return dataFunctionResults.foundDataAttributes.includes(item.name) && item.content;
|
|
371
|
+
});
|
|
372
|
+
for (const dataFunction of validDataFunctions) {
|
|
373
|
+
if (!dataFunction.hasExport) continue;
|
|
374
|
+
output.code.push(dataFunction.content);
|
|
375
|
+
output.code.push(`Alpine.data('${dataFunction.name}', ${dataFunction.name});`);
|
|
376
|
+
}
|
|
377
|
+
const result = output.imports.join("\n") + "\n" + output.code.join("\n") + "\n" + output.end.join("\n");
|
|
378
|
+
fs2.ensureFileSync(config.alpineDataPath);
|
|
379
|
+
fs2.writeFileSync(config.alpineDataPath, result);
|
|
380
|
+
return config.alpineDataPath;
|
|
381
|
+
}
|
|
382
|
+
async function buildCSS() {
|
|
383
|
+
const cssFiles = globSync(config.srcDir + "/**/*.css");
|
|
384
|
+
for (const file of cssFiles) {
|
|
385
|
+
const fileContents = fs2.readFileSync(file, "utf-8");
|
|
386
|
+
const result = await postcss([tailwindPostcss()]).process(fileContents, { from: file });
|
|
387
|
+
const newPath = file.replace(config.srcDir, config.distDir);
|
|
388
|
+
fs2.ensureFileSync(newPath);
|
|
389
|
+
fs2.writeFileSync(newPath, result.css);
|
|
390
|
+
}
|
|
391
|
+
logSize(config.distPublicDir, "css");
|
|
392
|
+
}
|
|
393
|
+
async function buildPublicFolderSymlinks() {
|
|
394
|
+
const files = globSync(config.publicDir + "/**/*.*", {
|
|
395
|
+
ignore: "/**/*.{css,js,ts,tsx,jsx}"
|
|
396
|
+
});
|
|
397
|
+
for (const file of files) {
|
|
398
|
+
try {
|
|
399
|
+
const newPath = file.replace(config.srcDir, config.distDir);
|
|
400
|
+
const splitNewPath = newPath.split("/");
|
|
401
|
+
splitNewPath.pop();
|
|
402
|
+
const newDir = splitNewPath.join("/");
|
|
403
|
+
fs2.ensureDirSync(newDir);
|
|
404
|
+
fs2.symlinkSync(file, newPath);
|
|
405
|
+
} catch {
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
async function logSize(pathName, type, validExtensions = [".js", ".css"]) {
|
|
410
|
+
const files = globSync(pathName + "/**/*" + (type === "css" ? ".css" : ""));
|
|
411
|
+
const fileSizes = files.map((file) => {
|
|
412
|
+
if (!validExtensions.find((ext) => file.endsWith(ext))) return false;
|
|
413
|
+
return {
|
|
414
|
+
file,
|
|
415
|
+
size: fs2.statSync(file).size / (1024 * 1e3)
|
|
416
|
+
};
|
|
417
|
+
}).filter(Boolean);
|
|
418
|
+
const totalSize = fileSizes.reduce((total, current) => {
|
|
419
|
+
return current.size + total;
|
|
420
|
+
}, 0);
|
|
421
|
+
console.info(`[${totalSize.toFixed(3)} MB] Built ${type}`);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// src/runDevServer.ts
|
|
425
|
+
import path5 from "path";
|
|
426
|
+
|
|
427
|
+
// src/util/env.ts
|
|
428
|
+
import dotenv from "dotenv";
|
|
429
|
+
import path4 from "path";
|
|
430
|
+
function setupEnv() {
|
|
431
|
+
dotenv.config({ path: path4.join(config.rootDir, `./.env.${process.env.STAGE || "dev"}`) });
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// src/runDevServer.ts
|
|
435
|
+
setupEnv();
|
|
436
|
+
async function runDevServer() {
|
|
437
|
+
process.env.NODE_ENV = "development";
|
|
438
|
+
const startServer = await import(config.serverDistAppPath + `?cache=${Date.now()}`);
|
|
439
|
+
await buildApp(true);
|
|
440
|
+
let appServer = await startServer.default();
|
|
441
|
+
const watcher = chokidar.watch(config.srcDir, {
|
|
442
|
+
ignoreInitial: true,
|
|
443
|
+
// Ignore map and prisma files
|
|
444
|
+
ignored: (pathName) => pathName.endsWith(".map") || pathName.startsWith(path5.join(config.serverDir, "./prisma"))
|
|
445
|
+
});
|
|
446
|
+
watcher.on("all", async (event, path6) => {
|
|
447
|
+
const shouldReloadServer = path6.startsWith(config.serverDir) && !path6.startsWith(config.runDir) || ["add", "unlink"].includes(event) && path6.startsWith(config.pagesDir);
|
|
448
|
+
if (shouldReloadServer) {
|
|
449
|
+
appServer.server.close();
|
|
450
|
+
await buildApp(true);
|
|
451
|
+
const startServer2 = await import(config.serverDistAppPath + `?cache=${Date.now()}`);
|
|
452
|
+
appServer = await startServer2.default();
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
await buildApp(true);
|
|
456
|
+
refreshEmitter.emit("refresh");
|
|
457
|
+
});
|
|
458
|
+
const wsApp = express();
|
|
459
|
+
const wsServer = http.createServer(wsApp);
|
|
460
|
+
expressWs(wsApp, wsServer);
|
|
461
|
+
class RefreshEmitter extends EventEmitter {
|
|
462
|
+
}
|
|
463
|
+
;
|
|
464
|
+
const refreshEmitter = new RefreshEmitter();
|
|
465
|
+
wsApp.ws("/dev-server", function(ws) {
|
|
466
|
+
refreshEmitter.removeAllListeners();
|
|
467
|
+
refreshEmitter.on("refresh", () => {
|
|
468
|
+
ws.send("refresh:client");
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
const wsPort = 3001;
|
|
472
|
+
wsServer.listen(wsPort);
|
|
473
|
+
wsServer.on("listening", () => {
|
|
474
|
+
console.info(`Dev server listening on port ${wsPort}`);
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/express.ts
|
|
479
|
+
import express2 from "express";
|
|
480
|
+
import { globSync as globSync2 } from "glob";
|
|
481
|
+
|
|
482
|
+
// src/auth.ts
|
|
483
|
+
import jsonwebtoken from "jsonwebtoken";
|
|
484
|
+
var { verify, sign } = jsonwebtoken;
|
|
485
|
+
async function signUser(email, username) {
|
|
486
|
+
return new Promise((resolve, reject) => {
|
|
487
|
+
sign(
|
|
488
|
+
{
|
|
489
|
+
user: {
|
|
490
|
+
email,
|
|
491
|
+
username
|
|
492
|
+
}
|
|
493
|
+
},
|
|
494
|
+
// @ts-ignore
|
|
495
|
+
process.env.JWT_PRIVATE_KEY,
|
|
496
|
+
{ expiresIn: "30d" },
|
|
497
|
+
(err, token) => {
|
|
498
|
+
if (err) reject(err);
|
|
499
|
+
resolve(token);
|
|
500
|
+
}
|
|
501
|
+
);
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
async function verifyUser(token) {
|
|
505
|
+
return new Promise((resolve, reject) => {
|
|
506
|
+
verify(token, process.env.JWT_PRIVATE_KEY, (err, authorizedData) => {
|
|
507
|
+
if (err) reject(err);
|
|
508
|
+
resolve(authorizedData);
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
function getTokenFromRequest(req) {
|
|
513
|
+
const { authorization } = req.headers;
|
|
514
|
+
if (!authorization) return null;
|
|
515
|
+
const token = authorization.split(" ").pop() || "";
|
|
516
|
+
return token || null;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// src/express.ts
|
|
520
|
+
import requestIP from "request-ip";
|
|
521
|
+
var doctypeHTML = "<!DOCTYPE html>";
|
|
522
|
+
async function createRouter() {
|
|
523
|
+
const methods = ["get", "post", "put", "patch", "delete"];
|
|
524
|
+
const router = express2.Router();
|
|
525
|
+
const routes = globSync2(config.pagesDir + "/**/*.{tsx,ts}");
|
|
526
|
+
const routeMap = routes.map((route) => {
|
|
527
|
+
return {
|
|
528
|
+
route: route.split(config.pagesDir).pop().replace(".tsx", "").replace(".js", "").replace(".ts", "").replace("/index", "/"),
|
|
529
|
+
path: route.replace(config.srcDir, config.distDir).replace(".tsx", ".js").replace(".ts", ".js"),
|
|
530
|
+
originalRoute: route
|
|
531
|
+
};
|
|
532
|
+
});
|
|
533
|
+
const routeResults = [];
|
|
534
|
+
for (const route of routeMap) {
|
|
535
|
+
const isJSX = route.originalRoute.endsWith(".tsx") || route.originalRoute.endsWith(".jsx");
|
|
536
|
+
const routeItem = process.env.NODE_ENV === "development" ? null : (await import(route.path)).default;
|
|
537
|
+
const slugRoute = route.route.toLowerCase().replace(/[ ]/g, "");
|
|
538
|
+
const foundMethod = methods.find((method) => slugRoute.endsWith(`.${method}`));
|
|
539
|
+
const isDynamicRoute = slugRoute.match(/\[(.*)\]/g);
|
|
540
|
+
let formattedRouteItem = slugRoute;
|
|
541
|
+
if (foundMethod) formattedRouteItem = formattedRouteItem.split(".").shift();
|
|
542
|
+
if (isDynamicRoute) {
|
|
543
|
+
const result = [...formattedRouteItem.matchAll(/(\[)(.*?)(\])/g)];
|
|
544
|
+
for (const match of result) {
|
|
545
|
+
formattedRouteItem = formattedRouteItem.replace(match[0], ":" + match[2]);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
routeResults.push({
|
|
549
|
+
formattedRouteItem,
|
|
550
|
+
foundMethod,
|
|
551
|
+
route
|
|
552
|
+
});
|
|
553
|
+
router[foundMethod || "get"](formattedRouteItem, async (req, res) => {
|
|
554
|
+
try {
|
|
555
|
+
if (routeItem) {
|
|
556
|
+
if (isJSX) {
|
|
557
|
+
res.send(doctypeHTML + await routeItem(req, res));
|
|
558
|
+
} else {
|
|
559
|
+
await routeItem(req, res);
|
|
560
|
+
}
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
const defaultRouteImport = (await import(route.path + `?cache=${Date.now()}`)).default;
|
|
564
|
+
if (isJSX) {
|
|
565
|
+
res.send(doctypeHTML + await defaultRouteImport(req, res));
|
|
566
|
+
} else {
|
|
567
|
+
await defaultRouteImport(req, res);
|
|
568
|
+
}
|
|
569
|
+
} catch (err) {
|
|
570
|
+
console.error(err);
|
|
571
|
+
res.status(err?.status || 500).send(err?.message || "Error");
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
return {
|
|
576
|
+
router,
|
|
577
|
+
routeResults
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
async function verifyUserMiddleware(req, _res, next) {
|
|
581
|
+
const { usertoken } = req.cookies;
|
|
582
|
+
if (!usertoken) {
|
|
583
|
+
req.user = null;
|
|
584
|
+
}
|
|
585
|
+
try {
|
|
586
|
+
const { user } = await verifyUser(usertoken);
|
|
587
|
+
req.user = user;
|
|
588
|
+
} catch (err) {
|
|
589
|
+
req.user = null;
|
|
590
|
+
}
|
|
591
|
+
next();
|
|
592
|
+
}
|
|
593
|
+
async function createXPineRouter(app, beforeErrorRoute) {
|
|
594
|
+
app.use(express2.static(config.distPublicDir));
|
|
595
|
+
app.use(verifyUserMiddleware);
|
|
596
|
+
app.use(requestIP.mw());
|
|
597
|
+
const { router, routeResults } = await createRouter();
|
|
598
|
+
app.use(function replaceableRouter(req, res, next) {
|
|
599
|
+
router(req, res, next);
|
|
600
|
+
});
|
|
601
|
+
const found404 = routeResults?.find((item) => item?.formattedRouteItem === "/404");
|
|
602
|
+
const import404 = process.env.NODE_ENV === "development" ? null : (await import(found404.route.path)).default;
|
|
603
|
+
if (beforeErrorRoute) beforeErrorRoute(app);
|
|
604
|
+
app.use(async function(req, res) {
|
|
605
|
+
res.status(404);
|
|
606
|
+
if (import404) {
|
|
607
|
+
res.send(doctypeHTML + await import404(req, res));
|
|
608
|
+
} else if (found404 && process.env.NODE_ENV === "development") {
|
|
609
|
+
const import404Item = (await import(found404.route.path + `?cache=${Date.now()}`)).default;
|
|
610
|
+
res.send(doctypeHTML + await import404Item(req, res));
|
|
611
|
+
} else {
|
|
612
|
+
res.send("Error");
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// src/util/html.ts
|
|
618
|
+
var html = class {
|
|
619
|
+
static attributeObjectToString(props) {
|
|
620
|
+
if (!props) return "";
|
|
621
|
+
return Object.entries(props).filter(([, value]) => value !== null && value !== void 0).map(([key, value], index) => {
|
|
622
|
+
const start = index === 0 ? " " : "";
|
|
623
|
+
return `${start}${key}="${value}"`;
|
|
624
|
+
}).join(" ");
|
|
625
|
+
}
|
|
626
|
+
static async fragment(props) {
|
|
627
|
+
const childrenResult = await Promise.all(props.children.flat());
|
|
628
|
+
return childrenResult.filter(Boolean).join("");
|
|
629
|
+
}
|
|
630
|
+
static async createElement(type, props, ...children) {
|
|
631
|
+
const childrenResult = await Promise.all(children.flat());
|
|
632
|
+
if (typeof type === "function") {
|
|
633
|
+
const result = await type({ ...props, children: childrenResult });
|
|
634
|
+
return result;
|
|
635
|
+
}
|
|
636
|
+
return `<${type}${this.attributeObjectToString(props)}>${childrenResult.filter(Boolean).join("")}</${type}>`;
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
function JSXRuntime() {
|
|
640
|
+
return true;
|
|
641
|
+
}
|
|
642
|
+
export {
|
|
643
|
+
JSXRuntime,
|
|
644
|
+
buildApp,
|
|
645
|
+
buildCSS,
|
|
646
|
+
buildPublicFolderSymlinks,
|
|
647
|
+
config,
|
|
648
|
+
createRouter,
|
|
649
|
+
createXPineRouter,
|
|
650
|
+
fromRoot,
|
|
651
|
+
getTokenFromRequest,
|
|
652
|
+
html,
|
|
653
|
+
logSize,
|
|
654
|
+
runDevServer,
|
|
655
|
+
setupEnv,
|
|
656
|
+
signUser,
|
|
657
|
+
verifyUser
|
|
658
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/build/dev-server.mjs
|
|
2
|
+
var socketProtocol = "ws:";
|
|
3
|
+
var echoSocketUrl = socketProtocol + "//" + window.location.hostname + ":3001/dev-server/";
|
|
4
|
+
var socket = new WebSocket(echoSocketUrl);
|
|
5
|
+
socket.addEventListener("message", async (msg) => {
|
|
6
|
+
if (msg.data === "refresh:client") {
|
|
7
|
+
window.location.reload();
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL2J1aWxkL2Rldi1zZXJ2ZXIubWpzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyJjb25zdCBzb2NrZXRQcm90b2NvbCA9ICd3czonO1xuY29uc3QgZWNob1NvY2tldFVybCA9IHNvY2tldFByb3RvY29sICsgJy8vJyArIHdpbmRvdy5sb2NhdGlvbi5ob3N0bmFtZSArICc6MzAwMS9kZXYtc2VydmVyLyc7XG5jb25zdCBzb2NrZXQgPSBuZXcgV2ViU29ja2V0KGVjaG9Tb2NrZXRVcmwpO1xuXG5zb2NrZXQuYWRkRXZlbnRMaXN0ZW5lcignbWVzc2FnZScsIGFzeW5jIChtc2cpID0+IHtcbiAgaWYgKG1zZy5kYXRhID09PSAncmVmcmVzaDpjbGllbnQnKSB7XG4gICAgd2luZG93LmxvY2F0aW9uLnJlbG9hZCgpO1xuICB9XG59KTtcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBQSxJQUFNLGlCQUFpQjtBQUN2QixJQUFNLGdCQUFnQixpQkFBaUIsT0FBTyxPQUFPLFNBQVMsV0FBVztBQUN6RSxJQUFNLFNBQVMsSUFBSSxVQUFVLGFBQWE7QUFFMUMsT0FBTyxpQkFBaUIsV0FBVyxPQUFPLFFBQVE7QUFDaEQsTUFBSSxJQUFJLFNBQVMsa0JBQWtCO0FBQ2pDLFdBQU8sU0FBUyxPQUFPO0FBQUEsRUFDekI7QUFDRixDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo=
|