diff --git a/.changeset/mean-bobcats-relax.md b/.changeset/mean-bobcats-relax.md new file mode 100644 index 000000000000..a89f97af4a9f --- /dev/null +++ b/.changeset/mean-bobcats-relax.md @@ -0,0 +1,13 @@ +--- +'@modern-js/create-request': patch +'@modern-js/app-tools': patch +'@modern-js/bff-runtime': patch +'@modern-js/bff-core': patch +'@modern-js/plugin-bff': patch +'@modern-js/server': patch +'@modern-js/core': patch +--- + +feat: bff support independent project + +feat: bff 支持跨项目调用 diff --git a/packages/cli/core/src/types/context.ts b/packages/cli/core/src/types/context.ts index 224cd37807ab..92f4112bd80d 100644 --- a/packages/cli/core/src/types/context.ts +++ b/packages/cli/core/src/types/context.ts @@ -57,6 +57,8 @@ export interface IAppContext { serverRoutes: ServerRoute[]; /** Whether to use api only mode */ apiOnly: boolean; + /** prefix for independent bff projects */ + indepBffPrefix?: string; /** The Builder instance */ builder?: UniBuilderInstance | UniBuilderWebpackInstance; /** Tools type of the current project */ diff --git a/packages/cli/plugin-bff/src/cli.ts b/packages/cli/plugin-bff/src/cli.ts index 45aae77531a3..5a935215079b 100644 --- a/packages/cli/plugin-bff/src/cli.ts +++ b/packages/cli/plugin-bff/src/cli.ts @@ -3,24 +3,133 @@ import type { AppTools, CliPlugin } from '@modern-js/app-tools'; import { ApiRouter } from '@modern-js/bff-core'; import { compile } from '@modern-js/server-utils'; import type { ServerRoute } from '@modern-js/types'; -import { fs, API_DIR, SHARED_DIR, normalizeOutputPath } from '@modern-js/utils'; +import { + fs, + API_DIR, + SHARED_DIR, + logger, + normalizeOutputPath, +} from '@modern-js/utils'; +import clientGenerator from './utils/client-generator'; +import pluginGenerator from './utils/plugin-generator'; const DEFAULT_API_PREFIX = '/api'; const TS_CONFIG_FILENAME = 'tsconfig.json'; -export const bffPlugin = (): CliPlugin => ({ +export interface BffPluginOptions { + projectType: 'web' | 'api'; + fetchDomain?: string; +} + +export const bffPlugin = (options?: BffPluginOptions): CliPlugin => ({ name: '@modern-js/plugin-bff', setup: api => { + const isApiProject = options?.projectType === 'api'; + + const compileApi = async () => { + const { + appDirectory, + distDirectory, + apiDirectory, + sharedDirectory, + moduleType, + } = api.useAppContext(); + const modernConfig = api.useResolvedConfigContext(); + + const distDir = path.resolve(distDirectory); + const apiDir = apiDirectory || path.resolve(appDirectory, API_DIR); + const sharedDir = + sharedDirectory || path.resolve(appDirectory, SHARED_DIR); + const tsconfigPath = path.resolve(appDirectory, TS_CONFIG_FILENAME); + + const sourceDirs = []; + if (await fs.pathExists(apiDir)) { + sourceDirs.push(apiDir); + } + + if (await fs.pathExists(sharedDir)) { + sourceDirs.push(sharedDir); + } + + const { server } = modernConfig; + const { alias } = modernConfig.source; + const { babel } = modernConfig.tools; + + if (sourceDirs.length > 0) { + await compile( + appDirectory, + { + server, + alias, + babelConfig: babel, + }, + { + sourceDirs, + distDir, + tsconfigPath, + moduleType, + }, + ); + } + }; + + const generator = async () => { + const { appDirectory, apiDirectory, lambdaDirectory, port } = + api.useAppContext(); + + const modernConfig = api.useResolvedConfigContext(); + const { bff } = modernConfig || {}; + const prefix = bff?.prefix || DEFAULT_API_PREFIX; + const httpMethodDecider = bff?.httpMethodDecider; + + const apiRouter = new ApiRouter({ + apiDir: apiDirectory, + appDir: appDirectory, + lambdaDir: lambdaDirectory, + prefix, + httpMethodDecider, + isBuild: true, + }); + + const lambdaDir = apiRouter.getLambdaDir(); + const existLambda = apiRouter.isExistLambda(); + + const ops = { + prefix, + appDir: appDirectory, + apiDir: apiDirectory, + lambdaDir, + existLambda, + port, + requestCreator: (bff as any)?.requestCreator, + target: 'client', + httpMethodDecider, + domain: options?.fetchDomain, + }; + + await clientGenerator(ops); + await pluginGenerator(ops.prefix); + }; + return { config() { return { tools: { bundlerChain: (chain, { CHAIN_ID, isServer }) => { - const { port, appDirectory, apiDirectory, lambdaDirectory } = - api.useAppContext(); + const { + port, + appDirectory, + apiDirectory, + lambdaDirectory, + indepBffPrefix, + } = api.useAppContext() as any; + const modernConfig = api.useResolvedConfigContext(); + const { bff } = modernConfig || {}; - const prefix = bff?.prefix || DEFAULT_API_PREFIX; + const prefix = + indepBffPrefix || bff?.prefix || DEFAULT_API_PREFIX; + const httpMethodDecider = bff?.httpMethodDecider; const apiRouter = new ApiRouter({ @@ -71,13 +180,20 @@ export const bffPlugin = (): CliPlugin => ({ source: { moduleScopes: [`./${API_DIR}`, /create-request/], }, + // dev: { + // hmr: isApiProject + // ? false + // : api.useResolvedConfigContext()?.dev?.hmr, + // }, }; }, modifyServerRoutes({ routes }) { const modernConfig = api.useResolvedConfigContext(); + const { indepBffPrefix } = api.useAppContext() as any; + const { bff } = modernConfig || {}; - const prefix = bff?.prefix || '/api'; + const prefix = indepBffPrefix || bff?.prefix || '/api'; const prefixList: string[] = []; @@ -117,51 +233,50 @@ export const bffPlugin = (): CliPlugin => ({ return { plugins }; }, + async beforeDev() { + if (isApiProject) { + await compileApi(); + await generator(); + } + }, async afterBuild() { - const { - appDirectory, - distDirectory, - apiDirectory, - sharedDirectory, - moduleType, - } = api.useAppContext(); - const modernConfig = api.useResolvedConfigContext(); - - const distDir = path.resolve(distDirectory); - const apiDir = apiDirectory || path.resolve(appDirectory, API_DIR); - const sharedDir = - sharedDirectory || path.resolve(appDirectory, SHARED_DIR); - const tsconfigPath = path.resolve(appDirectory, TS_CONFIG_FILENAME); - - const sourceDirs = []; - if (await fs.pathExists(apiDir)) { - sourceDirs.push(apiDir); + await compileApi(); + if (isApiProject) { + await generator(); } + }, + async watchFiles() { + const appContext = api.useAppContext(); + const config = api.useResolvedConfigContext(); + const { generateWatchFiles } = require('@modern-js/app-tools'); + const files = await generateWatchFiles( + appContext, + config.source.configDir, + ); - if (await fs.pathExists(sharedDir)) { - sourceDirs.push(sharedDir); + if (isApiProject) { + files.push(appContext.apiDirectory); } - const { server } = modernConfig; - const { alias } = modernConfig.source; - const { babel } = modernConfig.tools; - - if (sourceDirs.length > 0) { - await compile( - appDirectory, - { - server, - alias, - babelConfig: babel, - }, - { - sourceDirs, - distDir, - tsconfigPath, - moduleType, - }, - ); + return files; + }, + + async fileChange(e: { + filename: string; + eventType: string; + isPrivate: boolean; + }) { + const { filename, eventType, isPrivate } = e; + + if ( + isApiProject && + !isPrivate && + (eventType === 'change' || eventType === 'unlink') && + filename.startsWith('api/') + ) { + await compileApi(); + await generator(); } }, }; diff --git a/packages/cli/plugin-bff/src/server.ts b/packages/cli/plugin-bff/src/server.ts index 0defe0d371a7..c5c0f4709a98 100644 --- a/packages/cli/plugin-bff/src/server.ts +++ b/packages/cli/plugin-bff/src/server.ts @@ -35,9 +35,21 @@ export default (): ServerPlugin => ({ return { async prepare() { const appContext = api.useAppContext(); - const { appDirectory, distDirectory, render } = appContext; + const { + appDirectory, + distDirectory, + render, + indepBffPrefix, + apiDirectory, + } = appContext; const root = isProd() ? distDirectory : appDirectory; - const apiPath = path.resolve(root || process.cwd(), API_DIR); + + let apiPath = path.resolve(root || process.cwd(), API_DIR); + + if (indepBffPrefix && apiDirectory) { + // independent bff scenario + apiPath = apiDirectory; + } apiAppPath = path.resolve(apiPath, API_APP_NAME); const apiMod = await requireExistModule(apiAppPath); @@ -53,7 +65,8 @@ export default (): ServerPlugin => ({ /** bind api server */ const config = api.useConfigContext(); - const prefix = config?.bff?.prefix || '/api'; + const prefix = + (indepBffPrefix as string) || config?.bff?.prefix || '/api'; const enableHandleWeb = config?.bff?.enableHandleWeb; const httpMethodDecider = config?.bff?.httpMethodDecider; @@ -131,6 +144,7 @@ export default (): ServerPlugin => ({ const apiDir = path.resolve(pwd, API_DIR); const appContext = api.useAppContext(); const { apiDirectory, lambdaDirectory } = appContext; + apiRouter = new ApiRouter({ appDir: pwd, apiDir: (apiDirectory as string) || apiDir, diff --git a/packages/cli/plugin-bff/src/utils/client-generator.ts b/packages/cli/plugin-bff/src/utils/client-generator.ts new file mode 100644 index 000000000000..3faea2d08a21 --- /dev/null +++ b/packages/cli/plugin-bff/src/utils/client-generator.ts @@ -0,0 +1,170 @@ +import path from 'path'; +import { type GenClientOptions, generateClient } from '@modern-js/bff-core'; +import type { HttpMethodDecider } from '@modern-js/types'; +import { fs, logger } from '@modern-js/utils'; + +export type APILoaderOptions = { + prefix: string; + appDir: string; + apiDir: string; + lambdaDir: string; + existLambda: boolean; + port?: number; + requestCreator?: string; + target: string; + httpMethodDecider?: HttpMethodDecider; + domain?: string; +}; + +async function readDirectoryFiles(appDirectory: string, directory: string) { + const filesList: { + resourcePath: string; + source: string; + targetDir: string; + name: string; + absTargetDir: string; + relativeTargetDistDir: string; + }[] = []; + + async function readFiles(currentPath: string) { + const entries = await fs.readdir(currentPath, { withFileTypes: true }); + + for (const entry of entries) { + // if (entry.name === '_app.ts') { + // continue; + // } + + const resourcePath = path.join(currentPath, entry.name); + if (entry.isDirectory()) { + await readFiles(resourcePath); + } else { + const source = await fs.readFile(resourcePath, 'utf8'); + const targetDir = path.join( + './dist/client', + path.relative(directory, currentPath), + entry.name.replace('.ts', '.js'), + ); + const name = path.basename(entry.name, '.ts'); + const absTargetDir = path.resolve(targetDir); + + const relativePathFromAppDirectory = path.relative( + appDirectory, + currentPath, + ); + const typesFilePath = path.join( + './dist', + relativePathFromAppDirectory, + `${name}.d.ts`, + ); + const relativeTargetDistDir = `./${typesFilePath}`; + + filesList.push({ + resourcePath, + source, + targetDir, + name, + absTargetDir, + relativeTargetDistDir, + }); + } + } + } + + await readFiles(directory); + return filesList; +} + +async function writeTargetFile(absTargetDir: string, content: string) { + await fs.mkdir(path.dirname(absTargetDir), { recursive: true }); + await fs.writeFile(absTargetDir, content); +} + +async function clientGenerator(draftOptions: APILoaderOptions) { + const sourceList = await readDirectoryFiles( + draftOptions.appDir, + draftOptions.lambdaDir, + ); + + const getClitentCode = async (resourcePath: string, source: string) => { + const warning = `The file ${resourcePath} is not allowd to be imported in src directory, only API definition files are allowed.`; + + if (!draftOptions.existLambda) { + logger.warn(warning); + return; + } + + const options: GenClientOptions = { + prefix: (Array.isArray(draftOptions.prefix) + ? draftOptions.prefix[0] + : draftOptions.prefix) as string, + appDir: draftOptions.appDir, + apiDir: draftOptions.apiDir, + lambdaDir: draftOptions.lambdaDir, + target: draftOptions.target, + port: Number(draftOptions.port), + source, + resourcePath, + httpMethodDecider: draftOptions.httpMethodDecider, + domain: draftOptions.domain, + }; + + const { lambdaDir } = draftOptions as any; + if (!resourcePath.startsWith(lambdaDir)) { + logger.warn(warning); + return; + } + + options.requireResolve = require.resolve; + + const result = await generateClient(options); + + return result; + }; + + try { + for (const source of sourceList) { + const code = await getClitentCode(source.resourcePath, source.source); + if (code?.value) { + await writeTargetFile(source.absTargetDir, code.value); + } + } + logger.info(`Generate client bundle succeed`); + } catch (error) { + logger.error(`Generate Client bundle error: ${error}`); + } + + setPackage(sourceList); +} + +export default clientGenerator; + +async function setPackage( + files: { name: string; targetDir: string; relativeTargetDistDir: string }[], +) { + const packagePath = path.join(process.cwd(), 'package.json'); + try { + const packageData = await fs.readFile(packagePath, 'utf8'); + const packageJson = JSON.parse(packageData); + + if (!packageJson.exports) { + packageJson.exports = {}; + } + + files.forEach(file => { + const exportKey = `./${file.name}`; + const jsFilePath = `./${file.targetDir}`; + + packageJson.exports[exportKey] = { + import: jsFilePath, + types: file.relativeTargetDistDir, + }; + }); + + packageJson.exports['./server-plugin'] = `./dist/server-plugin/index.js`; + + await fs.writeFile(packagePath, JSON.stringify(packageJson, null, 2)); + logger.info(`Update package.json succeed`); + } catch (error) { + logger.error(`Update package.json error: ${error}`); + } +} diff --git a/packages/cli/plugin-bff/src/utils/plugin-generator.ts b/packages/cli/plugin-bff/src/utils/plugin-generator.ts new file mode 100644 index 000000000000..9b4bc6386679 --- /dev/null +++ b/packages/cli/plugin-bff/src/utils/plugin-generator.ts @@ -0,0 +1,32 @@ +import path from 'path'; +import { fs, logger } from '@modern-js/utils'; +import { API_APP_PACKAGE_NAME, API_APP_PREFIX } from './server-plugin'; + +async function pluginGenerator(prefix: string) { + const packagePath = path.join(process.cwd(), 'package.json'); + const packageData = await fs.readFile(packagePath, 'utf8'); + const packageJson = JSON.parse(packageData); + + const cwd = process.cwd(); + const pluginPath = path.resolve(cwd, './dist', 'server-plugin'); + if (!fs.existsSync(pluginPath)) { + fs.mkdirSync(pluginPath); + } + + let source = await fs.readFile( + path.resolve(__dirname, 'server-plugin.js'), + 'utf8', + ); + + source = source.replace( + new RegExp(API_APP_PACKAGE_NAME, 'g'), + packageJson.name, + ); + source = source.replace(new RegExp(API_APP_PREFIX, 'g'), prefix); + + fs.writeFileSync(path.join(pluginPath, 'index.js'), source); + + logger.info(`Generate server-plugin succeed`); +} + +export default pluginGenerator; diff --git a/packages/cli/plugin-bff/src/utils/server-plugin.ts b/packages/cli/plugin-bff/src/utils/server-plugin.ts new file mode 100644 index 000000000000..492ac57cb500 --- /dev/null +++ b/packages/cli/plugin-bff/src/utils/server-plugin.ts @@ -0,0 +1,40 @@ +import path from 'path'; +import type { ServerPlugin } from '@modern-js/server-core'; +import { API_DIR } from '@modern-js/utils'; + +export const API_APP_PACKAGE_NAME = '{packageName}'; +export const API_APP_PREFIX = '{prefix}'; +const DIST_DIR = 'dist'; +const LAMBDA_DIR = 'lambda'; +const NODE_MODULES = 'node_modules'; + +export const serverPlugin = (): ServerPlugin => ({ + name: '@modern-js/plugin-standalone-bff', + setup: api => { + let apiPatn = ''; + + return { + async prepare() { + const cwd = process.cwd(); + + const sdkPath = path.join(cwd, NODE_MODULES, API_APP_PACKAGE_NAME); + + const sdkDistPath = path.join(sdkPath, DIST_DIR); + const apiDirectory = path.join(sdkDistPath, API_DIR); + const lambdaDirectory = path.join(sdkDistPath, LAMBDA_DIR); + + apiPatn = apiDirectory; + const appContext = api.useAppContext(); + + api.setAppContext({ + ...appContext, + apiDirectory, + lambdaDirectory, + indepBffPrefix: API_APP_PREFIX, + }); + }, + }; + }, +}); + +export default serverPlugin; diff --git a/packages/cli/plugin-bff/tests/__snapshots__/loader.test.ts.snap b/packages/cli/plugin-bff/tests/__snapshots__/loader.test.ts.snap index 356be68a1918..640f081007ef 100644 --- a/packages/cli/plugin-bff/tests/__snapshots__/loader.test.ts.snap +++ b/packages/cli/plugin-bff/tests/__snapshots__/loader.test.ts.snap @@ -3,6 +3,6 @@ exports[`bff loader should works well 1`] = ` "import { createRequest } from '/packages/cli/plugin-bff/tests/fixtures/requestCreator/client'; -export var get = createRequest('/api/hello', 'GET', 80, 'functionName' ); +export var get = createRequest({"path":"/api/hello","method":"GET","port":80,"httpMethodDecider":"functionName"}); " `; diff --git a/packages/server/bff-core/src/client/generateClient.ts b/packages/server/bff-core/src/client/generateClient.ts index e442deb3e50e..d73fa3a8cdd6 100644 --- a/packages/server/bff-core/src/client/generateClient.ts +++ b/packages/server/bff-core/src/client/generateClient.ts @@ -18,6 +18,7 @@ export type GenClientOptions = { target?: string; requireResolve?: typeof require.resolve; httpMethodDecider?: HttpMethodDecider; + domain?: string; }; export const DEFAULT_CLIENT_REQUEST_CREATOR = '@modern-js/create-request'; @@ -34,13 +35,13 @@ export const generateClient = async ({ fetcher, requireResolve = require.resolve, httpMethodDecider, + domain, }: GenClientOptions): Promise => { if (!requestCreator) { requestCreator = requireResolve( `${DEFAULT_CLIENT_REQUEST_CREATOR}${target ? `/${target}` : ''}`, ).replace(/\\/g, '/'); } else { - // 这里约束传入的 requestCreator 包也必须有两个导出 client 和 server,因为目前的机制 client 和 server 要导出不同的 configure 函数;该 api 不对使用者暴露,后续可优化 let resolvedPath = requestCreator; try { resolvedPath = path.dirname(requireResolve(requestCreator)); @@ -58,7 +59,6 @@ export const generateClient = async ({ prefix, httpMethodDecider, }); - const handlerInfos = await apiRouter.getSingleModuleHandlers(resourcePath); if (!handlerInfos) { return Err(`generate client error: Cannot require module ${resourcePath}`); @@ -74,27 +74,29 @@ export const generateClient = async ({ const upperHttpMethod = httpMethod.toUpperCase(); const routeName = routePath; - if (action) { - handlersCode += `export ${exportStatement} createUploader('${routeName}');`; - } else if (target === 'server') { - handlersCode += `export ${exportStatement} createRequest('${routeName}', '${upperHttpMethod}', process.env.PORT || ${String( - port, - )}, '${httpMethodDecider ? httpMethodDecider : 'functionName'}' ${ - fetcher ? `, fetch` : '' - }); - `; + + if (action === 'upload') { + const requestOptions = { + path: routeName, + domain, + }; + handlersCode += `export ${exportStatement} createUploader(${JSON.stringify(requestOptions)});`; } else { - handlersCode += `export ${exportStatement} createRequest('${routeName}', '${upperHttpMethod}', ${String( - port, - )}, '${httpMethodDecider ? httpMethodDecider : 'functionName'}' ${ - fetcher ? `, fetch` : '' - }); + const requestOptions = { + path: routeName, + method: upperHttpMethod, + port: process.env.PORT || port, + httpMethodDecider: httpMethodDecider || 'functionName', + domain, + ...(fetcher ? { fetch: 'fetch' } : {}), + }; + handlersCode += `export ${exportStatement} createRequest(${JSON.stringify(requestOptions)}); `; } } const importCode = `import { createRequest${ - handlerInfos.find(i => i.action) ? ', createUploader' : '' + handlerInfos.find(i => i.action === 'upload') ? ', createUploader' : '' } } from '${requestCreator}'; ${fetcher ? `import { fetch } from '${fetcher}';\n` : ''}`; diff --git a/packages/server/bff-core/tests/client/__snapshots__/generateClient.test.ts.snap b/packages/server/bff-core/tests/client/__snapshots__/generateClient.test.ts.snap index ab7fab251506..485cf13dd313 100644 --- a/packages/server/bff-core/tests/client/__snapshots__/generateClient.test.ts.snap +++ b/packages/server/bff-core/tests/client/__snapshots__/generateClient.test.ts.snap @@ -3,16 +3,16 @@ exports[`client generateClient should support operator 1`] = ` "import { createRequest } from '@modern-js/create-request'; -export default createRequest('/normal/origin', 'GET', 3000, 'functionName' ); - export var DELETE = createRequest('/normal/origin', 'DELETE', 3000, 'functionName' ); - export var putRepo = createRequest('/put-repo', 'PUT', 3000, 'functionName' ); +export default createRequest({"path":"/normal/origin","method":"GET","port":3000,"httpMethodDecider":"functionName"}); + export var DELETE = createRequest({"path":"/normal/origin","method":"DELETE","port":3000,"httpMethodDecider":"functionName"}); + export var putRepo = createRequest({"path":"/put-repo","method":"PUT","port":3000,"httpMethodDecider":"functionName"}); " `; exports[`client generateClient should works correctly 1`] = ` "import { createRequest } from '@modern-js/create-request'; -export var get = createRequest('/api/:id/origin/foo', 'GET', 3000, 'functionName' ); - export var post = createRequest('/api/:id/origin/foo', 'POST', 3000, 'functionName' ); +export var get = createRequest({"path":"/api/:id/origin/foo","method":"GET","port":3000,"httpMethodDecider":"functionName"}); + export var post = createRequest({"path":"/api/:id/origin/foo","method":"POST","port":3000,"httpMethodDecider":"functionName"}); " `; diff --git a/packages/server/core/src/serverBase.ts b/packages/server/core/src/serverBase.ts index b8edc0031918..d392801b77b1 100644 --- a/packages/server/core/src/serverBase.ts +++ b/packages/server/core/src/serverBase.ts @@ -31,6 +31,7 @@ export type ServerBaseOptions = { sharedDirectory?: string; apiDirectory?: string; lambdaDirectory?: string; + indepBffPrefix?: string; }; runMode?: 'apiOnly' | 'ssrOnly' | 'webOnly'; }; @@ -96,11 +97,12 @@ export class ServerBase { internalDirectory: context?.internalDirectory || '', lambdaDirectory: context?.lambdaDirectory, sharedDirectory: context?.sharedDirectory || '', + indepBffPrefix: context?.indepBffPrefix || '', distDirectory: pwd, plugins: [], metaName: metaName || 'modern-js', serverBase: this, - }; + } as any; return createContext(appContext); } diff --git a/packages/server/create-request/src/browser.ts b/packages/server/create-request/src/browser.ts index 23532d7c24e0..ef209231a68f 100644 --- a/packages/server/create-request/src/browser.ts +++ b/packages/server/create-request/src/browser.ts @@ -5,8 +5,8 @@ import type { BFFRequestPayload, IOptions, RequestCreator, - RequestUploader, Sender, + UploadCreator, } from './types'; import { getUploadPayload } from './utiles'; @@ -34,13 +34,14 @@ export const configure = (options: IOptions) => { } }; -export const createRequest: RequestCreator = ( +export const createRequest: RequestCreator = ({ path, method, port, httpMethodDecider = 'functionName', // 后续可能要修改,暂时先保留 fetch = originFetch, -) => { + domain, +}) => { const getFinalPath = compile(path, { encode: encodeURIComponent }); const keys: Key[] = []; pathToRegexp(path, keys); @@ -119,6 +120,9 @@ export const createRequest: RequestCreator = ( headers.accept = `application/json,*/*;q=0.8`; + if (domain) { + finalURL = `${domain}${finalURL}`; + } return fetcher(finalURL, { method, body, @@ -129,11 +133,15 @@ export const createRequest: RequestCreator = ( return sender; }; -export const createUploader: RequestUploader = (path: string) => { +export const createUploader: UploadCreator = ({ path, domain }) => { const sender: Sender = (...args) => { const fetcher = realRequest || originFetch; const { body, headers } = getUploadPayload(args); - return fetcher(path, { method: 'POST', body, headers }); + return fetcher(domain ? `${domain}${path}` : path, { + method: 'POST', + body, + headers, + }); }; return sender; diff --git a/packages/server/create-request/src/node.ts b/packages/server/create-request/src/node.ts index f1b5e62ac01e..c5088f9711ec 100644 --- a/packages/server/create-request/src/node.ts +++ b/packages/server/create-request/src/node.ts @@ -7,8 +7,8 @@ import type { BFFRequestPayload, IOptions, RequestCreator, - RequestUploader, Sender, + UploadCreator, } from './types'; import { getUploadPayload } from './utiles'; @@ -37,14 +37,13 @@ export const configure = (options: IOptions) => { } }; -export const createRequest: RequestCreator = ( - path: string, - method: string, - port: number, - httpMethodDecider = 'functionName', - // 后续可能要修改,暂时先保留 - fetch = nodeFetch, -) => { +export const createRequest: RequestCreator = ({ + path, + method, + port, + httpMethodDecider = 'functionName', // 后续可能要修改,暂时先保留 + fetch = originFetch, +}) => { const getFinalPath = compile(path, { encode: encodeURIComponent }); const keys: Key[] = []; pathToRegexp(path, keys); @@ -130,7 +129,7 @@ export const createRequest: RequestCreator = ( return sender; }; -export const createUploader: RequestUploader = (path: string) => { +export const createUploader: UploadCreator = ({ path }) => { const sender: Sender = (...args) => { const fetcher = realRequest || originFetch; const { body, headers } = getUploadPayload(args); diff --git a/packages/server/create-request/src/types.ts b/packages/server/create-request/src/types.ts index bbbb619ca9ac..8c1a8415c13a 100644 --- a/packages/server/create-request/src/types.ts +++ b/packages/server/create-request/src/types.ts @@ -16,15 +16,25 @@ export type Sender = ((...args: any[]) => Promise) & { fetch?: F; }; +export type RequestOptions = { + path: string; + method: string; + port: number; + httpMethodDecider?: HttpMethodDecider; + domain?: string; + fetch?: F; +}; + export type RequestCreator = ( - path: string, - method: string, - port: number, - httpMethodDecider: HttpMethodDecider, - fetch?: F, + options: RequestOptions, ) => Sender; -export type RequestUploader = (path: string) => Sender; +export type UploadOptions = { + path: string; + domain?: string; +}; + +export type UploadCreator = (options: UploadOptions) => Sender; export type IOptions = { request?: F; diff --git a/packages/server/create-request/tests/browser.test.ts b/packages/server/create-request/tests/browser.test.ts index 9e1a3cc67bed..3f3030e94486 100644 --- a/packages/server/create-request/tests/browser.test.ts +++ b/packages/server/create-request/tests/browser.test.ts @@ -34,7 +34,11 @@ describe('configure', () => { }); configure({ request: customRequest }); - const request = createRequest(path, method, 8080, undefined); + const request = createRequest({ + path, + method, + port: 8080, + }); const res = await request(); const data = await res.json(); @@ -57,7 +61,11 @@ describe('configure', () => { }); configure({ request: customRequest }); - const request = createRequest(path, method, 8080, undefined); + const request = createRequest({ + path, + method, + port: 8080, + }); const res = await request({ query: { users: ['foo', 'bar'], @@ -78,7 +86,11 @@ describe('configure', () => { }); configure({ interceptor }); - const request = createRequest(path, method, 8080, undefined); + const request = createRequest({ + path, + method, + port: 8080, + }); const res = await request(); const data = await res.json(); @@ -100,7 +112,11 @@ describe('configure', () => { }); configure({ request: customRequest, interceptor }); - const request = createRequest(path, method, 8080, undefined); + const request = createRequest({ + path, + method, + port: 8080, + }); const res = await request(); const data = await res.json(); @@ -119,8 +135,11 @@ describe('configure', () => { }); configure({ interceptor }); - - const request = createRequest(`${path}/:id`, method, 8080, undefined); + const request = createRequest({ + path: `${path}/:id`, + method, + port: 8080, + }); const res = await request('modernjs'); const data = await res.json(); expect(res instanceof Response).toBe(true); @@ -137,7 +156,11 @@ describe('configure', () => { configure({ interceptor }); - const request = createRequest(`${path}/:id`, method, 8080, undefined); + const request = createRequest({ + path: `${path}/:id`, + method, + port: 8080, + }); const res = await request({ params: { id: 'modernjs', diff --git a/packages/server/create-request/tests/node.test.ts b/packages/server/create-request/tests/node.test.ts index 3c2c05ba1c4b..769ad33aaf9f 100644 --- a/packages/server/create-request/tests/node.test.ts +++ b/packages/server/create-request/tests/node.test.ts @@ -41,7 +41,7 @@ describe('configure', () => { const customRequest = jest.fn((requestPath: any) => fetch(requestPath)); configure({ request: customRequest as unknown as typeof fetch }); - const request = createRequest(path, method, port); + const request = createRequest({ path, method, port }); const res = await request(); const data = await res.json(); @@ -72,7 +72,7 @@ describe('configure', () => { const customRequest = jest.fn((requestPath: any) => fetch(requestPath)); configure({ request: customRequest as unknown as typeof fetch }); - const request = createRequest(path, method, port); + const request = createRequest({ path, method, port }); const res = await request({ query: { users: ['foo', 'bar'], @@ -96,7 +96,7 @@ describe('configure', () => { ); configure({ interceptor: interceptor as any }); - const request = createRequest(path, method, 8080); + const request = createRequest({ path, method, port: 8080 }); const res = await request(); const data = await res.json(); @@ -120,7 +120,7 @@ describe('configure', () => { request: customRequest as unknown as typeof fetch, interceptor: interceptor as any, }); - const request = createRequest(path, method, 8080); + const request = createRequest({ path, method, port: 8080 }); const res = await request(); const data = await res.json(); @@ -149,7 +149,7 @@ describe('configure', () => { .reply(200, response); configure({ allowedHeaders: ['authorization'] }); - const request = createRequest(path, method, 8080); + const request = createRequest({ path, method, port: 8080 }); const data = await request(); expect(data).toStrictEqual(response); @@ -168,7 +168,11 @@ describe('configure', () => { configure({ interceptor: interceptor as any }); - const request = createRequest(`${path}/:id`, method, 8080, undefined); + const request = createRequest({ + path: `${path}/:id`, + method, + port: 8080, + }); const res = await request('modernjs'); const data = await res.json(); expect(res instanceof Response).toBe(true); @@ -187,7 +191,11 @@ describe('configure', () => { configure({ interceptor: interceptor as any }); - const request = createRequest(`${path}/:id`, method, 8080, undefined); + const request = createRequest({ + path: `${path}/:id`, + method, + port: 8080, + }); const res = await request({ params: { id: 'modernjs', diff --git a/packages/server/plugin-express/src/cli/index.ts b/packages/server/plugin-express/src/cli/index.ts index 7d57228c0af7..59df1d8d7068 100644 --- a/packages/server/plugin-express/src/cli/index.ts +++ b/packages/server/plugin-express/src/cli/index.ts @@ -16,10 +16,12 @@ export const expressPlugin = (): CliPlugin => ({ appContext.internalDirectory, 'server', ); - const runtimePath = - process.env.NODE_ENV === 'development' - ? require.resolve('@modern-js/plugin-express/runtime') - : '@modern-js/plugin-express/runtime'; + // const runtimePath = + // process.env.NODE_ENV === 'development' + // ? require.resolve('@modern-js/plugin-express/runtime') + // : '@modern-js/plugin-express/runtime'; + + const runtimePath = '@modern-js/plugin-express/runtime'; return { source: { alias: { diff --git a/packages/solutions/app-tools/src/commands/dev.ts b/packages/solutions/app-tools/src/commands/dev.ts index 1da2aa67cdee..909482a9c33c 100644 --- a/packages/solutions/app-tools/src/commands/dev.ts +++ b/packages/solutions/app-tools/src/commands/dev.ts @@ -101,6 +101,7 @@ export const dev = async ( internalDirectory: appContext.internalDirectory, apiDirectory: appContext.apiDirectory, lambdaDirectory: appContext.lambdaDirectory, + indepBffPrefix: appContext.indepBffPrefix, sharedDirectory: appContext.sharedDirectory, }, serverConfigPath, @@ -110,7 +111,7 @@ export const dev = async ( serverConfigFile, plugins: pluginInstances, ...devServerOptions, - }; + } as any; const host = normalizedConfig.dev?.host || DEFAULT_DEV_HOST; @@ -132,6 +133,7 @@ export const dev = async ( printInstructions(hookRunners, appContext, normalizedConfig); }, ); + setServer(server); } else { const { server, afterListen } = await createDevServer( { diff --git a/packages/solutions/app-tools/src/index.ts b/packages/solutions/app-tools/src/index.ts index 421f8daa0dd8..53a70094c61c 100644 --- a/packages/solutions/app-tools/src/index.ts +++ b/packages/solutions/app-tools/src/index.ts @@ -11,6 +11,7 @@ export type { RuntimeUserConfig } from './types/config'; export { dev } from './commands/dev'; export type { DevOptions } from './utils/types'; +export { generateWatchFiles } from './utils/generateWatchFiles'; export * from './types'; diff --git a/packages/solutions/app-tools/src/plugins/deploy/index.ts b/packages/solutions/app-tools/src/plugins/deploy/index.ts index 7099c1efbc37..66ce6248adc0 100644 --- a/packages/solutions/app-tools/src/plugins/deploy/index.ts +++ b/packages/solutions/app-tools/src/plugins/deploy/index.ts @@ -48,6 +48,7 @@ export default (): CliPlugin => ({ setup: api => { const deployTarget = process.env.MODERNJS_DEPLOY || provider || 'node'; + console.log('deployTarget', deployTarget); return { async deploy() { const appContext = api.useAppContext(); diff --git a/packages/solutions/app-tools/src/plugins/deploy/platforms/node.ts b/packages/solutions/app-tools/src/plugins/deploy/platforms/node.ts index a953b0a2dc2d..5c60de3209e2 100644 --- a/packages/solutions/app-tools/src/plugins/deploy/platforms/node.ts +++ b/packages/solutions/app-tools/src/plugins/deploy/platforms/node.ts @@ -49,6 +49,7 @@ export const createNodePreset: CreatePreset = (appContext, config) => { }; const pluginImportCode = genPluginImportsCode(plugins || []); + const dynamicProdOptions = { config: serverConfig, serverConfigFile: DEFAULT_SERVER_CONFIG, @@ -71,6 +72,8 @@ export const createNodePreset: CreatePreset = (appContext, config) => { .replace('p_apiDirectory', serverAppContext.apiDirectory) .replace('p_lambdaDirectory', serverAppContext.lambdaDirectory); + console.log('isEsmProject', isEsmProject); + if (isEsmProject) { // We will not modify the entry file for the time, because we have not yet converted all the packages available for esm. const cjsEntryFilePath = path.join(outputDirectory, 'index.cjs'); diff --git a/packages/toolkit/utils/src/cli/require.ts b/packages/toolkit/utils/src/cli/require.ts index 23e6ccdc8748..3729cd4a4560 100644 --- a/packages/toolkit/utils/src/cli/require.ts +++ b/packages/toolkit/utils/src/cli/require.ts @@ -23,6 +23,7 @@ export async function compatibleRequire( const modulePath = isAbsolute(path) ? pathToFileURL(path).href : path; if (process.env.NODE_ENV === 'development') { const timestamp = Date.now(); + requiredModule = await import(`${modulePath}?t=${timestamp}`); } else { requiredModule = await import(modulePath); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2a26184dbbe0..9f8f603c1fd3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5197,6 +5197,113 @@ importers: specifier: ^5 version: 5.3.3 + tests/integration/bff-api-app: + dependencies: + '@modern-js/plugin-bff': + specifier: workspace:* + version: link:../../../packages/cli/plugin-bff + '@modern-js/plugin-express': + specifier: workspace:* + version: link:../../../packages/server/plugin-express + '@modern-js/runtime': + specifier: workspace:* + version: link:../../../packages/runtime/plugin-runtime + express: + specifier: ^4.17.2 + version: 4.21.1 + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@swc/core@1.3.42)(@types/node@14.18.35)(typescript@5.6.3) + tsconfig-paths: + specifier: 3.14.1 + version: 3.14.1 + type-fest: + specifier: 2.15.0 + version: 2.15.0 + zod: + specifier: ^3.22.3 + version: 3.22.3 + devDependencies: + '@modern-js/app-tools': + specifier: workspace:* + version: link:../../../packages/solutions/app-tools + '@types/express': + specifier: ^4.17.13 + version: 4.17.21 + '@types/jest': + specifier: ^29 + version: 29.5.14 + '@types/node': + specifier: ^14 + version: 14.18.35 + '@types/react': + specifier: ^18.3.11 + version: 18.3.12 + '@types/react-dom': + specifier: ^18.3.1 + version: 18.3.1 + typescript: + specifier: ^5 + version: 5.6.3 + + tests/integration/bff-client-app: + dependencies: + '@modern-js/plugin-express': + specifier: workspace:* + version: link:../../../packages/server/plugin-express + '@modern-js/runtime': + specifier: workspace:* + version: link:../../../packages/runtime/plugin-runtime + bff-api-app: + specifier: workspace:* + version: link:../bff-api-app + express: + specifier: ^4.17.2 + version: 4.21.1 + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@swc/core@1.3.42)(@types/node@14.18.35)(typescript@5.6.3) + tsconfig-paths: + specifier: 3.14.1 + version: 3.14.1 + zod: + specifier: ^3.22.3 + version: 3.22.3 + devDependencies: + '@modern-js/app-tools': + specifier: workspace:* + version: link:../../../packages/solutions/app-tools + '@types/express': + specifier: ^4.17.13 + version: 4.17.21 + '@types/jest': + specifier: ^29 + version: 29.5.14 + '@types/node': + specifier: ^14 + version: 14.18.35 + '@types/react': + specifier: ^18.3.11 + version: 18.3.12 + '@types/react-dom': + specifier: ^18.3.1 + version: 18.3.1 + typescript: + specifier: ^5 + version: 5.6.3 + tests/integration/bff-express: dependencies: '@modern-js/plugin-bff': @@ -12381,9 +12488,6 @@ packages: '@types/express@4.17.21': resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} - '@types/express@5.0.0': - resolution: {integrity: sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==} - '@types/finalhandler@1.2.3': resolution: {integrity: sha512-I+Ba0JZEiuSr8LLjVmBhvLBEN8KG9GSITNXWwPCLeAvZj/k5pXEdOBEvnEEIgA038eeaauJ3BPxbuxeFBsqqUw==} @@ -15550,9 +15654,6 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - function-bind@1.1.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -15585,9 +15686,6 @@ packages: get-func-name@2.0.0: resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==} - get-intrinsic@1.2.0: - resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==} - get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} @@ -15787,9 +15885,6 @@ packages: resolution: {integrity: sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==} engines: {node: '>=8'} - has-property-descriptors@1.0.0: - resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} - has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} @@ -21732,9 +21827,6 @@ packages: zod@3.22.3: resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==} - zod@3.23.8: - resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} - zwitch@1.0.5: resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==} @@ -26872,8 +26964,8 @@ snapshots: tapable: 2.2.1 watchpack: 2.4.2 webpack-sources: 3.2.3 - zod: 3.23.8 - zod-validation-error: 1.3.1(zod@3.23.8) + zod: 3.22.3 + zod-validation-error: 1.3.1(zod@3.22.3) optionalDependencies: '@swc/helpers': 0.5.13 optional: true @@ -27981,7 +28073,7 @@ snapshots: '@types/cookies@0.7.7': dependencies: '@types/connect': 3.4.38 - '@types/express': 5.0.0 + '@types/express': 4.17.21 '@types/keygrip': 1.0.2 '@types/node': 18.19.64 @@ -28052,13 +28144,6 @@ snapshots: '@types/qs': 6.9.16 '@types/serve-static': 1.15.7 - '@types/express@5.0.0': - dependencies: - '@types/body-parser': 1.19.5 - '@types/express-serve-static-core': 5.0.0 - '@types/qs': 6.9.16 - '@types/serve-static': 1.15.7 - '@types/finalhandler@1.2.3': dependencies: '@types/node': 18.19.55 @@ -29614,8 +29699,8 @@ snapshots: call-bind@1.0.2: dependencies: - function-bind: 1.1.1 - get-intrinsic: 1.2.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 call-bind@1.0.7: dependencies: @@ -30554,7 +30639,7 @@ snapshots: define-properties@1.1.4: dependencies: - has-property-descriptors: 1.0.0 + has-property-descriptors: 1.0.2 object-keys: 1.1.1 defined@1.0.0: {} @@ -31823,8 +31908,6 @@ snapshots: fsevents@2.3.3: optional: true - function-bind@1.1.1: {} - function-bind@1.1.2: {} function.prototype.name@1.1.5: @@ -31868,12 +31951,6 @@ snapshots: get-func-name@2.0.0: {} - get-intrinsic@1.2.0: - dependencies: - function-bind: 1.1.2 - has: 1.0.3 - has-symbols: 1.0.3 - get-intrinsic@1.2.4: dependencies: es-errors: 1.3.0 @@ -32124,10 +32201,6 @@ snapshots: has-own-prop@2.0.0: {} - has-property-descriptors@1.0.0: - dependencies: - get-intrinsic: 1.2.4 - has-property-descriptors@1.0.2: dependencies: es-define-property: 1.0.0 @@ -38875,7 +38948,6 @@ snapshots: yn: 3.1.1 optionalDependencies: '@swc/core': 1.3.42 - optional: true ts-node@10.9.2(@swc/core@1.3.42)(@types/node@16.11.68)(typescript@5.0.4): dependencies: @@ -40055,16 +40127,13 @@ snapshots: optionalDependencies: commander: 9.5.0 - zod-validation-error@1.3.1(zod@3.23.8): + zod-validation-error@1.3.1(zod@3.22.3): dependencies: - zod: 3.23.8 + zod: 3.22.3 optional: true zod@3.22.3: {} - zod@3.23.8: - optional: true - zwitch@1.0.5: {} zwitch@2.0.4: {} diff --git a/tests/integration/bff-api-app/api/_app.ts b/tests/integration/bff-api-app/api/_app.ts new file mode 100644 index 000000000000..dee27367d142 --- /dev/null +++ b/tests/integration/bff-api-app/api/_app.ts @@ -0,0 +1,10 @@ +import { hook } from '@modern-js/runtime/server'; +import type { NextFunction, Request, Response } from 'express'; + +export default hook(({ addMiddleware }) => { + addMiddleware(async (req: Request, res: Response, next: NextFunction) => { + res.setHeader('x-env', 'test'); + res.setHeader('Access-Control-Allow-Origin', '*'); + next(); + }); +}); diff --git a/tests/integration/bff-api-app/api/index.ts b/tests/integration/bff-api-app/api/index.ts new file mode 100644 index 000000000000..8e3751a61615 --- /dev/null +++ b/tests/integration/bff-api-app/api/index.ts @@ -0,0 +1,13 @@ +export default async () => { + console.log('Hello get bff-api-app'); + return { + message: 'Hello get bff-api-app1', + }; +}; + +export const post = async () => { + console.log('Hello post bff-api-app'); + return { + message: 'Hello post bff-api-app', + }; +}; diff --git a/tests/integration/bff-api-app/api/upload.ts b/tests/integration/bff-api-app/api/upload.ts new file mode 100644 index 000000000000..73fa2ecf5eb2 --- /dev/null +++ b/tests/integration/bff-api-app/api/upload.ts @@ -0,0 +1,19 @@ +import { Api, Upload } from '@modern-js/runtime/server'; +import { z } from 'zod'; + +const FileSchema = z.object({ + images: z.record(z.string(), z.any()), +}); + +export const upload = Api( + Upload('/upload', FileSchema), + async ({ formData }) => { + // do somethings + return { + data: { + code: 0, + file_name: formData.images.name, + }, + }; + }, +); diff --git a/tests/integration/bff-api-app/api/user.ts b/tests/integration/bff-api-app/api/user.ts new file mode 100644 index 000000000000..c59fc4e0dba2 --- /dev/null +++ b/tests/integration/bff-api-app/api/user.ts @@ -0,0 +1,28 @@ +import { Api, Post, Query } from '@modern-js/runtime/server'; +import { z } from 'zod'; + +const UserSchema = z.object({ + phone: z.string(), +}); + +export const email = Api(Post('/email'), Query(UserSchema), async () => { + return { + data: { + code: 0, + }, + }; +}); + +export default async () => { + console.log('User get bff-api-app'); + return { + message: 'User get bff-api-app', + }; +}; + +export const post = async () => { + console.log('User post bff-api-app'); + return { + message: 'User post bff-api-app12', + }; +}; diff --git a/tests/integration/bff-api-app/modern-app-env.d.ts b/tests/integration/bff-api-app/modern-app-env.d.ts new file mode 100644 index 000000000000..eaf4337e191b --- /dev/null +++ b/tests/integration/bff-api-app/modern-app-env.d.ts @@ -0,0 +1,4 @@ +/// +/// +/// +/// diff --git a/tests/integration/bff-api-app/modern.config.ts b/tests/integration/bff-api-app/modern.config.ts new file mode 100644 index 000000000000..8284f216db87 --- /dev/null +++ b/tests/integration/bff-api-app/modern.config.ts @@ -0,0 +1,16 @@ +import { bffPlugin } from '@modern-js/plugin-bff'; +import { expressPlugin } from '@modern-js/plugin-express'; +import { applyBaseConfig } from '../../utils/applyBaseConfig'; + +export default applyBaseConfig({ + bff: { + prefix: '/api-app', + }, + plugins: [ + expressPlugin(), + bffPlugin({ + projectType: 'api', + // fetchDomain: 'http://localhost:8080', + }), + ], +}); diff --git a/tests/integration/bff-api-app/package.json b/tests/integration/bff-api-app/package.json new file mode 100644 index 000000000000..dbb45bd78fa7 --- /dev/null +++ b/tests/integration/bff-api-app/package.json @@ -0,0 +1,56 @@ +{ + "name": "bff-api-app", + "version": "0.0.2", + "scripts": { + "dev": "modern dev", + "dev:bff": "modern dev --api-only", + "build": "modern build", + "serve": "modern serve", + "deploy": "modern deploy", + "start:bff": "modern serve --api-only", + "new": "modern new" + }, + "engines": { + "node": ">=14.17.6" + }, + "dependencies": { + "@modern-js/plugin-express": "workspace:*", + "@modern-js/plugin-bff": "workspace:*", + "@modern-js/runtime": "workspace:*", + "express": "^4.17.2", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "ts-node": "^10.9.1", + "tsconfig-paths": "3.14.1", + "type-fest": "2.15.0", + "zod": "^3.22.3" + }, + "devDependencies": { + "@modern-js/app-tools": "workspace:*", + "@types/express": "^4.17.13", + "@types/jest": "^29", + "@types/node": "^14", + "@types/react": "^18.3.11", + "@types/react-dom": "^18.3.1", + "typescript": "^5" + }, + "exports": { + "./_app": { + "import": "./dist/client/_app.js", + "types": "./dist/api/_app.d.ts" + }, + "./index": { + "import": "./dist/client/index.js", + "types": "./dist/api/index.d.ts" + }, + "./upload": { + "import": "./dist/client/upload.js", + "types": "./dist/api/upload.d.ts" + }, + "./user": { + "import": "./dist/client/user.js", + "types": "./dist/api/user.d.ts" + }, + "./server-plugin": "./dist/server-plugin/index.js" + } +} \ No newline at end of file diff --git a/tests/integration/bff-api-app/tsconfig.json b/tests/integration/bff-api-app/tsconfig.json new file mode 100644 index 000000000000..fde6ed5679f0 --- /dev/null +++ b/tests/integration/bff-api-app/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "@modern-js/tsconfig/base", + "compilerOptions": { + "declaration": true, + "jsx": "preserve", + "baseUrl": "./", + "types": ["jest"] + }, + "include": [ + "shared", + "config", + "api", + "modern.config.ts", + "modern-app-env.d.ts" + ], + "exclude": ["node_modules", "dist"] +} diff --git a/tests/integration/bff-client-app/api/_app.ts b/tests/integration/bff-client-app/api/_app.ts new file mode 100644 index 000000000000..c8e27bf0d679 --- /dev/null +++ b/tests/integration/bff-client-app/api/_app.ts @@ -0,0 +1,12 @@ +import { hook } from '@modern-js/runtime/server'; +import type { NextFunction, Request, Response } from 'express'; + +export default hook(({ addMiddleware }) => { + addMiddleware(async (req: Request, res: Response, next: NextFunction) => { + if (req.path === '/bff-api/client-app') { + res.end('client-app'); + } else { + next(); + } + }); +}); diff --git a/tests/integration/bff-client-app/api/index.ts b/tests/integration/bff-client-app/api/index.ts new file mode 100644 index 000000000000..34769c8031ba --- /dev/null +++ b/tests/integration/bff-client-app/api/index.ts @@ -0,0 +1,7 @@ +export default async () => ({ + message: 'Hello Modern.js get client', +}); + +export const post = async () => ({ + message: 'Hello Modern.js post client', +}); diff --git a/tests/integration/bff-client-app/modern.config.ts b/tests/integration/bff-client-app/modern.config.ts new file mode 100644 index 000000000000..a4a3747a673c --- /dev/null +++ b/tests/integration/bff-client-app/modern.config.ts @@ -0,0 +1,12 @@ +import { bffPlugin } from '@modern-js/plugin-bff'; +import { expressPlugin } from '@modern-js/plugin-express'; +import { serverPlugin } from 'bff-api-app/server-plugin'; +import { applyBaseConfig } from '../../utils/applyBaseConfig'; + +export default applyBaseConfig({ + bff: { + prefix: '/web-app', + }, + plugins: [bffPlugin(), expressPlugin(), serverPlugin()], + // plugins: [bffPlugin(), expressPlugin()], +}); diff --git a/tests/integration/bff-client-app/package.json b/tests/integration/bff-client-app/package.json new file mode 100644 index 000000000000..022403dacd6c --- /dev/null +++ b/tests/integration/bff-client-app/package.json @@ -0,0 +1,36 @@ +{ + "private": true, + "name": "bff-client-app", + "version": "2.9.0", + "scripts": { + "dev": "modern dev", + "dev:bff": "modern dev --api-only", + "build": "modern build", + "serve": "modern serve", + "start:bff": "modern serve --api-only", + "new": "modern new" + }, + "engines": { + "node": ">=14.17.6" + }, + "dependencies": { + "@modern-js/plugin-express": "workspace:*", + "@modern-js/runtime": "workspace:*", + "express": "^4.17.2", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "ts-node": "^10.9.1", + "tsconfig-paths": "3.14.1", + "bff-api-app": "workspace:*", + "zod": "^3.22.3" + }, + "devDependencies": { + "@modern-js/app-tools": "workspace:*", + "@types/express": "^4.17.13", + "@types/jest": "^29", + "@types/node": "^14", + "@types/react": "^18.3.11", + "@types/react-dom": "^18.3.1", + "typescript": "^5" + } +} diff --git a/tests/integration/bff-client-app/src/base/App.tsx b/tests/integration/bff-client-app/src/base/App.tsx new file mode 100644 index 000000000000..7c75ad59906e --- /dev/null +++ b/tests/integration/bff-client-app/src/base/App.tsx @@ -0,0 +1,59 @@ +// import getself from '@api/index'; +import get from 'bff-api-app/index'; + +import { configure } from '@modern-js/runtime/bff'; +import { post } from 'bff-api-app/index'; +import { upload } from 'bff-api-app/upload'; +import { post as postUser } from 'bff-api-app/user'; + +import { useEffect, useState } from 'react'; + +const getMockImage = () => { + const imageData = + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='; + const blob = new Blob( + [Uint8Array.from(atob(imageData.split(',')[1]), c => c.charCodeAt(0))], + { type: 'image/png' }, + ); + + return new File([blob], 'mock_image.png', { type: 'image/png' }); +}; + +// configure({ +// interceptor(request) { +// return async (path, params) => { +// // const url = `http://localhost:8080${path}`; +// const url = (path as string).replace(/^\/api-app/, '/api-client'); + +// const res = await request(url, params); +// return res.json(); +// }; +// }, +// }); + +const App = () => { + const [message, setMessage] = useState('bff-express'); + useEffect(() => { + const fetchData = async () => { + const res = await get(); + console.log('res', res); + + await new Promise(resolve => setTimeout(resolve, 50)); + console.log('res.message', res.message); + setMessage(res.message); + }; + + fetchData(); + post(); + postUser(); + + upload({ + files: { + images: getMockImage(), + }, + }); + }, []); + return
{message}
; +}; + +export default App; diff --git a/tests/integration/bff-client-app/src/modern-app-env.d.ts b/tests/integration/bff-client-app/src/modern-app-env.d.ts new file mode 100644 index 000000000000..eaf4337e191b --- /dev/null +++ b/tests/integration/bff-client-app/src/modern-app-env.d.ts @@ -0,0 +1,4 @@ +/// +/// +/// +/// diff --git a/tests/integration/bff-client-app/tsconfig.json b/tests/integration/bff-client-app/tsconfig.json new file mode 100644 index 000000000000..1141e5c1bb00 --- /dev/null +++ b/tests/integration/bff-client-app/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "@modern-js/tsconfig/base", + "compilerOptions": { + "declaration": false, + "module": "nodenext", + "moduleResolution": "nodenext", + "jsx": "preserve", + "baseUrl": "./", + "paths": { + "@/*": ["./src/*"], + "@shared/*": ["./shared/*"], + "@api/*": ["./api/*"] + }, + "types": ["jest"] + }, + "include": ["src", "shared", "config", "api", "modern.config.ts"] +} diff --git a/tests/integration/bff-express/api/upload.ts b/tests/integration/bff-express/api/upload.ts index 73fa2ecf5eb2..4e052da86ec6 100644 --- a/tests/integration/bff-express/api/upload.ts +++ b/tests/integration/bff-express/api/upload.ts @@ -11,7 +11,7 @@ export const upload = Api( // do somethings return { data: { - code: 0, + code: 10, file_name: formData.images.name, }, };