UNPKG

@onehilltech/blueprint

Version:

lightweight, simple, elegant framework for building mean applications

201 lines (167 loc) 5.71 kB
/* * Copyright (c) 2018 One Hill Technologies, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const { statSync } = require ('fs-extra'); const path = require ('path'); const assert = require ('assert'); const { BO } = require ('base-object'); const ApplicationModule = require ('./application-module'); const Events = require ('./messaging/events'); const { forEach, find, isEmpty, map } = require ('lodash'); const { props } = require ('bluebird'); const debug = require ('debug') ('blueprint:module-loader'); const KEYWORD_BLUEPRINT_MODULE = 'blueprint-module'; const FILE_PACKAGE_JSON = 'package.json'; function isBlueprintModule (packageObj) { return packageObj.keywords && packageObj.keywords.indexOf (KEYWORD_BLUEPRINT_MODULE) !== -1; } /** * @class BlueprintModule * * A simple representation of a Blueprint module. */ class BlueprintModule { constructor (name, path) { this.name = name; this.path = path; } get appPath () { return path.resolve (this.path, 'app') } } /** * @class BlueprintModuleCollector * * A utility class that gathers Blueprint modules. */ class BlueprintModuleCollector { constructor (appPath) { this.appPath = appPath; this.modules = []; this._seen = {}; } /** * Gather the collection of Blueprint modules for the given application path. * * @param appPath */ async gather (appPath) { // First, locate all the modules that we need to load. const packageFile = path.resolve (appPath, '..', FILE_PACKAGE_JSON); const packageObj = require (packageFile); if (packageObj && packageObj.dependencies) await this._searchDependencies (packageObj.dependencies); return this.modules; } /** * Search the dependencies for Blueprint modules. * @param dependencies * @private */ async _searchDependencies (dependencies) { if (isEmpty (dependencies)) return; debug (`searching dependencies: ${Object.keys (dependencies)}`); const promises = map (dependencies, (version, name) => this._handleNodeModule (name, version)); await props (promises); } /** * Handle processing a node module. * * @param name * @param version * @private */ async _handleNodeModule (name, version) { // Do not process the module more than once. if (this._seen[name]) return; // Open the package.json file for this node module, and determine // if the module is a Blueprint.js module. let modulePath; if (version.startsWith ('file:')) { // The file location is relative to the blueprint application. let relativePath = version.slice (5); modulePath = path.resolve (this.appPath, '..', relativePath); } else { modulePath = await this._resolveModulePath (name); } const packageFile = path.resolve (modulePath, FILE_PACKAGE_JSON); const packageObj = require (packageFile); // If the module is a Blueprint module, let's save it. We then need to // process the dependencies of the found Blueprint module. if (isBlueprintModule (packageObj) && !this._seen[name]) { this.modules.push (new BlueprintModule (name, modulePath)); this._seen[name] = true; await this._searchDependencies (packageObj.dependencies); } } /** * Resolve the full location of the modules path. * * @param name * @private */ async _resolveModulePath (name) { // Let's make sure the node_modules for the application appear on the path. This is // important for examples applications that reside within an existing application const paths = [path.resolve (this.appPath, '../node_modules'), ...module.paths]; let basename = find (paths, basename => { let modulePath = path.resolve (basename, name, 'package.json'); try { return statSync (modulePath).isFile (); } catch (err) { return false; } }); if (!basename) throw new Error (`Cannot locate modules for ${name}`); return path.resolve (basename, name); } } /** * @class ModuleLoader * * Utility class for loading Blueprint modules into an application. */ module.exports = BO.extend (Events, { /// The target application for the loader. app: null, init () { this._super.call (this, ...arguments); this._modules = {}; assert (!!this.app, 'You must define the app property'); }, /** * Load the Blueprint modules in the application path. */ async load () { // First, locate all the modules that we need to load. const collector = new BlueprintModuleCollector (this.app.appPath); const modules = await collector.gather (this.app.appPath); // Topologically sort the module by reversing the array. This will ensure // that we load the modules in correct order of dependency. modules.reverse (); // Load each module into memory. for (const blueprintModule of modules) { const module = new ApplicationModule ({app: this.app, name: blueprintModule.name, modulePath: blueprintModule.appPath}); await this.emit ('loading', module); await module.configure (); await this.emit ('loaded', module); } }, });