Skip to content

Commit

Permalink
app directory (#90)
Browse files Browse the repository at this point in the history
  • Loading branch information
tatethurston authored Oct 29, 2022
1 parent b7701f3 commit 679174b
Show file tree
Hide file tree
Showing 8 changed files with 429 additions and 77 deletions.
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@

## 0.1.6

- `nextjs-routes` now accepts path strings for static routes:
- Support [Next 13 app (beta)](https://nextjs.org/docs/advanced-features/custom-app)

- Accept path strings for static routes:

```tsx
<Link href="/foo">Foo</Link>
```

Thanks [@MariaSolOs](https://github.com/MariaSolOs) for the contribution!

- `nextjs-routes` now uses function overloads for `Link` and `router.push` and `router.replace`. This yields better hints for typos in pathnames:
- Use function overloads for `Link` and `router.push` and `router.replace`. This yields better hints for typos in pathnames:

```tsx
<Link href={{ pathname: "/foosy/[foo]" }}>Foo</Link>
Expand Down
2 changes: 1 addition & 1 deletion e2e/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion public.package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nextjs-routes",
"version": "0.1.5",
"version": "0.1.6",
"description": "Type safe routing for Next.js",
"license": "MIT",
"author": "Tate <[email protected]>",
Expand Down
284 changes: 284 additions & 0 deletions src/__snapshots__/core.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,148 @@ declare module "next/router" {
]
`;
exports[`route generation dedupes 1`] = `
[
[
"nextjs-routes.d.ts",
"// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
// This file will be automatically regenerated when your Next.js server is running.
/* eslint-disable */
// prettier-ignore
declare module "nextjs-routes" {
export type Route =
| { pathname: "/"; query?: Query | undefined };
type Query<Params = {}> = Params & {
[key: string]: string | string[] | undefined;
};
type QueryForPathname = {
[K in Route as K["pathname"]]: Exclude<K["query"], undefined>;
};
export type RoutedQuery<P extends Route["pathname"]> = QueryForPathname[P];
export type Locale = undefined;
/**
* A typesafe utility function for generating paths in your application.
*
* route({ pathname: "/foos/[foo]", query: { foo: "bar" }}) will produce "/foos/bar".
*/
export declare function route(r: Route): string;
}
// prettier-ignore
declare module "next/link" {
import type { Route } from "nextjs-routes";
import type { LinkProps as NextLinkProps } from "next/dist/client/link";
import type { PropsWithChildren, MouseEventHandler } from "react";
export * from "next/dist/client/link";
type Query = { query?: { [key: string]: string | string[] | undefined } };
type StaticRoute = Exclude<Route, { query: any }>["pathname"];
export interface LinkProps<Href extends Route | Query = Route | Query>
extends Omit<NextLinkProps, "href" | "locale"> {
href: Href;
locale?: false;
}
type LinkReactElement = DetailedReactHTMLElement<
{
onMouseEnter?: MouseEventHandler<Element> | undefined;
onClick: MouseEventHandler;
href?: string | undefined;
ref?: any;
},
HTMLElement
>;
declare function Link(
props: PropsWithChildren<LinkProps<Route>>
): LinkReactElement;
declare function Link(
props: PropsWithChildren<LinkProps<StaticRoute>>
): LinkReactElement;
declare function Link(
props: PropsWithChildren<LinkProps<Query>>
): LinkReactElement;
export default Link;
}
// prettier-ignore
declare module "next/router" {
import type { Locale, Route, RoutedQuery } from "nextjs-routes";
import type { NextRouter as Router } from "next/dist/client/router";
export * from "next/dist/client/router";
export { default } from "next/dist/client/router";
type NextTransitionOptions = NonNullable<Parameters<Router["push"]>[2]>;
type StaticRoute = Exclude<Route, { query: any }>["pathname"];
interface TransitionOptions extends Omit<NextTransitionOptions, "locale"> {
locale?: false;
};
export interface NextRouter<P extends Route["pathname"] = Route["pathname"]>
extends Omit<
Router,
| "push"
| "replace"
| "locale"
| "locales"
| "defaultLocale"
| "domainLocales"
> {
defaultLocale?: undefined;
domainLocales?: undefined;
locale?: Locale;
locales?: undefined;
pathname: P;
push(
url: Route,
as?: string,
options?: TransitionOptions
): Promise<boolean>;
push(
url: StaticRoute,
as?: string,
options?: TransitionOptions
): Promise<boolean>;
push(
url: { query: { [key: string]: string | string[] | undefined } },
as?: string,
options?: TransitionOptions
): Promise<boolean>;
query: RoutedQuery<P>;
replace(
url: Route,
as?: string,
options?: TransitionOptions
): Promise<boolean>;
replace(
url: StaticRoute,
as?: string,
options?: TransitionOptions
): Promise<boolean>;
replace(
url: { query: { [key: string]: string | string[] | undefined } },
as?: string,
options?: TransitionOptions
): Promise<boolean>;
route: P;
}
export function useRouter<P extends Route["pathname"]>(): NextRouter<P>;
}
",
],
]
`;
exports[`route generation no routes 1`] = `
[
[
Expand Down Expand Up @@ -732,6 +874,148 @@ declare module "next/router" {
]
`;
exports[`route generation transforms windows paths 1`] = `
[
[
"nextjs-routes.d.ts",
"// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
// This file will be automatically regenerated when your Next.js server is running.
/* eslint-disable */
// prettier-ignore
declare module "nextjs-routes" {
export type Route =
| { pathname: "src/pages/[foo]/bar"; query: Query<{ "foo": string }> };
type Query<Params = {}> = Params & {
[key: string]: string | string[] | undefined;
};
type QueryForPathname = {
[K in Route as K["pathname"]]: Exclude<K["query"], undefined>;
};
export type RoutedQuery<P extends Route["pathname"]> = QueryForPathname[P];
export type Locale = undefined;
/**
* A typesafe utility function for generating paths in your application.
*
* route({ pathname: "/foos/[foo]", query: { foo: "bar" }}) will produce "/foos/bar".
*/
export declare function route(r: Route): string;
}
// prettier-ignore
declare module "next/link" {
import type { Route } from "nextjs-routes";
import type { LinkProps as NextLinkProps } from "next/dist/client/link";
import type { PropsWithChildren, MouseEventHandler } from "react";
export * from "next/dist/client/link";
type Query = { query?: { [key: string]: string | string[] | undefined } };
type StaticRoute = Exclude<Route, { query: any }>["pathname"];
export interface LinkProps<Href extends Route | Query = Route | Query>
extends Omit<NextLinkProps, "href" | "locale"> {
href: Href;
locale?: false;
}
type LinkReactElement = DetailedReactHTMLElement<
{
onMouseEnter?: MouseEventHandler<Element> | undefined;
onClick: MouseEventHandler;
href?: string | undefined;
ref?: any;
},
HTMLElement
>;
declare function Link(
props: PropsWithChildren<LinkProps<Route>>
): LinkReactElement;
declare function Link(
props: PropsWithChildren<LinkProps<StaticRoute>>
): LinkReactElement;
declare function Link(
props: PropsWithChildren<LinkProps<Query>>
): LinkReactElement;
export default Link;
}
// prettier-ignore
declare module "next/router" {
import type { Locale, Route, RoutedQuery } from "nextjs-routes";
import type { NextRouter as Router } from "next/dist/client/router";
export * from "next/dist/client/router";
export { default } from "next/dist/client/router";
type NextTransitionOptions = NonNullable<Parameters<Router["push"]>[2]>;
type StaticRoute = Exclude<Route, { query: any }>["pathname"];
interface TransitionOptions extends Omit<NextTransitionOptions, "locale"> {
locale?: false;
};
export interface NextRouter<P extends Route["pathname"] = Route["pathname"]>
extends Omit<
Router,
| "push"
| "replace"
| "locale"
| "locales"
| "defaultLocale"
| "domainLocales"
> {
defaultLocale?: undefined;
domainLocales?: undefined;
locale?: Locale;
locales?: undefined;
pathname: P;
push(
url: Route,
as?: string,
options?: TransitionOptions
): Promise<boolean>;
push(
url: StaticRoute,
as?: string,
options?: TransitionOptions
): Promise<boolean>;
push(
url: { query: { [key: string]: string | string[] | undefined } },
as?: string,
options?: TransitionOptions
): Promise<boolean>;
query: RoutedQuery<P>;
replace(
url: Route,
as?: string,
options?: TransitionOptions
): Promise<boolean>;
replace(
url: StaticRoute,
as?: string,
options?: TransitionOptions
): Promise<boolean>;
replace(
url: { query: { [key: string]: string | string[] | undefined } },
as?: string,
options?: TransitionOptions
): Promise<boolean>;
route: P;
}
export function useRouter<P extends Route["pathname"]>(): NextRouter<P>;
}
",
],
]
`;
exports[`route generation typescript 1`] = `
[
[
Expand Down
16 changes: 9 additions & 7 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import type { NextConfig } from "next";
import { join } from "path";
import type { Configuration, WebpackPluginInstance } from "webpack";
import { getPagesDirectory } from "./utils.js";
import { getAppDirectory, getPagesDirectory } from "./utils.js";
import { watch } from "chokidar";
import { logger, writeNextjsRoutes } from "./core.js";

Expand All @@ -32,16 +32,17 @@ class NextJSRoutesPlugin implements WebpackPluginInstance {
) {}

apply() {
const pagesDirectory = getPagesDirectory();
if (pagesDirectory) {
const watchDirs = [getPagesDirectory(), getAppDirectory()]
.filter((x) => x != undefined)
.map((dir) => join(process.cwd(), dir as string));

if (watchDirs.length > 0) {
const options = {
...this.config,
...this.options,
pagesDirectory,
};
if (this.options.watch) {
const dir = join(process.cwd(), pagesDirectory);
const watcher = watch(dir, {
const watcher = watch(watchDirs, {
persistent: true,
});
// batch changes
Expand All @@ -51,10 +52,11 @@ class NextJSRoutesPlugin implements WebpackPluginInstance {
writeNextjsRoutes(options);
}
} else {
logger.error(`Could not find a Next.js pages directory. Expected to find either pages(1) or src/pages(2).
logger.error(`Could not find a Next.js pages directory. Expected to find either 'pages' (1), 'src/pages' (2), or 'app' (3) in your project root.
1. https://nextjs.org/docs/basic-features/pages
2. https://nextjs.org/docs/advanced-features/src-directory
3. https://nextjs.org/blog/next-13#app-directory-beta
`);
}
}
Expand Down
Loading

0 comments on commit 679174b

Please sign in to comment.