@flamingo-stack/openframe-frontend-core 0.0.834 → 0.0.836
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.
|
@@ -68,8 +68,9 @@ function withInlineStyles(nextConfig) {
|
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
70
|
* `withInlineStyles`, and per-route stylesheets when the build is `next-styles build` (`./route-styles.cjs`): the
|
|
71
|
-
*
|
|
72
|
-
*
|
|
71
|
+
* build runs the route step as its build adapter (`./route-styles-adapter.cjs`, which wraps the adapter the build
|
|
72
|
+
* would have had: the config's own `adapterPath`, else the host's `NEXT_ADAPTER_PATH`), knows its build, and records
|
|
73
|
+
* the settings the route step reads. Any other build, and `next dev`, is `withInlineStyles` alone.
|
|
73
74
|
*/
|
|
74
75
|
function withRouteStyles(nextConfig) {
|
|
75
76
|
return async (phase, ...rest) => {
|
|
@@ -86,13 +87,18 @@ function withRouteStyles(nextConfig) {
|
|
|
86
87
|
output: inline.output ?? null,
|
|
87
88
|
assetPrefix: inline.assetPrefix ?? '',
|
|
88
89
|
basePath: inline.basePath ?? '',
|
|
90
|
+
inner: inline.adapterPath ?? process.env.NEXT_ADAPTER_PATH ?? null,
|
|
91
|
+
options: JSON.parse(process.env.NEXT_STYLES_OPTIONS || '{}'),
|
|
89
92
|
};
|
|
90
93
|
writeFileSync(infoFile, `${JSON.stringify(info, null, 2)}\n`);
|
|
91
|
-
// Every route: the files are read at run time by `RouteStyles`, which no static trace can follow
|
|
94
|
+
// Every route: the files are read at run time by `RouteStyles`, which no static trace can follow (the adapter
|
|
95
|
+
// adds the sheets it writes after the trace to the server functions it hands on).
|
|
92
96
|
const includes = { ...inline.outputFileTracingIncludes };
|
|
93
97
|
for (const route of ['/', '/*', '/*/**']) includes[route] = [...(includes[route] ?? []), `./${STYLES_DIR}/**/*`];
|
|
94
98
|
return {
|
|
95
99
|
...inline,
|
|
100
|
+
// A path, never a require: the config must not reach the build tool (see `loadRouteStyles`).
|
|
101
|
+
adapterPath: join(__dirname, 'route-styles-adapter.cjs'),
|
|
96
102
|
env: { ...inline.env, NEXT_STYLES_BUILD: buildId, NEXT_STYLES_FULL_URL: fullSheetUrl(info) },
|
|
97
103
|
outputFileTracingIncludes: includes,
|
|
98
104
|
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The route step of `next-styles build` as a Next.js build adapter (`adapterPath`), set by `withRouteStyles`.
|
|
3
|
+
*
|
|
4
|
+
* WHY. A step that runs after `next build` changes files nobody serves any more on a host whose own build adapter
|
|
5
|
+
* packages the output when `next build` ends: on Vercel ("Applying modifyConfig from Vercel" in the build log) the
|
|
6
|
+
* pages, functions and static files are collected by the adapter's `onBuildComplete`, so the placeholders filled
|
|
7
|
+
* afterwards reached production unfilled and a page rendered per request found no `.next-styles/` (2026-11-01,
|
|
8
|
+
* hub #1753, rolled back). An adapter's `onBuildComplete` is the one hook Next runs after every page is prerendered
|
|
9
|
+
* and before anything is packaged (before the standalone copy too), so the step runs there.
|
|
10
|
+
*
|
|
11
|
+
* HOW. Next loads one adapter, so this one WRAPS the adapter the build would have had (`build.json`'s `inner`: the
|
|
12
|
+
* app's own `adapterPath`, else `NEXT_ADAPTER_PATH`, which the host sets): its `modifyConfig` is the inner one's,
|
|
13
|
+
* and its `onBuildComplete` writes the route sheets, fills the prerendered pages, adds what it wrote to the outputs
|
|
14
|
+
* the inner adapter packages (`.next-styles/` to every server function, the complete sheet to the static files),
|
|
15
|
+
* and then hands the outputs on. With no inner adapter (a build on this machine, a Docker image) the build goes on
|
|
16
|
+
* as Next's own.
|
|
17
|
+
*/
|
|
18
|
+
const crypto = require('node:crypto');
|
|
19
|
+
const fs = require('node:fs');
|
|
20
|
+
const { join, relative, resolve } = require('node:path');
|
|
21
|
+
const { pathToFileURL } = require('node:url');
|
|
22
|
+
|
|
23
|
+
const { STYLES_DIR, BUILD_INFO } = require('./route-styles-markers.cjs');
|
|
24
|
+
|
|
25
|
+
const readInfo = root => JSON.parse(fs.readFileSync(join(root, STYLES_DIR, BUILD_INFO), 'utf8'));
|
|
26
|
+
|
|
27
|
+
/** The adapter this one wraps (`null` for none), loaded once. */
|
|
28
|
+
let innerAdapter;
|
|
29
|
+
async function inner(root) {
|
|
30
|
+
if (innerAdapter !== undefined) return innerAdapter;
|
|
31
|
+
const path = readInfo(root).inner;
|
|
32
|
+
if (!path) return (innerAdapter = null);
|
|
33
|
+
const mod = await import(pathToFileURL(require.resolve(path, { paths: [root] })).href);
|
|
34
|
+
return (innerAdapter = mod.default ?? mod);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Every file under `dir`. */
|
|
38
|
+
function filesUnder(dir, out = []) {
|
|
39
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
40
|
+
const path = join(dir, entry.name);
|
|
41
|
+
if (entry.isDirectory()) filesUnder(path, out);
|
|
42
|
+
else out.push(path);
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** A file's content hash as Next's adapter computes it (`hashFile` in next/dist/build/adapter/build-complete). */
|
|
48
|
+
function hashFile(salt, file) {
|
|
49
|
+
return crypto.createHash('sha256').update(salt).update('file:').update(fs.readFileSync(file)).digest('hex');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* `outputs` with what the route step wrote: `.next-styles/` in every Node.js server function (read at run time by
|
|
54
|
+
* `RouteStyles`, so no trace finds the sheets written after the trace), and the complete sheet among the static
|
|
55
|
+
* files (collected before this step wrote it).
|
|
56
|
+
*/
|
|
57
|
+
function addRouteStyles(ctx) {
|
|
58
|
+
const root = ctx.projectDir;
|
|
59
|
+
const info = readInfo(root);
|
|
60
|
+
const salt = ctx.config.outputHashSalt || '';
|
|
61
|
+
const files = filesUnder(join(root, STYLES_DIR)).map(file => ({
|
|
62
|
+
key: relative(ctx.repoRoot, file).split('\\').join('/'),
|
|
63
|
+
file,
|
|
64
|
+
hash: hashFile(salt, file),
|
|
65
|
+
}));
|
|
66
|
+
const functions = [
|
|
67
|
+
...ctx.outputs.appPages,
|
|
68
|
+
...ctx.outputs.appRoutes,
|
|
69
|
+
...ctx.outputs.pages,
|
|
70
|
+
...ctx.outputs.pagesApi,
|
|
71
|
+
].filter(output => output.runtime === 'nodejs');
|
|
72
|
+
for (const output of functions) {
|
|
73
|
+
for (const { key, file, hash } of files) {
|
|
74
|
+
output.assets[key] = file;
|
|
75
|
+
// Next records a hash only for a Turbopack build; where it records any, it records one for every asset.
|
|
76
|
+
if (output.assetsHashes && Object.keys(output.assetsHashes).length) output.assetsHashes[key] = hash;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const staticDir = join(resolve(root, info.distDir), 'static');
|
|
80
|
+
const sheets = join(staticDir, 'next-styles');
|
|
81
|
+
for (const file of fs.existsSync(sheets) ? filesUnder(sheets) : []) {
|
|
82
|
+
const id = relative(resolve(root, info.distDir), file).split('\\').join('/');
|
|
83
|
+
if (ctx.outputs.staticFiles.some(output => output.id === id)) continue;
|
|
84
|
+
ctx.outputs.staticFiles.push({
|
|
85
|
+
type: 'STATIC_FILE', // AdapterOutputType.STATIC_FILE (next/dist/shared/lib/constants)
|
|
86
|
+
id,
|
|
87
|
+
// Next adds the basePath to every pathname before it calls the adapter.
|
|
88
|
+
pathname: `${ctx.config.basePath || ''}/_next/${id}`,
|
|
89
|
+
filePath: file,
|
|
90
|
+
immutableHash: undefined,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = {
|
|
96
|
+
name: 'next-styles',
|
|
97
|
+
|
|
98
|
+
async modifyConfig(config, ctx) {
|
|
99
|
+
const adapter = await inner(ctx.projectDir);
|
|
100
|
+
return typeof adapter?.modifyConfig === 'function' ? adapter.modifyConfig(config, ctx) : config;
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
async onBuildComplete(ctx) {
|
|
104
|
+
const root = ctx.projectDir;
|
|
105
|
+
const { options } = readInfo(root);
|
|
106
|
+
// The build tool, loaded only here (`./next-styles.cjs` explains why the config must not reach it).
|
|
107
|
+
const { writeRouteStyles } = require(join(__dirname, 'route-styles.cjs'));
|
|
108
|
+
const report = await writeRouteStyles({ root, ...options });
|
|
109
|
+
if (report.over.length) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`next-styles: ${report.over.length} route sheet(s) over the budget: ${report.over.join(', ')} (listed above)`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
addRouteStyles(ctx);
|
|
115
|
+
const adapter = await inner(root);
|
|
116
|
+
if (typeof adapter?.onBuildComplete === 'function') await adapter.onBuildComplete(ctx);
|
|
117
|
+
},
|
|
118
|
+
};
|
|
@@ -47,9 +47,14 @@ function routePattern(route) {
|
|
|
47
47
|
return { route, dynamic, regex: new RegExp(`^${source || '/'}$`) };
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* The page's path as a route is written. `/index` is the home page: Next calls it that on the server (Vercel serves it
|
|
52
|
+
* as the `index` function, and a page regenerated there renders with `/index` as its path), and no app route is
|
|
53
|
+
* named `index` because Next answers `/index` itself.
|
|
54
|
+
*/
|
|
50
55
|
function normalize(pathname) {
|
|
51
56
|
const path = `/${String(pathname || '').replace(/^\/+/, '')}`.replace(/\/+$/, '');
|
|
52
|
-
return path === '' ? '/' : path;
|
|
57
|
+
return path === '' || path === '/index' ? '/' : path;
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
/**
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
* per route with the app's own Tailwind config, PostCSS plugins and stylesheet. It writes them where the
|
|
20
20
|
* server reads them (`.next-styles/`, traced into the server bundle) and fills each prerendered page's
|
|
21
21
|
* placeholder. A page rendered later (on its first request, or again after `revalidate`) reads its route's
|
|
22
|
-
* sheet when it renders.
|
|
22
|
+
* sheet when it renders. It runs inside `next build`, as a build adapter (`./route-styles-adapter.cjs`), once
|
|
23
|
+
* every page is prerendered and before anything is packaged: on Vercel, whose own adapter packages the build
|
|
24
|
+
* when `next build` ends, a step run after it reached no deployment.
|
|
23
25
|
* 3. The complete sheet, built the same way from the full config, is a static file the page loads after its
|
|
24
26
|
* first paint, for what the page renders later (a menu opened, a panel loaded, a page navigated to).
|
|
25
27
|
*
|
|
@@ -127,22 +129,21 @@ async function stylesheetBuilder({ root, input, config }) {
|
|
|
127
129
|
};
|
|
128
130
|
}
|
|
129
131
|
|
|
130
|
-
/** Every file under `dir` whose
|
|
132
|
+
/** Every file under `dir` whose name ends with `suffix`. */
|
|
131
133
|
function filesUnder(dir, suffix, out = []) {
|
|
132
134
|
if (!fs.existsSync(dir)) return out;
|
|
133
135
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
134
136
|
const path = join(dir, entry.name);
|
|
135
|
-
|
|
136
|
-
if (entry.
|
|
137
|
-
if (entry.name !== 'node_modules') filesUnder(path, suffix, out);
|
|
138
|
-
} else if (path.endsWith(suffix)) out.push(path);
|
|
137
|
+
if (entry.isDirectory()) filesUnder(path, suffix, out);
|
|
138
|
+
else if (entry.name.endsWith(suffix)) out.push(path);
|
|
139
139
|
}
|
|
140
140
|
return out;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
/**
|
|
144
144
|
* The prerendered pages of the build, each with the route it belongs to: `{ file, route }`. A server build keeps
|
|
145
|
-
* them under `<distDir>/server/app` (
|
|
145
|
+
* them under `<distDir>/server/app` (its static 404 under `<distDir>/server/pages`), a static export writes them
|
|
146
|
+
* into `distDir`.
|
|
146
147
|
* The route of a page is the prerender manifest's `srcRoute` (`/blog/[slug]` for `/blog/x`).
|
|
147
148
|
*/
|
|
148
149
|
function prerenderedPages(root, info) {
|
|
@@ -151,6 +152,9 @@ function prerenderedPages(root, info) {
|
|
|
151
152
|
const routes = fs.existsSync(manifestFile) ? JSON.parse(fs.readFileSync(manifestFile, 'utf8')).routes : {};
|
|
152
153
|
const routeOf = new Map();
|
|
153
154
|
for (const [path, entry] of Object.entries(routes)) routeOf.set(path, entry.srcRoute ?? path);
|
|
155
|
+
// Next copies the app's not-found page to the static 404 (`server/pages/404.html`, `404.html` in an export),
|
|
156
|
+
// which no prerender entry names: the host serves it for every missing address.
|
|
157
|
+
if (!routeOf.has('/404')) routeOf.set('/404', '/_not-found');
|
|
154
158
|
const pathOfFile = (base, file) => {
|
|
155
159
|
const rel = relative(base, file)
|
|
156
160
|
.split('\\')
|
|
@@ -159,7 +163,8 @@ function prerenderedPages(root, info) {
|
|
|
159
163
|
const path = `/${rel}`.replace(/\/index$/, '') || '/';
|
|
160
164
|
return path === '' ? '/' : path;
|
|
161
165
|
};
|
|
162
|
-
const bases =
|
|
166
|
+
const bases =
|
|
167
|
+
info.output === 'export' ? [distDir] : [join(distDir, 'server', 'app'), join(distDir, 'server', 'pages')];
|
|
163
168
|
const pages = [];
|
|
164
169
|
for (const base of bases) {
|
|
165
170
|
for (const file of filesUnder(base, '.html')) {
|
|
@@ -173,12 +178,6 @@ function prerenderedPages(root, info) {
|
|
|
173
178
|
return pages;
|
|
174
179
|
}
|
|
175
180
|
|
|
176
|
-
/** The standalone copies of a server build's files (`<distDir>/standalone/...`), found by the copy of `marker`. */
|
|
177
|
-
function standaloneCopies(root, info, marker) {
|
|
178
|
-
const standalone = resolve(root, info.distDir, 'standalone');
|
|
179
|
-
return filesUnder(standalone, marker).map(file => file.slice(0, -marker.length));
|
|
180
|
-
}
|
|
181
|
-
|
|
182
181
|
/** `html` with its placeholders filled: the route's rules `css` and the complete sheet `full`. */
|
|
183
182
|
function fillPlaceholder(html, { css, full }) {
|
|
184
183
|
return html.split(PLACEHOLDER).join(css).split(COMPLETE_PLACEHOLDER).join(full);
|
|
@@ -272,8 +271,8 @@ async function crawlRoutes(root, routes, { concurrency = 4, timeoutMs = 60_000 }
|
|
|
272
271
|
}
|
|
273
272
|
|
|
274
273
|
/**
|
|
275
|
-
* The route sheets of a
|
|
276
|
-
* `budget` (brotli bytes per route). Resolves to the
|
|
274
|
+
* The route sheets of a build whose pages are prerendered: built, written, filled into the prerendered pages and
|
|
275
|
+
* checked against `budget` (brotli bytes per route). Resolves to the manifest and `over`, the routes over budget.
|
|
277
276
|
*/
|
|
278
277
|
async function writeRouteStyles({ root, input, config, fullConfig, budget = DEFAULT_BUDGET, crawl = true }) {
|
|
279
278
|
const stylesDir = join(root, STYLES_DIR);
|
|
@@ -309,16 +308,10 @@ async function writeRouteStyles({ root, input, config, fullConfig, budget = DEFA
|
|
|
309
308
|
}
|
|
310
309
|
for (const page of [...pages, ...crawled]) routes[page.route].pages += 1;
|
|
311
310
|
|
|
312
|
-
// The server reads these (
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
];
|
|
317
|
-
for (const dir of targets) {
|
|
318
|
-
fs.mkdirSync(join(dir, 'sheets'), { recursive: true });
|
|
319
|
-
for (const [file, css] of sheets) fs.writeFileSync(join(dir, 'sheets', file), css);
|
|
320
|
-
fs.writeFileSync(join(dir, 'sheets', fullFile), full);
|
|
321
|
-
}
|
|
311
|
+
// The server reads these (added to every server function by the adapter); the complete sheet is also a static file.
|
|
312
|
+
fs.mkdirSync(join(stylesDir, 'sheets'), { recursive: true });
|
|
313
|
+
for (const [file, css] of sheets) fs.writeFileSync(join(stylesDir, 'sheets', file), css);
|
|
314
|
+
fs.writeFileSync(join(stylesDir, 'sheets', fullFile), full);
|
|
322
315
|
const staticDir =
|
|
323
316
|
info.output === 'export'
|
|
324
317
|
? join(resolve(root, info.distDir), '_next', 'static')
|
|
@@ -326,24 +319,12 @@ async function writeRouteStyles({ root, input, config, fullConfig, budget = DEFA
|
|
|
326
319
|
fs.mkdirSync(join(staticDir, 'next-styles'), { recursive: true });
|
|
327
320
|
fs.writeFileSync(join(staticDir, 'next-styles', fullFile), full);
|
|
328
321
|
const manifest = { version: 1, buildId: info.buildId, full: fullFile, fullUrl, routes };
|
|
329
|
-
|
|
322
|
+
fs.writeFileSync(join(stylesDir, MANIFEST), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
330
323
|
|
|
331
|
-
// Fill every prerendered page, the standalone copies included (the server answers those directly).
|
|
332
|
-
const copies = standaloneCopies(root, info, join(STYLES_DIR, BUILD_INFO)).map(dir =>
|
|
333
|
-
join(dir, relative(root, resolve(root, info.distDir)), 'server', 'app'),
|
|
334
|
-
);
|
|
335
324
|
for (const page of pages) {
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
page.file,
|
|
339
|
-
...copies.map(dir => join(dir, relative(join(resolve(root, info.distDir), 'server', 'app'), page.file))),
|
|
340
|
-
];
|
|
341
|
-
for (const file of files) {
|
|
342
|
-
if (!fs.existsSync(file)) continue;
|
|
343
|
-
const html = fs.readFileSync(file, 'utf8');
|
|
344
|
-
if (html.includes(PLACEHOLDER) || html.includes(COMPLETE_PLACEHOLDER)) {
|
|
345
|
-
fs.writeFileSync(file, fillPlaceholder(html, { css, full }));
|
|
346
|
-
}
|
|
325
|
+
const html = fs.readFileSync(page.file, 'utf8');
|
|
326
|
+
if (html.includes(PLACEHOLDER) || html.includes(COMPLETE_PLACEHOLDER)) {
|
|
327
|
+
fs.writeFileSync(page.file, fillPlaceholder(html, { css: sheets.get(routes[page.route].file), full }));
|
|
347
328
|
}
|
|
348
329
|
}
|
|
349
330
|
|
|
@@ -352,7 +333,7 @@ async function writeRouteStyles({ root, input, config, fullConfig, budget = DEFA
|
|
|
352
333
|
console.log(
|
|
353
334
|
`[next-styles] complete sheet for ${missed.length} route(s) that answered no page: ${missed.join(', ')}`,
|
|
354
335
|
);
|
|
355
|
-
const over = Object.
|
|
336
|
+
const over = Object.keys(routes).filter(route => routes[route].brotli > budget);
|
|
356
337
|
console.log(
|
|
357
338
|
`[next-styles] complete sheet ${kb(Buffer.byteLength(full))} raw, ${kb(brotli(full))} brotli (after paint)`,
|
|
358
339
|
);
|
|
@@ -366,9 +347,25 @@ async function writeRouteStyles({ root, input, config, fullConfig, budget = DEFA
|
|
|
366
347
|
`[next-styles] FAIL: ${over.length} route sheet(s) over ${kb(budget)} brotli. A route's sheet is what its page renders ` +
|
|
367
348
|
'before it is interactive: render less on the first screen (load the rest when it opens), or raise the budget with the reason.',
|
|
368
349
|
);
|
|
369
|
-
process.exitCode = 1;
|
|
370
350
|
}
|
|
371
|
-
return manifest;
|
|
351
|
+
return { ...manifest, over };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* The `.next-styles/` copies of a standalone build (`output: 'standalone'`, what a Docker image runs). Next copies
|
|
356
|
+
* the files a trace names, and the trace ran before the route step wrote its sheets, so each copy is replaced by
|
|
357
|
+
* the finished folder. The pages in the copy are the filled ones (the adapter ran before Next copied them).
|
|
358
|
+
*/
|
|
359
|
+
function standaloneStyleDirs(root, info, dir = resolve(root, info.distDir, 'standalone'), out = []) {
|
|
360
|
+
if (!fs.existsSync(dir)) return out;
|
|
361
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
362
|
+
// A standalone build copies its dependencies under node_modules: no folder of the app is there.
|
|
363
|
+
if (!entry.isDirectory() || entry.name === 'node_modules') continue;
|
|
364
|
+
const path = join(dir, entry.name);
|
|
365
|
+
if (entry.name === STYLES_DIR && fs.existsSync(join(path, BUILD_INFO))) out.push(path);
|
|
366
|
+
else standaloneStyleDirs(root, info, path, out);
|
|
367
|
+
}
|
|
368
|
+
return out;
|
|
372
369
|
}
|
|
373
370
|
|
|
374
371
|
/** Runs `command args` in `root` with `env` added, resolving on exit 0. */
|
|
@@ -383,8 +380,9 @@ function run(command, args, { root, env }) {
|
|
|
383
380
|
}
|
|
384
381
|
|
|
385
382
|
/**
|
|
386
|
-
* `next build` with per-route sheets: prepares `.next-styles
|
|
387
|
-
* writes the
|
|
383
|
+
* `next build` with per-route sheets: prepares `.next-styles/` and runs the build with `NEXT_STYLES=routes` and the
|
|
384
|
+
* route step's `options` (`withRouteStyles` hands both to the adapter, which writes the sheets). `nextArgs` are
|
|
385
|
+
* passed to `next build`.
|
|
388
386
|
*/
|
|
389
387
|
async function buildWithRouteStyles({ root, nextArgs = [], ...options }) {
|
|
390
388
|
const stylesDir = join(root, STYLES_DIR);
|
|
@@ -393,11 +391,21 @@ async function buildWithRouteStyles({ root, nextArgs = [], ...options }) {
|
|
|
393
391
|
// The server bundle traces these files when it is built, so they exist before it is (filled after it).
|
|
394
392
|
fs.writeFileSync(join(stylesDir, MANIFEST), `${JSON.stringify({ version: 1, pending: true })}\n`);
|
|
395
393
|
const nextBin = require.resolve('next/dist/bin/next', { paths: [root] });
|
|
396
|
-
await run(process.execPath, [nextBin, 'build', ...nextArgs], {
|
|
394
|
+
await run(process.execPath, [nextBin, 'build', ...nextArgs], {
|
|
395
|
+
root,
|
|
396
|
+
env: { NEXT_STYLES: 'routes', NEXT_STYLES_OPTIONS: JSON.stringify(options) },
|
|
397
|
+
});
|
|
397
398
|
if (!fs.existsSync(join(stylesDir, BUILD_INFO))) {
|
|
398
399
|
throw new Error('next-styles: the build did not record its settings: wrap next.config with withRouteStyles');
|
|
399
400
|
}
|
|
400
|
-
|
|
401
|
+
const manifest = JSON.parse(fs.readFileSync(join(stylesDir, MANIFEST), 'utf8'));
|
|
402
|
+
if (manifest.pending) throw new Error('next-styles: the build ran without its route step (the build adapter)');
|
|
403
|
+
const info = JSON.parse(fs.readFileSync(join(stylesDir, BUILD_INFO), 'utf8'));
|
|
404
|
+
for (const copy of standaloneStyleDirs(root, info)) {
|
|
405
|
+
fs.rmSync(copy, { recursive: true, force: true });
|
|
406
|
+
fs.cpSync(stylesDir, copy, { recursive: true });
|
|
407
|
+
}
|
|
408
|
+
return manifest;
|
|
401
409
|
}
|
|
402
410
|
|
|
403
411
|
module.exports = {
|