@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 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 === "/" || url?.startsWith("/" + appPkg.appframe.article.id)) {
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
- let html = await getArticleHtml();
113
- html = await vite.transformIndexHtml(url ?? "", html);
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) {
@@ -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;
@@ -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.0",
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": "ba8b3dc19b519eee6a3732526f4d1c10d0e00649"
46
+ "gitHead": "9b6e8db4b3161a36eac6dc807095ba3fcea51ca5"
47
47
  }