@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 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
- npm i @mismo/core @mismo/next
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
- /** 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;
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
- import { output } from "./output.js";
5
+ // src/config.ts
5
6
  /**
6
- * Next.js's config for an app on mismo: writes the app's routes into the App Router's folder when
7
- * Next.js loads its config (before it reads the folder, for `next dev` and `next build` alike), and
8
- * again in dev as route files come and go; and points `@mismo/core` at Next.js's runtime, for Turbopack
9
- * and for webpack.
10
- *
11
- * export default withMismo({ … }, { config: "../shared/routes.config.ts", outDir: "app", types: "app/routes.gen.ts" });
12
- */
13
- export function withMismo(nextConfig, { config, outDir, types, cwd = process.cwd() }) {
14
- const next = nextConfig;
15
- watchAppRoutes({ config, output: output({ outDir, types }), cwd });
16
- // From the app, which depends on this adapter: the shared package importing `@mismo/core` may not.
17
- const runtime = createRequire(path.join(cwd, "package.json")).resolve("@mismo/next/runtime");
18
- // Turbopack takes a path relative to the app, webpack an absolute one.
19
- const relative = `./${path.relative(cwd, runtime).split(path.sep).join("/")}`;
20
- const previous = next.webpack;
21
- const changed = {
22
- turbopack: {
23
- ...next.turbopack,
24
- resolveAlias: { ...next.turbopack?.resolveAlias, "@mismo/core": relative },
25
- },
26
- webpack: (webpackConfig, context) => {
27
- const config = previous ? previous(webpackConfig, context) : webpackConfig;
28
- config.resolve = { ...config.resolve, alias: { ...config.resolve?.alias, "@mismo/core$": runtime } };
29
- return config;
30
- },
31
- };
32
- return { ...next, ...changed };
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
- name: string;
4
- rules: import("@mismo/core/generate").RouteRules;
5
- output: typeof output;
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
@@ -1,6 +1,10 @@
1
1
  import { output, rules } from "./output.js";
2
- export const next = {
3
- name: "Next.js",
4
- rules,
5
- output,
2
+ // src/index.ts
3
+ const next = {
4
+ name: "Next.js",
5
+ rules,
6
+ output
6
7
  };
8
+ export { next };
9
+
10
+ //# sourceMappingURL=index.js.map
@@ -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 { type RouteOutput, type RouteRules } from "@mismo/core/adapter";
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). The shared screens use hooks, so each file is a client component. A page renders in
8
- * a Suspense boundary, which Next.js needs around `useSearchParams` to prerender it; a layout hands
9
- * the page Next.js gives it (`children`) to `Slot`.
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
- outDir: string;
13
- types: string;
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
- export const rules = {
5
- router: "Next.js",
6
- target: "web",
7
- check(routes) {
8
- // It refuses two pages that resolve to one URL, such as `(a)/about` and `(b)/about`.
9
- const problems = oneUrlPerPage(routes, "Next.js");
10
- // Folder names it reads as something other than a route, without an error.
11
- for (const { route } of placesOf(routes)) {
12
- for (const segment of route.segments) {
13
- if (isParam(segment))
14
- continue;
15
- if (segment.startsWith("_"))
16
- problems.push(`${route.id}: "${segment}" starts with "_", a private folder to Next.js, left out of its routes.`);
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
- * Next.js's files, for a web app on the App Router: the routes folder's layout in `app/`, a
28
- * `page.tsx` per page and a `layout.tsx` per layout, next to the app's own (its root `layout.tsx`
29
- * among them). The shared screens use hooks, so each file is a client component. A page renders in
30
- * a Suspense boundary, which Next.js needs around `useSearchParams` to prerender it; a layout hands
31
- * the page Next.js gives it (`children`) to `Slot`.
32
- */
33
- export function output({ outDir, types }) {
34
- return {
35
- name: "next",
36
- target: "web",
37
- outDir,
38
- types,
39
- rules,
40
- runtime: "@mismo/next/runtime",
41
- emit(routes, context) {
42
- const files = new Map();
43
- for (const { route, folder } of placesOf(routes)) {
44
- const segments = [...folder, ...route.segments].map(toNext);
45
- const dirs = route.layout || segments.at(-1) !== "index" ? segments : segments.slice(0, -1);
46
- const file = [...dirs, route.layout ? "layout.tsx" : "page.tsx"].join("/");
47
- const from = importPath(path.join(context.outDir, file), route.file);
48
- files.set(file, route.layout ? layoutFile(from) : pageFile(from));
49
- }
50
- return files;
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) => (isParam(segment) ? `[${segment.slice(1)}]` : segment);
56
- const pageFile = (from) => `${GENERATED}"use client";
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 MismoPage() {
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 layoutFile = (from) => `${GENERATED}"use client";
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 MismoLayout({ children }: { children: ReactNode }) {
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 { type ReactNode } from "react";
2
- import { type BackLinkProps, type Href, type LinkProps, type Nav, type Routes, type SearchParams, type To } from "@mismo/core/contract";
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
- value: ReactNode;
7
- children: ReactNode;
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
- to: To;
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 { usePathname as useNextPathname, useParams as useNextParams, useRouter, useSearchParams as useNextSearchParams, } from "next/navigation";
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
- export { pathOf, } from "@mismo/core/contract";
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
- * 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
- */
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
- export function SlotChildren({ value, children }) {
16
- return _jsx(Children.Provider, { value: value, children: children });
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
- export function Slot() {
20
- return _jsx(_Fragment, { children: useContext(Children) });
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
- export function Link({ to, replace = false, label, children }) {
24
- return (_jsx(NextLink, { href: pathOf(to), replace: replace, "aria-label": label, children: children }));
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
- * Whether there is an earlier entry in this site's history to go back to. The Navigation API says
28
- * so exactly; without it, any earlier entry counts.
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
- const browser = globalThis;
32
- if (!browser.history)
33
- return false;
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
- * Goes back when there is somewhere to go back to in this app, and to `fallback` otherwise. A real
38
- * link to `fallback`, so a modified click still opens it in a new tab.
39
- */
40
- export function BackLink({ fallback, label, children }) {
41
- const router = useRouter();
42
- const onClick = (event) => {
43
- if (event.button !== 0 || event.metaKey || event.altKey || event.ctrlKey || event.shiftKey || !canGoBack())
44
- return;
45
- event.preventDefault();
46
- router.back();
47
- };
48
- return (_jsx(NextLink, { href: pathOf(fallback), "aria-label": label, onClick: onClick, children: children }));
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
- export function Redirect({ to }) {
52
- const router = useRouter();
53
- const path = pathOf(to);
54
- useEffect(() => router.replace(path), [router, path]);
55
- return null;
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
- export function useNav() {
58
- const router = useRouter();
59
- return useMemo(() => ({
60
- go: (to) => router.push(pathOf(to)),
61
- replace: (to) => router.replace(pathOf(to)),
62
- back: () => router.back(),
63
- canGoBack,
64
- preload: async (to) => router.prefetch(pathOf(to)),
65
- }), [router]);
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
- export function usePathname() {
69
- return useNextPathname();
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
- export function useParams(_href) {
73
- return useNextParams();
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
- export function useSearchParams() {
77
- const search = useNextSearchParams();
78
- return useMemo(() => Object.fromEntries([...search.keys()].map((key) => [key, search.get(key) ?? undefined])), [search]);
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.0",
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.0"
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": "rm -rf dist && tsc -p tsconfig.build.json && node ../scripts/fix-declarations.mjs dist"
63
+ "build": "tsdown"
56
64
  }
57
65
  }