From 818b60eb0b2b9fdb2e4153c052e9c14c9929e615 Mon Sep 17 00:00:00 2001 From: Andy Brown Date: Thu, 8 Oct 2020 10:22:28 -0700 Subject: [PATCH] refactor: add @bfc/types package (#4336) * refactor plugin host webpack config * add bundleId to publish type config * add types package * update yarn build:dev and add @bfc/tools to deps * build client before server * update imports to use @bfc/types * fix getBundleForView test * update remaining extension type references * fix additionalProperties typing * add @bfc/ui-shared where missing * fix extensions controller test * do not extract comments when bundling react * apply pr feedback * update more imports from shared to types * convert interfaces to types --- Composer/package.json | 14 +- .../utils/getCustomSchema.test.ts | 6 +- .../AdaptiveFlowEditor.tsx | 2 +- .../contexts/NodeRendererContext.ts | 4 +- .../utils/getCustomSchema.ts | 38 +- .../fields/ExpressionField/utils.ts | 4 +- .../components/fields/ObjectArrayField.tsx | 5 +- .../src/components/fields/OneOfField/utils.ts | 7 +- .../src/utils/resolvePropSchema.ts | 6 +- .../adaptive-form/src/utils/resolveRef.ts | 7 +- Composer/packages/client/.gitignore | 1 + .../client/config/extensions.config.js | 103 + .../client/config/webpack-react-dom.config.js | 22 - .../client/config/webpack-react.config.js | 17 - .../plugin-host-preload.tsx} | 14 +- Composer/packages/client/package.json | 4 +- Composer/packages/client/public/index.html | 2 +- .../src/components/PluginHost/PluginHost.tsx | 12 +- .../pages/language-generation/code-editor.tsx | 2 +- .../src/pages/plugin/pluginPageContainer.tsx | 10 +- .../src/pages/publish/createPublishTarget.tsx | 13 +- .../extensions/ExtensionSearchResults.tsx | 10 +- .../pages/setting/extensions/Extensions.tsx | 9 +- .../extensions/InstallExtensionDialog.tsx | 3 +- .../client/src/recoilModel/atoms/appState.ts | 4 +- .../src/recoilModel/dispatchers/extensions.ts | 11 +- .../persistence/FilePersistence.ts | 2 +- .../packages/client/src/recoilModel/types.ts | 34 +- Composer/packages/client/src/router.tsx | 2 +- Composer/packages/client/src/shell/lgApi.ts | 2 +- Composer/packages/client/src/shell/luApi.ts | 2 +- Composer/packages/client/src/utils/hooks.ts | 5 +- .../packages/client/src/utils/pageLinks.ts | 7 +- Composer/packages/client/tsconfig.build.json | 2 +- Composer/packages/client/tsconfig.json | 2 +- .../packages/extension-client/package.json | 1 + .../src/EditorExtensionContext.ts | 2 +- .../src/components/EditorExtension.tsx | 2 +- .../extension-client/src/hooks/index.ts | 1 - .../src/hooks/useActionApi.ts | 3 +- .../extension-client/src/hooks/useDebounce.ts | 8 - .../src/hooks/useDialogApi.ts | 2 +- .../src/hooks/useDialogEditApi.ts | 6 +- .../extension-client/src/hooks/useLgApi.ts | 3 +- .../extension-client/src/hooks/useLuApi.ts | 3 +- .../src/hooks/useRecognizerConfig.ts | 2 +- .../extension-client/src/hooks/useShellApi.ts | 2 +- .../src/hooks/useTriggerApi.ts | 2 +- .../packages/extension-client/src/index.ts | 2 + .../extension-client/src/types/extension.ts | 2 +- .../extension-client/src/types/flowSchema.ts | 2 +- .../extension-client/src/types/form.ts | 24 +- .../extension-client/src/types/formSchema.ts | 2 +- .../extension-client/src/types/menuSchema.ts | 2 +- .../src/types/recognizerSchema.ts | 2 +- .../__tests__/mergePluginConfigs.test.ts | 2 +- Composer/packages/extension/package.json | 1 + .../extension/src/extensionContext.ts | 5 +- .../extension/src/extensionRegistration.ts | 6 +- Composer/packages/extension/src/index.ts | 3 +- .../packages/extension/src/manager/manager.ts | 2 +- .../src/storage/extensionManifestStore.ts | 2 +- .../packages/extension/src/types/types.ts | 171 -- Composer/packages/extension/src/utils/npm.ts | 2 +- Composer/packages/lib/indexers/package.json | 7 +- Composer/packages/lib/package.json | 18 - Composer/packages/lib/shared/package.json | 4 +- .../lib/shared/src/{types => }/EditorAPI.ts | 0 .../src/copyUtils/CopyConstructorMap.ts | 2 +- .../lib/shared/src/copyUtils/ExternalApi.ts | 2 +- .../src/copyUtils/copyAdaptiveAction.ts | 2 +- .../src/copyUtils/copyAdaptiveActionList.ts | 2 +- .../shared/src/copyUtils/copyEditActions.ts | 2 +- .../lib/shared/src/copyUtils/copyForeach.ts | 2 +- .../shared/src/copyUtils/copyIfCondition.ts | 2 +- .../shared/src/copyUtils/copyInputDialog.ts | 2 +- .../shared/src/copyUtils/copySendActivity.ts | 2 +- .../src/copyUtils/copySwitchCondition.ts | 2 +- .../copyUtils/shallowCopyAdaptiveAction.ts | 2 +- .../lib/shared/src/deleteUtils/index.ts | 3 +- .../lib/shared/src/{types => }/diagnostic.ts | 15 +- .../packages/lib/shared/src/dialogFactory.ts | 4 +- Composer/packages/lib/shared/src/index.ts | 5 +- Composer/packages/lib/shared/src/labelMap.ts | 3 +- .../src/lgUtils/stringBuilders/buildLgType.ts | 5 +- .../lib/shared/src/schemaUtils/dereference.ts | 7 +- .../lib/shared/src/schemaUtils/getRef.ts | 11 +- .../lib/shared/src/skillsUtils/index.ts | 3 +- Composer/packages/lib/shared/src/viewUtils.ts | 2 +- .../src/walkerUtils/AdaptiveActionVisitor.ts | 2 +- .../src/walkerUtils/walkAdaptiveAction.ts | 2 +- .../src/walkerUtils/walkAdaptiveActionList.ts | 2 +- .../shared/src/walkerUtils/walkLgResources.ts | 3 +- .../controllers/__tests__/extensions.test.ts | 69 +- .../server/src/controllers/extensions.ts | 18 +- .../server/src/controllers/publisher.ts | 2 +- .../packages/server/src/locales/en-US.json | 2574 +---------------- Composer/packages/server/src/router/api.ts | 2 +- .../language-understanding/package.json | 2 + .../tools/language-servers/package.json | 14 - Composer/packages/tools/package.json | 14 - Composer/packages/types/.eslintrc.js | 10 + Composer/packages/types/.gitignore | 1 + Composer/packages/types/package.json | 20 + Composer/packages/types/src/diagnostic.ts | 55 + .../src/types => types/src}/dialogUtils.ts | 20 +- .../src/types => types/src}/extension.ts | 61 +- .../shared/src/types => types/src}/index.ts | 7 +- .../src/types => types/src}/indexers.ts | 106 +- Composer/packages/types/src/publish.ts | 57 + Composer/packages/types/src/runtime.ts | 84 + .../shared/src/types => types/src}/schema.ts | 28 +- .../shared/src/types => types/src}/sdk.ts | 112 +- .../shared/src/types => types/src}/server.ts | 18 +- .../src/types => types/src}/settings.ts | 46 +- .../shared/src/types => types/src}/shell.ts | 37 +- Composer/packages/types/src/user.ts | 6 + Composer/packages/types/tsconfig.json | 9 + Composer/packages/types/yarn.lock | 18 + Composer/packages/ui-plugins/lg/package.json | 1 + .../packages/ui-plugins/lg/src/LgField.tsx | 2 +- .../ui-plugins/luis/src/LuisIntentEditor.tsx | 2 +- .../packages/ui-plugins/prompts/package.json | 1 + .../schema-editor/src/utils/getDefinitions.ts | 6 +- .../plugins/sample-ui-plugin/package.json | 6 +- .../sample-ui-plugin/src/node/index.ts | 2 +- .../sample-ui-plugin/webpack.config.js | 11 +- 127 files changed, 843 insertions(+), 3351 deletions(-) create mode 100644 Composer/packages/client/config/extensions.config.js delete mode 100644 Composer/packages/client/config/webpack-react-dom.config.js delete mode 100644 Composer/packages/client/config/webpack-react.config.js rename Composer/packages/client/{public/plugin-host-preload.js => extension-container/plugin-host-preload.tsx} (72%) delete mode 100644 Composer/packages/extension-client/src/hooks/useDebounce.ts delete mode 100644 Composer/packages/extension/src/types/types.ts delete mode 100644 Composer/packages/lib/package.json rename Composer/packages/lib/shared/src/{types => }/EditorAPI.ts (100%) rename Composer/packages/lib/shared/src/{types => }/diagnostic.ts (85%) delete mode 100644 Composer/packages/tools/language-servers/package.json delete mode 100644 Composer/packages/tools/package.json create mode 100644 Composer/packages/types/.eslintrc.js create mode 100644 Composer/packages/types/.gitignore create mode 100644 Composer/packages/types/package.json create mode 100644 Composer/packages/types/src/diagnostic.ts rename Composer/packages/{lib/shared/src/types => types/src}/dialogUtils.ts (62%) rename Composer/packages/{extension/src/types => types/src}/extension.ts (54%) rename Composer/packages/{lib/shared/src/types => types/src}/index.ts (73%) rename Composer/packages/{lib/shared/src/types => types/src}/indexers.ts (72%) create mode 100644 Composer/packages/types/src/publish.ts create mode 100644 Composer/packages/types/src/runtime.ts rename Composer/packages/{lib/shared/src/types => types/src}/schema.ts (91%) rename Composer/packages/{lib/shared/src/types => types/src}/sdk.ts (85%) rename Composer/packages/{lib/shared/src/types => types/src}/server.ts (82%) rename Composer/packages/{lib/shared/src/types => types/src}/settings.ts (70%) rename Composer/packages/{lib/shared/src/types => types/src}/shell.ts (84%) create mode 100644 Composer/packages/types/src/user.ts create mode 100644 Composer/packages/types/tsconfig.json create mode 100644 Composer/packages/types/yarn.lock diff --git a/Composer/package.json b/Composer/package.json index f2e93589c4..04ad660b95 100644 --- a/Composer/package.json +++ b/Composer/package.json @@ -27,27 +27,21 @@ "packages/extension-client", "packages/form-dialogs", "packages/intellisense", - "packages/lib", "packages/lib/*", "packages/server", "packages/test-utils", - "packages/tools", "packages/tools/built-in-functions", - "packages/tools/language-servers", "packages/tools/language-servers/*", + "packages/types", "packages/ui-plugins/*" ], "scripts": { - "build": "node scripts/begin.js && yarn build:prod", - "build:prod": "yarn build:dev && yarn build:server && yarn build:client && yarn build:electron", - "build:dev": "yarn build:test && yarn build:lib && yarn build:tools && yarn build:extensions && yarn build:plugins && yarn l10n", - "build:test": "yarn workspace @bfc/test-utils build", - "build:lib": "yarn workspace @bfc/libs build:all", + "build": "node scripts/begin.js && yarn build:prod && yarn l10n", + "build:prod": "yarn build:dev && yarn build:client && yarn build:server && yarn build:electron", + "build:dev": "wsrun -ltm -x @bfc/electron-server -x @bfc/client -x @bfc/server -p @bfc/* -c build && yarn build:plugins", "build:electron": "yarn workspace @bfc/electron-server build && yarn workspace @bfc/electron-server l10n", - "build:extensions": "wsrun -lt -p @bfc/extension @bfc/intellisense @bfc/extension-client @bfc/adaptive-form @bfc/adaptive-flow @bfc/ui-plugin-* @bfc/form-dialogs -c build", "build:server": "yarn workspace @bfc/server build", "build:client": "yarn workspace @bfc/client build", - "build:tools": "yarn workspace @bfc/tools build:all", "build:plugins": "yarn build:plugins:localpublish && yarn build:plugins:samples && yarn build:plugins:azurePublish && yarn build:plugins:runtimes", "build:plugins:localpublish": "cd plugins/localPublish && yarn install && yarn build", "build:plugins:samples": "cd plugins/samples && yarn install && yarn build", diff --git a/Composer/packages/adaptive-flow/__tests__/adaptive-flow-editor/utils/getCustomSchema.test.ts b/Composer/packages/adaptive-flow/__tests__/adaptive-flow-editor/utils/getCustomSchema.test.ts index cd7e39d8be..320800e809 100644 --- a/Composer/packages/adaptive-flow/__tests__/adaptive-flow-editor/utils/getCustomSchema.test.ts +++ b/Composer/packages/adaptive-flow/__tests__/adaptive-flow-editor/utils/getCustomSchema.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { OBISchema } from '@bfc/shared'; +import { JSONSchema7 } from '@bfc/shared'; import { getCustomSchema } from '../../../src/adaptive-flow-editor/utils/getCustomSchema'; @@ -23,7 +23,7 @@ describe('getCustomSchema', () => { description: 'Send an activity.', }, }, - } as OBISchema; + } as JSONSchema7; expect(getCustomSchema({ oneOf: [], definitions: {} }, ejected)).toEqual({ actions: { oneOf: [ @@ -59,7 +59,7 @@ describe('getCustomSchema', () => { description: 'My Trigger.', }, }, - } as OBISchema; + } as JSONSchema7; expect(getCustomSchema({ oneOf: [], definitions: {} }, ejected)).toEqual({ actions: { oneOf: [ diff --git a/Composer/packages/adaptive-flow/src/adaptive-flow-editor/AdaptiveFlowEditor.tsx b/Composer/packages/adaptive-flow/src/adaptive-flow-editor/AdaptiveFlowEditor.tsx index 61c04ec80c..5377920e93 100644 --- a/Composer/packages/adaptive-flow/src/adaptive-flow-editor/AdaptiveFlowEditor.tsx +++ b/Composer/packages/adaptive-flow/src/adaptive-flow-editor/AdaptiveFlowEditor.tsx @@ -105,7 +105,7 @@ const VisualDesigner: React.FC = ({ onFocus, onBlur, schema }; const customFlowSchema: FlowUISchema = nodeContext.customSchemas.reduce((result, s) => { - const definitionKeys: string[] = Object.keys(s.definitions); + const definitionKeys = Object.keys(s.definitions ?? {}); definitionKeys.forEach(($kind) => { result[$kind] = { widget: 'ActionHeader', diff --git a/Composer/packages/adaptive-flow/src/adaptive-flow-editor/contexts/NodeRendererContext.ts b/Composer/packages/adaptive-flow/src/adaptive-flow-editor/contexts/NodeRendererContext.ts index d78c9e64e5..8f2f5f703c 100644 --- a/Composer/packages/adaptive-flow/src/adaptive-flow-editor/contexts/NodeRendererContext.ts +++ b/Composer/packages/adaptive-flow/src/adaptive-flow-editor/contexts/NodeRendererContext.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import React from 'react'; -import { DialogFactory, OBISchema } from '@bfc/shared'; +import { DialogFactory, JSONSchema7 } from '@bfc/shared'; export interface NodeRendererContextValue { focusedId?: string; @@ -10,7 +10,7 @@ export interface NodeRendererContextValue { focusedTab?: string; clipboardActions: any[]; dialogFactory: DialogFactory; - customSchemas: OBISchema[]; + customSchemas: JSONSchema7[]; } export const defaultRendererContextValue = { diff --git a/Composer/packages/adaptive-flow/src/adaptive-flow-editor/utils/getCustomSchema.ts b/Composer/packages/adaptive-flow/src/adaptive-flow-editor/utils/getCustomSchema.ts index 959cec5e7f..e88810fef8 100644 --- a/Composer/packages/adaptive-flow/src/adaptive-flow-editor/utils/getCustomSchema.ts +++ b/Composer/packages/adaptive-flow/src/adaptive-flow-editor/utils/getCustomSchema.ts @@ -1,24 +1,24 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { OBISchema, SDKKinds } from '@bfc/shared'; +import { JSONSchema7, SDKKinds } from '@bfc/shared'; import pickBy from 'lodash/pickBy'; interface CustomSchemaSet { - actions?: OBISchema; - triggers?: OBISchema; - recognizers?: OBISchema; + actions?: JSONSchema7; + triggers?: JSONSchema7; + recognizers?: JSONSchema7; } -const pickSchema = ( - picked$kinds: SDKKinds[], - sourceSchema: { [key in SDKKinds]: OBISchema } -): OBISchema | undefined => { +type SourceSchema = { [key in SDKKinds]: JSONSchema7 }; + +const pickSchema = (picked$kinds: SDKKinds[], sourceSchema: SourceSchema): JSONSchema7 | undefined => { if (!Array.isArray(picked$kinds) || picked$kinds.length === 0) return undefined; const pickedSchema = picked$kinds.reduce( (schema, $kind) => { const definition = sourceSchema[$kind]; + schema.definitions = schema.definitions ?? {}; schema.definitions[$kind] = definition; schema.oneOf?.push({ title: definition.title || $kind, @@ -30,11 +30,13 @@ const pickSchema = ( { oneOf: [], definitions: {}, - } as OBISchema + } as JSONSchema7 ); // Sort `oneOf` list alphabetically - pickedSchema.oneOf?.sort((a, b) => (a.$ref < b.$ref ? -1 : 1)); + pickedSchema.oneOf?.sort((a, b) => { + return (a.$ref ?? '') < (b.$ref ?? '') ? -1 : 1; + }); return pickedSchema; }; @@ -47,18 +49,18 @@ const roleImplementsInterface = (interfaceName: SDKKinds, $role?: SchemaRole): b return false; }; -const isActionSchema = (schema: OBISchema) => roleImplementsInterface(SDKKinds.IDialog, schema.$role); -const isTriggerSchema = (schema: OBISchema) => roleImplementsInterface(SDKKinds.ITrigger, schema.$role); -const isRecognizerSchema = (schema: OBISchema) => +const isActionSchema = (schema: JSONSchema7) => roleImplementsInterface(SDKKinds.IDialog, schema.$role); +const isTriggerSchema = (schema: JSONSchema7) => roleImplementsInterface(SDKKinds.ITrigger, schema.$role); +const isRecognizerSchema = (schema: JSONSchema7) => roleImplementsInterface(SDKKinds.IRecognizer, schema.$role) || roleImplementsInterface(SDKKinds.IEntityRecognizer, schema.$role); -export const getCustomSchema = (baseSchema?: OBISchema, ejectedSchema?: OBISchema): CustomSchemaSet => { +export const getCustomSchema = (baseSchema?: JSONSchema7, ejectedSchema?: JSONSchema7): CustomSchemaSet => { if (!baseSchema || !ejectedSchema) return {}; if (typeof baseSchema.definitions !== 'object' || typeof ejectedSchema.definitions !== 'object') return {}; const baseDefinitions = baseSchema.definitions; - const ejectedDefinitions = ejectedSchema.definitions; + const ejectedDefinitions = ejectedSchema.definitions ?? {}; const baseKindHash = Object.keys(baseDefinitions).reduce((hash, $kind) => { hash[$kind] = true; @@ -75,9 +77,9 @@ export const getCustomSchema = (baseSchema?: OBISchema, ejectedSchema?: OBISchem return pickBy( { - actions: pickSchema(actionKinds, ejectedDefinitions), - triggers: pickSchema(triggerKinds, ejectedDefinitions), - recognizers: pickSchema(recognizerKinds, ejectedDefinitions), + actions: pickSchema(actionKinds, ejectedDefinitions as SourceSchema), + triggers: pickSchema(triggerKinds, ejectedDefinitions as SourceSchema), + recognizers: pickSchema(recognizerKinds, ejectedDefinitions as SourceSchema), }, (v) => v !== undefined ); diff --git a/Composer/packages/adaptive-form/src/components/fields/ExpressionField/utils.ts b/Composer/packages/adaptive-form/src/components/fields/ExpressionField/utils.ts index 1bf98d71f2..5a4448fcf7 100644 --- a/Composer/packages/adaptive-form/src/components/fields/ExpressionField/utils.ts +++ b/Composer/packages/adaptive-form/src/components/fields/ExpressionField/utils.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { JSONSchema7, JSONSchema7Definition, SchemaDefinitions } from '@bfc/extension-client'; +import { JSONSchema7, SchemaDefinitions } from '@bfc/extension-client'; import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; import { resolveRef, getValueType } from '../../../utils'; @@ -43,7 +43,7 @@ function getOptionLabel(schema: JSONSchema7, parent: JSONSchema7): string { } export function getOneOfOptions( - oneOf: JSONSchema7Definition[], + oneOf: JSONSchema7[], parentSchema: JSONSchema7, definitions?: SchemaDefinitions ): SchemaOption[] { diff --git a/Composer/packages/adaptive-form/src/components/fields/ObjectArrayField.tsx b/Composer/packages/adaptive-form/src/components/fields/ObjectArrayField.tsx index 169c0a7ef9..51cd4ebedc 100644 --- a/Composer/packages/adaptive-form/src/components/fields/ObjectArrayField.tsx +++ b/Composer/packages/adaptive-form/src/components/fields/ObjectArrayField.tsx @@ -6,7 +6,6 @@ import { jsx } from '@emotion/core'; import React, { useState, useMemo, useRef } from 'react'; import { FieldProps, useShellApi } from '@bfc/extension-client'; import { DefaultButton } from 'office-ui-fabric-react/lib/Button'; -import { JSONSchema7 } from 'json-schema'; import { IconButton } from 'office-ui-fabric-react/lib/Button'; import { TextField, ITextField } from 'office-ui-fabric-react/lib/TextField'; import { TooltipHost } from 'office-ui-fabric-react/lib/Tooltip'; @@ -91,7 +90,7 @@ const ObjectArrayField: React.FC> = (props) => { allOrderProps.length > 2 || orderedProperties.some((property) => Array.isArray(property)) || Object.entries(properties).some(([key, propSchema]) => { - const resolved = resolveRef(propSchema as JSONSchema7, props.definitions); + const resolved = resolveRef(propSchema, props.definitions); return allOrderProps.includes(key) && resolved.$role === 'expression'; }) ); @@ -134,7 +133,7 @@ const ObjectArrayField: React.FC> = (props) => { {...props} transparentBorder id={`${id}.${idx}`} - schema={itemSchema as JSONSchema7} + schema={itemSchema} stackArrayItems={stackArrayItems} value={item.value} {...getArrayItemProps(arrayItems, idx, handleChange)} diff --git a/Composer/packages/adaptive-form/src/components/fields/OneOfField/utils.ts b/Composer/packages/adaptive-form/src/components/fields/OneOfField/utils.ts index 0bfbadf8b7..1e744e55cc 100644 --- a/Composer/packages/adaptive-form/src/components/fields/OneOfField/utils.ts +++ b/Composer/packages/adaptive-form/src/components/fields/OneOfField/utils.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { FieldProps, JSONSchema7, JSONSchema7Definition } from '@bfc/extension-client'; +import { FieldProps, JSONSchema7, SchemaDefinitions } from '@bfc/extension-client'; import merge from 'lodash/merge'; import omit from 'lodash/omit'; import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; @@ -23,10 +23,7 @@ function getOptionLabel(schema: JSONSchema7): string { return type || 'unknown'; } -export function getOptions( - schema: JSONSchema7, - definitions?: { [key: string]: JSONSchema7Definition } -): IDropdownOption[] { +export function getOptions(schema: JSONSchema7, definitions?: SchemaDefinitions): IDropdownOption[] { const { type, oneOf } = schema; if (type && Array.isArray(type)) { diff --git a/Composer/packages/adaptive-form/src/utils/resolvePropSchema.ts b/Composer/packages/adaptive-form/src/utils/resolvePropSchema.ts index 12fb4b1027..7bc605f5d6 100644 --- a/Composer/packages/adaptive-form/src/utils/resolvePropSchema.ts +++ b/Composer/packages/adaptive-form/src/utils/resolvePropSchema.ts @@ -1,15 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { JSONSchema7, JSONSchema7Definition } from '@bfc/extension-client'; +import { JSONSchema7, SchemaDefinitions } from '@bfc/extension-client'; import { resolveRef } from './resolveRef'; export function resolvePropSchema( schema: JSONSchema7, path: string, - definitions: { - [k: string]: JSONSchema7Definition; - } = {} + definitions: SchemaDefinitions = {} ): JSONSchema7 | undefined { const propSchema = schema.properties?.[path]; diff --git a/Composer/packages/adaptive-form/src/utils/resolveRef.ts b/Composer/packages/adaptive-form/src/utils/resolveRef.ts index 5fa870f4d1..433ff85adb 100644 --- a/Composer/packages/adaptive-form/src/utils/resolveRef.ts +++ b/Composer/packages/adaptive-form/src/utils/resolveRef.ts @@ -1,12 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { JSONSchema7, JSONSchema7Definition } from '@bfc/extension-client'; +import { JSONSchema7, SchemaDefinitions } from '@bfc/extension-client'; import formatMessage from 'format-message'; -export function resolveRef( - schema: JSONSchema7 = {}, - definitions: { [key: string]: JSONSchema7Definition } = {} -): JSONSchema7 { +export function resolveRef(schema: JSONSchema7 = {}, definitions: SchemaDefinitions = {}): JSONSchema7 { if (typeof schema?.$ref === 'string') { if (!schema?.$ref?.startsWith('#/definitions/')) { return schema; diff --git a/Composer/packages/client/.gitignore b/Composer/packages/client/.gitignore index de820129fc..b612041ef0 100644 --- a/Composer/packages/client/.gitignore +++ b/Composer/packages/client/.gitignore @@ -1,2 +1,3 @@ build/ public/*-bundle.js +public/plugin-host-preload.js diff --git a/Composer/packages/client/config/extensions.config.js b/Composer/packages/client/config/extensions.config.js new file mode 100644 index 0000000000..c1fe1357a8 --- /dev/null +++ b/Composer/packages/client/config/extensions.config.js @@ -0,0 +1,103 @@ +const path = require('path'); + +const PnpWebpackPlugin = require('pnp-webpack-plugin'); +const TerserPlugin = require('terser-webpack-plugin'); + +const paths = require('./paths'); + +module.exports = (webpackEnv) => { + const isEnvDevelopment = webpackEnv === 'development'; + const isEnvProduction = webpackEnv === 'production'; + + return [ + { + entry: { + 'react-bundle': 'react', + }, + mode: isEnvProduction ? 'production' : 'development', + // export react globally under a variable named React + output: { + path: path.resolve(__dirname, '../public'), + library: 'React', + libraryTarget: 'var', + }, + resolve: { + extensions: ['.js'], + }, + optimization: { + minimize: isEnvProduction, + minimizer: [ + new TerserPlugin({ + extractComments: false, + }), + ], + }, + }, + { + entry: { + 'react-dom-bundle': 'react-dom', + }, + mode: isEnvProduction ? 'production' : 'development', + // export react-dom globally under a variable named ReactDOM + output: { + path: path.resolve(__dirname, '../public'), + library: 'ReactDOM', + libraryTarget: 'var', + }, + externals: { + // ReactDOM depends on React, but we need this to resolve to the globally-exposed React variable in react-bundle.js (created by extensions.config.js). + // If we don't do this, ReactDom will bundle its own copy of React and we will have 2 copies which breaks hooks. + react: 'React', + }, + resolve: { + extensions: ['.js'], + }, + optimization: { + minimize: isEnvProduction, + minimizer: [ + new TerserPlugin({ + extractComments: false, + }), + ], + }, + }, + { + mode: isEnvProduction ? 'production' : 'development', + entry: { + 'plugin-host-preload': path.resolve(__dirname, '../extension-container/plugin-host-preload.tsx'), + }, + output: { + path: path.resolve(__dirname, '../public'), + }, + externals: { + // expect react & react-dom to be available in the extension host iframe globally under "React" and "ReactDOM" variables + react: 'React', + 'react-dom': 'ReactDOM', + }, + resolve: { + extensions: ['.js'], + plugins: [PnpWebpackPlugin], + }, + resolveLoader: { + plugins: [ + // Also related to Plug'n'Play, but this time it tells Webpack to load its loaders + // from the current package. + PnpWebpackPlugin.moduleLoader(module), + ], + }, + module: { + rules: [ + { + test: /\.tsx?$/, + loader: require.resolve('ts-loader'), + include: [path.resolve(__dirname, '../extension-container')], + options: PnpWebpackPlugin.tsLoaderOptions({ + transpileOnly: true, + configFile: path.resolve(__dirname, '../tsconfig.build.json'), + }), + }, + ], + }, + }, + ]; +}; diff --git a/Composer/packages/client/config/webpack-react-dom.config.js b/Composer/packages/client/config/webpack-react-dom.config.js deleted file mode 100644 index 4e325e745c..0000000000 --- a/Composer/packages/client/config/webpack-react-dom.config.js +++ /dev/null @@ -1,22 +0,0 @@ -const { resolve } = require('path'); - -module.exports = { - entry: { - 'react-dom-bundle': 'react-dom', - }, - mode: 'production', - // export react-dom globally under a variable named ReactDOM - output: { - path: resolve(__dirname, '../public'), - library: 'ReactDOM', - libraryTarget: 'var', - }, - externals: { - // ReactDOM depends on React, but we need this to resolve to the globally-exposed React variable in react-bundle.js (created by webpack-react.config.js). - // If we don't do this, ReactDom will bundle its own copy of React and we will have 2 copies which breaks hooks. - react: 'React', - }, - resolve: { - extensions: ['.js'], - }, -}; diff --git a/Composer/packages/client/config/webpack-react.config.js b/Composer/packages/client/config/webpack-react.config.js deleted file mode 100644 index 711737994a..0000000000 --- a/Composer/packages/client/config/webpack-react.config.js +++ /dev/null @@ -1,17 +0,0 @@ -const { resolve } = require('path'); - -module.exports = { - entry: { - 'react-bundle': 'react', - }, - mode: 'production', - // export react globally under a variable named React - output: { - path: resolve(__dirname, '../public'), - library: 'React', - libraryTarget: 'var', - }, - resolve: { - extensions: ['.js'], - }, -}; diff --git a/Composer/packages/client/public/plugin-host-preload.js b/Composer/packages/client/extension-container/plugin-host-preload.tsx similarity index 72% rename from Composer/packages/client/public/plugin-host-preload.js rename to Composer/packages/client/extension-container/plugin-host-preload.tsx index 688fa7b79f..13117fd1ac 100644 --- a/Composer/packages/client/public/plugin-host-preload.js +++ b/Composer/packages/client/extension-container/plugin-host-preload.tsx @@ -1,3 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import ReactDOM from 'react-dom'; + +if (!document.head.title) { + const title = document.createElement('title'); + title.innerHTML = 'Plugin Host'; + document.head.append(title); +} + // add default doc styles if (!document.getElementById('plugin-host-default-styles')) { const styles = document.createElement('style'); @@ -23,7 +34,8 @@ if (!document.getElementById('plugin-root')) { } // initialize the API object window.Composer = {}; + // init the render function -window.Composer['render'] = function (component) { +window.Composer.render = function (component) { ReactDOM.render(component, document.getElementById('plugin-root')); }; diff --git a/Composer/packages/client/package.json b/Composer/packages/client/package.json index b9417f2435..4c0aab0466 100644 --- a/Composer/packages/client/package.json +++ b/Composer/packages/client/package.json @@ -8,9 +8,9 @@ "node": ">=12" }, "scripts": { - "start": "yarn build:react-bundles && node scripts/start.js", + "start": "yarn build:extension-bundles && node scripts/start.js", "build": "node --max_old_space_size=4096 scripts/build.js", - "build:react-bundles": "webpack --config ./config/webpack-react.config.js && webpack --config ./config/webpack-react-dom.config.js", + "build:extension-bundles": "webpack --config ./config/extensions.config.js", "clean": "rimraf build", "test": "jest", "lint": "eslint --quiet --ext .js,.jsx,.ts,.tsx ./src ./__tests__", diff --git a/Composer/packages/client/public/index.html b/Composer/packages/client/public/index.html index f07ec70b3f..047330b0d4 100644 --- a/Composer/packages/client/public/index.html +++ b/Composer/packages/client/public/index.html @@ -36,7 +36,7 @@ }; window.CSPSettings = { - nonce: window.__nonce__ + nonce: window.__nonce__ }; diff --git a/Composer/packages/client/src/components/PluginHost/PluginHost.tsx b/Composer/packages/client/src/components/PluginHost/PluginHost.tsx index 1b801b878e..07957cd8df 100644 --- a/Composer/packages/client/src/components/PluginHost/PluginHost.tsx +++ b/Composer/packages/client/src/components/PluginHost/PluginHost.tsx @@ -13,8 +13,9 @@ import { iframeStyle } from './styles'; interface PluginHostProps { extraIframeStyles?: SerializedStyles[]; - pluginName?: string; - pluginType?: PluginType; + pluginName: string; + pluginType: PluginType; + bundleId: string; } /** Binds closures around Composer client code to plugin iframe's window object */ @@ -41,7 +42,7 @@ export const PluginHost: React.FC = (props) => { const { extraIframeStyles = [] } = props; useEffect(() => { - const { pluginName, pluginType } = props; + const { pluginName, pluginType, bundleId } = props; // renders the plugin's UI inside of the iframe const renderPluginView = async () => { if (pluginName && pluginType) { @@ -62,13 +63,14 @@ export const PluginHost: React.FC = (props) => { const cb = () => { resolve(); }; + const bundleUri = `/api/extensions/${pluginName}/${bundleId}`; // If plugin bundles end up being too large and block the client thread due to the load, enable the async flag on this call - injectScript(iframeDocument, pluginScriptId, `/api/extensions/${pluginName}/view/${pluginType}`, false, cb); + injectScript(iframeDocument, pluginScriptId, bundleUri, false, cb); }); } }; renderPluginView(); - }, [props.pluginName, props.pluginType, targetRef]); + }, [props.pluginName, props.pluginType, props.bundleId, targetRef]); return ; }; diff --git a/Composer/packages/client/src/pages/language-generation/code-editor.tsx b/Composer/packages/client/src/pages/language-generation/code-editor.tsx index 30e5ec6aba..aa6cfd03ac 100644 --- a/Composer/packages/client/src/pages/language-generation/code-editor.tsx +++ b/Composer/packages/client/src/pages/language-generation/code-editor.tsx @@ -13,7 +13,7 @@ import { RouteComponentProps } from '@reach/router'; import querystring from 'query-string'; import { CodeEditorSettings } from '@bfc/shared'; import { useRecoilValue } from 'recoil'; -import { LgFile } from '@bfc/shared/src/types/indexers'; +import { LgFile } from '@bfc/extension-client'; import { localeState, lgFilesState, settingsState } from '../../recoilModel/atoms/botState'; import { userSettingsState, dispatcherState } from '../../recoilModel'; diff --git a/Composer/packages/client/src/pages/plugin/pluginPageContainer.tsx b/Composer/packages/client/src/pages/plugin/pluginPageContainer.tsx index 662b0ca484..c589300d45 100644 --- a/Composer/packages/client/src/pages/plugin/pluginPageContainer.tsx +++ b/Composer/packages/client/src/pages/plugin/pluginPageContainer.tsx @@ -6,10 +6,14 @@ import { RouteComponentProps } from '@reach/router'; import { PluginHost } from '../../components/PluginHost/PluginHost'; -const PluginPageContainer: React.FC> = (props) => { - const { pluginId } = props; +const PluginPageContainer: React.FC> = (props) => { + const { pluginId, bundleId } = props; - return ; + if (!pluginId || !bundleId) { + return null; + } + + return ; }; export { PluginPageContainer }; diff --git a/Composer/packages/client/src/pages/publish/createPublishTarget.tsx b/Composer/packages/client/src/pages/publish/createPublishTarget.tsx index cc9fe43042..f897f60595 100644 --- a/Composer/packages/client/src/pages/publish/createPublishTarget.tsx +++ b/Composer/packages/client/src/pages/publish/createPublishTarget.tsx @@ -73,8 +73,8 @@ const CreatePublishTarget: React.FC = (props) => { return targetType ? props.types.find((t) => t.name === targetType)?.schema : undefined; }, [props.targets, targetType]); - const hasView = useMemo(() => { - return targetType ? props.types.find((t) => t.name === targetType)?.hasView : undefined; + const targetBundleId = useMemo(() => { + return targetType ? props.types.find((t) => t.name === targetType)?.bundleId : undefined; }, [props.targets, targetType]); const updateName = (e, newName) => { @@ -85,7 +85,7 @@ const CreatePublishTarget: React.FC = (props) => { const saveDisabled = useMemo(() => { const disabled = !targetType || !name || !!errorMessage; - if (hasView) { + if (targetBundleId) { // plugin config must also be valid return disabled || !pluginConfigIsValid; } @@ -107,13 +107,14 @@ const CreatePublishTarget: React.FC = (props) => { }; const publishTargetContent = useMemo(() => { - if (hasView && targetType) { + if (targetBundleId && targetType) { // render custom plugin view return ( ); } @@ -133,7 +134,7 @@ const CreatePublishTarget: React.FC = (props) => {