diff --git a/docs/content/docs/developer-guide/deployment.md b/docs/content/docs/developer-guide/deployment.md index d9cb1db7e0..ffcc6a4eed 100644 --- a/docs/content/docs/developer-guide/deployment.md +++ b/docs/content/docs/developer-guide/deployment.md @@ -15,3 +15,7 @@ The bare minimum requirements are: A typical pattern is to run the Vendure app on the server, e.g. at `http://localhost:3000` an then use [nginx as a reverse proxy](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/) to direct requests from the Internet to the Vendure application. Here is a good guide to setting up a production-ready server for an app such as Vendure: https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-18-04 + +## Admin UI + +If you have customized the Admin UI with extensions, it can make sense to [compile your extensions ahead-of-time as part of the deployment process]({{< relref "/docs/plugins/extending-the-admin-ui" >}}#compiling-as-a-deployment-step). diff --git a/docs/content/docs/developer-guide/order-workflow/_index.md b/docs/content/docs/developer-guide/order-workflow/_index.md index 0085c6cbf2..b396a69fdc 100644 --- a/docs/content/docs/developer-guide/order-workflow/_index.md +++ b/docs/content/docs/developer-guide/order-workflow/_index.md @@ -25,5 +25,5 @@ So if the customer adds 2 *Widgets* to the Order, there will be **one OrderLine* ## Shop client order workflow -The [GraphQL API Guide]({{< relref "/docs/developer-guide/graphql-api-guide" >}}#order-flow) lists the GraphQL operations you will need to implement this workflow in your storefront client application. +The [GraphQL Shop API Guide]({{< relref "/docs/storefront/shop-api-guide" >}}#order-flow) lists the GraphQL operations you will need to implement this workflow in your storefront client application. diff --git a/docs/content/docs/developer-guide/overview/_index.md b/docs/content/docs/developer-guide/overview/_index.md index b0cc73da74..af59c64ee2 100644 --- a/docs/content/docs/developer-guide/overview/_index.md +++ b/docs/content/docs/developer-guide/overview/_index.md @@ -36,4 +36,4 @@ Vendure supports multiple databases. Currently it is tested with MySQL/MariaDB, ## Custom Business Logic (Plugins) -Not shown on the diagram (for the sake of simplicity) are plugins. Plugins are the mechanism by which you extend Vendure with your own business logic and functionality. See [the Plugins docs]({{< relref "/docs/developer-guide/plugins" >}}) +Not shown on the diagram (for the sake of simplicity) are plugins. Plugins are the mechanism by which you extend Vendure with your own business logic and functionality. See [the Plugins docs]({{< relref "/docs/plugins" >}}) diff --git a/docs/content/docs/developer-guide/plugins/_index.md b/docs/content/docs/developer-guide/plugins/_index.md deleted file mode 100644 index 01b7760b60..0000000000 --- a/docs/content/docs/developer-guide/plugins/_index.md +++ /dev/null @@ -1,183 +0,0 @@ ---- -title: "Plugins" -weight: 2 -showtoc: true ---- - -# Plugins - -Plugins are the method by which the built-in functionality of Vendure can be extended. Plugins in Vendure allow one to: - -1. Modify the [VendureConfig]({{< ref "/docs/typescript-api/configuration" >}}#vendureconfig) object. -2. Extend the GraphQL API, including modifying existing types and adding completely new queries and mutations. -3. Define new database entities and interact directly with the database. -4. Run code before the server bootstraps, such as starting webservers. - -These abilities make plugins a very versatile and powerful means of implementing custom business requirements. - -This section details the official Vendure plugins included in the main Vendure repo, as well as a guide on writing your own plugins for Vendure. - -## Plugin Architecture - -{{< figure src="plugin_architecture.png" >}} - -A plugin in Vendure is a specialized Nestjs Module which is decorated with the [`VendurePlugin` class decorator]({{< relref "vendure-plugin" >}}). This diagram illustrates the how a plugin can integrate with and extend Vendure. - -1. A Plugin may define logic to be run by the [Vendure Worker]({{< relref "vendure-worker" >}}). This is suitable for long-running or resource-intensive tasks and is done by providing controllers via the [`workers` metadata property]({{< relref "vendure-plugin-metadata" >}}#workers). -2. A Plugin can modify any aspect of server configuration via the [`configuration` metadata property]({{< relref "vendure-plugin-metadata" >}}#configuration). -3. A Plugin can extend the GraphQL APIs via the [`shopApiExtensions` metadata property]({{< relref "vendure-plugin-metadata" >}}#shopapiextensions) and the [`adminApiExtensions` metadata property]({{< relref "vendure-plugin-metadata" >}}#adminapiextensions). -4. A Plugin can interact with Vendure by importing the [`PluginCommonModule`]({{< relref "plugin-common-module" >}}), by which it may inject and of the core Vendure services (which are responsible for all interaction with the database as well as business logic). Additionally a plugin may define new database entities via the [`entities` metadata property]({{< relref "vendure-plugin-metadata" >}}#entities) and otherwise define any other providers and controllers just like any [Nestjs module](https://docs.nestjs.com/modules). -5. A Plugin can run arbitrary code, which allows it to make use of external services. For example, a plugin could interface with a cloud storage provider, a payment gateway, or a video encoding service. - -## Plugin Examples - -Here are some simplified examples of plugins which serve to illustrate what can be done with Vendure plugins. *Note: implementation details are skipped in these examples for the sake of brevity. A complete example with explanation can be found in [Writing A Vendure Plugin]({{< relref "writing-a-vendure-plugin" >}}).* - -{{% alert "primary" %}} - For a complete working example of a Vendure plugin, see the [real-world-vendure Reviews plugin](https://github.com/vendure-ecommerce/real-world-vendure/tree/master/src/plugins/reviews) -{{% /alert %}} - -### Modifying the VendureConfig - -This example shows how to modify the VendureConfig, in this case by adding a custom field to allow product ratings. -```TypeScript -@VendurePlugin({ - configure: config => { - config.customFields.Product.push({ - name: 'rating', - type: 'float', - min: 0, - max: 5, - }); - return config; - }, -}) -class ProductRatingPlugin {} -``` - -### Defining a new database entity - -This example shows how new TypeORM database entities can be defined by plugins. - -```TypeScript -@Entity() -class ProductReview extends VendureEntity { - @Column() - text: string; - - @Column() - rating: number; -} - -@VendurePlugin({ - entites: [ProductReview], -}) -export class ReviewsPlugin {} -``` - -### Extending the GraphQL API - -This example adds a new query to the GraphQL Admin API. It also demonstrates how [Nest's dependency injection](https://docs.nestjs.com/providers) can be used to encapsulate and inject services within the plugin module. - -```TypeScript -@VendurePlugin({ - imports: [PluginCommonModule], - providers: [TopSellersService], - adminApiExtensions: { - schema: gql` - extend type Query { - topSellers(from: DateTime! to: DateTime!): [Product!]! - } - `, - resolvers: [TopSellersResolver] - } -}) -export class TopSellersPlugin {} - -@Resolver() -class TopSellersResolver { - - constructor(private topSellersService: TopSellersService) {} - - @Query() - topSellers(@Ctx() ctx: RequestContext, @Args() args: any) { - return this.topSellersService.getTopSellers(ctx, args.from, args.to); - } - -} - -@Injectable() -class TopSellersService { - getTopSellers() { /* ... */ } -} -``` - -### Adding a REST endpoint - -This plugin adds a single REST endpoint at `/products` which returns a list of all Products. Since this uses no Vendure-specific metadata, it could also be written using the Nestjs `@Module()` decorator rather than the `@VendurePlugin()` decorator. - -```TypeScript -@VendurePlugin({ - imports: [PluginCommonModule], - controllers: [ProductsController], -}) -export class RestPlugin {} - -@Controller('products') -export class ProductsController { - constructor(private productService: ProductService) {} - - @Get() - findAll(@Ctx() ctx: RequestContext) { - return this.productService.findAll(ctx); - } -} -``` - -### Running processes on the Worker - -This example shows how to set up a microservice running on the Worker process, as well as subscribing to events via the EventBus. - -Also see the docs for [WorkerService]({{< relref "worker-service" >}}) and [WorkerMessage]({{< relref "worker-message" >}}). - -```TypeScript -@VendurePlugin({ - imports: [PluginCommonModule], - workers: [OrderProcessingController], -}) -export class OrderAnalyticsPlugin implements OnVendureBootstrap { - - constructor( - private workerService: WorkerService, - private eventBus: EventBus, - ) {} - - /** - * When the server bootstraps, set up a subscription for events - * published whenever an Order changes state. When an Order has - * been fulfilled, we send a message to the controller running on - * the Worker process to let it process that order. - */ - onVendureBootstrap() { - this.eventBus.ofType(OrderStateTransitionEvent).subscribe(event => { - if (event.toState === 'Fulfilled') { - this.workerService.send(new ProcessOrderMessage({ order: event.order })).subscribe(); - } - }); - } - -} - -/** - * This controller will run on the Worker process. - */ -@Controller() -class OrderProcessingController { - - @MessagePattern(ProcessOrderMessage.pattern) - async processOrder(order) { - // Do some expensive computation - } - -} -``` diff --git a/docs/content/docs/developer-guide/plugins/extending-the-admin-ui.md b/docs/content/docs/developer-guide/plugins/extending-the-admin-ui.md deleted file mode 100644 index c972bc7e07..0000000000 --- a/docs/content/docs/developer-guide/plugins/extending-the-admin-ui.md +++ /dev/null @@ -1,347 +0,0 @@ ---- -title: "Extending the Admin UI" -weight: 1 ---- -# Extending the Admin UI - -When creating a plugin, you may wish to extend the Admin UI in order to expose an interface to the plugin's functionality. - -This is possible by defining [AdminUiExtensions]({{< ref "admin-ui-extension" >}}). A UI extension is an [Angular module](https://angular.io/guide/ngmodules) which gets compiled into the Admin UI application bundle by the AdminUiPlugin. - -{{% alert warning %}} -Note: an understanding of [Angular](https://angular.io/) is required to successfully work with UI extensions. Try [Angular's "Getting Started" guide](https://angular.io/start) to learn more. -{{% /alert %}} - -{{% alert "primary" %}} - For a complete working example of a Vendure plugin which extends the Admin UI, see the [real-world-vendure Reviews plugin](https://github.com/vendure-ecommerce/real-world-vendure/tree/master/src/plugins/reviews) -{{% /alert %}} - -## Simple Example - -Here is a very simple example to illustrate how a UI extension works: - -### 1. Create the Angular module - -Below is an Angular module with a single component `GreeterComponent` which displays a greeting. A route is defined to load `GreeterComponent` at the route `/greet`. - -```TypeScript -// project/ui-extensions/greeter-extension.module.ts - -import { Component, NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { SharedModule } from '@vendure/admin-ui/src'; - -@Component({ - selector: 'greeter', - template: `

{{ greeting }}

`, -}) -export class GreeterComponent { - greeting = 'Hello!'; -} - -@NgModule({ - imports: [ - SharedModule, - RouterModule.forChild([{ - path: 'greet', - component: GreeterComponent, - data: { breadcrumb: 'Greeter' }, - }]), - ], - declarations: [GreeterComponent], -}) -export class GreeterModule {} -``` - -### 2. Define the extension in the AdminUiOptions - -Now we need to tell the AdminUiPlugin where to find the extension, and which file contains the NgModule itself (since a non-trivial UI extension will likely contain multiple files). - -```TypeScript -// project/vendure-config.ts - -import { AdminUiPlugin } from '@vendure/admin-ui-plugin'; -import { VendureConfig } from '@vendure/core'; - -export const config: VendureConfig = { - // ... - plugins: [ - AdminUiPlugin.init({ - port: 5001, - extensions: [ - { - extensionPath: path.join(__dirname, 'ui-extensions'), - ngModules: [{ - type: 'lazy', - ngModuleFileName: 'greeter-extension.module.ts', - ngModuleName: 'GreeterModule', - }], - } - ], - }) - ] -} -``` - -### 3. Test the extension - -Running the Vendure server will now cause the UI extension to be compiled into the Admin UI application. In the console you'll see a message like: - -```console -info 9/25/19, 09:54 - [Vendure Server] Bootstrapping Vendure Server (pid: 43092)... -info 9/25/19, 09:54 - [AdminUiPlugin] Compiling Admin UI with extensions... - - - -info 9/25/19, 09:55 - [AdminUiPlugin] Completed compilation! -``` - -Now go to the Admin UI app in your browser and log in. You should now be able to manually enter the URL `http://localhost:3000/admin/extensions/greet` and you should see the component with the "Hello!" header: - -{{< figure src="../ui-extensions-greeter.jpg" >}} - -## Lazy vs Shared Modules - -In the above example we set the `type` to `'lazy'`. A lazy module is not loaded when the Admin UI app is bootstrapped. It is only lazily-loaded when the `/extensions` route is activated. For this reason, lazy modules should not be used to host any logic that should be executed at bootstrap time, or that applies to other parts of the Admin UI app. For such logic, `'shared'` modules should be used. - -Shared modules get imported into the main Admin UI `AppModule` and therefore are present at bootstrap time. The main use-case for shared modules is to define custom navigation items and custom field controls using the `NavBuilderService` and the `CustomFieldComponentService` respectively (see below). - -## Custom navigation: NavBuilderService - -### Extending the NavMenu - -Once you have defined some custom views in a lazy extension module, you need some way for the administrator to access these views. For this you will use the `NavBuilderService` to define new navigation items. Let's add a new section to the Admin UI main nav bar containing a link to the lazy module from the simple example above: - -```TypeScript -// project/ui-extensions/shared-extension.module.ts - -import { NgModule, APP_INITIALIZER } from '@angular/core'; -import { SharedModule, NavBuilderService } from '@vendure/admin-ui/src'; - -@NgModule({ - imports: [SharedModule], - providers: [{ - provide: APP_INITIALIZER, - multi: true, - useFactory: addNavItems, - deps: [NavBuilderService], - }] -}) -export class SharedExtensionModule {} - -export function addNavItems(navBuilderService: NavBuilderService) { - return () => { - navBuilderService.addNavMenuSection({ - id: 'greeter', - label: 'My Extensions', - items: [ - { - id: 'greeter', - label: 'Greeter', - routerLink: ['/extensions/greet'], - // Icon can by any of https://clarity.design/icons - icon: 'cursor-hand-open', - }, - ], - }, - // Add this section before the "settings" section - 'settings'); - }; -} -``` - -This module makes use of the [APP_INITIALIZER](https://angular.io/api/core/APP_INITIALIZER) provider, which allows us to execute logic which runs when the Angular app completes initialization. In the `addNavItems()` function we return a new function in which we add a new section to the nav menu. - -Next we must add this shared module to the AdminUiOptions: - -```TypeScript -// project/vendure-config.ts - - plugins: [ - AdminUiPlugin.init({ - port: 3002, - extensions: [ - { - extensionPath: path.join(__dirname, 'ui-extensions'), - ngModules: [{ - type: 'lazy', - ngModuleFileName: 'greeter-extension.module.ts', - ngModuleName: 'GreeterModule', - }, { - type: 'shared', - ngModuleFileName: 'shared-extension.module.ts', - ngModuleName: 'SharedExtensionModule', - }], - }, - ], - }), - ], -``` - -Running the server will compile our new shared module into the app, and the result should look like this: - -{{< figure src="../ui-extensions-navbar.jpg" >}} - -### Adding new ActionBar buttons - -It may not always make sense to navigate to your extension view from the main nav menu. For example, a "product reviews" extension that shows reviews for a particular product. In this case, you can add new buttons to the "ActionBar", which is the horizontal section at the top of each screen containing the primary actions for that view. - -Here's an example of how this is done: - -```TypeScript -@NgModule({ - imports: [SharedModule], - providers: [{ - provide: APP_INITIALIZER, - multi: true, - useFactory: addNavItems, - deps: [NavBuilderService], - }] -}) -export class SharedExtensionModule {} - -export function addNavItems(navBuilderService: NavBuilderService) { - return () => { - navBuilderService.addActionBarItem({ - id: 'product-reviews', - label: 'Product reviews', - locationId: 'product-detail', - buttonStyle: 'outline', - routerLink: ['./reviews'], - requiresPermission: 'SuperAdmin' - }); - }; -} -``` - -{{< figure src="../ui-extensions-actionbar.jpg" >}} - -In each list or detail view in the app, the ActionBar has a unique `locationId` which is how the app knows in which view to place your button. Here is a complete list of available locations into which you can add new ActionBar buttons: -```text -asset-list -collection-detail -collection-list -facet-detail -facet-list -product-detail -product-list -customer-detail -customer-list -promotion-detail -promotion-list -order-detail -order-list -administrator-detail -administrator-list -channel-detail -channel-list -country-detail -country-list -global-settings-detail -payment-method-detail -payment-method-list -role-detail -role-list -shipping-method-detail -shipping-method-list -tax-category-detail -tax-category-list -tax-rate-detail -tax-rate-list -``` - -## CustomField controls: CustomFieldComponentService - -Another way to extend the Admin UI app is to define custom form control components for manipulating any [Custom Fields]({{< ref "/docs/typescript-api/custom-fields" >}}) you have defined on your entities. - -Let's say you define a custom "intensity" field on the Product entity: - -```TypeScript -// project/vendure-config.ts - -customFields: { - Product: [ - { name: 'intensity', type: 'int', min: 0, max: 100, defaultValue: 0 }, - ], -} -``` -By default, the "intensity" field will be displayed as a number input: - -{{< figure src="../ui-extensions-custom-field-default.jpg" >}} - -But let's say we want to display a range slider instead. Here's how we can do this using our shared extension module combined with the `CustomFieldComponentService`: - -```TypeScript -import { NgModule, APP_INITIALIZER, Component } from '@angular/core'; -import { FormControl } from '@angular/forms'; -import { SharedModule, CustomFieldControl, CustomFieldConfig, CustomFieldComponentService } from '@vendure/admin-ui/src'; - -@Component({ - template: ` - - {{ formControl.value }} - `, -}) -export class SliderControl implements CustomFieldControl { - customFieldConfig: CustomFieldConfig; - formControl: FormControl; -} - -@NgModule({ - imports: [SharedModule], - declarations: [SliderControl], - entryComponents: [SliderControl], - providers: [{ - provide: APP_INITIALIZER, - multi: true, - useFactory: defineCustomFieldControls, - deps: [CustomFieldComponentService], - }] -}) -export class SharedExtensionModule { } - -export function defineCustomFieldControls(customFieldComponentService: CustomFieldComponentService) { - return () => { - customFieldComponentService.registerCustomFieldComponent('Product', 'intensity', SliderControl); - } -} -``` - -Re-compiling the Admin UI will result in our SliderControl now being used for the "intensity" custom field: - -{{< figure src="../ui-extensions-custom-field-slider.jpg" >}} - -To recap the steps involved: - -1. Create an Angular Component which implements the `CustomFieldControl` interface. -2. Add this component to your shared extension module's `declarations` and `entryComponents` arrays. -3. Create a function to run on app initialization and use the `CustomFieldComponentService` to register your component for the given entity & custom field name. - -## Watch mode - -When you are developing your Admin UI extension, you can set the `watch` option to `true` which will compile the Admin UI app in development mode, and recompile and auto-refresh the browser on any changes to your extension source files. - -```TypeScript -// project/vendure-config.ts - - plugins: [ - AdminUiPlugin.init({ - port: 3002, - extensions: [ - { - extensionPath: path.join(__dirname, 'ui-extensions'), - ngModules: [{ - type: 'lazy', - ngModuleFileName: 'greeter-extension.module.ts', - ngModuleName: 'GreeterModule', - }], - } - ], - watch: true, - }), - ], -``` diff --git a/docs/content/docs/getting-started.md b/docs/content/docs/getting-started.md index e2635776a5..0157b2ddc6 100644 --- a/docs/content/docs/getting-started.md +++ b/docs/content/docs/getting-started.md @@ -73,4 +73,4 @@ Log in with the superadmin credentials: ## Next Steps * Get a better understanding of how Vendure works by reading the [Architecture Overview]({{< relref "/docs/developer-guide/overview" >}}) -* Learn how to implement a storefront with the [GraphQL API Guide]({{< relref "/docs/developer-guide/graphql-api-guide" >}}) +* Learn how to implement a storefront with the [GraphQL API Guide]({{< relref "/docs/storefront/shop-api-guide" >}}) diff --git a/docs/content/docs/plugins/_index.md b/docs/content/docs/plugins/_index.md new file mode 100644 index 0000000000..372ed04b34 --- /dev/null +++ b/docs/content/docs/plugins/_index.md @@ -0,0 +1,21 @@ +--- +title: "Plugins" +weight: 1 +--- + +# Vendure Plugins + +Plugins are the method by which the built-in functionality of Vendure can be extended. Plugins in Vendure allow one to: + +* Modify the [VendureConfig]({{< ref "/docs/typescript-api/configuration" >}}#vendureconfig) object. +* Extend the GraphQL API, including modifying existing types and adding completely new queries and mutations. +* Define new database entities and interact directly with the database. +* Run code before the server bootstraps, such as starting webservers. +* Respond to events such as new orders being placed. +* Trigger background tasks to run on the worker process. +* ... and more! + + +These abilities make plugins a very versatile and powerful means of implementing custom business requirements. + +This section details the official Vendure plugins included in the main Vendure repo, as well as a guide on writing your own plugins for Vendure. diff --git a/docs/content/docs/plugins/available-plugins.md b/docs/content/docs/plugins/available-plugins.md new file mode 100644 index 0000000000..7a89e939b5 --- /dev/null +++ b/docs/content/docs/plugins/available-plugins.md @@ -0,0 +1,20 @@ +--- +title: "Available Plugins" +weight: 1 +--- + +# Available Plugins + +## Core Plugins + +The Vendure monorepo contains a number of "core" plugins - that is, commonly-used plugins which are developed and versioned alongside the server core itself. + +* **[AdminUiPlugin]({{< relref "/docs/typescript-api/admin-ui-plugin" >}})**: This plugin bundles a default build of the Vendure Admin UI and serves it when the server is started. +* **[AssetServerPlugin]({{< relref "/docs/typescript-api/asset-server-plugin" >}})**: This plugin stores and serves assets from the local file system, and can perform on-the-fly image transformations. +* **[ElasticsearchPlugin]({{< relref "/docs/typescript-api/elasticsearch-plugin" >}})**: This plugin allows your product search to be powered by Elasticsearch - a powerful Open Source search engine. +* **[EmailPlugin]({{< relref "/docs/typescript-api/email-plugin" >}})**: The EmailPlugin creates and sends transactional emails based on Vendure events. + +## Third-party plugins + +Have you created a Vendure plugin that you'd like to share? Contact us and we can list it here! + diff --git a/docs/content/docs/plugins/extending-the-admin-ui/_index.md b/docs/content/docs/plugins/extending-the-admin-ui/_index.md new file mode 100644 index 0000000000..0d912f1c8e --- /dev/null +++ b/docs/content/docs/plugins/extending-the-admin-ui/_index.md @@ -0,0 +1,378 @@ +--- +title: 'Extending the Admin UI' +weight: 3 +--- + +# Extending the Admin UI + +When creating a plugin, you may wish to extend the Admin UI in order to expose an interface to the plugin's functionality. + +This is possible by defining [AdminUiExtensions]({{< ref "admin-ui-extension" >}}). A UI extension is an [Angular module](https://angular.io/guide/ngmodules) which gets compiled into the Admin UI application bundle by the [`compileUiExtensions`]({{< relref "compile-ui-extensions" >}}) function exported by the `@vendure/ui-devkit` package. + +{{% alert warning %}} +Note: a basic understanding of [Angular](https://angular.io/) is required to successfully work with UI extensions. Try [Angular's "Getting Started" guide](https://angular.io/start) to learn more. +{{% /alert %}} + +{{% alert "primary" %}} +For a complete working example of a Vendure plugin which extends the Admin UI, see the [real-world-vendure Reviews plugin](https://github.com/vendure-ecommerce/real-world-vendure/tree/master/src/plugins/reviews) +{{% /alert %}} + +## Simple Example + +Here is a very simple example to illustrate how a UI extension works: + +### 1. Install `@vendure/ui-devkit` + +To create UI extensions, you'll need to install the `@vendure/ui-devkit` package. This package contains a compiler for building your +customized version of the Admin UI, as well as the Angular dependencies you'll need to create your extensions. + +```bash +yarn add @vendure/ui-devkit + +# or + +npm install @vendure/ui-devkit +``` + +### 2. Create a simple component + +Here's a very simple Angular component which displays a greeting: + +```TypeScript +// project/ui-extensions/greeter.component.ts +import { Component } from '@angular/core'; + +@Component({ + selector: 'greeter', + template: `

{{ greeting }}

`, +}) +export class GreeterComponent { + greeting = 'Hello!'; +} +``` + +### 3. Create the Angular module + +Next we need to declare an Angular module to house the component: + +```TypeScript +// project/ui-extensions/greeter.module.ts +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { SharedModule } from '@vendure/admin-ui/core'; +import { GreeterComponent } from './greeter.component'; + +@NgModule({ + imports: [ + SharedModule, + RouterModule.forChild([{ + path: '', + pathMatch: 'full', + component: GreeterComponent, + data: { breadcrumb: 'Greeter' }, + }]), + ], + declarations: [GreeterComponent], +}) +export class GreeterModule {} +``` + +{{% alert "primary" %}} +**Note:** The `SharedModule` should, in general, always be imported by your extension modules. It provides the basic Angular +directives and other common functionality that any extension would require. +{{% /alert %}} + +### 4. Pass the extension to the `compileUiExtensions` function + +Now we need to tell the `compileUiExtensions` function where to find the extension, and which file contains the NgModule itself (since a non-trivial UI extension will likely contain multiple files). + +```TypeScript +// project/vendure-config.ts +import path from 'path'; +import { AdminUiPlugin } from '@vendure/admin-ui-plugin'; +import { VendureConfig } from '@vendure/core'; +import { compileUiExtensions } from '@vendure/ui-devkit/compiler'; + +export const config: VendureConfig = { + // ... + plugins: [ + AdminUiPlugin.init({ + port: 5001, + app: compileUiExtensions({ + outputPath: path.join(__dirname, 'admin-ui'), + extensions: [{ + extensionPath: path.join(__dirname, 'ui-extensions'), + ngModules: [{ + type: 'lazy', + route: 'greet', + ngModuleFileName: 'greeter.module.ts', + ngModuleName: 'GreeterModule', + }], + }], + }), + }), + ], +} +``` + +### 5. Start the server to compile + +The `compileUiExtensions()` function returns a `compile()` function which will be invoked by the AdminUiPlugin upon server bootstrap. During this compilation process, a new directory will be generated at `/admin-ui` (as specified by the `outputPath` option) which will contains the uncompiled sources of your new Admin UI app. + +Next, these source files will be run through the Angular compiler, the output of which will be visible in the console. + +```console +info 9/25/19, 09:54 - [AdminUiPlugin] Compiling Admin UI app in production mode... + + + +info 9/25/19, 09:55 - [AdminUiPlugin] Admin UI successfully compiled +``` + +Now go to the Admin UI app in your browser and log in. You should now be able to manually enter the URL `http://localhost:3000/admin/extensions/greet` and you should see the component with the "Hello!" header: + +{{< figure src="./ui-extensions-greeter.jpg" >}} + + +## Dev mode + +When you are developing your Admin UI extension, you can set the `devMode` option to `true` which will compile the Admin UI app in development mode, and recompile and auto-refresh the browser on any changes to your extension source files. + +```TypeScript +// project/vendure-config.ts +plugins: [ + AdminUiPlugin.init({ + port: 3002, + app: compileUiExtensions({ + outputPath: path.join(__dirname, 'admin-ui'), + extensions: [{ + // ... + }], + devMode: true, + }), + }), +], +``` + + +## Lazy vs Shared Modules + +In the above example we set the `type` to `'lazy'`. A lazy module is not loaded when the Admin UI app is bootstrapped. It is only lazily-loaded when the `/extensions/greet` route is activated. This allows you to create large, complex extensions without impacting the initial load time of your Admin UI. + +The other kind of extension module is `'shared'`. These are not lazily-loaded and are used to customize the shared interface elements such as the navigation menu items. Let's explore this by adding a menu item for our Greeter module. + +## Custom navigation + +### Extending the NavMenu + +Once you have defined some custom routes in a lazy extension module, you need some way for the administrator to access them. For this you will use the `addNavMenuItem` and `addNavMenuSection` functions. Let's add a new section to the Admin UI main nav bar containing a link to the lazy module from the simple example above: + +```TypeScript +// project/ui-extensions/greeter-shared.module.ts +import { NgModule } from '@angular/core'; +import { SharedModule, addNavMenuSection } from '@vendure/admin-ui/core'; + +@NgModule({ + imports: [SharedModule], + providers: [ + addNavMenuSection({ + id: 'greeter', + label: 'My Extensions', + items: [{ + id: 'greeter', + label: 'Greeter', + routerLink: ['/extensions/greet'], + // Icon can be any of https://clarity.design/icons + icon: 'cursor-hand-open', + }], + }, + // Add this section before the "settings" section + 'settings'), + ] +}) +export class GreeterSharedModule {} +``` + +Now we must also register this new module with the compiler: + +```TypeScript +// project/vendure-config.ts + +ngModules: [ + { + type: 'lazy', + route: 'greet', + ngModuleFileName: 'greeter.module.ts', + ngModuleName: 'GreeterModule', + }, + { + type: 'shared', + ngModuleFileName: 'greeter-shared.module.ts', + ngModuleName: 'GreeterSharedModule', + } +], +``` + +Running the server will compile our new shared module into the app, and the result should look like this: + +{{< figure src="./ui-extensions-navbar.jpg" >}} + +### Adding new ActionBar buttons + +It may not always make sense to navigate to your extension view from the main nav menu. For example, a "product reviews" extension that shows reviews for a particular product. In this case, you can add new buttons to the "ActionBar", which is the horizontal section at the top of each screen containing the primary actions for that view. + +Here's an example of how this is done: + +```TypeScript +import { NgModule } from '@angular/core'; +import { SharedModule, addActionBarItem } from '@vendure/admin-ui/core'; + +@NgModule({ + imports: [SharedModule], + providers: [ + addActionBarItem({ + id: 'product-reviews', + label: 'Product reviews', + locationId: 'product-detail', + buttonStyle: 'outline', + routerLink: ['./reviews'], + requiresPermission: 'SuperAdmin' + }), + ], +}) +export class SharedExtensionModule {} +``` + +{{< figure src="./ui-extensions-actionbar.jpg" >}} + +In each list or detail view in the app, the ActionBar has a unique `locationId` which is how the app knows in which view to place your button. Here is a complete list of available locations into which you can add new ActionBar buttons: + +```text +asset-list +collection-detail +collection-list +facet-detail +facet-list +product-detail +product-list +customer-detail +customer-list +promotion-detail +promotion-list +order-detail +order-list +administrator-detail +administrator-list +channel-detail +channel-list +country-detail +country-list +global-settings-detail +payment-method-detail +payment-method-list +role-detail +role-list +shipping-method-detail +shipping-method-list +tax-category-detail +tax-category-list +tax-rate-detail +tax-rate-list +``` + +## CustomField controls + +Another way to extend the Admin UI app is to define custom form control components for manipulating any [Custom Fields]({{< ref "/docs/typescript-api/custom-fields" >}}) you have defined on your entities. + +Let's say you define a custom "intensity" field on the Product entity: + +```TypeScript +// project/vendure-config.ts + +customFields: { + Product: [ + { name: 'intensity', type: 'int', min: 0, max: 100, defaultValue: 0 }, + ], +} +``` + +By default, the "intensity" field will be displayed as a number input: + +{{< figure src="./ui-extensions-custom-field-default.jpg" >}} + +But let's say we want to display a range slider instead. Here's how we can do this using our shared extension module combined with the `registerCustomFieldComponent()` function: + +```TypeScript +import { NgModule, Component } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { SharedModule, CustomFieldControl, + CustomFieldConfigType, registerCustomFieldComponent } from '@vendure/admin-ui/core'; + +@Component({ + template: ` + + {{ formControl.value }} + `, +}) +export class SliderControl implements CustomFieldControl { + customFieldConfig: CustomFieldConfigType; + formControl: FormControl; +} + +@NgModule({ + imports: [SharedModule], + declarations: [SliderControl], + providers: [ + registerCustomFieldComponent('Product', 'intensity', SliderControl), + ] +}) +export class SharedExtensionModule { } +``` + +Re-compiling the Admin UI will result in our SliderControl now being used for the "intensity" custom field: + +{{< figure src="./ui-extensions-custom-field-slider.jpg" >}} + +To recap the steps involved: + +1. Create an Angular Component which implements the `CustomFieldControl` interface. +2. Add this component to your shared extension module's `declarations` array. +3. Use `registerCustomFieldComponent()` to register your component for the given entity & custom field name. + +## Compiling as a deployment step + +Although the examples so far all use the `compileUiExtensions` function in conjunction with the AdminUiPlugin, it is also possible to use it on its own: + +```TypeScript +// compile-admin-ui.ts +import { compileUiExtensions } from '@vendure/ui-devkit/compiler'; + +compileUiExtensions({ + outputPath: path.join(__dirname, 'admin-ui'), + extensions: [/* ... */], +}).compile(); +``` + +This can then be run from the command line: + +```bash +yarn ts-node compile-admin-ui.ts +``` + +Once complete, the production-ready app bundle will be output to `admin-ui/dist`. This method is suitable for a production setup, so that the Admin UI can be compiled ahead-of-time as part of your deployment process. This ensures that your Vendure server starts up as quickly as possible. In this case, you can pass the path of the compiled app to the AdminUiPlugin: + +```TypeScript +// project/vendure-config.ts +plugins: [ + AdminUiPlugin.init({ + port: 3002, + app: { + path: 'admin-ui/dist' + } + }), +], +``` diff --git a/docs/content/docs/developer-guide/plugins/ui-extensions-actionbar.jpg b/docs/content/docs/plugins/extending-the-admin-ui/ui-extensions-actionbar.jpg similarity index 100% rename from docs/content/docs/developer-guide/plugins/ui-extensions-actionbar.jpg rename to docs/content/docs/plugins/extending-the-admin-ui/ui-extensions-actionbar.jpg diff --git a/docs/content/docs/developer-guide/plugins/ui-extensions-custom-field-default.jpg b/docs/content/docs/plugins/extending-the-admin-ui/ui-extensions-custom-field-default.jpg similarity index 100% rename from docs/content/docs/developer-guide/plugins/ui-extensions-custom-field-default.jpg rename to docs/content/docs/plugins/extending-the-admin-ui/ui-extensions-custom-field-default.jpg diff --git a/docs/content/docs/developer-guide/plugins/ui-extensions-custom-field-slider.jpg b/docs/content/docs/plugins/extending-the-admin-ui/ui-extensions-custom-field-slider.jpg similarity index 100% rename from docs/content/docs/developer-guide/plugins/ui-extensions-custom-field-slider.jpg rename to docs/content/docs/plugins/extending-the-admin-ui/ui-extensions-custom-field-slider.jpg diff --git a/docs/content/docs/developer-guide/plugins/ui-extensions-greeter.jpg b/docs/content/docs/plugins/extending-the-admin-ui/ui-extensions-greeter.jpg similarity index 100% rename from docs/content/docs/developer-guide/plugins/ui-extensions-greeter.jpg rename to docs/content/docs/plugins/extending-the-admin-ui/ui-extensions-greeter.jpg diff --git a/docs/content/docs/developer-guide/plugins/ui-extensions-navbar.jpg b/docs/content/docs/plugins/extending-the-admin-ui/ui-extensions-navbar.jpg similarity index 100% rename from docs/content/docs/developer-guide/plugins/ui-extensions-navbar.jpg rename to docs/content/docs/plugins/extending-the-admin-ui/ui-extensions-navbar.jpg diff --git a/docs/content/docs/plugins/plugin-architecture/_index.md b/docs/content/docs/plugins/plugin-architecture/_index.md new file mode 100644 index 0000000000..51bfb6705b --- /dev/null +++ b/docs/content/docs/plugins/plugin-architecture/_index.md @@ -0,0 +1,17 @@ +--- +title: "Plugin Architecture" +weight: 0 +showtoc: true +--- + +# Plugin Architecture + +{{< figure src="plugin_architecture.png" >}} + +A plugin in Vendure is a specialized Nestjs Module which is decorated with the [`VendurePlugin` class decorator]({{< relref "vendure-plugin" >}}). This diagram illustrates the how a plugin can integrate with and extend Vendure. + +1. A Plugin may define logic to be run by the [Vendure Worker]({{< relref "vendure-worker" >}}). This is suitable for long-running or resource-intensive tasks and is done by providing controllers via the [`workers` metadata property]({{< relref "vendure-plugin-metadata" >}}#workers). +2. A Plugin can modify any aspect of server configuration via the [`configuration` metadata property]({{< relref "vendure-plugin-metadata" >}}#configuration). +3. A Plugin can extend the GraphQL APIs via the [`shopApiExtensions` metadata property]({{< relref "vendure-plugin-metadata" >}}#shopapiextensions) and the [`adminApiExtensions` metadata property]({{< relref "vendure-plugin-metadata" >}}#adminapiextensions). +4. A Plugin can interact with Vendure by importing the [`PluginCommonModule`]({{< relref "plugin-common-module" >}}), by which it may inject any of the core Vendure services (which are responsible for all interaction with the database as well as business logic). Additionally a plugin may define new database entities via the [`entities` metadata property]({{< relref "vendure-plugin-metadata" >}}#entities) and otherwise define any other providers and controllers just like any [Nestjs module](https://docs.nestjs.com/modules). +5. A Plugin can run arbitrary code, which allows it to make use of external services. For example, a plugin could interface with a cloud storage provider, a payment gateway, or a video encoding service. diff --git a/docs/content/docs/developer-guide/plugins/plugin_architecture.png b/docs/content/docs/plugins/plugin-architecture/plugin_architecture.png similarity index 100% rename from docs/content/docs/developer-guide/plugins/plugin_architecture.png rename to docs/content/docs/plugins/plugin-architecture/plugin_architecture.png diff --git a/docs/content/docs/plugins/plugin-examples.md b/docs/content/docs/plugins/plugin-examples.md new file mode 100644 index 0000000000..5b6cfeae31 --- /dev/null +++ b/docs/content/docs/plugins/plugin-examples.md @@ -0,0 +1,243 @@ +--- +title: "Plugin Examples" +weight: 3 +showtoc: true +--- + +# Plugin Examples + +Here are some simplified examples of plugins which serve to illustrate what can be done with Vendure plugins. *Note: implementation details are skipped in these examples for the sake of brevity. A complete example with explanation can be found in [Writing A Vendure Plugin]({{< relref "writing-a-vendure-plugin" >}}).* + +{{% alert "primary" %}} + For a complete working example of a Vendure plugin, see the [real-world-vendure Reviews plugin](https://github.com/vendure-ecommerce/real-world-vendure/tree/master/src/plugins/reviews) +{{% /alert %}} + +## Modifying the VendureConfig + +This example shows how to modify the VendureConfig, in this case by adding a custom field to allow product ratings. +```TypeScript +// my-plugin.ts +import { VendurePlugin } from '@vendure/core'; + +@VendurePlugin({ + configure: config => { + config.customFields.Product.push({ + name: 'rating', + type: 'float', + min: 0, + max: 5, + }); + return config; + }, +}) +class ProductRatingPlugin {} +``` + +## Defining a new database entity + +This example shows how new TypeORM database entities can be defined by plugins. + +```TypeScript +// product-review.entity.ts +import { DeepPartial } from '@vendure/common/lib/shared-types'; +import { VendureEntity } from '@vendure/core'; +import { Column, Entity } from 'typeorm'; + +@Entity() +class ProductReview extends VendureEntity { + constructor(input?: DeepPartial) { + super(input); + } + + @Column() + text: string; + + @Column() + rating: number; +} +``` +```TypeScript +// reviews-plugin.ts +import { VendurePlugin } from '@vendure/core'; +import { ProductReview } from './product-review.entity'; + +@VendurePlugin({ + entites: [ProductReview], +}) +export class ReviewsPlugin {} +``` + +## Extending the GraphQL API + +This example adds a new query to the GraphQL Admin API. It also demonstrates how [Nest's dependency injection](https://docs.nestjs.com/providers) can be used to encapsulate and inject services within the plugin module. + +```TypeScript +// top-sellers.resolver.ts +import { Args, Query, Resolver } from '@nestjs/graphql'; +import { Ctx, RequestContext } from '@vendure/core' + +@Resolver() +class TopSellersResolver { + + constructor(private topSellersService: TopSellersService) {} + + @Query() + topSellers(@Ctx() ctx: RequestContext, @Args() args: any) { + return this.topSellersService.getTopSellers(ctx, args.from, args.to); + } + +} +``` +{{% alert "primary" %}} + **Note:** The `@Ctx` decorator gives you access to [the `RequestContext`]({{< relref "request-context" >}}), which is an object containing useful information about the current request - active user, current channel etc. +{{% /alert %}} +```TypeScript +// top-sellers.service.ts +import { Injectable } from '@nestjs/common'; + +@Injectable() +class TopSellersService { + getTopSellers() { /* ... */ } +} +``` + +```TypeScript +// top-sellers.plugin.ts +import gql from 'graphql-tag'; +import { VendurePlugin } from '@vendure/core'; +import { TopSellersService } from './top-sellers.service' +import { TopSellersResolver } from './top-sellers.resolver' + +@VendurePlugin({ + providers: [TopSellersService], + adminApiExtensions: { + schema: gql` + extend type Query { + topSellers(from: DateTime! to: DateTime!): [Product!]! + } + `, + resolvers: [TopSellersResolver] + } +}) +export class TopSellersPlugin {} +``` + +Using the `gql` tag, it is possible to: + +* add new queries `extend type Query { ... }` +* add new mutations (`extend type Mutation { ... }`) +* define brand new types `type MyNewType { ... }` +* add new fields to built-in types (`extend type Product { newField: String }`) + +## Adding a REST endpoint + +This plugin adds a single REST endpoint at `http://localhost:3000/products` which returns a list of all Products. Find out more about [Nestjs REST Controllers](https://docs.nestjs.com/controllers). +```TypeScript +// products.controller.ts +import { Controller, Get } from '@nestjs/common'; +import { Ctx, ProductService, RequestContext } from '@vendure/core'; + +@Controller('products') +export class ProductsController { + constructor(private productService: ProductService) {} + + @Get() + findAll(@Ctx() ctx: RequestContext) { + return this.productService.findAll(ctx); + } +} +``` +```TypeScript +// rest.plugin.ts +import { PluginCommonModule, VendurePlugin } from '@vendure/core'; +import { ProductsController } from './products.controller'; + +@VendurePlugin({ + imports: [PluginCommonModule], + controllers: [ProductsController], +}) +export class RestPlugin {} +``` + +{{% alert "primary" %}} + **Note:** [The `PluginCommonModule`]({{< relref "plugin-common-module" >}}) should be imported to gain access to Vendure core providers - in this case it is required in order to be able to inject `ProductService` into our controller. +{{% /alert %}} + +Side note: since this uses no Vendure-specific metadata, it could also be written using the Nestjs `@Module()` decorator rather than the `@VendurePlugin()` decorator. + +## Running processes on the Worker + +This example shows how to set up a microservice running on the Worker process, as well as subscribing to events via the [EventBus]({{< relref "event-bus" >}}). + +Also see the docs for [WorkerService]({{< relref "worker-service" >}}) and + +```TypeScript +// order-processing.controller.ts +import { asyncObservable, ID, Order } from '@vendure/core'; +import { Controller } from '@nestjs/common'; +import { MessagePattern } from '@nestjs/microservices'; +import { InjectConnection } from '@nestjs/typeorm'; +import { Connection } from 'typeorm'; + +@Controller() +class OrderProcessingController { + + constructor(@InjectConnection() private connection: Connection) {} + + @MessagePattern(ProcessOrderMessage.pattern) + async processOrder(orderId: ID) { + const order = await this.connection.getRepository(Order).findOne(orderId); + // ...do some expensive / slow computation + return true; + } + +} +``` +* This controller will be executed as a microservice in the [Vendure worker process]({{< relref "vendure-worker" >}}). This makes it suitable for long-running or resource-intensive tasks that you do not want to interfere with the main process which is handling GraphQL API requests. +* Messages are sent to the worker using [WorkerMessages]({{< relref "worker-message" >}}), each of which has a unique pattern and can include a payload of data sent from the main process. +* The return value of the method should correspond to the return type of the WorkerMessage (the second generic argument, `boolean` in the case of `ProcessOrderMessage` - see next snippet) + +```TypeScript +// process-order-message.ts +import { ID, WorkerMessage } from '@vendure/core'; + +export class ProcessOrderMessage extends WorkerMessage<{ orderId: ID }, boolean> { + static readonly pattern = 'ProcessOrder'; +} +``` + +The `ProcessOrderMessage` is sent in response to certain events: + +```TypeScript +import { OnVendureBootstrap, OrderStateTransitionEvent, PluginCommonModule, + VendurePlugin, WorkerService, EventBus } from '@vendure/core'; +import { OrderProcessingController } from './process-order.controller'; +import { ProcessOrderMessage } from './process-order-message'; + +@VendurePlugin({ + imports: [PluginCommonModule], + workers: [OrderProcessingController], +}) +export class OrderAnalyticsPlugin implements OnVendureBootstrap { + + constructor( + private workerService: WorkerService, + private eventBus: EventBus, + ) {} + + /** + * When the server bootstraps, set up a subscription for events + * published whenever an Order changes state. When an Order has + * been fulfilled, we send a message to the controller running on + * the Worker process to let it process that order. + */ + onVendureBootstrap() { + this.eventBus.ofType(OrderStateTransitionEvent).subscribe(event => { + if (event.toState === 'Fulfilled') { + this.workerService.send(new ProcessOrderMessage({ orderId: event.order.id })).subscribe(); + } + }); + } + +} +``` diff --git a/docs/content/docs/developer-guide/plugins/writing-a-vendure-plugin.md b/docs/content/docs/plugins/writing-a-vendure-plugin.md similarity index 99% rename from docs/content/docs/developer-guide/plugins/writing-a-vendure-plugin.md rename to docs/content/docs/plugins/writing-a-vendure-plugin.md index 2bcf47310f..f831f8b93e 100644 --- a/docs/content/docs/developer-guide/plugins/writing-a-vendure-plugin.md +++ b/docs/content/docs/plugins/writing-a-vendure-plugin.md @@ -1,6 +1,6 @@ --- title: "Writing a Vendure Plugin" -weight: 0 +weight: 2 --- # Writing a Vendure Plugin @@ -115,7 +115,7 @@ Some explanations of this code are in order: * The `@Resolver()` decorator tells Nest that this class contains GraphQL resolvers. * We are able to use Nest's dependency injection to inject an instance of our `CatFetcher` class into the constructor of the resolver. We are also injecting an instance of the built-in `ProductService` class, which is responsible for operations on Products. * We use the `@Mutation()` decorator to mark this method as a resolver for a mutation with the corresponding name. -* The `@Allow()` decorator enables us to define permissions restrictions on the mutation. Only those users whose permissions include `UpdateCatalog` may perform this operation. For a full list of available permissions, see the [Permission enum]({{< relref "../../graphql-api/admin/enums" >}}#permission). +* The `@Allow()` decorator enables us to define permissions restrictions on the mutation. Only those users whose permissions include `UpdateCatalog` may perform this operation. For a full list of available permissions, see the [Permission enum]({{< relref "/docs/graphql-api/admin/enums" >}}#permission). * The `@Ctx()` decorator injects the current `RequestContext` into the resolver. This provides information about the current request such as the current Session, User and Channel. It is required by most of the internal service methods. * The `@Args()` decorator injects the arguments passed to the mutation as an object. diff --git a/docs/content/docs/storefront/_index.md b/docs/content/docs/storefront/_index.md index e6ba98eb9c..d0602ab8c4 100644 --- a/docs/content/docs/storefront/_index.md +++ b/docs/content/docs/storefront/_index.md @@ -3,36 +3,6 @@ title: "Storefront" weight: 1 --- -# Storefront +# Storefront -The storefront is the application which customers use to interact with your store. - -As a headless server, Vendure provides a GraphQL API and admin UI app, but no storefront. The key advantage of the headless model is that the storefront (or indeed, any number of client applications) can be developed completely independently of the server. This flexibility comes at the cost of having to build and maintain your own storefront. - -However, we'd like to lower the barrier to getting started in the regard, so here are some options you may wish to investigate: - -## Vendure Angular Storefront - -{{< figure src="./vendure-storefront-screenshot-01.jpg" >}} - -This is an example storefront PWA application built with Angular. If you have Angular experience you may wish to use this as the basis of your own storefront implementation. - -A live demo can be found here: [demo.vendure.io/storefront/](https://demo.vendure.io/storefront/) - -Keep up with development here: [github.com/vendure-ecommerce/storefront](https://github.com/vendure-ecommerce/storefront) - -## DEITY Falcon - -[DEITY Falcon](https://falcon.deity.io/docs/getting-started/intro) is a React-based PWA storefront solution. It uses a modular architecture which allows it to connect to any e-commerce backend. We are developing the [Vendure Falcon API](https://www.npmjs.com/package/@vendure/falcon-vendure-api) which allows Falcon to be used with Vendure. - -Here's a video showing how to quickly get started with Vendure + DEITY Falcon: - -{{< vimeo 322812102 >}} - -## Vue Storefront - -[Vue Storefront](https://www.vuestorefront.io/) is one of the most popular backend-agnostic storefront PWA solutions. They offer extensive documentation on connecting their frontend application with a custom backend such as Vendure: https://github.com/DivanteLtd/vue-storefront-integration-sdk - -## Gatsby - -We have developed a [Gatsby](https://www.gatsbyjs.org/)-based storefront app: [vendure-ecommerce/gatsby-storefront](https://github.com/vendure-ecommerce/gatsby-storefront). This is a proof-of-concept which can be used as the starting point for your own Gatsby-based storefront. +This section contains documentation to help you build a storefront client application for Vendure. diff --git a/docs/content/docs/developer-guide/authentication-and-sessions.md b/docs/content/docs/storefront/authentication-and-sessions.md similarity index 99% rename from docs/content/docs/developer-guide/authentication-and-sessions.md rename to docs/content/docs/storefront/authentication-and-sessions.md index f63ae2fc52..f079a76635 100644 --- a/docs/content/docs/developer-guide/authentication-and-sessions.md +++ b/docs/content/docs/storefront/authentication-and-sessions.md @@ -1,5 +1,6 @@ --- title: "Authentication & Sessions" +weight: 1 showtoc: true --- diff --git a/docs/content/docs/storefront/building-a-storefront/_index.md b/docs/content/docs/storefront/building-a-storefront/_index.md new file mode 100644 index 0000000000..815917ca1f --- /dev/null +++ b/docs/content/docs/storefront/building-a-storefront/_index.md @@ -0,0 +1,39 @@ +--- +title: "Building a Storefront" +weight: 0 +showtoc: true +--- + +# Building a Storefront + +The storefront is the application which customers use to buy things from your store. + +As a headless server, Vendure provides a GraphQL API and admin UI app, but no storefront. The key advantage of the headless model is that the storefront (or indeed, any number of client applications) can be developed completely independently of the server. This flexibility comes at the cost of having to build and maintain your own storefront. + +However, we'd like to lower the barrier to getting started in the regard, so here are some options you may wish to investigate: + +## Vendure Angular Storefront + +{{< figure src="./vendure-storefront-screenshot-01.jpg" >}} + +This is an example storefront PWA application built with Angular. If you have Angular experience you may wish to use this as the basis of your own storefront implementation. + +A live demo can be found here: [demo.vendure.io/storefront/](https://demo.vendure.io/storefront/) + +Keep up with development here: [github.com/vendure-ecommerce/storefront](https://github.com/vendure-ecommerce/storefront) + +## DEITY Falcon + +[DEITY Falcon](https://falcon.deity.io/docs/getting-started/intro) is a React-based PWA storefront solution. It uses a modular architecture which allows it to connect to any e-commerce backend. We are developing the [Vendure Falcon API](https://www.npmjs.com/package/@vendure/falcon-vendure-api) which allows Falcon to be used with Vendure. + +Here's a video showing how to quickly get started with Vendure + DEITY Falcon: + +{{< vimeo 322812102 >}} + +## Vue Storefront + +[Vue Storefront](https://www.vuestorefront.io/) is one of the most popular backend-agnostic storefront PWA solutions. They offer extensive documentation on connecting their frontend application with a custom backend such as Vendure: https://github.com/DivanteLtd/vue-storefront-integration-sdk + +## Gatsby + +We have developed a [Gatsby](https://www.gatsbyjs.org/)-based storefront app: [vendure-ecommerce/gatsby-storefront](https://github.com/vendure-ecommerce/gatsby-storefront). This is a proof-of-concept which can be used as the starting point for your own Gatsby-based storefront. diff --git a/docs/content/docs/storefront/vendure-storefront-screenshot-01.jpg b/docs/content/docs/storefront/building-a-storefront/vendure-storefront-screenshot-01.jpg similarity index 100% rename from docs/content/docs/storefront/vendure-storefront-screenshot-01.jpg rename to docs/content/docs/storefront/building-a-storefront/vendure-storefront-screenshot-01.jpg diff --git a/docs/content/docs/developer-guide/graphql-api-guide/_index.md b/docs/content/docs/storefront/shop-api-guide.md similarity index 98% rename from docs/content/docs/developer-guide/graphql-api-guide/_index.md rename to docs/content/docs/storefront/shop-api-guide.md index c72288e43e..74a944f892 100644 --- a/docs/content/docs/developer-guide/graphql-api-guide/_index.md +++ b/docs/content/docs/storefront/shop-api-guide.md @@ -1,10 +1,10 @@ --- -title: "GraphQL API Guide" -weight: 0 +title: "Shop API Guide" +weight: 2 showtoc: true --- -# GraphQL API Guide +# GraphQL Shop API Guide This is an overview of the GraphQL Shop API, which is used when implementing a storefront application with Vendure. diff --git a/packages/common/src/shared-types.ts b/packages/common/src/shared-types.ts index e2b16ea776..85c0fbec0a 100644 --- a/packages/common/src/shared-types.ts +++ b/packages/common/src/shared-types.ts @@ -54,7 +54,20 @@ export type ID = string | number; /** * @description - * A data type for a custom field. + * A data type for a custom field. The CustomFieldType determines the data types used in the generated + * database columns and GraphQL fields as follows (key: m = MySQL, p = Postgres, s = SQLite): + * + * Type | DB type | GraphQL type + * ----- |--------- |--------------- + * string | varchar | String + * localeString | varchar | String + * int | int | Int + * float | double precision | Float + * boolean | tinyint (m), bool (p), boolean (s) | Boolean + * datetime | datetime (m,s), timestamp (p) | DateTime + * + * Additionally, the CustomFieldType also dictates which [configuration options](/docs/typescript-api/custom-fields/#configuration-options) + * are available for that custom field. * * @docsCategory custom-fields */ diff --git a/packages/ui-devkit/src/compiler/compile.ts b/packages/ui-devkit/src/compiler/compile.ts index 513eadf3d2..c4395adffd 100644 --- a/packages/ui-devkit/src/compiler/compile.ts +++ b/packages/ui-devkit/src/compiler/compile.ts @@ -19,14 +19,15 @@ const EXTENSION_ROUTES_FILE = 'src/extension.routes.ts'; const SHARED_EXTENSIONS_FILE = 'src/shared-extensions.module.ts'; /** - * Builds the admin-ui app using the Angular CLI `ng build --prod` command. + * @description + * Compiles the Admin UI app with the specified extensions. + * + * @docsCategory UiDevkit */ -export function compileUiExtensions({ - outputPath, - devMode, - watchPort, - extensions, -}: UiExtensionCompilerOptions): AdminUiAppConfig | AdminUiAppDevModeConfig { +export function compileUiExtensions( + options: UiExtensionCompilerOptions, +): AdminUiAppConfig | AdminUiAppDevModeConfig { + const { outputPath, devMode, watchPort, extensions } = options; if (devMode) { return runWatchMode(outputPath, watchPort || 4200, extensions); } else {