@ultimat3/render 22.2.0 → 22.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/render",
3
- "version": "22.2.0",
3
+ "version": "22.2.2",
4
4
  "description": "The route primitive and the five render modes: static, isr, ssr, stream, spa.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,11 +36,11 @@
36
36
  "test": "bun test"
37
37
  },
38
38
  "dependencies": {
39
- "@ultimat3/cache": "22.2.0",
40
- "@ultimat3/core": "22.2.0",
41
- "@ultimat3/http": "22.2.0",
42
- "@ultimat3/i18n": "22.2.0",
43
- "@ultimat3/seo": "22.2.0",
39
+ "@ultimat3/cache": "22.2.2",
40
+ "@ultimat3/core": "22.2.2",
41
+ "@ultimat3/http": "22.2.2",
42
+ "@ultimat3/i18n": "22.2.2",
43
+ "@ultimat3/seo": "22.2.2",
44
44
  "sass": "1.104.0"
45
45
  }
46
46
  }
@@ -4,11 +4,14 @@
4
4
  * `bun test` all load a component the same way and there is no separate "bundled" behaviour.
5
5
  */
6
6
 
7
+ // why: Bun ships no path API, and a relative app root has to be resolved against the working
8
+ // directory before it can be compared with the absolute paths the Bun plugin hands this loader.
9
+ import { resolve } from 'node:path';
7
10
  import { renderThrowable } from '@ultimat3/core';
8
11
  import { compileStylesheet, isGlobalStylesheet, stripCharset } from './css-modules';
9
12
  import { PrerenderFailedError } from './errors';
10
13
  import type { Surface } from './surfaces';
11
- import { surfaceOf } from './surfaces';
14
+ import { surfaceUnder } from './surfaces';
12
15
 
13
16
  /**
14
17
  * Why a transform at all: `tsconfig.json` says `jsx: 'preserve'`, which makes Bun fall back to the
@@ -101,6 +104,34 @@ export function registeredStylesheets(): readonly Stylesheet[] {
101
104
  return [...stylesheets.values()];
102
105
  }
103
106
 
107
+ /**
108
+ * The directory a sheet's surface is read below. Absent, the process's working directory — which
109
+ * is the app root in the container (`WORKDIR /app`) and under every `x` command run from one.
110
+ */
111
+ let stylesheetRoot: string | undefined;
112
+
113
+ const surfaceOfSheet = (path: string): Surface | null =>
114
+ surfaceUnder(stylesheetRoot ?? process.cwd(), path);
115
+
116
+ /**
117
+ * Names the app root the registry classifies against; `loadApp` calls it before importing a module.
118
+ * Every sheet already registered is classified again, because a sheet can load before the root is
119
+ * named — and a classification frozen then would keep the answer the wrong root gave. The revision
120
+ * moves only when an answer does, so naming the same root twice mints no new stylesheet URL.
121
+ * A relative root is resolved against the working directory. `undefined` returns to the working
122
+ * directory.
123
+ */
124
+ export function setStylesheetRoot(root: string | undefined): void {
125
+ // Resolved: `loadApp('.')` is a legal call, and `.` is a prefix of no absolute path.
126
+ stylesheetRoot = root === undefined ? undefined : resolve(root);
127
+ for (const [path, sheet] of stylesheets) {
128
+ const surface = surfaceOfSheet(path);
129
+ if (surface === sheet.surface) continue;
130
+ stylesheets.set(path, { ...sheet, surface });
131
+ revision += 1;
132
+ }
133
+ }
134
+
104
135
  /** Test seam: the registry is process-global because the module cache it mirrors is too. */
105
136
  export function clearStylesheets(): void {
106
137
  if (stylesheets.size > 0) revision += 1;
@@ -159,7 +190,7 @@ export function loadStylesheet(path: string, source: string): string {
159
190
  if (stylesheets.get(path)?.css !== compiled.css) revision += 1;
160
191
  stylesheets.set(path, {
161
192
  file: path,
162
- surface: surfaceOf(path),
193
+ surface: surfaceOfSheet(path),
163
194
  global: isGlobalStylesheet(path),
164
195
  css: compiled.css,
165
196
  });
package/src/server.ts CHANGED
@@ -30,6 +30,7 @@ export {
30
30
  installRenderLoader,
31
31
  loadStylesheet,
32
32
  registeredStylesheets,
33
+ setStylesheetRoot,
33
34
  stylesFor,
34
35
  stylesheetsRevision,
35
36
  transformTsx,
package/src/surfaces.ts CHANGED
@@ -91,6 +91,25 @@ export function surfaceOf(file: string): Surface | null {
91
91
  return locateSurface(file)?.surface ?? null;
92
92
  }
93
93
 
94
+ /**
95
+ * The surface of an ABSOLUTE path, read from the part below the app `root`. `surfaceOf` takes the
96
+ * first surface-named segment, which is right for the root-relative paths the route table and the
97
+ * boundary check hold — and wrong for an absolute one whose root is itself called `app/`: the
98
+ * scaffold's container runs from `WORKDIR /app`, and every stylesheet there read as `app`, so the
99
+ * site bundle was empty and every site document shipped with no stylesheet at all.
100
+ *
101
+ * A file outside `root` is read as `surfaceOf` reads it. A file inside an installed package
102
+ * (`node_modules/…`) has no surface whatever its directories are called: it is a package sheet,
103
+ * which both graphs carry.
104
+ */
105
+ export function surfaceUnder(root: string, file: string): Surface | null {
106
+ const base = normalize(root).replace(/\/+$/, '');
107
+ const path = normalize(file);
108
+ const relative = path.startsWith(`${base}/`) ? path.slice(base.length + 1) : path;
109
+ if (/(?:^|\/)node_modules\//.test(relative)) return null;
110
+ return surfaceOf(relative);
111
+ }
112
+
94
113
  function normalize(file: string): string {
95
114
  return file.replace(/\\/g, '/').replace(/^\.\//, '');
96
115
  }