{"id":18561180,"url":"https://github.com/apostrophecms/moog-require","last_synced_at":"2025-04-10T02:31:23.733Z","repository":{"id":26102220,"uuid":"29546376","full_name":"apostrophecms/moog-require","owner":"apostrophecms","description":"Leverage npm's \"require\" with the \"moog\" module subclassing tool","archived":false,"fork":false,"pushed_at":"2021-09-15T20:02:41.000Z","size":96,"stargazers_count":3,"open_issues_count":1,"forks_count":2,"subscribers_count":11,"default_branch":"main","last_synced_at":"2025-04-03T05:30:04.145Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/apostrophecms.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-01-20T19:00:48.000Z","updated_at":"2022-10-25T20:20:55.000Z","dependencies_parsed_at":"2022-09-20T19:50:56.511Z","dependency_job_id":null,"html_url":"https://github.com/apostrophecms/moog-require","commit_stats":null,"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apostrophecms%2Fmoog-require","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apostrophecms%2Fmoog-require/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apostrophecms%2Fmoog-require/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apostrophecms%2Fmoog-require/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/apostrophecms","download_url":"https://codeload.github.com/apostrophecms/moog-require/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247980833,"owners_count":21027803,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-06T22:06:04.162Z","updated_at":"2025-04-10T02:31:23.342Z","avatar_url":"https://github.com/apostrophecms.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/punkave/moog-require.svg?branch=master)](https://travis-ci.org/punkave/moog-require)\n\n# moog-require\n\n`moog-require` provides powerful module subclassing for server-side development. It extends the features of [moog](https://github.com/punkave/moog) with the following additions:\n\n* Fetches modules from a local modules folder if they are not defined explicitly\n* If a module is defined explicitly and also exists in localModules, the local modules folder becomes a source of defaults for properties not defined explicitly\n* Fetches modules from npm if they are not defined either explicitly or via the local modules folder\n* If a module exists by the same name both in npm and via explicit definition or local modules, automatically extends the npm module without the need for a new name (like the \"category\" feature of Objective C)\n* Provides access to an \"asset chain\" of subclass module directories and type names, to implement template overrides and the like\n* Also supports bundling moog modules in a single npm module, if explicitly configured\n\n## Example\n\n```javascript\n\n// IMPLICIT BASE CLASS OF ALL MODULES\n// (if configured - see app.js below)\n//\n// In node_modules/module/index.js\n\nmodule.exports = {\n  self.construct = function(self, options) {\n    self.renderTemplate = function(name, data) {\n      var i;\n      for (i = 0; (i \u003c options.__meta.length); i++) {\n        var meta = options.__meta[i];\n        var path = meta.dirname + '/views/' + name + '.html';\n        if (fs.existsSync(path)) {\n          // Deepest subclass wins\n          return templateEngine.render(path, data);\n        }\n      }\n    };\n\n  };\n};\n\n// NPM MODULE\n//\n// In node_modules/events/index.js\n\nmodule.exports = {\n  color: 'red',\n\n  construct: function(self, options) {\n\n    self.defaultTags = options.tags;\n\n    self.get = function(params, callback) {\n      // Go get some events\n      return callback(events);\n    };\n  }\n}\n\n// PROJECT LEVEL SUBCLASS OF NPM MODULE\n//\n// in lib/modules/events/index.js\nmodule.exports = {\n  color: 'green',\n\n  construct: function(self, options) {\n    var superGet = self.get;\n    self.get = function(params, callback) {\n      // override: only interested in upcoming events\n      params.upcoming = true;\n      return superGet(params, callback);\n    };\n  }\n};\n\n// in app.js\n\nvar synth = require('moog-require')({\n  localModules: __dirname + '/lib/modules',\n  defaultBaseClass: 'module'\n});\n\nsynth.define({\n\n  // SETTING A DEFAULT OPTION THAT APPLIES TO *ALL* MODULES\n  // (because we're setting it for the defaultBaseClass)\n  'module': {\n    color: 'gray'\n  },\n\n  // CONFIGURATION (IMPLICIT SUBCLASS) OF A PROJECT-LEVEL MODULE\n  // (same technique works to configure an npm module)\n\n  'events': {\n    color: 'blue',\n    // More overrides in lib/modules/events/index.js (above).\n    // Anything here in app.js wins\n  },\n\n  // EXTENDING A PROJECT-LEVEL MODULE TO CREATE A NEW ONE\n  'parties': {\n\n    // Let's subclass a module right here in app.js (usually we'd just\n    // set site-specific options here and put code in\n    // lib/modules/parties/index.js, but you're not restricted)\n\n    extend: 'events',\n    color: 'lavender',\n\n    // Let's alter the \"tags\" option before the\n    // base class constructors are aware of it\n\n    beforeConstruct: function(self, options) {\n      options.tags = (options.tags || []).concat('party');\n    },\n\n    // This constructor can take a callback, even though\n    // the base classes don't. You can mix and match\n\n    construct: function(self, options, callback) {\n      // options.color will be lavender\n      var superGet = self.get;\n      self.get = function(params, callback) {\n        // override: only interested in parties. Let's\n        // assume the base class uses this as a query\n        params.title = /party/i;\n        return superGet(params, callback);\n      };\n\n      // Output names and full folder paths of all modules in the\n      // subclassing chain; we can use this to push assets and\n      // implement template overrides\n      console.log(options._directories);\n    },\n\n    setBridge: function(modules) {\n      // Do something that requires access to the\n      // other modules, which are properties of\n      // the modules object\n    }\n  }\n});\n\n// Instantiate all the modules, passing in some\n// universal options that are provided to all of them. This\n// only instantiates modules mentioned in `definitions`, but\n// they may override or subclass modules in npm or the\n// project-level modules folder\n\nreturn synth.createAll({ mailer: myMailer }, function(err, modules) {\n  return modules.events.get({ ... }, function(err, events) {\n    ...\n  });\n});\n\n// We can also tell the modules about each other. This\n// invokes the setBridge method of each module, if any,\n// and passes the modules object to it\n\nsynth.bridge(modules);\n\n// We can also create an instance of any module at any time,\n// and pass it additional options. This is useful if you are\n// not following the singleton pattern. We don't promise\n// killer performance if you create thousands of objects\n// per second\n\nreturn synth.create('parties', { color: 'purple' }, function(err, party) {\n  ...\n});\n```\n\n## Replacing a module with another npm module\n\nThe `monsters` npm module works great for most people, but you've created a superior replacement, `scary-monsters`. And you want people to be able to use it as a drop-in replacement, without changing code that refers to the `monsters` module.\n\nThis is especially useful if you want other moog types that subclass `monsters` to automatically subclass `scary-monsters` instead.\n\nSo the `index.js` of your `scary-monsters` npm module might look like:\n\n```javascript\nmodule.exports = {\n  replace: 'monsters',\n  construct: function(self, options) { ... }\n}\n```\n\nNote the `replace` property.\n\nNow, an application developer who wants to use `scary-monsters` instead of the usual `monsters` module can simply configure it instead of `monsters`. The type name `monsters` will still be defined. The type name `scary-monsters` is **not** defined.\n\n```javascript\nvar synth = require('moog-require')({\n  localModules: __dirname + '/lib/modules',\n  defaultBaseClass: 'module'\n});\n\nsynth.define({\n  'scary-monsters': { ... configuration ... }\n});\n\n// This works\nsynth.create('monsters', {});\n\n// This does NOT work\nsynth.create('scary-monsters', {});\n```\n\nNote that if you want to further extend `scary-monsters` at project level, you should use a `lib/modules/scary-monsters` folder. Anything in `lib/modules/monsters` will be ignored. Similarly, in `app.js`, don't configure `monsters`, just configure `scary-monsters`.\n\n## Improving a module with another npm module: implicit subclassing\n\nThe `improve` property is similar to `replace`, but allows you to implicitly subclass an existing type rather than completely replacing it. If you `improve` the `monsters` type, all other code will regard your subclass as the `monsters` type.\n\nThis is useful if you wish to release an npm module that subclasses a well-known module to add more functionality, without requiring developers to change the source code of other modules in order to use it.\n\nHere is an example:\n\nSo the `index.js` of your `scary-monsters` npm module might look like:\n\n```javascript\nmodule.exports = {\n  improve: 'monsters',\n  construct: function(self, options) {\n    var superJump = self.jump;\n    self.jump = function(howHigh) {\n      // Limit height of jumps\n      if (howHigh \u003e 100) {\n        howHigh = 100;\n      }\n      // Call original version\n      superJump(howHigh / 2);\n    };\n  }\n}\n```\n\nNote the `improve` property.\n\nJust like the `replace` option, the `improve` option defines the type with the name specified by `improve`. That is, your subclass is substituted everywhere for the `monsters` type. The `scary-monsters` type is **not** defined.\n\nHere is an example of application-level code:\n\n```javascript\nvar synth = require('moog-require')({\n  localModules: __dirname + '/lib/modules',\n  defaultBaseClass: 'module'\n});\n\nsynth.define({\n  'scary-monsters': { ... configuration ... }\n});\n\n// This works\nsynth.create('monsters', {});\n\n// This does NOT work\nsynth.create('scary-monsters', {});\n```\n\nNote that if you want to further extend `scary-monsters` at project level, you should use a `lib/modules/scary-monsters` folder. Code in `lib/modules/monsters` will be loaded, but it will subclass the original `monsters` module, and then `scary-monsters` will subclass that. This is probably not what you want. Similarly, in `app.js`, don't configure `monsters`, just configure `scary-monsters`.\n\n## Calling `require` yourself\n\nDon't.\n\nWell, okay...\n\nIf you want to write this:\n\n```javascript\n`extend': require('./lib/weird-place/my-module/index.js')\n```\n\nYou may do so, but in that case your module must export its `__name`, `__dirname` and `__filename`, like so:\n\n```javascript\nmodule.exports = {\n  __name: 'my-module',\n  __dirname: __dirname,\n  __filename: __filename,\n  construct: function(self, options) { ... }\n};\n```\n\nThis is only necessary if you are using `require` directly. Most of the time, you will be happier if you just specify a module name and let us `require` it for you. This even works in npm modules. (Yes, it will still find it if it is an npm dependency of your own module.)\n\n## Packaging multiple moog-require modules in a single npm module\n\nSometimes several modules are conceptually distinct, but are developed and versioned in tandem. In these cases there is no benefit from separate packaging, just a significant delay in `npm install`. npm peer dependencies are one way to handle this, but [npm peer dependencies may be on the chopping block](http://dailyjs.com/2014/04/16/node-roundup/), and they are significantly slower than pre-packaging modules together.\n\nThe difficulty of course is that the link between npm module names and moog-require module names is broken when we do this. So we need another way to indicate to moog-require that it should look in the appropriate place.\n\nSince searching for \"X\", where X is actually provided by module \"Y\", is not a core feature of npm itself we have kept this mechanism simple: you can give `moog-require` an array of npm module names that contain a \"bundle\" of definitions rather than a single definition. An npm \"bundle\" module then must export a `moogBundle` array property which contains the names of the moog-require modules it defines. The actual definitions live in `lib/modules/module-one/index.js`, `lib/modules/module-two/index.js`, etc. *within the bundle npm module*. `moog-require` will find these automatically and will consider these first before requiring normally from npm.\n\nHere's an example:\n\n```javascript\n// In node_modules/mybundle/index.js\n\nmodule.exports = {\n  moogBundle: {\n    modules: [ 'module-one', 'module-two' ],\n    directory: 'lib/modules'\n  }\n};\n\n// In node_modules/mybundle/lib/modules/module-one/index.js\n\nmodule.exports = {\n  construct: function(self, options) { ... }\n};\n\n// In node_modules/mybundle/lib/modules/module-two/index.js\n\nmodule.exports = {\n  construct: function(self, options) { ... }\n};\n```\n\n```javascript\n// In our application\n\nvar synth = require('moog-require')({\n  bundles: [ 'mybundle' ],\n  localModules: __dirname + '/lib/modules',\n  defaultBaseClass: 'module'\n});\n\nsynth.define({\n  'module-one': {},\n  'module-two': {}\n});\n```\n\nNote that just as before, we must include these modules in our explicit `define` calls if we want to instantiate them with `createAll`, although we don't have to override any properties; we can pass empty objects to just use the defaults defined in the project level folder, and/or implicitly inherit from npm.\n\nHowever, you may explicitly `create` a type that exists only in the project level folder and/or npm.\n\n## Nesting modules in subdirectories\n\nFor your convenience, `moog-require` has optional support for loading modules from nested subdirectories. The rules of the game are very simple:\n\n* You must set the `nestedModuleSubdirs` option to `true`.\n* Modules can now be found nested beneath your `localModules` folder, at any depth.\n* The names of the parent directories **do not matter**. They are purely for your organizational convenience.\n* The name of the actual module directory must still be the full name of the module.\n\nIf the same module exists in two places, an exception is thrown.\n\n## Changelog\n\n**The 2.x series is deprecated for new work, as its functionality was folded into Apostrophe 3.x. See below for 1.x release notes relevant to maintenance of Apostrophe 2.x.**\n\n1.3.2: starting in version 1.3.1, this module only loads other modules via `npm` if they are explicit npm dependencies, which is necessary for stability and security. However, it is too strict: if the project has no `package.json` at all at the level of `app.js`, `npm` search up the tree, and this module should too. Beginning in verison 1.3.2, it does search up the tree. However it stops at the first `package.json` found.\n\n1.3.1: `moog-require` loads modules from npm if they exist there and are configured by name in the application. This was always intended only as a way to load direct, intentional dependencies of your project. However, since npm \"flattens\" the dependency tree, dependencies of dependencies that happen to have the same name as a project-level module could be loaded by default, crashing the site or causing unexpected behavior. So beginning with this release, `moog-require` scans `package.json` to verify an npm module is actually a dependency of the project itself before attempting to load it.\n\n1.3.0: achieved an approximately 100x performance improvement when `nestedModuleSubdirs` is in use by fetching\na list of index.js files on the first `define` call and then searching that prefetched list each\ntime. This solution is much faster than the glob module cache.\n\n1.2.0: use `originalToMy` to handle moog class names with npm namespaces in them.\n\n1.1.1: use `importFresh` to avoid bugs when two instances of `moog-require` are loading the same module definitions. Previously if modules created by the two instances later modified sub-properties of `options`, they would inadvertently share values. This fix is critical for both `apostrophe-monitor` and `apostrophe-multisite`.\n\n1.1.0: support for the `nestedModuleSubdirs` option.\n\n1.0.1: shallowly clone the result of `require` rather than attaching `.__meta` to a potentially shared object. This allows multiple instances of `moog-require` in multiple instances of `apostrophe` to independently track where modules were loaded from.\n\n1.0.0: `moog`, `async` and `lodash` dependencies updated to satisfy `npm audit`. Declared 1.x as this has been a stable part of Apostrophe 2.x for a long time.\n\n0.4.1: fixed `moog` dependency to use the version that supports `autoload: false`.\n\n0.4.0: added `isImprovement` method which returns true if a type name turned out to be an improvement of another type via the `improve` keyword. This is useful when you wish to instantiate all of the types except for those that are just improvements of others.\n\n0.3.0: introduced the `replace` and `improve` options, which allow an npm module to substitute itself for another moog type completely, or enhance it via implicit subclassing. This is useful when releasing a drop-in replacement for a well-known module.\n\n0.2.0: depends on `moog` 0.2.0 which introduces the `mirror` method.\n\n0.1.0: compatible with `moog` 0.1.0 in which the `__meta` property became an object with `chain` and `name` properties.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapostrophecms%2Fmoog-require","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fapostrophecms%2Fmoog-require","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapostrophecms%2Fmoog-require/lists"}