UNPKG

@overlook/plugin

Version:

Overlook framework Plugin class

123 lines (109 loc) 4.07 kB
/* -------------------- * @overlook/plugin module * Entry point * ------------------*/ 'use strict'; // Modules const assert = require('assert'), {Extension, getOptionsFromArgs} = require('class-extension'), makeSymbols = require('@overlook/util-make-symbols'), {isArray} = require('is-it-type'); // Imports const {name: pkgName, version: pkgVersion} = require('../package.json'); // Exports // Make PLUGIN_VERSION symbol const {PLUGIN_VERSION, IS_PLUGIN} = makeSymbols(pkgName, ['PLUGIN_VERSION', 'IS_PLUGIN']); /** * Plugin class. * Receives `name`, `version`, `extend`, `extends`, `dependencies` and `symbols` properties. * Only `extend` is mandatory. * `version` is mandatory if `name` is provided. * * All props can be provided in a single props object. * Optionally, some properties can be provided as individual arguments: * - `name` as 1st argument (string) * - `version` as 2nd argument (string) (after `name`) * - `extend` (function) as any argument * - `extends` (array) as any argument * * Properties can also be provided as a series of props objects. * The last instance of a property takes precedence. * e.g. `({name: 'foo'}, {name: 'bar'})` -> `name` is 'bar' * Explicit arguments (e.g. `name` provided directly as arg) take precedence over properties, * regardless of order. * e.g. `('foo', {name: 'bar'})` -> `name` is 'foo' * e.g. `(() => {}, {extend() {}})` -> `extend` is first function * * Examples of acceptable arguments: * - extend * - extends, extend * - name, version, extend * - name, version, extends, extend * - name, version, {extend}, {symbols} * - {name, version, extend, extends, symbols} * - {name, version}, {symbols}, extend * * @param {string} [name] - Plugin name * @param {string} [version] - Plugin version * @param {function} [extend] - Extend function * @param {Array<Plugin>} [extends] - Dependent plugins * @param {Object} [props] - Properties object * @param {string} [props.name] - Plugin name * @param {string} [props.version] - Plugin version * @param {function} [props.extend] - Extend function * @param {Array<Plugin>} [props.extends] - Dependent plugins * @param {Object} [props.dependencies] - Dependencies object * @param {Array} [props.symbols] - Array of symbol names */ class Plugin { constructor(...args) { // Combine args into options object const options = getOptionsFromArgs(args); // Validate extends array const _extends = options.extends; if (_extends) { assert(isArray(_extends), `extends option must be an array - received ${_extends}`); for (const plugin of _extends) { assert(isPlugin(plugin), `extends array must contain only plugins - received ${plugin}`); } } // Pass options to Extension constructor const extension = new Extension(options); Object.assign(this, extension); // Inherit own enumerable properties of plugins this plugin extends const propNamesUsed = Object.create(null); if (_extends) { for (const plugin of _extends) { const pluginName = plugin.name || 'anonymous'; for (const key of Object.keys(plugin)) { if (['name', 'version', 'extend', 'extends', 'dependencies'].includes(key)) continue; assert( !propNamesUsed[key], `Property '${key}' clash between '${propNamesUsed[key]}' and '${pluginName}' plugins` ); this[key] = plugin[key]; propNamesUsed[key] = pluginName; } } } // Create Symbols and add to plugin const symbolNames = options.symbols; if (symbolNames) { const symbols = makeSymbols(options.name, symbolNames); for (const key of Object.keys(symbols)) { const existing = propNamesUsed[key]; assert(!existing, `Symbol '${key}' clashes with '${existing}' plugin`); this[key] = symbols[key]; } } } } function isPlugin(p) { return !!p && !!p[IS_PLUGIN]; } Plugin.isPlugin = isPlugin; Plugin.PLUGIN_VERSION = PLUGIN_VERSION; Plugin[PLUGIN_VERSION] = pkgVersion; Plugin.prototype[PLUGIN_VERSION] = pkgVersion; Plugin.prototype[IS_PLUGIN] = true; module.exports = Plugin;