Giter Site home page Giter Site logo

aspida / pathpida Goto Github PK

View Code? Open in Web Editor NEW
503.0 3.0 31.0 1 MB

TypeScript friendly internal link client for Next.js, Nuxt.js and Sapper.

Home Page: https://github.com/aspida/pathpida

License: MIT License

TypeScript 99.41% JavaScript 0.25% Vue 0.35%
typescript nextjs nuxtjs sapper

pathpida's Introduction

aspida


aspida


TypeScript friendly HTTP client wrapper for the browser and node.js.



Features

  • Path, URL query, header, body, and response can all specify the type
  • FormData / URLSearchParams content can also specify the type
  • HTTP client supports axios / fetch / node-fetch

vscode


Procedure

  1. Reproduce the endpoint directory structure in the "api" directory
  2. Export a Type alias named "Methods"
  3. Call "aspida" with npm scripts
  4. API type definition file "api/$api.ts" will be generated, so import the application and make an HTTP request

Getting Started

Installation (axios ver.)

  • Using npm:

    $ npm install aspida @aspida/axios axios
  • Using Yarn:

    $ yarn add aspida @aspida/axios axios

Create "api" directory

$ mkdir api

Type helper DefineMethods

DefineMethods<> is helper type for defining Methods. In earlier aspida, user shuold define without checking for API definition. If you already have aspida in your projects, you can easily use DefineMethods<> with surrounding your definition. It is just an option whether to use DefineMethods<>. Be aware of the limitation that DefineMethods<> can't detect extra meaningless properties.

Create an endpoint type definition file

  • GET: /v1/users/?limit={number}

  • POST: /v1/users

    api/v1/users/index.ts

    import type { DefineMethods } from "aspida";
    
    type User = {
      id: number;
      name: string;
    };
    
    export type Methods = DefineMethods<{
      get: {
        query?: {
          limit: number;
        };
    
        resBody: User[];
      };
    
      post: {
        reqBody: {
          name: string;
        };
    
        resBody: User;
        /**
         * reqHeaders(?): ...
         * reqFormat: ...
         * status: ...
         * resHeaders(?): ...
         * polymorph: [...]
         */
      };
    }>;
  • GET: /v1/users/${userId}

  • PUT: /v1/users/${userId}

    api/v1/users/_userId@number/index.ts

    Specify the type of path variable “userId” starting with underscore with “@number”
    If not specified with @, the default path variable type is "number | string"

    import type { DefineMethods } from "aspida";
    
    type User = {
      id: number;
      name: string;
    };
    
    export type Methods = DefineMethods<{
      get: {
        resBody: User;
      };
    
      put: {
        reqBody: {
          name: string;
        };
    
        resBody: User;
      };
    }>;

Build type definition file

package.json

{
  "scripts": {
    "api:build": "aspida"
  }
}
$ npm run api:build

> api/$api.ts was built successfully.

Make HTTP request from application

src/index.ts

import aspida from "@aspida/axios";
import api from "../api/$api";
(async () => {
  const userId = 0;
  const limit = 10;
  const client = api(aspida());

  await client.v1.users.post({ body: { name: "taro" } });

  const res = await client.v1.users.get({ query: { limit } });
  console.log(res);
  // req -> GET: /v1/users/?limit=10
  // res -> { status: 200, body: [{ id: 0, name: "taro" }], headers: {...} }

  const user = await client.v1.users._userId(userId).$get();
  console.log(user);
  // req -> GET: /v1/users/0
  // res -> { id: 0, name: "taro" }
})();

aspida official HTTP clients

Command Line Interface Options

Option Type Default Description
--config
-c
string "aspida.config.js" Specify the path to the configuration file.
--watch
-w
Enable watch mode.
Regenerate $api.ts according to the increase / decrease of the API endpoint file.
--version
-v
Print aspida version.

Options of aspida.config.js

Option Type Default Description
input string "api" Specifies the endpoint type definition root directory
baseURL string "" Specify baseURL of the request
trailingSlash boolean false Append / to the request URL
outputEachDir boolean false Generate $api.ts in each endpoint directory
outputMode (>=1.6.0) "all" | "normalOnly" | "aliasOnly" "all" Output either get or $get for compression

Node.js API

import { version, build, watch } from "aspida/dist/commands";

console.log(version()); // 0.x.y

build();
build("./app/aspida.config.js");
build({ input: "api1" });
build([
  { baseURL: "https://example.com/v1" },
  {
    input: "api2",
    baseURL: "https://example.com/v2",
    trailingSlash: true,
    outputEachDir: true,
    outputMode: "all",
  },
]);

watch();
watch("./app/aspida.config.js");
watch({ input: "api1" });
watch([
  { baseURL: "https://example.com/v1" },
  {
    input: "api2",
    baseURL: "https://example.com/v2",
    trailingSlash: true,
    outputEachDir: true,
    outputMode: "all",
  },
]);

Ecosystem

Tips

  1. Change the directory where type definition file is placed to other than "api"
  2. Serialize GET parameters manually
  3. POST with FormData
  4. POST with URLSearchParams
  5. Receive response with ArrayBuffer
  6. Define polymorphic request
  7. Define endpoints that contain special characters
  8. Import only some endpoints
  9. Retrieve endpoint URL string
  10. Reflect TSDoc comments

Change the directory where type definition file is placed to other than "api"

Create a configuration file at the root of the project

aspida.config.js

module.exports = { input: "src" };

Specify baseURL in configuration file

module.exports = { baseURL: "https://example.com/api" };

If you want to define multiple API endpoints, specify them in an array

module.exports = [{ input: "api1" }, { input: "api2", baseURL: "https://example.com/api" }];

Serialize GET parameters manually

aspida leaves GET parameter serialization to standard HTTP client behavior
If you want to serialize manually, you can use config object of HTTP client

src/index.ts

import axios from "axios";
import qs from "qs";
import aspida from "@aspida/axios";
import api from "../api/$api";
(async () => {
  const client = api(
    aspida(axios, { paramsSerializer: params => qs.stringify(params, { indices: false }) })
  );

  const users = await client.v1.users.$get({
    // config: { paramsSerializer: (params) => qs.stringify(params, { indices: false }) },
    query: { ids: [1, 2, 3] },
  });
  console.log(users);
  // req -> GET: /v1/users/?ids=1&ids=2&ids=3
  // res -> [{ id: 1, name: "taro1" }, { id: 2, name: "taro2" }, { id: 3, name: "taro3" }]
})();

POST with FormData

api/v1/users/index.ts

import type { DefineMethods } from "aspida";
import type { ReadStream } from "fs";

export type Methods = DefineMethods<{
  post: {
    reqFormat: FormData;

    reqBody: {
      name: string;
      icon: File | ReadStream;
    };

    resBody: {
      id: number;
      name: string;
    };
  };
}>;

src/index.ts

import aspida from "@aspida/axios";
import api from "../api/$api";
(async () => {
  const client = api(aspida());

  const user = await client.v1.users.$post({
    body: {
      name: "taro",
      icon: imageFile,
    },
  });
  console.log(user);
  // req -> POST: h/v1/users
  // res -> { id: 0, name: "taro" }
})();

Post in Node.js (>=1.6.0)

src/index.ts

import fs from "fs";
import aspida from "@aspida/axios";
import api from "../api/$api";
(async () => {
  const client = api(aspida());
  const fileName = "images/sample.jpg";
  const user = await client.v1.users.$post({
    body: {
      name: "taro",
      icon: fs.createReadStream(fileName),
    },
  });
  console.log(user);
  // req -> POST: h/v1/users
  // res -> { id: 0, name: "taro" }
})();

POST with URLSearchParams

api/v1/users/index.ts

import type { DefineMethods } from "aspida";

export type Methods = DefineMethods<{
  post: {
    reqFormat: URLSearchParams;

    reqBody: {
      name: string;
    };

    resBody: {
      id: number;
      name: string;
    };
  };
}>;

src/index.ts

import aspida from "@aspida/axios";
import api from "../api/$api";
(async () => {
  const client = api(aspida());

  const user = await client.v1.users.$post({ body: { name: "taro" } });
  console.log(user);
  // req -> POST: /v1/users
  // res -> { id: 0, name: "taro" }
})();

Receive response with ArrayBuffer

api/v1/users/index.ts

import type { DefineMethods } from "aspida";

export type Methods = DefineMethods<{
  get: {
    query: {
      name: string;
    };

    resBody: ArrayBuffer;
  };
}>;

src/index.ts

import aspida from "@aspida/axios";
import api from "../api/$api";
(async () => {
  const client = api(aspida());

  const user = await client.v1.users.$get({ query: { name: "taro" } });
  console.log(user);
  // req -> GET: /v1/users/?name=taro
  // res -> ArrayBuffer
})();

Define polymorphic request

api/users/index.ts

import type { DefineMethods } from 'aspida'

type User = {
  id: number
  name: string
}

export Methods = DefineMethods<{
  post: {
    // common properties
    reqFormat: FormData
    /**
     * query(?): ...
     * reqHeaders(?): ...
     * reqBody(?): ...
     * status: ...
     * resHeaders(?): ...
     * resBody(?): ...
     */
    polymorph: [
      // polymorphic types
      {
        reqBody: Omit<User, 'id'>
        resBody: User
        /**
         * query(?): ...
         * reqHeaders(?): ...
         * status: ...
         * resHeaders(?): ...
         */
      },
      {
        reqBody: Omit<User, 'id'>[]
        resBody: User[]
      }
    ]
  }
}>

src/index.ts

import aspida from "@aspida/axios";
import api from "../api/$api";
(async () => {
  const client = api(aspida());

  const user = await client.users.$post({ body: { name: "taro" } });
  console.log(user); // { id: 0, name: "taro" }

  const users = await client.users.$post({
    body: [{ name: "hanako" }, { name: "mario" }],
  });
  console.log(users); // [{ id: 1, name: "hanako" }, { id: 2, name: "mario" }]
})();

Define endpoints that contain special characters

Special characters are encoded as percent-encoding in the file name
Example ":" -> "%3A"

api/foo%3Abar/index.ts

import type { DefineMethods } from "aspida";

export type Methods = DefineMethods<{
  get: {
    resBody: string;
  };
}>;

api/users/_userId@number%3Aread/index.ts

import type { DefineMethods } from "aspida";

export type Methods = DefineMethods<{
  get: {
    resBody: User;
  };
}>;

With clients, "%3A" -> ":"

src/index.ts

import aspida from "@aspida/axios";
import api from "../api/$api";
(async () => {
  const client = api(aspida());

  const message = await client.foo_bar.$get();
  console.log(message);
  // req -> GET: /foo:bar

  const user = await client.users._userId_read(1).$get();
  console.log(user);
  // req -> GET: /users/1:read
})();

Import only some endpoints

If you don't need to use all of api/$api.ts , you can split them up and import only part of them
outputEachDir option generates $api.ts in each endpoint directory
$api.ts will not be generated under the directory containing the path variable

aspida.config.js

module.exports = { outputEachDir: true };

Import only $api.ts of the endpoint you want to use and put it into Object

src/index.ts

import aspida from "@aspida/axios";
import api0 from "../api/v1/foo/$api";
import api1 from "../api/v2/bar/$api";
(async () => {
  const aspidaClient = aspida();
  const client = {
    foo: api0(aspidaClient),
    bar: api1(aspidaClient),
  };

  const message = await client.bar._id(1).$get();
  // req -> GET: /v2/bar/1
})();

Retrieve endpoint URL string

src/index.ts

import aspida from "@aspida/axios";
import api from "../api/$api";
(async () => {
  const client = api(aspida());

  console.log(client.v1.users.$path());
  // /v1/users

  console.log(client.vi.users.$path({ query: { limit: 10 } }));
  // /v1/users?limit=10

  console.log(
    client.vi.users.$path({
      method: "post",
      query: { id: 1 },
    })
  );
  // /v1/users?id=1
})();

Reflect TSDoc comments

api/index.ts

import type { DefineMethods } from "aspida";

/**
 * root comment
 *
 * @remarks
 * root remarks comment
 */
export type Methods = DefineMethods<{
  /**
   * post method comment
   *
   * @remarks
   * post method remarks comment
   */
  post: {
    /** post query comment */
    query: { limit: number };

    /** post reqHeaders comment */
    reqHeaders: { token: string };

    reqFormat: FormData;
    /** post reqBody comment */
    reqBody: UserCreation;

    /**
     * post resBody comment1
     * post resBody comment2
     */
    resBody: User;
  };
}>;
$ npm run api:build

api/$api.ts

/**
 * root comment
 *
 * @remarks
 * root remarks comment
 */
const api = <T>({ baseURL, fetch }: AspidaClient<T>) => {
  return {
    /**
     * post method comment
     *
     * @remarks
     * post method remarks comment
     *
     * @param option.query - post query comment
     * @param option.headers - post reqHeaders comment
     * @param option.body - post reqBody comment
     * @returns post resBody comment1
     * post resBody comment2
     */
    $post: (option: {
      body: Methods0["post"]["reqBody"];
      query: Methods0["post"]["query"];
      headers: Methods0["post"]["reqHeaders"];
      config?: T;
    }) =>
      fetch<Methods0["post"]["resBody"]>(prefix, PATH0, POST, option)
        .json()
        .then(r => r.body),
  };
};

License

aspida is licensed under a MIT License.

pathpida's People

Contributors

dependabot[bot] avatar dqn avatar e9 avatar ishinojun avatar kqito avatar lumakernel avatar rikusen0335 avatar solufa avatar su8ru avatar yo-iwamoto avatar yutaura avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

pathpida's Issues

似た名前のファイルとフォルダがある場合にキーが重複する

Description

Next.jsでpathpidaのGenerate static files pathを利用させていただいております。

public/以下で、public/image-pngというフォルダと、public/image.pngというファイルのように、ハイフンかドット違いのアイテムが存在する場合、$path.ts内のstaticPathで、image_pngというキーが重複して生成されてしまいます。

export const staticPath = {
  image_png: {
    ....
  },
  image_png: '/image.png',
}

Environment

  • Package version:
    v0.13.0
  • OS:
    • Linux
    • Windows
    • macOS
  • Node.js version:
    v14.15.1
  • npm version:
    7.5.3

Additional context

Next.js v10.0.6

[Next.js] Generate `pathname` as actual URL being navigated to

Description

Currently, generating a page path looks like this:

pagesPath.asset._symbol(ticker.symbol).$url()

And the result is this:

{
  "hash": undefined,
  "pathname": "/asset/[symbol]",
  "query": {
    "symbol": "AAPL",
  },
}

Describe the solution you'd like

I want the pathname to return the actual URL (/asset/AAPL), rather than /asset/[symbol]

Say I'm navigating to /asset/[symbol], and symbol = 'AAPL'

pagesPath.asset._symbol('AAPL').$url()

I'd want the result to include the actual URL path that it's navigating to as the pathname:

{
  "hash": undefined,
  "pathname": "/asset/AAPL",
  "query": {
    "symbol": "AAPL",
  },
}

With Next.js 9.4+, we no longer need to use the /asset/[symbol] syntax. I want to know the actual URL, since I need this for redirect URLs and more.

Describe alternatives you've considered

I've considered a regex that replaces every instance of [some-string] with the corresponding value from query. But this isn't ideal.

You can see it for yourself here. Note that Stackblitz has its own routing, so if you want to see the URL change, you have to click Open in new window in the top right.

kebab-case parameters

Description

When I use query or slug in kebab case, the generated file has a syntax error, and I want to solve this problem by enclosing the description in quotation marks.

翻訳処理前の日本語 kebab case での query や slug を使用すると生成されるファイルが syntax error となるので、記述をクォーテーションなどで囲うことで解決したい。
src/pages/
└── test
    ├── [test-id]
    │   └── index.js
    └── index.js
install -Dv /dev/null src/pages/test/index.js
install -Dv /dev/null src/pages/test/[test-id]/index.js
node bin/index.js

current output

image

export const pagesPath = {
  "test": {
    _test-id: (test-id: string | number) => ({
      $url: (url?: { hash?: string }) => ({ pathname: '/test/[test-id]' as const, query: { test-id }, hash: url?.hash })
    }),
    $url: (url?: { hash?: string }) => ({ pathname: '/test' as const, hash: url?.hash })
  }
}

export type PagesPath = typeof pagesPath

modified output

export const pagesPath = {
  "test": {
    "_test-id": (test_id: string | number) => ({
      $url: (url?: { hash?: string }) => ({ pathname: '/test/[test-id]' as const, query: { 'test-id': test_id }, hash: url?.hash })
    }),
・・・

Describe the solution you'd like

  • properties are not renamed by enclosing it in quotation marks
  • variable are set to snake case

Additional context

pull request for the forked repository

Cannot find module 'next/dist/next-server/server/config'

Description

Next.js 11.1.0において 'next/dist/next-server/server/config' はエクスポートされていないため、

Cannot find module 'next/dist/next-server/server/config'

のエラーに遭遇します。

Environment

  • Package version:
    v0.15.4
  • OS:
    • Linux
    • Windows
    • macOS
  • Node.js version:
    14.17.4
  • yarn version:
    1.22.10

Additional context

Next.js 11.1.0

[Feature request] Include/exclude filename pattern

Description

Thanks a lot for this sick library.

I'm using Next.js with CSS Modules to style pages/components, and the pages directory looks like this.

pages
  |- index.tsx
  |- index.module.scss
  |- profile.tsx
  |- profile.module.scss
     ...

The problem here is that pathpida generates page typedefs for *.module.scss files too. It's not something fatal, but could leads to some typing mistakes as the editor actually suggests pagesPath.profile_module (this PAGE doesn't exist!) as well as pagesPath.profile (disired one, apparently) when you type pagesPath.prof....

Describe the solution you'd like

Include/exclude pattern.

yarn pathpida --include "*.tsx"
or
yarn pathpida --exclude "*.module.scss"

Describe alternatives you've considered

Adding underscore _ as prefix to all the *.module.scss files solves the problem itself, but is not very elegant imo.

Additional context

シングルクオーテーションなどが使えない

Description

ある程度制約を使う側に課している部分はあると思うんですが、とはいえ使えないにしてもエラー or (警告+無視)はでたほうがいいかなと…いい感じのサポート基準の落とし所は next.js に揃えるとかですかね。

frourio にもおそらく言えると思います、 create-frourio-app は一部そうとも言える箇所あり (EJS で埋め込んでいる箇所)

似たようなツールで JSON.stringify(str) で対処しているのは見かけました。何だったかは忘れました。現状のままだと prettier に怒られるという問題はあります。

JSON.stringify の single quote をエスケープしちゃえばよし、はおそらく正しい気がします。楽に対処するならこれですかね。

[Next.js] The path to page.tsx directly under the Route Groups directory is not generated

Description

The page.tsx directly under Route Groups directory is a Next.js routable page, but the URL is not generated by pathpida.

日本語

Route Groups直下のpage.tsxはNext.jsのルーティング可能なページとして扱われますが、pathpidaでURLが生成されません。

Route Groups
https://nextjs.org/docs/app/building-your-application/routing/route-groups

Environment

  • Package version: v0.22.0
  • OS:
    • Linux
    • Windows
    • macOS
  • Node.js version: v20.9.0
  • npm version: 10.1.0

Additional context

Minimal Reproduction Repository
https://github.com/Tim0401/pathpida-nextjs-route-group

スクリーンショット 2023-11-14 16 16 07

my-app/lib/$path.ts

const buildSuffix = (url?: {query?: Record<string, string>, hash?: string}) => {
  const query = url?.query;
  const hash = url?.hash;
  if (!query && !hash) return '';
  const search = query ? `?${new URLSearchParams(query)}` : '';
  return `${search}${hash ? `#${hash}` : ''}`;
};

export const pagesPath = {
  $url: (url?: { hash?: string }) => ({ pathname: '/' as const, hash: url?.hash, path: `/${buildSuffix(url)}` })
};

export type PagesPath = typeof pagesPath;

I expect the URL to /test to be generated in addition to the pagesPath.

Not working on [email protected] if src/app is not exist

Description

Using pathpida without src/app in v13.4 of Next.js causes an error.

$ yarn pathpida
yarn run v1.22.19
$ /Users/numb/foo/node_modules/.bin/pathpida
node:fs:1452
  handleErrorFromBinding(ctx);
  ^

Error: ENOENT: no such file or directory, scandir '/Users/numb/foo/src/app'
    at Object.readdirSync (node:fs:1452:3)
    at parseAppDir (/Users/numb/foo/node_modules/pathpida/dist/nextjs/parseAppDir.js:96:38)
    at createNextTemplate (/Users/numb/foo/node_modules/pathpida/dist/nextjs/createNextTemplate.js:34:41)
    at /Users/numb/foo/node_modules/pathpida/dist/buildTemplate.js:25:68
    at /Users/numb/foo/node_modules/pathpida/dist/cli.js:90:43
    at step (/Users/numb/foo/node_modules/pathpida/dist/cli.js:33:23)
    at Object.next (/Users/numb/foo/node_modules/pathpida/dist/cli.js:14:53)
    at fulfilled (/Users/numb/foo/node_modules/pathpida/dist/cli.js:5:58) {
  errno: -2,
  syscall: 'scandir',
  code: 'ENOENT',
  path: '/Users/numb/foo/src/app'
}

If src/app exists, it is not an error.

Environment

  • Package version:

    v0.20.1

  • OS:

    • Linux
    • Windows
    • macOS
  • Node.js version:
    v18.16.0

  • npm version:
    9.5.1

Additional context

[bug] URLエンコードされたパスを使用している際に Unexpected token が発生する

Description

URLエンコードされたパスを使用している際(pages/%E6%97%A5%E6%9C%AC%E8%AA%9E/index.tsx => /日本語)に、生成されるファイルが以下のようになり Unexpected token でエラーとなります。

export const pagesPath = {
  %E6%97%A5%E6%9C%AC%E8%AA%9E: {
    $url: (url?: { hash?: string }) => ({ pathname: '/%E6%97%A5%E6%9C%AC%E8%AA%9E' as const, hash: url?.hash }),
  },
};

Expected

export const pagesPath = {
  '%E6%97%A5%E6%9C%AC%E8%AA%9E': {
    $url: (url?: { hash?: string }) => ({ pathname: '/%E6%97%A5%E6%9C%AC%E8%AA%9E' as const, hash: url?.hash }),
  },
};

Environment

  • Package version: v0.18.0
  • Using with: Next.js
  • OS:
    • Linux
  • Node.js version: v14.18.2
  • npm version: v8.3.2

[Feature request] Add an option to use Type-Only Imports

Description

If we use the TypeScript compiler option "importsNotUsedAsValues": "error", imports that are not type-only will result in an error.

Describe the solution you'd like

Adding an option to use Type-Only Imports like --useTypeOnlyImports.

Describe alternatives you've considered

  • Add // @ts-nocheck to the top of the generated $path.ts file
  • Replace import to import type by using script.

Additional context

Doesn't work with NX

Description

We are using NX and our nextjs folder is under root/apps/app-name/pages/page-name.tsx. Not sure if there is a way to use pathpida here.

Describe the solution you'd like

Allow CLI option to pass path to the pages folder which can default to the current logic that pathpaida supports

Describe alternatives you've considered

Just loop through all folders and look for pages folder? not sure it's a good idea.

Additional context

I think this line needs to accept cli option https://github.com/aspida/pathpida/blob/master/src/getConfig.ts#L49

Next.js App RouterのDynamic Routesに対するエラー

Description

Next.jsのApp Routerを利用し、Dynamic Routesに対するリンクを作成しようとすると、以下のエラーが発生します。

Error: Dynamic href `/items/[itemId]` found in <Link> while using the `/app` router, this is not supported. Read more: https://nextjs.org/docs/messages/app-dir-dynamic-href

Environment

  • Package version: v0.20.1
  • OS:
    • Linux
    • Windows
    • macOS
  • Node.js version: v18.15.0
  • npm version: 9.6.1

Additional context

以下のサンプルコードで、問題を再現して確認いただけます。

立ち上げた上で http://localhost:3000/items にアクセスしてみてください。

https://github.com/hiterm/2023-06-05-pathpida-nextjs-app-router-example

Get url string of pagesPath

Description

Hi, I many times we need get final url of pagesPath
Example:

const urlObject= pagesPath.dashboard.project._projectId("abc").$url();
// return { pathname: '/dashboard/project/[projectId]' as const, query: { projectId }, hash: url?.hash }
// But many times we will need the final string, like use in window.open
window.open("Need an url string here, not object")

Describe the solution you'd like

This will be good, i think
pagesPath.dashboard.project._projectId("abc").$urlString;

Additional context

image

support static dir

Description

Describe the solution you'd like

Describe alternatives you've considered

Additional context

Ignore dotfiles or files ignored by .gitignore

Description

staticPath.DS_Storeなどのファイルが混ざらないようにして欲しいです。

Describe the solution you'd like

Solution1: ファイル名が.からはじまる場合に含めない

e.g. ka2n@680893a

Solution2: .gitignoreを読み込む

https://github.com/kaelzhang/node-ignorehttps://github.com/npm/ignore-walk などを利用し、.gitignoreを使用してファイルを無視することもできそうです。
e.g. ka2n@967a75f

モノレポに変えてFWごとに分割

Description

関係ないFWの依存をインストールさせているのが良くない

Describe the solution you'd like

Describe alternatives you've considered

Additional context

add optional query

Description

Describe the solution you'd like

Describe alternatives you've considered

Additional context

staticPath の basepath オプションへのサポート

Pathpida を create-frourio-app に組み込む、というロードマップがあるかと思うのですが、試しにフロントエンドを GitHub Pages で basepath を使ってデプロイした際に、

  const { basePath } = useRouter()

// ...
          <img
            src={`${basePath}/vercel.svg`}
            alt="Vercel Logo"
            className={styles.logo}
          />

と、自分で basePath をおいて上げる必要がありました。

このとき、もし pathpida を現在の仕様で入れると、

  const { basePath } = useRouter()

// ...
          <img
            src={`${basePath}/${staticPath.vercel_svg}`}
            alt="Vercel Logo"
            className={styles.logo}
          />

となります。
好みの範囲かもしれませんが、型安全に、プロパティが存在することと、static path が存在することを同一にするというのがゴールだとすれば、 staticPath は事前に config から basePath を取って含めておいてくれたほうがいいかもしれない、ということを思いました。つまり、basepath があっても以下のように書けたほうが嬉しいかもしれない、ということです。

// ...
          <img
            src={staticPath.vercel_svg}
            alt="Vercel Logo"
            className={styles.logo}
          />

もしくは staticPath.vercel_svg.$full_path()staticPath.vercel_svg.$path() など… 名付けが難しいですしこれは微妙かもしれません。

Not working properly with Next.js Route Group

Description

When configuring the following directory structure in the app router, the object keys in $path.ts conflict. (An object literal cannot have multiple properties with the same name.)

└── app/
    ├── (withHeader)/
    │   ├── layout.tsx
    │   └── items/
    │       └── page.tsx
    └── items/
        └── [itemId]/
            └── page.tsx

Environment

  • Package version: v0.22.0
  • OS:
    • Linux
    • Windows
    • macOS
  • Node.js version: v20.8.0
  • npm version: v10.1.0

Additional context

output

  "items": {
    $url: (url?: { hash?: string }) => ({ pathname: '/items' as const, hash: url?.hash, path: `/items${buildSuffix(url)}` })
  },
  "items": {
    _itemId: (itemId: string | number) => ({
      $url: (url?: { hash?: string }) => ({ pathname: '/items/[itemId]' as const, query: { itemId }, hash: url?.hash, path: `/items/${itemId}${buildSuffix(url)}` })
    })
  },

Support `pageExtensions` for Next.js

Description

Next.js can use pageExtensions to set the extension. ref: https://nextjs.org/docs/api-reference/next.config.js/custom-page-extensions.
If you use page.tsx for this extension, pathpida will generate the wrong type definition file.

file expect actual
pages/hello.page.tsx pagesPath.hello pagesPath.hello_page
pages/post/[id].page.tsx pagesPath.post._id pagesPath.post.[id]_page

Environment

  • Package version: v0.16.0
  • OS:
    • Linux
    • Windows
    • macOS
  • Node.js version:
    • v14.17.3
  • yarn version:
    • 1.22.10

Additional context

Here is a repository that reproduces the bug.
https://github.com/odan-sandbox/next-pathpida-page-extensions-sandbox

want to be able to set the directory to output the `$path.ts` file.

sorry. I am not good at English, so I will draft in Japanese.

Description

現状、utilsとlibの二つのディレクトリが存在するときに優先的にutilsに$pathファイルが出力される。

Describe the solution you'd like

$path.tsの出力先を.pathpida.config.jsもしくは、--outputオプションで設定できるようにしてほしい。

Describe alternatives you've considered

libとutilsの二つのディレクトリが存在しないようにディレクトリ構成を検討しました。

例)lib/utils/**, lib/$path.ts

gitのコンフリクト対策

Description

$path.tsでQueryに通し番号が振られますがgitでコンフリクトしやすいです。

インポートパスからハッシュ値や型名に使える文字列に変換することで差分を局所化できないでしょうか。

[path parameter] interface ... extends ... が利用できない

export interface Query {
  id: number
}

(上の例はいずれにせよ ParsedUrlQueryInput に代入不可と言われてしまう)

import { ParsedUrlQueryInput } from 'querystring'

export interface Query extends ParsedUrlQueryInput {
  id: number
}

利用できても良いかな、ぐらいに思った。

依存関係のNext.jsのアップデートをお願いします。

Description

内部で依存しているnextのバージョンが^10.2.2となっており、
Next.js側で使われているライブラリ(例えばpostcss)のバージョンにsecurity updateの通知がきます。

お手数ですが、パッケージのアップデートをお願いできればと思います。

support Sapper

Description

When will Svelte-kit be released?

Describe the solution you'd like

Describe alternatives you've considered

Additional context

I want to use GatsbyJS as well.

Description

I love this OSS. I always create $path.ts files by hand to use the same in GatsbyJS, which I use often.
GatsbyJS also uses pages/ to generate paths.
I find this similarity to the framework I'm targeting with pathpida!
I would be very happy if you would consider it.

Describe the solution you'd like

I want to make it available in GatsbyJS.

Describe alternatives you've considered

Additional context

Make it work even if the `pages` directory is below `src` directory

Description

Next.js allows you to create a pages directory below the src directory, but this library will not work under those circumstances.

The example solution

In getConfig.ts

import fs from 'fs'
import path from 'path'

export type Config = {
  type: 'nextjs' | 'nuxtjs'
  input: string
  staticDir?: string
  output: string
  trailingSlash?: boolean
}

export default (enableStatic: boolean, dir = process.cwd()): Config => {
  const nuxtjsPath = path.join(dir, 'nuxt.config.js')
  const nuxttsPath = path.join(dir, 'nuxt.config.ts')
  const nextLibPath = path.join(dir, 'lib')
  const nextUtilsPath = path.join(dir, 'utils')
- const inputPath = path.posix.join(dir, 'pages')
+ const pagesPath = path.posix.join(dir, 'pages')
+ const pagesBelowSrcPath = path.posix.join(dir, 'src', 'pages')
  const type = fs.existsSync(nuxtjsPath) || fs.existsSync(nuxttsPath) ? 'nuxtjs' : 'nextjs'
  if (type === 'nextjs') {
    const output = fs.existsSync(nextUtilsPath) ? nextUtilsPath : nextLibPath
    if (!fs.existsSync(output)) fs.mkdirSync(output)

    return {
      type: 'nextjs',
-     input: inputPath,
+     input: fs.existsSync(pagesPath) ? pagesPath : pagesBelowSrcPath,
      staticDir: enableStatic ? path.posix.join(dir, 'public') : undefined,
      output
    }
  } else {
    const config = fs.existsSync(nuxtjsPath)
      ? require(nuxtjsPath)
      : { trailingSlash: /trailingSlash: true/.test(fs.readFileSync(nuxttsPath, 'utf8')) }

    return {
      type: 'nuxtjs',
-     input: inputPath,
+     input: pagesPath,
      staticDir: enableStatic ? path.posix.join(dir, 'static') : undefined,
      output: path.posix.join(dir, 'plugins'),
      trailingSlash: config.trailingSlash
    }
  }
}

Remarks

I usually use Next.js and haven't used Nuxt.js before, so I don't know if the same circumstance happens with Nuxt.js

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.