@ultimat3/render 22.3.3 → 22.3.5

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.3.3",
3
+ "version": "22.3.5",
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.3.3",
40
- "@ultimat3/core": "22.3.3",
41
- "@ultimat3/http": "22.3.3",
42
- "@ultimat3/i18n": "22.3.3",
43
- "@ultimat3/seo": "22.3.3",
39
+ "@ultimat3/cache": "22.3.5",
40
+ "@ultimat3/core": "22.3.5",
41
+ "@ultimat3/http": "22.3.5",
42
+ "@ultimat3/i18n": "22.3.5",
43
+ "@ultimat3/seo": "22.3.5",
44
44
  "sass": "1.104.0"
45
45
  }
46
46
  }
@@ -78,6 +78,12 @@ export interface Stylesheet {
78
78
  readonly surface: Surface | null;
79
79
  /** A plain (non-module) stylesheet: the tokens and the reset, which the cascade needs first. */
80
80
  readonly global: boolean;
81
+ /**
82
+ * Registered only by an ISLAND build, never by the server's module graph — what `x build --target
83
+ * docker` records in the island store. Not an ordering key: every sheet is ordered by
84
+ * `stylesheetOrder`, whoever registered it.
85
+ */
86
+ readonly island: boolean;
81
87
  readonly css: string;
82
88
  }
83
89
 
@@ -139,18 +145,25 @@ export function clearStylesheets(): void {
139
145
  }
140
146
 
141
147
  /**
142
- * The CSS a document on `surface` must carry: the global layer first, then the modules, each group
143
- * in load order. A `site/` page never receives `app/` CSS — that is axiom 6 applied to bytes the
144
- * browser parses, not just bytes it executes.
148
+ * The CSS a document on `surface` must carry. A `site/` page never receives `app/` CSS — that is
149
+ * axiom 6 applied to bytes the browser parses, not just bytes it executes.
145
150
  *
146
151
  * `shared/` is carried by both graphs by definition — it is the one directory both surfaces import
147
152
  * from, and it is where an app's own global stylesheet lives. Filtering it out (which this did)
148
153
  * meant an app could put its tokens in the one place the convention names and have every document
149
154
  * silently drop them.
150
155
  *
151
- * Globals sort ahead of modules rather than riding load order: the reset styles bare elements at
152
- * the lowest specificity there is, so a reset that happened to register after a module rule wins
153
- * ties it must lose. Insertion order alone made that depend on which page a request hit first.
156
+ * THE ORDER IS A FUNCTION OF THE SHEETS, NEVER OF THEIR ARRIVAL (22.3.5). A sheet registers in Bun's
157
+ * `onLoad`, which runs as the loader fetches a module's dependencies — in parallel — and an island
158
+ * build's too: the arrival order differed on every boot of one image, and so did the joined bytes
159
+ * and the `/styles/<hash>.css` minted from them (notificado.co, 22.3.2–22.3.4: two pods, two URLs,
160
+ * each the other's 404). `stylesheetOrder` is the whole rule:
161
+ *
162
+ * | key | first | why |
163
+ * |---|---|---|
164
+ * | global | the global layer | the reset styles bare elements at the lowest specificity there is, so a reset after a module rule wins ties it must lose |
165
+ * | owner | a package's sheet, then `shared/`, then the surface's own | library before app: an app's rule wins a tie against the component it restyles |
166
+ * | path | app-root-relative, by code unit | a total order; the same on every machine |
154
167
  */
155
168
  export function stylesFor(surface: Surface | null): string {
156
169
  const carried = [...stylesheets.values()].filter(
@@ -158,11 +171,30 @@ export function stylesFor(surface: Surface | null): string {
158
171
  );
159
172
  // `stripCharset` on every sheet, not only the first: a `@charset` or a BOM is legal at byte 0 of
160
173
  // a FILE and nowhere else, and this join is what turns seven files into one.
161
- return [...carried.filter((sheet) => sheet.global), ...carried.filter((sheet) => !sheet.global)]
174
+ return carried
175
+ .sort(stylesheetOrder)
162
176
  .map((sheet) => stripCharset(sheet.css))
163
177
  .join('');
164
178
  }
165
179
 
180
+ const OWNER_RANK = (sheet: Stylesheet): number =>
181
+ sheet.surface === null ? 0 : sheet.surface === 'shared' ? 1 : 2;
182
+
183
+ /** Relative to the app root, so a container at `/app` and a laptop order one app alike. */
184
+ const orderPath = (sheet: Stylesheet): string => {
185
+ const root = stylesheetRoot ?? process.cwd();
186
+ return sheet.file.startsWith(`${root}/`) ? sheet.file.slice(root.length + 1) : sheet.file;
187
+ };
188
+
189
+ /** See `stylesFor`: global layer, then owner, then path. Exported for the tests that pin it. */
190
+ export function stylesheetOrder(a: Stylesheet, b: Stylesheet): number {
191
+ if (a.global !== b.global) return a.global ? -1 : 1;
192
+ const owner = OWNER_RANK(a) - OWNER_RANK(b);
193
+ if (owner !== 0) return owner;
194
+ const [x, y] = [orderPath(a), orderPath(b)];
195
+ return x < y ? -1 : x > y ? 1 : 0;
196
+ }
197
+
166
198
  /**
167
199
  * `.tsx` source → JS calling the server factory. Exported so the transform is testable as a pure
168
200
  * function: the plugin below is the six lines of glue that hand it a file.
@@ -181,17 +213,26 @@ export function transformTsx(source: string): string {
181
213
  * compilation — so a token file `@use`d by twenty `page.module.scss` would inline its `:root` block
182
214
  * twenty times. One file, imported for its side effect, is the shape that cannot do that.
183
215
  */
184
- export function loadStylesheet(path: string, source: string): string {
216
+ export function loadStylesheet(
217
+ path: string,
218
+ source: string,
219
+ origin: 'module' | 'island' = 'module',
220
+ ): string {
185
221
  const compiled = compileStylesheet(path, source);
186
222
  // An EMPTY compile unregisters: under `x dev` an edit that deleted every rule left the old entry
187
223
  // in place, serving rules the file no longer had until the process restarted.
188
224
  if (compiled.css.length === 0 && stylesheets.delete(path)) revision += 1;
189
225
  if (compiled.css.length > 0) {
190
- if (stylesheets.get(path)?.css !== compiled.css) revision += 1;
226
+ const held = stylesheets.get(path);
227
+ // Island-only until the server graph loads it too; then it takes its place in load order.
228
+ const island = origin === 'island' && (held === undefined || held.island);
229
+ if (held?.css !== compiled.css || held.island !== island) revision += 1;
230
+ if (held?.island === true && !island) stylesheets.delete(path);
191
231
  stylesheets.set(path, {
192
232
  file: path,
193
233
  surface: surfaceOfSheet(path),
194
234
  global: isGlobalStylesheet(path),
235
+ island,
195
236
  css: compiled.css,
196
237
  });
197
238
  }