Closed1
next.jsのファイル一覧からUrlObject形式の型生成するやつ作る
- next.jsでファイル一覧から型つくりたかった
- パスを生成するライブラリに頼らずなるべくnext.js wayを補助する方向にしたかった
- template literal typeでやるのはちょっと難しくて挫折した
-
/
を含まないstring型みたいなのを作れずパズルが終了した
-
- UrlObject形式なら割と簡易に作れそうな感じだったのでそっちにした
installation
$ yarn add recursive-readdir
生成スクリプト
// bin/typegen.js
const recursiveReaddir = require("recursive-readdir")
const {getRouteRegex}= require("next/dist/next-server/lib/router/utils/route-regex")
const path = require("path")
const convertToUrlObject = (str) => {
const r = getRouteRegex(str)
const queries = Object.entries(r.groups).map(([name, opts]) => {
const queryKey = opts.optional ? `${name}?` : name
const valueKey = opts.repeat ? `ReadonlyArray<string> | ReadonlyArray<number>` :`string | number`
return `${queryKey} : ${valueKey}`
})
const q = queries.length > 0 ? `query: { ${queries.join(",")} }` : null
const typeValue = [ `pathname: "/${str}"`, q ].filter(x => !!x).join(" , ")
return `UrlObject & { ${typeValue} }`
}
const execute = async (sourceDir) => {
const files = await recursiveReaddir(path.join(sourceDir,"pages"))
const baseDir = path.normalize(sourceDir)
const regexp = new RegExp(`^${baseDir}/pages/`)
const pathnames = files
.map(file => {
const pathname = file
.replace(regexp, "") // remove basedir
.replace(/.[jt]sx?$/, "") // remove exteension
return pathname
})
.filter(file => !/^_.+/.test(file)) // filter _app / _document
const paths = pathnames.map(pathname => {
const paths = pathname.split("/")
if (paths[paths.length - 1] === "index") { // /index -> /
const omitIndex = paths.slice(0, paths.length - 1).join("/")
return omitIndex
}
return pathname
})
const q = paths.map(str => convertToUrlObject(str))
.join("\n | ")
const types = [
`// auto-generated`,
`type AppUrlObject = ${q}`,
].join("\n")
console.log(types)
}
execute("./")
// srcディレクトリに配置していたらこう
// execute("./src")
実行
これを実行すると、こういうのが出てくる
% node bin/typegen.js
// auto-generated
type AppUrlObject = UrlObject & { pathname: "/" }
| UrlObject & { pathname: "/users" }
| UrlObject & { pathname: "/api/hello" }
| UrlObject & { pathname: "/api/rest/[...slug]" , query: { slug : ReadonlyArray<string> | ReadonlyArray<number> } }
| UrlObject & { pathname: "/api/rest2/[[...slug]]" , query: { slug? : ReadonlyArray<string> | ReadonlyArray<number> } }
| UrlObject & { pathname: "/users/[user_id]" , query: { user_id : string | number } }
| UrlObject & { pathname: "/users/[user_id]/posts" , query: { user_id : string | number } }
| UrlObject & { pathname: "/users/[user_id]/posts/[post_id]/comments" , query: { user_id : string | number,post_id : string | number } }
使い方
あとこんな感じで使える(はず)
// auto-generated (コピペしてきたもの)
type AppUrlObject = UrlObject & { pathname: "/" }
| UrlObject & { pathname: "/users" }
| UrlObject & { pathname: "/api/hello" }
| UrlObject & { pathname: "/api/rest/[...slug]", query: { slug: ReadonlyArray<string> | ReadonlyArray<number> } }
| UrlObject & { pathname: "/api/rest2/[[...slug]]", query: { slug?: ReadonlyArray<string> | ReadonlyArray<number> } }
| UrlObject & { pathname: "/users/[user_id]", query: { user_id: string | number } }
| UrlObject & { pathname: "/users/[user_id]/posts", query: { user_id: string | number } }
| UrlObject & { pathname: "/users/[user_id]/posts/[post_id]/comments", query: { user_id: string | number, post_id: string | number } }
const AppLink: FC<{ href: AppUrlObject }> = ({ href }) => {
return <div>
<NextLink href={href}>{JSON.stringify(href)}</NextLink>
</div>
}
export const SomeApp = () => {
// const userId = 10
// const url = `/users/${userId}/posts/` as const
return <div>
<AppLink href={{
pathname: "/",
}} />
<AppLink href={{
pathname: "/users/[user_id]",
query: { user_id: 10 }
}} />
<AppLink href={{
pathname: "/api/rest2/[[...slug]]",
query: { slug: [10, 11] }
}} />
</div>
}
例えば下記のようなのは怒られる
<AppLink href={{
pathname: "/unknown/[user_id]",
query: { user_id: 10 }
}} />
<AppLink href={{
pathname: "/users/[user_id]",
}} />
<AppLink href={{
pathname: "/api/rest2/[[...slug]]",
query: { slug: 11 }
}} />
もうちょっと緩めにしたければこんなふうにするのも可
type WeakAppUrl = AppUrlObject | string | UrlObject
このスクラップは2021/04/24にクローズされました