Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server Plugin API #471

Merged
merged 5 commits into from
Feb 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions packages/cli/src/commands/develop.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const generateCompilation = require('../lifecycles/compile');
const livereload = require('livereload');
const pluginLiveReloadServer = require('../plugins/server/plugin-livereload')()[0];
const { ServerInterface } = require('../lib/server-interface');
const { devServer } = require('../lifecycles/serve');

module.exports = runDevServer = async () => {
Expand All @@ -9,18 +10,26 @@ module.exports = runDevServer = async () => {
try {
const compilation = await generateCompilation();
const { port } = compilation.config.devServer;
const { userWorkspace } = compilation.context;

devServer(compilation).listen(port, () => {

console.info(`Started local development server at localhost:${port}`);
const liveReloadServer = livereload.createServer({
exts: ['html', 'css', 'js', 'md'],
applyCSSLive: false // https://github.com/napcs/node-livereload/issues/33#issuecomment-693707006
});
// custom user server plugins
const servers = [...compilation.config.plugins.concat([pluginLiveReloadServer]).filter((plugin) => {
return plugin.type === 'server';
}).map((plugin) => {
const provider = plugin.provider(compilation);

liveReloadServer.watch(userWorkspace, () => {
console.info(`Now watching directory "${userWorkspace}" for changes.`);
});
if (!(provider instanceof ServerInterface)) {
console.warn(`WARNING: ${plugin.name}'s provider is not an instance of ServerInterface.`);
}

return provider;
})];

return Promise.all(servers.map(async (server) => {
return server.start();
}));
});
} catch (err) {
reject(err);
Expand Down
18 changes: 18 additions & 0 deletions packages/cli/src/lib/server-interface.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class ServerInterface {
constructor(compilation, options = {}) {
this.compilation = compilation;
this.options = options;
}

async start() {
return Promise.resolve(true);
}

async stop() {
return Promise.resolve(true);
}
}

module.exports = {
ServerInterface
};
2 changes: 1 addition & 1 deletion packages/cli/src/lifecycles/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ module.exports = readAndMergeConfig = async() => {
// }

if (plugins && plugins.length > 0) {
const types = ['resource'];
const types = ['resource', 'server'];

plugins.forEach(plugin => {
if (!plugin.type || types.indexOf(plugin.type) < 0) {
Expand Down
9 changes: 7 additions & 2 deletions packages/cli/src/lifecycles/serve.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ const pluginResourceStandardHtml = require('../plugins/resource/plugin-standard-
const pluginResourceStandardImage = require('../plugins/resource/plugin-standard-image');
const pluginResourceStandardJavaScript = require('../plugins/resource/plugin-standard-javascript');
const pluginResourceStandardJson = require('../plugins/resource/plugin-standard-json');
const { ResourceInterface } = require('../lib/resource-interface');
const pluginLiveReloadResource = require('../plugins/server/plugin-livereload')()[1];
const pluginUserWorkspace = require('../plugins/resource/plugin-user-workspace');
const { ResourceInterface } = require('../lib/resource-interface');

function getDevServer(compilation) {
const app = new Koa();
Expand Down Expand Up @@ -87,7 +88,11 @@ function getDevServer(compilation) {

// allow intercepting of urls
app.use(async (ctx) => {
const reducedResponse = await resources.reduce(async (responsePromise, resource) => {
const modifiedResources = resources.concat(
pluginLiveReloadResource.provider(compilation)
);

const reducedResponse = await modifiedResources.reduce(async (responsePromise, resource) => {
const body = await responsePromise;
const { url } = ctx;
const { headers } = ctx.response;
Expand Down
10 changes: 0 additions & 10 deletions packages/cli/src/plugins/resource/plugin-standard-html.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,6 @@ const getAppTemplate = (contents, userWorkspace) => {
};

const getUserScripts = (contents) => {
// TODO use an HTML parser? https://www.npmjs.com/package/node-html-parser
if (process.env.__GWD_COMMAND__ === 'develop') { // eslint-disable-line no-underscore-dangle
// TODO setup and teardown should be done together
// console.debug('running in develop mode, attach live reload script');
contents = contents.replace('</head>', `
<script src="http://localhost:35729/livereload.js?snipver=1"></script>
</head>
`);
}

if (process.env.__GWD_COMMAND__ === 'build') { // eslint-disable-line no-underscore-dangle
// TODO setup and teardown should be done together
// console.debug('running in build mode, polyfill WebComponents for puppeteer');
Expand Down
58 changes: 58 additions & 0 deletions packages/cli/src/plugins/server/plugin-livereload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const livereload = require('livereload');
const path = require('path');
const { ResourceInterface } = require('../../lib/resource-interface');
const { ServerInterface } = require('../../lib/server-interface');

class LiveReloadServer extends ServerInterface {
constructor(compilation, options = {}) {
super(compilation, options);

this.liveReloadServer = livereload.createServer({
exts: ['html', 'css', 'js', 'md'],
applyCSSLive: false // https://github.com/napcs/node-livereload/issues/33#issuecomment-693707006
});
}

async start() {
const { userWorkspace } = this.compilation.context;

this.liveReloadServer.watch(userWorkspace, () => {
console.info(`Now watching directory "${userWorkspace}" for changes.`);
return Promise.resolve(true);
});
}
}

class LiveReloadResource extends ResourceInterface {

async shouldIntercept(url) {
return Promise.resolve(path.extname(url) === '' && process.env.__GWD_COMMAND__ === 'develop'); // eslint-disable-line no-underscore-dangle
}

async intercept(url, body) {
return new Promise((resolve, reject) => {
try {
const contents = body.replace('</head>', `
<script src="http://localhost:35729/livereload.js?snipver=1"></script>
</head>
`);

resolve(contents);
} catch (e) {
reject(e);
}
});
}
}

module.exports = (options = {}) => {
return [{
type: 'server',
name: 'plugin-live-reload:server',
provider: (compilation) => new LiveReloadServer(compilation, options)
}, {
type: 'resource',
name: 'plugin-live-reload:resource',
provider: (compilation) => new LiveReloadResource(compilation, options)
}];
};
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('Build Greenwood With: ', function() {
try {
await setup.runGreenwoodCommand('build');
} catch (err) {
expect(err).to.contain('Error: greenwood.config.js plugins must be one of type "resource". got "indexxx" instead.');
expect(err).to.contain('Error: greenwood.config.js plugins must be one of type "resource, server". got "indexxx" instead.');
}
});
});
Expand Down
7 changes: 5 additions & 2 deletions www/pages/plugins/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Greenwood aims to cater to these use cases through two approaches:
### API
Each plugin must return a function that has the following three properties:.
- `name`: A string to give your plugin a name and used for error handling and troubleshooting.
- `type`: A string to specify to Greenwood the type of plugin. Right now the current supported plugin type `'resource'`
- `type`: A string to specify to Greenwood the type of plugin. Right now the current supported plugin type [`'resource'`](/plugins/resource/) and [`'plugin'`](/plugins/server/).
- `provider`: A function that will be invoked by Greenwood that Can accept a `compilation` param that provides read-only access to parts of Greenwood's state and configuration that can be used by a plugin.

Here is an example of creating a plugin in a _greenwood.config.js_.
Expand Down Expand Up @@ -106,4 +106,7 @@ module.exports = {
While each API has its own documentation section on the left sidebar of this page, here is a quick overview of the current set of Plugin APIs Greenwood supports.

#### Resource Plugins
Resource plugins allow users to interact with the request and response lifecycles of files at a variety of different ways. These lifecycles provide the ability to do things like introduce new file types, to adding hosted 3rd party scripts to your site.
[Resource plugins](/plugins/resource/) allow users to interact with the request and response lifecycles of files at a variety of different ways. These lifecycles provide the ability to do things like introduce new file types, to adding hosted 3rd party scripts to your site.

#### Server Plugins
[Server plugins](/plugins/server/) allow developers to start and stop custom servers as part of the **serve** lifecycle of Greenwood. These lifecycles provide the ability to do things like start a GraphQL server, or reverse proy requests to a custom server.
2 changes: 1 addition & 1 deletion www/pages/plugins/resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Resource plugins allow developers to interact with the request and response life
- Introduce additional file types, like TypeScript

### API (Resource Interface)
Although JavaScript is loosely typed, a [resource "interface"](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/cli/src/lib/resource-interface.js) has been provided by Greenwood that you can use to start building our own resource plugins. Effectively you have to define two things:
Although JavaScript is loosely typed, a [resource "interface"](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/cli/src/lib/resource-interface.js) has been provided by Greenwood that you can use to start building your own resource plugins. Effectively you have to define two things:
- `extensions`: The file types your plugin will operate on
- `contentType`: A browser compatible contentType to ensure browsers correctly interpret you transformations

Expand Down
64 changes: 64 additions & 0 deletions www/pages/plugins/server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
label: 'Server'
menu: side
title: 'Server'
index: 3
---

## Server

Server plugins allow developers to start and stop custom servers as part of the serve lifecycle of Greenwood. These lifecycles provide the ability to do things like:
- Start a live reload server (like Greenwood does by default)
- Starting a GraphQL server
- Reverse proxy to help route external requests

### API (Server Interface)
Although JavaScript is loosely typed, a [server "interface"](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/cli/src/lib/server-interface.js) has been provided by Greenwood that you can use to start building your own server plugins. Effectively you just have to provide two methods
- `start` - function to run to start your server
- `stop` - function to run to stop / teaddown your server


They can be used in a _greenwood.config.js_ just like any other plugin type.
```javascript
const pluginMyServerFoo = require('./plugin-my-server');

module.exports = {

...

plugins: [
pluginMyServer()
]

}
```

## Example
The below is an excerpt of [Greenwood's internal LiveReload server](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/cli/src/plugins/server/plugin-livereload.js) plugin.

```javascript
class LiveReloadServer extends ServerInterface {
constructor(compilation, options = {}) {
super(compilation, options);

this.liveReloadServer = livereload.createServer({ /* options */});
}

async start() {
const { userWorkspace } = this.compilation.context;

return this.liveReloadServer.watch(userWorkspace, () => {
console.info(`Now watching directory "${userWorkspace}" for changes.`);
return Promise.resolve(true);
});
}
}

module.exports = (options = {}) => {
return {
type: 'server',
name: 'plugin-livereload',
provider: (compilation) => new LiveReloadServer(compilation, options)
}
};
```