Added resolverBasePath option to rpc loaders (#3380)
Co-authored-by: beerose <alexsandra.sikora@gmail.com>
This commit is contained in:
5
.changeset/violet-lions-help.md
Normal file
5
.changeset/violet-lions-help.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@blitzjs/rpc": patch
|
||||
---
|
||||
|
||||
Add resolverBasePath to Blitz config to change the way rpc routes are generated
|
||||
1
.npmrc
1
.npmrc
@@ -6,3 +6,4 @@ public-hoist-pattern[]=*types*
|
||||
public-hoist-pattern[]=*eslint*
|
||||
public-hoist-pattern[]=@prettier/plugin-*
|
||||
public-hoist-pattern[]=*prettier-plugin-*
|
||||
strict-peer-dependencies=false
|
||||
|
||||
@@ -10,6 +10,7 @@ module.exports = withBlitz(
|
||||
customServer: {
|
||||
hotReload: false,
|
||||
},
|
||||
resolverBasePath: "root",
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type {NextConfig} from "next"
|
||||
import {
|
||||
GetServerSideProps,
|
||||
GetServerSidePropsResult,
|
||||
@@ -7,21 +8,20 @@ import {
|
||||
NextApiResponse,
|
||||
} from "next"
|
||||
import type {
|
||||
Ctx as BlitzCtx,
|
||||
AddParameters,
|
||||
AsyncFunc,
|
||||
BlitzServerPlugin,
|
||||
Ctx as BlitzCtx,
|
||||
FirstParam,
|
||||
Middleware,
|
||||
MiddlewareResponse,
|
||||
AsyncFunc,
|
||||
FirstParam,
|
||||
AddParameters,
|
||||
} from "blitz"
|
||||
import {handleRequestWithMiddleware, startWatcher, stopWatcher} from "blitz"
|
||||
import type {NextConfig} from "next"
|
||||
import {getQueryKey, getInfiniteQueryKey, installWebpackConfig} from "@blitzjs/rpc"
|
||||
import {dehydrate} from "@blitzjs/rpc"
|
||||
import {dehydrate, getQueryKey, getInfiniteQueryKey, loaderClient, loaderServer} from "@blitzjs/rpc"
|
||||
import {DefaultOptions, QueryClient} from "react-query"
|
||||
import {IncomingMessage, ServerResponse} from "http"
|
||||
import {withSuperJsonProps} from "./superjson"
|
||||
import {ResolverBasePath} from "@blitzjs/rpc/src/index-server"
|
||||
|
||||
export * from "./index-browser"
|
||||
|
||||
@@ -133,12 +133,59 @@ export const setupBlitzServer = ({plugins}: SetupBlitzOptions) => {
|
||||
|
||||
export interface BlitzConfig extends NextConfig {
|
||||
blitz?: {
|
||||
resolverBasePath?: ResolverBasePath
|
||||
customServer?: {
|
||||
hotReload?: boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface WebpackRuleOptions {
|
||||
resolverBasePath?: ResolverBasePath
|
||||
}
|
||||
|
||||
interface WebpackRule {
|
||||
test: RegExp
|
||||
use: Array<{
|
||||
loader: string
|
||||
options: WebpackRuleOptions
|
||||
}>
|
||||
}
|
||||
|
||||
interface InstallWebpackConfigOptions {
|
||||
webpackConfig: {
|
||||
module: {
|
||||
rules: WebpackRule[]
|
||||
}
|
||||
}
|
||||
nextConfig: BlitzConfig
|
||||
}
|
||||
|
||||
export function installWebpackConfig({webpackConfig, nextConfig}: InstallWebpackConfigOptions) {
|
||||
const options: WebpackRuleOptions = {
|
||||
resolverBasePath: nextConfig.blitz?.resolverBasePath,
|
||||
}
|
||||
|
||||
webpackConfig.module.rules.push({
|
||||
test: /\/\[\[\.\.\.blitz]]\.[jt]s$/,
|
||||
use: [
|
||||
{
|
||||
loader: loaderServer,
|
||||
options,
|
||||
},
|
||||
],
|
||||
})
|
||||
webpackConfig.module.rules.push({
|
||||
test: /[\\/](queries|mutations)[\\/]/,
|
||||
use: [
|
||||
{
|
||||
loader: loaderClient,
|
||||
options,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
export function withBlitz(nextConfig: BlitzConfig = {}) {
|
||||
if (
|
||||
process.env.NODE_ENV !== "production" &&
|
||||
@@ -158,8 +205,8 @@ export function withBlitz(nextConfig: BlitzConfig = {}) {
|
||||
}
|
||||
|
||||
const config = Object.assign({}, nextConfig, {
|
||||
webpack: (config: any, options: any) => {
|
||||
installWebpackConfig(config)
|
||||
webpack: (config: InstallWebpackConfigOptions["webpackConfig"], options: any) => {
|
||||
installWebpackConfig({webpackConfig: config, nextConfig})
|
||||
if (typeof nextConfig.webpack === "function") {
|
||||
return nextConfig.webpack(config, options)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import {normalizePathTrailingSlash} from "next/dist/client/normalize-trailing-sl
|
||||
import {addBasePath} from "next/dist/shared/lib/router/router"
|
||||
import {deserialize, serialize} from "superjson"
|
||||
import {SuperJSONResult} from "superjson/dist/types"
|
||||
import {isServer, CSRFTokenMismatchError} from "blitz"
|
||||
import {CSRFTokenMismatchError, isServer} from "blitz"
|
||||
import {getQueryKeyFromUrlAndParams, queryClient} from "./react-query-utils"
|
||||
import {
|
||||
getAntiCSRFToken,
|
||||
@@ -55,7 +55,7 @@ export function __internal_buildRpcClient({
|
||||
resolverType,
|
||||
routePath,
|
||||
}: BuildRpcClientParams): RpcClient {
|
||||
const fullRoutePath = normalizeApiRoute("/api/rpc/" + routePath)
|
||||
const fullRoutePath = normalizeApiRoute("/api/rpc" + routePath)
|
||||
|
||||
const httpClient: RpcClientBase = async (params, opts = {}) => {
|
||||
const debug = (await import("debug")).default("blitz:rpc")
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import {assert, Ctx, baseLogger, prettyMs, newLine} from "blitz"
|
||||
import {assert, baseLogger, Ctx, newLine, prettyMs} from "blitz"
|
||||
import {NextApiRequest, NextApiResponse} from "next"
|
||||
import {deserialize, serialize as superjsonSerialize} from "superjson"
|
||||
import chalk from "chalk"
|
||||
import {resolve} from "path"
|
||||
|
||||
// TODO - optimize end user server bundles by not exporting all client stuff here
|
||||
export * from "./index-browser"
|
||||
@@ -24,6 +25,7 @@ function getGlobalObject<T extends Record<string, unknown>>(key: string, default
|
||||
|
||||
type Resolver = (...args: unknown[]) => Promise<unknown>
|
||||
type ResolverFiles = Record<string, () => Promise<{default?: Resolver}>>
|
||||
export type ResolverBasePath = "queries|mutations" | "root" | undefined
|
||||
|
||||
// We define `global.__internal_blitzRpcResolverFiles` to ensure we use the same global object.
|
||||
// Needed for Next.js. I'm guessing that Next.js is including the `node_modules/` files in a seperate bundle than user files.
|
||||
@@ -47,21 +49,9 @@ export function __internal_addBlitzRpcResolver(
|
||||
return resolver
|
||||
}
|
||||
|
||||
import {resolve} from "path"
|
||||
const dir = __dirname + (() => "")() // trick to avoid `@vercel/ncc` to glob import
|
||||
const loaderServer = resolve(dir, "./loader-server.cjs")
|
||||
const loaderClient = resolve(dir, "./loader-client.cjs")
|
||||
|
||||
export function installWebpackConfig<T extends any[]>(config: {module?: {rules?: T}}) {
|
||||
config.module!.rules!.push({
|
||||
test: /\/\[\[\.\.\.blitz]]\.[jt]s$/,
|
||||
use: [{loader: loaderServer}],
|
||||
})
|
||||
config.module!.rules!.push({
|
||||
test: /[\\/](queries|mutations)[\\/]/,
|
||||
use: [{loader: loaderClient}],
|
||||
})
|
||||
}
|
||||
export const loaderServer = resolve(dir, "./loader-server.cjs")
|
||||
export const loaderClient = resolve(dir, "./loader-client.cjs")
|
||||
|
||||
// ----------
|
||||
// END LOADER
|
||||
|
||||
@@ -3,20 +3,13 @@ import {
|
||||
convertFilePathToResolverName,
|
||||
convertFilePathToResolverType,
|
||||
convertPageFilePathToRoutePath,
|
||||
Loader,
|
||||
LoaderOptions,
|
||||
toPosixPath,
|
||||
} from "./loader-utils"
|
||||
import {assert} from "blitz"
|
||||
import {posix} from "path"
|
||||
|
||||
// Subset of `import type { LoaderDefinitionFunction } from 'webpack'`
|
||||
type Loader = {
|
||||
_compiler?: {
|
||||
name: string
|
||||
context: string
|
||||
}
|
||||
resource: string
|
||||
cacheable: (enabled: boolean) => void
|
||||
}
|
||||
|
||||
export async function loader(this: Loader, input: string): Promise<string> {
|
||||
const compiler = this._compiler!
|
||||
@@ -25,8 +18,12 @@ export async function loader(this: Loader, input: string): Promise<string> {
|
||||
|
||||
const isSSR = compiler.name === "server"
|
||||
if (!isSSR) {
|
||||
const code = await transformBlitzRpcResolverClient(input, toPosixPath(id), toPosixPath(root))
|
||||
return code
|
||||
return await transformBlitzRpcResolverClient(
|
||||
input,
|
||||
toPosixPath(id),
|
||||
toPosixPath(root),
|
||||
this.query,
|
||||
)
|
||||
}
|
||||
|
||||
return input
|
||||
@@ -34,13 +31,18 @@ export async function loader(this: Loader, input: string): Promise<string> {
|
||||
|
||||
module.exports = loader
|
||||
|
||||
export async function transformBlitzRpcResolverClient(_src: string, id: string, root: string) {
|
||||
export async function transformBlitzRpcResolverClient(
|
||||
_src: string,
|
||||
id: string,
|
||||
root: string,
|
||||
options?: LoaderOptions,
|
||||
) {
|
||||
assertPosixPath(id)
|
||||
assertPosixPath(root)
|
||||
|
||||
const resolverFilePath = "/" + posix.relative(root, id)
|
||||
assertPosixPath(resolverFilePath)
|
||||
const routePath = convertPageFilePathToRoutePath(resolverFilePath)
|
||||
const routePath = convertPageFilePathToRoutePath(resolverFilePath, options?.resolverBasePath)
|
||||
const resolverName = convertFilePathToResolverName(resolverFilePath)
|
||||
const resolverType = convertFilePathToResolverType(resolverFilePath)
|
||||
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
import {posix, join, dirname} from "path"
|
||||
import {dirname, join, posix} from "path"
|
||||
import {promises} from "fs"
|
||||
import {
|
||||
assertPosixPath,
|
||||
toPosixPath,
|
||||
buildPageExtensionRegex,
|
||||
getIsRpcFile,
|
||||
topLevelFoldersThatMayContainResolvers,
|
||||
convertPageFilePathToRoutePath,
|
||||
getIsRpcFile,
|
||||
Loader,
|
||||
LoaderOptions,
|
||||
topLevelFoldersThatMayContainResolvers,
|
||||
toPosixPath,
|
||||
} from "./loader-utils"
|
||||
|
||||
// Subset of `import type { LoaderDefinitionFunction } from 'webpack'`
|
||||
type Loader = {
|
||||
_compiler?: {
|
||||
name: string
|
||||
context: string
|
||||
}
|
||||
resource: string
|
||||
cacheable: (enabled: boolean) => void
|
||||
}
|
||||
|
||||
export async function loader(this: Loader, input: string): Promise<string> {
|
||||
const compiler = this._compiler!
|
||||
@@ -29,8 +23,13 @@ export async function loader(this: Loader, input: string): Promise<string> {
|
||||
this.cacheable(false)
|
||||
|
||||
const resolvers = await collectResolvers(root, ["ts", "js"])
|
||||
const code = await transformBlitzRpcServer(input, toPosixPath(id), toPosixPath(root), resolvers)
|
||||
return code
|
||||
return await transformBlitzRpcServer(
|
||||
input,
|
||||
toPosixPath(id),
|
||||
toPosixPath(root),
|
||||
resolvers,
|
||||
this.query,
|
||||
)
|
||||
}
|
||||
|
||||
return input
|
||||
@@ -43,6 +42,7 @@ export async function transformBlitzRpcServer(
|
||||
id: string,
|
||||
root: string,
|
||||
resolvers: string[],
|
||||
options?: LoaderOptions,
|
||||
) {
|
||||
assertPosixPath(id)
|
||||
assertPosixPath(root)
|
||||
@@ -55,7 +55,7 @@ export async function transformBlitzRpcServer(
|
||||
|
||||
for (let resolverFilePath of resolvers) {
|
||||
const relativeResolverPath = posix.relative(dirname(id), join(root, resolverFilePath))
|
||||
const routePath = convertPageFilePathToRoutePath(resolverFilePath)
|
||||
const routePath = convertPageFilePathToRoutePath(resolverFilePath, options?.resolverBasePath)
|
||||
code += `__internal_addBlitzRpcResolver('${routePath}', () => import('${relativeResolverPath}'));`
|
||||
code += "\n"
|
||||
}
|
||||
|
||||
24
packages/blitz-rpc/src/loader-utils.test.ts
Normal file
24
packages/blitz-rpc/src/loader-utils.test.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import {describe, expect, it} from "vitest"
|
||||
import {convertPageFilePathToRoutePath} from "./loader-utils"
|
||||
|
||||
const FILE_PATH = "app/queries/getData.ts"
|
||||
|
||||
describe("convertPageFilePathToRoutePath", () => {
|
||||
it("should return the full path when resolverBasePath is set to root", () => {
|
||||
const res = convertPageFilePathToRoutePath(FILE_PATH, "root")
|
||||
|
||||
expect(res).toEqual("app/queries/getData")
|
||||
})
|
||||
|
||||
it("should return the relative path when resolverBasePath is set to queries|mutations", () => {
|
||||
const res = convertPageFilePathToRoutePath(FILE_PATH, "queries|mutations")
|
||||
|
||||
expect(res).toEqual("/getData")
|
||||
})
|
||||
|
||||
it("should return the relative path when resolverBasePath is set to undefined", () => {
|
||||
const res = convertPageFilePathToRoutePath(FILE_PATH, undefined)
|
||||
|
||||
expect(res).toEqual("/getData")
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,20 @@
|
||||
import {assert} from "blitz"
|
||||
import {win32, posix, sep} from "path"
|
||||
import {posix, sep, win32} from "path"
|
||||
import {ResolverBasePath} from "./index-server"
|
||||
|
||||
export interface LoaderOptions {
|
||||
resolverBasePath?: ResolverBasePath
|
||||
}
|
||||
|
||||
export interface Loader {
|
||||
_compiler?: {
|
||||
name: string
|
||||
context: string
|
||||
}
|
||||
resource: string
|
||||
cacheable: (enabled: boolean) => void
|
||||
query: LoaderOptions
|
||||
}
|
||||
|
||||
export function assertPosixPath(path: string) {
|
||||
const errMsg = `Wrongly formatted path: ${path}`
|
||||
@@ -34,7 +49,14 @@ export function buildPageExtensionRegex(pageExtensions: string[]) {
|
||||
|
||||
const fileExtensionRegex = /\.([a-z]+)$/
|
||||
|
||||
export function convertPageFilePathToRoutePath(filePath: string) {
|
||||
export function convertPageFilePathToRoutePath(
|
||||
filePath: string,
|
||||
resolverBasePath: ResolverBasePath,
|
||||
) {
|
||||
if (resolverBasePath === "root") {
|
||||
return filePath.replace(fileExtensionRegex, "")
|
||||
}
|
||||
|
||||
return filePath
|
||||
.replace(/^.*?[\\/]queries[\\/]/, "/")
|
||||
.replace(/^.*?[\\/]mutations[\\/]/, "/")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {Ctx} from "blitz"
|
||||
import {describe, it, expect} from "vitest"
|
||||
import {describe, expect, it} from "vitest"
|
||||
import {z} from "zod"
|
||||
import {ParserType, resolver} from "./resolver"
|
||||
|
||||
@@ -44,10 +44,9 @@ const asyncResolver = resolver.pipe(
|
||||
const resolverTest = async ({type}: {type?: ParserType}) => {
|
||||
const resolver1 = type === "sync" ? syncResolver : asyncResolver
|
||||
|
||||
const result1 = await resolver1(
|
||||
{email: "test@example.com"},
|
||||
{session: {$authorize: () => undefined} as Ctx},
|
||||
)
|
||||
const result1 = await resolver1({email: "test@example.com"}, {
|
||||
session: {$authorize: () => undefined},
|
||||
} as Ctx)
|
||||
expect(result1).toBe("test@example.com")
|
||||
|
||||
const resolver2 = resolver.pipe(
|
||||
@@ -55,9 +54,8 @@ const resolverTest = async ({type}: {type?: ParserType}) => {
|
||||
return input.email
|
||||
},
|
||||
)
|
||||
const result2 = await resolver2(
|
||||
{email: "test@example.com"},
|
||||
{session: {$authorize: () => undefined} as Ctx},
|
||||
)
|
||||
const result2 = await resolver2({email: "test@example.com"}, {
|
||||
session: {$authorize: () => undefined},
|
||||
} as Ctx)
|
||||
expect(result2).toBe("test@example.com")
|
||||
}
|
||||
|
||||
666
pnpm-lock.yaml
generated
666
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user