@animus-ui/layergate 0.0.0-stage → 0.1.36
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/README.md +239 -2
- package/dist/compile-C4GyCiuY.cjs +323 -0
- package/dist/compile-Cu_GFS3l.mjs +295 -0
- package/dist/compile-DZwVBjTM.d.cts +126 -0
- package/dist/compile-DZwVBjTM.d.cts.map +1 -0
- package/dist/compile-DZwVBjTM.d.mts +126 -0
- package/dist/compile-DZwVBjTM.d.mts.map +1 -0
- package/dist/host-BGcb1rMJ.mjs +169 -0
- package/dist/host-C4ZaMdkx.cjs +210 -0
- package/dist/index.cjs +6 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.mjs +3 -0
- package/dist/plan-Bsn_yxS9.mjs +29 -0
- package/dist/plan-hvk04qzh.cjs +40 -0
- package/dist/plan-w-y0limy.d.cts +35 -0
- package/dist/plan-w-y0limy.d.cts.map +1 -0
- package/dist/plan-w-y0limy.d.mts +35 -0
- package/dist/plan-w-y0limy.d.mts.map +1 -0
- package/dist/sheet-BUnyt5Jt.cjs +576 -0
- package/dist/sheet-DJGhbeYI.mjs +571 -0
- package/dist/vite.cjs +295 -0
- package/dist/vite.d.cts +59 -0
- package/dist/vite.d.cts.map +1 -0
- package/dist/vite.d.mts +59 -0
- package/dist/vite.d.mts.map +1 -0
- package/dist/vite.mjs +290 -0
- package/dist/webpack-loader.cjs +104 -0
- package/dist/webpack-loader.d.cts +35 -0
- package/dist/webpack-loader.d.cts.map +1 -0
- package/dist/webpack-loader.d.mts +36 -0
- package/dist/webpack-loader.d.mts.map +1 -0
- package/dist/webpack-loader.mjs +104 -0
- package/dist/webpack-state-BpF2PPjA.d.cts +15 -0
- package/dist/webpack-state-BpF2PPjA.d.cts.map +1 -0
- package/dist/webpack-state-BtmtzsWZ.cjs +61 -0
- package/dist/webpack-state-CXoQQ7XR.d.mts +15 -0
- package/dist/webpack-state-CXoQQ7XR.d.mts.map +1 -0
- package/dist/webpack-state-DwltSkQq.mjs +44 -0
- package/dist/webpack.cjs +161 -0
- package/dist/webpack.d.cts +98 -0
- package/dist/webpack.d.cts.map +1 -0
- package/dist/webpack.d.mts +98 -0
- package/dist/webpack.d.mts.map +1 -0
- package/dist/webpack.mjs +155 -0
- 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 };
|
package/dist/webpack.cjs
ADDED
|
@@ -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"}
|
package/dist/webpack.mjs
ADDED
|
@@ -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.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.36",
|
|
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
|
+
}
|