vite-react-ssg 0.0.2 → 0.1.0

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.
@@ -7,6 +7,8 @@ const fs = require('fs-extra');
7
7
  const vite = require('vite');
8
8
  const JSDOM = require('jsdom');
9
9
  const SiteMetadataDefaults = require('./vite-react-ssg.4ca822c0.cjs');
10
+ const express = require('express');
11
+ const node_fs = require('node:fs');
10
12
  const React = require('react');
11
13
  const reactHelmetAsync = require('react-helmet-async');
12
14
  const server_js = require('react-router-dom/server.js');
@@ -16,6 +18,7 @@ const server = require('react-dom/server');
16
18
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
17
19
 
18
20
  const fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
21
+ const express__default = /*#__PURE__*/_interopDefaultCompat(express);
19
22
  const React__default = /*#__PURE__*/_interopDefaultCompat(React);
20
23
 
21
24
  function getDefaultExportFromCjs (x) {
@@ -875,9 +878,10 @@ function routesToPaths(routes) {
875
878
  pathToEntry[path] = /* @__PURE__ */ new Set([entry]);
876
879
  }
877
880
  if (!routes)
878
- return { paths: ["/"] };
881
+ return { paths: ["/"], pathToEntry };
879
882
  const paths = /* @__PURE__ */ new Set();
880
883
  const getPaths = (routes2, prefix = "") => {
884
+ const parentPath = prefix;
881
885
  prefix = prefix.replace(/\/$/g, "");
882
886
  for (const route of routes2) {
883
887
  let path = route.path;
@@ -885,6 +889,10 @@ function routesToPaths(routes) {
885
889
  path = prefix && !route.path.startsWith("/") ? `${prefix}${route.path ? `/${route.path}` : ""}` : route.path;
886
890
  paths.add(path);
887
891
  addEntry(path, route.entry);
892
+ if (pathToEntry[parentPath]) {
893
+ const pathCopy = path;
894
+ pathToEntry[parentPath].forEach((entry) => addEntry(pathCopy, entry));
895
+ }
888
896
  }
889
897
  if (route.index)
890
898
  addEntry(prefix, route.entry);
@@ -895,6 +903,39 @@ function routesToPaths(routes) {
895
903
  getPaths(routes);
896
904
  return { paths: Array.from(paths), pathToEntry };
897
905
  }
906
+ function createFetchRequest(req) {
907
+ const origin = `${req.protocol}://${req.get("host")}`;
908
+ const url = new URL(req.originalUrl || req.url, origin);
909
+ const controller = new AbortController();
910
+ req.on("close", () => controller.abort());
911
+ const headers = new Headers();
912
+ for (const [key, values] of Object.entries(req.headers)) {
913
+ if (values) {
914
+ if (Array.isArray(values)) {
915
+ for (const value of values)
916
+ headers.append(key, value);
917
+ } else {
918
+ headers.set(key, values);
919
+ }
920
+ }
921
+ }
922
+ const init = {
923
+ method: req.method,
924
+ headers,
925
+ signal: controller.signal
926
+ };
927
+ if (req.method !== "GET" && req.method !== "HEAD")
928
+ init.body = req.body;
929
+ return new Request(url.href, init);
930
+ }
931
+ async function resolveAlias(config, entry) {
932
+ const resolver = config.createResolver();
933
+ const result = await resolver(entry, config.root);
934
+ return result || node_path.join(config.root, entry);
935
+ }
936
+ const { version } = JSON.parse(
937
+ node_fs.readFileSync(new URL("../../package.json", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (document.currentScript && document.currentScript.src || new URL('shared/vite-react-ssg.823b31c9.cjs', document.baseURI).href)))).toString()
938
+ );
898
939
 
899
940
  async function getCritters(outDir, options = {}) {
900
941
  try {
@@ -965,17 +1006,18 @@ async function render(routes, request) {
965
1006
  throw context;
966
1007
  const router = server_js.createStaticRouter(dataRoutes, context);
967
1008
  const app = /* @__PURE__ */ React__default.createElement(reactHelmetAsync.HelmetProvider, { context: helmetContext }, /* @__PURE__ */ React__default.createElement(SiteMetadataDefaults.SiteMetadataDefaults, null), /* @__PURE__ */ React__default.createElement(server_js.StaticRouterProvider, { router, context }));
968
- const appHtml = await renderStaticApp(app);
1009
+ const appHTML = await renderStaticApp(app);
969
1010
  const { helmet } = helmetContext;
970
- helmet.htmlAttributes.toString();
971
- helmet.bodyAttributes.toString();
972
- [
1011
+ const htmlAttributes = helmet.htmlAttributes.toString();
1012
+ const bodyAttributes = helmet.bodyAttributes.toString();
1013
+ const metaStrings = [
973
1014
  helmet.title.toString(),
974
1015
  helmet.meta.toString(),
975
1016
  helmet.link.toString(),
976
1017
  helmet.script.toString()
977
1018
  ];
978
- return appHtml;
1019
+ const metaAttributes = metaStrings.filter(Boolean);
1020
+ return { appHTML, htmlAttributes, bodyAttributes, metaAttributes };
979
1021
  }
980
1022
 
981
1023
  function renderPreloadLinks(document, modules, ssrManifest) {
@@ -1011,7 +1053,7 @@ function renderPreloadLink(document, file) {
1011
1053
  });
1012
1054
  }
1013
1055
  }
1014
- function createLink(document) {
1056
+ function createLink$1(document) {
1015
1057
  return document.createElement("link");
1016
1058
  }
1017
1059
  function setAttrs(el, attrs) {
@@ -1023,11 +1065,68 @@ function appendLink(document, attrs) {
1023
1065
  const exits = document.head.querySelector(`link[href='${attrs.file}']`);
1024
1066
  if (exits)
1025
1067
  return;
1026
- const link = createLink(document);
1068
+ const link = createLink$1(document);
1027
1069
  setAttrs(link, attrs);
1028
1070
  document.head.appendChild(link);
1029
1071
  }
1030
1072
 
1073
+ async function renderHTML({
1074
+ rootContainerId,
1075
+ indexHTML,
1076
+ appHTML,
1077
+ metaAttributes,
1078
+ bodyAttributes,
1079
+ htmlAttributes,
1080
+ initialState
1081
+ }) {
1082
+ const stateScript = initialState ? `
1083
+ <script>window.__INITIAL_STATE__=${initialState}<\/script>` : "";
1084
+ const headStartTag = "<head>";
1085
+ const metaTags = metaAttributes.join("");
1086
+ indexHTML = indexHTML.replace(headStartTag, headStartTag + metaTags);
1087
+ const bodyStartTag = "<body";
1088
+ indexHTML = indexHTML.replace(bodyStartTag, `${bodyStartTag} ${bodyAttributes}`);
1089
+ const htmlStartTag = "<html";
1090
+ indexHTML = indexHTML.replace(htmlStartTag, `${htmlStartTag} ${htmlAttributes}`);
1091
+ const container = `<div id="${rootContainerId}"></div>`;
1092
+ if (indexHTML.includes(container)) {
1093
+ return indexHTML.replace(
1094
+ container,
1095
+ `<div id="${rootContainerId}" data-server-rendered="true">${appHTML}</div>${stateScript}`
1096
+ );
1097
+ }
1098
+ const html5Parser = await import('html5parser');
1099
+ const ast = html5Parser.parse(indexHTML);
1100
+ let renderedOutput;
1101
+ html5Parser.walk(ast, {
1102
+ enter: (node) => {
1103
+ if (!renderedOutput && node?.type === html5Parser.SyntaxKind.Tag && Array.isArray(node.attributes) && node.attributes.length > 0 && node.attributes.some((attr) => attr.name.value === "id" && attr.value?.value === rootContainerId)) {
1104
+ const attributesStringified = [...node.attributes.map(({ name: { value: name }, value }) => `${name}="${value.value}"`)].join(" ");
1105
+ const indexHTMLBefore = indexHTML.slice(0, node.start);
1106
+ const indexHTMLAfter = indexHTML.slice(node.end);
1107
+ renderedOutput = `${indexHTMLBefore}<${node.name} ${attributesStringified} data-server-rendered="true">${appHTML}</${node.name}>${stateScript}${indexHTMLAfter}`;
1108
+ }
1109
+ }
1110
+ });
1111
+ if (!renderedOutput)
1112
+ throw new Error(`Could not find a tag with id="${rootContainerId}" to replace it with server-side rendered HTML`);
1113
+ return renderedOutput;
1114
+ }
1115
+ async function detectEntry(root) {
1116
+ const scriptSrcReg = /<script(?:.*?)src=["'](.+?)["'](?!<)(?:.*)\>(?:[\n\r\s]*?)(?:<\/script>)/img;
1117
+ const html = await fs__default.readFile(node_path.join(root, "index.html"), "utf-8");
1118
+ const scripts = [...html.matchAll(scriptSrcReg)];
1119
+ const [, entry] = scripts.find((matchResult) => {
1120
+ const [script] = matchResult;
1121
+ const [, scriptType] = script.match(/.*\stype=(?:'|")?([^>'"\s]+)/i) || [];
1122
+ return scriptType === "module";
1123
+ }) || [];
1124
+ return entry || "src/main.ts";
1125
+ }
1126
+ function createLink(href) {
1127
+ return `<link rel="stylesheet" href="${href}">`;
1128
+ }
1129
+
1031
1130
  function DefaultIncludedRoutes(paths, _routes) {
1032
1131
  return paths.filter((i) => !i.includes(":") && !i.includes("*"));
1033
1132
  }
@@ -1098,7 +1197,7 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1098
1197
  const prefix = format === "esm" && process.platform === "win32" ? "file://" : "";
1099
1198
  const ext = format === "esm" ? ".mjs" : ".cjs";
1100
1199
  const serverEntry = node_path.join(prefix, ssgOut, node_path.parse(ssrEntry).name + ext);
1101
- const _require = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (document.currentScript && document.currentScript.src || new URL('shared/vite-react-ssg.0e1b881e.cjs', document.baseURI).href)));
1200
+ const _require = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (document.currentScript && document.currentScript.src || new URL('shared/vite-react-ssg.823b31c9.cjs', document.baseURI).href)));
1102
1201
  const { createRoot, includedRoutes: serverEntryIncludedRoutes } = format === "esm" ? await import(serverEntry) : _require(serverEntry);
1103
1202
  const includedRoutes = serverEntryIncludedRoutes || configIncludedRoutes;
1104
1203
  const { routes } = await createRoot(false);
@@ -1119,18 +1218,21 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1119
1218
  try {
1120
1219
  const appCtx = await createRoot(false, route);
1121
1220
  const { routes: routes2, initialState, triggerOnSSRAppRendered, transformState = SiteMetadataDefaults.serializeState } = appCtx;
1122
- const transformedIndexHTML = await onBeforePageRender?.(route, indexHTML, appCtx);
1221
+ const transformedIndexHTML = await onBeforePageRender?.(route, indexHTML, appCtx) || indexHTML;
1123
1222
  const url = new URL(route, "http://vite-react-ssg.com");
1124
1223
  url.search = "";
1125
1224
  url.hash = "";
1126
1225
  url.pathname = route;
1127
1226
  const request = new Request(url.href);
1128
- const appHTML = await render([...routes2], request);
1227
+ const { appHTML, bodyAttributes, htmlAttributes, metaAttributes } = await render([...routes2], request);
1129
1228
  await triggerOnSSRAppRendered?.(route, appHTML, appCtx);
1130
1229
  const renderedHTML = await renderHTML({
1131
1230
  rootContainerId,
1132
1231
  appHTML,
1133
- indexHTML,
1232
+ indexHTML: transformedIndexHTML,
1233
+ metaAttributes,
1234
+ bodyAttributes,
1235
+ htmlAttributes,
1134
1236
  initialState: null
1135
1237
  });
1136
1238
  const jsdom = new JSDOM.JSDOM(renderedHTML);
@@ -1171,59 +1273,11 @@ ${kolorist.gray("[vite-react-ssg]")} ${kolorist.green("Build finished.")}`);
1171
1273
  }, waitInSeconds * 1e3);
1172
1274
  timeout.unref();
1173
1275
  }
1174
- async function detectEntry(root) {
1175
- const scriptSrcReg = /<script(?:.*?)src=["'](.+?)["'](?!<)(?:.*)\>(?:[\n\r\s]*?)(?:<\/script>)/img;
1176
- const html = await fs__default.readFile(node_path.join(root, "index.html"), "utf-8");
1177
- const scripts = [...html.matchAll(scriptSrcReg)];
1178
- const [, entry] = scripts.find((matchResult) => {
1179
- const [script] = matchResult;
1180
- const [, scriptType] = script.match(/.*\stype=(?:'|")?([^>'"\s]+)/i) || [];
1181
- return scriptType === "module";
1182
- }) || [];
1183
- return entry || "src/main.ts";
1184
- }
1185
- async function resolveAlias(config, entry) {
1186
- const resolver = config.createResolver();
1187
- const result = await resolver(entry, config.root);
1188
- return result || node_path.join(config.root, entry);
1189
- }
1190
1276
  function rewriteScripts(indexHTML, mode) {
1191
1277
  if (!mode || mode === "sync")
1192
1278
  return indexHTML;
1193
1279
  return indexHTML.replace(/<script type="module" /g, `<script type="module" ${mode} `);
1194
1280
  }
1195
- async function renderHTML({
1196
- rootContainerId,
1197
- indexHTML,
1198
- appHTML,
1199
- initialState
1200
- }) {
1201
- const stateScript = initialState ? `
1202
- <script>window.__INITIAL_STATE__=${initialState}<\/script>` : "";
1203
- const container = `<div id="${rootContainerId}"></div>`;
1204
- if (indexHTML.includes(container)) {
1205
- return indexHTML.replace(
1206
- container,
1207
- `<div id="${rootContainerId}" data-server-rendered="true">${appHTML}</div>${stateScript}`
1208
- );
1209
- }
1210
- const html5Parser = await import('html5parser');
1211
- const ast = html5Parser.parse(indexHTML);
1212
- let renderedOutput;
1213
- html5Parser.walk(ast, {
1214
- enter: (node) => {
1215
- if (!renderedOutput && node?.type === html5Parser.SyntaxKind.Tag && Array.isArray(node.attributes) && node.attributes.length > 0 && node.attributes.some((attr) => attr.name.value === "id" && attr.value?.value === rootContainerId)) {
1216
- const attributesStringified = [...node.attributes.map(({ name: { value: name }, value }) => `${name}="${value.value}"`)].join(" ");
1217
- const indexHTMLBefore = indexHTML.slice(0, node.start);
1218
- const indexHTMLAfter = indexHTML.slice(node.end);
1219
- renderedOutput = `${indexHTMLBefore}<${node.name} ${attributesStringified} data-server-rendered="true">${appHTML}</${node.name}>${stateScript}${indexHTMLAfter}`;
1220
- }
1221
- }
1222
- });
1223
- if (!renderedOutput)
1224
- throw new Error(`Could not find a tag with id="${rootContainerId}" to replace it with server-side rendered HTML`);
1225
- return renderedOutput;
1226
- }
1227
1281
  async function formatHtml(html, formatting) {
1228
1282
  if (formatting === "minify") {
1229
1283
  const htmlMinifier = await import('html-minifier');
@@ -1259,4 +1313,185 @@ function collectModules(manifest, entry, mods = /* @__PURE__ */ new Set()) {
1259
1313
  return mods;
1260
1314
  }
1261
1315
 
1316
+ const SHORTCUTS = [
1317
+ {
1318
+ key: "u",
1319
+ description: "show server url",
1320
+ action(vite, _) {
1321
+ vite.config.logger.info("");
1322
+ printServerInfo(vite, true);
1323
+ }
1324
+ },
1325
+ {
1326
+ key: "c",
1327
+ description: "clear console",
1328
+ action(vite, _) {
1329
+ vite.config.logger.clearScreen("error");
1330
+ }
1331
+ },
1332
+ {
1333
+ key: "q",
1334
+ description: "quit",
1335
+ action(_, server) {
1336
+ server.close(() => process.exit());
1337
+ }
1338
+ }
1339
+ ];
1340
+ function bindShortcuts(viteServer, server) {
1341
+ if (!process.stdin.isTTY || process.env.CI)
1342
+ return;
1343
+ viteServer.config.logger.info(
1344
+ kolorist.dim(kolorist.green(" \u279C")) + kolorist.dim(" press ") + kolorist.bold("h") + kolorist.dim(" to show help")
1345
+ );
1346
+ const shortcuts = SHORTCUTS;
1347
+ let actionRunning = false;
1348
+ const onInput = async (input) => {
1349
+ if (input === "" || input === "") {
1350
+ try {
1351
+ await server.close();
1352
+ } finally {
1353
+ process.exit(1);
1354
+ }
1355
+ return;
1356
+ }
1357
+ if (actionRunning)
1358
+ return;
1359
+ if (input === "h") {
1360
+ viteServer.config.logger.info(
1361
+ [
1362
+ "",
1363
+ kolorist.bold(" Shortcuts"),
1364
+ ...shortcuts.map(
1365
+ (shortcut2) => kolorist.dim(" press ") + kolorist.bold(shortcut2.key) + kolorist.dim(` to ${shortcut2.description}`)
1366
+ )
1367
+ ].join("\n")
1368
+ );
1369
+ }
1370
+ const shortcut = shortcuts.find((shortcut2) => shortcut2.key === input);
1371
+ if (!shortcut)
1372
+ return;
1373
+ actionRunning = true;
1374
+ await shortcut.action(viteServer, server);
1375
+ actionRunning = false;
1376
+ };
1377
+ process.stdin.setRawMode(true);
1378
+ process.stdin.on("data", onInput).setEncoding("utf8").resume();
1379
+ server.on("close", () => {
1380
+ process.stdin.off("data", onInput).pause();
1381
+ });
1382
+ }
1383
+
1384
+ async function dev(ssgOptions = {}, viteConfig = {}) {
1385
+ const mode = process.env.MODE || process.env.NODE_ENV || ssgOptions.mode || "development";
1386
+ const config = await vite.resolveConfig(viteConfig, "serve", mode, mode);
1387
+ const cwd = process.cwd();
1388
+ const root = config.root || cwd;
1389
+ const {
1390
+ entry = await detectEntry(root),
1391
+ onBeforePageRender,
1392
+ onPageRendered,
1393
+ rootContainerId = "root"
1394
+ } = Object.assign({}, config.ssgOptions || {}, ssgOptions);
1395
+ const ssrEntry = await resolveAlias(config, entry);
1396
+ const template = await fs__default.readFile(node_path.join(root, "index.html"), "utf-8");
1397
+ let viteServer;
1398
+ globalThis.__ssr_start_time = performance.now();
1399
+ createServer().then((app) => {
1400
+ const port = viteServer.config.server.port || 5173;
1401
+ const server = app.listen(port, () => {
1402
+ printServerInfo(viteServer);
1403
+ bindShortcuts(viteServer, server);
1404
+ });
1405
+ });
1406
+ async function createServer() {
1407
+ process.env.__DEV_MODE_SSR = "true";
1408
+ const app = express__default();
1409
+ viteServer = await vite.createServer({
1410
+ // ...options,
1411
+ server: { middlewareMode: true },
1412
+ appType: "custom"
1413
+ });
1414
+ app.use(viteServer.middlewares);
1415
+ app.use("*", async (req, res) => {
1416
+ try {
1417
+ const url = req.originalUrl;
1418
+ const indexHTML = await viteServer.transformIndexHtml(url, template);
1419
+ const createRoot = await viteServer.ssrLoadModule(ssrEntry).then((m) => m.createRoot);
1420
+ const appCtx = await createRoot(false, url);
1421
+ const { routes } = appCtx;
1422
+ const transformedIndexHTML = await onBeforePageRender?.(url, indexHTML, appCtx) || indexHTML;
1423
+ const { appHTML, bodyAttributes, htmlAttributes, metaAttributes } = await render([...routes], createFetchRequest(req));
1424
+ const mod = await viteServer.moduleGraph.getModuleByUrl(entry);
1425
+ const assetsUrls = /* @__PURE__ */ new Set();
1426
+ const collectAssets = async (mod2) => {
1427
+ if (!mod2?.ssrTransformResult)
1428
+ return;
1429
+ const { deps = [], dynamicDeps = [] } = mod2?.ssrTransformResult;
1430
+ const allDeps = [...deps, ...dynamicDeps];
1431
+ for (const dep of allDeps) {
1432
+ if (dep.endsWith(".css")) {
1433
+ assetsUrls.add(dep);
1434
+ } else if (dep.endsWith(".ts") || dep.endsWith(".tsx")) {
1435
+ const depModule = await viteServer.moduleGraph.getModuleByUrl(dep);
1436
+ depModule && await collectAssets(depModule);
1437
+ }
1438
+ }
1439
+ };
1440
+ await collectAssets(mod);
1441
+ const preloadLink = [...assetsUrls].map((item) => createLink(item));
1442
+ metaAttributes.push(...preloadLink);
1443
+ const renderedHTML = await renderHTML({
1444
+ rootContainerId,
1445
+ appHTML,
1446
+ indexHTML: transformedIndexHTML,
1447
+ metaAttributes,
1448
+ bodyAttributes,
1449
+ htmlAttributes,
1450
+ initialState: null
1451
+ });
1452
+ const transformed = await onPageRendered?.(url, renderedHTML, appCtx) || renderedHTML;
1453
+ res.status(200).set({ "Content-Type": "text/html" }).end(transformed);
1454
+ } catch (e) {
1455
+ viteServer.ssrFixStacktrace(e);
1456
+ console.error(`[vite-react-ssg] error: ${e.stack}`);
1457
+ res.status(500).end(e.stack);
1458
+ }
1459
+ });
1460
+ return app;
1461
+ }
1462
+ }
1463
+ async function printServerInfo(server, onlyUrl = false) {
1464
+ const info = server.config.logger.info;
1465
+ const port = server.config.server.port || 5173;
1466
+ const url = `http://localhost:${port}/`;
1467
+ if (!onlyUrl) {
1468
+ let ssrReadyMessage = " -- SSR";
1469
+ if (globalThis.__ssr_start_time) {
1470
+ ssrReadyMessage += ` ready in ${kolorist.reset(kolorist.bold(`${Math.round(
1471
+ // @ts-expect-error global var
1472
+ performance.now() - globalThis.__ssr_start_time
1473
+ )}ms`))}`;
1474
+ }
1475
+ info(
1476
+ `
1477
+ ${kolorist.bgLightCyan(` VITE-REACT-SSG v${version} `)}`,
1478
+ { clear: !server.config.logger.hasWarned }
1479
+ );
1480
+ info(
1481
+ `${kolorist.cyan(`
1482
+ VITE v${vite.version}`) + kolorist.dim(ssrReadyMessage)}
1483
+ `
1484
+ );
1485
+ }
1486
+ info(
1487
+ kolorist.green(" dev server running at:")
1488
+ );
1489
+ printUrls(url, info);
1490
+ }
1491
+ function printUrls(url, info) {
1492
+ const colorUrl = (url2) => kolorist.cyan(url2.replace(/:(\d+)\//, (_, port) => `:${kolorist.bold(port)}/`));
1493
+ info(` ${kolorist.green("\u279C")} ${kolorist.bold("Local")}: ${colorUrl(url)}`);
1494
+ }
1495
+
1262
1496
  exports.build = build;
1497
+ exports.dev = dev;
@@ -126,6 +126,14 @@ interface ViteReactSSGClientOptions {
126
126
  * @default `#root`
127
127
  */
128
128
  rootContainer?: string | Element;
129
+ /**
130
+ * Use SSR during development.
131
+ *
132
+ * If false, the 'dev' script in 'package.json' needs to be set to `"vite"`.
133
+ *
134
+ * @default true
135
+ */
136
+ ssrWhenDev?: boolean;
129
137
  }
130
138
  interface CommonRouteOptions {
131
139
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vite-react-ssg",
3
3
  "type": "module",
4
- "version": "0.0.2",
4
+ "version": "0.1.0",
5
5
  "packageManager": "pnpm@8.6.6",
6
6
  "description": "",
7
7
  "author": "Riri <Daydreamerriri@outlook.com>",
@@ -75,6 +75,7 @@
75
75
  }
76
76
  },
77
77
  "dependencies": {
78
+ "express": "^4.18.2",
78
79
  "fs-extra": "^11.1.1",
79
80
  "html-minifier": "^4.0.0",
80
81
  "html5parser": "^2.0.2",
@@ -86,6 +87,7 @@
86
87
  },
87
88
  "devDependencies": {
88
89
  "@ririd/eslint-config": "0.6.0",
90
+ "@types/express": "^4.17.17",
89
91
  "@types/fs-extra": "^11.0.1",
90
92
  "@types/html-minifier": "^4.0.2",
91
93
  "@types/jsdom": "^21.1.1",