@olenbetong/appframe-vite 4.1.0 → 4.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/devServer.js +56 -3
- package/lib/localization.d.ts +0 -1
- package/lib/localization.js +14 -3
- package/package.json +2 -2
package/lib/devServer.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Client } from "@olenbetong/appframe-data";
|
|
|
2
2
|
import dotenv from "dotenv";
|
|
3
3
|
import { exists } from "fs-extra";
|
|
4
4
|
import { JSDOM } from "jsdom";
|
|
5
|
+
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
5
6
|
import { resolve } from "node:path";
|
|
6
7
|
import { importJson } from "./importJson.js";
|
|
7
8
|
import { getStringCache } from "./localization.js";
|
|
@@ -41,6 +42,18 @@ export function getProxyRoutes() {
|
|
|
41
42
|
}
|
|
42
43
|
return proxyRoutes;
|
|
43
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* We want to keep the development environment as close to the production environment as possible,
|
|
47
|
+
* so we fetch the article HTML from Synergi. There are however some changes we need to do:
|
|
48
|
+
*
|
|
49
|
+
* - Remove any article scripts and styles (they are the production version of the application)
|
|
50
|
+
* - Remove Bugsnag (don't want to report all unhandled exceptions in development)
|
|
51
|
+
* - Remove the web-vitals (only want production results when measuring performance)
|
|
52
|
+
* - Remove the server worker registration
|
|
53
|
+
* - Add strings from local string cache to avoid tons of localize requests when starting the application
|
|
54
|
+
* - Add the hostname to links with absolute paths (they are not part of the application, and will fail without host)
|
|
55
|
+
* @returns
|
|
56
|
+
*/
|
|
44
57
|
async function getArticleHtml() {
|
|
45
58
|
let { hostname, username, password } = await getLoginInfo();
|
|
46
59
|
let article = appframe.article?.id;
|
|
@@ -104,15 +117,55 @@ async function getArticleHtml() {
|
|
|
104
117
|
}
|
|
105
118
|
throw Error(`Failed to get article '${article}' from '${hostname}': ${response.status} ${response.statusText}`);
|
|
106
119
|
}
|
|
120
|
+
const cachePath = resolve(process.cwd(), "./node_modules/.appframe");
|
|
121
|
+
const cacheFile = resolve(cachePath, "./index.html");
|
|
122
|
+
async function getCacheAge() {
|
|
123
|
+
if (await exists(cacheFile)) {
|
|
124
|
+
let file = await stat(cacheFile);
|
|
125
|
+
let age = (Date.now() - file.mtime.getTime()) / 1000;
|
|
126
|
+
return age;
|
|
127
|
+
}
|
|
128
|
+
return -1;
|
|
129
|
+
}
|
|
130
|
+
async function getCachedHtml(age) {
|
|
131
|
+
let maxAge = 3 * 60 * 60; // 3 hours
|
|
132
|
+
if (age < maxAge) {
|
|
133
|
+
return await readFile(cacheFile, { encoding: "utf-8" });
|
|
134
|
+
}
|
|
135
|
+
return "";
|
|
136
|
+
}
|
|
107
137
|
export function createDevMiddleware(vite) {
|
|
108
138
|
return async (req, res, next) => {
|
|
109
139
|
const url = req.originalUrl;
|
|
110
|
-
if (url === "/"
|
|
140
|
+
if (url === "/") {
|
|
141
|
+
res.statusCode = 302;
|
|
142
|
+
res.setHeader("Location", `/${appPkg.appframe.article.id}`);
|
|
143
|
+
res.end("");
|
|
144
|
+
}
|
|
145
|
+
else if (url?.startsWith("/" + appPkg.appframe.article.id)) {
|
|
111
146
|
try {
|
|
112
|
-
|
|
113
|
-
|
|
147
|
+
// Unless the browser is explicitly disabling cache by setting the Cache-Control header
|
|
148
|
+
// to "no-cache", we cache the generated HTML for up to 3 hours
|
|
149
|
+
let cacheControl = req.headers["cache-control"];
|
|
150
|
+
let html = "";
|
|
151
|
+
let age = await getCacheAge();
|
|
152
|
+
if (cacheControl !== "no-cache" && age > 0) {
|
|
153
|
+
html = await getCachedHtml(age);
|
|
154
|
+
}
|
|
155
|
+
if (!html) {
|
|
156
|
+
html = await getArticleHtml();
|
|
157
|
+
html = await vite.transformIndexHtml(url ?? "", html);
|
|
158
|
+
if (!(await exists(cacheFile))) {
|
|
159
|
+
await mkdir(cachePath, { recursive: true });
|
|
160
|
+
}
|
|
161
|
+
await writeFile(cacheFile, html, { encoding: "utf-8" });
|
|
162
|
+
}
|
|
114
163
|
res.statusCode = 200;
|
|
115
164
|
res.setHeader("Content-Type", "text/html");
|
|
165
|
+
res.setHeader("Cache-Control", "max-age=10800000");
|
|
166
|
+
if (age >= 0) {
|
|
167
|
+
res.setHeader("Age", age);
|
|
168
|
+
}
|
|
116
169
|
res.end(html);
|
|
117
170
|
}
|
|
118
171
|
catch (error) {
|
package/lib/localization.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Connect } from "vite";
|
|
2
2
|
export declare function addStringToCache(text: string, translation: string): void;
|
|
3
|
-
export declare function getStringFromCache(text: string): string | undefined;
|
|
4
3
|
export declare const localizeMiddleware: Connect.NextHandleFunction;
|
|
5
4
|
export declare function getStringCache(): {
|
|
6
5
|
[k: string]: string;
|
package/lib/localization.js
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* There can be potentially many requests to localize strings on startup,
|
|
3
|
+
* and since the dev server runs on HTTP 1.1, it is limited to just a few
|
|
4
|
+
* requests at the same time. This means starting the application sometimes
|
|
5
|
+
* takes a long time, because the data fetch requests are queued behind a lot
|
|
6
|
+
* of localize requests.
|
|
7
|
+
*
|
|
8
|
+
* To fix this, we add a middleware that handles the localize route, and
|
|
9
|
+
* cache the proxied results in a file under /node_modules/.appframe. In the
|
|
10
|
+
* handler that generates the article HTML, we then get the cached strings,
|
|
11
|
+
* and add a script that adds the strings to the client cache.
|
|
12
|
+
*/
|
|
1
13
|
import { exists } from "fs-extra";
|
|
2
14
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
15
|
import { resolve } from "node:path";
|
|
@@ -27,14 +39,13 @@ async function saveCacheToFile() {
|
|
|
27
39
|
let data = JSON.stringify(Object.fromEntries(stringCache));
|
|
28
40
|
await writeFile(cacheFile, data, { encoding: "utf-8" });
|
|
29
41
|
}
|
|
42
|
+
// Avoid running 10s to 100s of writes in a row by debouncing the
|
|
43
|
+
// cache save operation.
|
|
30
44
|
const debouncedSaveCacheToFile = debounce(saveCacheToFile, 1000);
|
|
31
45
|
export function addStringToCache(text, translation) {
|
|
32
46
|
stringCache.set(text, translation);
|
|
33
47
|
debouncedSaveCacheToFile();
|
|
34
48
|
}
|
|
35
|
-
export function getStringFromCache(text) {
|
|
36
|
-
return stringCache.get(text);
|
|
37
|
-
}
|
|
38
49
|
export const localizeMiddleware = async (req, res, next) => {
|
|
39
50
|
// @ts-ignore
|
|
40
51
|
let text = req.body.text;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@olenbetong/appframe-vite",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.2",
|
|
4
4
|
"description": "Tools to use and deploy Vite applications to Appframe",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -43,5 +43,5 @@
|
|
|
43
43
|
"open": "^9.1.0",
|
|
44
44
|
"tcp-port-used": "^1.0.2"
|
|
45
45
|
},
|
|
46
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "9b6e8db4b3161a36eac6dc807095ba3fcea51ca5"
|
|
47
47
|
}
|