@rsc-kit/core 0.21.3 → 0.22.0
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/dist/js/navigate.js +16 -3
- package/dist/js/navigate.js.map +1 -1
- package/dist/vite.d.ts +25 -0
- package/dist/vite.js +245 -210
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
package/dist/vite.js
CHANGED
|
@@ -849,43 +849,6 @@ const INSTALL_FETCH_MS = 30000
|
|
|
849
849
|
const within = (ms, promise) =>
|
|
850
850
|
Promise.race([promise, new Promise((_, reject) => setTimeout(() => reject(new Error('no answer in ' + ms + ' ms')), ms))])
|
|
851
851
|
|
|
852
|
-
// The same limit, and the download stopped when it is reached. A race only
|
|
853
|
-
// stops the waiting: the fetch it gave up on went on downloading beside
|
|
854
|
-
// everything after it.
|
|
855
|
-
const abortable = (ms, start) => {
|
|
856
|
-
const controller = new AbortController()
|
|
857
|
-
const timer = setTimeout(() => controller.abort(), ms)
|
|
858
|
-
|
|
859
|
-
return within(ms, start(controller.signal)).finally(() => clearTimeout(timer))
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
// How many precache downloads run together. The page a visitor is looking at
|
|
863
|
-
// comes first; the precache is for the next visit.
|
|
864
|
-
const PRECACHE_AT_ONCE = 4
|
|
865
|
-
|
|
866
|
-
// Run a task over every item, at most limit of them at a time. Resolves once
|
|
867
|
-
// all have settled; a task that fails is its own business.
|
|
868
|
-
const pool = (items, limit, task) =>
|
|
869
|
-
new Promise((resolve) => {
|
|
870
|
-
let next = 0
|
|
871
|
-
let running = 0
|
|
872
|
-
const launch = () => {
|
|
873
|
-
if (next >= items.length && running === 0) return resolve()
|
|
874
|
-
|
|
875
|
-
while (running < limit && next < items.length) {
|
|
876
|
-
running++
|
|
877
|
-
Promise.resolve(task(items[next++]))
|
|
878
|
-
.catch(() => {})
|
|
879
|
-
.finally(() => {
|
|
880
|
-
running--
|
|
881
|
-
launch()
|
|
882
|
-
})
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
launch()
|
|
887
|
-
})
|
|
888
|
-
|
|
889
852
|
self.addEventListener('install', (event) => {
|
|
890
853
|
// The new worker takes over rather than waiting for every tab to close.
|
|
891
854
|
// Safe here because assets are content-hashed: a page already open keeps
|
|
@@ -899,33 +862,22 @@ self.addEventListener('install', (event) => {
|
|
|
899
862
|
// worker cannot see. Added here so it is in the cache before it is
|
|
900
863
|
// needed, which is the only moment it cannot be fetched.
|
|
901
864
|
//
|
|
902
|
-
//
|
|
903
|
-
// for one failure - and a worker whose install rejects is never
|
|
865
|
+
// Each file on its own rather than cache.addAll, which rejects the whole
|
|
866
|
+
// list for one failure - and a worker whose install rejects is never
|
|
904
867
|
// installed. The worker before it then stays in charge, serving its own
|
|
905
868
|
// build's pages, for every visit until one install happens to succeed:
|
|
906
|
-
// a network blip, a quota, a file a deploy replaced mid-install
|
|
907
|
-
// phone kept a worker from builds ago whose bugs had long been fixed.
|
|
869
|
+
// a network blip, a quota, a file a deploy replaced mid-install.
|
|
908
870
|
// A file that did not arrive costs a cache entry; the update still
|
|
909
871
|
// lands. The browser checks /sw.js for itself, without the page's
|
|
910
872
|
// javascript, so an install that cannot fail is also what rescues a
|
|
911
873
|
// visitor stuck on a worker far older than this one.
|
|
912
|
-
//
|
|
913
|
-
// A few at a time and at low priority, never all at once. An install
|
|
914
|
-
// runs beside the page that triggered it - the first visit after a
|
|
915
|
-
// deploy - and a hundred and seventy files requested together queued
|
|
916
|
-
// that page's own requests behind them: on a phone, the page sat
|
|
917
|
-
// unhydrated for about forty seconds, until the precache had finished.
|
|
918
874
|
.then((cache) =>
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
return cache.put(url, response)
|
|
875
|
+
Promise.all(
|
|
876
|
+
(OFFLINE_URL ? [...PRECACHE, OFFLINE_URL] : PRECACHE).map((url) =>
|
|
877
|
+
within(INSTALL_FETCH_MS, cache.add(url)).catch((error) => {
|
|
878
|
+
console.warn('[rsc-kit] offline: ' + url + ' was not precached (' + (error && error.message || error) + '); it will be cached the first time it is fetched')
|
|
925
879
|
}),
|
|
926
|
-
)
|
|
927
|
-
console.warn('[rsc-kit] offline: ' + url + ' was not precached (' + (error && error.message || error) + '); it will be cached the first time it is fetched')
|
|
928
|
-
}),
|
|
880
|
+
),
|
|
929
881
|
).then(() => cache),
|
|
930
882
|
)
|
|
931
883
|
// And the payload each precached page boots from. A document alone is
|
|
@@ -5558,6 +5510,35 @@ self.addEventListener('activate', (event) => {
|
|
|
5558
5510
|
})())
|
|
5559
5511
|
})
|
|
5560
5512
|
`;
|
|
5513
|
+
/**
|
|
5514
|
+
* A read of import.meta.env.PUBLIC_*, refused by name.
|
|
5515
|
+
*
|
|
5516
|
+
* PUBLIC_ was a client prefix beside VITE_ until 0.22. Dropped silently, a
|
|
5517
|
+
* read of one would compile to undefined - in the browser, for a pixel id or
|
|
5518
|
+
* an api url, with nothing said anywhere - so the build names each file that
|
|
5519
|
+
* still reads one and the variable to rename, and stops. Server code reading
|
|
5520
|
+
* process.env.PUBLIC_* is untouched: that is an ordinary server variable.
|
|
5521
|
+
*/
|
|
5522
|
+
export function publicPrefixReads(code) {
|
|
5523
|
+
return [...new Set([...code.matchAll(/\bimport\.meta\.env\.(PUBLIC_[A-Za-z0-9_]+)/g)].map((m) => m[1]))];
|
|
5524
|
+
}
|
|
5525
|
+
function publicPrefixRefused() {
|
|
5526
|
+
return {
|
|
5527
|
+
name: "rsc-kit:public-prefix",
|
|
5528
|
+
enforce: "pre",
|
|
5529
|
+
transform(code, id) {
|
|
5530
|
+
if (id.includes("/node_modules/") || !code.includes("import.meta.env.PUBLIC_"))
|
|
5531
|
+
return;
|
|
5532
|
+
const names = publicPrefixReads(code);
|
|
5533
|
+
if (names.length === 0)
|
|
5534
|
+
return;
|
|
5535
|
+
this.error(`${id.split("?")[0]} reads ${names.map((n) => `import.meta.env.${n}`).join(", ")}. ` +
|
|
5536
|
+
"Only VITE_ variables reach the browser now; rename " +
|
|
5537
|
+
names.map((n) => `${n} to VITE_${n.slice("PUBLIC_".length)}`).join(", ") +
|
|
5538
|
+
" in the code and in your .env files.");
|
|
5539
|
+
},
|
|
5540
|
+
};
|
|
5541
|
+
}
|
|
5561
5542
|
/**
|
|
5562
5543
|
* Named imports from a barrel package, unrolled on the server environments.
|
|
5563
5544
|
*
|
|
@@ -5929,6 +5910,45 @@ function nodeEnvSetIn(mode, envDir) {
|
|
|
5929
5910
|
}
|
|
5930
5911
|
return culprit;
|
|
5931
5912
|
}
|
|
5913
|
+
/**
|
|
5914
|
+
* The PUBLIC_ variables an app still sets, and where each one is set.
|
|
5915
|
+
*
|
|
5916
|
+
* PUBLIC_ was a client prefix beside VITE_ until 0.22. An app that sets one
|
|
5917
|
+
* expects it in the browser, and most read it through a validated env object
|
|
5918
|
+
* rather than import.meta.env, so no source scan sees the read: in the
|
|
5919
|
+
* browser the value was simply gone, and a required one threw from the
|
|
5920
|
+
* validator on the first page that imported it. The variables themselves are
|
|
5921
|
+
* the signal. Same files, same order and same directories as nodeEnvSetIn;
|
|
5922
|
+
* a name only in the process environment is reported as such.
|
|
5923
|
+
*/
|
|
5924
|
+
export function publicVariablesSet(mode, envDir, environment = process.env) {
|
|
5925
|
+
const files = [".env", ".env.local", `.env.${mode}`, `.env.${mode}.local`];
|
|
5926
|
+
const found = new Map();
|
|
5927
|
+
const dirs = [...new Set([projectRoot, envDir || projectRoot])].filter((dir) => !!dir);
|
|
5928
|
+
for (const dir of dirs)
|
|
5929
|
+
for (const file of files) {
|
|
5930
|
+
const path = join(dir, file);
|
|
5931
|
+
if (!existsSync(path))
|
|
5932
|
+
continue;
|
|
5933
|
+
readFileSync(path, "utf-8").split("\n").forEach((line, at) => {
|
|
5934
|
+
const name = /^\s*(?:export\s+)?(PUBLIC_[A-Za-z0-9_]+)\s*=/.exec(line)?.[1];
|
|
5935
|
+
if (name)
|
|
5936
|
+
found.set(name, `${path}:${at + 1}`);
|
|
5937
|
+
});
|
|
5938
|
+
}
|
|
5939
|
+
for (const name of Object.keys(environment)) {
|
|
5940
|
+
if (name.startsWith("PUBLIC_") && !found.has(name))
|
|
5941
|
+
found.set(name, "the environment");
|
|
5942
|
+
}
|
|
5943
|
+
return [...found].map(([name, where]) => ({ name, where }));
|
|
5944
|
+
}
|
|
5945
|
+
function refusePublicVariables(set) {
|
|
5946
|
+
return ("[rsc-kit] Only VITE_ variables reach the browser; PUBLIC_ stopped being a client prefix in 0.22. " +
|
|
5947
|
+
"These are still set, and would be undefined in the browser:\n\n" +
|
|
5948
|
+
set.map(({ name, where }) => ` ${name} (${where}) -> VITE_${name.slice("PUBLIC_".length)}`).join("\n") +
|
|
5949
|
+
"\n\nRename each where it is set and where it is read - an env schema's clientPrefix and client " +
|
|
5950
|
+
"keys included.");
|
|
5951
|
+
}
|
|
5932
5952
|
function refuseDevelopmentBuild(config) {
|
|
5933
5953
|
const culprit = nodeEnvSetIn(config.mode, config.envDir);
|
|
5934
5954
|
return ("[rsc-kit] vite build is running as a development build, and the output cannot work: " +
|
|
@@ -6273,11 +6293,11 @@ export function rscKit(options = {}) {
|
|
|
6273
6293
|
// saying so at the end of each docker build is not a finding.
|
|
6274
6294
|
rollupOptions: { checks: { pluginTimings: false } },
|
|
6275
6295
|
},
|
|
6276
|
-
// What reaches the browser
|
|
6277
|
-
//
|
|
6278
|
-
//
|
|
6279
|
-
//
|
|
6280
|
-
envPrefix: ["VITE_"
|
|
6296
|
+
// What reaches the browser: Vite's own VITE_ prefix, and only that.
|
|
6297
|
+
// PUBLIC_ was accepted beside it until 0.22; a read of one is now
|
|
6298
|
+
// refused by name (publicPrefixRefused) rather than left undefined.
|
|
6299
|
+
// A config that set its own prefixes keeps them; Vite merges.
|
|
6300
|
+
envPrefix: ["VITE_"],
|
|
6281
6301
|
environments: {
|
|
6282
6302
|
// Server bundles — stay under the (non-public) out dir. `bun` and
|
|
6283
6303
|
// `bun:*` are the runtime's own modules, like `node:*`: nothing to
|
|
@@ -6375,159 +6395,167 @@ export function rscKit(options = {}) {
|
|
|
6375
6395
|
* and restarting on every keystroke would throw away the module graph for
|
|
6376
6396
|
* nothing.
|
|
6377
6397
|
*/
|
|
6378
|
-
configureServer
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
//
|
|
6388
|
-
//
|
|
6389
|
-
//
|
|
6390
|
-
//
|
|
6391
|
-
//
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
res.setHeader("Content-Type", "application/manifest+json");
|
|
6410
|
-
res.end(webManifest(webManifestOptions));
|
|
6411
|
-
return;
|
|
6412
|
-
}
|
|
6413
|
-
const asset = allAppAssets(foundAssets).find((a) => a.href === url);
|
|
6414
|
-
const file = asset ? join(sourceDir, "app", asset.file) : null;
|
|
6415
|
-
if (!asset || !file || !existsSync(file))
|
|
6416
|
-
return next();
|
|
6417
|
-
res.setHeader("Content-Type", typeOf(asset.file));
|
|
6418
|
-
createReadStream(file).pipe(res);
|
|
6419
|
-
});
|
|
6420
|
-
// rpc() has to reach the backend while the dev server is serving.
|
|
6421
|
-
//
|
|
6422
|
-
// A built deployment installs this itself: the server running
|
|
6423
|
-
// createRscHandler passes `hostCalls`. Nothing does that here, and with
|
|
6424
|
-
// no host installed every rpc() is refused — so a page whose data comes
|
|
6425
|
-
// from the backend renders its shell and then blanks, which reads as a
|
|
6426
|
-
// hydration bug rather than a missing wire.
|
|
6427
|
-
server.httpServer?.once("listening", async () => {
|
|
6428
|
-
const env = server.environments?.rsc;
|
|
6429
|
-
if (!env?.runner)
|
|
6430
|
-
return;
|
|
6431
|
-
// The app's own .env, unprefixed. A Laravel app already has both of
|
|
6432
|
-
// these, which is what makes this need no configuring: APP_URL is the
|
|
6433
|
-
// backend and RSC_HOST_CALL_SECRET is the secret it checks.
|
|
6434
|
-
const fromEnv = loadEnv(server.config.mode, projectRoot, "");
|
|
6435
|
-
const secret = hostCallOptions?.secret ?? fromEnv.RSC_HOST_CALL_SECRET;
|
|
6436
|
-
const origin = hostCallOptions?.endpoint ?? fromEnv.RSC_BACKEND ?? fromEnv.APP_URL;
|
|
6437
|
-
if (!secret || !origin)
|
|
6438
|
-
return;
|
|
6439
|
-
const path = hostCallOptions?.path ??
|
|
6440
|
-
fromEnv.RSC_HOST_CALL_PATH ??
|
|
6441
|
-
"/__rsc/host-call";
|
|
6442
|
-
const endpoint = origin.replace(/\/$/, "") + path;
|
|
6443
|
-
try {
|
|
6444
|
-
const entry = await env.runner.import(join(genDir, "entry.rsc.tsx"));
|
|
6445
|
-
const install = entry.installHostFn;
|
|
6446
|
-
install?.(httpHostCalls({ endpoint, secret }));
|
|
6447
|
-
}
|
|
6448
|
-
catch (error) {
|
|
6449
|
-
// Reported rather than thrown: the dev server is still useful for
|
|
6450
|
-
// every page that needs no data, and a failure here would otherwise
|
|
6451
|
-
// look like the server refusing to start.
|
|
6452
|
-
server.config.logger.warn(`[rsc-kit] could not wire host calls to ${endpoint}: ` +
|
|
6453
|
-
(error instanceof Error ? error.message : String(error)));
|
|
6454
|
-
}
|
|
6455
|
-
});
|
|
6456
|
-
// Written once the server is listening, because only then is the port
|
|
6457
|
-
// known. Removed on shutdown so a backend can tell a dev server that is
|
|
6458
|
-
// gone from one that is merely slow to answer.
|
|
6459
|
-
if (hotFile) {
|
|
6460
|
-
const remove = () => {
|
|
6461
|
-
try {
|
|
6462
|
-
if (existsSync(hotFile))
|
|
6463
|
-
rmSync(hotFile);
|
|
6398
|
+
// Ahead of every other plugin's configureServer, so this middleware is
|
|
6399
|
+
// first in the stack. Nitro's dev handler is registered by its own plugin,
|
|
6400
|
+
// which an app lists before this one, and it answers every request that
|
|
6401
|
+
// does not look like an asset itself - /manifest.webmanifest and
|
|
6402
|
+
// /robots.txt went to the app's router and came back 404, or 500 from a
|
|
6403
|
+
// page that could not render for that url, and this never saw them.
|
|
6404
|
+
// Swapping the plugins in the app's config is not a fix: Nitro then loads
|
|
6405
|
+
// .env too late for the app's first request.
|
|
6406
|
+
configureServer: { order: "pre", handler(server) {
|
|
6407
|
+
// The icons and share images live in app/, which nothing serves; the
|
|
6408
|
+
// build copies them beside the client output. There is no output while
|
|
6409
|
+
// developing, so the same hrefs the head tags carry are answered from
|
|
6410
|
+
// app/ here - and the web manifest with them - or the tab has no icon
|
|
6411
|
+
// and the console a 404 for a file that is right there.
|
|
6412
|
+
server.middlewares.use((req, res, next) => {
|
|
6413
|
+
const url = (req.url ?? "").split("?")[0];
|
|
6414
|
+
// A worker registered by a production run on this origin - `bun run
|
|
6415
|
+
// start` on the port the dev server uses next - outlives that run and
|
|
6416
|
+
// answers the dev server's documents from its cache: a stored page is
|
|
6417
|
+
// cache-first, so a refresh brings back yesterday's document with
|
|
6418
|
+
// yesterday's module hashes, and a 504 for each. The browser fetches
|
|
6419
|
+
// /sw.js again on every navigation to look for an update; in
|
|
6420
|
+
// development that fetch gets a worker whose only job is to remove
|
|
6421
|
+
// itself, its caches, and reload the pages it controlled.
|
|
6422
|
+
// robots.txt, a hand-written sitemap.xml, humans.txt: beside the root
|
|
6423
|
+
// layout, served at the root. The build copies them beside the client
|
|
6424
|
+
// output; here they are read from app/.
|
|
6425
|
+
if (url.startsWith("/") && rootFiles.includes(url.slice(1))) {
|
|
6426
|
+
res.setHeader("Content-Type", rootFileType(url.slice(1)));
|
|
6427
|
+
res.end(readFileSync(join(sourceDir, "app", url.slice(1))));
|
|
6428
|
+
return;
|
|
6464
6429
|
}
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
const address = server.httpServer?.address();
|
|
6475
|
-
const url = resolved ??
|
|
6476
|
-
(typeof address === "object" && address
|
|
6477
|
-
? `http://${address.family === "IPv6" ? `[${address.address}]` : address.address}:${address.port}`
|
|
6478
|
-
: null);
|
|
6479
|
-
if (!url)
|
|
6430
|
+
if (url === "/sw.js") {
|
|
6431
|
+
res.setHeader("Content-Type", "text/javascript");
|
|
6432
|
+
res.setHeader("Cache-Control", "no-store");
|
|
6433
|
+
res.end(DEV_WORKER);
|
|
6434
|
+
return;
|
|
6435
|
+
}
|
|
6436
|
+
if (webManifestOptions && url === MANIFEST_PATH) {
|
|
6437
|
+
res.setHeader("Content-Type", "application/manifest+json");
|
|
6438
|
+
res.end(webManifest(webManifestOptions));
|
|
6480
6439
|
return;
|
|
6481
|
-
|
|
6482
|
-
|
|
6440
|
+
}
|
|
6441
|
+
const asset = allAppAssets(foundAssets).find((a) => a.href === url);
|
|
6442
|
+
const file = asset ? join(sourceDir, "app", asset.file) : null;
|
|
6443
|
+
if (!asset || !file || !existsSync(file))
|
|
6444
|
+
return next();
|
|
6445
|
+
res.setHeader("Content-Type", typeOf(asset.file));
|
|
6446
|
+
createReadStream(file).pipe(res);
|
|
6483
6447
|
});
|
|
6484
|
-
|
|
6485
|
-
|
|
6448
|
+
// rpc() has to reach the backend while the dev server is serving.
|
|
6449
|
+
//
|
|
6450
|
+
// A built deployment installs this itself: the server running
|
|
6451
|
+
// createRscHandler passes `hostCalls`. Nothing does that here, and with
|
|
6452
|
+
// no host installed every rpc() is refused — so a page whose data comes
|
|
6453
|
+
// from the backend renders its shell and then blanks, which reads as a
|
|
6454
|
+
// hydration bug rather than a missing wire.
|
|
6455
|
+
server.httpServer?.once("listening", async () => {
|
|
6456
|
+
const env = server.environments?.rsc;
|
|
6457
|
+
if (!env?.runner)
|
|
6458
|
+
return;
|
|
6459
|
+
// The app's own .env, unprefixed. A Laravel app already has both of
|
|
6460
|
+
// these, which is what makes this need no configuring: APP_URL is the
|
|
6461
|
+
// backend and RSC_HOST_CALL_SECRET is the secret it checks.
|
|
6462
|
+
const fromEnv = loadEnv(server.config.mode, projectRoot, "");
|
|
6463
|
+
const secret = hostCallOptions?.secret ?? fromEnv.RSC_HOST_CALL_SECRET;
|
|
6464
|
+
const origin = hostCallOptions?.endpoint ?? fromEnv.RSC_BACKEND ?? fromEnv.APP_URL;
|
|
6465
|
+
if (!secret || !origin)
|
|
6466
|
+
return;
|
|
6467
|
+
const path = hostCallOptions?.path ??
|
|
6468
|
+
fromEnv.RSC_HOST_CALL_PATH ??
|
|
6469
|
+
"/__rsc/host-call";
|
|
6470
|
+
const endpoint = origin.replace(/\/$/, "") + path;
|
|
6471
|
+
try {
|
|
6472
|
+
const entry = await env.runner.import(join(genDir, "entry.rsc.tsx"));
|
|
6473
|
+
const install = entry.installHostFn;
|
|
6474
|
+
install?.(httpHostCalls({ endpoint, secret }));
|
|
6475
|
+
}
|
|
6476
|
+
catch (error) {
|
|
6477
|
+
// Reported rather than thrown: the dev server is still useful for
|
|
6478
|
+
// every page that needs no data, and a failure here would otherwise
|
|
6479
|
+
// look like the server refusing to start.
|
|
6480
|
+
server.config.logger.warn(`[rsc-kit] could not wire host calls to ${endpoint}: ` +
|
|
6481
|
+
(error instanceof Error ? error.message : String(error)));
|
|
6482
|
+
}
|
|
6483
|
+
});
|
|
6484
|
+
// Written once the server is listening, because only then is the port
|
|
6485
|
+
// known. Removed on shutdown so a backend can tell a dev server that is
|
|
6486
|
+
// gone from one that is merely slow to answer.
|
|
6487
|
+
if (hotFile) {
|
|
6488
|
+
const remove = () => {
|
|
6489
|
+
try {
|
|
6490
|
+
if (existsSync(hotFile))
|
|
6491
|
+
rmSync(hotFile);
|
|
6492
|
+
}
|
|
6493
|
+
catch { }
|
|
6494
|
+
};
|
|
6495
|
+
server.httpServer?.once("listening", () => {
|
|
6496
|
+
// The url Vite resolved, not one built from the port. A dev server
|
|
6497
|
+
// whose port is already taken on IPv4 binds IPv6 only and keeps the
|
|
6498
|
+
// number — so http://127.0.0.1:<port> is a reachable-looking address
|
|
6499
|
+
// that nothing answers, and the backend reports the renderer as down
|
|
6500
|
+
// while it is plainly running.
|
|
6501
|
+
const resolved = server.resolvedUrls?.local?.[0];
|
|
6502
|
+
const address = server.httpServer?.address();
|
|
6503
|
+
const url = resolved ??
|
|
6504
|
+
(typeof address === "object" && address
|
|
6505
|
+
? `http://${address.family === "IPv6" ? `[${address.address}]` : address.address}:${address.port}`
|
|
6506
|
+
: null);
|
|
6507
|
+
if (!url)
|
|
6508
|
+
return;
|
|
6509
|
+
mkdirSync(dirname(hotFile), { recursive: true });
|
|
6510
|
+
writeFileSync(hotFile, url.replace(/\/$/, ""));
|
|
6511
|
+
});
|
|
6512
|
+
for (const signal of ["SIGINT", "SIGTERM", "exit"]) {
|
|
6513
|
+
process.once(signal, remove);
|
|
6514
|
+
}
|
|
6515
|
+
server.httpServer?.once("close", remove);
|
|
6486
6516
|
}
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
server.watcher.on("unlink", hostActionsChanged);
|
|
6530
|
-
},
|
|
6517
|
+
const shapes = new Set(ROUTE_FILES.map((name) => name));
|
|
6518
|
+
const affectsRouting = (file) => {
|
|
6519
|
+
if (!file.startsWith(sourceDir))
|
|
6520
|
+
return false;
|
|
6521
|
+
const base = file.split("/").pop() ?? "";
|
|
6522
|
+
const stem = base.replace(/\.(tsx|jsx|ts|js)$/, "");
|
|
6523
|
+
// The host's route-config file, whatever it named it. Hardcoding one
|
|
6524
|
+
// here would put a backend's convention back into a plugin that is
|
|
6525
|
+
// published without any — generic-host.test.ts fails if it reappears.
|
|
6526
|
+
return ((base !== stem && shapes.has(stem)) ||
|
|
6527
|
+
SECTION_FILE.test(base) ||
|
|
6528
|
+
(routeConfig !== null && base === routeConfig.file));
|
|
6529
|
+
};
|
|
6530
|
+
const restart = (file) => {
|
|
6531
|
+
if (!affectsRouting(file))
|
|
6532
|
+
return;
|
|
6533
|
+
server.config.logger.info(`[rsc-kit] route tree changed (${file.slice(sourceDir.length + 1)}) — restarting`);
|
|
6534
|
+
void server.restart();
|
|
6535
|
+
};
|
|
6536
|
+
// Watched explicitly: the Vite root is the *out* directory, so the app's
|
|
6537
|
+
// source tree is outside it and nothing would report a file appearing.
|
|
6538
|
+
server.watcher.add(sourceDir);
|
|
6539
|
+
server.watcher.on("add", restart);
|
|
6540
|
+
server.watcher.on("unlink", restart);
|
|
6541
|
+
// The host actions are read once, in config(), from the file the
|
|
6542
|
+
// backend writes; the "use server" stubs are generated from it. A
|
|
6543
|
+
// backend that writes a new action - `make:rsc-action` under a running
|
|
6544
|
+
// dev server - rewrites the file, and until the server started again
|
|
6545
|
+
// the stub was not there to import. Any event: the file appears on a
|
|
6546
|
+
// first action, changes on the next, and goes when the last is removed.
|
|
6547
|
+
const hostActionsPath = join(projectRoot, HOST_ACTIONS_FILE);
|
|
6548
|
+
const hostActionsChanged = (file) => {
|
|
6549
|
+
if (file !== hostActionsPath)
|
|
6550
|
+
return;
|
|
6551
|
+
server.config.logger.info(`[rsc-kit] ${HOST_ACTIONS_FILE} changed — restarting`);
|
|
6552
|
+
void server.restart();
|
|
6553
|
+
};
|
|
6554
|
+
server.watcher.add(hostActionsPath);
|
|
6555
|
+
server.watcher.on("add", hostActionsChanged);
|
|
6556
|
+
server.watcher.on("change", hostActionsChanged);
|
|
6557
|
+
server.watcher.on("unlink", hostActionsChanged);
|
|
6558
|
+
} },
|
|
6531
6559
|
/**
|
|
6532
6560
|
* Freeze what can be frozen, once every bundle exists.
|
|
6533
6561
|
*
|
|
@@ -6626,6 +6654,12 @@ export function rscKit(options = {}) {
|
|
|
6626
6654
|
if (config.command === "build" && !config.isProduction) {
|
|
6627
6655
|
throw new Error(refuseDevelopmentBuild(config));
|
|
6628
6656
|
}
|
|
6657
|
+
// Named before anything is bundled, in development and in a build: a
|
|
6658
|
+
// PUBLIC_ variable is one the app expects in the browser, and it would
|
|
6659
|
+
// arrive there as undefined. See publicVariablesSet.
|
|
6660
|
+
const publicSet = publicVariablesSet(config.mode, config.envDir);
|
|
6661
|
+
if (publicSet.length > 0)
|
|
6662
|
+
throw new Error(refusePublicVariables(publicSet));
|
|
6629
6663
|
isWatch = config.build?.watch != null;
|
|
6630
6664
|
resolvedConfig = config;
|
|
6631
6665
|
const nitroMain = config.plugins.find((p) => p.name === "nitro:main");
|
|
@@ -6675,6 +6709,7 @@ export function rscKit(options = {}) {
|
|
|
6675
6709
|
clientChunks: (meta) => meta.normalizedId,
|
|
6676
6710
|
...actionEncryptionKey(),
|
|
6677
6711
|
}),
|
|
6712
|
+
publicPrefixRefused(),
|
|
6678
6713
|
barrelImportsPlugin(),
|
|
6679
6714
|
useSsrModules(),
|
|
6680
6715
|
serverRendererInRsc(),
|