Bladeren bron

feat(docs): Add docs for Plugins

Michael Bromley 7 jaren geleden
bovenliggende
commit
b10011c4ab

+ 11 - 2
docs/assets/styles/_markdown.scss

@@ -84,7 +84,16 @@ $block-border-radius: 4px;
         :last-child { margin-bottom: 0; }
         :last-child { margin-bottom: 0; }
     }
     }
 
 
-    table tr td {
-        padding: $padding-8;
+    table {
+        width: 100%;
+        th {
+            text-align: left;
+        }
+        td, th {
+            padding: $padding-4;
+        }
+        tr:nth-child(odd) td {
+            background-color: $gray-100;
+        }
     }
     }
 }
 }

+ 1 - 0
docs/assets/styles/_shortcodes.scss

@@ -8,6 +8,7 @@
     border: 1px solid;
     border: 1px solid;
     border-radius: 2px;
     border-radius: 2px;
     padding: 0 18px;
     padding: 0 18px;
+    margin-bottom: 12px;
 
 
     &.primary { border-color: $color-default; background: transparentize($color-default, 0.9); }
     &.primary { border-color: $color-default; background: transparentize($color-default, 0.9); }
     &.danger { border-color: $color-danger; background: transparentize($color-danger, 0.9); }
     &.danger { border-color: $color-danger; background: transparentize($color-danger, 0.9); }

+ 1 - 1
docs/assets/styles/main.scss

@@ -177,7 +177,7 @@ ul.contents-list {
 
 
 .book-footer {
 .book-footer {
     height: 200px;
     height: 200px;
-    background-color: $gray-100;
+    // background-color: $gray-100;
     margin-top: 60px;
     margin-top: 60px;
 }
 }
 
 

+ 4 - 0
docs/content/docs/_index.md

@@ -5,3 +5,7 @@ showtoc: false
 ---
 ---
 
 
 # Vendure Documentation
 # Vendure Documentation
+
+{{% alert "warning" %}}
+**Note**: Vendure is currently in alpha and as such, the information and APIs documented here are subject to change.
+{{% /alert %}}

+ 1 - 0
docs/content/docs/configuration/_index.md

@@ -1,6 +1,7 @@
 ---
 ---
 title: "Configuration"
 title: "Configuration"
 weight: 9
 weight: 9
+showtoc: false
 ---
 ---
 
 
 # Vendure Configuration Docs
 # Vendure Configuration Docs

+ 42 - 5
docs/content/docs/getting-started.md

@@ -7,7 +7,7 @@ weight: 0
 
 
 ## Requirements
 ## Requirements
  
  
-* [Node.js](https://nodejs.org/en/) v8 or above
+* [Node.js](https://nodejs.org/en/) **v8.9.0** or above
 * An SQL database compatible with [TypeORM](http://typeorm.io/#/), i.e. MySQL, MariaDB, PostgreSQL, SQLite, Microsoft SQL Server, sql.js, Oracle.
 * An SQL database compatible with [TypeORM](http://typeorm.io/#/), i.e. MySQL, MariaDB, PostgreSQL, SQLite, Microsoft SQL Server, sql.js, Oracle.
  
  
 ## Installation
 ## Installation
@@ -16,25 +16,49 @@ The following instructions describe how to run a development instance of Vendure
 
 
 ### Set up the database
 ### Set up the database
 
 
-You'll need a database to store your shop data. The simplest way to try out Vendure is to use SQLite, since it does not 
-require a separate database server to work.
+You'll need a database to store your shop data.
 
 
+{{% tab "SQLite" %}}
+The simplest way to try out Vendure is to use SQLite, since it does not require a separate database server to work. You only need to install the [sqlite3 driver](https://www.npmjs.com/package/sqlite3) to allow Vendure to read and write to an SQLite database file:
 ```bash
 ```bash
 $ npm install sqlite3
 $ npm install sqlite3
+
+# or with Yarn
+$ yarn add sqlite3
+
+```
+{{% /tab %}}
+{{% tab "MySQL/MariaDB" %}}
+You'll need a MySQL or MariaDB server available to your local machine. For development we can recommend the [bitnami-docker-phpmyadmin](https://github.com/bitnami/bitnami-docker-phpmyadmin) Docker image, which is MariaDB including phpMyAdmin.
+
+In addition, you must install the [mysql driver](https://www.npmjs.com/package/mysql) for Node.js:
+```bash
+$ npm install mysql
+
+# or with Yarn
+$ yarn add mysql
+
 ```
 ```
+{{% /tab %}}
 
 
 ### Install ts-node
 ### Install ts-node
 
 
-This allows us to run TypeScript directly without a compilation step. Useful for development.
+**TypeScript only:** If you want to use TypeScript, [ts-node](https://www.npmjs.com/package/ts-node) allows you to run TypeScript directly without a compilation step, which is convenient for development.
 
 
 ```bash
 ```bash
 $ npm install --save-dev ts-node
 $ npm install --save-dev ts-node
+
+# or with Yarn
+$ yarn add --dev ts-node 
 ```
 ```
 
 
 ### Install Vendure
 ### Install Vendure
 
 
 ```bash
 ```bash
 $ npm install @vendure/core
 $ npm install @vendure/core
+
+# or with Yarn
+$ yarn add @vendure/core
 ```
 ```
 
 
 ### Initialize with the Vendure CLI
 ### Initialize with the Vendure CLI
@@ -43,6 +67,9 @@ Vendure includes a CLI program which can generate the initial configuration and
 
 
 ```bash
 ```bash
 $ npx vendure init
 $ npx vendure init
+
+# or with Yarn
+$ yarn vendure init
 ```
 ```
 
 
 The init command will ask a series of questions which allow the CLI to generate a configuration and index file.
 The init command will ask a series of questions which allow the CLI to generate a configuration and index file.
@@ -51,9 +78,19 @@ The init command will ask a series of questions which allow the CLI to generate
 
 
 Once the init script has completed, the server can be started.
 Once the init script has completed, the server can be started.
 
 
+{{% tab "TypeScript" %}}
+```bash
+$ npx ts-node index
+
+# or with Yarn
+$ yarn ts-node init
+```
+{{% /tab %}}
+{{% tab "JavaScript" %}}
 ```bash
 ```bash
-$ ts-node index
+$ node index
 ```
 ```
+{{% /tab %}}
 
 
 Assuming the default config settings, you can now access:
 Assuming the default config settings, you can now access:
 
 

+ 1 - 0
docs/content/docs/graphql-api/_index.md

@@ -1,6 +1,7 @@
 ---
 ---
 title: "GraphQL API"
 title: "GraphQL API"
 weight: 3
 weight: 3
+showtoc: false
 ---
 ---
 
 
 # GraphQL API Docs
 # GraphQL API Docs

+ 0 - 6
docs/content/docs/plugins.md

@@ -1,6 +0,0 @@
----
-title: "Plugins"
-weight: 2
----
- 
-# Plugins

+ 18 - 0
docs/content/docs/plugins/_index.md

@@ -0,0 +1,18 @@
+---
+title: "Plugins"
+weight: 2
+showtoc: false
+---
+ 
+# Plugins
+
+Plugins in Vendure allow one to:
+
+1. Modify the [VendureConfig]({{< relref "vendure-config" >}}) 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 built-in plugins which ship with Vendure as well as a guide to writing your own plugins.

+ 19 - 0
docs/content/docs/plugins/admin-ui-plugin.md

@@ -0,0 +1,19 @@
+---
+title: "AdminUiPlugin"
+---
+
+# AdminUiPlugin
+
+This plugin starts a static server for the Admin UI app, and proxies it via the `/admin/` path of the main Vendure server.
+
+The Admin UI allows you to administer all aspects of your store, from inventory management to order tracking. It is the tool used by store administrators on a day-to-day basis for the management of the store.
+
+
+```ts 
+const config: VendureConfig = {
+  // Add an instance of the plugin to the plugins array
+  plugins: [
+    new AdminUiPlugin({ port: 3002 }),
+  ],
+};
+```

+ 67 - 0
docs/content/docs/plugins/default-asset-server-plugin.md

@@ -0,0 +1,67 @@
+---
+title: "DefaultAssetServerPlugin"
+---
+
+# DefaultAssetServerPlugin
+
+The `DefaultAssetServerPlugin` serves assets (images and other files) from the local file system. It can also perform on-the-fly image transformations and caches the results for subsequent calls.
+
+```ts
+const config: VendureConfig = {
+  // Add an instance of the plugin to the plugins array
+  plugins: [
+    new DefaultAssetServerPlugin({
+      route: 'assets',
+      assetUploadDir: path.join(__dirname, 'assets'),
+      port: 4000,
+    }),
+  ],
+};
+```
+
+The full configuration is documented at [DefaultAssetServerOptions]({{< relref "default-asset-server-options" >}})
+
+## Image transformation
+
+Asset preview images can be transformed (resized & cropped) on the fly by appending query parameters to the url:
+
+`http://localhost:3000/assets/some-asset.jpg?w=500&h=300&mode=resize`
+
+The above URL will return `some-asset.jpg`, resized to fit in the bounds of a 500px x 300px rectangle.
+
+### Preview mode
+
+The `mode` parameter can be either `crop` or `resize`. See the [ImageTransformMode]({{< relref "image-transform-mode" >}}) docs for details.
+
+### Transform presets
+
+Presets can be defined which allow a single preset name to be used instead of specifying the width, height and mode. Presets are configured via the DefaultAssetServerOptions [presets property]({{< relref "default-asset-server-options" >}}#presets).
+
+For example, defining the following preset:
+
+```ts
+new DefaultAssetServerPlugin({
+  // ...
+  presets: [
+    { name: 'my-preset', width: 85, height: 85, mode: 'crop' },
+  ],
+}),
+```
+
+means that a request to:
+
+`http://localhost:3000/assets/some-asset.jpg?preset=my-preset`
+
+is equivalent to:
+
+`http://localhost:3000/assets/some-asset.jpg?w=85&h=85&mode=crop`
+
+The DefaultAssetServerPlugin comes pre-configured with the following presets:
+
+name | width | height | mode
+-----|-------|--------|-----
+tiny | 50px | 50px | crop
+thumb | 150px | 150px | crop
+small | 300px | 300px | resize
+medium | 500px | 500px | resize
+large | 800px | 800px | resize

+ 67 - 0
docs/content/docs/plugins/default-email-plugin.md

@@ -0,0 +1,67 @@
+---
+title: "DefaultEmailPlugin"
+---
+
+# DefaultEmailPlugin
+
+The DefaultEmailPlugin configures the the [EmailOptions]({{< relref "email-options" >}}) to use an [MJML](https://mjml.io/)-based email generator and presents a simplified interface for typical email requirements.
+
+```ts 
+const config: VendureConfig = {
+  // Add an instance of the plugin to the plugins array
+  plugins: [
+    new DefaultEmailPlugin({
+      templatePath: path.join(__dirname, 'vendure/email/templates'),
+      transport: {
+        type: 'smtp',
+        host: 'smtp.example.com',
+        port: 587,
+        auth: {
+          user: 'username',
+          pass: 'password',
+        }
+      },
+    }),
+  ],
+};
+```
+
+## Customizing templates
+
+Emails are generated from templates which use [MJML](https://mjml.io/) syntax. MJML is an open-source HTML-like markup language which makes the task of creating responsive email markup simple. By default, the templates are installed to `<project root>/vendure/email/templates` and can be freely edited.
+
+Dynamic data such as the recipient's name or order items are specified using [Handlebars syntax](https://handlebarsjs.com/):
+
+```HTML
+<p>Dear {{ order.customer.firstName }} {{ order.customer.lastName }},</p>
+
+<p>Thank you for your order!</p>
+
+<mj-table cellpadding="6px">
+  {{#each order.lines }}
+    <tr class="order-row">
+      <td>{{ quantity }} x {{ productVariant.name }}</td>
+      <td>{{ productVariant.quantity }}</td>
+      <td>{{ formatMoney totalPrice }}</td>
+    </tr>
+  {{/each}}
+</mj-table>
+```
+
+### Handlebars helpers
+
+The following helper functions are available for use in email templates:
+
+* `formatMoney`: Formats an amount of money (which are always stored as integers in Vendure) as a decimal, e.g. `123` => `1.23`
+* `formatDate`: Formats a Date value with the [dateformat](https://www.npmjs.com/package/dateformat) package.
+
+## Dev mode
+
+For development, the `transport` option can be replaced by `devMode: true`. Doing so configures Vendure to use the [file transport]({{< relref "file-transport-options" >}}) and outputs emails as rendered HTML files in a directory named "test-emails" which is located adjacent to the directory configured in the `templatePath`.
+
+```ts 
+new DefaultEmailPlugin({
+  templatePath: path.join(__dirname, 'vendure/email/templates'),
+  devMode: true,
+})
+```

+ 20 - 0
docs/content/docs/plugins/default-search-plugin.md

@@ -0,0 +1,20 @@
+---
+title: "DefaultSearchPlugin"
+---
+
+# DefaultSearchPlugin
+
+The DefaultSearchPlugin provides a full-text Product search based on the full-text searching capabilities of the underlying database.
+
+```ts
+const config: VendureConfig = {
+  // Add an instance of the plugin to the plugins array
+  plugins: [
+    new DefaultSearchPlugin(),
+  ],
+};
+```
+
+{{% alert "warning" %}}
+Note that the current implementation of the DefaultSearchPlugin is only implemented and tested against a MySQL/MariaDB database. In addition, the search result quality has not yet been optimized.
+{{% /alert %}}

+ 255 - 0
docs/content/docs/plugins/writing-a-vendure-plugin.md

@@ -0,0 +1,255 @@
+---
+title: "Writing a Vendure Plugin"
+weight: 0
+---
+
+# Writing a Vendure Plugin
+
+A Vendure plugin is a class which implements the [`VendurePlugin` interface]({{< relref "vendure-plugin" >}}). This interface allows the plugin to:
+
+1. Modify the [VendureConfig]({{< relref "vendure-config" >}}) 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.
+
+## Example: RandomCatPlugin
+
+Let's learn about these capabilities by writing a plugin which defines a new database entity and GraphQL mutation.
+
+This plugin will add a new mutation, `addRandomCat`, to the GraphQL API which allows us to conveniently link a random cat image from [http://random.cat](http://random.cat) to any product in out catalog.
+
+### Step 1: Define a new custom field
+
+We need a place to store the url of the cat image, so we will add a custom field to the Product entity. This is done by modifying the VendureConfig object in the the plugin's [`configure` method]({{< relref "vendure-plugin" >}}#configure):
+
+```ts 
+import { VendurePlugin } from '@vendure/core';
+
+export class RandomCatPlugin implements VendurePlugin {
+    configure(config) {
+        config.customFields.Product.push({
+            type: 'string',
+            name: 'catImageUrl',
+        });
+        return config;
+    }
+}
+```
+
+### Step 2: Create a service to fetch the data
+
+Now we will create a service which is responsible for making the HTTP call to the random.cat API and returning the URL of a random cat image:
+
+```ts 
+import http from 'http';
+import { Injectable } from '@nestjs/common';
+
+@Injectable()
+export class CatFetcher {
+    /** Fetch a random cat image url from random.cat */
+    fetchCat(): Promise<string> {
+        return new Promise((resolve) => {
+            http.get('http://aws.random.cat/meow', (resp) => {
+                let data = '';
+                resp.on('data', chunk => data += chunk);
+                resp.on('end', () => resolve(JSON.parse(data).file));
+            });
+        });
+    }
+}
+```
+
+{{% alert %}}
+The `@Injectable()` decorator is part of the underlying [Nest framework](https://nestjs.com/), and allows us to make use of Nest's powerful dependency injection features. In this case, we'll be able to inject the `CatFetcher` service into the resolver which we will soon create.
+{{% /alert %}}
+
+
+{{% alert "warning" %}}
+To use decorators with TypeScript, you must set the "emitDecoratorMetadata" and "experimentalDecorators" compiler options to `true` in your tsconfig.json file.
+{{% /alert %}}
+
+### Step 3: Extend the GraphQL API
+
+Next we will extend the Vendure GraphQL API to add our new mutation. This is done by implementing the [`defineGraphQlTypes` method](({{< relref "vendure-plugin" >}}#definegraphqltypes)) in our plugin.
+
+```ts 
+import gql from 'graphql-tag';
+
+export class RandomCatPlugin implements VendurePlugin {
+
+    configure(config) {
+        // as above
+    }
+
+    defineGraphQlTypes() {
+        return gql`
+            extend type Mutation {
+                addRandomCat(id: ID!): Product!
+            }
+        `;
+    }
+}
+```
+
+### Step 4: Create a resolver
+
+Now that we've defined the new mutation, we'll need a resolver function to handle it. To do this, we'll create a new resolver class, following the [Nest GraphQL resolver architecture](https://docs.nestjs.com/graphql/resolvers-map). In short, this will be a class which has the `@Resolver()` decorator and features a method to handle our new mutation.
+
+```ts 
+import { Args, Mutation, Resolver } from '@nestjs/graphql';
+import { Ctx, Allow, ProductService, RequestContext } from '@vendure/core';
+
+@Resolver()
+export class RandomCatResolver {
+
+    constructor(private productService: ProductService, private catFetcher: CatFetcher) {}
+
+    @Mutation()
+    @Allow(Permission.UpdateCatalog)
+    async addRandomCat(@Ctx() ctx: RequestContext, @Args() args) {
+        const catImageUrl = await this.catFetcher.fetchCat();
+        return this.productService.update(ctx, {
+            id: args.id,
+            customFields: { catImageUrl },
+        });
+    }
+}
+```
+
+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 "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.
+
+### Step 5: Export the providers
+
+In order that the Vendure server (and the underlying Nest framework) is able to use the `CatFetcher` and `RandomCatResolver` classes, we must export them via the [`defineProviders` method](({{< relref "vendure-plugin" >}}#defineproviders)) in our plugin:
+
+```ts 
+export class RandomCatPlugin implements VendurePlugin {
+
+    configure(config) {
+        // as above
+    }
+
+    defineGraphQlTypes() {
+        // as above
+    }
+
+    defineProviders() {
+        return [CatFetcher, RandomCatResolver];
+    }
+}
+```
+
+### Step 6: Add the plugin to the Vendure config
+
+Finally we need to add an instance of our plugin to the config object with which we bootstrap out Vendure server:
+
+```ts 
+import { bootstrap } from '@vendure/core';
+
+bootstrap({
+    // .. config options
+    plugins: [
+        new RandomCatPlugin(),
+    ],
+});
+```
+
+### Step 7: Test the plugin
+
+Once we have started the Vendure server with the new config, we should be able to send the following GraphQL query:
+
+```GraphQL
+mutation {
+  addRandomCat(id: "1") {
+    id
+    name
+    customFields {
+      catImageUrl
+    }
+  }
+}
+```
+
+which should yield the following response:
+
+```JSON 
+{
+  "data": {
+    "addRandomCat": {
+      "id": "1",
+      "name": "Spiky Cactus",
+      "customFields": {
+        "catImageUrl": "https://purr.objects-us-east-1.dream.io/i/OoNx6.jpg"
+      }
+    }
+  }
+}
+```
+
+### Full example plugin
+
+```ts
+import { Injectable } from '@nestjs/common';
+import { Args, Mutation, Resolver } from '@nestjs/graphql';
+import gql from 'graphql-tag';
+import http from 'http';
+import { Allow, Ctx, Permission, ProductService, RequestContext, VendureConfig, VendurePlugin } from '@vendure/core';
+
+export class RandomCatPlugin implements VendurePlugin {
+    configure(config: Required<VendureConfig>) {
+        config.customFields.Product.push({
+            type: 'string',
+            name: 'catImageUrl',
+        });
+        return config;
+    }
+
+    defineGraphQlTypes() {
+        return gql`
+            extend type Mutation {
+                addRandomCat(id: ID!): Product!
+            }
+        `;
+    }
+
+    defineProviders() {
+        return [CatFetcher, RandomCatResolver];
+    }
+}
+
+@Injectable()
+export class CatFetcher {
+    /** Fetch a random cat image url from random.cat */
+    fetchCat(): Promise<string> {
+        return new Promise((resolve) => {
+            http.get('http://aws.random.cat/meow', (resp) => {
+                let data = '';
+                resp.on('data', chunk => data += chunk);
+                resp.on('end', () => resolve(JSON.parse(data).file));
+            });
+        });
+    }
+}
+
+@Resolver()
+export class RandomCatResolver {
+    constructor(private productService: ProductService, private catFetcher: CatFetcher) {}
+
+    @Mutation()
+    @Allow(Permission.UpdateCatalog)
+    async addRandomCat(@Ctx() ctx: RequestContext, @Args() args) {
+        const catImageUrl = await this.catFetcher.fetchCat();
+        return this.productService.update(ctx, {
+            id: args.id,
+            customFields: { catImageUrl },
+        });
+    }
+}
+```