@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
@@ -0,0 +1,44 @@
1
+ import { dirname, resolve } from "node:path";
2
+ import { existsSync } from "node:fs";
3
+ import { fileURLToPath } from "node:url";
4
+ //#region src/webpack-state.ts
5
+ /**
6
+ * What the webpack plugin and its loader share. webpack loads the loader
7
+ * from its own file, a module instance apart from the plugin's, so the
8
+ * state is kept on `globalThis`, by plugin id.
9
+ */
10
+ /** CSS resolution as css-loader resolves an `@import`: the `style`
11
+ * condition alone, its fields, CSS files only, relative first. */
12
+ const CSS_RESOLVE = {
13
+ conditionNames: ["style"],
14
+ mainFields: [
15
+ "css",
16
+ "style",
17
+ "main",
18
+ "..."
19
+ ],
20
+ mainFiles: ["index", "..."],
21
+ extensions: [".css"],
22
+ restrictions: [/\.css$/i],
23
+ preferRelative: true
24
+ };
25
+ const KEY = Symbol.for("@animus-ui/layergate/webpack");
26
+ function registry() {
27
+ const global = globalThis;
28
+ return global[KEY] ??= {
29
+ states: /* @__PURE__ */ new Map(),
30
+ made: 0
31
+ };
32
+ }
33
+ const here = dirname(fileURLToPath(import.meta.url));
34
+ /** The loader's file: the built one beside this module, or the source in
35
+ * the workspace. */
36
+ function loaderPath() {
37
+ for (const built of ["webpack-loader.cjs", "webpack-loader.mjs"]) {
38
+ const path = resolve(here, built);
39
+ if (existsSync(path)) return path;
40
+ }
41
+ return resolve(here, "webpack-loader.ts");
42
+ }
43
+ //#endregion
44
+ export { loaderPath as n, registry as r, CSS_RESOLVE as t };
@@ -0,0 +1,161 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ const require_plan = require("./plan-hvk04qzh.cjs");
6
+ const require_host = require("./host-C4ZaMdkx.cjs");
7
+ const require_webpack_state = require("./webpack-state-BtmtzsWZ.cjs");
8
+ //#region src/webpack.ts
9
+ /**
10
+ * The webpack adapter. The plugin inserts layergate's loader into every
11
+ * existing global-CSS loader chain (one with a css-loader that is not a
12
+ * CSS Modules one), so it runs on each stylesheet before PostCSS and
13
+ * css-loader, imported ones too: css-loader's `importLoaders` is raised by
14
+ * one, on a copy of its options. No rule is added. For an owned graph the
15
+ * loader resolves and inlines every `@import` itself and returns finished
16
+ * CSS with no local import, so css-loader has nothing left to resolve and
17
+ * a `layer()` condition never reaches its import parser.
18
+ */
19
+ const NAME = "layergate";
20
+ const CSS_LOADER = /(?:^|[\\/])css-loader(?:[\\/]|$)/;
21
+ const POSTCSS_LOADER = /(?:^|[\\/])postcss-loader(?:[\\/]|$)/;
22
+ const LIGHTNINGCSS_LOADER = /lightningcss-loader/;
23
+ const NEXT_FONT_LOADER = /next-font/;
24
+ const loaderOf = (entry) => entry instanceof Object ? entry.loader ?? "" : entry;
25
+ /** Whether css-loader with `options` reads every stylesheet as a CSS
26
+ * Module or ICSS sheet: `modules` true, a mode name, or an object
27
+ * without `auto`; or `icss: true`. */
28
+ function allModules({ modules, icss }) {
29
+ if (icss === true || modules === true) return true;
30
+ if (Object.prototype.toString.call(modules) === "[object String]") return true;
31
+ return modules instanceof Object && !(modules instanceof Function) && !("auto" in modules && modules.auto !== void 0);
32
+ }
33
+ /**
34
+ * Inserts `entry` into every loader chain of `rules` whose css-loader is
35
+ * explicitly global (`modules: false`, as Next's global chains are): just
36
+ * before PostCSS runs (after it in `use`), or before css-loader without
37
+ * PostCSS. A chain whose css-loader decides per file whether a stylesheet
38
+ * is a CSS Module (`modules` unset or `auto`) is left alone, and reported:
39
+ * layergate never predicts that decision.
40
+ */
41
+ function insertLoader(rules, entry, prefix = "module.rules") {
42
+ const coverage = {
43
+ wrapped: 0,
44
+ skipped: []
45
+ };
46
+ for (const [index, candidate] of rules.entries()) {
47
+ if (!(candidate instanceof Object)) continue;
48
+ const rule = candidate;
49
+ const place = `${prefix}[${index}]`;
50
+ for (const [key, nested] of [["oneOf", rule.oneOf], ["rules", rule.rules]]) {
51
+ if (!Array.isArray(nested)) continue;
52
+ const counts = insertLoader(nested, entry, `${place}.${key}`);
53
+ coverage.wrapped += counts.wrapped;
54
+ coverage.skipped.push(...counts.skipped);
55
+ }
56
+ if (!Array.isArray(rule.use)) continue;
57
+ const use = rule.use;
58
+ const loaders = use.map(loaderOf);
59
+ if (loaders.includes(entry.loader)) continue;
60
+ if (loaders.some((loader) => NEXT_FONT_LOADER.test(loader))) continue;
61
+ const css = loaders.findIndex((loader) => CSS_LOADER.test(loader));
62
+ if (css === -1) {
63
+ if (loaders.some((loader) => LIGHTNINGCSS_LOADER.test(loader))) coverage.skipped.push(`${place} is not layered: it uses Lightning CSS, not css-loader`);
64
+ continue;
65
+ }
66
+ const cssEntry = use[css];
67
+ const options = cssEntry instanceof Object ? cssEntry.options ?? {} : {};
68
+ if (allModules(options)) continue;
69
+ if (options.modules !== false) {
70
+ coverage.skipped.push(`${place} is not layered: its css-loader is not \`modules: false\`. Give global CSS a rule with \`modules: false\`, or opt out`);
71
+ continue;
72
+ }
73
+ const postcss = loaders.findIndex((loader) => POSTCSS_LOADER.test(loader));
74
+ const at = (postcss === -1 ? css : postcss) + 1;
75
+ rule.use = [
76
+ ...use.slice(0, css),
77
+ {
78
+ loader: loaderOf(cssEntry),
79
+ options: {
80
+ ...options,
81
+ importLoaders: (options.importLoaders ?? 0) + 1
82
+ }
83
+ },
84
+ ...use.slice(css + 1, at),
85
+ entry,
86
+ ...use.slice(at)
87
+ ];
88
+ coverage.wrapped += 1;
89
+ }
90
+ return coverage;
91
+ }
92
+ /** What `specifier` resolves to from `context`, as webpack resolves it
93
+ * (by realpath unless `resolve.symlinks` is false), or null. */
94
+ const resolveOne = (resolver, context, specifier) => new Promise((done) => resolver.resolve({}, context, specifier, {}, (error, result) => done(error || !result ? null : require_host.slashes(result))));
95
+ var LayergatePlugin = class {
96
+ id;
97
+ state;
98
+ watchIgnored;
99
+ constructor(options = {}) {
100
+ const shared = require_webpack_state.registry();
101
+ this.id = `layergate-${process.pid}-${shared.made++}`;
102
+ this.watchIgnored = options.watchIgnored !== false;
103
+ const plan = require_plan.resolvePlan(options);
104
+ this.state = {
105
+ plan,
106
+ owner: require_host.ownership(plan),
107
+ optedOut: /* @__PURE__ */ new Set(),
108
+ declaredBy: options.declaredBy ? require_host.real(options.declaredBy) : null,
109
+ roots: [],
110
+ units: new require_host.Units()
111
+ };
112
+ shared.states.set(this.id, this.state);
113
+ }
114
+ /** Whether `path` is a file a finished stylesheet read: one the watcher
115
+ * must see, wherever it lies. The watcher asks of every path it scans,
116
+ * so this reads no file system: webpack watches the resolved paths the
117
+ * loader reported. */
118
+ watches(path) {
119
+ return this.state.units.reads(require_host.slashes(path));
120
+ }
121
+ apply(compiler) {
122
+ const { wrapped, skipped } = insertLoader(compiler.options.module.rules, {
123
+ loader: require_webpack_state.loaderPath(),
124
+ options: { id: this.id }
125
+ });
126
+ compiler.hooks.thisCompilation.tap(NAME, (compilation) => {
127
+ for (const reason of skipped) compilation.warnings.push(/* @__PURE__ */ new Error(`[layergate] ${reason} [layergate.coverage]`));
128
+ if (wrapped === 0 && skipped.length === 0 && this.state.owner.owning()) compilation.warnings.push(/* @__PURE__ */ new Error("[layergate] no CSS loader chain with a css-loader was found: nothing is layered [layergate.coverage]"));
129
+ });
130
+ if (this.watchIgnored) this.watchOwned(compiler);
131
+ const prepare = async () => {
132
+ const resolver = compiler.resolverFactory.get("normal", require_webpack_state.CSS_RESOLVE);
133
+ const resolve = (specifier) => resolveOne(resolver, compiler.context, specifier);
134
+ const { sheets, optOut } = this.state.plan;
135
+ const [declared, optedOut] = await Promise.all([require_host.resolveEach(sheets, "sheets", compiler.context, resolve), require_host.resolveEach(optOut, "optOut", compiler.context, resolve)]);
136
+ this.state.roots = declared;
137
+ this.state.owner.reset(compiler.context, declared.map(require_host.real));
138
+ this.state.optedOut = new Set(optedOut.map(require_host.real));
139
+ };
140
+ compiler.hooks.beforeRun.tapPromise(NAME, prepare);
141
+ compiler.hooks.watchRun.tapPromise(NAME, async () => {
142
+ for (const path of compiler.modifiedFiles ?? []) this.state.units.changed(require_host.slashes(path));
143
+ for (const path of compiler.removedFiles ?? []) this.state.units.changed(require_host.slashes(path));
144
+ await prepare();
145
+ });
146
+ compiler.hooks.shutdown.tap(NAME, () => require_webpack_state.registry().states.delete(this.id));
147
+ }
148
+ /** Makes the host's `watchOptions.ignored` watch every file a finished
149
+ * stylesheet read. */
150
+ watchOwned(compiler) {
151
+ const watchOptions = compiler.options.watchOptions ??= {};
152
+ const ignored = watchOptions.ignored;
153
+ const ignores = ignored instanceof RegExp ? (path) => ignored.test(path) : ignored instanceof Function ? (path) => Boolean(ignored(path)) : null;
154
+ if (!ignores) return;
155
+ watchOptions.ignored = (path) => ignores(path) && !this.watches(path);
156
+ }
157
+ };
158
+ //#endregion
159
+ exports.LayergatePlugin = LayergatePlugin;
160
+ exports.default = LayergatePlugin;
161
+ exports.insertLoader = insertLoader;
@@ -0,0 +1,98 @@
1
+ import { n as LayergateOptions } from "./plan-w-y0limy.cjs";
2
+ import { t as CSS_RESOLVE } from "./webpack-state-BpF2PPjA.cjs";
3
+ //#region src/webpack.d.ts
4
+ export interface LayergateWebpackOptions extends LayergateOptions {
5
+ /** A stylesheet (a file path) whose head delivers the declared sheets,
6
+ * finished, in order; a module's own import of a declared sheet then
7
+ * loads nothing, so each arrives once. Without one, a declared sheet
8
+ * is owned wherever it is imported. */
9
+ declaredBy?: string;
10
+ /** Whether the plugin makes `watchOptions.ignored` (a RegExp or a
11
+ * function) watch the owned files it reads, those under node_modules
12
+ * too. A host that owns the watcher passes false and asks `watches`.
13
+ * Default true. */
14
+ watchIgnored?: boolean;
15
+ }
16
+ /** The loader entry the plugin inserts. */
17
+ export interface LoaderEntry {
18
+ loader: string;
19
+ options: {
20
+ id: string;
21
+ };
22
+ }
23
+ /** How many loader chains `insertLoader` wrapped, and why it left each
24
+ * chain it could not wrap, by the chain's place in the rules. */
25
+ export interface Coverage {
26
+ wrapped: number;
27
+ skipped: string[];
28
+ }
29
+ /** What webpack's resolver is handed besides the request: nothing the
30
+ * plugin sets. */
31
+ type ResolveContext = Record<string, never>;
32
+ interface Resolver {
33
+ resolve(context: ResolveContext, path: string, request: string, resolveContext: ResolveContext, callback: (error: Error | null, result?: string | false) => void): void;
34
+ }
35
+ /** A compilation, as far as the plugin reads it. */
36
+ interface Compilation {
37
+ warnings: Error[];
38
+ errors: Error[];
39
+ }
40
+ /** The compiler, as far as the plugin reads it. */
41
+ export interface Compiler {
42
+ context: string;
43
+ /** What changed since the last compile, in watch mode. */
44
+ modifiedFiles?: ReadonlySet<string>;
45
+ removedFiles?: ReadonlySet<string>;
46
+ options: {
47
+ module: {
48
+ rules: unknown[];
49
+ };
50
+ watchOptions?: {
51
+ ignored?: unknown;
52
+ };
53
+ };
54
+ resolverFactory: {
55
+ get(type: string, options: typeof CSS_RESOLVE): Resolver;
56
+ };
57
+ hooks: {
58
+ beforeRun: {
59
+ tapPromise(name: string, run: () => Promise<void>): void;
60
+ };
61
+ watchRun: {
62
+ tapPromise(name: string, run: () => Promise<void>): void;
63
+ };
64
+ thisCompilation: {
65
+ tap(name: string, run: (compilation: Compilation) => void): void;
66
+ };
67
+ shutdown: {
68
+ tap(name: string, run: () => void): void;
69
+ };
70
+ };
71
+ }
72
+ /**
73
+ * Inserts `entry` into every loader chain of `rules` whose css-loader is
74
+ * explicitly global (`modules: false`, as Next's global chains are): just
75
+ * before PostCSS runs (after it in `use`), or before css-loader without
76
+ * PostCSS. A chain whose css-loader decides per file whether a stylesheet
77
+ * is a CSS Module (`modules` unset or `auto`) is left alone, and reported:
78
+ * layergate never predicts that decision.
79
+ */
80
+ export declare function insertLoader(rules: unknown[], entry: LoaderEntry, prefix?: string): Coverage;
81
+ export declare class LayergatePlugin {
82
+ readonly id: string;
83
+ private readonly state;
84
+ private readonly watchIgnored;
85
+ constructor(options?: LayergateWebpackOptions);
86
+ /** Whether `path` is a file a finished stylesheet read: one the watcher
87
+ * must see, wherever it lies. The watcher asks of every path it scans,
88
+ * so this reads no file system: webpack watches the resolved paths the
89
+ * loader reported. */
90
+ watches(path: string): boolean;
91
+ apply(compiler: Compiler): void;
92
+ /** Makes the host's `watchOptions.ignored` watch every file a finished
93
+ * stylesheet read. */
94
+ private watchOwned;
95
+ }
96
+ //#endregion
97
+ export { type LayergateOptions, LayergatePlugin as default };
98
+ //# sourceMappingURL=webpack.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webpack.d.cts","names":[],"sources":["../src/webpack.ts"],"mappings":";;;iBAmBiB,gCAAgC;;;;;EAK/C;;;;;EAKA;;;iBAOe;EACf;EACA;IAAW;;;;;iBAoBI;EACf;EACA;;;;KAKG,iBAAiB;UAEZ;EACR,QACE,SAAS,gBACT,cACA,iBACA,gBAAgB,gBAChB,WAAW,OAAO,cAAc;;;UAK1B;EACR,UAAU;EACV,QAAQ;;;iBAIO;EACf;;EAEA,gBAAgB;EAChB,eAAe;EACf;IACE;MAAU;;IACV;MAAiB;;;EAEnB;IACE,IAAI,cAAc,gBAAgB,cAAc;;EAElD;IACE;MACE,WAAW,cAAc,WAAW;;IAEtC;MACE,WAAW,cAAc,WAAW;;IAEtC;MACE,IAAI,cAAc,MAAM,aAAa;;IAEvC;MACE,IAAI,cAAc;;;;;;;;;;;;wBAqCR,aACd,kBACA,OAAO,aACP,kBACC;qBAwEU;WACF;mBACQ;mBACA;EAEjB,YAAY,UAAS;;;;;EAoBrB,QAAQ;EAIR,MAAM,UAAU;;;UAuDR"}
@@ -0,0 +1,98 @@
1
+ import { n as LayergateOptions } from "./plan-w-y0limy.mjs";
2
+ import { t as CSS_RESOLVE } from "./webpack-state-CXoQQ7XR.mjs";
3
+ //#region src/webpack.d.ts
4
+ export interface LayergateWebpackOptions extends LayergateOptions {
5
+ /** A stylesheet (a file path) whose head delivers the declared sheets,
6
+ * finished, in order; a module's own import of a declared sheet then
7
+ * loads nothing, so each arrives once. Without one, a declared sheet
8
+ * is owned wherever it is imported. */
9
+ declaredBy?: string;
10
+ /** Whether the plugin makes `watchOptions.ignored` (a RegExp or a
11
+ * function) watch the owned files it reads, those under node_modules
12
+ * too. A host that owns the watcher passes false and asks `watches`.
13
+ * Default true. */
14
+ watchIgnored?: boolean;
15
+ }
16
+ /** The loader entry the plugin inserts. */
17
+ export interface LoaderEntry {
18
+ loader: string;
19
+ options: {
20
+ id: string;
21
+ };
22
+ }
23
+ /** How many loader chains `insertLoader` wrapped, and why it left each
24
+ * chain it could not wrap, by the chain's place in the rules. */
25
+ export interface Coverage {
26
+ wrapped: number;
27
+ skipped: string[];
28
+ }
29
+ /** What webpack's resolver is handed besides the request: nothing the
30
+ * plugin sets. */
31
+ type ResolveContext = Record<string, never>;
32
+ interface Resolver {
33
+ resolve(context: ResolveContext, path: string, request: string, resolveContext: ResolveContext, callback: (error: Error | null, result?: string | false) => void): void;
34
+ }
35
+ /** A compilation, as far as the plugin reads it. */
36
+ interface Compilation {
37
+ warnings: Error[];
38
+ errors: Error[];
39
+ }
40
+ /** The compiler, as far as the plugin reads it. */
41
+ export interface Compiler {
42
+ context: string;
43
+ /** What changed since the last compile, in watch mode. */
44
+ modifiedFiles?: ReadonlySet<string>;
45
+ removedFiles?: ReadonlySet<string>;
46
+ options: {
47
+ module: {
48
+ rules: unknown[];
49
+ };
50
+ watchOptions?: {
51
+ ignored?: unknown;
52
+ };
53
+ };
54
+ resolverFactory: {
55
+ get(type: string, options: typeof CSS_RESOLVE): Resolver;
56
+ };
57
+ hooks: {
58
+ beforeRun: {
59
+ tapPromise(name: string, run: () => Promise<void>): void;
60
+ };
61
+ watchRun: {
62
+ tapPromise(name: string, run: () => Promise<void>): void;
63
+ };
64
+ thisCompilation: {
65
+ tap(name: string, run: (compilation: Compilation) => void): void;
66
+ };
67
+ shutdown: {
68
+ tap(name: string, run: () => void): void;
69
+ };
70
+ };
71
+ }
72
+ /**
73
+ * Inserts `entry` into every loader chain of `rules` whose css-loader is
74
+ * explicitly global (`modules: false`, as Next's global chains are): just
75
+ * before PostCSS runs (after it in `use`), or before css-loader without
76
+ * PostCSS. A chain whose css-loader decides per file whether a stylesheet
77
+ * is a CSS Module (`modules` unset or `auto`) is left alone, and reported:
78
+ * layergate never predicts that decision.
79
+ */
80
+ export declare function insertLoader(rules: unknown[], entry: LoaderEntry, prefix?: string): Coverage;
81
+ export declare class LayergatePlugin {
82
+ readonly id: string;
83
+ private readonly state;
84
+ private readonly watchIgnored;
85
+ constructor(options?: LayergateWebpackOptions);
86
+ /** Whether `path` is a file a finished stylesheet read: one the watcher
87
+ * must see, wherever it lies. The watcher asks of every path it scans,
88
+ * so this reads no file system: webpack watches the resolved paths the
89
+ * loader reported. */
90
+ watches(path: string): boolean;
91
+ apply(compiler: Compiler): void;
92
+ /** Makes the host's `watchOptions.ignored` watch every file a finished
93
+ * stylesheet read. */
94
+ private watchOwned;
95
+ }
96
+ //#endregion
97
+ export { type LayergateOptions, LayergatePlugin as default };
98
+ //# sourceMappingURL=webpack.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webpack.d.mts","names":[],"sources":["../src/webpack.ts"],"mappings":";;;iBAmBiB,gCAAgC;;;;;EAK/C;;;;;EAKA;;;iBAOe;EACf;EACA;IAAW;;;;;iBAoBI;EACf;EACA;;;;KAKG,iBAAiB;UAEZ;EACR,QACE,SAAS,gBACT,cACA,iBACA,gBAAgB,gBAChB,WAAW,OAAO,cAAc;;;UAK1B;EACR,UAAU;EACV,QAAQ;;;iBAIO;EACf;;EAEA,gBAAgB;EAChB,eAAe;EACf;IACE;MAAU;;IACV;MAAiB;;;EAEnB;IACE,IAAI,cAAc,gBAAgB,cAAc;;EAElD;IACE;MACE,WAAW,cAAc,WAAW;;IAEtC;MACE,WAAW,cAAc,WAAW;;IAEtC;MACE,IAAI,cAAc,MAAM,aAAa;;IAEvC;MACE,IAAI,cAAc;;;;;;;;;;;;wBAqCR,aACd,kBACA,OAAO,aACP,kBACC;qBAwEU;WACF;mBACQ;mBACA;EAEjB,YAAY,UAAS;;;;;EAoBrB,QAAQ;EAIR,MAAM,UAAU;;;UAuDR"}
@@ -0,0 +1,155 @@
1
+ import { n as resolvePlan } from "./plan-Bsn_yxS9.mjs";
2
+ import { a as real, i as ownership, o as resolveEach, s as slashes, t as Units } from "./host-BGcb1rMJ.mjs";
3
+ import { n as loaderPath, r as registry, t as CSS_RESOLVE } from "./webpack-state-DwltSkQq.mjs";
4
+ //#region src/webpack.ts
5
+ /**
6
+ * The webpack adapter. The plugin inserts layergate's loader into every
7
+ * existing global-CSS loader chain (one with a css-loader that is not a
8
+ * CSS Modules one), so it runs on each stylesheet before PostCSS and
9
+ * css-loader, imported ones too: css-loader's `importLoaders` is raised by
10
+ * one, on a copy of its options. No rule is added. For an owned graph the
11
+ * loader resolves and inlines every `@import` itself and returns finished
12
+ * CSS with no local import, so css-loader has nothing left to resolve and
13
+ * a `layer()` condition never reaches its import parser.
14
+ */
15
+ const NAME = "layergate";
16
+ const CSS_LOADER = /(?:^|[\\/])css-loader(?:[\\/]|$)/;
17
+ const POSTCSS_LOADER = /(?:^|[\\/])postcss-loader(?:[\\/]|$)/;
18
+ const LIGHTNINGCSS_LOADER = /lightningcss-loader/;
19
+ const NEXT_FONT_LOADER = /next-font/;
20
+ const loaderOf = (entry) => entry instanceof Object ? entry.loader ?? "" : entry;
21
+ /** Whether css-loader with `options` reads every stylesheet as a CSS
22
+ * Module or ICSS sheet: `modules` true, a mode name, or an object
23
+ * without `auto`; or `icss: true`. */
24
+ function allModules({ modules, icss }) {
25
+ if (icss === true || modules === true) return true;
26
+ if (Object.prototype.toString.call(modules) === "[object String]") return true;
27
+ return modules instanceof Object && !(modules instanceof Function) && !("auto" in modules && modules.auto !== void 0);
28
+ }
29
+ /**
30
+ * Inserts `entry` into every loader chain of `rules` whose css-loader is
31
+ * explicitly global (`modules: false`, as Next's global chains are): just
32
+ * before PostCSS runs (after it in `use`), or before css-loader without
33
+ * PostCSS. A chain whose css-loader decides per file whether a stylesheet
34
+ * is a CSS Module (`modules` unset or `auto`) is left alone, and reported:
35
+ * layergate never predicts that decision.
36
+ */
37
+ function insertLoader(rules, entry, prefix = "module.rules") {
38
+ const coverage = {
39
+ wrapped: 0,
40
+ skipped: []
41
+ };
42
+ for (const [index, candidate] of rules.entries()) {
43
+ if (!(candidate instanceof Object)) continue;
44
+ const rule = candidate;
45
+ const place = `${prefix}[${index}]`;
46
+ for (const [key, nested] of [["oneOf", rule.oneOf], ["rules", rule.rules]]) {
47
+ if (!Array.isArray(nested)) continue;
48
+ const counts = insertLoader(nested, entry, `${place}.${key}`);
49
+ coverage.wrapped += counts.wrapped;
50
+ coverage.skipped.push(...counts.skipped);
51
+ }
52
+ if (!Array.isArray(rule.use)) continue;
53
+ const use = rule.use;
54
+ const loaders = use.map(loaderOf);
55
+ if (loaders.includes(entry.loader)) continue;
56
+ if (loaders.some((loader) => NEXT_FONT_LOADER.test(loader))) continue;
57
+ const css = loaders.findIndex((loader) => CSS_LOADER.test(loader));
58
+ if (css === -1) {
59
+ if (loaders.some((loader) => LIGHTNINGCSS_LOADER.test(loader))) coverage.skipped.push(`${place} is not layered: it uses Lightning CSS, not css-loader`);
60
+ continue;
61
+ }
62
+ const cssEntry = use[css];
63
+ const options = cssEntry instanceof Object ? cssEntry.options ?? {} : {};
64
+ if (allModules(options)) continue;
65
+ if (options.modules !== false) {
66
+ coverage.skipped.push(`${place} is not layered: its css-loader is not \`modules: false\`. Give global CSS a rule with \`modules: false\`, or opt out`);
67
+ continue;
68
+ }
69
+ const postcss = loaders.findIndex((loader) => POSTCSS_LOADER.test(loader));
70
+ const at = (postcss === -1 ? css : postcss) + 1;
71
+ rule.use = [
72
+ ...use.slice(0, css),
73
+ {
74
+ loader: loaderOf(cssEntry),
75
+ options: {
76
+ ...options,
77
+ importLoaders: (options.importLoaders ?? 0) + 1
78
+ }
79
+ },
80
+ ...use.slice(css + 1, at),
81
+ entry,
82
+ ...use.slice(at)
83
+ ];
84
+ coverage.wrapped += 1;
85
+ }
86
+ return coverage;
87
+ }
88
+ /** What `specifier` resolves to from `context`, as webpack resolves it
89
+ * (by realpath unless `resolve.symlinks` is false), or null. */
90
+ const resolveOne = (resolver, context, specifier) => new Promise((done) => resolver.resolve({}, context, specifier, {}, (error, result) => done(error || !result ? null : slashes(result))));
91
+ var LayergatePlugin = class {
92
+ id;
93
+ state;
94
+ watchIgnored;
95
+ constructor(options = {}) {
96
+ const shared = registry();
97
+ this.id = `layergate-${process.pid}-${shared.made++}`;
98
+ this.watchIgnored = options.watchIgnored !== false;
99
+ const plan = resolvePlan(options);
100
+ this.state = {
101
+ plan,
102
+ owner: ownership(plan),
103
+ optedOut: /* @__PURE__ */ new Set(),
104
+ declaredBy: options.declaredBy ? real(options.declaredBy) : null,
105
+ roots: [],
106
+ units: new Units()
107
+ };
108
+ shared.states.set(this.id, this.state);
109
+ }
110
+ /** Whether `path` is a file a finished stylesheet read: one the watcher
111
+ * must see, wherever it lies. The watcher asks of every path it scans,
112
+ * so this reads no file system: webpack watches the resolved paths the
113
+ * loader reported. */
114
+ watches(path) {
115
+ return this.state.units.reads(slashes(path));
116
+ }
117
+ apply(compiler) {
118
+ const { wrapped, skipped } = insertLoader(compiler.options.module.rules, {
119
+ loader: loaderPath(),
120
+ options: { id: this.id }
121
+ });
122
+ compiler.hooks.thisCompilation.tap(NAME, (compilation) => {
123
+ for (const reason of skipped) compilation.warnings.push(/* @__PURE__ */ new Error(`[layergate] ${reason} [layergate.coverage]`));
124
+ if (wrapped === 0 && skipped.length === 0 && this.state.owner.owning()) compilation.warnings.push(/* @__PURE__ */ new Error("[layergate] no CSS loader chain with a css-loader was found: nothing is layered [layergate.coverage]"));
125
+ });
126
+ if (this.watchIgnored) this.watchOwned(compiler);
127
+ const prepare = async () => {
128
+ const resolver = compiler.resolverFactory.get("normal", CSS_RESOLVE);
129
+ const resolve = (specifier) => resolveOne(resolver, compiler.context, specifier);
130
+ const { sheets, optOut } = this.state.plan;
131
+ const [declared, optedOut] = await Promise.all([resolveEach(sheets, "sheets", compiler.context, resolve), resolveEach(optOut, "optOut", compiler.context, resolve)]);
132
+ this.state.roots = declared;
133
+ this.state.owner.reset(compiler.context, declared.map(real));
134
+ this.state.optedOut = new Set(optedOut.map(real));
135
+ };
136
+ compiler.hooks.beforeRun.tapPromise(NAME, prepare);
137
+ compiler.hooks.watchRun.tapPromise(NAME, async () => {
138
+ for (const path of compiler.modifiedFiles ?? []) this.state.units.changed(slashes(path));
139
+ for (const path of compiler.removedFiles ?? []) this.state.units.changed(slashes(path));
140
+ await prepare();
141
+ });
142
+ compiler.hooks.shutdown.tap(NAME, () => registry().states.delete(this.id));
143
+ }
144
+ /** Makes the host's `watchOptions.ignored` watch every file a finished
145
+ * stylesheet read. */
146
+ watchOwned(compiler) {
147
+ const watchOptions = compiler.options.watchOptions ??= {};
148
+ const ignored = watchOptions.ignored;
149
+ const ignores = ignored instanceof RegExp ? (path) => ignored.test(path) : ignored instanceof Function ? (path) => Boolean(ignored(path)) : null;
150
+ if (!ignores) return;
151
+ watchOptions.ignored = (path) => ignores(path) && !this.watches(path);
152
+ }
153
+ };
154
+ //#endregion
155
+ export { LayergatePlugin, LayergatePlugin as default, insertLoader };
package/package.json CHANGED
@@ -1,6 +1,92 @@
1
1
  {
2
2
  "name": "@animus-ui/layergate",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.35",
4
+ "description": "Load third-party stylesheets into a cascade layer, per file, with their imports inlined and their URLs kept working",
5
+ "keywords": [
6
+ "cascade-layers",
7
+ "css",
8
+ "layer",
9
+ "nextjs",
10
+ "vendor",
11
+ "vite",
12
+ "vite-plugin",
13
+ "webpack"
14
+ ],
15
+ "homepage": "https://github.com/codecaaron/animus/tree/main/packages/layergate#readme",
16
+ "license": "MIT",
17
+ "author": "codecaaron <airrobb@gmail.com>",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/codecaaron/animus",
21
+ "directory": "packages/layergate"
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "type": "module",
27
+ "sideEffects": false,
28
+ "main": "./dist/index.cjs",
29
+ "module": "./dist/index.mjs",
30
+ "types": "./dist/index.d.mts",
31
+ "exports": {
32
+ ".": {
33
+ "import": {
34
+ "types": "./dist/index.d.mts",
35
+ "default": "./dist/index.mjs"
36
+ },
37
+ "require": {
38
+ "types": "./dist/index.d.cts",
39
+ "default": "./dist/index.cjs"
40
+ }
41
+ },
42
+ "./vite": {
43
+ "import": {
44
+ "types": "./dist/vite.d.mts",
45
+ "default": "./dist/vite.mjs"
46
+ },
47
+ "require": {
48
+ "types": "./dist/vite.d.cts",
49
+ "default": "./dist/vite.cjs"
50
+ }
51
+ },
52
+ "./webpack": {
53
+ "import": {
54
+ "types": "./dist/webpack.d.mts",
55
+ "default": "./dist/webpack.mjs"
56
+ },
57
+ "require": {
58
+ "types": "./dist/webpack.d.cts",
59
+ "default": "./dist/webpack.cjs"
60
+ }
61
+ }
62
+ },
63
+ "publishConfig": {
64
+ "access": "public"
65
+ },
66
+ "scripts": {
67
+ "build": "bun run build:ts",
68
+ "build:ts": "tsdown",
69
+ "compile": "bun check"
70
+ },
71
+ "dependencies": {
72
+ "magic-string": "^0.30.21"
73
+ },
74
+ "devDependencies": {
75
+ "vite": "8.3.4"
76
+ },
77
+ "peerDependencies": {
78
+ "vite": ">=8 <9",
79
+ "webpack": ">=5 <6"
80
+ },
81
+ "peerDependenciesMeta": {
82
+ "vite": {
83
+ "optional": true
84
+ },
85
+ "webpack": {
86
+ "optional": true
87
+ }
88
+ },
89
+ "engines": {
90
+ "node": ">=22.12.0"
91
+ }
92
+ }