0.1.3 • Published 8 years ago

webend v0.1.3

Weekly downloads
2
License
MIT
Repository
github
Last release
8 years ago

Build Status Test Coverage

Everything is a Plugin

Webend is a web wide plugin system. A feature often consists of frontend and backend part. Combine both and develop each feature as plugin to keep it easily, maintainable und testable. Support easily different product lines for different customers.

Installation

npm i --save webend

The system is devided into two parts: webend & webend_hub. The webend will load the NodeJS backend plugins while the webend_hub will connect all frontend plugins. To support front- and backend you have to install both

Setup

Just import the webend package.

import 'webend';  //require('webend');

Two config files have been copied to your project root directory while the installation. The config.json is the configuration for the system and the pluginconfig.json the configuration for all plugins.

Before we can start the system we have to configure the path for a secret wich will be used for the encryption of the control page. Therefor edit the config.json. Change privateKeyPath to your local private key. (The file could contain just a string.) There you can also change the port of the control page.

The users that are allowed to login to the control app have to be defined. Therefore create a users.json in your project root which contains the username as key and the password as value. If this file doesn't exists then the default user admin:admin will be used.

Now you can start your module!

Control App

Open localhost:8080 in the browser and login with your defined user. The app should be self explaining. Activate the autostart of webend_hub.

Write a PLUGIN

1) Instantiate a new npm package

A plugin is always a npm package. Create a new one with: npm init

The plugin name always has to have webend_ as prefix!

2) NodeJS Backend Part

The entry point to your backend has to be declared in the main field of the package.json. The given entry file has to export a function(es5) or per default a class(es6/typescript). The constructor will be fired when the plugin will be loaded. So you can put your starting code there. Sometimes some initialisations on the class needs to be done. This could be done also in the constructor and you can move your starting code to a new start method. This start method is optional and will be fired after the class has been created. The given entry will be loaded by an dependency injection framework. Webend uses tiny-di.

There are the following Injectables available:

NameTypeDescriptionExample
injectortiny-diThe Injector can be used to bind additional instances to the injector or get instances from the injector. Further methods of the injector can be found in the tiny-di documentation.injector.get('status')
statusobjectGets the loading status of the different pluginsstatus'webend_hub' -> true
autostartobjectGets the autostart setting of the pluginsautostart'webend_hub' -> true
configobjectcontains the configs for all pluginsconfig'webend_hub' -> {port: 8080}
{pluginName}objectGets the instance of an other plugin which has to be a dependency of the current pluginn.a.

Thats a lot of theory so here is an example:

import * as express from 'express';
import TinyDiInjectable from './tinydiinjectable';

export default class Server extends TinyDiInjectable {
  constructor(_config: any, injector: TinyDiInjector) {
    let config = _config['webend_server'];
    super();
    
    let app = express();
    
    app.get('/', (req, res) => {
      res.send('Hello World');
    });
    
    injector.bind('webend_server_app').to(app);
    
    let server = app.listen(config.port);
    console.log('started on ' + config.port);
  }
}
Server.$inject = {
  deps: ['config', 'injector'],
  callAs: 'class'
};

We create an plugin which creates an express server and makes the app variable available for other plugins. To inject dependencies we have to declare the $inject property on the class. Therfore we need to inherit that property from the TinyDiInjectable class. In es5 you don´t need the TinyDiInjectable class. Its only for declaration purposes.

The port should be configurable through the Control App. Therefore we change the package.json to:

{
  "name": "webend_server",
  "version": "0.0.1",
  "main": "server.js",
  "dependencies": {
    "express": "^4.13.4"
  },
  "webendConfig": {
    "port": "3000"
  }
}

The configurations can be injected. We only want the config of you plugin so we use _config['webend_server'] only. We want to export the express instance that other plugins can define other routes on it. Thats why we have injected the injector to bind it to webend_server_app with injector.bind('webend_server_app').to(app);. The name should always contain the current plugin name that there are no naming conflicts between different plugins.

3) Frontend Part

The frontend part can be a collection(object) of Angular 2 components, directives, services or an AngularJS Module. The following browser field has to be added to the package.json:

{
  "name": "webend_mypage",
  "browser": {
    "entry": "main",
    "framework": "angular2" //or "angular"
  }
}

The entry defines the entry module and should not contain the js extension of the file. Only the filename is needed. As frontend frameworks are currently only angular and angular2 supported. A plugin can only contain code of one framework but plugins of different frameworks can be combined and the ressources can be shared between different frameworks!

At first we need to connect one (or mulptiple) directives to the document body. Therefore we got two different opportunities. The automatic modus is the default one. There we have to create a directive which has the plugin name as selector. All these directives will be automatically added to the document body. The second one is the more configurable modus. There you have to edit the configuration of the webend_hub. Therefore open the controll app, navigate to Management and click on the configure button of the webend_hub. There you can set the useCustomIndexHtml to true and edit the index.html. It is fully customizable and you can add your root directive into the body.

Now we want to use ressources of other plugins. We start with an Angular 2 plugin and want to use components, directives and services of another Angular 2 plugin.

Angular 2

For example we implement the plugin webend_mypage. The requested ressources are defined in another plugin which is a optional dependency of webend_mypage. Its optional because will also want be able to load the plugin without the additional. The entry file is main.ts which looks like:

import { Component, Inject } from '@angular/core';
declare var webend: any;
let MyPageService = webend.getService('MyPageService');

@Component({
  moduleId: module.id,
  selector: 'webend_mypage',
  template: '<span my-highlight>this my page!!!</span> <my-addon></my-addon> {{secret}}',
  directives: [webend.getComponent('my-addon'), webend.getDirective('[my-highlight]')],
  providers: [MyPageService]
})
export class webend_mypageComponent {
  public secret: string;
  constructor(@Inject(MyPageService) myService: any) {
    this.secret = myService.secret;
  }
}

The selector for the component is equal to the plugin name so the component will be attached to the document body. To use components or directives in your template we have to push them into the directives array. We can't require them because we don't know if the plugin is avialable. But we can request a component through webend.getComponent. This wil lreturn the requested component iff available and otherwise it will return a component with empty template. So this will never fail and we can use the component in our template. The same could be done for a directive. The input of the functions are the selectors as string.

Nearly the same is also available services. Angular 2 has a dependency tree so you can inject thorugh the providers array. They will automatically added to the root injector that they are available for all plugins.

AngularJS ressources can also be used. Therefore is an adapter defined on the webend: webend.adapter.*. Have a look to ngAdapter for usage. Not all AngularJS directives can currently be upgraded! To be able to use optional AngularJS ressources use getOptNg1Directive or getOptNg1Service. Example:

webend.adapter.upgradeNg1Directive(webend.getOptNg1Directive('myDirective'));

AngularJS

Lets have a look to an AngularJS plugin (webend_myng1page):

let mod = angular.module('webend_myng1page', [])
  .directive('webendMyng1page', MyNg1Page);

declare var webend: any;

ctrl.$inject = [webend.getOptNg1Service('MyPageService'), '$scope'];
function ctrl(MyPageService: any, $scope: any) {
  $scope.secret = MyPageService.secret;
}

function MyNg1Page() {
  return {
    template: `
      <div>
        Hello from the old AngularJS World!
        {{secret}}
        <div red-light>This is a Highlight!</div>
      </div>
    `,
    controller: ctrl
  };
}

export default mod;

We have to create an AngularJS module with the name of the plugin. The controller of the added directive wants to use a optional AngularJS Service. Therfore the Service has be requested thorugh webend.getOptNg1Service. Thereby it will be secured that the service is always available (but could be empty). The directive uses the redLight directive of another plugin. This could be defined or not. AngularJS ingores unknown directives so there is no need to secure that the directive exists.

It is possible to use Angular2 components, directives and services in an AngularJS plugin. Therefore use the webend.adapter to downgrade the specific ressource. Have a look to ngAdapter for documentation.

Dependencies

We always talked about optional Dependencies. Thats a new case which I want to be highlgihted. If you need plugins as dependency its possible to request them the described way. I would recommend that but it is also possible to use them in the standard native way.

Summary: additional functions on webend

NameReturn ValueDescription
getComponentComponent(Angular2)Returns the requested component iff available otherwise a component with empty template.
getDirectiveDirective(Angular2)Returns the requested directive iff available otherwise a directive which does nothing.
getServiceService(Angular2)Returns the requested service iff available otherwise an empty service.
getOptNg1DirectiveStringInitialises empty directive if not existent. Returns directive selector.
getOptNg1ServiceStringInitialises empty service if not existent. Returns name of service.
adapterngAdapterUse the ngAdapter to up and downgrade directives and services.
0.1.5

8 years ago

0.1.4

8 years ago

0.1.3

8 years ago

0.1.2

8 years ago

0.1.1

8 years ago

0.1.0

8 years ago

0.0.1

8 years ago