From 13ebf11bad0ac4befbb1aee8dbc1d6b552c8b6a2 Mon Sep 17 00:00:00 2001 From: Ketan Reddy Date: Thu, 21 Nov 2024 10:12:50 -0800 Subject: [PATCH 1/2] Add missing DSL docs --- docs/site/astro.config.mjs | 4 + .../site/src/content/docs/authoring/index.mdx | 172 ++++++++++ .../src/content/docs/authoring/plugins.mdx | 46 +++ .../src/content/docs/authoring/schema.mdx | 308 ++++++++++++++++++ .../site/src/content/docs/authoring/views.mdx | 133 ++++++++ 5 files changed, 663 insertions(+) create mode 100644 docs/site/src/content/docs/authoring/index.mdx create mode 100644 docs/site/src/content/docs/authoring/plugins.mdx create mode 100644 docs/site/src/content/docs/authoring/schema.mdx create mode 100644 docs/site/src/content/docs/authoring/views.mdx diff --git a/docs/site/astro.config.mjs b/docs/site/astro.config.mjs index b66f05864..a4cb197ce 100644 --- a/docs/site/astro.config.mjs +++ b/docs/site/astro.config.mjs @@ -87,6 +87,10 @@ export default defineConfig({ label: "Guides", autogenerate: { directory: "guides" }, }, + { + label: "Authoring", + autogenerate: { directory: "authoring" }, + }, { label: "Content", autogenerate: { directory: "content" }, diff --git a/docs/site/src/content/docs/authoring/index.mdx b/docs/site/src/content/docs/authoring/index.mdx new file mode 100644 index 000000000..c2bf5b653 --- /dev/null +++ b/docs/site/src/content/docs/authoring/index.mdx @@ -0,0 +1,172 @@ +--- +title: Overview +--- + +# TSX/JSX Content Authoring (Player DSL) + +While Player content _can_ be written directly in JSON, it's definitely not the preferable authoring format. To take advantage of existing developer tool-chains, Player provides a mechanism for authoring content in (J/T)SX as React components and simple TypeScript objects. The Player CLI can then be used to transpile the React tree into a JSON content. + +## DSL Benefits + +At a high level, the benefits to writing Player content in the DSL can be summarized by three key factors: + +#### Easier maintainability +Simply put, DSL code more concise than its JSON equivalent. That means there is less code for you to have to maintain. Also, as its easier to read than JSON, when you do need to make updates to it, its much more wieldy to work with. + +#### Better development experience +Since the DSL leverages a lot of standard TypeScript language features, most editors will offer quality of life features like typechecking, suggestions, and code generation. All of this is in service of shortening the feedback loop of writing content and ensuring it is what you intended for it. + +#### Easier to extend +The DSL now offers a easily accessible programatic hook into Player content. This allows custom tooling to be created around your DSL integration much easier that before. Common patterns can be extracted into higher level compoennts, functions can be created to generate code, and code generation can be integrated into almost any process where relevant data is present. + +For a further explination on the benefits, see the DSL Benefits section in the [DSL Views](./views.mdx#dsl-benefits-in-views) and the [DSL Schema](./schema.mdx#dsl-benefit-in-schema) + +## Writing DSL Content + +In order to use the DSL to write content, your plugin library should ship a DSL component package. These will define the primitive _components_ to use to build up the tree. Authorship of these components is covered in the [Writing DSL Components](../assets/dsl) secton. The Player Reference Assets ship their own DSL Components via the `@player-ui/reference-assets-components` pacakge. + +In the examples below, we will use the Player Reference Assets Components. + +### Basic Setup + +To get started, you'll need the following dependencies in your `package.json`: + +```json +{ + "dependencies": { + "@player-tools/dsl": "0.4.1", + "@player-tools/cli": "0.4.1", + "@player-ui/reference-assets-components": "0.6.0", + "@types/react": "17.0.39", + "react": "17.0.2" + } +} +``` + +Next, you'll need to configure your environment for DSL Compilation and JSON validation. Below is a basic configuration that can be added in your `package.json`. For a more detailed explination and examples on further customization please refer to the [CLI](../tools/cli) section. + +```json +{ + "player": { + "dsl": { + "src": "./src/main/tsx", + "outDir": "./out" + }, + "json": { + "src": "./out/*.json" + }, + } +} +``` + +### Basic Format and File Layout + +By default, all files that contain a Player Flow should be exported as a `.tsx` file and the schema should be in a `.ts` file. For how to change this behavior, please refer to the [DSL Plugins](./plugins) section of the docs. Each of these files should contain a default export of their appropriate object. For example a file that exports a flow should look like the following: + +```tsx +export default { + id: 'my-flow', + views: [....], + navigation: {....} +} +``` + +and a file that exports the schema should look like: + +```typescript +const mySchema = {...} + +export default mySchema + +``` + +### Navigation + +At this time the `navigation` section is a basic JS object. The `@player-ui/types` package provides typescript typings for this. + +```tsx +import { Navigation } from '@player-ui/types'; + +const navigation: Navigation = { + BEGIN: 'Start', + Start: { + startState: 'VIEW_1', + VIEW_1: { + state_type: 'VIEW', + ref: 'view-1', + transitions: { + '*': 'END_Done', + }, + }, + END_Done: { + state_type: 'END', + outcome: 'done', + }, + }, +}; +``` + +One convenience feature is the auto injection of the the `ref` property for a `VIEW` type state if the corresponding view is a React tree. + +```tsx +import { Navigation } from '@player-ui/types'; + +const view = ( + + Some value + + Some label + + +); + +const navigation: Navigation = { + BEGIN: 'Start', + Start: { + startState: 'VIEW_1', + VIEW_1: { + state_type: 'VIEW', + ref: view, + transitions: { + '*': 'END_Done', + }, + }, + END_Done: { + state_type: 'END', + outcome: 'done', + }, + }, +}; +``` + +_Note: The `Navigation` type we're importing here from the `@player-ui/types` package is different than the `Navigation` type from the `@player-tools/dsl` package. The former is the core definition for what the Navigation section of Player content is. The latter has specific replacements to take DSL constructs where normal objects would be defined._ + +### Bindings and Expressions + +Both `binding` and `expression` in the JSX authoring leverages a tagged template, typically abbreviated as `b` and `e` respectively. In a similar fashion to using `css` or `graphql` in a JS file, this enables syntax-highlighting and validation of bindings and expressions within a JS file. + +```tsx +import { binding as b, expression as e } from '@player-tools/dsl'; + +const myBinding = b`foo.bar`; +const myExpression = e`foo()`; +``` + +The binding and expression instances can also automatically dereference themselves when used inside of another string: + +```tsx +const stringWithBinding = `Some text: ${myBinding}`; // 'Some text: {{foo.bar}}' +const stringWithExp = `Some expr: ${myExpression}`; // 'Some expr: @[foo()]@' +``` + +### View + +Please refer to the [Views](../dsl/views) section for a detailed overview of how to write DSL Views + +### Schema + +Please refer to the [Schema](../dsl/schema) section for a detailed overview of how to write DSL Schemas + +## Compiling DSL Content + +Once your DSL content is authored, you can use the Player CLI to compile and validate your content. For documentation on this functionality, please refer to the [Player CLI](../tools/cli) section \ No newline at end of file diff --git a/docs/site/src/content/docs/authoring/plugins.mdx b/docs/site/src/content/docs/authoring/plugins.mdx new file mode 100644 index 000000000..0742932b4 --- /dev/null +++ b/docs/site/src/content/docs/authoring/plugins.mdx @@ -0,0 +1,46 @@ +--- +title: Plugins +--- + +# DSl Plugins + +Much like the rest of Player, DSL compilation supports plugins that can influce how content gets compiled and generated. DSL Plugins are a subset of CLI Plugins that use either the hooks available on the CLI itself or on the DSL compiler instance created by the CLI. This section will cover the hooks that are available for use and why you might want to tap them. + +## CLI Hooks + +The `createCompilerContext` function available to plugins that extend the `PlayerCLIPlugin` class gives access to the `CompilationContext` instance. This class manages the context around DSL compilation and exposes two related hooks. + +### `identifyContentType` + +The `identifyContentType` hooks's purpose is to allow plugins to inject custom behavior around detecting what kind of file is being compiled. By default there are three types of content the CLI is aware of (`view`, `flow`, and `schema`). Its methods for detecting which kind of content is contained within a file is very rudimentary (the logic can be found [here](https://github.com/player-ui/tools/blob/main/language/dsl/src/compiler/utils.ts#L5)). In order to allow desired convention or orchestrate the compilation of custom file types, this hook provides a mechanism for allowing that custom logic to be injected. The result of this hook is used in the next hook + +### `compileContent` + +The `compileContent` hook's purpose is to allow the custom compilation logic for any identified file type. As it is an `AsyncSeriesBailHook` it will take the first result returned from a tap who was able to return a result for the compilation for the given file of the identified type. In the case where no external logic is added, the hook will attempt to compile any of its known content types with the built in compiler instance. + +## Compilation Hooks + +The CLI will initialize an instance of the `DSLCompiler` and provide a reference to it via the `onCreateDSLCompiler` function available to plugins that extend the `PlayerCLIPlugin` class. On the compiler itself, the following hook are available to modify the behavior of how DSL content is compiled. + +### `preProcessFlow` +_Note: Only called for `view` or `flow` content_ + +This hook allows transformations on the content before it is compiled. This enables the injection of additonal data or resolving any integration specific convention into something that may be understood by the compiler. This hook can also be used to collate information on what is being compiled for use later. + +### `postProcessFlow` +_Note: Only called for `view` or `flow` content_ + +This hook allows transformations on the content after it is compiled. This allows modifications to the compiled content which in some cases may be preferable as manipulating JSON may be easier than a React Tree. + +### `schemaGenerator` + +This hook gives access to the internal `SchemaGenerator` object which is responsible for compiling the schema. On this generator there are the following hooks. + +#### `createSchemaNode` + +This hook allows custom logic for processing schema nodes as they are generated. This enables arbitrary properties to be statically or dynamically added based on the authored schema node. One potential usecase of this is to allow integration specific semantic conventions to be defined and injected into the final schema. For example, the presence of a specific `Symbol` might mean that a property needs to be injected or even that the schema tree from this point on needs to be modified. + + +### `onEnd` + +This hook is called to signal that the compilation of all files has been completed. This allows any post processing on the output as a whole to take place as a part of the build process. This may include actions like moving or bundling the compilation results or writing new files based on information collected via other hooks on the files that were processed. \ No newline at end of file diff --git a/docs/site/src/content/docs/authoring/schema.mdx b/docs/site/src/content/docs/authoring/schema.mdx new file mode 100644 index 000000000..9aa65bf28 --- /dev/null +++ b/docs/site/src/content/docs/authoring/schema.mdx @@ -0,0 +1,308 @@ +--- +title: Schema +--- + +# Basic Schema + +To author a schema object you should first start by constructing a standard typescript object where the nested paths correlate to the paths on your desired schema. When compiled to the final Player `Schema` object, the intermediate types and ROOT elements will automatically be constructed. A basic example would be: + +```typescript +export default { + foo: { + bar: { + baz: {...} + faz: {...} + } + } +} +``` + +which correlates to a schema of: + +```json +{ + "ROOT": { + "foo": { + "type": "fooType" + } + }, + "fooType": { + "bar": { + "type": "barType" + } + }, + "barType": { + "baz": {...}, + "faz": {...} + } +} +``` + +## Arrays + +A single object array can be used to indicate an array type, for example: + +```typescript +export default { + foo: [ + { + bar: {...} + } + ] +} +``` + +will generate the schema: + +```json +{ + "ROOT": { + "foo": { + "type": "fooType", + "isArray": true + } + }, + "fooType": { + "bar": { + "type": "barType" + } + }, + "barType": { + "baz": {...}, + "faz": {...} + } +} +``` + +## Changing the Name of a Generated Type + +To change the name of the generated type at any point in the tree, import the `SchemaTypeName` symbol from the `@player-tools/dsl` and use it as a key on the object whos name you want to change: + +```typescript +import { SchemaTypeName } from "@player-tools/dsl" +export default { + foo: { + bar: { + [SchemaTypeName]: "buzz", + baz: {...} + faz: {...} + } + } +} +``` + +will generate the schema: + +```json +{ + "ROOT": { + "foo": { + "type": "fooType", + "isArray": true + } + }, + "fooType": { + "buzz": { + "type": "buzzType" + } + }, + "buzzType": { + "baz": { + "type": "" + }, + "faz": { + "type": "" + } + } +} +``` + +# Defining Data Types + +The leaf nodes of the schema will need some concrete definition of what data exists at that point of the schema. There are two ways to provide this data. + +## Data Refs + +The `@player-ui/common-types-plugin` package exposes the types it provides to Player when used and _references_ to those types as well. Using these `Language.DataTypeRef` values you can indicate what the data type will be at that node and that it will be a type explicitly defined in Player so no additional information needs to be provided (e.g. validations nor formats) as at runtime it will use the type loaded into Player by the plugin. + +It is recommended that if your player integration loads additional types, to export similar references to those types to make authorship easier. + +##### Local Data Types + +Sometimes you need to define specific data types that extend existing types for certain pieces of data in a schema, whether that be for specific validations, formatting or both. In this case, in your DSL project you can define an object of type `Schema.DataType` and provide that value to a leaf node. That will indicate that this unique type needs to be included in its entirety to Player as it has information not already contained in Player. + +##### What that Looks Like + +Using our previous example we can fill in the values with some types now to look like this in the ts object: + +```typescript +import { dataTypes } from '@player-ui/common-types-plugin'; +import type { Schema } from '@player-ui/types'; + +const mycustombooleantype = { + type: "BooleanType", + validation: [ + { + type: 'oneOf', + message: 'Value has to be true or false', + options: [true, false], + }, + ], +} satisfies Schema.DataType + +const mySchema = { + foo: { + bar: { + baz: dataTypes.BooleanTypeRef + faz: mycustombooleantype + } + } +} + +export default mySchema +``` + +and like this in the final schema: + +```json +{ + "ROOT":{ + "foo":{ + "type": "fooType" + } + }, + "fooType":{ + "bar": { + "type":"barType" + } + }, + "barType":{ + "baz":{ + "type": "BooleanType" + }, + "faz":{ + "type": "BooleanType", + "validation": [ + { + "type": "oneOf", + "message": "Value has to be true or false", + "options": [true, false], + }, + ], + } + } +} +``` + +# DSLSchema Type + +A `DSLSchema` Type is provided in order be able to customize a set of the acceptable data types and validator functions to be used for authoring the DSL data schema in your workspace. This is useful as it allows content authors to get realtime feedback on their data schema. It can catch any structural issues that may produce an invalid schema at compile time or produce a schema that uses functionality thats not available at runtime. + +_Note: A ready-to-use DSLSchema type is shipped with `@player-ui/reference-assets-components`. This type is predefined with the `DataType` and `ValidatorFunction` references inferred from the `@player-ui/common-types-plugin`. Next, you'll be presented the steps in its creation for reference._ + +The first step to fill in the `DSLSchema` type with your integration specific values is importing the `DSLSchema` type and the relevant helper types and utilities from `@player-tools/dsl`. For this example we are importing the `@player-ui/common-types-plugin` in order to use its data types and validators. Our first step is to generate the `DataType` and `ValidatorFunction` object types and references: + +```typescript +import { + DSLSchema, + DataTypeReference + DataTypeRefs, + ValidatorFunctionRefs, + getObjectReferences +} from "@player-tools/dsl" +import { + dataTypes as commonDataTypes, + validators as commonValidators +} from "@player-ui/common-types-plugin"; + +/** Abstracting the types from commonDataTypes to be passed as generic to the DataTypeRefs type for dynamically generating the data type reference Types */ +type myCommonDataTypesRefs = DataTypeRefs + +/** Using getObjectReferences helper to generate the actual data type references to be used in your schema by passing inferred types from commonDataTypes and myCommonDataTypesRefs */ +export const dataRefs = getObjectReferences( + coreDataSet +); +``` + +We'll proceed generating the validation function types: + +```typescript +/** Abstracting types from coreValidators and using as generic of ValidatorFunctionRefs for dynamically generating the data validation function reference Types */ +type commonValidatorRefs = ValidatorFunctionRefs +``` + +The final step is to provide the data Types set and validator function reference Types as generics for the `DataTypeReference` type which is the sole generic type passed into the `DSLSchema` instance: +```typescript +type CommonDSLSchema = DSLSchema< + DataTypeReference +> +``` + +Finally, this is how to use the custom schema type to type check your schema. By adding the `satisfies` keyword followed by your `DSLSchema` generated type, your editor's LSP will show if there is anything not compliant with the data types and validation functions you defined in the schema: + +```typescript +import { CommonDSLSchema, dataRefs } from "./MyTypes" + +const { BooleanTypeRef } = dataRefs + +const exampleSchema = { + myDataSet = { + /** Simply using the BooleanTypeReference to define "firstPath" type to Boolean */ + firstPath: BooleanTypeRef + secondPath: { + /** For adding custom validation for "secondPath", define an object definition with the data "type" property, which is "TextType" for this example */ + type: "TextType", + /** In the validation array open another object definition specifying the use of the "required" validator with the "type" property, with a custom "message" value */ + validation: [ + { + type: "required", + message: "This field is required" + } + ] + } + } +} satisfies CommonDSLSchema +``` + +_Note: The `satisfies` Typescript operator is used instead of type assignment (`exampleSchema:DSLSChema`), because `satisfies` doesn't lose the inferred type of your value by changing and broadening the type unlike using assignment, it simply type-checks, creating localised feedback if anything is incorrect._ + + +# Using the Schema Object in JSX/TSX Content + +As the schema is now a TypeScript obejct, you can now directly reference the schema anywhere in content. The `makeBindingsForObject()` function takes your schema object and constructs the bindings opaquely within the object. This allows the use of the native path in your authored content and for the actual underlying binding to be used when the content is compiled. Additionally, as the underlying bindings are exposed, can you can use the native object path with functions like `.toString()`, `.toValue()`, and `toRefString()` like you could with regular string template bindings. + +```jsx +import { makeBindingsForObject } from '@player-tools/dsl'; +import { mySchema } from './schema' + +const schema = makeBindingsForObject(mySchema) + +const baz = schema.foo.bar.baz + +const view = ( + + + + The current value is {baz.toString()} + + + +) + +const navigation = {...} + +export default { + id: "example", + views: [view], + navigation, +} +``` + +# DSL Benefits in Schema +Player's schema has a few issues that makes it both hard to write and very susceptible to experience breaking mistakes. The DSL Schema aims to mitigate all of these pitfalls. + +## Format +The flattened nature of Player's schema makes it confusing to write for content authors new to Player and tedius to write for more experienced content authors. By allowing the schema to be represented in a tree format, the schema is both easier to write and more closely resembles the shape that the data will take in the data model. + +## Ease of Use +The biggest quality of life improvement the DSL schema brings is that the schema object can be directly used in content. This almost completely eliminates the positiblity of mistyping a schema path leading to runtime errors and massively reduces how verbose content needs to be. Additionally, with plugins able to export references to the data types and validation functions they export, content can now directly reference those artifacts further reducing the possibility of producing invalid content. Lastly, given the number of features that are available in the schema, it can be overwhelming to remember what can/should be used where. By making the schema a normal TypeScript object and assigning it the proper type, content authors are given autocomplete suggestions and a cursory level of validation in their development environment while they are writing their schema. diff --git a/docs/site/src/content/docs/authoring/views.mdx b/docs/site/src/content/docs/authoring/views.mdx new file mode 100644 index 000000000..33a12d17a --- /dev/null +++ b/docs/site/src/content/docs/authoring/views.mdx @@ -0,0 +1,133 @@ +--- +title: Views +--- + +# Overview +Writing assets or views is as simple as creating a React element using your base DSL components: + +```tsx +import React from 'react'; +import { Input, Text, Collection } from '@player-ui/reference-assets-components'; + +const view = ( + + Some value + + Some label + + +); +``` + +When compiled, this would produce the following JSON. + +```json +{ + "id": "root", + "type": "collection", + "values": [ + { + "asset": { + "id": "root-values-1", + "type": "text", + "value": "Some value" + } + }, + { + "asset": { + "id": "root-values-2", + "type": "input", + "label": { + "asset": { + "id": "root-values-2-label", + "type": "text", + "value": "Some label" + } + } + } + } + ] +} +``` + +Not only is the source DSL content a fraction of the output object's size (making it easier to read and maintain) as the base components use the same TypeScript types as the assets themselves, you will receive in editor suggestions and type checks as you author your content. + +# View Concepts in DSL + +## Templates + +Templates are included via the `@player-tools/dsl` package. This can be used in any asset slot: + +```tsx +import React from 'react'; +import { dataTypes } from '@player-ui/common-types-plugin'; +import { makeBindingsForObject, Template } from '@player-tools/dsl'; + +const schema = { + foo: [{ + bar: dataTypes.StringType, + }], +}; + +const bindings = makeBindingsForObject(schema); + + + + + + +``` + +Templates can be nested within one another, and the auto-id generation will handle adding the `_index_` information to any generated `id`. + +## Switches + +The `@player-tools/dsl` module also includes support for _static_ and _dynamic_ switches. + +Use the `isDynamic` flag to denote this should be a `dynamicSwitch` instead of a `staticSwitch`: + +```tsx +import React from 'react'; +import { Switch } from '@player-tools/dsl'; + + + + + + Text 1 + + + Text 1 + + + + +``` + +# DSL Benefits in Views + +## IDs + +Any asset can accept an `id` property, however automatic ID creation is supported out of the box by the base `Asset` component and it's generation behavior can be further customized via your component's implementation. + +## Collection/Text Creation + +In the event that an asset object is expected, but a `string` or `number` is found, Player will attempt to automatically create a text node, provided the asset-library has a text-asset-factory configured. + +Similarly, if a single asset is expected but a list of them is found instead, Player will attempt to create a _collection_ asset provided the library has the proper configuration set. + +## Meta Components + +As DSL components are React component, they can be composed into reusable building blocks to simplify and abstract away common UI patterns. By centralizing these patterns, code duplication can be minimized and updates across multiple sets of content can be simplified. These composed components don't just have to be built on top of the base set of DSL components, DSL components themselves can offer common shortcuts for behavior. For example, if we wanted to offer an out of the box `Action` component that could be used as a `Next` action asset, we could export the following from the DSL components library. + +```tsx +import React from 'react'; + +Action.Next = () => ( + + Continue + +); +``` \ No newline at end of file From f4b5cd7a42179c3833107fafe2b0040f8a51ac19 Mon Sep 17 00:00:00 2001 From: Ketan Reddy Date: Thu, 21 Nov 2024 10:15:03 -0800 Subject: [PATCH 2/2] Update PR template --- .github/PULL_REQUEST_TEMPLATE.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index c2792efdb..38a7a36c7 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -19,19 +19,7 @@ Indicate the type of change your pull request is: - [ ] `patch` - [ ] `minor` - [ ] `major` - - +- [ ] `N/A` ### Does your PR have any documentation updates? @@ -44,4 +32,17 @@ Please refer to our site https://player-ui.github.io/latest/about, and include a If you are unable to update the current documents, please create an issue for us to get back to it. +--> + + \ No newline at end of file