@jk2908/solas 0.5.3 → 0.6.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/CHANGELOG.md +29 -0
- package/README.md +81 -85
- package/dist/config.d.ts +25 -0
- package/dist/config.js +152 -0
- package/dist/events.d.ts +4 -0
- package/dist/events.js +5 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.js +59 -37
- package/dist/internal/boundary-error.d.ts +4 -0
- package/dist/internal/boundary-error.js +8 -0
- package/dist/internal/browser-router/history.d.ts +46 -0
- package/dist/internal/browser-router/history.js +131 -0
- package/dist/internal/browser-router/link.d.ts +1 -1
- package/dist/internal/browser-router/link.js +1 -1
- package/dist/internal/browser-router/router.d.ts +6 -14
- package/dist/internal/browser-router/router.js +39 -13
- package/dist/internal/browser-router/shared.d.ts +183 -164
- package/dist/internal/browser-router/shared.js +49 -56
- package/dist/internal/browser-router/use-router.d.ts +1 -10
- package/dist/internal/browser-router/use-search-params.js +3 -3
- package/dist/internal/build.d.ts +108 -110
- package/dist/internal/build.js +569 -581
- package/dist/internal/codegen/config.js +3 -3
- package/dist/internal/codegen/environments.js +5 -6
- package/dist/internal/codegen/format.d.ts +6 -0
- package/dist/internal/codegen/format.js +24 -0
- package/dist/internal/codegen/manifest.js +2 -2
- package/dist/internal/codegen/maps.d.ts +1 -1
- package/dist/internal/codegen/maps.js +2 -2
- package/dist/internal/codegen/types.js +4 -4
- package/dist/internal/codegen/utils.js +2 -2
- package/dist/internal/env/request-context.js +1 -1
- package/dist/internal/env/rsc.d.ts +6 -4
- package/dist/internal/env/rsc.js +27 -25
- package/dist/internal/env/ssr.js +4 -4
- package/dist/internal/http-router/router.js +8 -8
- package/dist/internal/metadata.d.ts +1 -1
- package/dist/internal/metadata.js +1 -1
- package/dist/internal/navigation/http-exception-boundary.d.ts +1 -1
- package/dist/internal/navigation/redirect-boundary.d.ts +1 -1
- package/dist/internal/navigation/redirect.js +6 -6
- package/dist/internal/postbuild.js +18 -17
- package/dist/internal/prerender.d.ts +130 -138
- package/dist/internal/prerender.js +326 -354
- package/dist/internal/public-files.js +2 -4
- package/dist/internal/render/head.js +2 -2
- package/dist/internal/resolver.js +1 -1
- package/dist/internal/server/actions.js +2 -2
- package/dist/internal/ui/error-boundary.d.ts +1 -1
- package/dist/manifest.d.ts +7 -0
- package/dist/manifest.js +85 -0
- package/dist/prerender.d.ts +1 -1
- package/dist/prerender.js +1 -1
- package/dist/solas.d.ts +2 -46
- package/dist/solas.js +6 -266
- package/dist/types.d.ts +14 -21
- package/dist/types.js +1 -1
- package/dist/utils/base-path.d.ts +12 -14
- package/dist/utils/base-path.js +75 -82
- package/dist/utils/compress.d.ts +9 -11
- package/dist/utils/compress.js +59 -64
- package/dist/utils/context.d.ts +4 -6
- package/dist/utils/context.js +17 -22
- package/dist/utils/logger.js +2 -2
- package/dist/utils/time.d.ts +2 -4
- package/dist/utils/time.js +23 -29
- package/package.json +18 -8
- package/dist/runtimes/bun.d.ts +0 -13
- package/dist/runtimes/bun.js +0 -39
package/dist/index.js
CHANGED
|
@@ -5,10 +5,12 @@ import { createServer, loadConfigFromFile, } from 'vite';
|
|
|
5
5
|
import rsc from '@vitejs/plugin-rsc';
|
|
6
6
|
import { ExportReader } from './utils/export-reader.js';
|
|
7
7
|
import { Logger } from './utils/logger.js';
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
8
|
+
import { debounce } from './utils/time.js';
|
|
9
|
+
import * as Config from './config.js';
|
|
10
|
+
import * as Build from './internal/build.js';
|
|
10
11
|
import { writeConfig } from './internal/codegen/config.js';
|
|
11
12
|
import { writeBrowserEntry, writeRSCEntry, writeSSREntry, } from './internal/codegen/environments.js';
|
|
13
|
+
import { format as formatSource } from './internal/codegen/format.js';
|
|
12
14
|
import { writeManifest } from './internal/codegen/manifest.js';
|
|
13
15
|
import { writeMaps } from './internal/codegen/maps.js';
|
|
14
16
|
import { writeTypes } from './internal/codegen/types.js';
|
|
@@ -16,7 +18,6 @@ import { postbuild } from './internal/postbuild.js';
|
|
|
16
18
|
import { collect as collectPublicFiles } from './internal/public-files.js';
|
|
17
19
|
import { createRuntime } from './internal/runtimes/create.js';
|
|
18
20
|
import { Runtime } from './internal/runtimes/runtime.js';
|
|
19
|
-
import { Solas } from './solas.js';
|
|
20
21
|
const DEFAULT_CONFIG = {
|
|
21
22
|
runtime: 'auto',
|
|
22
23
|
precompress: false,
|
|
@@ -25,15 +26,14 @@ const DEFAULT_CONFIG = {
|
|
|
25
26
|
trailingSlash: 'never',
|
|
26
27
|
};
|
|
27
28
|
function solas(c) {
|
|
28
|
-
const validatedConfig =
|
|
29
|
-
...c,
|
|
30
|
-
url: c?.url ?? process.env.VITE_APP_URL?.toString(),
|
|
31
|
-
});
|
|
29
|
+
const validatedConfig = Config.validate(c);
|
|
32
30
|
const config = {
|
|
33
31
|
...DEFAULT_CONFIG,
|
|
34
32
|
...validatedConfig,
|
|
35
33
|
runtime: validatedConfig.runtime ?? DEFAULT_CONFIG.runtime,
|
|
36
34
|
};
|
|
35
|
+
const envUrl = process.env.VITE_APP_URL?.toString();
|
|
36
|
+
let resolvedUrl = c?.url ?? (envUrl && envUrl.length > 0 ? envUrl : undefined);
|
|
37
37
|
Runtime.runtime = createRuntime(config.runtime);
|
|
38
38
|
if (config.logger?.level)
|
|
39
39
|
Logger.defaultLevel = config.logger.level;
|
|
@@ -93,8 +93,8 @@ function solas(c) {
|
|
|
93
93
|
}
|
|
94
94
|
async function build() {
|
|
95
95
|
const cwd = process.cwd();
|
|
96
|
-
const routesDir = path.join(cwd,
|
|
97
|
-
const generatedDir = path.join(cwd,
|
|
96
|
+
const routesDir = path.join(cwd, Config.APP_DIR);
|
|
97
|
+
const generatedDir = path.join(cwd, Config.GENERATED_DIR);
|
|
98
98
|
await Promise.all([
|
|
99
99
|
fs.mkdir(routesDir, { recursive: true }),
|
|
100
100
|
fs.mkdir(generatedDir, { recursive: true }),
|
|
@@ -107,12 +107,27 @@ function solas(c) {
|
|
|
107
107
|
['config.ts', writeConfig(config)],
|
|
108
108
|
['manifest.ts', writeManifest(manifest)],
|
|
109
109
|
['maps.ts', writeMaps(imports, modules)],
|
|
110
|
-
[`${
|
|
111
|
-
[
|
|
112
|
-
[
|
|
113
|
-
[
|
|
110
|
+
[`${Config.SLUG}.d.ts`, writeTypes(manifest)],
|
|
111
|
+
[Config.ENTRY_RSC, writeRSCEntry(config)],
|
|
112
|
+
[Config.ENTRY_SSR, writeSSREntry()],
|
|
113
|
+
[Config.ENTRY_BROWSER, writeBrowserEntry()],
|
|
114
114
|
];
|
|
115
|
-
|
|
115
|
+
// remove generated files no longer emitted by the current codegen, so
|
|
116
|
+
// old artifacts don't linger and break module resolution. keep
|
|
117
|
+
// closeBundle's build.json, which lives in the same directory
|
|
118
|
+
const currentFiles = new Set(files.map(([file]) => file));
|
|
119
|
+
const existing = await fs
|
|
120
|
+
.readdir(generatedDir, { withFileTypes: true })
|
|
121
|
+
.catch(() => []);
|
|
122
|
+
await Promise.all(existing
|
|
123
|
+
.filter(entry => entry.isFile() &&
|
|
124
|
+
entry.name !== 'build.json' &&
|
|
125
|
+
!currentFiles.has(entry.name))
|
|
126
|
+
.map(entry => fs.rm(path.join(generatedDir, entry.name), { force: true })));
|
|
127
|
+
const writes = await Promise.all(files.map(async ([file, content]) => {
|
|
128
|
+
const formatted = await formatSource(file, content);
|
|
129
|
+
return maybeWrite(path.join(generatedDir, file), formatted);
|
|
130
|
+
}));
|
|
116
131
|
const changed = writes.filter(n => n !== null);
|
|
117
132
|
// early return if nothing has changed
|
|
118
133
|
if (!changed.length)
|
|
@@ -125,7 +140,7 @@ function solas(c) {
|
|
|
125
140
|
// normalise all watcher paths to forward slashes so path checks behave the
|
|
126
141
|
// same on Windows and POSIX
|
|
127
142
|
const WATCH_CWD = process.cwd().replace(/\\/g, '/');
|
|
128
|
-
const WATCH_APP_ROOT = `${WATCH_CWD}/${
|
|
143
|
+
const WATCH_APP_ROOT = `${WATCH_CWD}/${Config.APP_DIR}/`;
|
|
129
144
|
// convert watcher paths to a consistent slash format before comparing them
|
|
130
145
|
const normaliseWatchPath = (p) => p.replace(/\\/g, '/');
|
|
131
146
|
// resolve relative watcher paths against the project root so prefix checks are reliable
|
|
@@ -136,7 +151,7 @@ function solas(c) {
|
|
|
136
151
|
// edits needing special treatment because verb exports can change in-place
|
|
137
152
|
const routeFile = /\/\+(layout|page|401|403|404|500|loading|middleware|endpoint)\.(t|j)sx?$/;
|
|
138
153
|
const endpointFile = /\/\+endpoint\.(t|j)sx?$/;
|
|
139
|
-
const rebuild =
|
|
154
|
+
const rebuild = debounce((event, p) => {
|
|
140
155
|
const queue = () => {
|
|
141
156
|
void (async () => {
|
|
142
157
|
// collapse bursts of file events into one active rebuild plus a single
|
|
@@ -197,9 +212,7 @@ function solas(c) {
|
|
|
197
212
|
return [];
|
|
198
213
|
if (Array.isArray(plugin))
|
|
199
214
|
return plugin.flatMap(flatten);
|
|
200
|
-
if (typeof plugin === 'object' &&
|
|
201
|
-
'name' in plugin &&
|
|
202
|
-
plugin.name === Solas.Config.NAME) {
|
|
215
|
+
if (typeof plugin === 'object' && 'name' in plugin && plugin.name === Config.NAME) {
|
|
203
216
|
return [];
|
|
204
217
|
}
|
|
205
218
|
return [plugin];
|
|
@@ -219,26 +232,35 @@ function solas(c) {
|
|
|
219
232
|
return utilityServer;
|
|
220
233
|
}
|
|
221
234
|
const plugin = {
|
|
222
|
-
name:
|
|
235
|
+
name: Config.NAME,
|
|
223
236
|
async config(viteConfig) {
|
|
224
237
|
const pkg = JSON.parse(fsSync.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
|
|
225
238
|
if (typeof pkg.name !== 'string' || pkg.name.length === 0) {
|
|
226
|
-
throw new Error(`Missing ${
|
|
239
|
+
throw new Error(`Missing ${Config.NAME} package name`);
|
|
227
240
|
}
|
|
228
241
|
if (typeof pkg.version !== 'string' || pkg.version.length === 0) {
|
|
229
|
-
throw new Error(`Missing ${
|
|
242
|
+
throw new Error(`Missing ${Config.NAME} package version`);
|
|
230
243
|
}
|
|
231
244
|
viteConfig.build ??= {};
|
|
232
|
-
viteConfig.build.outDir =
|
|
245
|
+
viteConfig.build.outDir = Config.OUT_DIR;
|
|
233
246
|
// keep framework files under one reserved url prefix
|
|
234
|
-
viteConfig.build.assetsDir =
|
|
247
|
+
viteConfig.build.assetsDir = Config.ASSETS_DIR;
|
|
235
248
|
viteConfig.build.emptyOutDir = true;
|
|
236
249
|
// let users move the source public folder if they want
|
|
237
|
-
viteConfig.publicDir ??=
|
|
250
|
+
viteConfig.publicDir ??= Config.PUBLIC_DIR;
|
|
238
251
|
viteConfig.server ??= {};
|
|
239
|
-
viteConfig.server.port
|
|
252
|
+
viteConfig.server.port ??= 8787;
|
|
253
|
+
// derive the public url from vite's dev server settings when no
|
|
254
|
+
// origin was set explicitly via the url option or VITE_APP_URL
|
|
255
|
+
if (resolvedUrl === undefined) {
|
|
256
|
+
const { host, port } = viteConfig.server;
|
|
257
|
+
const hostname = typeof host === 'string' && host.length > 0 && host !== '0.0.0.0'
|
|
258
|
+
? host
|
|
259
|
+
: 'localhost';
|
|
260
|
+
resolvedUrl = `http://${hostname}:${port}`;
|
|
261
|
+
}
|
|
240
262
|
viteConfig.define ??= {};
|
|
241
|
-
viteConfig.define['import.meta.env.VITE_APP_URL'] = JSON.stringify(
|
|
263
|
+
viteConfig.define['import.meta.env.VITE_APP_URL'] = JSON.stringify(resolvedUrl);
|
|
242
264
|
viteConfig.define['import.meta.env.SOLAS_VERSION'] = JSON.stringify(pkg.version);
|
|
243
265
|
viteConfig.optimizeDeps ??= {};
|
|
244
266
|
viteConfig.optimizeDeps.exclude = [
|
|
@@ -255,12 +277,12 @@ function solas(c) {
|
|
|
255
277
|
...viteConfig.resolve.alias,
|
|
256
278
|
{
|
|
257
279
|
find: '.solas',
|
|
258
|
-
replacement: path.resolve(process.cwd(),
|
|
280
|
+
replacement: path.resolve(process.cwd(), Config.GENERATED_DIR),
|
|
259
281
|
},
|
|
260
282
|
]
|
|
261
283
|
: {
|
|
262
284
|
...viteConfig.resolve.alias,
|
|
263
|
-
'.solas': path.resolve(process.cwd(),
|
|
285
|
+
'.solas': path.resolve(process.cwd(), Config.GENERATED_DIR),
|
|
264
286
|
};
|
|
265
287
|
},
|
|
266
288
|
configResolved(resolvedConfig) {
|
|
@@ -268,7 +290,7 @@ function solas(c) {
|
|
|
268
290
|
buildContext.command = resolvedConfig.command;
|
|
269
291
|
},
|
|
270
292
|
configureServer(server) {
|
|
271
|
-
logger.info('[configureServer]', `Watching for changes in ./${
|
|
293
|
+
logger.info('[configureServer]', `Watching for changes in ./${Config.APP_DIR}...`);
|
|
272
294
|
server.watcher
|
|
273
295
|
.on('add', (p) => rebuild('add', p))
|
|
274
296
|
.on('change', (p) => rebuild('change', p))
|
|
@@ -294,7 +316,7 @@ function solas(c) {
|
|
|
294
316
|
}
|
|
295
317
|
// resolve sitemap routes
|
|
296
318
|
let sitemapRoutes = [];
|
|
297
|
-
if (config.sitemap &&
|
|
319
|
+
if (config.sitemap && resolvedUrl) {
|
|
298
320
|
const auto = [
|
|
299
321
|
...new Set([...buildContext.knownRoutes, ...buildContext.prerenderRoutes]),
|
|
300
322
|
];
|
|
@@ -306,7 +328,7 @@ function solas(c) {
|
|
|
306
328
|
}
|
|
307
329
|
}
|
|
308
330
|
// write build manifest
|
|
309
|
-
const generatedDir = path.join(process.cwd(),
|
|
331
|
+
const generatedDir = path.join(process.cwd(), Config.GENERATED_DIR);
|
|
310
332
|
await Runtime.write(path.join(generatedDir, 'build.json'), JSON.stringify({
|
|
311
333
|
base: resolvedViteConfig?.base ?? '/',
|
|
312
334
|
publicFiles: await collectPublicFiles(resolvedViteConfig?.publicDir),
|
|
@@ -314,7 +336,7 @@ function solas(c) {
|
|
|
314
336
|
sitemapRoutes,
|
|
315
337
|
precompress: config.precompress,
|
|
316
338
|
trailingSlash: config.trailingSlash,
|
|
317
|
-
url:
|
|
339
|
+
url: resolvedUrl,
|
|
318
340
|
}));
|
|
319
341
|
},
|
|
320
342
|
buildApp: {
|
|
@@ -328,12 +350,12 @@ function solas(c) {
|
|
|
328
350
|
plugin,
|
|
329
351
|
rsc({
|
|
330
352
|
entries: {
|
|
331
|
-
rsc: `./${
|
|
332
|
-
ssr: `./${
|
|
333
|
-
client: `./${
|
|
353
|
+
rsc: `./${Config.GENERATED_DIR}/${Config.ENTRY_RSC}`,
|
|
354
|
+
ssr: `./${Config.GENERATED_DIR}/${Config.ENTRY_SSR}`,
|
|
355
|
+
client: `./${Config.GENERATED_DIR}/${Config.ENTRY_BROWSER}`,
|
|
334
356
|
},
|
|
335
357
|
}),
|
|
336
358
|
];
|
|
337
359
|
}
|
|
338
360
|
export default solas;
|
|
339
|
-
export
|
|
361
|
+
export * as Solas from './solas.js';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as BrowserRouter from './shared.js';
|
|
2
|
+
/**
|
|
3
|
+
* A small in-memory mirror of browser history used by BrowserRouter.
|
|
4
|
+
* It keeps local entries/index in sync with push/replace/go and
|
|
5
|
+
* popstate updates
|
|
6
|
+
*/
|
|
7
|
+
export declare class BrowserRouterHistory {
|
|
8
|
+
#private;
|
|
9
|
+
constructor(url: {
|
|
10
|
+
pathname: string;
|
|
11
|
+
search?: string;
|
|
12
|
+
hash?: string;
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* Returns a copy of tracked entries
|
|
16
|
+
*/
|
|
17
|
+
get entries(): BrowserRouter.HistoryEntry[];
|
|
18
|
+
/**
|
|
19
|
+
* Returns the tracked history index
|
|
20
|
+
*/
|
|
21
|
+
get index(): number;
|
|
22
|
+
/**
|
|
23
|
+
* Returns the tracked history length
|
|
24
|
+
*/
|
|
25
|
+
get length(): number;
|
|
26
|
+
/**
|
|
27
|
+
* Returns the current tracked entry
|
|
28
|
+
*/
|
|
29
|
+
get current(): BrowserRouter.HistoryEntry;
|
|
30
|
+
/**
|
|
31
|
+
* Pushes a new history entry and advances the index
|
|
32
|
+
*/
|
|
33
|
+
pushState(path: string): BrowserRouter.HistoryEntry;
|
|
34
|
+
/**
|
|
35
|
+
* Replaces the current history entry without changing length
|
|
36
|
+
*/
|
|
37
|
+
replaceState(path: string): BrowserRouter.HistoryEntry;
|
|
38
|
+
/**
|
|
39
|
+
* Applies relative history movement and updates local index
|
|
40
|
+
*/
|
|
41
|
+
go(delta: number): BrowserRouter.HistoryEntry;
|
|
42
|
+
/**
|
|
43
|
+
* Syncs local index and entry from a popstate event
|
|
44
|
+
*/
|
|
45
|
+
onPopState(): BrowserRouter.HistoryEntry;
|
|
46
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small in-memory mirror of browser history used by BrowserRouter.
|
|
3
|
+
* It keeps local entries/index in sync with push/replace/go and
|
|
4
|
+
* popstate updates
|
|
5
|
+
*/
|
|
6
|
+
export class BrowserRouterHistory {
|
|
7
|
+
#entries;
|
|
8
|
+
#index;
|
|
9
|
+
constructor(url) {
|
|
10
|
+
const entry = {
|
|
11
|
+
pathname: url.pathname,
|
|
12
|
+
search: url.search ?? '',
|
|
13
|
+
hash: url.hash ?? '',
|
|
14
|
+
};
|
|
15
|
+
this.#entries = [entry];
|
|
16
|
+
this.#index = 0;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Returns a copy of tracked entries
|
|
20
|
+
*/
|
|
21
|
+
get entries() {
|
|
22
|
+
return [...this.#entries];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Returns the tracked history index
|
|
26
|
+
*/
|
|
27
|
+
get index() {
|
|
28
|
+
return this.#index;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Returns the tracked history length
|
|
32
|
+
*/
|
|
33
|
+
get length() {
|
|
34
|
+
return this.#entries.length;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Returns the current tracked entry
|
|
38
|
+
*/
|
|
39
|
+
get current() {
|
|
40
|
+
return this.#entries[this.#index] ?? null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Pushes a new history entry and advances the index
|
|
44
|
+
*/
|
|
45
|
+
pushState(path) {
|
|
46
|
+
if (this.#index < this.#entries.length - 1) {
|
|
47
|
+
// drop forward entries when pushing from the middle
|
|
48
|
+
this.#entries = this.#entries.slice(0, this.#index + 1);
|
|
49
|
+
}
|
|
50
|
+
window.history.pushState(null, '', path);
|
|
51
|
+
const entry = {
|
|
52
|
+
pathname: window.location.pathname,
|
|
53
|
+
search: window.location.search,
|
|
54
|
+
hash: window.location.hash,
|
|
55
|
+
};
|
|
56
|
+
this.#entries.push(entry);
|
|
57
|
+
this.#index++;
|
|
58
|
+
return entry;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Replaces the current history entry without changing length
|
|
62
|
+
*/
|
|
63
|
+
replaceState(path) {
|
|
64
|
+
window.history.replaceState(null, '', path);
|
|
65
|
+
const entry = {
|
|
66
|
+
pathname: window.location.pathname,
|
|
67
|
+
search: window.location.search,
|
|
68
|
+
hash: window.location.hash,
|
|
69
|
+
};
|
|
70
|
+
if (this.#entries.length === 0) {
|
|
71
|
+
this.#entries = [entry];
|
|
72
|
+
this.#index = 0;
|
|
73
|
+
return entry;
|
|
74
|
+
}
|
|
75
|
+
this.#entries[this.#index] = entry;
|
|
76
|
+
return entry;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Applies relative history movement and updates local index
|
|
80
|
+
*/
|
|
81
|
+
go(delta) {
|
|
82
|
+
if (!Number.isInteger(delta) || delta === 0) {
|
|
83
|
+
return this.current;
|
|
84
|
+
}
|
|
85
|
+
const nextIndex = Math.max(0, Math.min(this.#index + delta, this.#entries.length - 1));
|
|
86
|
+
if (nextIndex !== this.#index)
|
|
87
|
+
this.#index = nextIndex;
|
|
88
|
+
window.history.go(delta);
|
|
89
|
+
return this.current;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Syncs local index and entry from a popstate event
|
|
93
|
+
*/
|
|
94
|
+
onPopState() {
|
|
95
|
+
const current = {
|
|
96
|
+
pathname: window.location.pathname,
|
|
97
|
+
search: window.location.search,
|
|
98
|
+
hash: window.location.hash,
|
|
99
|
+
};
|
|
100
|
+
const prev = this.#entries[this.#index - 1];
|
|
101
|
+
const next = this.#entries[this.#index + 1];
|
|
102
|
+
if (prev &&
|
|
103
|
+
prev.pathname === current.pathname &&
|
|
104
|
+
prev.search === current.search &&
|
|
105
|
+
prev.hash === current.hash) {
|
|
106
|
+
this.#index--;
|
|
107
|
+
this.#entries[this.#index] = current;
|
|
108
|
+
return current;
|
|
109
|
+
}
|
|
110
|
+
if (next &&
|
|
111
|
+
next.pathname === current.pathname &&
|
|
112
|
+
next.search === current.search &&
|
|
113
|
+
next.hash === current.hash) {
|
|
114
|
+
this.#index++;
|
|
115
|
+
this.#entries[this.#index] = current;
|
|
116
|
+
return current;
|
|
117
|
+
}
|
|
118
|
+
// fallback: locate an existing matching entry
|
|
119
|
+
const index = this.#entries.findIndex(entry => entry.pathname === current.pathname &&
|
|
120
|
+
entry.search === current.search &&
|
|
121
|
+
entry.hash === current.hash);
|
|
122
|
+
if (index >= 0) {
|
|
123
|
+
this.#index = index;
|
|
124
|
+
this.#entries[index] = current;
|
|
125
|
+
return current;
|
|
126
|
+
}
|
|
127
|
+
this.#entries.push(current);
|
|
128
|
+
this.#index = this.#entries.length - 1;
|
|
129
|
+
return current;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useRef } from 'react';
|
|
4
|
-
import
|
|
4
|
+
import * as BrowserRouter from './shared.js';
|
|
5
5
|
import { useRouter } from './use-router.js';
|
|
6
6
|
function guard(path, prefetcher) {
|
|
7
7
|
const connection = window.navigator.connection;
|
|
@@ -1,22 +1,14 @@
|
|
|
1
1
|
import type { RscPayload } from '../env/rsc.js';
|
|
2
|
-
import
|
|
3
|
-
export
|
|
4
|
-
export declare const BrowserRouterContext: import("react").Context<
|
|
5
|
-
go: BrowserRouter.Go;
|
|
6
|
-
prefetch: (path: string) => void;
|
|
7
|
-
refresh: BrowserRouter.Refresh;
|
|
8
|
-
isNavigating: boolean;
|
|
9
|
-
url: {
|
|
10
|
-
pathname?: string | undefined;
|
|
11
|
-
search?: string | undefined;
|
|
12
|
-
};
|
|
13
|
-
}>;
|
|
2
|
+
import * as BrowserRouter from './shared.js';
|
|
3
|
+
export * as BrowserRouter from './shared.js';
|
|
4
|
+
export declare const BrowserRouterContext: import("react").Context<BrowserRouter.Context>;
|
|
14
5
|
export declare function BrowserRouterProvider({ children, setPayload, isNavigating, url }: {
|
|
15
6
|
children: React.ReactNode;
|
|
16
7
|
setPayload?: (payload: RscPayload) => void;
|
|
17
8
|
isNavigating?: boolean;
|
|
18
|
-
url
|
|
19
|
-
pathname
|
|
9
|
+
url: {
|
|
10
|
+
pathname: string;
|
|
20
11
|
search?: string;
|
|
12
|
+
hash?: string;
|
|
21
13
|
};
|
|
22
14
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -3,16 +3,25 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { createContext, useCallback, useEffect, useMemo, useRef } from 'react';
|
|
4
4
|
import { createFromFetch } from '@vitejs/plugin-rsc/browser';
|
|
5
5
|
import { Logger } from '../../utils/logger.js';
|
|
6
|
-
import
|
|
6
|
+
import * as Events from '../../events.js';
|
|
7
|
+
import { BrowserRouterHistory } from './history.js';
|
|
7
8
|
import { ResponseCache } from './response-cache.js';
|
|
8
|
-
import
|
|
9
|
-
export
|
|
9
|
+
import * as BrowserRouter from './shared.js';
|
|
10
|
+
export * as BrowserRouter from './shared.js';
|
|
10
11
|
export const BrowserRouterContext = createContext({
|
|
11
12
|
go: async () => '',
|
|
12
13
|
prefetch: () => { },
|
|
13
14
|
refresh: async () => '',
|
|
14
15
|
isNavigating: false,
|
|
15
|
-
url: {
|
|
16
|
+
url: {
|
|
17
|
+
pathname: '',
|
|
18
|
+
search: '',
|
|
19
|
+
hash: '',
|
|
20
|
+
},
|
|
21
|
+
history: {
|
|
22
|
+
entries: [],
|
|
23
|
+
index: -1,
|
|
24
|
+
},
|
|
16
25
|
});
|
|
17
26
|
const DEFAULT_GO_CONFIG = {
|
|
18
27
|
replace: false,
|
|
@@ -22,6 +31,7 @@ const responseCache = new ResponseCache();
|
|
|
22
31
|
export function BrowserRouterProvider({ children, setPayload, isNavigating = false, url, }) {
|
|
23
32
|
const id = useRef(0);
|
|
24
33
|
const controller = useRef(null);
|
|
34
|
+
const history = useRef(new BrowserRouterHistory(url));
|
|
25
35
|
/**
|
|
26
36
|
* Navigates to a given path
|
|
27
37
|
*
|
|
@@ -35,6 +45,13 @@ export function BrowserRouterProvider({ children, setPayload, isNavigating = fal
|
|
|
35
45
|
const navigationId = id.current;
|
|
36
46
|
const currentPath = window.location.pathname + window.location.search;
|
|
37
47
|
let path = currentPath;
|
|
48
|
+
// support numeric relative navigation like router.go(-1) to go back,
|
|
49
|
+
// or router.go(1) to go forward. Will trigger a popstate event which
|
|
50
|
+
// is intercepted
|
|
51
|
+
if (typeof to === 'number') {
|
|
52
|
+
const entry = history.current.go(to);
|
|
53
|
+
return `${entry.pathname}${entry.search}`;
|
|
54
|
+
}
|
|
38
55
|
const replace = opts?.replace ?? DEFAULT_GO_CONFIG.replace;
|
|
39
56
|
controller.current?.abort();
|
|
40
57
|
controller.current = null;
|
|
@@ -51,10 +68,10 @@ export function BrowserRouterProvider({ children, setPayload, isNavigating = fal
|
|
|
51
68
|
path = key;
|
|
52
69
|
if (path !== currentPath) {
|
|
53
70
|
if (replace) {
|
|
54
|
-
|
|
71
|
+
history.current.replaceState(path);
|
|
55
72
|
}
|
|
56
73
|
else {
|
|
57
|
-
|
|
74
|
+
history.current.pushState(path);
|
|
58
75
|
}
|
|
59
76
|
}
|
|
60
77
|
let promise = responseCache.get(path);
|
|
@@ -78,10 +95,10 @@ export function BrowserRouterProvider({ children, setPayload, isNavigating = fal
|
|
|
78
95
|
if (navigationId !== id.current)
|
|
79
96
|
return resolvedPath;
|
|
80
97
|
if (resolvedPath !== path) {
|
|
81
|
-
|
|
98
|
+
history.current.replaceState(resolvedPath);
|
|
82
99
|
}
|
|
83
100
|
setPayload?.(payload);
|
|
84
|
-
window.dispatchEvent(new CustomEvent(
|
|
101
|
+
window.dispatchEvent(new CustomEvent(Events.names.NAVIGATION, {
|
|
85
102
|
detail: { path: resolvedPath },
|
|
86
103
|
}));
|
|
87
104
|
return resolvedPath;
|
|
@@ -90,7 +107,7 @@ export function BrowserRouterProvider({ children, setPayload, isNavigating = fal
|
|
|
90
107
|
if (err instanceof Error && err.name === 'AbortError') {
|
|
91
108
|
return path;
|
|
92
109
|
}
|
|
93
|
-
window.dispatchEvent(new CustomEvent(
|
|
110
|
+
window.dispatchEvent(new CustomEvent(Events.names.NAVIGATION_ERROR, {
|
|
94
111
|
detail: {
|
|
95
112
|
path,
|
|
96
113
|
error: err instanceof Error ? err.message : Logger.print(err),
|
|
@@ -138,9 +155,13 @@ export function BrowserRouterProvider({ children, setPayload, isNavigating = fal
|
|
|
138
155
|
});
|
|
139
156
|
}, [go]);
|
|
140
157
|
useEffect(() => {
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
158
|
+
const h = history.current;
|
|
159
|
+
function handler() {
|
|
160
|
+
h.onPopState();
|
|
161
|
+
void go(BrowserRouter.toTarget(window.location.pathname + window.location.search), {
|
|
162
|
+
replace: true,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
144
165
|
window.addEventListener('popstate', handler);
|
|
145
166
|
return () => {
|
|
146
167
|
controller.current?.abort();
|
|
@@ -154,8 +175,13 @@ export function BrowserRouterProvider({ children, setPayload, isNavigating = fal
|
|
|
154
175
|
refresh,
|
|
155
176
|
isNavigating,
|
|
156
177
|
url: {
|
|
157
|
-
pathname: url
|
|
178
|
+
pathname: url.pathname,
|
|
158
179
|
search: url?.search,
|
|
180
|
+
hash: url?.hash,
|
|
181
|
+
},
|
|
182
|
+
history: {
|
|
183
|
+
entries: history.current.entries,
|
|
184
|
+
index: history.current.index,
|
|
159
185
|
},
|
|
160
186
|
}), [go, prefetch, refresh, isNavigating, url]);
|
|
161
187
|
return _jsx(BrowserRouterContext, { value: value, children: children });
|