@plurid/plurid-react-server 0.0.0-16 → 0.0.0-17

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.
@@ -46,6 +46,14 @@ interface PluridServerOptions {
46
46
  * Name of the directory where the assets files are bundled.
47
47
  */
48
48
  assetsDirectory: string;
49
+ /**
50
+ * Directory of static assets (favicon, og-image, manifest, robots) served at
51
+ * the URL root `/`. Empty string (the default) resolves to
52
+ * `<buildDirectory>/public`; the mount is skipped if the directory does not
53
+ * exist, so apps without a public directory are unaffected. The framework
54
+ * points this at `source/public` during `plurid dev`.
55
+ */
56
+ publicDirectory: string;
49
57
  /**
50
58
  * Default: `/gateway`.
51
59
  */
@@ -136,6 +144,44 @@ interface PluridServerTemplateConfiguration {
136
144
  */
137
145
  defaultPreloadedPluridMetastate?: string;
138
146
  minify?: boolean;
147
+ /**
148
+ * Favicon links injected into `<head>`. A bare string is the primary icon
149
+ * (`rel="icon"`); the object expands to icon / apple-touch-icon / sized /
150
+ * mask-icon links plus a `theme-color` meta. Paths resolve against the served
151
+ * public directory (see `PluridServerOptions.publicDirectory`).
152
+ */
153
+ favicon?: string | {
154
+ icon?: string;
155
+ apple?: string;
156
+ sizes?: Record<string, string>;
157
+ maskIcon?: string;
158
+ themeColor?: string;
159
+ };
160
+ /**
161
+ * Web app manifest href, injected as `<link rel="manifest">`.
162
+ */
163
+ manifest?: string;
164
+ /**
165
+ * Static `<head>` metadata injected AHEAD of the react-helmet-async output,
166
+ * so per-route `<Helmet>` tags still override these defaults.
167
+ */
168
+ head?: {
169
+ title?: string;
170
+ description?: string;
171
+ meta?: Array<{
172
+ name?: string;
173
+ property?: string;
174
+ content: string;
175
+ }>;
176
+ links?: Array<{
177
+ rel: string;
178
+ href: string;
179
+ }>;
180
+ };
181
+ /**
182
+ * Override the built-in 500 error page HTML (sent on a render failure).
183
+ */
184
+ errorHtml?: string;
139
185
  }
140
186
  interface PluridStillerOptions {
141
187
  /**
@@ -213,6 +259,12 @@ declare class PluridServer {
213
259
  private resolvePreserveAfterServe;
214
260
  private handleGateway;
215
261
  private renderApplication;
262
+ /**
263
+ * Build the static `<head>` markup from the template config (favicon set,
264
+ * manifest, default title / meta / links). Returns an empty string when none
265
+ * are configured, so the head is byte-identical to before for existing apps.
266
+ */
267
+ private buildStaticHead;
216
268
  private getContentAndStyles;
217
269
  private computeRequestTime;
218
270
  private handleOptions;
@@ -46,6 +46,14 @@ interface PluridServerOptions {
46
46
  * Name of the directory where the assets files are bundled.
47
47
  */
48
48
  assetsDirectory: string;
49
+ /**
50
+ * Directory of static assets (favicon, og-image, manifest, robots) served at
51
+ * the URL root `/`. Empty string (the default) resolves to
52
+ * `<buildDirectory>/public`; the mount is skipped if the directory does not
53
+ * exist, so apps without a public directory are unaffected. The framework
54
+ * points this at `source/public` during `plurid dev`.
55
+ */
56
+ publicDirectory: string;
49
57
  /**
50
58
  * Default: `/gateway`.
51
59
  */
@@ -136,6 +144,44 @@ interface PluridServerTemplateConfiguration {
136
144
  */
137
145
  defaultPreloadedPluridMetastate?: string;
138
146
  minify?: boolean;
147
+ /**
148
+ * Favicon links injected into `<head>`. A bare string is the primary icon
149
+ * (`rel="icon"`); the object expands to icon / apple-touch-icon / sized /
150
+ * mask-icon links plus a `theme-color` meta. Paths resolve against the served
151
+ * public directory (see `PluridServerOptions.publicDirectory`).
152
+ */
153
+ favicon?: string | {
154
+ icon?: string;
155
+ apple?: string;
156
+ sizes?: Record<string, string>;
157
+ maskIcon?: string;
158
+ themeColor?: string;
159
+ };
160
+ /**
161
+ * Web app manifest href, injected as `<link rel="manifest">`.
162
+ */
163
+ manifest?: string;
164
+ /**
165
+ * Static `<head>` metadata injected AHEAD of the react-helmet-async output,
166
+ * so per-route `<Helmet>` tags still override these defaults.
167
+ */
168
+ head?: {
169
+ title?: string;
170
+ description?: string;
171
+ meta?: Array<{
172
+ name?: string;
173
+ property?: string;
174
+ content: string;
175
+ }>;
176
+ links?: Array<{
177
+ rel: string;
178
+ href: string;
179
+ }>;
180
+ };
181
+ /**
182
+ * Override the built-in 500 error page HTML (sent on a render failure).
183
+ */
184
+ errorHtml?: string;
139
185
  }
140
186
  interface PluridStillerOptions {
141
187
  /**
@@ -213,6 +259,12 @@ declare class PluridServer {
213
259
  private resolvePreserveAfterServe;
214
260
  private handleGateway;
215
261
  private renderApplication;
262
+ /**
263
+ * Build the static `<head>` markup from the template config (favicon set,
264
+ * manifest, default title / meta / links). Returns an empty string when none
265
+ * are configured, so the head is byte-identical to before for existing apps.
266
+ */
267
+ private buildStaticHead;
216
268
  private getContentAndStyles;
217
269
  private computeRequestTime;
218
270
  private handleOptions;
@@ -248,7 +248,7 @@ var template = async (data) => {
248
248
 
249
249
  ${headScripts.join("\n")}
250
250
 
251
- <script src="${vendorScriptSource}"></script>
251
+ ${vendorScriptSource ? `<script src="${vendorScriptSource}"></script>` : ""}
252
252
  <script defer src="${mainScriptSource}"></script>
253
253
  </head>
254
254
  <body ${bodyAttributes}>
@@ -328,7 +328,7 @@ var PluridRenderer = class {
328
328
  this.styles = styles;
329
329
  this.headScripts = headScripts;
330
330
  this.bodyScripts = bodyScripts;
331
- this.vendorScriptSource = vendorScriptSource || DEFAULT_RENDERER_VENDOR_SCRIPT_SOURCE;
331
+ this.vendorScriptSource = vendorScriptSource ?? DEFAULT_RENDERER_VENDOR_SCRIPT_SOURCE;
332
332
  this.mainScriptSource = mainScriptSource || DEFAULT_RENDERER_MAIN_SCRIPT_SOURCE;
333
333
  this.root = root || DEFAULT_RENDERER_ROOT;
334
334
  this.content = assetsPathRewrite(content) || "";
@@ -883,7 +883,7 @@ var PluridServer = class {
883
883
  error
884
884
  );
885
885
  }
886
- response.status(500).send(SERVER_ERROR_TEMPLATE);
886
+ response.status(500).send(this.template?.errorHtml || SERVER_ERROR_TEMPLATE);
887
887
  return;
888
888
  }
889
889
  }
@@ -991,7 +991,7 @@ var PluridServer = class {
991
991
  error
992
992
  );
993
993
  }
994
- response.status(500).send(SERVER_ERROR_TEMPLATE);
994
+ response.status(500).send(this.template?.errorHtml || SERVER_ERROR_TEMPLATE);
995
995
  return;
996
996
  }
997
997
  }
@@ -1163,7 +1163,7 @@ var PluridServer = class {
1163
1163
  const {
1164
1164
  helmet
1165
1165
  } = this.helmet;
1166
- const head = helmet ? `
1166
+ const helmetHead = helmet ? `
1167
1167
  ${helmet.meta.toString()}
1168
1168
  ${helmet.title.toString()}
1169
1169
  ${helmet.base.toString()}
@@ -1172,6 +1172,7 @@ var PluridServer = class {
1172
1172
  ${helmet.noscript.toString()}
1173
1173
  ${helmet.script.toString()}
1174
1174
  ` : "";
1175
+ const head = helmetHead + this.buildStaticHead();
1175
1176
  const htmlAttributes = {
1176
1177
  ...this.template?.htmlAttributes,
1177
1178
  ...helmet?.htmlAttributes.toComponent()
@@ -1210,6 +1211,67 @@ var PluridServer = class {
1210
1211
  });
1211
1212
  return renderer;
1212
1213
  }
1214
+ /**
1215
+ * Build the static `<head>` markup from the template config (favicon set,
1216
+ * manifest, default title / meta / links). Returns an empty string when none
1217
+ * are configured, so the head is byte-identical to before for existing apps.
1218
+ */
1219
+ buildStaticHead() {
1220
+ const template2 = this.template;
1221
+ if (!template2) {
1222
+ return "";
1223
+ }
1224
+ const escapeAttribute2 = (value) => value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
1225
+ const escapeText = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1226
+ const parts = [];
1227
+ const favicon = template2.favicon;
1228
+ if (typeof favicon === "string") {
1229
+ parts.push(`<link rel="icon" href="${escapeAttribute2(favicon)}">`);
1230
+ } else if (favicon) {
1231
+ if (favicon.icon) {
1232
+ parts.push(`<link rel="icon" href="${escapeAttribute2(favicon.icon)}">`);
1233
+ }
1234
+ if (favicon.apple) {
1235
+ parts.push(`<link rel="apple-touch-icon" href="${escapeAttribute2(favicon.apple)}">`);
1236
+ }
1237
+ for (const [size, href] of Object.entries(favicon.sizes || {})) {
1238
+ parts.push(`<link rel="icon" sizes="${escapeAttribute2(size)}" href="${escapeAttribute2(href)}">`);
1239
+ }
1240
+ if (favicon.maskIcon) {
1241
+ const color = favicon.themeColor ? ` color="${escapeAttribute2(favicon.themeColor)}"` : "";
1242
+ parts.push(`<link rel="mask-icon" href="${escapeAttribute2(favicon.maskIcon)}"${color}>`);
1243
+ }
1244
+ if (favicon.themeColor) {
1245
+ parts.push(`<meta name="theme-color" content="${escapeAttribute2(favicon.themeColor)}">`);
1246
+ }
1247
+ }
1248
+ if (template2.manifest) {
1249
+ parts.push(`<link rel="manifest" href="${escapeAttribute2(template2.manifest)}">`);
1250
+ }
1251
+ const head = template2.head;
1252
+ if (head) {
1253
+ if (head.title) {
1254
+ parts.push(`<title>${escapeText(head.title)}</title>`);
1255
+ }
1256
+ if (head.description) {
1257
+ parts.push(`<meta name="description" content="${escapeAttribute2(head.description)}">`);
1258
+ }
1259
+ for (const meta of head.meta || []) {
1260
+ const selector = meta.name ? `name="${escapeAttribute2(meta.name)}"` : meta.property ? `property="${escapeAttribute2(meta.property)}"` : "";
1261
+ if (!selector) {
1262
+ continue;
1263
+ }
1264
+ parts.push(`<meta ${selector} content="${escapeAttribute2(meta.content)}">`);
1265
+ }
1266
+ for (const link of head.links || []) {
1267
+ parts.push(`<link rel="${escapeAttribute2(link.rel)}" href="${escapeAttribute2(link.href)}">`);
1268
+ }
1269
+ }
1270
+ if (parts.length === 0) {
1271
+ return "";
1272
+ }
1273
+ return "\n " + parts.join("\n ");
1274
+ }
1213
1275
  async getContentAndStyles(isoMatch, pluridMetastate, preserveResult, matchedPlane) {
1214
1276
  const stylesheet = new import_styled_components2.ServerStyleSheet();
1215
1277
  let content = "";
@@ -1283,6 +1345,7 @@ var PluridServer = class {
1283
1345
  open: partialOptions?.open ?? DEFAULT_SERVER_OPTIONS.OPEN,
1284
1346
  buildDirectory: partialOptions?.buildDirectory || DEFAULT_SERVER_OPTIONS.BUILD_DIRECTORY,
1285
1347
  assetsDirectory: partialOptions?.assetsDirectory || DEFAULT_SERVER_OPTIONS.ASSETS_DIRECTORY,
1348
+ publicDirectory: partialOptions?.publicDirectory || "",
1286
1349
  gatewayEndpoint: partialOptions?.gatewayEndpoint || DEFAULT_SERVER_OPTIONS.GATEWAY,
1287
1350
  staticCache: partialOptions?.staticCache || 0,
1288
1351
  ignore: partialOptions?.ignore || [],
@@ -1335,6 +1398,15 @@ var PluridServer = class {
1335
1398
  maxAge: this.options.staticCache
1336
1399
  })
1337
1400
  );
1401
+ const publicPath = this.options.publicDirectory || import_path2.default.join(this.options.buildDirectory, "public");
1402
+ if (import_fs2.default.existsSync(publicPath)) {
1403
+ this.serverApplication.use(
1404
+ import_express.default.static(publicPath, {
1405
+ index: false,
1406
+ maxAge: this.options.staticCache
1407
+ })
1408
+ );
1409
+ }
1338
1410
  this.loadMiddleware();
1339
1411
  }
1340
1412
  loadMiddleware() {