From 7d8a757ffa6afa0be051cdf4f60440b2fc733927 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Wed, 7 Sep 2022 13:52:23 +0200 Subject: [PATCH] feat: new React hook `useData()` enabling using Telefunc for SSR data fetching --- examples/vite-plugin-ssr/package.json | 2 +- .../vite-plugin-ssr/pages/index/TodoList.tsx | 11 ++++------ examples/vite-plugin-ssr/vite.config.js | 3 +-- pnpm-lock.yaml | 8 +++---- telefunc/client/react/index.ts | 1 + telefunc/node/react/index.ts | 1 + .../server/runTelefunc/getTelefunctionKey.ts | 11 ---------- .../server/runTelefunc/getTelefunctions.ts | 7 ++++--- .../server/runTelefunc/parseHttpRequest.ts | 5 ++--- .../transformer/transformTelefuncFileSSR.ts | 21 +++++++++++++------ .../transformer/transformTelefuncFileSync.ts | 17 +++++++++++---- telefunc/node/transformer/utils.ts | 2 ++ telefunc/node/utils.ts | 3 ++- telefunc/node/vite/transform.ts | 2 ++ telefunc/package.json | 2 ++ telefunc/react.ts | 3 +++ telefunc/shared/react/index.ts | 1 + telefunc/shared/react/useData.ts | 17 +++++++++++++++ telefunc/shared/react/utils.ts | 1 + telefunc/utils/getTelefunctionKey.ts | 7 +++++++ 20 files changed, 83 insertions(+), 42 deletions(-) create mode 100644 telefunc/client/react/index.ts create mode 100644 telefunc/node/react/index.ts delete mode 100644 telefunc/node/server/runTelefunc/getTelefunctionKey.ts create mode 100644 telefunc/node/transformer/utils.ts create mode 100644 telefunc/react.ts create mode 100644 telefunc/shared/react/index.ts create mode 100644 telefunc/shared/react/useData.ts create mode 100644 telefunc/shared/react/utils.ts create mode 100644 telefunc/utils/getTelefunctionKey.ts diff --git a/examples/vite-plugin-ssr/package.json b/examples/vite-plugin-ssr/package.json index 07f5b4e8..971e47cd 100644 --- a/examples/vite-plugin-ssr/package.json +++ b/examples/vite-plugin-ssr/package.json @@ -14,7 +14,7 @@ "express": "^4.17.1", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-streaming": "^0.3.1", + "react-streaming": "^0.3.2", "telefunc": "0.1.25", "typescript": "^4.7.4", "vite": "^3.0.9", diff --git a/examples/vite-plugin-ssr/pages/index/TodoList.tsx b/examples/vite-plugin-ssr/pages/index/TodoList.tsx index 9d78ef59..c335d21e 100644 --- a/examples/vite-plugin-ssr/pages/index/TodoList.tsx +++ b/examples/vite-plugin-ssr/pages/index/TodoList.tsx @@ -1,14 +1,11 @@ +export { TodoList } + import React, { useState } from 'react' import { onNewTodo, loadTodoItems } from './TodoList.telefunc.js' -import { useAsync } from 'react-streaming' - -export { TodoList } +import { useData } from 'telefunc/react' function TodoList() { - const todoItemsInitial = useAsync('loadTodoItems', async () => { - const todoItems = await loadTodoItems() - return todoItems - }) + const todoItemsInitial = useData(loadTodoItems) const [todoItems, setTodoItems] = useState(todoItemsInitial) const [draft, setDraft] = useState('') return ( diff --git a/examples/vite-plugin-ssr/vite.config.js b/examples/vite-plugin-ssr/vite.config.js index 322d3d60..1e56cf31 100644 --- a/examples/vite-plugin-ssr/vite.config.js +++ b/examples/vite-plugin-ssr/vite.config.js @@ -3,6 +3,5 @@ import react from '@vitejs/plugin-react' import ssr from 'vite-plugin-ssr/plugin' export default { - plugins: [react(), ssr(), telefunc()], - ssr: { external: ['react-streaming'] } + plugins: [react(), ssr(), telefunc()] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ade8308a..8c4c5b88 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -177,7 +177,7 @@ importers: express: ^4.17.1 react: ^18.2.0 react-dom: ^18.2.0 - react-streaming: ^0.3.1 + react-streaming: ^0.3.2 telefunc: 0.1.25 typescript: ^4.7.4 vite: ^3.0.9 @@ -190,7 +190,7 @@ importers: express: 4.17.2 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 - react-streaming: 0.3.1_biqbaboplfbrettd7655fr4n2y + react-streaming: 0.3.2_biqbaboplfbrettd7655fr4n2y telefunc: link:../../telefunc typescript: 4.7.4 vite: 3.0.9 @@ -14433,8 +14433,8 @@ packages: engines: {node: '>=0.10.0'} dev: false - /react-streaming/0.3.1_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-a6G9Fj7V7kumW9qlWcWbS+ViGS/WHxjiYTjOHsxjkHLNaQVU7D6CeNxpPtkih7ARY2OurUWJe0EGiftTONlErQ==} + /react-streaming/0.3.2_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-VED+77shvmGkREefO6B3EvJ6lLID/JUn3QkUMY3CVwlhGwMdsazfME89oChI/UlZUp5Y5a5OkVl9z5GI5aZkAg==} peerDependencies: react: '>=18' react-dom: '>=18' diff --git a/telefunc/client/react/index.ts b/telefunc/client/react/index.ts new file mode 100644 index 00000000..687d1f11 --- /dev/null +++ b/telefunc/client/react/index.ts @@ -0,0 +1 @@ +export * from '../../shared/react' diff --git a/telefunc/node/react/index.ts b/telefunc/node/react/index.ts new file mode 100644 index 00000000..687d1f11 --- /dev/null +++ b/telefunc/node/react/index.ts @@ -0,0 +1 @@ +export * from '../../shared/react' diff --git a/telefunc/node/server/runTelefunc/getTelefunctionKey.ts b/telefunc/node/server/runTelefunc/getTelefunctionKey.ts deleted file mode 100644 index 62e7e4fa..00000000 --- a/telefunc/node/server/runTelefunc/getTelefunctionKey.ts +++ /dev/null @@ -1,11 +0,0 @@ -export { getTelefunctionKey } - -function getTelefunctionKey({ - telefunctionFilePath, - telefunctionFileExport -}: { - telefunctionFilePath: string - telefunctionFileExport: string -}) { - return telefunctionFilePath + ':' + telefunctionFileExport -} diff --git a/telefunc/node/server/runTelefunc/getTelefunctions.ts b/telefunc/node/server/runTelefunc/getTelefunctions.ts index 85078c4d..90eb6610 100644 --- a/telefunc/node/server/runTelefunc/getTelefunctions.ts +++ b/telefunc/node/server/runTelefunc/getTelefunctions.ts @@ -1,8 +1,7 @@ export { getTelefunctions } -import { assertUsage, isCallable } from '../../utils' +import { assertUsage, isCallable, getTelefunctionKey } from '../../utils' import type { Telefunction, TelefuncFiles } from '../types' -import { getTelefunctionKey } from './getTelefunctionKey' import { getTelefunctionName } from './getTelefunctionName' async function getTelefunctions(runContext: { telefuncFilesLoaded: TelefuncFiles }): Promise<{ @@ -11,12 +10,14 @@ async function getTelefunctions(runContext: { telefuncFilesLoaded: TelefuncFiles const telefunctions: Record = {} Object.entries(runContext.telefuncFilesLoaded).forEach(([telefunctionFilePath, telefuncFileExports]) => { Object.entries(telefuncFileExports).forEach(([telefunctionFileExport, exportValue]) => { - const telefunctionKey = getTelefunctionKey({ telefunctionFilePath, telefunctionFileExport }) + const telefunctionKey = getTelefunctionKey(telefunctionFilePath, telefunctionFileExport) assertTelefunction(exportValue, { telefunctionFileExport, telefunctionFilePath }) telefunctions[telefunctionKey] = exportValue + // @ts-ignore + // telefunctions[telefunctionKey]._key = telefunctionKey }) }) diff --git a/telefunc/node/server/runTelefunc/parseHttpRequest.ts b/telefunc/node/server/runTelefunc/parseHttpRequest.ts index 5e516e77..8856c44b 100644 --- a/telefunc/node/server/runTelefunc/parseHttpRequest.ts +++ b/telefunc/node/server/runTelefunc/parseHttpRequest.ts @@ -1,8 +1,7 @@ export { parseHttpRequest } import { parse } from '@brillout/json-s/parse' -import { getTelefunctionKey } from './getTelefunctionKey' -import { assertUsage, hasProp, getProjectError, getUrlPathname, assert } from '../../utils' +import { assertUsage, hasProp, getProjectError, getUrlPathname, assert, getTelefunctionKey } from '../../utils' import { getTelefunctionName } from './getTelefunctionName' function parseHttpRequest(runContext: { @@ -75,7 +74,7 @@ function parseHttpRequest(runContext: { const telefunctionFilePath = bodyParsed.file const telefunctionFileExport = bodyParsed.name const telefunctionArgs = bodyParsed.args - const telefunctionKey = getTelefunctionKey({ telefunctionFilePath, telefunctionFileExport }) + const telefunctionKey = getTelefunctionKey(telefunctionFilePath, telefunctionFileExport) const telefunctionName = getTelefunctionName({ telefunctionFilePath, telefunctionFileExport }) return { diff --git a/telefunc/node/transformer/transformTelefuncFileSSR.ts b/telefunc/node/transformer/transformTelefuncFileSSR.ts index 23ef4ad7..dc6bb427 100644 --- a/telefunc/node/transformer/transformTelefuncFileSSR.ts +++ b/telefunc/node/transformer/transformTelefuncFileSSR.ts @@ -1,7 +1,7 @@ export { transformTelefuncFileSSR } import { getExportNames } from './getExportNames' -import { assertPosixPath } from '../utils' +import { assertPosixPath, getTelefunctionKey } from './utils' async function transformTelefuncFileSSR(src: string, id: string, root: string) { assertPosixPath(id) @@ -18,16 +18,25 @@ async function transformTelefuncFileSSR(src: string, id: string, root: string) { function getCode(exportNames: readonly string[], src: string, filePath: string) { assertPosixPath(filePath) - const telefuncImport = 'import { __internal_addTelefunction } from "telefunc";' + let code = src; - // No break line between `telefuncImport` and `src` in order to preserve the source map's line mapping - let code = telefuncImport + src + { + const telefuncImport = 'import { __internal_addTelefunction } from "telefunc";' + // No break line between `telefuncImport` and `src` in order to preserve the source map's line mapping + code = telefuncImport + src + } + + const extraLines: string[] = [] code += '\n\n' for (const exportName of exportNames) { - code += `__internal_addTelefunction(${exportName}, "${exportName}", "${filePath}");` - code += '\n' + extraLines.push(`__internal_addTelefunction(${exportName}, "${exportName}", "${filePath}");`) + const telefunctionKey = getTelefunctionKey(filePath, exportName) + extraLines.push(`${exportName}['_key'] = ${JSON.stringify(telefunctionKey)};`) } + code += '\n' + extraLines.join('\n') + return code } + diff --git a/telefunc/node/transformer/transformTelefuncFileSync.ts b/telefunc/node/transformer/transformTelefuncFileSync.ts index 4ec8db19..a8c36221 100644 --- a/telefunc/node/transformer/transformTelefuncFileSync.ts +++ b/telefunc/node/transformer/transformTelefuncFileSync.ts @@ -18,18 +18,27 @@ function transformTelefuncFileSync(id: string, root: string, exportNames: readon } export function getCode(exportNames: readonly string[], telefuncFilePath: string) { - const lines = [] + const lines: string[] = [] lines.push('// @ts-nocheck') lines.push(`import { __internal_fetchTelefunc } from 'telefunc/client';`) exportNames.forEach((exportName) => { - const exportValue = `(...args) => __internal_fetchTelefunc('${telefuncFilePath}', '${exportName}', args);` + const varName = exportName === 'default' ? 'defaultExport' : exportName + + lines.push(`const ${varName} = (...args) => __internal_fetchTelefunc('${telefuncFilePath}', '${exportName}', args);`) + + { + assert(!telefuncFilePath.includes(':')) + const key = `${telefuncFilePath}:${exportName}` + lines.push(`${varName}._key = ${JSON.stringify(key)};`) + } + if (exportName === 'default') { - lines.push(`export default ${exportValue}`) + lines.push(`export default ${varName};`) } else { - lines.push(`export const ${exportName} = ${exportValue};`) + lines.push(`export { ${varName} };`) } }) diff --git a/telefunc/node/transformer/utils.ts b/telefunc/node/transformer/utils.ts new file mode 100644 index 00000000..6ba4271e --- /dev/null +++ b/telefunc/node/transformer/utils.ts @@ -0,0 +1,2 @@ +// TODO +export * from '../../node/utils' diff --git a/telefunc/node/utils.ts b/telefunc/node/utils.ts index f15e7ffb..8a2b4e21 100644 --- a/telefunc/node/utils.ts +++ b/telefunc/node/utils.ts @@ -5,7 +5,7 @@ export * from '../utils/isPromise' export * from '../utils/moduleExists' export * from '../utils/cast' export * from '../utils/filesystemPathHandling' -export * from '../utils./../utils/unique' +export * from '../utils/unique' export * from '../utils/nodeRequire' export * from '../utils/getUrlPathname' export * from '../utils/getGlobalObject' @@ -16,3 +16,4 @@ export * from '../utils/projectInfo' export * from '../utils/objectAssign' export * from '../utils/lowercaseFirstLetter' export * from '../utils/dynamicImport' +export * from '../utils/getTelefunctionKey' diff --git a/telefunc/node/vite/transform.ts b/telefunc/node/vite/transform.ts index de5af08e..a2ced2c9 100644 --- a/telefunc/node/vite/transform.ts +++ b/telefunc/node/vite/transform.ts @@ -1,5 +1,6 @@ import { Plugin } from 'vite' import { transformTelefuncFile } from '../transformer/transformTelefuncFile' +import { transformTelefuncFileSSR } from '../transformer/transformTelefuncFileSSR' import { generateShield } from '../server/shield/codegen/transformer' import { assert, toPosixPath, viteIsSSR_options } from './utils' @@ -21,6 +22,7 @@ function transform(): Plugin { if (!viteIsSSR_options(options)) { return transformTelefuncFile(code, id, root) } else { + code = (await transformTelefuncFileSSR(code, id, root)).code if (id.endsWith('.ts')) { return generateShield(code) } diff --git a/telefunc/package.json b/telefunc/package.json index befa15cf..bc8a461f 100644 --- a/telefunc/package.json +++ b/telefunc/package.json @@ -64,6 +64,8 @@ "typescript": "^4.7.4", "vite": "^3.0.9" }, + "peerDependencies": { "react-streaming": "^0.3.2" }, + "peerDependenciesMeta": { "react-streaming": { "optional": true } }, "files": [ "dist/cjs/", "dist/esm/", diff --git a/telefunc/react.ts b/telefunc/react.ts new file mode 100644 index 00000000..0c4d71e5 --- /dev/null +++ b/telefunc/react.ts @@ -0,0 +1,3 @@ +// Help TS's resolver until it supports `package.json#exports` +export * from './dist/esm/client/react' +export { default } from './dist/esm/client/react' diff --git a/telefunc/shared/react/index.ts b/telefunc/shared/react/index.ts new file mode 100644 index 00000000..5a140d35 --- /dev/null +++ b/telefunc/shared/react/index.ts @@ -0,0 +1 @@ +export { useData } from './useData' diff --git a/telefunc/shared/react/useData.ts b/telefunc/shared/react/useData.ts new file mode 100644 index 00000000..aae3c038 --- /dev/null +++ b/telefunc/shared/react/useData.ts @@ -0,0 +1,17 @@ +export { useData } + +import { useAsync } from 'react-streaming' +import { assert } from './utils' + +function useData any>( + telefunction: Telefunction, + ...args: Parameters +): Awaited> { + // @ts-ignore + const telefunctionKey = telefunction._key as string + assert(telefunctionKey) + const asyncFn: () => ReturnType = () => telefunction(...args) + const key = [telefunctionKey, ...args] + const result = useAsync(key, asyncFn) + return result +} diff --git a/telefunc/shared/react/utils.ts b/telefunc/shared/react/utils.ts new file mode 100644 index 00000000..41df7afb --- /dev/null +++ b/telefunc/shared/react/utils.ts @@ -0,0 +1 @@ +export * from '../../utils/assert' diff --git a/telefunc/utils/getTelefunctionKey.ts b/telefunc/utils/getTelefunctionKey.ts new file mode 100644 index 00000000..15cfe6e6 --- /dev/null +++ b/telefunc/utils/getTelefunctionKey.ts @@ -0,0 +1,7 @@ +import { assert } from './assert' + +export function getTelefunctionKey(filePath: string, exportName: string) { + assert(!filePath.includes(':')) // This assert isn't actually necessary since `telefunctionKey` is never parsed + const telefunctionKey = filePath + ':' + exportName + return telefunctionKey +}