diff --git a/lib/extension-template.web-view.tsx b/lib/extension-template.web-view.tsx index 884aa20..b378557 100644 --- a/lib/extension-template.web-view.tsx +++ b/lib/extension-template.web-view.tsx @@ -1,11 +1,11 @@ import papi from "papi"; import { useState } from "react"; import { QuickVerseDataProvider } from "extension-types"; +import { Button } from "papi-components"; const { react: { hooks: { useData, useDataProvider }, - components: { Button }, }, logger, } = papi; diff --git a/lib/main.ts b/lib/main.ts index 77490a5..05d25fa 100644 --- a/lib/main.ts +++ b/lib/main.ts @@ -5,7 +5,13 @@ import extensionTemplateReact from "./extension-template.web-view"; import extensionTemplateReactStyles from "./extension-template.web-view.scss?inline"; // @ts-expect-error ts(1192) this file has no default export; the text is exported by rollup import extensionTemplateHtml from "./extension-template-html.web-view.ejs"; -import type { WebViewContentType } from "shared/data/web-view.model"; +import type { + SavedWebViewDefinition, + WebViewContentType, + WebViewDefinition, +} from "shared/data/web-view.model"; +import type { UnsubscriberAsync } from "shared/utils/papi-util"; +import type { IWebViewProvider } from "shared/models/web-view-provider.model"; const { logger } = papi; @@ -13,8 +19,6 @@ console.log(import.meta.env.PROD); logger.info("Extension template is importing!"); -const unsubscribers = []; - type QuickVerseSetData = string | { text: string; isHeresy: boolean }; class QuickVerseDataProviderEngine @@ -129,6 +133,50 @@ class QuickVerseDataProviderEngine } } +const htmlWebViewType = "paranext-extension-template.html"; + +/** + * Simple web view provider that provides sample html web views when papi requests them + */ +const htmlWebViewProvider: IWebViewProvider = { + async getWebView( + savedWebView: SavedWebViewDefinition + ): Promise { + if (savedWebView.webViewType !== htmlWebViewType) + throw new Error( + `${htmlWebViewType} provider received request to provide a ${savedWebView.webViewType} web view` + ); + return { + ...savedWebView, + title: "Extension Template HTML", + contentType: "html" as WebViewContentType.HTML, + content: extensionTemplateHtml, + }; + }, +}; + +const reactWebViewType = "paranext-extension-template.react"; + +/** + * Simple web view provider that provides React web views when papi requests them + */ +const reactWebViewProvider: IWebViewProvider = { + async getWebView( + savedWebView: SavedWebViewDefinition + ): Promise { + if (savedWebView.webViewType !== reactWebViewType) + throw new Error( + `${reactWebViewType} provider received request to provide a ${savedWebView.webViewType} web view` + ); + return { + ...savedWebView, + title: "Extension Template React", + content: extensionTemplateReact, + styles: extensionTemplateReactStyles, + }; + }, +}; + export async function activate() { logger.info("Extension template is activating!"); @@ -137,6 +185,16 @@ export async function activate() { new QuickVerseDataProviderEngine() ); + const htmlWebViewProviderPromise = papi.webViews.registerWebViewProvider( + htmlWebViewType, + htmlWebViewProvider + ); + + const reactWebViewProviderPromise = papi.webViews.registerWebViewProvider( + reactWebViewType, + reactWebViewProvider + ); + const unsubPromises = [ papi.commands.registerCommand( "extension-template.do-stuff", @@ -146,31 +204,29 @@ export async function activate() { ), ]; - papi.webViews.addWebView({ - id: 'Extension template WebView React', - content: extensionTemplateReact, - styles: extensionTemplateReactStyles, - }); - - papi.webViews.addWebView({ - id: 'Extension template WebView HTML', - contentType: 'html' as WebViewContentType.HTML, - content: extensionTemplateHtml, - }); + // Create webviews or get an existing webview if one already exists for this type + // Note: here, we are using `existingId: '?'` to indicate we do not want to create a new webview + // if one already exists. The webview that already exists could have been created by anyone + // anywhere; it just has to match `webViewType`. See `paranext-core's hello-someone.ts` for an example of keeping + // an existing webview that was specifically created by `paranext-core's hello-someone`. + papi.webViews.getWebView(htmlWebViewType, undefined, { existingId: "?" }); + papi.webViews.getWebView(reactWebViewType, undefined, { existingId: "?" }); // For now, let's just make things easy and await the data provider promise at the end so we don't hold everything else up const quickVerseDataProviderInfo = await quickVerseDataProviderInfoPromise; - - return Promise.all( - unsubPromises.map((unsubPromise) => unsubPromise.promise) - ).then(() => { - logger.info("Extension template is finished activating!"); - return papi.util.aggregateUnsubscriberAsyncs( - unsubPromises - .map((unsubPromise) => unsubPromise.unsubscriber) - .concat([quickVerseDataProviderInfo.dispose]) + const htmlWebViewProviderResolved = await htmlWebViewProviderPromise; + const reactWebViewProviderResolved = await reactWebViewProviderPromise; + + const combinedUnsubscriber: UnsubscriberAsync = + papi.util.aggregateUnsubscriberAsyncs( + (await Promise.all(unsubPromises)).concat([ + quickVerseDataProviderInfo.dispose, + htmlWebViewProviderResolved.dispose, + reactWebViewProviderResolved.dispose, + ]) ); - }); + logger.info("Extension template is finished activating!"); + return combinedUnsubscriber; } export async function deactivate() { diff --git a/package-lock.json b/package-lock.json index 223e67b..19f6cc7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "paranext-extension-template", "version": "0.0.1", "dependencies": { + "papi-components": "file:../paranext-core/lib/papi-components", "react": "^18.2.0", "react-dom": "^18.2.0" }, @@ -22,13 +23,44 @@ "glob": "^10.2.2", "papi-dts": "file:../paranext-core/lib/papi-dts", "patch-package": "^7.0.0", - "rollup-plugin-import-manager": "^0.6.2", + "rollup-plugin-import-manager": "^0.6.3", "rollup-plugin-string": "^3.0.0", "sass": "^1.62.1", "typescript": "^4.9.3", "vite": "^4.3.9" } }, + "../paranext-core/lib/papi-components": { + "version": "0.0.1", + "license": "MIT", + "devDependencies": { + "@emotion/react": "^11.10.6", + "@emotion/styled": "^11.10.6", + "@mui/material": "^5.11.12", + "@types/react": "^18.0.26", + "@types/react-dom": "^18.0.9", + "@typescript-eslint/eslint-plugin": "^5.46.0", + "@typescript-eslint/parser": "^5.46.0", + "@vitejs/plugin-react": "^3.1.0", + "dts-bundle-generator": "^7.1.0", + "eslint": "^8.29.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-prettier": "^4.2.1", + "prettier": "^2.8.4", + "stylelint": "^14.16.0", + "stylelint-config-recommended": "^9.0.0", + "stylelint-config-sass-guidelines": "^9.0.1", + "ts-node": "^10.9.1", + "tslib": "^2.4.1", + "typescript": "^4.9.5", + "vite": "^4.3.9" + }, + "peerDependencies": { + "react": ">=18.2.0", + "react-data-grid": ">=7.0.0-beta.30", + "react-dom": ">=18.2.0" + } + }, "../paranext-core/lib/papi-dts": { "version": "0.0.1", "dev": true, @@ -1686,9 +1718,9 @@ "dev": true }, "node_modules/import-manager": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/import-manager/-/import-manager-0.4.2.tgz", - "integrity": "sha512-rsBYufGO0lhIcHWrsrrCRh++Vu7rW/e32TzOdZJgApbzGWIk2svDGESqQHoH6eeesKAKsFXEO5+EOTi1S2r/XA==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/import-manager/-/import-manager-0.4.3.tgz", + "integrity": "sha512-qxw0n3VeGzPSf2pKakRVIIu+lQyL9ZvWAIq7yiLPeiJu48uxOIYti9jpZ1E1grIUuEnPUWo4YWxtvuidtqMNIg==", "dev": true, "dependencies": { "acorn": "^8.8.2", @@ -2034,6 +2066,10 @@ "node": ">=0.10.0" } }, + "node_modules/papi-components": { + "resolved": "../paranext-core/lib/papi-components", + "link": true + }, "node_modules/papi-dts": { "resolved": "../paranext-core/lib/papi-dts", "link": true @@ -2374,15 +2410,15 @@ } }, "node_modules/rollup-plugin-import-manager": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-import-manager/-/rollup-plugin-import-manager-0.6.2.tgz", - "integrity": "sha512-pQOXlR8xhgExuMXyc7j0JOu5wFsGKZKM1CEe0e48ILHtqTwP1T22lyunjas71unc5lueSzF8cEAqMZ+kSBeN8w==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-import-manager/-/rollup-plugin-import-manager-0.6.3.tgz", + "integrity": "sha512-s7JMrrdntLafYhHsSn0g4LKZmp40oyHDx9hg8KzVajhM0kTyA5iwNpjNq4K6e9or1iLjRFK+oVmNVBLYFYka3A==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.2", "colorette": "^2.0.20", "diff": "^5.1.0", - "import-manager": "^0.4.2" + "import-manager": "^0.4.3" } }, "node_modules/rollup-plugin-string": { @@ -3986,9 +4022,9 @@ "dev": true }, "import-manager": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/import-manager/-/import-manager-0.4.2.tgz", - "integrity": "sha512-rsBYufGO0lhIcHWrsrrCRh++Vu7rW/e32TzOdZJgApbzGWIk2svDGESqQHoH6eeesKAKsFXEO5+EOTi1S2r/XA==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/import-manager/-/import-manager-0.4.3.tgz", + "integrity": "sha512-qxw0n3VeGzPSf2pKakRVIIu+lQyL9ZvWAIq7yiLPeiJu48uxOIYti9jpZ1E1grIUuEnPUWo4YWxtvuidtqMNIg==", "dev": true, "requires": { "acorn": "^8.8.2", @@ -4239,6 +4275,31 @@ "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", "dev": true }, + "papi-components": { + "version": "file:../paranext-core/lib/papi-components", + "requires": { + "@emotion/react": "^11.10.6", + "@emotion/styled": "^11.10.6", + "@mui/material": "^5.11.12", + "@types/react": "^18.0.26", + "@types/react-dom": "^18.0.9", + "@typescript-eslint/eslint-plugin": "^5.46.0", + "@typescript-eslint/parser": "^5.46.0", + "@vitejs/plugin-react": "^3.1.0", + "dts-bundle-generator": "^7.1.0", + "eslint": "^8.29.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-prettier": "^4.2.1", + "prettier": "^2.8.4", + "stylelint": "^14.16.0", + "stylelint-config-recommended": "^9.0.0", + "stylelint-config-sass-guidelines": "^9.0.1", + "ts-node": "^10.9.1", + "tslib": "^2.4.1", + "typescript": "^4.9.5", + "vite": "^4.3.9" + } + }, "papi-dts": { "version": "file:../paranext-core/lib/papi-dts" }, @@ -4478,15 +4539,15 @@ } }, "rollup-plugin-import-manager": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-import-manager/-/rollup-plugin-import-manager-0.6.2.tgz", - "integrity": "sha512-pQOXlR8xhgExuMXyc7j0JOu5wFsGKZKM1CEe0e48ILHtqTwP1T22lyunjas71unc5lueSzF8cEAqMZ+kSBeN8w==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-import-manager/-/rollup-plugin-import-manager-0.6.3.tgz", + "integrity": "sha512-s7JMrrdntLafYhHsSn0g4LKZmp40oyHDx9hg8KzVajhM0kTyA5iwNpjNq4K6e9or1iLjRFK+oVmNVBLYFYka3A==", "dev": true, "requires": { "@rollup/pluginutils": "^5.0.2", "colorette": "^2.0.20", "diff": "^5.1.0", - "import-manager": "^0.4.2" + "import-manager": "^0.4.3" } }, "rollup-plugin-string": { diff --git a/package.json b/package.json index a725be5..54b6326 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "lint": "tsc" }, "dependencies": { + "papi-components": "file:../paranext-core/lib/papi-components", "react": "^18.2.0", "react-dom": "^18.2.0" }, @@ -29,7 +30,7 @@ "glob": "^10.2.2", "papi-dts": "file:../paranext-core/lib/papi-dts", "patch-package": "^7.0.0", - "rollup-plugin-import-manager": "^0.6.2", + "rollup-plugin-import-manager": "^0.6.3", "rollup-plugin-string": "^3.0.0", "sass": "^1.62.1", "typescript": "^4.9.3", diff --git a/vite/vite-web-view.config.ts b/vite/vite-web-view.config.ts index 1cd5fde..3c43920 100644 --- a/vite/vite-web-view.config.ts +++ b/vite/vite-web-view.config.ts @@ -29,7 +29,7 @@ const webViewConfig = defineConfig(async ({ mode }) => { ], // Since Vite is in library mode `process` is not replaced by default and that won't work in the // renderer. - define: { 'process.env.NODE_ENV': JSON.stringify(mode) }, + define: { "process.env.NODE_ENV": JSON.stringify(mode) }, build: { // This project is a library as it is being used in Paranext lib: { @@ -75,6 +75,10 @@ const webViewConfig = defineConfig(async ({ mode }) => { rollupOptions: { // Do not bundle papi because it will be imported in Paranext external: paranextProvidedModules, + output: { + // Disable code splitting and chunks. Extension WebViews must be a single file + manualChunks: () => "webView", + }, }, // Bundle the sourcemap into the webview file since it will be injected as a string // into the main file diff --git a/vite/vite.config.ts b/vite/vite.config.ts index 852bf2b..0746d67 100644 --- a/vite/vite.config.ts +++ b/vite/vite.config.ts @@ -70,6 +70,10 @@ const extensionConfig = defineConfig(async () => { rollupOptions: { // Do not bundle papi because it will be imported in Paranext external: paranextProvidedModules, + output: { + // Disable code splitting and chunks. Extension main must be a single file + manualChunks: () => "main", + }, }, // Generate sourcemaps as separate files since VSCode can load them directly sourcemap: true,