@animus-ui/layergate 0.0.0-stage → 0.1.35

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.
Files changed (46) hide show
  1. package/README.md +239 -2
  2. package/dist/compile-C4GyCiuY.cjs +323 -0
  3. package/dist/compile-Cu_GFS3l.mjs +295 -0
  4. package/dist/compile-DZwVBjTM.d.cts +126 -0
  5. package/dist/compile-DZwVBjTM.d.cts.map +1 -0
  6. package/dist/compile-DZwVBjTM.d.mts +126 -0
  7. package/dist/compile-DZwVBjTM.d.mts.map +1 -0
  8. package/dist/host-BGcb1rMJ.mjs +169 -0
  9. package/dist/host-C4ZaMdkx.cjs +210 -0
  10. package/dist/index.cjs +6 -0
  11. package/dist/index.d.cts +3 -0
  12. package/dist/index.d.mts +3 -0
  13. package/dist/index.mjs +3 -0
  14. package/dist/plan-Bsn_yxS9.mjs +29 -0
  15. package/dist/plan-hvk04qzh.cjs +40 -0
  16. package/dist/plan-w-y0limy.d.cts +35 -0
  17. package/dist/plan-w-y0limy.d.cts.map +1 -0
  18. package/dist/plan-w-y0limy.d.mts +35 -0
  19. package/dist/plan-w-y0limy.d.mts.map +1 -0
  20. package/dist/sheet-BUnyt5Jt.cjs +576 -0
  21. package/dist/sheet-DJGhbeYI.mjs +571 -0
  22. package/dist/vite.cjs +295 -0
  23. package/dist/vite.d.cts +59 -0
  24. package/dist/vite.d.cts.map +1 -0
  25. package/dist/vite.d.mts +59 -0
  26. package/dist/vite.d.mts.map +1 -0
  27. package/dist/vite.mjs +290 -0
  28. package/dist/webpack-loader.cjs +104 -0
  29. package/dist/webpack-loader.d.cts +35 -0
  30. package/dist/webpack-loader.d.cts.map +1 -0
  31. package/dist/webpack-loader.d.mts +36 -0
  32. package/dist/webpack-loader.d.mts.map +1 -0
  33. package/dist/webpack-loader.mjs +104 -0
  34. package/dist/webpack-state-BpF2PPjA.d.cts +15 -0
  35. package/dist/webpack-state-BpF2PPjA.d.cts.map +1 -0
  36. package/dist/webpack-state-BtmtzsWZ.cjs +61 -0
  37. package/dist/webpack-state-CXoQQ7XR.d.mts +15 -0
  38. package/dist/webpack-state-CXoQQ7XR.d.mts.map +1 -0
  39. package/dist/webpack-state-DwltSkQq.mjs +44 -0
  40. package/dist/webpack.cjs +161 -0
  41. package/dist/webpack.d.cts +98 -0
  42. package/dist/webpack.d.cts.map +1 -0
  43. package/dist/webpack.d.mts +98 -0
  44. package/dist/webpack.d.mts.map +1 -0
  45. package/dist/webpack.mjs +155 -0
  46. package/package.json +90 -4
package/README.md CHANGED
@@ -1,3 +1,240 @@
1
- # Temporary Holding Version
1
+ # @animus-ui/layergate
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ For the vendor stylesheet you have to import: a date picker's, a grid
4
+ library's, a package's `styles.css`. layergate loads it into a cascade
5
+ layer below yours, so its normal rules can't fight your design system,
6
+ whatever their specificity or load order.
7
+
8
+ A stylesheet outside any `@layer` beats every layered rule, so vendor CSS
9
+ imported as usual overrides a design system or utility framework that uses
10
+ layers. layergate loads that CSS itself, inlines its `@import`s, and puts
11
+ each file's own rules in one layer below yours:
12
+
13
+ ```css
14
+ @layer vendor, app;
15
+ @layer vendor {
16
+ /* react-grid-layout/css/styles.css, as written */
17
+ }
18
+ ```
19
+
20
+ Your own CSS is out of scope. App stylesheets and CSS Modules stay as you
21
+ configure them: layergate layers only the vendor files you declare
22
+ (`sheets`) or opt into (`dependencies`), including where your CSS imports
23
+ one.
24
+
25
+ ## Vite
26
+
27
+ ```ts
28
+ import { layergate } from '@animus-ui/layergate/vite';
29
+ // vite.config.ts
30
+ import { defineConfig } from 'vite';
31
+
32
+ export default defineConfig({
33
+ plugins: [
34
+ layergate({
35
+ layer: 'vendor',
36
+ order: ['vendor', 'base', 'components', 'utilities'],
37
+ sheets: ['react-grid-layout/css/styles.css', './styles/legacy.css'],
38
+ dependencies: ['react-datepicker'],
39
+ optOut: ['./styles/legacy/keep-unlayered.css'],
40
+ }),
41
+ ],
42
+ });
43
+ ```
44
+
45
+ ```ts
46
+ // your entry module, once, before other CSS
47
+ import 'virtual:layergate';
48
+ ```
49
+
50
+ | Option | Meaning |
51
+ | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
52
+ | `layer` | The layer owned stylesheets go into. Default `vendor`. |
53
+ | `order` | The full layer order every owned stylesheet declares first. It must include `layer`. Default `[layer]`. |
54
+ | `sheets` | Stylesheets `virtual:layergate` delivers, in this order, whether or not anything else imports them: package specifiers or paths relative to the Vite root. |
55
+ | `dependencies` | Also own the `.css` a JS module imports from a package other than your app: `true` for every package, or package names. Default: none. |
56
+ | `optOut` | Files inside an owned stylesheet whose own rules stay outside the layer. Exact specifiers or paths, never globs. |
57
+
58
+ A JS module's import of an owned stylesheet, by any spelling, gets the same
59
+ single copy, and the declared sheets always come first, in their declared
60
+ order, whichever owned stylesheet the app imports first. Vite's own CSS
61
+ pipeline then processes each one once: PostCSS, minification, injection and
62
+ extraction. A nested `@import` of a package resolves as a CSS import does,
63
+ through its `style` entry. Fonts and images keep resolving, because
64
+ layergate rewrites a relative `url()` to resolve from the delivered file.
65
+ Edits to an owned file, nested imports and files under `node_modules`
66
+ included, hot-update in place.
67
+
68
+ ## webpack
69
+
70
+ ```js
71
+ // webpack.config.js
72
+ const { LayergatePlugin } = require('@animus-ui/layergate/webpack');
73
+
74
+ module.exports = {
75
+ plugins: [
76
+ new LayergatePlugin({
77
+ layer: 'vendor',
78
+ order: ['vendor', 'base', 'components', 'utilities'],
79
+ sheets: ['react-grid-layout/css/styles.css'],
80
+ dependencies: ['react-datepicker'],
81
+ declaredBy: require.resolve('./src/styles/app.css'),
82
+ }),
83
+ ],
84
+ };
85
+ ```
86
+
87
+ The plugin adds no rule. It joins every loader chain of yours whose
88
+ css-loader is explicitly global (`modules: false`, as Next's global chains
89
+ are), just before PostCSS runs, and raises that css-loader's
90
+ `importLoaders` by one, so every stylesheet passes through it, imported
91
+ ones included. An owned stylesheet arrives finished, its `@import`s
92
+ resolved as css-loader resolves them and inlined, so css-loader never sees
93
+ a `layer()` import. PostCSS, Sass and css-loader keep running as before.
94
+
95
+ Any other chain is left untouched. One whose css-loader may treat a file
96
+ as a CSS Module (`modules` unset, or `auto`) is reported in one line
97
+ (`layergate.coverage`); a chain that only builds CSS Modules, such as
98
+ Next's, is left alone silently.
99
+
100
+ ### CSS Modules alongside layergate
101
+
102
+ layergate does not decide which stylesheets are CSS Modules. If one rule
103
+ handles both, split it: global CSS in a rule whose css-loader has
104
+ `modules: false`, which layergate joins, and CSS Modules in a rule of
105
+ their own, which it leaves alone.
106
+
107
+ ```js
108
+ module.exports = {
109
+ module: {
110
+ rules: [
111
+ {
112
+ test: /\.css$/,
113
+ exclude: /\.module\.css$/,
114
+ use: [
115
+ 'style-loader',
116
+ { loader: 'css-loader', options: { modules: false } },
117
+ ],
118
+ },
119
+ {
120
+ test: /\.module\.css$/,
121
+ use: [
122
+ 'style-loader',
123
+ { loader: 'css-loader', options: { modules: true } },
124
+ ],
125
+ },
126
+ ],
127
+ },
128
+ };
129
+ ```
130
+
131
+ Next already builds CSS this way.
132
+
133
+ What layergate returns is never cached by webpack, so a warm start with a
134
+ persistent cache finishes owned stylesheets again: about 118 ms per 200
135
+ vendor sheets in a synthetic measurement. Caching them safely needs their
136
+ provenance (the files and plan they came from) kept with the cache, a
137
+ possible later change. Ownership, opt-outs and
138
+ the declared sheets go by realpath; `url()`s are rebased from where webpack
139
+ reads each file, so `resolve.symlinks: false` resolves them as css-loader
140
+ does. The `declaredBy` stylesheet is never layered, even when an owned
141
+ stylesheet imports it.
142
+
143
+ webpack has no `virtual:layergate`. Pass `declaredBy`, the path of a
144
+ stylesheet of yours: the declared sheets are delivered at its head, and a
145
+ module's own import of one then loads nothing. Without it, import each
146
+ declared sheet yourself; it is owned wherever it is imported.
147
+
148
+ - A remote `@import` in an owned stylesheet is an error
149
+ (`layergate.remote`): webpack cannot load it inside the layer, and an
150
+ unlayered copy would escape it. Vendor the sheet locally, or opt the file
151
+ out if unlayered is acceptable.
152
+ - A chain that uses Lightning CSS instead of css-loader is not covered,
153
+ and is reported (`layergate.coverage`).
154
+ - In watch mode, an edit to any file an owned stylesheet read, files under
155
+ `node_modules` included, rebuilds it. The plugin makes a RegExp or
156
+ function `watchOptions.ignored` watch those files; a host that owns its
157
+ watch options passes `watchIgnored: false` and asks `plugin.watches(path)`.
158
+ - Turbopack runs no loader on the stylesheets a stylesheet imports, so
159
+ layergate cannot work there.
160
+
161
+ With Next and Animus, use the `quarantine` option of
162
+ `@animus-ui/next-plugin`, which sets this up for Next's client compiler.
163
+
164
+ ## What it does to a stylesheet
165
+
166
+ - **Each file is decided on its own.** A file's own rules go into the
167
+ layer. An opted-out file's own rules stay outside it, under the
168
+ conditions of the `@import` that loads it, and the files it imports keep
169
+ their own decisions.
170
+ - **Imports keep their conditions.** `@import "x.css" layer(a) supports(…)
171
+ screen` becomes `@media screen { @supports (…) { @layer a { … } } }`
172
+ inside the layer, so its rules land in `vendor.a`. An anonymous `layer`
173
+ import gets a private layer of its own, never the shared one.
174
+ - **Text is kept as written.** Selectors, declarations, `!important`,
175
+ keyframes and the file's own layers reach the browser unchanged. Only a
176
+ relative `url()` in a file outside the delivered file's directory changes.
177
+ - **Remote imports stay remote.** Under Vite, an `http(s):` import is
178
+ hoisted to the top, in its layer, and reported, since its contents are
179
+ not inspected. Under webpack it is an error (see above).
180
+
181
+ ## What a layer does not change
182
+
183
+ A layer bounds normal declarations only. These are kept and reported as
184
+ warnings, never stripped:
185
+
186
+ - `!important` declarations, which beat your normal declarations from any
187
+ layer (`layergate.important`);
188
+ - animations and keyframes, whose values override normal declarations
189
+ while they run (`layergate.animation`).
190
+
191
+ Font, keyframe and property names are global, whatever their layer.
192
+
193
+ ## Not yet
194
+
195
+ - CSS Modules, Sass, Less and Stylus are left to Vite. Under webpack, Sass
196
+ output is finished like any CSS, and CSS Modules are left alone.
197
+ - An owned file can arrive more than once. A stylesheet Vite loads
198
+ itself, such as an app sheet whose `@import` reaches an owned file, is
199
+ finished by layergate too: its own rules stay unlayered and the owned
200
+ file is inlined layered, so no unlayered copy escapes. But each finished
201
+ stylesheet stands on its own, so the same owned file reached from two of
202
+ them is delivered in both, each copy layered. One delivery per file
203
+ across the document would need a document-wide registry, a possible
204
+ later increment.
205
+ - App CSS is left alone unless you list it in `sheets`. CSS that loads
206
+ before `virtual:layergate`, or another tool's layer order statement that
207
+ comes first, can still place your layers after `vendor`.
208
+ - A `.css` request with a query (`?inline`, `?url`, `?raw`) is left to Vite.
209
+ - A stylesheet with `@namespace` is an error (`layergate.namespace`): it
210
+ applies to a whole stylesheet, which a file inlined into another is not.
211
+ - A host's PostCSS runs once, over each finished stylesheet, not per file.
212
+ - Turbopack: not supported (see webpack above).
213
+
214
+ ## Delivering from a host stylesheet
215
+
216
+ A tool with its own stylesheet module can deliver the declared sheets at
217
+ its head instead of through `virtual:layergate`: pass `declaredBy` (the
218
+ module's specifier, which an owned stylesheet a JS module imports then
219
+ loads first), and prepend `await plugin.stylesheetHead.call(this, id)` to
220
+ that module's CSS in its `load` hook. The head uses root-relative URLs, so
221
+ assets resolve from a virtual module too; a file outside the Vite root is
222
+ reached through `/@fs/`: in development a declared sheet's directory is
223
+ added to `server.fs.allow` when the server starts (beside your own list, or
224
+ Vite's default workspace root), and an asset it references in another
225
+ directory is allowed when it is found; in a build it is emitted as an
226
+ asset. Animus's `quarantine`
227
+ option works this way.
228
+
229
+ ## Core API
230
+
231
+ `compileUnit(roots, { layer, order, resolve, read, quarantined, owns, url, base, cache })`
232
+ compiles stylesheet graphs, in order, into one finished stylesheet with the
233
+ host's resolver and file reader, and returns the CSS, a source map, the files
234
+ read, whether anything was layered, and the diagnostics. `owns` says which
235
+ files start an owned graph (default: all), and `url` maps a referenced file
236
+ to its URL text (default: relative to the first root).
237
+ `base` is the directory generated layer names are relative to (the project
238
+ root), so they are the same on every machine. `cache` keeps parsed files
239
+ between compiles; drop a file's entry when it changes. The Vite and webpack
240
+ adapters are built on it.
@@ -0,0 +1,323 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+ //#endregion
23
+ const require_sheet = require("./sheet-BUnyt5Jt.cjs");
24
+ let magic_string = require("magic-string");
25
+ magic_string = __toESM(magic_string, 1);
26
+ let node_crypto = require("node:crypto");
27
+ let node_path = require("node:path");
28
+ //#region src/compile.ts
29
+ /**
30
+ * Compiles one owned stylesheet graph into one finished stylesheet: every
31
+ * local `@import` inlined at its position, under its media, supports and
32
+ * layer conditions, and each file's own text placed inside the quarantine
33
+ * layer, or outside it for a file opted out. Each file is decided on its
34
+ * own, before assembly, so a parent's decision never reaches a child's
35
+ * text. File text is kept as written; only relative URLs are rewritten, to
36
+ * resolve from the finished stylesheet's location.
37
+ */
38
+ const open = (frame) => frame.kind === "media" ? `@media ${frame.text} {\n` : frame.kind === "supports" ? `@supports (${frame.text}) {\n` : `@layer ${frame.text} {\n`;
39
+ /** A URL the finished stylesheet leaves to the browser. */
40
+ const isRemote = (url) => /^(?:[a-z][a-z0-9+.-]*:)?\/\//i.test(url);
41
+ /** A URL that resolves the same from any stylesheet location. */
42
+ const isLocationFree = (url) => url === "" || /^(?:[a-z][a-z0-9+.-]*:|\/|#|%23)/i.test(url);
43
+ const quoted = (url) => `"${url.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\a ")}"`;
44
+ /** `@import "x"` names a stylesheet relative to its own; a bare name that
45
+ * is no relative file is the package a bundler resolves. */
46
+ function candidates(url) {
47
+ if (url.startsWith("~")) return [url.slice(1)];
48
+ if (url.startsWith(".") || url.startsWith("/")) return [url];
49
+ return [`./${url}`, url];
50
+ }
51
+ /** Line and column (1-based) of each offset in `text`. */
52
+ function locator(text) {
53
+ const starts = [0];
54
+ for (let at = 0; at < text.length; at++) if (text[at] === "\n") starts.push(at + 1);
55
+ return (offset) => {
56
+ let low = 0;
57
+ let high = starts.length - 1;
58
+ while (low < high) {
59
+ const mid = low + high + 1 >> 1;
60
+ if (starts[mid] <= offset) low = mid;
61
+ else high = mid - 1;
62
+ }
63
+ return {
64
+ line: low + 1,
65
+ column: offset - starts[low] + 1
66
+ };
67
+ };
68
+ }
69
+ /** A URL's path and its `?query#fragment`. */
70
+ function splitSuffix(url) {
71
+ const at = url.search(/[?#]/);
72
+ return at === -1 ? [url, ""] : [url.slice(0, at), url.slice(at)];
73
+ }
74
+ /** The frames an import adds, media, then supports, then layer; an
75
+ * anonymous layer as `anonymous`. */
76
+ function importFrames(statement, anonymous) {
77
+ const frames = [];
78
+ if (statement.media) frames.push({
79
+ kind: "media",
80
+ text: statement.media
81
+ });
82
+ if (statement.supports !== null) frames.push({
83
+ kind: "supports",
84
+ text: statement.supports
85
+ });
86
+ if (statement.layer.kind === "named") frames.push({
87
+ kind: "layer",
88
+ text: statement.layer.name
89
+ });
90
+ else if (statement.layer.kind === "anonymous") frames.push({
91
+ kind: "layer",
92
+ text: anonymous()
93
+ });
94
+ return frames;
95
+ }
96
+ /** A remote `@import` hoisted to the top of the finished stylesheet: its
97
+ * layer is every layer on its way, its supports every supports, and its
98
+ * media the innermost media. */
99
+ function remoteImport(url, frames) {
100
+ const of = (kind) => frames.filter((frame) => frame.kind === kind).map((frame) => frame.text);
101
+ const [layers, supports, media] = [
102
+ of("layer"),
103
+ of("supports"),
104
+ of("media")
105
+ ];
106
+ return [
107
+ `@import url(${quoted(url)})`,
108
+ layers.length > 0 ? ` layer(${layers.join(".")})` : "",
109
+ supports.length > 0 ? ` supports(${supports.map((text) => `(${text})`).join(" and ")})` : "",
110
+ media.length > 0 ? ` ${combinedMedia(media)}` : "",
111
+ ";"
112
+ ].join("");
113
+ }
114
+ /** Media conditions on the way to an import, as one: joined when each is a
115
+ * bare condition (`(min-width: 1px)`), otherwise the innermost. */
116
+ function combinedMedia(media) {
117
+ return media.every((text) => text.startsWith("(") && !text.includes(",")) ? media.join(" and ") : media.at(-1);
118
+ }
119
+ /** The layer names `frames` nest, outermost first. */
120
+ function layerPath(frames) {
121
+ return frames.filter((frame) => frame.kind === "layer").flatMap((frame) => frame.text.split("."));
122
+ }
123
+ /** Compiles `roots`, in order, into one finished stylesheet. */
124
+ async function compileUnit(roots, options) {
125
+ const deliveryDir = (0, node_path.dirname)(options.delivery ?? roots[0]);
126
+ /** The URL text reaching `target`; by default relative to the
127
+ * delivery's directory, so a file there keeps its URLs as written. */
128
+ const url = options.url ?? ((target) => (0, node_path.relative)(deliveryDir, target).split(node_path.sep).join("/"));
129
+ /** Files whose own text went into the layer, and owned files opted out. */
130
+ const layered = /* @__PURE__ */ new Set();
131
+ const optedOut = /* @__PURE__ */ new Set();
132
+ const bundle = new magic_string.Bundle({ separator: "" });
133
+ const diagnostics = [];
134
+ const loaded = /* @__PURE__ */ new Map();
135
+ const remote = [];
136
+ /** Every layer path, in the order the original cascade first creates it. */
137
+ const created = /* @__PURE__ */ new Set();
138
+ const create = (path) => {
139
+ for (let depth = 1; depth <= path.length; depth++) {
140
+ const name = path.slice(0, depth).join(".");
141
+ created.add(name);
142
+ }
143
+ };
144
+ const report = (code, severity, message, file, at) => {
145
+ diagnostics.push({
146
+ code,
147
+ severity,
148
+ message,
149
+ file,
150
+ ...loaded.get(file).locate(at)
151
+ });
152
+ };
153
+ /** A stable private name for the anonymous layer an import occurrence
154
+ * creates: from the unit and the chain of imports that reaches it from
155
+ * the root, so each occurrence, of the same file too, gets its own, and
156
+ * the same on every machine. */
157
+ const anonymousName = (occurrence) => () => `layergate-anonymous-${(0, node_crypto.createHash)("sha256").update(occurrence).digest("hex").slice(0, 10)}`;
158
+ /** What a file holds that the layer cannot hold back, or that layergate
159
+ * cannot place, reported once per file. */
160
+ const reportEffects = (file, sheet) => {
161
+ const effect = (offsets, code, what) => {
162
+ if (offsets.length === 0) return;
163
+ const them = offsets.length === 1 ? "it" : "them";
164
+ report(code, "warning", `${offsets.length} ${what} kept: the quarantine layer does not hold ${them} back`, file, offsets[0]);
165
+ };
166
+ effect(sheet.important, "layergate.important", "!important declaration(s)");
167
+ effect(sheet.keyframes, "layergate.animation", "@keyframes rule(s)");
168
+ effect(sheet.animations, "layergate.animation", "animation declaration(s)");
169
+ };
170
+ /** What layergate leaves as written or cannot place, reported once per
171
+ * file; `placed` says the file's text sits inside a block, where
172
+ * `@namespace` cannot. */
173
+ const reportFile = (file, sheet, placed) => {
174
+ for (const at of sheet.ignoredImports) report("layergate.ignored-import", "warning", "@import after other rules is ignored by browsers, and layergate leaves it as written", file, at);
175
+ if (placed && sheet.namespaces.length > 0) report("layergate.namespace", "error", "@namespace applies to a whole stylesheet, which layergate cannot place yet", file, sheet.namespaces[0]);
176
+ };
177
+ const load = async (file) => {
178
+ if (loaded.has(file)) return loaded.get(file);
179
+ let entry = null;
180
+ try {
181
+ let parsed = options.cache?.get(file);
182
+ if (!parsed) {
183
+ const text = String(await options.read(file));
184
+ parsed = {
185
+ text,
186
+ sheet: require_sheet.readSheet(text)
187
+ };
188
+ options.cache?.set(file, parsed);
189
+ }
190
+ const source = new magic_string.default(parsed.text);
191
+ const kept = !options.url && (0, node_path.dirname)(file) === deliveryDir;
192
+ for (const reference of kept ? [] : parsed.sheet.urls) {
193
+ if (isLocationFree(reference.url) || isRemote(reference.url)) continue;
194
+ const [path, suffix] = splitSuffix(reference.url);
195
+ const rebased = url((0, node_path.join)((0, node_path.dirname)(file), path)) + suffix;
196
+ if (rebased === reference.url) continue;
197
+ source.overwrite(reference.start, reference.end, reference.kind === "token" ? `url(${quoted(rebased)})` : quoted(rebased));
198
+ }
199
+ for (const [from, to] of parsed.sheet.removed) source.remove(from, to);
200
+ entry = {
201
+ ...parsed,
202
+ source,
203
+ locate: locator(parsed.text)
204
+ };
205
+ } catch (error) {
206
+ diagnostics.push({
207
+ code: "layergate.unreadable",
208
+ severity: "error",
209
+ message: `cannot read ${file}: ${error instanceof Error ? error.message : String(error)}`,
210
+ file
211
+ });
212
+ }
213
+ loaded.set(file, entry);
214
+ return entry;
215
+ };
216
+ /** The file's text from `from` to `to` inside `frames`, unless it holds
217
+ * only whitespace. */
218
+ const emit = (file, frames, from, to) => {
219
+ const entry = loaded.get(file);
220
+ let content = false;
221
+ let at = from;
222
+ for (const [start, end] of entry.sheet.removed) {
223
+ if (end <= at || start >= to) continue;
224
+ content ||= /\S/.test(entry.text.slice(at, start));
225
+ at = end;
226
+ }
227
+ if (!content && !/\S/.test(entry.text.slice(at, to))) return;
228
+ if (frames.length > 0) bundle.addSource({ content: new magic_string.default(frames.map(open).join("")) });
229
+ bundle.addSource({
230
+ filename: file,
231
+ content: entry.source.snip(from, to)
232
+ });
233
+ bundle.addSource({ content: new magic_string.default(`\n${"}\n".repeat(frames.length)}`) });
234
+ };
235
+ /** Emits `file` as one occurrence: `frames` are the conditions its import
236
+ * chain puts it under, `stack` the files on that chain, `occurrence` the
237
+ * chain's import indexes. A file imported twice is emitted at each
238
+ * import, as a browser applies it at each. */
239
+ const visit = async (file, frames, stack, occurrence, insideOwned) => {
240
+ const first = !loaded.has(file);
241
+ const entry = await load(file);
242
+ if (!entry) return;
243
+ const owned = insideOwned || (options.owns?.(file) ?? true);
244
+ const quarantined = owned && (options.quarantined?.(file) ?? true);
245
+ if (owned && !quarantined) optedOut.add(file);
246
+ if (first) reportFile(file, entry.sheet, quarantined || stack.length > 1);
247
+ if (quarantined && !layered.has(file)) {
248
+ layered.add(file);
249
+ reportEffects(file, entry.sheet);
250
+ }
251
+ const own = quarantined ? [{
252
+ kind: "layer",
253
+ text: options.layer
254
+ }, ...frames] : frames;
255
+ const ownPath = layerPath(own);
256
+ const createLayers = (from, to) => {
257
+ for (const { at, names } of entry.sheet.layers) if (at >= from && at < to) for (const name of names) create([...ownPath, ...name.split(".")]);
258
+ };
259
+ create(ownPath);
260
+ let cursor = 0;
261
+ for (const [index, statement] of entry.sheet.imports.entries()) {
262
+ createLayers(cursor, statement.start);
263
+ emit(file, own, cursor, statement.start);
264
+ cursor = statement.end;
265
+ const added = importFrames(statement, anonymousName(`${occurrence}/${index}`));
266
+ if (isRemote(statement.url)) {
267
+ const chain = [...own, ...added];
268
+ if (options.remote === "refuse" && chain.some((frame) => frame.kind !== "media")) {
269
+ report("layergate.remote", "error", `@import "${statement.url}" is remote, and this host cannot load it inside a layer: vendor the sheet locally, or opt this file out explicitly if unlayered is acceptable`, file, statement.start);
270
+ continue;
271
+ }
272
+ const media = chain.filter((frame) => frame.kind === "media").length;
273
+ const kept = media > 1 ? `; its ${media} media conditions are joined where each is a bare condition, else only the innermost is kept` : "";
274
+ report("layergate.remote", "warning", `@import "${statement.url}" stays remote, so its contents are not inspected${kept}`, file, statement.start);
275
+ create(layerPath(chain));
276
+ remote.push(remoteImport(statement.url, chain));
277
+ continue;
278
+ }
279
+ let child = null;
280
+ for (const candidate of candidates(statement.url)) {
281
+ child = await options.resolve(candidate, file);
282
+ if (child) break;
283
+ }
284
+ if (!child) report("layergate.unresolved", "error", `cannot resolve @import "${statement.url}"`, file, statement.start);
285
+ else if (stack.includes(child)) report("layergate.cycle", "warning", `@import "${statement.url}" imports a stylesheet already being imported; skipped`, file, statement.start);
286
+ else await visit(child, [...frames, ...added], [...stack, child], `${occurrence}/${index}`, owned);
287
+ }
288
+ createLayers(cursor, entry.text.length);
289
+ emit(file, own, cursor, entry.text.length);
290
+ };
291
+ for (const root of roots) {
292
+ const name = (0, node_path.relative)(options.base ?? deliveryDir, root).split(node_path.sep).join("/");
293
+ await visit(root, [], [root], name, false);
294
+ }
295
+ bundle.prepend([
296
+ `@layer ${options.order.join(", ")};\n`,
297
+ ...remote.length > 0 ? [`@layer ${[...created].join(", ")};\n`] : [],
298
+ ...remote.map((line) => `${line}\n`)
299
+ ].join(""));
300
+ for (const file of optedOut) diagnostics.push({
301
+ code: "layergate.opt-out",
302
+ severity: "info",
303
+ message: `opted out: its own rules stay outside @layer ${options.layer}`,
304
+ file
305
+ });
306
+ return {
307
+ css: bundle.toString(),
308
+ map: options.map === false ? null : bundle.generateMap({
309
+ hires: "boundary",
310
+ includeContent: true
311
+ }),
312
+ files: [...loaded.keys()],
313
+ layered: layered.size > 0,
314
+ diagnostics
315
+ };
316
+ }
317
+ //#endregion
318
+ Object.defineProperty(exports, "compileUnit", {
319
+ enumerable: true,
320
+ get: function() {
321
+ return compileUnit;
322
+ }
323
+ });