pathpida
TypeScript friendly internal link client for Next.js, Nuxt.js and Sapper.
Features
- Type safety. Automatically generate type definition files for manipulating internal links in Next.js/Nuxt.js/Sapper.
- Zero configuration. No configuration required can be used immediately after installation.
- Zero runtime. Lightweight because runtime code is not included in the bundle.
- Support for static files. Static files in public/ are also supported, so static assets can be safely referenced.
Table of Contents
- Install
- Command Line Interface Options
- Setup - Next.js
- Usage - Next.js
- Define query - Next.js
- Generate static files path - Next.js
- Setup - Nuxt.js
- Usage - Nuxt.js
- Define query - Nuxt.js
- Generate static files path - Nuxt.js
- Setup - Sapper
- Usage - Sapper
- Define query - Sapper
- Generate static files path - Sapper
- License
Install
-
Using npm:
$ npm install pathpida npm-run-all --save-dev
-
Using Yarn:
$ yarn add pathpida npm-run-all --dev
Command Line Interface Options
Option | Type | Description |
---|---|---|
--enableStatic -s |
Generate static files path in $path.ts . |
|
--ignorePath -p |
string |
Specify the ignore pattern file path. |
--output -o |
string |
Specify the output directory for $path.ts . |
--watch -w |
Enable watch mode. Regenerate $path.ts .
|
|
--version -v |
Print pathpida version. |
Setup - Next.js
package.json
{
"scripts": {
"dev": "run-p dev:*",
"dev:next": "next dev",
"dev:path": "pathpida --ignorePath .gitignore --watch",
"build": "pathpida --ignorePath .gitignore && next build"
}
}
Usage - Next.js
pages/index.tsx
pages/post/create.tsx
pages/post/[pid].tsx
pages/post/[...slug].tsx
lib/$path.ts or utils/$path.ts // Generated automatically by pathpida
or
src/pages/index.tsx
src/pages/post/create.tsx
src/pages/post/[pid].tsx
src/pages/post/[...slug].tsx
src/lib/$path.ts or src/utils/$path.ts // Generated automatically by pathpida
pages/index.tsx
import Link from "next/link"
import { pagesPath } from "../lib/$path"
console.log(pagesPath.post.create.$url()) // { pathname: '/post/create' }
console.log(pagesPath.post._pid(1).$url()) // { pathname: '/post/[pid]', query: { pid: 1 }}
console.log(pagesPath.post._slug(["a", "b", "c"]).$url()) // { pathname: '/post//[...slug]', query: { slug: ['a', 'b', 'c'] }}
export default () => {
const onClick = useCallback(() => {
router.push(pagesPath.post._pid(1).$url())
}, [])
return (
<>
<Link href={pagesPath.post._slug(["a", "b", "c"]).$url()} />
<div onClick={onClick} />
</>
)
}
Define query - Next.js
pages/post/create.tsx
export type Query = {
userId: number
name?: string
}
export default () => <div />
pages/post/[pid].tsx
export type OptionalQuery = {
limit: number
label?: string
}
export default () => <div />
pages/index.tsx
import Link from "next/link"
import { pagesPath } from "../lib/$path"
console.log(pagesPath.post.create.$url({ query: { userId: 1 } })) // { pathname: '/post/create', query: { userId: 1 }}
console.log(pagesPath.post.create.$url()) // type error
console.log(pagesPath.post._pid(1).$url()) // { pathname: '/post/[pid]', query: { pid: 1 }}
console.log(pagesPath.post._pid(1).$url({ query: { limit: 10 }, hash: "sample" })) // { pathname: '/post/[pid]', query: { pid: 1, limit: 10 }, hash: 'sample' }
export default () => {
const onClick = useCallback(() => {
router.push(pagesPath.post._pid(1).$url())
}, [])
return (
<>
<Link href={pagesPath.post._slug(["a", "b", "c"]).$url()} />
<div onClick={onClick} />
</>
)
}
Generate static files path - Next.js
package.json
{
"scripts": {
"dev": "run-p dev:*",
"dev:next": "next dev",
"dev:path": "pathpida --enableStatic --watch",
"build": "pathpida --enableStatic && next build"
}
}
pages/index.tsx
pages/post/create.tsx
pages/post/[pid].tsx
pages/post/[...slug].tsx
public/aa.json
public/bb/cc.png
lib/$path.ts or utils/$path.ts // Generated automatically by pathpida
or
src/pages/index.tsx
src/pages/post/create.tsx
src/pages/post/[pid].tsx
src/pages/post/[...slug].tsx
public/aa.json
public/bb/cc.png
src/lib/$path.ts or src/utils/$path.ts // Generated automatically by pathpida
pages/index.tsx
import Link from "next/link"
import { pagesPath, staticPath } from "../lib/$path"
console.log(staticPath.aa_json) // /aa.json
export default () => {
return (
<>
<Link href={pagesPath.post._slug(["a", "b", "c"]).$url()} />
<img src={staticPath.bb.cc_png} />
</>
)
}
Setup - Nuxt.js
package.json
{
"scripts": {
"dev": "run-p dev:*",
"dev:nuxt": "nuxt-ts",
"dev:path": "pathpida --ignorePath .gitignore --watch",
"build": "pathpida --ignorePath .gitignore && nuxt-ts build"
}
}
nuxt.config.js
or nuxt.config.ts
{
plugins: ['~/plugins/$path'],
srcDir: 'client', // optional
router: {
trailingSlash: true // optional
}
}
Usage - Nuxt.js
pages/index.vue
pages/post/create.vue
pages/post/_pid.tsx
plugins/$path.ts // Generated automatically by pathpida
pages/index.vue
<template>
<div>
<nuxt-link :to="$pagesPath.post._pid(1).$url()" />
<div @click="onClick" />
</div>
</template>
<script lang="ts">
import Vue from "vue"
export default Vue.extend({
methods: {
onClick() {
this.$router.push(this.$pagesPath.post._pid(1).$url())
}
}
})
</script>
Define query - Nuxt.js
pages/post/create.vue
<script lang="ts">
import Vue from "vue"
export type Query = {
userId: number
name?: string
}
export default Vue.extend({})
</script>
pages/post/_pid.vue
<script lang="ts">
import Vue from "vue"
export type OptionalQuery = {
limit: number
label?: string
}
export default Vue.extend({})
</script>
pages/index.vue
<template>
<div>
<nuxt-link :to="$pagesPath.post.create.$url({ query: { userId: 1 } })" />
<div @click="onClick" />
</div>
</template>
<script lang="ts">
import Vue from "vue"
export default Vue.extend({
methods: {
onClick() {
this.$router.push(this.$pagesPath.post._pid(1).$url())
this.$router.push(this.$pagesPath.post._pid(1).$url({ query: { limit: 10 }, hash: "sample" }))
}
}
})
</script>
⚠️ In the case of .vue file, Query/OptionalQuery type must not contain any reference types.
This is because due to typescript restrictions, types exported from .vue
files cannot be imported in plugins/$path.ts
.
If you want to import types from other files, please use import types with absolute paths.
types/users.ts
export type UserId = number
pages/post/create.vue
<script lang="ts">
import Vue from "vue"
export type Query = {
userId: import("~/types/users").UserId
name?: string
}
export default Vue.extend({})
</script>
Generate static files path - Nuxt.js
package.json
{
"scripts": {
"dev": "run-p dev:*",
"dev:nuxt": "nuxt-ts",
"dev:path": "pathpida --enableStatic --watch",
"build": "pathpida --enableStatic && nuxt-ts build"
}
}
pages/index.vue
pages/post/create.vue
pages/post/_pid.vue
static/aa.json
static/bb/cc.png
plugins/$path.ts // Generated automatically by pathpida
pages/index.vue
<template>
<div>
<nuxt-link :to="$pagesPath.post.create.$url({ query: { userId: 1 } })" />
<img :src="$staticPath.bb.cc_png" />
</div>
</template>
<script lang="ts">
import Vue from "vue"
export default Vue.extend({})
</script>
Setup - Sapper
package.json
{
"scripts": {
"dev": "run-p dev:*",
"dev:sapper": "sapper dev",
"dev:path": "pathpida --ignorePath .gitignore --watch",
"build": "pathpida --ignorePath .gitignore && sapper build --legacy",
"export": "pathpida --ignorePath .gitignore && sapper export --legacy"
}
}
Usage - Sapper
src/routes/blog/[slug].json.ts
src/routes/blog/[slug].svelte
src/routes/blog/index.json.js
src/routes/blog/index.svelte
src/node_modules/$path.ts // Generated automatically by pathpida
src/routes/blog/index.svelte
<script context="module" lang="ts">
import { pagesPath } from "$path"
export function preload() {
return this.fetch(pagesPath.blog_json.$url())
.then((r: { json: () => any }) => r.json())
.then((posts: { slug: string; title: string; html: any }[]) => {
return { posts }
})
}
</script>
<script lang="ts">
export let posts: { slug: string; title: string; html: any }[]
</script>
<ul>
{#each posts as post}
<li><a rel="prefetch" href="{pagesPath.blog._slug(post.slug).$url()}">{post.title}</a></li>
{/each}
</ul>
Define query - Sapper
src/routes/blog/[slug].json.ts
import posts from "./_posts.js"
export type Query = {
// or OptionalQuery
id: number
}
const lookup = new Map()
posts.forEach(post => {
lookup.set(post.slug, JSON.stringify(post))
})
src/routes/blog/[slug].svelte
<script context="module" lang="ts">
import { pagesPath } from "$path"
export async function preload({ params }) {
const res = await this.fetch(pagesPath.blog._slug_json(params.slug).$url({ query: { id: 1 } }))
const data = await res.json()
if (res.status === 200) {
return { post: data }
} else {
this.error(res.status, data.message)
}
}
</script>
⚠️ In the case of .svelte file, Query/OptionalQuery type must not contain any reference types.
This is because due to typescript restrictions, types exported from .svelte
files cannot be imported in src/node_modules/$path.ts
.
If you want to import types from other files, please use import types with absolute paths.
src/node_modules/types/users.ts
export type UserId = number
src/routes/blog/[slug].json.ts
import posts from "./_posts.js"
export type Query = {
id: import("types/users").UserId
}
const lookup = new Map()
posts.forEach(post => {
lookup.set(post.slug, JSON.stringify(post))
})
Generate static files path - Sapper
package.json
{
"scripts": {
"dev": "run-p dev:*",
"dev:sapper": "sapper dev",
"dev:path": "pathpida --enableStatic --watch",
"build": "pathpida --enableStatic && sapper build --legacy",
"export": "pathpida --enableStatic && sapper export --legacy"
}
}
src/routes/index.svelte
static/logo-512.png
src/node_modules/$path.ts // Generated automatically by pathpida
src/routes/index.svelte
<script>
import { staticPath } from "$path"
</script>
<figure>
<img alt="Logo" src="{staticPath.logo_512_png}" />
</figure>
License
pathpida is licensed under a MIT License.