UNPKG

we-core

Version:

We.js is a node.js framework for build real time applications, sites or blogs!

402 lines (344 loc) 11.3 kB
/** * We.js Plugin prototype file */ const _ = require('lodash'), path = require('path'), fs = require('fs'), async = require('async'); module.exports = function getPluginPrototype (we) { /** * We.js plugin Class constructor * * @param {string} name plugin npm pakage name * @param {string} projectPath project path where the plugin is instaled */ function Plugin (pluginPath) { this.pluginPath = pluginPath; this.we = we; this.events = this.we.events; this.hooks = this.we.hooks; this.router = this.we.router; /** * Plugin Assets * @type {Object} */ this.assets = { js: {}, css: {} }; this['package.json'] = require( path.join( pluginPath , 'package.json') ); this.controllersPath = path.join( this.pluginPath, this.controllerFolder ); this.modelsPath = path.join( this.pluginPath, this.modelFolder ); this.modelHooksPath = path.join( this.pluginPath, this.modelHookFolder ); this.modelInstanceMethodsPath = path.join( this.pluginPath, this.modelInstanceMethodFolder ); this.modelClassMethodsPath = path.join( this.pluginPath, this.modelClassMethodFolder ); this.searchParsersPath = path.join( this.pluginPath, this.searchParsersFolder ); this.searchTargetsPath = path.join( this.pluginPath, this.searchTargetsFolder ); this.templatesPath = this.pluginPath + '/server/templates'; this.helpersPath = this.pluginPath + '/server/helpers'; this.resourcesPath = this.pluginPath + '/server/resources'; this.routesPath = this.pluginPath + '/server/routes'; this.helpers = {}; this.layouts = {}; this.templates = {}; /** * Default plugin config object * * @type {Object} */ this.configs = {}; /** * Default plugin resources * * @type {Object} */ this.controllers = {}; this.models = {}; this.routes = {}; this.appFiles = []; this.appAdminFiles = []; } /** * Default initializer function, override in plugin.js file if need * * @param {Object} we we.js object * @param {Function} cb callback */ Plugin.prototype.init = function initPlugin(we, cb) { return cb(); }; /** * Set plugin config * @param {Object} config */ Plugin.prototype.setConfigs = function setConfigs (configs) { this.configs = configs; }; // default plugin paths Plugin.prototype.controllerFolder = 'server/controllers'; Plugin.prototype.modelFolder = 'server/models'; Plugin.prototype.modelHookFolder = 'server/models/hooks'; Plugin.prototype.modelInstanceMethodFolder = 'server/models/instanceMethods'; Plugin.prototype.modelClassMethodFolder = 'server/models/classMethods'; Plugin.prototype.searchParsersFolder = 'server/search/parsers'; Plugin.prototype.searchTargetsFolder = 'server/search/targets'; /** * Set plugin routes * * @param {object} routes */ Plugin.prototype.setRoutes = function setRoutes (routes) { const routePaths = Object.keys(routes); let routePath; for (let i = routePaths.length - 1; i >= 0; i--) { routePath = routePaths[i]; this.setRoute(routePath, routes[routePath]); } }; /** * Set one route in plugin routes * @param {string} path route path * @param {object} configs route configs how will be avaible as res.locals */ Plugin.prototype.setRoute = function setRoute (routePath, configs) { this.routes[routePath] = configs; this.routes[routePath].path = routePath; this.routes[routePath].pluginPath = this.pluginPath; }; Plugin.prototype.setResource = function setResource (opts) { let router = this.we.router, fullName = (opts.namePrefix || '') + opts.name; // first save resource in name or merge route options if exists if (!router.resourcesByName[fullName]) { router.resourcesByName[fullName] = opts; } else { _.merge(router.resourcesByName[fullName], opts); } if (opts.parent) { // is subroute if (!router.resourcesByName[opts.parent]) { // parent dont are set // temporary create parent resource wit subroutes attr router.resourcesByName[opts.parent] = { subRoutes: {} }; // add reference to route in parent subroutes router.resourcesByName[opts.parent].subRoutes[fullName] = router.resourcesByName[fullName]; } else { // parent resource is set if (!router.resourcesByName[opts.parent].subRoutes) { // add subRoutes object if dont are set router.resourcesByName[opts.parent].subRoutes = {}; } if (!router.resourcesByName[opts.parent].subRoutes[fullName]) { router.resourcesByName[opts.parent].subRoutes[fullName] = {}; } // add reference to route in parent resource subroutes router.resourcesByName[opts.parent].subRoutes[fullName] = router.resourcesByName[fullName]; } } else { // is route route router.resources[fullName] = router.resourcesByName[fullName]; } }; /** * Set plugin layouts * @param {object} layouts */ Plugin.prototype.setLayouts = function setLayouts (layouts) { this.layouts = layouts; }; /** * Load all we.js plugin features * * Auto load avaible for plugins, helpers ... * */ Plugin.prototype.loadFeatures = function loadFeatures (we, cb) { const self = this; if (self.fastLoader) { // fast loader option for load faster plugins: self.fastLoader(we, (err)=> { if (err) return cb(err); // plugin loader hook, for allow hook extensions: we.hooks.trigger('plugin:load:features', { plugin: self, we: we }, cb); }); } else { // find and load all plugin features ... async.parallel([ next => self.loadSearchParsers(next), next => self.loadSearchTargets(next), next => self.loadControllers(next), next => self.loadModelHooks(next), next => self.loadInstanceMethods(next), next => self.loadClassMethods(next), next => self.loadModels(next), next => self.loadResources(next), next => self.loadRoutes(next), function loaderHookExtensor (done) { we.hooks.trigger('plugin:load:features', { plugin: self, we: we }, done); }, ], cb); } }; /** * Get generic featureFiles * Read feature dir, check if are an .js file and return full path and name of each file * * @param {String} fgPath feature path * @param {Function} done callback */ Plugin.prototype.getGenericFeatureFiles = function getGenericFeatureFiles (fgPath, done) { fs.readdir(fgPath, (e, fileNames) => { if (e) { if (e.code !== 'ENOENT') return done(e); return done(null, []); } done(null, fileNames .filter(fileName => { return fileName.endsWith('.js'); }) .map(fileName => { return { name: fileName.slice(0, -3), path: path.join(fgPath, fileName) }; })); }); }; /** * load plugin controllers */ Plugin.prototype.loadControllers = function loadControllers (done) { this.getGenericFeatureFiles(this.controllersPath, (e, modules) => { if (e) return done(e); modules.forEach(m => { let attrs = require(m.path); attrs._controllersPath = this.controllersPath; we.controllers[m.name] = new we.class.Controller(attrs); }); done(); }); }; /** * load plugin model hooks */ Plugin.prototype.loadModelHooks = function loadModelHooks (done) { this.getGenericFeatureFiles(this.modelHooksPath, (e, modules) => { if (e) return done(e); modules.forEach(m => { we.db.modelHooks[m.name] = require(m.path).bind({ we: we }); }); done(); }); }; /** * load plugin model instance methods */ Plugin.prototype.loadInstanceMethods = function loadInstanceMethods (done) { this.getGenericFeatureFiles(this.modelInstanceMethodsPath, (e, modules) => { if (e) return done(e); modules.forEach(m => { we.db.modelInstanceMethods[m.name] = require(m.path); }); done(); }); }; /** * load plugin model class methods */ Plugin.prototype.loadClassMethods = function loadClassMethods (done) { this.getGenericFeatureFiles(this.modelClassMethodsPath, (e, modules) => { if (e) return done(e); modules.forEach(m => { we.db.modelClassMethods[m.name] = require(m.path); }); done(); }); }; /** * load plugin search parsers */ Plugin.prototype.loadSearchParsers = function loadSearchParsers (done) { this.getGenericFeatureFiles(this.searchParsersPath, (e, modules) => { if (e) return done(e); modules.forEach(m => { we.router.search.parsers[m.name] = require(m.path).bind({ we: we }); }); done(); }); }; /** * load plugin search targets */ Plugin.prototype.loadSearchTargets = function loadSearchTargets (done) { this.getGenericFeatureFiles(this.searchTargetsPath, (e, modules) => { if (e) return done(e); modules.forEach(m => { we.router.search.targets[m.name] = require(m.path).bind({ we: we }); }); done(); }); }; /** * load plugin models with suport to JSON and .js file formats */ Plugin.prototype.loadModels = function loadModels (done) { fs.readdir(this.modelsPath, (e, fileNames) => { if (e) { if (e.code !== 'ENOENT') return done(e); return done(); } let name; fileNames.forEach(fileName => { if (fileName.endsWith('.js')) { // js model name = fileName.slice(0, -3); we.db.modelsConfigs[name] = require(path.join( this.modelsPath, fileName) )(we); } else if (fileName.endsWith('.json')) { // json model name = fileName.slice(0, -5); we.db.modelsConfigs[name] = we.db.defineModelFromJson( require(path.join(this.modelsPath, fileName)), we); } }); done(); }); }; /** * Load route resources from folder server/resources */ Plugin.prototype.loadResources = function loadResources (cb) { fs.readdir(this.resourcesPath, (err, list) => { if (err) { if (err.code === 'ENOENT') return cb(); return cb(err); } list .map(item => { return path.join(this.resourcesPath, item); }) .forEach(p => { this.setResource( require(p) ); }); cb(); }); }; /** * Load routes from folder server/routes */ Plugin.prototype.loadRoutes = function loadRoutes (cb) { fs.readdir(this.routesPath, (err, list) => { if (err) { if (err.code === 'ENOENT') return cb(); return cb(err); } list .map(item => { return path.join(this.routesPath, item); }) .forEach(p => { this.setRoutes( require(p) ); }); cb(); }); }; // -- Add css and js to we-plugin-view assets feature Plugin.prototype.addJs = function addJs (fileName, cfg) { this.assets.js[fileName] = cfg; }; Plugin.prototype.addCss = function addCss (fileName, cfg) { this.assets.css[fileName] = cfg; }; return Plugin; };