@animus-ui/next-plugin 0.1.31 → 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.
- package/README.md +84 -0
- package/dist/font-preloads.cjs +60 -0
- package/dist/font-preloads.d.ts +13 -0
- package/dist/font-preloads.d.ts.map +1 -0
- package/dist/font-preloads.mjs +56 -0
- package/dist/index.cjs +212 -37
- package/dist/index.mjs +212 -37
- package/dist/plugin.d.ts +24 -7
- package/dist/plugin.d.ts.map +1 -1
- package/dist/quarantine.d.ts +7 -0
- package/dist/quarantine.d.ts.map +1 -0
- package/dist/rolldown-runtime-VH7oDXx4.cjs +28 -0
- package/dist/{turbopack-config-3jtLDAIM.mjs → turbopack-config-BOUPGkmi.mjs} +3 -2
- package/dist/{turbopack-config-BoNfzhNu.cjs → turbopack-config-W5eoa_xN.cjs} +2 -1
- package/dist/turbopack-config.d.ts.map +1 -1
- package/dist/turbopack-loader.cjs +3 -24
- package/dist/turbopack-loader.mjs +1 -1
- package/dist/types.d.ts +16 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/watch-ignored.d.ts +21 -0
- package/dist/watch-ignored.d.ts.map +1 -0
- package/dist/with-animus.d.ts +4 -0
- package/dist/with-animus.d.ts.map +1 -1
- package/package.json +14 -3
package/README.md
CHANGED
|
@@ -71,6 +71,47 @@ export default withAnimus({
|
|
|
71
71
|
Under webpack (`next dev` / `next build` on 15, or `--webpack` on 16) the
|
|
72
72
|
plugin behaves exactly as before.
|
|
73
73
|
|
|
74
|
+
## Third-party stylesheets
|
|
75
|
+
|
|
76
|
+
A library's plain CSS outside any `@layer` beats every Animus layer. With
|
|
77
|
+
`quarantine`, [`@animus-ui/layergate`](../layergate/README.md) loads it into
|
|
78
|
+
`anm-vendor`, a layer below all of Animus's, so Animus's styles win and the
|
|
79
|
+
library's still apply where Animus sets nothing:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm install -D @animus-ui/layergate
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
export default withAnimus({
|
|
87
|
+
system: './src/ds.ts',
|
|
88
|
+
quarantine: {
|
|
89
|
+
sheets: ['react-grid-layout/css/styles.css'], // delivered ahead of Animus's own CSS
|
|
90
|
+
dependencies: ['react-datepicker'], // CSS these packages' modules import
|
|
91
|
+
optOut: [], // exact files whose own rules stay unlayered
|
|
92
|
+
},
|
|
93
|
+
})(nextConfig);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
It needs webpack: `next dev` and `next build` on Next 15, `--webpack` on
|
|
97
|
+
Next 16. Turbopack runs no loader on the stylesheets a stylesheet imports,
|
|
98
|
+
so with `quarantine` set any Turbopack run fails config loading, whatever
|
|
99
|
+
the `turbopack` option says. Next marks a Turbopack run by setting
|
|
100
|
+
`TURBOPACK`, so Animus refuses whenever that variable is set, even empty.
|
|
101
|
+
The converse: a `TURBOPACK` inherited from the environment also refuses a
|
|
102
|
+
webpack build of Next 15, loudly, so clear stale values in CI.
|
|
103
|
+
|
|
104
|
+
Vendor stylesheets layergate finishes are not kept in webpack's persistent
|
|
105
|
+
cache, so a warm rebuild finishes them again: about 118 ms per 200 vendor
|
|
106
|
+
sheets in a synthetic measurement.
|
|
107
|
+
|
|
108
|
+
`sheets` arrive at the head of Animus's own stylesheet, on every page.
|
|
109
|
+
`!important` declarations and animations still win, and are reported. App
|
|
110
|
+
CSS keeps its own rules unlayered; where it imports a vendor file, that file
|
|
111
|
+
is layered. CSS Modules are left alone. A remote `@import` in a vendor
|
|
112
|
+
stylesheet is an error: vendor the sheet locally, or opt the file out if
|
|
113
|
+
unlayered is acceptable.
|
|
114
|
+
|
|
74
115
|
## Appearance bootstrap (no-FOUC color mode)
|
|
75
116
|
|
|
76
117
|
Optional. Restores a persisted color mode before first paint with zero runtime
|
|
@@ -128,6 +169,49 @@ Two rules either way:
|
|
|
128
169
|
you add to the script tag. The full CSP footguns are documented in the
|
|
129
170
|
[`@animus-ui/vite-plugin` README](https://github.com/codecaaron/animus/tree/main/packages/vite-plugin#content-security-policy).
|
|
130
171
|
|
|
172
|
+
## Font preloads
|
|
173
|
+
|
|
174
|
+
A font face declared with `preload: true` can start loading before the
|
|
175
|
+
stylesheet's `@font-face` applies. The plugin injects nothing into the
|
|
176
|
+
document, so render the component that delivers it. It renders nothing when
|
|
177
|
+
no face preloads.
|
|
178
|
+
|
|
179
|
+
**App Router:** render `AnimusFontPreloads` in the root layout. React hoists
|
|
180
|
+
each preload into `<head>` ahead of the stylesheets, and dedupes it by href,
|
|
181
|
+
against `next/font` too.
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
// app/layout.tsx
|
|
185
|
+
import { AnimusFontPreloads } from '@animus-ui/next-plugin/font-preloads';
|
|
186
|
+
|
|
187
|
+
export default function RootLayout({
|
|
188
|
+
children,
|
|
189
|
+
}: {
|
|
190
|
+
children: React.ReactNode;
|
|
191
|
+
}) {
|
|
192
|
+
return (
|
|
193
|
+
<html lang="en">
|
|
194
|
+
<body>
|
|
195
|
+
<AnimusFontPreloads />
|
|
196
|
+
{children}
|
|
197
|
+
</body>
|
|
198
|
+
</html>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Pages Router:** render `AnimusFontPreloadLinks` inside `<Head>` in
|
|
204
|
+
`pages/_document.tsx`. It renders plain `<link>` tags, which any React
|
|
205
|
+
version renders.
|
|
206
|
+
|
|
207
|
+
Each preload names the URL the stylesheet's `url()` loads (the hashed
|
|
208
|
+
`/_next/static/media/…` file, under webpack and Turbopack, in build and in
|
|
209
|
+
development), so the browser fetches the file once. `withAnimus` adds this
|
|
210
|
+
package to `transpilePackages`, so Next bundles the component and resolves
|
|
211
|
+
the module it reads. Nothing detects a layout that never renders the
|
|
212
|
+
component: the faces then load when the stylesheet applies, as without
|
|
213
|
+
`preload`.
|
|
214
|
+
|
|
131
215
|
## MDX
|
|
132
216
|
|
|
133
217
|
Animus reads each `.mdx` file as the JSX it renders: its imports, exports,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.cjs");
|
|
3
|
+
let react = require("react");
|
|
4
|
+
let react_dom = require("react-dom");
|
|
5
|
+
let virtual_animus_font_preloads = require("virtual:animus/font-preloads");
|
|
6
|
+
virtual_animus_font_preloads = require_rolldown_runtime.__toESM(virtual_animus_font_preloads);
|
|
7
|
+
//#region src/font-preloads.ts
|
|
8
|
+
/**
|
|
9
|
+
* The font preloads the system's faces ask for (`preload: true`), rendered
|
|
10
|
+
* into the document. Server-only; it imports neither the engine nor Node.
|
|
11
|
+
*/
|
|
12
|
+
/** The origins of the absolute hrefs, once each: an asset prefix on
|
|
13
|
+
* another host makes the bundler's URLs absolute. `fontPreloadOrigins`'s
|
|
14
|
+
* rule, restated: its module would bring the engine into the bundle. A
|
|
15
|
+
* malformed href warms no origin, and never fails the render. */
|
|
16
|
+
const ORIGINS = [...new Set(virtual_animus_font_preloads.default.flatMap(({ href }) => {
|
|
17
|
+
if (!/^(?:https?:)?\/\//i.test(href)) return [];
|
|
18
|
+
try {
|
|
19
|
+
const { origin } = new URL(href, "https://animus.invalid");
|
|
20
|
+
return [href.startsWith("//") ? `//${href.slice(2).split(/[/?#]/)[0]}` : origin];
|
|
21
|
+
} catch {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
}))];
|
|
25
|
+
/**
|
|
26
|
+
* App Router: render it in the root layout. React hoists each preload into
|
|
27
|
+
* `<head>` ahead of the stylesheets and dedupes it by href, against
|
|
28
|
+
* `next/font` too. Renders nothing; a no-op when no face preloads.
|
|
29
|
+
*/
|
|
30
|
+
function AnimusFontPreloads() {
|
|
31
|
+
for (const origin of ORIGINS) (0, react_dom.preconnect)(origin, { crossOrigin: "" });
|
|
32
|
+
for (const { href, type } of virtual_animus_font_preloads.default) (0, react_dom.preload)(href, {
|
|
33
|
+
as: "font",
|
|
34
|
+
type,
|
|
35
|
+
crossOrigin: ""
|
|
36
|
+
});
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Pages Router: render it inside `<Head>` in `pages/_document`, as plain
|
|
41
|
+
* `<link>` tags any React version renders. Empty when no face preloads.
|
|
42
|
+
*/
|
|
43
|
+
function AnimusFontPreloadLinks() {
|
|
44
|
+
return (0, react.createElement)(react.Fragment, null, ...ORIGINS.map((origin) => (0, react.createElement)("link", {
|
|
45
|
+
key: origin,
|
|
46
|
+
rel: "preconnect",
|
|
47
|
+
href: origin,
|
|
48
|
+
crossOrigin: ""
|
|
49
|
+
})), ...virtual_animus_font_preloads.default.map(({ href, type }) => (0, react.createElement)("link", {
|
|
50
|
+
key: href,
|
|
51
|
+
rel: "preload",
|
|
52
|
+
href,
|
|
53
|
+
as: "font",
|
|
54
|
+
type,
|
|
55
|
+
crossOrigin: ""
|
|
56
|
+
})));
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
exports.AnimusFontPreloadLinks = AnimusFontPreloadLinks;
|
|
60
|
+
exports.AnimusFontPreloads = AnimusFontPreloads;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* App Router: render it in the root layout. React hoists each preload into
|
|
4
|
+
* `<head>` ahead of the stylesheets and dedupes it by href, against
|
|
5
|
+
* `next/font` too. Renders nothing; a no-op when no face preloads.
|
|
6
|
+
*/
|
|
7
|
+
export declare function AnimusFontPreloads(): null;
|
|
8
|
+
/**
|
|
9
|
+
* Pages Router: render it inside `<Head>` in `pages/_document`, as plain
|
|
10
|
+
* `<link>` tags any React version renders. Empty when no face preloads.
|
|
11
|
+
*/
|
|
12
|
+
export declare function AnimusFontPreloadLinks(): ReactElement;
|
|
13
|
+
//# sourceMappingURL=font-preloads.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"font-preloads.d.ts","sourceRoot":"","sources":["../src/font-preloads.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AA4B1C;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAMzC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,YAAY,CAuBrD"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Fragment, createElement } from "react";
|
|
2
|
+
import { preconnect, preload } from "react-dom";
|
|
3
|
+
import preloads from "virtual:animus/font-preloads";
|
|
4
|
+
//#region src/font-preloads.ts
|
|
5
|
+
/**
|
|
6
|
+
* The font preloads the system's faces ask for (`preload: true`), rendered
|
|
7
|
+
* into the document. Server-only; it imports neither the engine nor Node.
|
|
8
|
+
*/
|
|
9
|
+
/** The origins of the absolute hrefs, once each: an asset prefix on
|
|
10
|
+
* another host makes the bundler's URLs absolute. `fontPreloadOrigins`'s
|
|
11
|
+
* rule, restated: its module would bring the engine into the bundle. A
|
|
12
|
+
* malformed href warms no origin, and never fails the render. */
|
|
13
|
+
const ORIGINS = [...new Set(preloads.flatMap(({ href }) => {
|
|
14
|
+
if (!/^(?:https?:)?\/\//i.test(href)) return [];
|
|
15
|
+
try {
|
|
16
|
+
const { origin } = new URL(href, "https://animus.invalid");
|
|
17
|
+
return [href.startsWith("//") ? `//${href.slice(2).split(/[/?#]/)[0]}` : origin];
|
|
18
|
+
} catch {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
}))];
|
|
22
|
+
/**
|
|
23
|
+
* App Router: render it in the root layout. React hoists each preload into
|
|
24
|
+
* `<head>` ahead of the stylesheets and dedupes it by href, against
|
|
25
|
+
* `next/font` too. Renders nothing; a no-op when no face preloads.
|
|
26
|
+
*/
|
|
27
|
+
function AnimusFontPreloads() {
|
|
28
|
+
for (const origin of ORIGINS) preconnect(origin, { crossOrigin: "" });
|
|
29
|
+
for (const { href, type } of preloads) preload(href, {
|
|
30
|
+
as: "font",
|
|
31
|
+
type,
|
|
32
|
+
crossOrigin: ""
|
|
33
|
+
});
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Pages Router: render it inside `<Head>` in `pages/_document`, as plain
|
|
38
|
+
* `<link>` tags any React version renders. Empty when no face preloads.
|
|
39
|
+
*/
|
|
40
|
+
function AnimusFontPreloadLinks() {
|
|
41
|
+
return createElement(Fragment, null, ...ORIGINS.map((origin) => createElement("link", {
|
|
42
|
+
key: origin,
|
|
43
|
+
rel: "preconnect",
|
|
44
|
+
href: origin,
|
|
45
|
+
crossOrigin: ""
|
|
46
|
+
})), ...preloads.map(({ href, type }) => createElement("link", {
|
|
47
|
+
key: href,
|
|
48
|
+
rel: "preload",
|
|
49
|
+
href,
|
|
50
|
+
as: "font",
|
|
51
|
+
type,
|
|
52
|
+
crossOrigin: ""
|
|
53
|
+
})));
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { AnimusFontPreloadLinks, AnimusFontPreloads };
|
package/dist/index.cjs
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_turbopack_config = require("./turbopack-config-
|
|
2
|
+
const require_turbopack_config = require("./turbopack-config-W5eoa_xN.cjs");
|
|
3
3
|
let _animus_ui_extract_pipeline = require("@animus-ui/extract/pipeline");
|
|
4
4
|
let _animus_ui_extract_session = require("@animus-ui/extract/session");
|
|
5
5
|
let fs = require("fs");
|
|
6
6
|
let path = require("path");
|
|
7
7
|
let url = require("url");
|
|
8
|
+
let module$1 = require("module");
|
|
8
9
|
//#region src/same-directory.ts
|
|
9
10
|
/** Whether two paths name one directory: by real path, which resolves a
|
|
10
11
|
* trailing slash, a symlink and the filesystem's own spelling, else by
|
|
11
12
|
* device and inode. */
|
|
12
13
|
function sameDirectory(a, b) {
|
|
13
|
-
const real = (path$
|
|
14
|
+
const real = (path$12) => {
|
|
14
15
|
try {
|
|
15
|
-
return fs.realpathSync.native(path$
|
|
16
|
+
return fs.realpathSync.native(path$12);
|
|
16
17
|
} catch {
|
|
17
|
-
return (0, path.resolve)(path$
|
|
18
|
+
return (0, path.resolve)(path$12);
|
|
18
19
|
}
|
|
19
20
|
};
|
|
20
21
|
if (real(a) === real(b)) return true;
|
|
@@ -27,11 +28,97 @@ function sameDirectory(a, b) {
|
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
//#endregion
|
|
30
|
-
//#region src/
|
|
31
|
+
//#region src/watch-ignored.ts
|
|
32
|
+
/**
|
|
33
|
+
* webpack's `watchOptions.ignored`, read as Watchpack reads it, so Animus can
|
|
34
|
+
* wrap it: its own epoch file stays ignored, and the files and directories it
|
|
35
|
+
* registers are watched whatever the host ignores (Next ignores every
|
|
36
|
+
* `node_modules` path, where an installed source kit lives).
|
|
37
|
+
*/
|
|
31
38
|
/** The value arrives from a consumer's `next.config`, so the union above is a
|
|
32
39
|
* model of what webpack accepts, not a proof about the value in hand. */
|
|
33
40
|
const isWatchIgnoreString = (ignored) => Object.prototype.toString.call(ignored) === "[object String]";
|
|
34
41
|
const isWatchIgnoreMatcher = (ignored) => Object.prototype.toString.call(ignored) === "[object Function]";
|
|
42
|
+
/** A glob as Watchpack compiles it (`glob-to-regexp` with `globstar` and
|
|
43
|
+
* `extended`, then matching the path or anything under it). */
|
|
44
|
+
function globSource(glob) {
|
|
45
|
+
let source = "";
|
|
46
|
+
let inGroup = false;
|
|
47
|
+
for (let index = 0; index < glob.length; index++) {
|
|
48
|
+
const char = glob[index];
|
|
49
|
+
switch (char) {
|
|
50
|
+
case "/":
|
|
51
|
+
case "$":
|
|
52
|
+
case "^":
|
|
53
|
+
case "+":
|
|
54
|
+
case ".":
|
|
55
|
+
case "(":
|
|
56
|
+
case ")":
|
|
57
|
+
case "=":
|
|
58
|
+
case "!":
|
|
59
|
+
case "|":
|
|
60
|
+
source += `\\${char}`;
|
|
61
|
+
break;
|
|
62
|
+
case "?":
|
|
63
|
+
source += ".";
|
|
64
|
+
break;
|
|
65
|
+
case "{":
|
|
66
|
+
inGroup = true;
|
|
67
|
+
source += "(";
|
|
68
|
+
break;
|
|
69
|
+
case "}":
|
|
70
|
+
inGroup = false;
|
|
71
|
+
source += ")";
|
|
72
|
+
break;
|
|
73
|
+
case ",":
|
|
74
|
+
source += inGroup ? "|" : "\\,";
|
|
75
|
+
break;
|
|
76
|
+
case "*": {
|
|
77
|
+
const before = glob[index - 1];
|
|
78
|
+
let stars = 1;
|
|
79
|
+
while (glob[index + 1] === "*") {
|
|
80
|
+
stars += 1;
|
|
81
|
+
index += 1;
|
|
82
|
+
}
|
|
83
|
+
const after = glob[index + 1];
|
|
84
|
+
if (stars > 1 && (before === "/" || before === void 0) && (after === "/" || after === void 0)) {
|
|
85
|
+
source += "((?:[^/]*(?:/|$))*)";
|
|
86
|
+
index += 1;
|
|
87
|
+
} else source += "([^/]*)";
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
default: source += char;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return `^${source}(?:$|\\/)`;
|
|
94
|
+
}
|
|
95
|
+
/** What `ignored` ignores, as Watchpack decides it; nothing when unset. */
|
|
96
|
+
function ignoredMatcher(ignored) {
|
|
97
|
+
const slashed = (path$6) => path$6.replace(/\\/g, "/");
|
|
98
|
+
if (ignored === void 0 || ignored === null) return () => false;
|
|
99
|
+
if (isWatchIgnoreMatcher(ignored)) return (path$7) => Boolean(ignored(path$7));
|
|
100
|
+
if (ignored instanceof RegExp) return (path$8) => ignored.test(slashed(path$8));
|
|
101
|
+
const sources = (isWatchIgnoreString(ignored) ? [ignored] : ignored).map((entry) => entry instanceof RegExp ? entry.source : entry.length > 0 ? globSource(entry) : null).filter((source) => source !== null);
|
|
102
|
+
if (sources.length === 0) return () => false;
|
|
103
|
+
const pattern = new RegExp(sources.join("|"));
|
|
104
|
+
return (path$9) => pattern.test(slashed(path$9));
|
|
105
|
+
}
|
|
106
|
+
/** Windows paths compare without case, as `path.win32.relative` does. */
|
|
107
|
+
const comparable = process.platform === "win32" ? (path$10) => path$10.toLowerCase() : (path$11) => path$11;
|
|
108
|
+
/** Whether a path is one of `directories` or lies under one where a kit's
|
|
109
|
+
* sources can live: never in the kit's own `node_modules` or another path
|
|
110
|
+
* ingestion excludes, which stay as the host decides. A prefix test:
|
|
111
|
+
* webpack's watcher asks for every path it watches. */
|
|
112
|
+
function withinDirectories(directories) {
|
|
113
|
+
const prefixes = directories.map((dir) => comparable(dir.endsWith(path.sep) ? dir : dir + path.sep)).sort((a, b) => b.length - a.length);
|
|
114
|
+
return (path$5) => {
|
|
115
|
+
const key = comparable(path$5) + path.sep;
|
|
116
|
+
const prefix = prefixes.find((candidate) => key.startsWith(candidate));
|
|
117
|
+
return prefix !== void 0 && !(0, _animus_ui_extract_pipeline.isExcludedPackageRelativePath)(key.slice(prefix.length));
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/plugin.ts
|
|
35
122
|
const UNSUPPORTED_WEBPACK_MESSAGE = "[animus-extract] Unsupported webpack: NormalModule.getCompilationHooks(compilation).needBuild is required for dev transform coherence and this webpack does not expose it. Use a Next.js version whose webpack exposes this hook.";
|
|
36
123
|
const PLUGIN_NAME = "AnimusWebpackPlugin";
|
|
37
124
|
var AnimusWebpackPlugin = class {
|
|
@@ -48,6 +135,16 @@ var AnimusWebpackPlugin = class {
|
|
|
48
135
|
/** This turn's rejected analysis. A rejection must fail the compilation,
|
|
49
136
|
* never `watchRun`: webpack stops watching after a failed `watchRun`. */
|
|
50
137
|
watchFailure = null;
|
|
138
|
+
/** What the Session's watch plan last registered: `watchIgnored` never
|
|
139
|
+
* ignores it. */
|
|
140
|
+
registered = {
|
|
141
|
+
files: /* @__PURE__ */ new Set(),
|
|
142
|
+
directories: () => false
|
|
143
|
+
};
|
|
144
|
+
/** Other files the watcher must see, by the plugins that read them. */
|
|
145
|
+
alsoWatched = [];
|
|
146
|
+
/** The webpack config Next hands its watcher, when withAnimus made it. */
|
|
147
|
+
watchedConfig;
|
|
51
148
|
/** `development` is `next dev`: an error-level diagnostic reports and the
|
|
52
149
|
* compilation keeps publishing, whatever the emission mode. */
|
|
53
150
|
constructor(options, { development = false, build } = {}) {
|
|
@@ -55,6 +152,7 @@ var AnimusWebpackPlugin = class {
|
|
|
55
152
|
(0, _animus_ui_extract_pipeline.assertSupportedEngine)(options.engine);
|
|
56
153
|
this.session = new _animus_ui_extract_session.ExtractionSession(options, { build });
|
|
57
154
|
this.session.development = development;
|
|
155
|
+
this.session.writesFontPreloadsModule = true;
|
|
58
156
|
this.session.delivery = {
|
|
59
157
|
...this.session.delivery,
|
|
60
158
|
systemPropsModuleId: _animus_ui_extract_session.TURBOPACK_SYSTEM_PROPS_ID
|
|
@@ -67,19 +165,39 @@ var AnimusWebpackPlugin = class {
|
|
|
67
165
|
if (!Array.isArray(loaders)) return false;
|
|
68
166
|
return loaders.some((entry) => entry?.loader !== void 0 && this.animusLoaderPaths.has(entry.loader));
|
|
69
167
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
168
|
+
/** The webpack config whose watch options the host's watcher reads,
|
|
169
|
+
* rewritten when the plugin is applied. */
|
|
170
|
+
watchConfig(config) {
|
|
171
|
+
this.watchedConfig = config;
|
|
172
|
+
}
|
|
173
|
+
/** Makes `watchIgnored` watch the paths `watches` names too: one matcher
|
|
174
|
+
* owns the host's watch options. */
|
|
175
|
+
watchAlso(watches) {
|
|
176
|
+
this.alsoWatched.push(watches);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* `watchOptions.ignored` for the host's own `ignored`: this session's epoch
|
|
180
|
+
* file stays ignored, and every file and directory the Session's watch plan
|
|
181
|
+
* registers is watched, whatever the host ignores. Next ignores every
|
|
182
|
+
* `node_modules` path, where an installed source kit and its system live.
|
|
183
|
+
*/
|
|
184
|
+
watchIgnored(ignored, context) {
|
|
185
|
+
const hostIgnores = ignoredMatcher(ignored);
|
|
186
|
+
let epochRoot;
|
|
187
|
+
let epochPath;
|
|
188
|
+
const epoch = () => {
|
|
189
|
+
const root = this.session.rootDir ?? context;
|
|
190
|
+
if (root !== epochRoot) {
|
|
191
|
+
epochRoot = root;
|
|
192
|
+
epochPath = root === void 0 ? void 0 : (0, _animus_ui_extract_session.replacementEpochPath)((0, _animus_ui_extract_session.sessionArtifactDir)(root, this.session.sessionId));
|
|
193
|
+
}
|
|
194
|
+
return epochPath;
|
|
195
|
+
};
|
|
196
|
+
return (path$2) => {
|
|
197
|
+
if (path$2 === epoch()) return true;
|
|
198
|
+
if (this.registered.files.has(path$2) || this.registered.directories(path$2)) return false;
|
|
199
|
+
return hostIgnores(path$2) && !this.alsoWatched.some((watches) => watches(path$2));
|
|
200
|
+
};
|
|
83
201
|
}
|
|
84
202
|
/** Webpack's export conditions select the system's kit files as they
|
|
85
203
|
* select the bundle's; `...`, webpack's own defaults, names none. Its
|
|
@@ -127,7 +245,15 @@ var AnimusWebpackPlugin = class {
|
|
|
127
245
|
if (compiler.options?.name === "edge-server") return;
|
|
128
246
|
const NormalModule = compiler.webpack?.NormalModule;
|
|
129
247
|
if (NormalModule?.getCompilationHooks === void 0) throw new Error(UNSUPPORTED_WEBPACK_MESSAGE);
|
|
130
|
-
|
|
248
|
+
const options = compiler.options;
|
|
249
|
+
if (options) {
|
|
250
|
+
options.watchOptions ??= {};
|
|
251
|
+
options.watchOptions.ignored = this.watchIgnored(options.watchOptions.ignored, compiler.context);
|
|
252
|
+
}
|
|
253
|
+
if (this.watchedConfig) this.watchedConfig.watchOptions = {
|
|
254
|
+
...this.watchedConfig.watchOptions,
|
|
255
|
+
ignored: this.watchIgnored(this.watchedConfig.watchOptions?.ignored, compiler.context)
|
|
256
|
+
};
|
|
131
257
|
compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
|
|
132
258
|
const epochMoved = this.epochMovedForNextCompilation;
|
|
133
259
|
this.epochMovedForNextCompilation = false;
|
|
@@ -145,6 +271,10 @@ var AnimusWebpackPlugin = class {
|
|
|
145
271
|
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
|
|
146
272
|
const registerSystemDependencies = () => {
|
|
147
273
|
const plan = this.session.watchPlan();
|
|
274
|
+
this.registered = {
|
|
275
|
+
files: /* @__PURE__ */ new Set([...plan.files, ...plan.missing]),
|
|
276
|
+
directories: withinDirectories(plan.directories)
|
|
277
|
+
};
|
|
148
278
|
for (const path$3 of plan.files) if ((0, fs.existsSync)(path$3)) compilation.fileDependencies.add(path$3);
|
|
149
279
|
else compilation.missingDependencies.add(path$3);
|
|
150
280
|
for (const path$4 of plan.missing) compilation.missingDependencies.add(path$4);
|
|
@@ -174,13 +304,13 @@ var AnimusWebpackPlugin = class {
|
|
|
174
304
|
this.extractAliases(_compiler);
|
|
175
305
|
this.extractConditions(_compiler);
|
|
176
306
|
if (await this.rejoin()) {
|
|
177
|
-
this.session.agreeWithSystemAuthority();
|
|
307
|
+
await this.session.agreeWithSystemAuthority();
|
|
178
308
|
return;
|
|
179
309
|
}
|
|
180
310
|
const existing = (0, _animus_ui_extract_session.getAnalysisStartedPromise)(this.session.key);
|
|
181
311
|
if (existing) {
|
|
182
312
|
await existing;
|
|
183
|
-
this.session.agreeWithSystemAuthority();
|
|
313
|
+
await this.session.agreeWithSystemAuthority();
|
|
184
314
|
return;
|
|
185
315
|
}
|
|
186
316
|
const promise = this.session.runFullPipeline();
|
|
@@ -212,7 +342,7 @@ var AnimusWebpackPlugin = class {
|
|
|
212
342
|
}
|
|
213
343
|
async analyzeWatchTurn(compiler) {
|
|
214
344
|
if (!this.initialized && await this.rejoin()) {
|
|
215
|
-
this.session.agreeWithSystemAuthority();
|
|
345
|
+
await this.session.agreeWithSystemAuthority();
|
|
216
346
|
this.initialized = true;
|
|
217
347
|
this.lastBuiltEpoch = (0, _animus_ui_extract_session.getReplacementEpoch)(this.session.key);
|
|
218
348
|
this.epochMovedForNextCompilation = true;
|
|
@@ -230,7 +360,7 @@ var AnimusWebpackPlugin = class {
|
|
|
230
360
|
if ((0, _animus_ui_extract_session.getAnalysisStartedPromise)(this.session.key) === startup) (0, _animus_ui_extract_session.resetAnalysisStartedPromise)(this.session.key);
|
|
231
361
|
throw err;
|
|
232
362
|
}
|
|
233
|
-
this.session.agreeWithSystemAuthority();
|
|
363
|
+
await this.session.agreeWithSystemAuthority();
|
|
234
364
|
this.initialized = true;
|
|
235
365
|
this.lastBuiltEpoch = (0, _animus_ui_extract_session.getReplacementEpoch)(this.session.key);
|
|
236
366
|
this.epochMovedForNextCompilation = this.watchFailure !== null;
|
|
@@ -240,7 +370,7 @@ var AnimusWebpackPlugin = class {
|
|
|
240
370
|
modifiedFiles: compiler.modifiedFiles,
|
|
241
371
|
removedFiles: compiler.removedFiles
|
|
242
372
|
});
|
|
243
|
-
this.session.agreeWithSystemAuthority();
|
|
373
|
+
await this.session.agreeWithSystemAuthority();
|
|
244
374
|
const epoch = (0, _animus_ui_extract_session.getReplacementEpoch)(this.session.key);
|
|
245
375
|
this.epochMovedForNextCompilation = this.lastBuiltEpoch !== null && epoch !== null && epoch !== this.lastBuiltEpoch;
|
|
246
376
|
if (epoch !== null) this.lastBuiltEpoch = epoch;
|
|
@@ -296,6 +426,29 @@ var AnimusWebpackPlugin = class {
|
|
|
296
426
|
}
|
|
297
427
|
};
|
|
298
428
|
//#endregion
|
|
429
|
+
//#region src/quarantine.ts
|
|
430
|
+
/**
|
|
431
|
+
* layergate's webpack plugin for the `quarantine` option, loaded on first
|
|
432
|
+
* use: layergate is an optional peer. Only the client compiler builds
|
|
433
|
+
* stylesheets, so only it gets one.
|
|
434
|
+
*/
|
|
435
|
+
/** The error Turbopack meets: it runs no loader on a stylesheet's
|
|
436
|
+
* imports, so layergate cannot load a graph there. */
|
|
437
|
+
const QUARANTINE_TURBOPACK_MESSAGE = "[animus-extract] the `quarantine` option needs webpack: Turbopack runs no loader on the stylesheets a stylesheet imports, so it cannot keep them in `anm-vendor`. Run `next build --webpack` and `next dev --webpack`, or remove `quarantine` [layergate.turbopack]";
|
|
438
|
+
function quarantinePlugin(options, order, declaredBy) {
|
|
439
|
+
let layergate;
|
|
440
|
+
try {
|
|
441
|
+
layergate = (0, module$1.createRequire)(require("url").pathToFileURL(__filename).href)("@animus-ui/layergate/webpack");
|
|
442
|
+
} catch (cause) {
|
|
443
|
+
throw (0, _animus_ui_extract_pipeline.layergateMissing)("@animus-ui/next-plugin", cause);
|
|
444
|
+
}
|
|
445
|
+
return new layergate.LayergatePlugin({
|
|
446
|
+
...(0, _animus_ui_extract_pipeline.layergateOptions)(options, order),
|
|
447
|
+
declaredBy,
|
|
448
|
+
watchIgnored: false
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
//#endregion
|
|
299
452
|
//#region src/with-animus.ts
|
|
300
453
|
const __filename$1 = (0, url.fileURLToPath)(require("url").pathToFileURL(__filename).href);
|
|
301
454
|
const __dirname$1 = (0, path.dirname)(__filename$1);
|
|
@@ -313,7 +466,8 @@ function withAnimus(options) {
|
|
|
313
466
|
"turbopack",
|
|
314
467
|
"unstable_turbopack",
|
|
315
468
|
"loaderPath",
|
|
316
|
-
"phase"
|
|
469
|
+
"phase",
|
|
470
|
+
"quarantine"
|
|
317
471
|
], [], {
|
|
318
472
|
onUnknownKey: "warn",
|
|
319
473
|
warn: (message) => console.warn(`[animus-extract] ${message}`)
|
|
@@ -325,8 +479,10 @@ function withAnimus(options) {
|
|
|
325
479
|
const build = (0, _animus_ui_extract_session.createBuildId)();
|
|
326
480
|
const appRoot = options.root === void 0 ? process.cwd() : (0, path.resolve)(options.root);
|
|
327
481
|
return (authoredConfig) => {
|
|
328
|
-
const nextConfig =
|
|
329
|
-
|
|
482
|
+
const nextConfig = withTranspiled(authoredConfig, transpiledPackages(appRoot));
|
|
483
|
+
const turbopack = require_turbopack_config.resolveTurbopackMode(options);
|
|
484
|
+
if (options.quarantine && (turbopack || "TURBOPACK" in process.env)) throw new _animus_ui_extract_pipeline.AnimusConfigError(QUARANTINE_TURBOPACK_MESSAGE);
|
|
485
|
+
if (turbopack) {
|
|
330
486
|
warnTurbopackCwdRoot(options, appRoot);
|
|
331
487
|
return turbopackConfig(nextConfig, options, build, appRoot);
|
|
332
488
|
}
|
|
@@ -338,8 +494,11 @@ function withAnimus(options) {
|
|
|
338
494
|
if (context.dir?.length && !sameDirectory(context.dir, appRoot)) throw new _animus_ui_extract_pipeline.AnimusConfigError(`[animus-extract] Next builds ${context.dir}, but Animus read the app's source kits from ${appRoot}. Run next from the app directory, or pass withAnimus({ root }) naming it.`);
|
|
339
495
|
const rootDir = appRoot;
|
|
340
496
|
const { mode } = (0, _animus_ui_extract_pipeline.resolveMode)(options.mode, () => context.dev === true ? "development" : "production");
|
|
497
|
+
const vendorOrder = options.quarantine ? (0, _animus_ui_extract_pipeline.withVendorLayer)(options.layers) : void 0;
|
|
498
|
+
const layers = vendorOrder ?? options.layers;
|
|
341
499
|
const plugin = new AnimusWebpackPlugin({
|
|
342
500
|
...options,
|
|
501
|
+
layers,
|
|
343
502
|
mode
|
|
344
503
|
}, {
|
|
345
504
|
development: context.dev === true,
|
|
@@ -352,13 +511,15 @@ function withAnimus(options) {
|
|
|
352
511
|
const stubCssPath = (0, _animus_ui_extract_session.stylesPath)(sessionDir);
|
|
353
512
|
if (!(0, fs.existsSync)(stubCssPath)) {
|
|
354
513
|
const { declaration } = (0, _animus_ui_extract_pipeline.assembleStylesheet)({
|
|
355
|
-
layers
|
|
514
|
+
layers,
|
|
356
515
|
variableCss: "",
|
|
357
516
|
globalCss: "",
|
|
358
517
|
split: true
|
|
359
518
|
});
|
|
360
519
|
(0, fs.writeFileSync)(stubCssPath, declaration);
|
|
361
520
|
}
|
|
521
|
+
const sessionFontPreloadsPath = (0, _animus_ui_extract_session.fontPreloadsPath)(sessionDir);
|
|
522
|
+
if (!(0, fs.existsSync)(sessionFontPreloadsPath)) (0, fs.writeFileSync)(sessionFontPreloadsPath, (0, _animus_ui_extract_pipeline.fontPreloadsModule)([]));
|
|
362
523
|
if (!warnedGitignore) {
|
|
363
524
|
warnedGitignore = true;
|
|
364
525
|
try {
|
|
@@ -371,6 +532,12 @@ function withAnimus(options) {
|
|
|
371
532
|
const isExternalPackageFile = (filePath) => plugin.getExternalPackageDirs().some((dir) => (0, _animus_ui_extract_pipeline.isPathWithinRoot)(dir, filePath));
|
|
372
533
|
config.plugins = config.plugins || [];
|
|
373
534
|
config.plugins.push(plugin);
|
|
535
|
+
plugin.watchConfig(config);
|
|
536
|
+
if (options.quarantine && vendorOrder && context.isServer !== true) {
|
|
537
|
+
const gate = quarantinePlugin(options.quarantine, vendorOrder, stubCssPath);
|
|
538
|
+
config.plugins.push(gate);
|
|
539
|
+
plugin.watchAlso((path$1) => gate.watches(path$1));
|
|
540
|
+
}
|
|
374
541
|
const DefinePlugin = context.webpack?.DefinePlugin;
|
|
375
542
|
if (DefinePlugin) config.plugins.push(new DefinePlugin({ __ANIMUS_DEV__: JSON.stringify(mode === "development") }));
|
|
376
543
|
config.resolve = config.resolve || {};
|
|
@@ -382,6 +549,7 @@ function withAnimus(options) {
|
|
|
382
549
|
compiler.hooks.normalModuleFactory.tap("AnimusVirtualResolve", (nmf) => {
|
|
383
550
|
nmf.hooks.beforeResolve.tap("AnimusVirtualResolve", (resolveData) => {
|
|
384
551
|
if (resolveData.request === "virtual:animus/system-props") resolveData.request = sessionSystemPropsPath;
|
|
552
|
+
if (resolveData.request === _animus_ui_extract_session.FONT_PRELOADS_MODULE_ID) resolveData.request = sessionFontPreloadsPath;
|
|
385
553
|
if (resolveData.request === "virtual:animus/styles.css" || resolveData.request === _animus_ui_extract_session.ANIMUS_CSS_MODULE_ID) resolveData.request = (0, _animus_ui_extract_session.stylesPath)(sessionDir);
|
|
386
554
|
const srcEntry = plugin.getExternalSourceEntries().get(resolveData.request);
|
|
387
555
|
if (srcEntry) {
|
|
@@ -438,11 +606,7 @@ function bindTurbopackWatchDeathReport(outcome, rootDir) {
|
|
|
438
606
|
console.error(`[animus-extract] dev watcher for ${rootDir} died — source edits are no longer extracted; restart the dev server`);
|
|
439
607
|
};
|
|
440
608
|
}
|
|
441
|
-
|
|
442
|
-
* `transpilePackages`. The kit redirect points both bundles at a kit's
|
|
443
|
-
* source, but Next's server keeps an installed package external, and so
|
|
444
|
-
* loads its runtime entry, unless the package is transpiled; transpiling
|
|
445
|
-
* also compiles its TypeScript source. */
|
|
609
|
+
const NEXT_PLUGIN_PACKAGE = "@animus-ui/next-plugin";
|
|
446
610
|
let warnedTurbopackCwd = false;
|
|
447
611
|
/** Turbopack's loaders get no app dir either, so a cwd that is not the app
|
|
448
612
|
* cannot be detected later: warn at startup when the cwd holds no Next
|
|
@@ -459,15 +623,25 @@ function warnTurbopackCwdRoot(options, appRoot) {
|
|
|
459
623
|
warnedTurbopackCwd = true;
|
|
460
624
|
console.warn(`[animus-extract] the current directory (${appRoot}) holds no next.config: under Turbopack, Animus reads the app from the current directory. Run next from the app directory, or pass withAnimus({ root }) naming it.`);
|
|
461
625
|
}
|
|
462
|
-
|
|
626
|
+
/** The config with `packages` added to `transpilePackages`. Next's server
|
|
627
|
+
* keeps an installed package external, and so loads its runtime entry,
|
|
628
|
+
* unless the package is transpiled. */
|
|
629
|
+
function withTranspiled(nextConfig, packages) {
|
|
463
630
|
const declared = nextConfig.transpilePackages ?? [];
|
|
464
|
-
const
|
|
465
|
-
if (
|
|
631
|
+
const added = packages.filter((name) => !declared.includes(name));
|
|
632
|
+
if (added.length === 0) return nextConfig;
|
|
466
633
|
return {
|
|
467
634
|
...nextConfig,
|
|
468
|
-
transpilePackages: [...declared, ...
|
|
635
|
+
transpilePackages: [...declared, ...added]
|
|
469
636
|
};
|
|
470
637
|
}
|
|
638
|
+
/** The packages Next must transpile: the source kits the app depends on,
|
|
639
|
+
* since the kit redirect points both bundles at a kit's TypeScript source,
|
|
640
|
+
* and this package, whose font-preloads entry imports the session module
|
|
641
|
+
* only the bundle's alias resolves. */
|
|
642
|
+
function transpiledPackages(rootDir) {
|
|
643
|
+
return [...(0, _animus_ui_extract_pipeline.sourceKitDependencies)(rootDir, (0, _animus_ui_extract_session.requireEngine)()).map((kit) => kit.name), NEXT_PLUGIN_PACKAGE];
|
|
644
|
+
}
|
|
471
645
|
function turbopackConfig(nextConfig, options, build, appRoot) {
|
|
472
646
|
const resolveExtensions = nextConfig.turbopack?.resolveExtensions ?? nextConfig.experimental?.turbo?.resolveExtensions;
|
|
473
647
|
const outputExclude = nextConfig.distDir ? (0, _animus_ui_extract_pipeline.outputExclusion)(appRoot, (0, path.resolve)(appRoot, nextConfig.distDir)) : null;
|
|
@@ -525,6 +699,7 @@ async function analyzeForTurbopack(options, phase, rootDir, resolveExtensions, o
|
|
|
525
699
|
unbundledComputedImports: true,
|
|
526
700
|
build: `${build}-${phase}`
|
|
527
701
|
});
|
|
702
|
+
session.writesFontPreloadsModule = true;
|
|
528
703
|
const owner = turbopackOwner(build);
|
|
529
704
|
owner.session?.close();
|
|
530
705
|
owner.watcher?.close();
|