@mismo/next 0.1.0-beta.0 → 0.1.0-beta.1
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 +20 -1
- package/dist/config.d.ts +9 -8
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +49 -28
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -4
- package/dist/index.js.map +1 -0
- package/dist/options.d.ts +50 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/output.d.ts +15 -8
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +132 -53
- package/dist/output.js.map +1 -0
- package/dist/runtime.d.ts +12 -11
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +68 -54
- package/dist/runtime.js.map +1 -0
- package/package.json +11 -3
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ mismo for Next.js (App Router). [mismo](https://mismo.dev) shares screens, route
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
|
|
10
|
+
pnpm add @mismo/core @mismo/next
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Setup
|
|
@@ -26,6 +26,25 @@ export default withMismo(
|
|
|
26
26
|
|
|
27
27
|
`@acme/app` is your shared package, which Next.js compiles along with mismo. Supports Next.js `>=16 <17`.
|
|
28
28
|
|
|
29
|
+
## Route options
|
|
30
|
+
|
|
31
|
+
Plain values go beside the screen, and mismo copies them into the generated server page. Code goes in the route's server file, which the native app never bundles:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// routes/orgs/[id].tsx
|
|
35
|
+
export const next = { revalidate: 3600 } satisfies NextRouteOptions;
|
|
36
|
+
|
|
37
|
+
// routes/orgs/[id].server.ts
|
|
38
|
+
export const next = {
|
|
39
|
+
async generateMetadata({ params }) {
|
|
40
|
+
const { id } = await params;
|
|
41
|
+
return { title: `Organization ${id}` };
|
|
42
|
+
},
|
|
43
|
+
} satisfies NextServerOptions;
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Shared screens stay client components, since they also run on native. See [route options](https://mismo.dev/docs/route-options).
|
|
47
|
+
|
|
29
48
|
See [Getting started](https://mismo.dev/docs/getting-started) for the whole setup: the shared routes package, and each app.
|
|
30
49
|
|
|
31
50
|
## License
|
package/dist/config.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export interface WithMismoOptions {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
/** The shared `routes.config.ts`, relative to the app. */
|
|
3
|
+
config: string;
|
|
4
|
+
/** The App Router's folder, relative to the app: `app`, or `src/app`. */
|
|
5
|
+
outDir: string;
|
|
6
|
+
/** The app's types file, relative to the app. */
|
|
7
|
+
types: string;
|
|
8
|
+
/** The app's folder. Next.js loads its config there, so it is `process.cwd()` by default. */
|
|
9
|
+
cwd?: string;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* Next.js's config for an app on mismo: writes the app's routes into the App Router's folder when
|
|
@@ -17,3 +17,4 @@ export interface WithMismoOptions {
|
|
|
17
17
|
* export default withMismo({ … }, { config: "../shared/routes.config.ts", outDir: "app", types: "app/routes.gen.ts" });
|
|
18
18
|
*/
|
|
19
19
|
export declare function withMismo<T extends object>(nextConfig: T, { config, outDir, types, cwd }: WithMismoOptions): T;
|
|
20
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","names":[],"sources":["../src/config.ts"],"mappings":"A,iBAiBiB;;EAEf;;EAEA;;EAEA;;EAEA;;;;;;;;;;wBAWc,UAAU,kBAAkB,YAAY,KAAK,QAAQ,QAAQ,OAAO,OAAuB,mBAAmB"}
|
package/dist/config.js
CHANGED
|
@@ -1,33 +1,54 @@
|
|
|
1
|
+
import { output } from "./output.js";
|
|
1
2
|
import { createRequire } from "node:module";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { watchAppRoutes } from "@mismo/core/generate";
|
|
4
|
-
|
|
5
|
+
// src/config.ts
|
|
5
6
|
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
7
|
+
* Next.js's config for an app on mismo: writes the app's routes into the App Router's folder when
|
|
8
|
+
* Next.js loads its config (before it reads the folder, for `next dev` and `next build` alike), and
|
|
9
|
+
* again in dev as route files come and go; and points `@mismo/core` at Next.js's runtime, for Turbopack
|
|
10
|
+
* and for webpack.
|
|
11
|
+
*
|
|
12
|
+
* export default withMismo({ … }, { config: "../shared/routes.config.ts", outDir: "app", types: "app/routes.gen.ts" });
|
|
13
|
+
*/
|
|
14
|
+
function withMismo(nextConfig, { config, outDir, types, cwd = process.cwd() }) {
|
|
15
|
+
const next = nextConfig;
|
|
16
|
+
watchAppRoutes({
|
|
17
|
+
config,
|
|
18
|
+
output: output({
|
|
19
|
+
outDir,
|
|
20
|
+
types
|
|
21
|
+
}),
|
|
22
|
+
cwd
|
|
23
|
+
});
|
|
24
|
+
const runtime = createRequire(path.join(cwd, "package.json")).resolve("@mismo/next/runtime");
|
|
25
|
+
const relative = `./${path.relative(cwd, runtime).split(path.sep).join("/")}`;
|
|
26
|
+
const previous = next.webpack;
|
|
27
|
+
const changed = {
|
|
28
|
+
turbopack: {
|
|
29
|
+
...next.turbopack,
|
|
30
|
+
resolveAlias: {
|
|
31
|
+
...next.turbopack?.resolveAlias,
|
|
32
|
+
"@mismo/core": relative
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
webpack: (webpackConfig, context) => {
|
|
36
|
+
const config = previous ? previous(webpackConfig, context) : webpackConfig;
|
|
37
|
+
config.resolve = {
|
|
38
|
+
...config.resolve,
|
|
39
|
+
alias: {
|
|
40
|
+
...config.resolve?.alias,
|
|
41
|
+
"@mismo/core$": runtime
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
return config;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
...next,
|
|
49
|
+
...changed
|
|
50
|
+
};
|
|
33
51
|
}
|
|
52
|
+
export { withMismo };
|
|
53
|
+
|
|
54
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","names":[],"sources":["../src/config.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport path from \"node:path\";\n\nimport { watchAppRoutes } from \"@mismo/core/generate\";\n\nimport { output } from \"./output.ts\";\n\n/** The part of Next.js's config this changes, read from any config object. */\ninterface NextConfigPart {\n turbopack?: { resolveAlias?: Record<string, unknown> } | undefined;\n webpack?: ((config: WebpackConfig, context: unknown) => WebpackConfig) | null | undefined;\n}\n\ninterface WebpackConfig {\n resolve?: { alias?: Record<string, unknown> };\n}\n\nexport interface WithMismoOptions {\n /** The shared `routes.config.ts`, relative to the app. */\n config: string;\n /** The App Router's folder, relative to the app: `app`, or `src/app`. */\n outDir: string;\n /** The app's types file, relative to the app. */\n types: string;\n /** The app's folder. Next.js loads its config there, so it is `process.cwd()` by default. */\n cwd?: string;\n}\n\n/**\n * Next.js's config for an app on mismo: writes the app's routes into the App Router's folder when\n * Next.js loads its config (before it reads the folder, for `next dev` and `next build` alike), and\n * again in dev as route files come and go; and points `@mismo/core` at Next.js's runtime, for Turbopack\n * and for webpack.\n *\n * export default withMismo({ … }, { config: \"../shared/routes.config.ts\", outDir: \"app\", types: \"app/routes.gen.ts\" });\n */\nexport function withMismo<T extends object>(nextConfig: T, { config, outDir, types, cwd = process.cwd() }: WithMismoOptions): T {\n const next = nextConfig as T & NextConfigPart;\n watchAppRoutes({ config, output: output({ outDir, types }), cwd });\n\n // From the app, which depends on this adapter: the shared package importing `@mismo/core` may not.\n const runtime = createRequire(path.join(cwd, \"package.json\")).resolve(\"@mismo/next/runtime\");\n // Turbopack takes a path relative to the app, webpack an absolute one.\n const relative = `./${path.relative(cwd, runtime).split(path.sep).join(\"/\")}`;\n const previous = next.webpack;\n\n const changed: NextConfigPart = {\n turbopack: {\n ...next.turbopack,\n resolveAlias: { ...next.turbopack?.resolveAlias, \"@mismo/core\": relative },\n },\n webpack: (webpackConfig: WebpackConfig, context: unknown) => {\n const config = previous ? previous(webpackConfig, context) : webpackConfig;\n config.resolve = { ...config.resolve, alias: { ...config.resolve?.alias, \"@mismo/core$\": runtime } };\n return config;\n },\n };\n\n return { ...next, ...changed };\n}\n"],"mappings":";;;;;;;;;;;;;AAoCA,SAAgB,UAA4B,YAAe,EAAE,QAAQ,QAAQ,OAAO,MAAM,QAAQ,IAAI,KAA0B;CAC9H,MAAM,OAAO;CACb,eAAe;EAAE;EAAQ,QAAQ,OAAO;GAAE;GAAQ;EAAM,CAAC;EAAG;CAAI,CAAC;CAGjE,MAAM,UAAU,cAAc,KAAK,KAAK,KAAK,cAAc,CAAC,CAAC,CAAC,QAAQ,qBAAqB;CAE3F,MAAM,WAAW,KAAK,KAAK,SAAS,KAAK,OAAO,CAAC,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,GAAG;CAC1E,MAAM,WAAW,KAAK;CAEtB,MAAM,UAA0B;EAC9B,WAAW;GACT,GAAG,KAAK;GACR,cAAc;IAAE,GAAG,KAAK,WAAW;IAAc,eAAe;GAAS;EAC3E;EACA,UAAU,eAA8B,YAAqB;GAC3D,MAAM,SAAS,WAAW,SAAS,eAAe,OAAO,IAAI;GAC7D,OAAO,UAAU;IAAE,GAAG,OAAO;IAAS,OAAO;KAAE,GAAG,OAAO,SAAS;KAAO,gBAAgB;IAAQ;GAAE;GACnG,OAAO;EACT;CACF;CAEA,OAAO;EAAE,GAAG;EAAM,GAAG;CAAQ;AAC/B"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { output } from "./output.js";
|
|
2
|
+
import { NextRouteOptions, NextServerOptions } from "./options.js";
|
|
2
3
|
export declare const next: {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
name: string;
|
|
5
|
+
rules: import("@mismo/core/generate").RouteRules;
|
|
6
|
+
output: typeof output;
|
|
6
7
|
};
|
|
8
|
+
export type { NextRouteOptions, NextServerOptions };
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;qBAOa"}
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["// `@mismo/next`: mismo for Next.js's App Router. Routes written out for a Next.js app (`output`),\n// and what the App Router cannot express (`rules`). The runtime is `@mismo/next/runtime`, pointed\n// at by `withMismo` (`@mismo/next/config`).\nimport type { RouterAdapter } from \"@mismo/core/adapter\";\n\nimport { output, rules } from \"./output.ts\";\n\nexport const next = {\n name: \"Next.js\",\n rules,\n output,\n} satisfies RouterAdapter;\n\nexport type { NextRouteOptions, NextServerOptions } from \"./options.ts\";\n"],"mappings":";;AAOA,MAAa,OAAO;CAClB,MAAM;CACN;CACA;AACF"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Metadata, ResolvingMetadata, ResolvingViewport, Viewport } from "next";
|
|
2
|
+
/** What Next.js passes `generateMetadata` and `generateViewport`: the route's params and query. */
|
|
3
|
+
type RouteProps = {
|
|
4
|
+
params: Promise<Record<string, string | string[] | undefined>>;
|
|
5
|
+
searchParams: Promise<Record<string, string | string[] | undefined>>;
|
|
6
|
+
};
|
|
7
|
+
/** Route segment config: written as literals, since Next.js reads it without running the file. */
|
|
8
|
+
type SegmentConfig = {
|
|
9
|
+
dynamic?: "auto" | "force-dynamic" | "error" | "force-static";
|
|
10
|
+
dynamicParams?: boolean;
|
|
11
|
+
revalidate?: number | false;
|
|
12
|
+
fetchCache?: "auto" | "default-cache" | "only-cache" | "force-cache" | "force-no-store" | "default-no-store" | "only-no-store";
|
|
13
|
+
runtime?: "nodejs" | "edge";
|
|
14
|
+
preferredRegion?: "auto" | "global" | "home" | string | string[];
|
|
15
|
+
maxDuration?: number;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Next.js's options for a shared route that are plain values, exported as `next` from the route's
|
|
19
|
+
* own module: metadata and segment config. The module is a client component, which Next.js can't
|
|
20
|
+
* read from the server, so mismo copies each value into the generated server page as written. Each
|
|
21
|
+
* must stand on its own: no names, calls or functions. Anything that needs code goes in the route's
|
|
22
|
+
* server file (`NextServerOptions`).
|
|
23
|
+
*
|
|
24
|
+
* export const next = { metadata: { title: "Settings" }, revalidate: 60 } satisfies NextRouteOptions;
|
|
25
|
+
*/
|
|
26
|
+
export type NextRouteOptions = SegmentConfig & {
|
|
27
|
+
metadata?: Metadata;
|
|
28
|
+
viewport?: Viewport;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Next.js's options for a shared route that run on the server, exported as `next` from the route's
|
|
32
|
+
* server file (`orgs/[id].server.ts`), which only the web app's generated server components import:
|
|
33
|
+
* functions such as `generateMetadata` and `generateStaticParams`, and any value that needs code.
|
|
34
|
+
*
|
|
35
|
+
* export const next = {
|
|
36
|
+
* async generateMetadata({ params }) {
|
|
37
|
+
* const { id } = await params;
|
|
38
|
+
* return { title: `Organization ${id}` };
|
|
39
|
+
* },
|
|
40
|
+
* } satisfies NextServerOptions;
|
|
41
|
+
*/
|
|
42
|
+
export type NextServerOptions = SegmentConfig & {
|
|
43
|
+
metadata?: Metadata;
|
|
44
|
+
generateMetadata?: (props: RouteProps, parent: ResolvingMetadata) => Metadata | Promise<Metadata>;
|
|
45
|
+
viewport?: Viewport;
|
|
46
|
+
generateViewport?: (props: RouteProps, parent: ResolvingViewport) => Viewport | Promise<Viewport>;
|
|
47
|
+
generateStaticParams?: () => Record<string, string | string[]>[] | Promise<Record<string, string | string[]>[]>;
|
|
48
|
+
};
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","names":[],"sources":["../src/options.ts"],"mappings":";;KAGK;EACH,QAAQ,QAAQ;EAChB,cAAc,QAAQ;;;KAInB;EACH;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;;;;;;YAYU,mBAAmB;EAC7B,WAAW;EACX,WAAW;;;;;;;;;;;;;;YAeD,oBAAoB;EAC9B,WAAW;EACX,oBAAoB,OAAO,YAAY,QAAQ,sBAAsB,WAAW,QAAQ;EACxF,WAAW;EACX,oBAAoB,OAAO,YAAY,QAAQ,sBAAsB,WAAW,QAAQ;EACxF,6BAA6B,sCAAsC,QAAQ"}
|
package/dist/output.d.ts
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
/** What Next.js's App Router cannot express, from its folder conventions. */
|
|
3
|
-
export declare const rules: RouteRules;
|
|
1
|
+
import { RouteOutput } from "@mismo/core/adapter";
|
|
4
2
|
/**
|
|
5
3
|
* Next.js's files, for a web app on the App Router: the routes folder's layout in `app/`, a
|
|
6
4
|
* `page.tsx` per page and a `layout.tsx` per layout, next to the app's own (its root `layout.tsx`
|
|
7
|
-
* among them).
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* among them).
|
|
6
|
+
*
|
|
7
|
+
* Each is a server component that renders a client one beside it: `page.tsx` renders
|
|
8
|
+
* `mismo-page.tsx`, `layout.tsx` renders `mismo-layout.tsx`. The shared screens use hooks, so they
|
|
9
|
+
* render on the client side of that line; the server file carries the route's Next.js options
|
|
10
|
+
* (metadata, `generateStaticParams`, its rendering and caching config), which Next.js reads only
|
|
11
|
+
* from server files. They come from the route's server file, `orgs/[id].server.ts`, as
|
|
12
|
+
* `export const next = { … }` (see `NextServerOptions`).
|
|
13
|
+
*
|
|
14
|
+
* A page renders in a Suspense boundary, which Next.js needs around `useSearchParams` to prerender
|
|
15
|
+
* it; a layout hands the page Next.js gives it (`children`) to `Slot`.
|
|
10
16
|
*/
|
|
11
17
|
export declare function output({ outDir, types }: {
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
outDir: string;
|
|
19
|
+
types: string;
|
|
14
20
|
}): RouteOutput;
|
|
21
|
+
//# sourceMappingURL=output.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.d.ts","names":[],"sources":["../src/output.ts"],"mappings":";;;;;;;;;;;;;;;;wBAuDgB,SAAS,QAAQ;EAAW;EAAgB;IAAkB"}
|
package/dist/output.js
CHANGED
|
@@ -1,66 +1,142 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { GENERATED, importPath, isGroup, isParam, oneUrlPerPage, placesOf } from "@mismo/core/adapter";
|
|
2
|
+
import { GENERATED, exportedOptions, importPath, isGroup, isParam, isPlainValue, oneUrlPerPage, placesOf, serverModule } from "@mismo/core/adapter";
|
|
3
|
+
// src/output.ts
|
|
3
4
|
/** What Next.js's App Router cannot express, from its folder conventions. */
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (segment.startsWith("@"))
|
|
18
|
-
problems.push(`${route.id}: "${segment}" starts with "@", a parallel route's slot to Next.js.`);
|
|
19
|
-
if (isGroup(segment) && segment.startsWith("(."))
|
|
20
|
-
problems.push(`${route.id}: "${segment}" is an intercepting route to Next.js.`);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return problems;
|
|
24
|
-
},
|
|
5
|
+
const rules = {
|
|
6
|
+
router: "Next.js",
|
|
7
|
+
target: "web",
|
|
8
|
+
check(routes) {
|
|
9
|
+
const problems = oneUrlPerPage(routes, "Next.js");
|
|
10
|
+
for (const { route } of placesOf(routes)) for (const segment of route.segments) {
|
|
11
|
+
if (isParam(segment)) continue;
|
|
12
|
+
if (segment.startsWith("_")) problems.push(`${route.id}: "${segment}" starts with "_", a private folder to Next.js, left out of its routes.`);
|
|
13
|
+
if (segment.startsWith("@")) problems.push(`${route.id}: "${segment}" starts with "@", a parallel route's slot to Next.js.`);
|
|
14
|
+
if (isGroup(segment) && segment.startsWith("(.")) problems.push(`${route.id}: "${segment}" is an intercepting route to Next.js.`);
|
|
15
|
+
}
|
|
16
|
+
return problems;
|
|
17
|
+
}
|
|
25
18
|
};
|
|
26
19
|
/**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
20
|
+
* Next.js's files, for a web app on the App Router: the routes folder's layout in `app/`, a
|
|
21
|
+
* `page.tsx` per page and a `layout.tsx` per layout, next to the app's own (its root `layout.tsx`
|
|
22
|
+
* among them).
|
|
23
|
+
*
|
|
24
|
+
* Each is a server component that renders a client one beside it: `page.tsx` renders
|
|
25
|
+
* `mismo-page.tsx`, `layout.tsx` renders `mismo-layout.tsx`. The shared screens use hooks, so they
|
|
26
|
+
* render on the client side of that line; the server file carries the route's Next.js options
|
|
27
|
+
* (metadata, `generateStaticParams`, its rendering and caching config), which Next.js reads only
|
|
28
|
+
* from server files. They come from the route's server file, `orgs/[id].server.ts`, as
|
|
29
|
+
* `export const next = { … }` (see `NextServerOptions`).
|
|
30
|
+
*
|
|
31
|
+
* A page renders in a Suspense boundary, which Next.js needs around `useSearchParams` to prerender
|
|
32
|
+
* it; a layout hands the page Next.js gives it (`children`) to `Slot`.
|
|
33
|
+
*/
|
|
34
|
+
function output({ outDir, types }) {
|
|
35
|
+
return {
|
|
36
|
+
name: "next",
|
|
37
|
+
target: "web",
|
|
38
|
+
outDir,
|
|
39
|
+
types,
|
|
40
|
+
rules,
|
|
41
|
+
runtime: "@mismo/next/runtime",
|
|
42
|
+
emit(routes, context) {
|
|
43
|
+
const files = /* @__PURE__ */ new Map();
|
|
44
|
+
for (const { route, folder } of placesOf(routes)) {
|
|
45
|
+
const segments = [...folder, ...route.segments].map(toNext);
|
|
46
|
+
const dirs = route.layout || segments.at(-1) !== "index" ? segments : segments.slice(0, -1);
|
|
47
|
+
const kind = route.layout ? "layout" : "page";
|
|
48
|
+
const server = [...dirs, `${kind}.tsx`].join("/");
|
|
49
|
+
const client = [...dirs, `mismo-${kind}.tsx`].join("/");
|
|
50
|
+
const from = importPath(path.join(context.outDir, client), route.file);
|
|
51
|
+
files.set(client, route.layout ? clientLayout(from) : clientPage(from));
|
|
52
|
+
files.set(server, serverFile(route, kind, path.join(context.outDir, server)));
|
|
53
|
+
}
|
|
54
|
+
return files;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
53
57
|
}
|
|
54
58
|
/** A segment as Next.js spells it: `:id` is `[id]`. */
|
|
55
|
-
const toNext = (segment) =>
|
|
56
|
-
|
|
59
|
+
const toNext = (segment) => isParam(segment) ? `[${segment.slice(1)}]` : segment;
|
|
60
|
+
/** Options Next.js reads as values at run time: passed on as the export of their name. */
|
|
61
|
+
const VALUE_EXPORTS = /* @__PURE__ */ new Set([
|
|
62
|
+
"metadata",
|
|
63
|
+
"generateMetadata",
|
|
64
|
+
"viewport",
|
|
65
|
+
"generateViewport",
|
|
66
|
+
"generateStaticParams"
|
|
67
|
+
]);
|
|
68
|
+
/**
|
|
69
|
+
* Route segment config: Next.js reads these from the file's source without running it, so each
|
|
70
|
+
* must be a literal, and is written into the generated file as one.
|
|
71
|
+
*/
|
|
72
|
+
const CONFIG_EXPORTS = /* @__PURE__ */ new Set([
|
|
73
|
+
"dynamic",
|
|
74
|
+
"dynamicParams",
|
|
75
|
+
"revalidate",
|
|
76
|
+
"fetchCache",
|
|
77
|
+
"runtime",
|
|
78
|
+
"preferredRegion",
|
|
79
|
+
"maxDuration"
|
|
80
|
+
]);
|
|
81
|
+
const LITERAL = /^(?:-?\d+(?:\.\d+)?|true|false|"[^"\\]*"|'[^'\\]*'|\[\s*(?:(?:"[^"]*"|'[^']*')\s*,?\s*)*\])$/;
|
|
82
|
+
/**
|
|
83
|
+
* The server component: the route's Next.js options, and the client component it renders.
|
|
84
|
+
*
|
|
85
|
+
* Options come from two places. The route's module may set plain values (`metadata: { title }`,
|
|
86
|
+
* `revalidate: 60`): it's a client module Next.js can't read from the server, so they're copied in
|
|
87
|
+
* as text. Its server file may set anything, functions included (`generateMetadata`), and is
|
|
88
|
+
* imported. An option set in both is an error, so each has one source.
|
|
89
|
+
*/
|
|
90
|
+
function serverFile(route, kind, at) {
|
|
91
|
+
const imports = [];
|
|
92
|
+
const exports = [];
|
|
93
|
+
const server = serverModule(route.file);
|
|
94
|
+
const fromModule = exportedOptions(route.file, "next") ?? /* @__PURE__ */ new Map();
|
|
95
|
+
const fromServer = server && exportedOptions(server, "next") || /* @__PURE__ */ new Map();
|
|
96
|
+
const known = (key, where) => {
|
|
97
|
+
if (!VALUE_EXPORTS.has(key) && !CONFIG_EXPORTS.has(key)) throw new Error(`${where}: \`next.${key}\` isn't a Next.js route option. It takes ${[...VALUE_EXPORTS, ...CONFIG_EXPORTS].join(", ")}.`);
|
|
98
|
+
};
|
|
99
|
+
const config = (key, value, where) => {
|
|
100
|
+
const literal = value.replace(/\s+as\s+const$/, "").trim();
|
|
101
|
+
if (!LITERAL.test(literal)) throw new Error(`${where}: \`next.${key}\` must be a literal, such as \`${key}: 60\`. Next.js reads it without running the file.`);
|
|
102
|
+
exports.push(`export const ${key} = ${literal};`);
|
|
103
|
+
};
|
|
104
|
+
for (const [key, value] of fromModule) {
|
|
105
|
+
known(key, route.id);
|
|
106
|
+
if (fromServer.has(key)) throw new Error(`${route.id}: \`next.${key}\` is set in both the route's module and its server file. Keep one.`);
|
|
107
|
+
if (CONFIG_EXPORTS.has(key)) config(key, value, route.id);
|
|
108
|
+
else if (isPlainValue(value)) exports.push(`export const ${key} = ${value.replace(/\s+as\s+const$/, "").trim()};`);
|
|
109
|
+
else throw new Error(`${route.id}: \`next.${key}\` isn't a plain value, so the server can't take it from the route's module. Move it to the route's server file, ${route.id}.server.ts.`);
|
|
110
|
+
}
|
|
111
|
+
for (const [key, value] of fromServer) {
|
|
112
|
+
known(key, `${route.id}.server`);
|
|
113
|
+
if (CONFIG_EXPORTS.has(key)) config(key, value, `${route.id}.server`);
|
|
114
|
+
else exports.push(`export const ${key} = next.${key};`);
|
|
115
|
+
}
|
|
116
|
+
if (server && exports.some((line) => line.includes("= next."))) imports.push(`import { next } from "${importPath(at, server)}";`);
|
|
117
|
+
if (kind === "layout") {
|
|
118
|
+
imports.unshift("import type { ReactNode } from \"react\";");
|
|
119
|
+
imports.push("import Shell from \"./mismo-layout\";");
|
|
120
|
+
} else imports.push("import Screen from \"./mismo-page\";");
|
|
121
|
+
const component = kind === "layout" ? `export default function MismoLayout({ children }: { children: ReactNode }) {
|
|
122
|
+
return <Shell>{children}</Shell>;
|
|
123
|
+
}` : `export default function MismoPage() {
|
|
124
|
+
return <Screen />;
|
|
125
|
+
}`;
|
|
126
|
+
return `${GENERATED}${[
|
|
127
|
+
imports.join("\n"),
|
|
128
|
+
...exports.length ? [exports.join("\n")] : [],
|
|
129
|
+
component
|
|
130
|
+
].join("\n\n")}\n`;
|
|
131
|
+
}
|
|
132
|
+
const clientPage = (from) => `${GENERATED}"use client";
|
|
57
133
|
|
|
58
134
|
import { Suspense } from "react";
|
|
59
135
|
|
|
60
136
|
import Page from "${from}";
|
|
61
137
|
|
|
62
138
|
// Next.js prerenders a page only with a boundary around \`useSearchParams\`.
|
|
63
|
-
export default function
|
|
139
|
+
export default function MismoScreen() {
|
|
64
140
|
return (
|
|
65
141
|
<Suspense>
|
|
66
142
|
<Page />
|
|
@@ -68,7 +144,7 @@ export default function MismoPage() {
|
|
|
68
144
|
);
|
|
69
145
|
}
|
|
70
146
|
`;
|
|
71
|
-
const
|
|
147
|
+
const clientLayout = (from) => `${GENERATED}"use client";
|
|
72
148
|
|
|
73
149
|
import { SlotChildren } from "@mismo/next/runtime";
|
|
74
150
|
import type { ReactNode } from "react";
|
|
@@ -76,7 +152,7 @@ import type { ReactNode } from "react";
|
|
|
76
152
|
import Layout from "${from}";
|
|
77
153
|
|
|
78
154
|
// Next.js gives a layout its page as \`children\`: the shared layout renders it with \`Slot\`.
|
|
79
|
-
export default function
|
|
155
|
+
export default function MismoShell({ children }: { children: ReactNode }) {
|
|
80
156
|
return (
|
|
81
157
|
<SlotChildren value={children}>
|
|
82
158
|
<Layout />
|
|
@@ -84,3 +160,6 @@ export default function MismoLayout({ children }: { children: ReactNode }) {
|
|
|
84
160
|
);
|
|
85
161
|
}
|
|
86
162
|
`;
|
|
163
|
+
export { output, rules };
|
|
164
|
+
|
|
165
|
+
//# sourceMappingURL=output.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.js","names":[],"sources":["../src/output.ts"],"sourcesContent":["import path from \"node:path\";\n\nimport {\n exportedOptions,\n type Files,\n isPlainValue,\n GENERATED,\n importPath,\n isGroup,\n isParam,\n oneUrlPerPage,\n placesOf,\n type RouteOutput,\n type RouteRules,\n type SharedRoute,\n serverModule,\n} from \"@mismo/core/adapter\";\n\n/** What Next.js's App Router cannot express, from its folder conventions. */\nexport const rules: RouteRules = {\n router: \"Next.js\",\n target: \"web\",\n check(routes) {\n // It refuses two pages that resolve to one URL, such as `(a)/about` and `(b)/about`.\n const problems = oneUrlPerPage(routes, \"Next.js\");\n\n // Folder names it reads as something other than a route, without an error.\n for (const { route } of placesOf(routes)) {\n for (const segment of route.segments) {\n if (isParam(segment)) continue;\n if (segment.startsWith(\"_\")) problems.push(`${route.id}: \"${segment}\" starts with \"_\", a private folder to Next.js, left out of its routes.`);\n if (segment.startsWith(\"@\")) problems.push(`${route.id}: \"${segment}\" starts with \"@\", a parallel route's slot to Next.js.`);\n if (isGroup(segment) && segment.startsWith(\"(.\")) problems.push(`${route.id}: \"${segment}\" is an intercepting route to Next.js.`);\n }\n }\n\n return problems;\n },\n};\n\n/**\n * Next.js's files, for a web app on the App Router: the routes folder's layout in `app/`, a\n * `page.tsx` per page and a `layout.tsx` per layout, next to the app's own (its root `layout.tsx`\n * among them).\n *\n * Each is a server component that renders a client one beside it: `page.tsx` renders\n * `mismo-page.tsx`, `layout.tsx` renders `mismo-layout.tsx`. The shared screens use hooks, so they\n * render on the client side of that line; the server file carries the route's Next.js options\n * (metadata, `generateStaticParams`, its rendering and caching config), which Next.js reads only\n * from server files. They come from the route's server file, `orgs/[id].server.ts`, as\n * `export const next = { … }` (see `NextServerOptions`).\n *\n * A page renders in a Suspense boundary, which Next.js needs around `useSearchParams` to prerender\n * it; a layout hands the page Next.js gives it (`children`) to `Slot`.\n */\nexport function output({ outDir, types }: { outDir: string; types: string }): RouteOutput {\n return {\n name: \"next\",\n target: \"web\",\n outDir,\n types,\n rules,\n runtime: \"@mismo/next/runtime\",\n emit(routes, context) {\n const files: Files = new Map();\n\n for (const { route, folder } of placesOf(routes)) {\n const segments = [...folder, ...route.segments].map(toNext);\n const dirs = route.layout || segments.at(-1) !== \"index\" ? segments : segments.slice(0, -1);\n const kind = route.layout ? \"layout\" : \"page\";\n const server = [...dirs, `${kind}.tsx`].join(\"/\");\n const client = [...dirs, `mismo-${kind}.tsx`].join(\"/\");\n\n const from = importPath(path.join(context.outDir, client), route.file);\n files.set(client, route.layout ? clientLayout(from) : clientPage(from));\n files.set(server, serverFile(route, kind, path.join(context.outDir, server)));\n }\n\n return files;\n },\n };\n}\n\n/** A segment as Next.js spells it: `:id` is `[id]`. */\nconst toNext = (segment: string) => (isParam(segment) ? `[${segment.slice(1)}]` : segment);\n\n/** Options Next.js reads as values at run time: passed on as the export of their name. */\nconst VALUE_EXPORTS = new Set([\"metadata\", \"generateMetadata\", \"viewport\", \"generateViewport\", \"generateStaticParams\"]);\n\n/**\n * Route segment config: Next.js reads these from the file's source without running it, so each\n * must be a literal, and is written into the generated file as one.\n */\nconst CONFIG_EXPORTS = new Set([\"dynamic\", \"dynamicParams\", \"revalidate\", \"fetchCache\", \"runtime\", \"preferredRegion\", \"maxDuration\"]);\nconst LITERAL = /^(?:-?\\d+(?:\\.\\d+)?|true|false|\"[^\"\\\\]*\"|'[^'\\\\]*'|\\[\\s*(?:(?:\"[^\"]*\"|'[^']*')\\s*,?\\s*)*\\])$/;\n\n/**\n * The server component: the route's Next.js options, and the client component it renders.\n *\n * Options come from two places. The route's module may set plain values (`metadata: { title }`,\n * `revalidate: 60`): it's a client module Next.js can't read from the server, so they're copied in\n * as text. Its server file may set anything, functions included (`generateMetadata`), and is\n * imported. An option set in both is an error, so each has one source.\n */\nfunction serverFile(route: SharedRoute, kind: \"page\" | \"layout\", at: string): string {\n const imports: string[] = [];\n const exports: string[] = [];\n const server = serverModule(route.file);\n const fromModule = exportedOptions(route.file, \"next\") ?? new Map<string, string>();\n const fromServer = (server && exportedOptions(server, \"next\")) || new Map<string, string>();\n\n const known = (key: string, where: string) => {\n if (!VALUE_EXPORTS.has(key) && !CONFIG_EXPORTS.has(key)) {\n throw new Error(`${where}: \\`next.${key}\\` isn't a Next.js route option. It takes ${[...VALUE_EXPORTS, ...CONFIG_EXPORTS].join(\", \")}.`);\n }\n };\n const config = (key: string, value: string, where: string) => {\n const literal = value.replace(/\\s+as\\s+const$/, \"\").trim();\n if (!LITERAL.test(literal)) {\n throw new Error(`${where}: \\`next.${key}\\` must be a literal, such as \\`${key}: 60\\`. Next.js reads it without running the file.`);\n }\n exports.push(`export const ${key} = ${literal};`);\n };\n\n for (const [key, value] of fromModule) {\n known(key, route.id);\n if (fromServer.has(key)) throw new Error(`${route.id}: \\`next.${key}\\` is set in both the route's module and its server file. Keep one.`);\n if (CONFIG_EXPORTS.has(key)) config(key, value, route.id);\n else if (isPlainValue(value)) exports.push(`export const ${key} = ${value.replace(/\\s+as\\s+const$/, \"\").trim()};`);\n else {\n throw new Error(\n `${route.id}: \\`next.${key}\\` isn't a plain value, so the server can't take it from the route's module. Move it to the route's server file, ${route.id}.server.ts.`,\n );\n }\n }\n\n for (const [key, value] of fromServer) {\n known(key, `${route.id}.server`);\n if (CONFIG_EXPORTS.has(key)) config(key, value, `${route.id}.server`);\n else exports.push(`export const ${key} = next.${key};`);\n }\n if (server && exports.some((line) => line.includes(\"= next.\"))) imports.push(`import { next } from \"${importPath(at, server)}\";`);\n\n if (kind === \"layout\") {\n imports.unshift('import type { ReactNode } from \"react\";');\n imports.push('import Shell from \"./mismo-layout\";');\n } else {\n imports.push('import Screen from \"./mismo-page\";');\n }\n\n const component =\n kind === \"layout\"\n ? `export default function MismoLayout({ children }: { children: ReactNode }) {\n return <Shell>{children}</Shell>;\n}`\n : `export default function MismoPage() {\n return <Screen />;\n}`;\n\n return `${GENERATED}${[imports.join(\"\\n\"), ...(exports.length ? [exports.join(\"\\n\")] : []), component].join(\"\\n\\n\")}\\n`;\n}\n\nconst clientPage = (from: string) => `${GENERATED}\"use client\";\n\nimport { Suspense } from \"react\";\n\nimport Page from \"${from}\";\n\n// Next.js prerenders a page only with a boundary around \\`useSearchParams\\`.\nexport default function MismoScreen() {\n return (\n <Suspense>\n <Page />\n </Suspense>\n );\n}\n`;\n\nconst clientLayout = (from: string) => `${GENERATED}\"use client\";\n\nimport { SlotChildren } from \"@mismo/next/runtime\";\nimport type { ReactNode } from \"react\";\n\nimport Layout from \"${from}\";\n\n// Next.js gives a layout its page as \\`children\\`: the shared layout renders it with \\`Slot\\`.\nexport default function MismoShell({ children }: { children: ReactNode }) {\n return (\n <SlotChildren value={children}>\n <Layout />\n </SlotChildren>\n );\n}\n`;\n"],"mappings":"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"}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
export { pathOf, type BackLinkProps, type Href, type LinkProps, type Nav, type Routes, type SearchParams, type To, } from "@mismo/core/contract";
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { BackLinkProps, BackLinkProps as BackLinkProps$1, Href, Href as Href$1, LinkProps, LinkProps as LinkProps$1, Nav, Nav as Nav$1, Routes, Routes as Routes$1, SearchParams, SearchParams as SearchParams$1, To, To as To$1, pathOf } from "@mismo/core/contract";
|
|
4
3
|
/** For the generated `layout.tsx`: the page below the shared layout it renders. */
|
|
5
4
|
export declare function SlotChildren({ value, children }: {
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
value: ReactNode;
|
|
6
|
+
children: ReactNode;
|
|
8
7
|
}): import("react").JSX.Element;
|
|
9
8
|
/** Where a layout renders its child route. */
|
|
10
9
|
export declare function Slot(): import("react").JSX.Element;
|
|
11
10
|
/** A real link, so it can be opened in a new tab. Next.js loads its page ahead as it shows. */
|
|
12
|
-
export declare function Link({ to, replace, label, children }: LinkProps): import("react").JSX.Element;
|
|
11
|
+
export declare function Link({ to, replace, label, children }: LinkProps$1): import("react").JSX.Element;
|
|
13
12
|
/**
|
|
14
13
|
* Goes back when there is somewhere to go back to in this app, and to `fallback` otherwise. A real
|
|
15
14
|
* link to `fallback`, so a modified click still opens it in a new tab.
|
|
16
15
|
*/
|
|
17
|
-
export declare function BackLink({ fallback, label, children }: BackLinkProps): import("react").JSX.Element;
|
|
16
|
+
export declare function BackLink({ fallback, label, children }: BackLinkProps$1): import("react").JSX.Element;
|
|
18
17
|
/** Navigates as soon as it renders, replacing the current entry. */
|
|
19
18
|
export declare function Redirect({ to }: {
|
|
20
|
-
|
|
19
|
+
to: To$1;
|
|
21
20
|
}): null;
|
|
22
|
-
export declare function useNav(): Nav;
|
|
21
|
+
export declare function useNav(): Nav$1;
|
|
23
22
|
/** The current path, such as `/orgs/acme`. */
|
|
24
23
|
export declare function usePathname(): string;
|
|
25
24
|
/** The current route's params. `href` is the route the screen is for, which types them. */
|
|
26
|
-
export declare function useParams<H extends Href>(_href: H): Routes[H];
|
|
25
|
+
export declare function useParams<H extends Href$1>(_href: H): Routes$1[H];
|
|
27
26
|
/** The query of the current URL. A key given twice is its first value, as on native. */
|
|
28
|
-
export declare function useSearchParams(): SearchParams;
|
|
27
|
+
export declare function useSearchParams(): SearchParams$1;
|
|
28
|
+
export { type BackLinkProps, type Href, type LinkProps, type Nav, type Routes, type SearchParams, type To, pathOf };
|
|
29
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","names":["LinkProps","BackLinkProps","To","Nav","Href","Routes","SearchParams"],"sources":["../src/runtime.tsx"],"mappings":";;;wBA+BgB,eAAe,OAAO;EAAc,OAAO;EAAW,UAAU;oBAAW,IAAA;;wBAK3E,wBAAI,IAAA;;wBAKJ,OAAO,IAAI,SAAiB,OAAO,YAAYA,8BAAS,IAAA;;;;;wBAsBxD,WAAW,UAAU,OAAO,YAAYC,kCAAa,IAAA;;wBAiBrD,WAAW;EAAQ,IAAIC;;wBAQvB,UAAUC;;wBAgBV;;wBAKA,UAAU,UAAUC,QAAM,OAAO,IAAIC,SAAO;;wBAK5C,mBAAmBC"}
|
package/dist/runtime.js
CHANGED
|
@@ -1,79 +1,93 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
3
2
|
import NextLink from "next/link";
|
|
4
|
-
import {
|
|
3
|
+
import { useParams as useParams$1, usePathname as usePathname$1, useRouter, useSearchParams as useSearchParams$1 } from "next/navigation";
|
|
5
4
|
import { createContext, useContext, useEffect, useMemo } from "react";
|
|
6
|
-
import { pathOf } from "@mismo/core/contract";
|
|
7
|
-
|
|
5
|
+
import { pathOf, pathOf as pathOf$1 } from "@mismo/core/contract";
|
|
6
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
7
|
+
// src/runtime.tsx
|
|
8
8
|
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
* The page a Next.js layout was given. Next.js passes it as `children`, where other routers render
|
|
10
|
+
* an outlet: the generated `layout.tsx` provides it, and `Slot` renders it, so a shared layout is
|
|
11
|
+
* the same on every router.
|
|
12
|
+
*/
|
|
13
13
|
const Children = createContext(null);
|
|
14
14
|
/** For the generated `layout.tsx`: the page below the shared layout it renders. */
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
function SlotChildren({ value, children }) {
|
|
16
|
+
return /* @__PURE__ */ jsx(Children.Provider, {
|
|
17
|
+
value,
|
|
18
|
+
children
|
|
19
|
+
});
|
|
17
20
|
}
|
|
18
21
|
/** Where a layout renders its child route. */
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
function Slot() {
|
|
23
|
+
return /* @__PURE__ */ jsx(Fragment, { children: useContext(Children) });
|
|
21
24
|
}
|
|
22
25
|
/** A real link, so it can be opened in a new tab. Next.js loads its page ahead as it shows. */
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
function Link({ to, replace = false, label, children }) {
|
|
27
|
+
return /* @__PURE__ */ jsx(NextLink, {
|
|
28
|
+
href: pathOf$1(to),
|
|
29
|
+
replace,
|
|
30
|
+
"aria-label": label,
|
|
31
|
+
children
|
|
32
|
+
});
|
|
25
33
|
}
|
|
26
34
|
/**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
35
|
+
* Whether there is an earlier entry in this site's history to go back to. The Navigation API says
|
|
36
|
+
* so exactly; without it, any earlier entry counts.
|
|
37
|
+
*/
|
|
30
38
|
function canGoBack() {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return browser.navigation?.canGoBack ?? browser.history.length > 1;
|
|
39
|
+
const browser = globalThis;
|
|
40
|
+
if (!browser.history) return false;
|
|
41
|
+
return browser.navigation?.canGoBack ?? browser.history.length > 1;
|
|
35
42
|
}
|
|
36
43
|
/**
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
* Goes back when there is somewhere to go back to in this app, and to `fallback` otherwise. A real
|
|
45
|
+
* link to `fallback`, so a modified click still opens it in a new tab.
|
|
46
|
+
*/
|
|
47
|
+
function BackLink({ fallback, label, children }) {
|
|
48
|
+
const router = useRouter();
|
|
49
|
+
const onClick = (event) => {
|
|
50
|
+
if (event.button !== 0 || event.metaKey || event.altKey || event.ctrlKey || event.shiftKey || !canGoBack()) return;
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
router.back();
|
|
53
|
+
};
|
|
54
|
+
return /* @__PURE__ */ jsx(NextLink, {
|
|
55
|
+
href: pathOf$1(fallback),
|
|
56
|
+
"aria-label": label,
|
|
57
|
+
onClick,
|
|
58
|
+
children
|
|
59
|
+
});
|
|
49
60
|
}
|
|
50
61
|
/** Navigates as soon as it renders, replacing the current entry. */
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
function Redirect({ to }) {
|
|
63
|
+
const router = useRouter();
|
|
64
|
+
const path = pathOf$1(to);
|
|
65
|
+
useEffect(() => router.replace(path), [router, path]);
|
|
66
|
+
return null;
|
|
56
67
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
68
|
+
function useNav() {
|
|
69
|
+
const router = useRouter();
|
|
70
|
+
return useMemo(() => ({
|
|
71
|
+
go: (to) => router.push(pathOf$1(to)),
|
|
72
|
+
replace: (to) => router.replace(pathOf$1(to)),
|
|
73
|
+
back: () => router.back(),
|
|
74
|
+
canGoBack,
|
|
75
|
+
preload: async (to) => router.prefetch(pathOf$1(to))
|
|
76
|
+
}), [router]);
|
|
66
77
|
}
|
|
67
78
|
/** The current path, such as `/orgs/acme`. */
|
|
68
|
-
|
|
69
|
-
|
|
79
|
+
function usePathname() {
|
|
80
|
+
return usePathname$1();
|
|
70
81
|
}
|
|
71
82
|
/** The current route's params. `href` is the route the screen is for, which types them. */
|
|
72
|
-
|
|
73
|
-
|
|
83
|
+
function useParams(_href) {
|
|
84
|
+
return useParams$1();
|
|
74
85
|
}
|
|
75
86
|
/** The query of the current URL. A key given twice is its first value, as on native. */
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
87
|
+
function useSearchParams() {
|
|
88
|
+
const search = useSearchParams$1();
|
|
89
|
+
return useMemo(() => Object.fromEntries([...search.keys()].map((key) => [key, search.get(key) ?? void 0])), [search]);
|
|
79
90
|
}
|
|
91
|
+
export { BackLink, Link, Redirect, Slot, SlotChildren, pathOf, useNav, useParams, usePathname, useSearchParams };
|
|
92
|
+
|
|
93
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.js","names":["pathOf","useNextPathname","useNextParams","useNextSearchParams"],"sources":["../src/runtime.tsx"],"sourcesContent":["\"use client\";\n\nimport NextLink from \"next/link\";\nimport {\n usePathname as useNextPathname,\n useParams as useNextParams,\n useRouter,\n useSearchParams as useNextSearchParams,\n} from \"next/navigation\";\nimport { createContext, type MouseEvent, type ReactNode, useContext, useEffect, useMemo } from \"react\";\nimport { type BackLinkProps, type Href, type LinkProps, type Nav, pathOf, type Routes, type SearchParams, type To } from \"@mismo/core/contract\";\n\nexport {\n pathOf,\n type BackLinkProps,\n type Href,\n type LinkProps,\n type Nav,\n type Routes,\n type SearchParams,\n type To,\n} from \"@mismo/core/contract\";\n\n/**\n * The page a Next.js layout was given. Next.js passes it as `children`, where other routers render\n * an outlet: the generated `layout.tsx` provides it, and `Slot` renders it, so a shared layout is\n * the same on every router.\n */\nconst Children = createContext<ReactNode>(null);\n\n/** For the generated `layout.tsx`: the page below the shared layout it renders. */\nexport function SlotChildren({ value, children }: { value: ReactNode; children: ReactNode }) {\n return <Children.Provider value={value}>{children}</Children.Provider>;\n}\n\n/** Where a layout renders its child route. */\nexport function Slot() {\n return <>{useContext(Children)}</>;\n}\n\n/** A real link, so it can be opened in a new tab. Next.js loads its page ahead as it shows. */\nexport function Link({ to, replace = false, label, children }: LinkProps) {\n return (\n <NextLink href={pathOf(to)} replace={replace} aria-label={label}>\n {children}\n </NextLink>\n );\n}\n\n/**\n * Whether there is an earlier entry in this site's history to go back to. The Navigation API says\n * so exactly; without it, any earlier entry counts.\n */\nfunction canGoBack(): boolean {\n const browser = globalThis as { navigation?: { canGoBack?: boolean }; history?: { length: number } };\n if (!browser.history) return false;\n return browser.navigation?.canGoBack ?? browser.history.length > 1;\n}\n\n/**\n * Goes back when there is somewhere to go back to in this app, and to `fallback` otherwise. A real\n * link to `fallback`, so a modified click still opens it in a new tab.\n */\nexport function BackLink({ fallback, label, children }: BackLinkProps) {\n const router = useRouter();\n\n const onClick = (event: MouseEvent) => {\n if (event.button !== 0 || event.metaKey || event.altKey || event.ctrlKey || event.shiftKey || !canGoBack()) return;\n event.preventDefault();\n router.back();\n };\n\n return (\n <NextLink href={pathOf(fallback)} aria-label={label} onClick={onClick}>\n {children}\n </NextLink>\n );\n}\n\n/** Navigates as soon as it renders, replacing the current entry. */\nexport function Redirect({ to }: { to: To }) {\n const router = useRouter();\n const path = pathOf(to);\n\n useEffect(() => router.replace(path), [router, path]);\n return null;\n}\n\nexport function useNav(): Nav {\n const router = useRouter();\n\n return useMemo(\n () => ({\n go: (to) => router.push(pathOf(to)),\n replace: (to) => router.replace(pathOf(to)),\n back: () => router.back(),\n canGoBack,\n preload: async (to) => router.prefetch(pathOf(to)),\n }),\n [router],\n );\n}\n\n/** The current path, such as `/orgs/acme`. */\nexport function usePathname(): string {\n return useNextPathname();\n}\n\n/** The current route's params. `href` is the route the screen is for, which types them. */\nexport function useParams<H extends Href>(_href: H): Routes[H] {\n return useNextParams() as unknown as Routes[H];\n}\n\n/** The query of the current URL. A key given twice is its first value, as on native. */\nexport function useSearchParams(): SearchParams {\n const search = useNextSearchParams();\n return useMemo(() => Object.fromEntries([...search.keys()].map((key) => [key, search.get(key) ?? undefined])), [search]);\n}\n"],"mappings":";;;;;;;;;;;;AA4BA,MAAM,WAAW,cAAyB,IAAI;;AAG9C,SAAgB,aAAa,EAAE,OAAO,YAAuD;CAC3F,OAAO,oBAAC,SAAS,UAAV;EAA0B;EAAQ;CAA4B,CAAA;AACvE;;AAGA,SAAgB,OAAO;CACrB,OAAO,oBAAA,UAAA,EAAA,UAAG,WAAW,QAAQ,EAAI,CAAA;AACnC;;AAGA,SAAgB,KAAK,EAAE,IAAI,UAAU,OAAO,OAAO,YAAuB;CACxE,OACE,oBAAC,UAAD;EAAU,MAAMA,SAAO,EAAE;EAAY;EAAS,cAAY;EACvD;CACO,CAAA;AAEd;;;;;AAMA,SAAS,YAAqB;CAC5B,MAAM,UAAU;CAChB,IAAI,CAAC,QAAQ,SAAS,OAAO;CAC7B,OAAO,QAAQ,YAAY,aAAa,QAAQ,QAAQ,SAAS;AACnE;;;;;AAMA,SAAgB,SAAS,EAAE,UAAU,OAAO,YAA2B;CACrE,MAAM,SAAS,UAAU;CAEzB,MAAM,WAAW,UAAsB;EACrC,IAAI,MAAM,WAAW,KAAK,MAAM,WAAW,MAAM,UAAU,MAAM,WAAW,MAAM,YAAY,CAAC,UAAU,GAAG;EAC5G,MAAM,eAAe;EACrB,OAAO,KAAK;CACd;CAEA,OACE,oBAAC,UAAD;EAAU,MAAMA,SAAO,QAAQ;EAAG,cAAY;EAAgB;EAC3D;CACO,CAAA;AAEd;;AAGA,SAAgB,SAAS,EAAE,MAAkB;CAC3C,MAAM,SAAS,UAAU;CACzB,MAAM,OAAOA,SAAO,EAAE;CAEtB,gBAAgB,OAAO,QAAQ,IAAI,GAAG,CAAC,QAAQ,IAAI,CAAC;CACpD,OAAO;AACT;AAEA,SAAgB,SAAc;CAC5B,MAAM,SAAS,UAAU;CAEzB,OAAO,eACE;EACL,KAAK,OAAO,OAAO,KAAKA,SAAO,EAAE,CAAC;EAClC,UAAU,OAAO,OAAO,QAAQA,SAAO,EAAE,CAAC;EAC1C,YAAY,OAAO,KAAK;EACxB;EACA,SAAS,OAAO,OAAO,OAAO,SAASA,SAAO,EAAE,CAAC;CACnD,IACA,CAAC,MAAM,CACT;AACF;;AAGA,SAAgB,cAAsB;CACpC,OAAOC,cAAgB;AACzB;;AAGA,SAAgB,UAA0B,OAAqB;CAC7D,OAAOC,YAAc;AACvB;;AAGA,SAAgB,kBAAgC;CAC9C,MAAM,SAASC,kBAAoB;CACnC,OAAO,cAAc,OAAO,YAAY,CAAC,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,KAAK,OAAO,IAAI,GAAG,KAAK,KAAA,CAAS,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC;AACzH"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mismo/next",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.1",
|
|
4
4
|
"description": "mismo for Next.js (App Router): run shared screens and routes in a Next.js app.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mismo",
|
|
@@ -10,6 +10,14 @@
|
|
|
10
10
|
"routes"
|
|
11
11
|
],
|
|
12
12
|
"homepage": "https://mismo.dev",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/KrakenWagen/mismo.git",
|
|
16
|
+
"directory": "packages/next"
|
|
17
|
+
},
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/KrakenWagen/mismo/issues"
|
|
20
|
+
},
|
|
13
21
|
"license": "MIT",
|
|
14
22
|
"type": "module",
|
|
15
23
|
"exports": {
|
|
@@ -35,7 +43,7 @@
|
|
|
35
43
|
"node": ">=22.18"
|
|
36
44
|
},
|
|
37
45
|
"dependencies": {
|
|
38
|
-
"@mismo/core": "^0.1.0-beta.
|
|
46
|
+
"@mismo/core": "^0.1.0-beta.1"
|
|
39
47
|
},
|
|
40
48
|
"peerDependencies": {
|
|
41
49
|
"next": ">=16 <17",
|
|
@@ -52,6 +60,6 @@
|
|
|
52
60
|
},
|
|
53
61
|
"scripts": {
|
|
54
62
|
"typecheck": "tsc -p tsconfig.json",
|
|
55
|
-
"build": "
|
|
63
|
+
"build": "tsdown"
|
|
56
64
|
}
|
|
57
65
|
}
|