From f61d9f416c27f3cf74ec05ebac0d7c6cb444a939 Mon Sep 17 00:00:00 2001 From: Deryk DeGuzman Date: Tue, 30 Jan 2024 11:13:21 -0800 Subject: [PATCH] chore: Downgrade to React 17 and expose NextJs-compatible component --- .gitignore | 2 +- buildConfigs.js | 15 ++++---- package-lock.json | 83 ++++++++++++++++++++++++++--------------- package.json | 14 +++---- src/ReactSearchNext.tsx | 23 ++++++++++++ src/index.tsx | 29 +------------- src/types.ts | 27 ++++++++++++++ src/useSearch.tsx | 41 ++++++++------------ 8 files changed, 134 insertions(+), 100 deletions(-) create mode 100644 src/ReactSearchNext.tsx diff --git a/.gitignore b/.gitignore index 2cd4e92..085ed63 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ /node_modules -/dist +/lib /docs/public/script.js /docs/public/script.js.map /coverage diff --git a/buildConfigs.js b/buildConfigs.js index f9b410c..b8e8acf 100644 --- a/buildConfigs.js +++ b/buildConfigs.js @@ -1,29 +1,28 @@ const { sassPlugin } = require("esbuild-sass-plugin"); const cssPlugin = require("esbuild-css-modules-plugin"); const { dependencies, devDependencies, peerDependencies } = require("./package.json"); -const entryFile = "src/index.tsx"; const sharedConfig = { bundle: true, - entryPoints: [entryFile], + entryPoints: ["src/index.tsx", "src/ReactSearchNext.tsx"], logLevel: "info", treeShaking: true, minify: true, sourcemap: true, external: [...Object.keys(dependencies), ...Object.keys(devDependencies), ...Object.keys(peerDependencies)], - target: ["esnext", "node12.22.0"], - plugins: [cssPlugin(), sassPlugin({ type: "style" })] + target: ["es6", "node12.22.0"], + plugins: [cssPlugin(), sassPlugin({ type: "style" })], + outdir: "./lib", + outbase: "./src" }; module.exports = { esm: { ...sharedConfig, - format: "esm", - outfile: "./dist/index.esm.js" + format: "esm" }, cjs: { ...sharedConfig, - format: "cjs", - outfile: "./dist/index.cjs.js" + format: "cjs" } }; diff --git a/package-lock.json b/package-lock.json index e514a6c..d8ad680 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@vectara/react-search", - "version": "0.0.4", + "version": "0.0.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@vectara/react-search", - "version": "0.0.4", + "version": "0.0.8", "license": "MIT", "dependencies": { "@types/react": "^18.2.45", @@ -19,7 +19,7 @@ }, "devDependencies": { "@testing-library/jest-dom": "^6.2.0", - "@testing-library/react": "^14.1.2", + "@testing-library/react": "^12.1.5", "@types/jest": "^29.5.11", "@types/lodash": "^4.14.202", "@types/prismjs": "^1.26.3", @@ -33,8 +33,8 @@ "jest-environment-jsdom": "^29.7.0", "live-server": "^1.2.2", "markdown-to-jsx": "^7.3.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "react": "^17.0.2", + "react-dom": "^17.0.2", "react-icons": "^5.0.1", "react-router-dom": "^6.8.2", "rimraf": "^5.0.5", @@ -1669,9 +1669,9 @@ } }, "node_modules/@testing-library/dom": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", - "integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==", + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz", + "integrity": "sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==", "dev": true, "dependencies": { "@babel/code-frame": "^7.10.4", @@ -1684,7 +1684,7 @@ "pretty-format": "^27.0.2" }, "engines": { - "node": ">=14" + "node": ">=12" } }, "node_modules/@testing-library/dom/node_modules/aria-query": { @@ -1760,21 +1760,41 @@ } }, "node_modules/@testing-library/react": { - "version": "14.1.2", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.1.2.tgz", - "integrity": "sha512-z4p7DVBTPjKM5qDZ0t5ZjzkpSNb+fZy1u6bzO7kk8oeGagpPCAtgh4cx1syrfp7a+QWkM021jGqjJaxJJnXAZg==", + "version": "12.1.5", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz", + "integrity": "sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^9.0.0", - "@types/react-dom": "^18.0.0" + "@testing-library/dom": "^8.0.0", + "@types/react-dom": "<18.0.0" }, "engines": { - "node": ">=14" + "node": ">=12" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "<18.0.0", + "react-dom": "<18.0.0" + } + }, + "node_modules/@testing-library/react/node_modules/@types/react": { + "version": "17.0.75", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.75.tgz", + "integrity": "sha512-MSA+NzEzXnQKrqpO63CYqNstFjsESgvJAdAyyJ1n6ZQq/GLgf6nOfIKwk+Twuz0L1N6xPe+qz5xRCJrbhMaLsw==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@testing-library/react/node_modules/@types/react-dom": { + "version": "17.0.25", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.25.tgz", + "integrity": "sha512-urx7A7UxkZQmThYA4So0NelOVjx3V4rNFVJwp0WZlbIK5eM4rNJDiN3R/E9ix0MBh6kAEojk/9YL+Te6D9zHNA==", + "dev": true, + "dependencies": { + "@types/react": "^17" } }, "node_modules/@tootallnate/once": { @@ -7632,11 +7652,12 @@ } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", "dependencies": { - "loose-envify": "^1.1.0" + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" }, "engines": { "node": ">=0.10.0" @@ -7654,16 +7675,17 @@ } }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", + "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", "dev": true, "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "object-assign": "^4.1.1", + "scheduler": "^0.20.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "17.0.2" } }, "node_modules/react-focus-lock": { @@ -8151,12 +8173,13 @@ } }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", "dev": true, "dependencies": { - "loose-envify": "^1.1.0" + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" } }, "node_modules/semver": { diff --git a/package.json b/package.json index 38683fd..668b264 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,11 @@ "name": "@vectara/react-search", "version": "0.0.8", "description": "A Vectara-powered Search component", - "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", - "types": "dist/index.d.ts", + "main": "lib/index.cjs.js", + "module": "lib/index.esm.js", + "types": "lib/index.d.ts", "scripts": { - "build": "npm run clean && node build.js && tsc --emitDeclarationOnly --outDir dist", + "build": "npm run clean && node build.js && tsc --emitDeclarationOnly --outDir lib", "buildDocs": "node docs/build.js", "docs": "node docs/docsServer.js", "clean": "rimraf dist", @@ -40,7 +40,7 @@ }, "devDependencies": { "@testing-library/jest-dom": "^6.2.0", - "@testing-library/react": "^14.1.2", + "@testing-library/react": "^12.1.5", "@types/jest": "^29.5.11", "@types/lodash": "^4.14.202", "@types/prismjs": "^1.26.3", @@ -54,8 +54,8 @@ "jest-environment-jsdom": "^29.7.0", "live-server": "^1.2.2", "markdown-to-jsx": "^7.3.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "react": "^17.0.2", + "react-dom": "^17.0.2", "react-icons": "^5.0.1", "react-router-dom": "^6.8.2", "rimraf": "^5.0.5", diff --git a/src/ReactSearchNext.tsx b/src/ReactSearchNext.tsx new file mode 100644 index 0000000..845bfbd --- /dev/null +++ b/src/ReactSearchNext.tsx @@ -0,0 +1,23 @@ +import { ReactNode, useEffect, useState } from "react"; +import { Props } from "types"; + +/** + * An implementation of the ReactSearch component for NextJs. + * For NextJs, the ReactSearch child component is imported and rendered via useEffect. + * Doing it this way guarantees that the component is only rendered on the client, avoiding server-side errors. + */ +export const ReactSearchNext = (props: Props): ReactNode => { + const [search, setSearch] = useState(null); + + useEffect(() => { + const importAndRenderSearch = async () => { + const { ReactSearch } = await import("./"); + + setSearch(); + }; + + importAndRenderSearch(); + }, []); + + return search; +}; diff --git a/src/index.tsx b/src/index.tsx index fb9b844..a5f0b1a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -10,7 +10,7 @@ import { } from "react"; import getUuid from "uuid-by-string"; import { VuiFlexContainer, VuiFlexItem, VuiSpinner, VuiText } from "./vui"; -import { DeserializedSearchResult } from "./types"; +import { DeserializedSearchResult, Props } from "./types"; import { useSearch } from "./useSearch"; import { SearchResult } from "./SearchResult"; import { SearchModal } from "./SearchModal"; @@ -24,33 +24,6 @@ const getQueryParam = (urlParams: URLSearchParams, key: string) => { return undefined; }; -export interface Props { - // Vectara customer ID - customerId: string; - - // Vectara API key - apiKey: string; - - // Vectara corpus ID - corpusId: string; - - // An optional API url to direct requests toward - apiUrl?: string; - - // The number of previous searches to cache. - // Default is 0. - historySize?: number; - - // The search input placeholder. - placeholder?: string; - - // Whether to enable deeplinking to a particular search. - isDeeplinkable?: boolean; - - // Whether to open selected results in a new browser tab. - openResultsInNewTab?: boolean; -} - /** * A client-side search component that queries a specific corpus with a user-provided string. */ diff --git a/src/types.ts b/src/types.ts index bd2789a..c519330 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,30 @@ +export interface Props { + // Vectara customer ID + customerId: string; + + // Vectara API key + apiKey: string; + + // Vectara corpus ID + corpusId: string; + + // An optional API url to direct requests toward + apiUrl?: string; + + // The number of previous searches to cache. + // Default is 0. + historySize?: number; + + // The search input placeholder. + placeholder?: string; + + // Whether to enable deeplinking to a particular search. + isDeeplinkable?: boolean; + + // Whether to open selected results in a new browser tab. + openResultsInNewTab?: boolean; +} + export type DeserializedSearchResult = { id: string; snippet: { diff --git a/src/useSearch.tsx b/src/useSearch.tsx index a3dae6c..769993f 100644 --- a/src/useSearch.tsx +++ b/src/useSearch.tsx @@ -35,31 +35,28 @@ export const useSearch = ( numResults: 20, corpusKey: [ { - corpusId, - }, - ], - }, - ], + corpusId + } + ] + } + ] }); }, [corpusId] ); - const fetchSearchResults = async ( - query: string - ): Promise => { + const fetchSearchResults = async (query: string): Promise => { setIsLoading(true); const requestBody = generateRequestBody(query); const response = await fetch(apiUrl, { headers, body: requestBody, - method: "POST", + method: "POST" }); const responseJson = await response.json(); setIsLoading(false); - const results = - deserializeSearchResponse(responseJson.responseSet?.[0]) ?? []; + const results = deserializeSearchResponse(responseJson.responseSet?.[0]) ?? []; return compileDedupedResults(results); }; @@ -81,13 +78,11 @@ const parseMetadata = (rawMetadata: DocMetadata[]) => { source: metadata.source as string, url: metadata.url, title: metadata.title, - metadata, + metadata }; }; -const deserializeSearchResponse = ( - searchResponse?: SearchResponse -): Array | undefined => { +const deserializeSearchResponse = (searchResponse?: SearchResponse): Array | undefined => { if (!searchResponse) return undefined; const results: Array = []; @@ -105,12 +100,12 @@ const deserializeSearchResponse = ( snippet: { pre, text, - post, + post }, source, url, title, - metadata, + metadata }); }); @@ -121,18 +116,12 @@ const START_TAG = "%START_SNIPPET%"; const END_TAG = "%END_SNIPPET%"; const parseSnippet = (source: string) => { - const [pre, textAndPost] = - source.indexOf(START_TAG) !== -1 ? source.split(START_TAG) : ["", source]; - const [text, post] = - textAndPost.indexOf(END_TAG) !== -1 - ? textAndPost.split(END_TAG) - : [textAndPost, ""]; + const [pre, textAndPost] = source.indexOf(START_TAG) !== -1 ? source.split(START_TAG) : ["", source]; + const [text, post] = textAndPost.indexOf(END_TAG) !== -1 ? textAndPost.split(END_TAG) : [textAndPost, ""]; return { pre, post, text }; }; -const compileDedupedResults = ( - undedupedResults: DeserializedSearchResult[] -): DeserializedSearchResult[] => { +const compileDedupedResults = (undedupedResults: DeserializedSearchResult[]): DeserializedSearchResult[] => { const listedUrls: Record = {}; const dedupedResults: DeserializedSearchResult[] = [];