UNPKG

@kwsites/moog-require

Version:

Replacement for `moog-require` with added support for TypeScript modules

163 lines (141 loc) 5.61 kB
const _ = require('lodash'); const fs = require('fs'); const npmResolve = require('resolve'); const path = require('path'); const resolveFrom = require('resolve-from'); const debug = require('debug'); module.exports = function (options) { const self = require('moog')(options); if (!self.options.root) { throw 'The root option is required. Pass the node variable "module" as root. This allows moog to require modules on your behalf.'; } self.root = self.options.root; self.log = debug('moog-require'); self.getFreshImport = require('./lib/fresh-import')(self); self.getProjectPath = require('./lib/project-path')(self); self.bundled = {}; self.improvements = {}; if (self.options.bundles) { _.each(self.options.bundles, function (bundleName) { var bundlePath = getNpmPath(self.root.filename, bundleName); if (!bundlePath) { throw 'The configured bundle ' + bundleName + ' was not found in npm.'; } var bundle = self.getFreshImport(bundlePath); if (!bundle.moogBundle) { throw 'The configured bundle ' + bundleName + ' does not export a moogBundle property.'; } var modules = bundle.moogBundle.modules; if (!modules) { throw ( 'The configured bundle ' + bundleName + ' does not have a "modules" property within its "moogBundle" property.' ); } _.each(modules, function (name) { self.bundled[name] = path.normalize( path.dirname(bundlePath) + '/' + bundle.moogBundle.directory + '/' + name + '/index.js' ); }); }); } var superDefine = self.define; self.define = function (type, definition, extending) { var result; // For the define-many-at-once case let the base class do the work if (typeof type === 'object') { return superDefine(type); } var projectLevelDefinition; var npmDefinition; var originalType; var projectLevelPath = self.getProjectPath(type); if (fs.existsSync(projectLevelPath)) { projectLevelDefinition = self.getFreshImport(resolveFrom(path.dirname(self.root.filename), projectLevelPath)); } var relativeTo; if (extending) { relativeTo = extending.__meta.filename; } else { relativeTo = self.root.filename; } var npmPath = getNpmPath(relativeTo, type); if (npmPath) { npmDefinition = self.getFreshImport(npmPath); npmDefinition.__meta = { npm: true, dirname: path.dirname(npmPath), filename: npmPath, name: type, }; if (npmDefinition.improve) { // Remember which types were actually improvements of other types for // the benefit of applications that would otherwise instantiate them all self.improvements[type] = true; // Improve an existing type with an implicit subclass, // rather than defining one under a new name originalType = type; type = npmDefinition.improve; // If necessary, start by autoloading the original type if (!self.isDefined(type, { autoload: false })) { self.define(type); } } else if (npmDefinition.replace) { // Replace an existing type with the one defined by // this npm module delete self.definitions[npmDefinition.replace]; type = npmDefinition.replace; } } if (!(definition || projectLevelDefinition || npmDefinition)) { // Can't find it nohow. Use the standard undefined type error message return superDefine(type); } if (!definition) { definition = {}; } projectLevelDefinition = projectLevelDefinition || {}; projectLevelDefinition.__meta = { dirname: path.dirname(projectLevelPath), filename: projectLevelPath, }; _.defaults(definition, projectLevelDefinition); // Insert the npm definition as a defined type, then let the // base class define the local definition normally. This results // in an implicit base class, allowing local template overrides // even if there is no other local code if (npmDefinition) { result = superDefine(type, npmDefinition); if (npmDefinition.improve) { // Restore the name of the improving module as otherwise our asset chains have // multiple references to my-foo which is ambiguous result.__meta.name = originalType; } } result = superDefine(type, definition); if (npmDefinition && npmDefinition.improve) { // Restore the name of the improving module as otherwise our asset chains have // multiple references to my-foo which is ambiguous result.__meta.name = self.originalToMy(originalType); } return result; }; function getNpmPath(parentPath, type) { parentPath = path.resolve(parentPath); if (_.has(self.bundled, type)) { return self.bundled[type]; } try { return npmResolve.sync(type, { basedir: path.dirname(parentPath) }); } catch (e) { // Not found via npm. This does not mean it doesn't // exist as a project-level thing return null; } } self.isImprovement = function (name) { return _.has(self.improvements, name); }; return self; };