├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── index.js ├── package.json ├── support └── mk │ ├── browserify.mk │ ├── coveralls.mk │ ├── istanbul.mk │ ├── jshint.mk │ ├── mocha.mk │ ├── node.mk │ ├── notes.mk │ └── testling.mk └── test ├── bootstrap └── node.js ├── integration └── app.test.js ├── projects ├── app │ ├── app.js │ └── node_modules │ │ ├── foo-plugin │ │ └── index.js │ │ └── framework │ │ └── index.js └── baz-plugin │ └── index.js └── require.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | reports 3 | 4 | # Mac OS X 5 | .DS_Store 6 | 7 | # Node.js 8 | node_modules 9 | npm-debug.log 10 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "forin": true, 8 | "immed": true, 9 | "latedef": true, 10 | "newcap": true, 11 | "noarg": true, 12 | "noempty": true, 13 | "nonew": true, 14 | "quotmark": "single", 15 | "undef": true, 16 | "unused": true, 17 | "trailing": true, 18 | 19 | "laxcomma": true 20 | } 21 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | README.md 2 | Makefile 3 | build/ 4 | docs/ 5 | examples/ 6 | reports/ 7 | support/ 8 | test/ 9 | 10 | # Mac OS X 11 | .DS_Store 12 | 13 | # Node.js 14 | .npmignore 15 | node_modules/ 16 | npm-debug.log 17 | 18 | # Git 19 | .git* 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: "node_js" 2 | node_js: 3 | - "0.4" 4 | - "0.6" 5 | - "0.8" 6 | - "0.10" 7 | 8 | before_install: 9 | - "npm install istanbul -g" 10 | - "npm install coveralls -g" 11 | 12 | script: "make ci-travis" 13 | 14 | after_success: 15 | - "make submit-coverage-to-coveralls" 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2013 Jared Hanson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SOURCES = *.js 2 | TESTS = test/*.test.js test/**/*.test.js 3 | 4 | lint: lint-jshint 5 | test: test-mocha 6 | test-cov: test-istanbul-mocha 7 | view-cov: view-istanbul-report 8 | 9 | 10 | # ============================================================================== 11 | # Node.js 12 | # ============================================================================== 13 | include support/mk/node.mk 14 | include support/mk/mocha.mk 15 | 16 | # ============================================================================== 17 | # Browserify 18 | # ============================================================================== 19 | BROWSERIFY_MAIN = ./lib/index.js 20 | 21 | include support/mk/browserify.mk 22 | include support/mk/testling.mk 23 | 24 | # ============================================================================== 25 | # Code Quality 26 | # ============================================================================== 27 | include support/mk/notes.mk 28 | include support/mk/jshint.mk 29 | include support/mk/istanbul.mk 30 | 31 | # ============================================================================== 32 | # Continuous Integration 33 | # ============================================================================== 34 | include support/mk/coveralls.mk 35 | 36 | ci-travis: test test-cov 37 | submit-coverage-to-coveralls: submit-istanbul-lcov-to-coveralls 38 | 39 | # ============================================================================== 40 | # Clean 41 | # ============================================================================== 42 | clean: 43 | rm -rf build 44 | rm -rf reports 45 | 46 | clobber: clean clobber-node 47 | 48 | 49 | .PHONY: lint test test-cov view-cov ci-travis clean clobber 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # parent-require 2 | 3 | [![Build](https://travis-ci.org/jaredhanson/node-parent-require.png)](http://travis-ci.org/jaredhanson/node-parent-require) 4 | [![Coverage](https://coveralls.io/repos/jaredhanson/node-parent-require/badge.png)](https://coveralls.io/r/jaredhanson/node-parent-require) 5 | [![Dependencies](https://david-dm.org/jaredhanson/node-parent-require.png)](http://david-dm.org/jaredhanson/node-parent-require) 6 | 7 | 8 | Require modules from parent (i.e. loading) module. 9 | 10 | ## Install 11 | 12 | $ npm install parent-require 13 | 14 | ## Usage 15 | 16 | `parent-require` addresses an annoying error condition that arises when 17 | developing plugins, which have [peer dependencies](http://blog.nodejs.org/2013/02/07/peer-dependencies/), 18 | that are `npm link`'d into an application. 19 | 20 | The problem is best illustrated by example. We'll use a shared package of [Mongoose](http://mongoosejs.com/) 21 | schemas, but the concept applies equally well to any module you plugin to a 22 | larger framework. 23 | 24 | #### Develop a Plugin for a Framework 25 | 26 | Let's develop a set of shared [Mongoose](http://mongoosejs.com/) schemas for a 27 | user database, packaged as `mongoose-schemas-users` for reuse by any application 28 | that needs to query the database. 29 | 30 | ```javascript 31 | var mongoose = require('mongoose'); 32 | 33 | var UserSchema = new mongoose.Schema(...); 34 | 35 | module.exports = UserSchema; 36 | ``` 37 | 38 | The important bit here is that `mongoose` is a *peer dependency* of this 39 | package. 40 | 41 | #### Require a Plugin from an App 42 | 43 | Now, let's install this package... 44 | 45 | npm install mongoose-schemas-users 46 | 47 | ..and require it within our application: 48 | 49 | ```javascript 50 | var mongoose = require('mongoose') 51 | , schemas = require('mongoose-schemas-users') 52 | 53 | mongoose.model('User', schemas.UserSchema); 54 | ``` 55 | 56 | So far, so good. 57 | 58 | #### npm link Plugin for Development 59 | 60 | During the course of developing the application, we discover that we need to 61 | tweak the schemas we've defined. This is usually easy: 62 | 63 | npm link mongoose-schemas-users 64 | 65 | We've made some edits, and run the application: 66 | 67 | Error: Cannot find module 'mongoose' 68 | 69 | WTF?!? This issue arises because `mongoose` is a *peer dependency*. Now that 70 | it has been `npm link`'d to a directory that resides outside of the application 71 | itself, Node's typical resolution algorithm fails to find it. 72 | 73 | #### Fallback to Parent Require 74 | 75 | This is where `parent-require` comes into play. It provides a fallback to 76 | `require` modules from the *loading* (aka parent) module. Because the loading 77 | module exists within the application itself, Node's resolution algorithm will 78 | correctly find our peer dependency. 79 | 80 | ```javascript 81 | try { 82 | var mongoose = require('mongoose'); 83 | } catch (_) { 84 | // workaround when `npm link`'ed for development 85 | var prequire = require('parent-require') 86 | , mongoose = prequire('mongoose'); 87 | } 88 | 89 | var UserSchema = new mongoose.Schema(...); 90 | 91 | module.exports = UserSchema; 92 | ``` 93 | 94 | With the fallback in place, we can both `npm install` and `npm link` this 95 | plugin, correctly resolving peer dependencies in both cases. 96 | 97 | ## Tests 98 | 99 | $ npm install 100 | $ npm test 101 | 102 | ## Credits 103 | 104 | - [Jared Hanson](http://github.com/jaredhanson) 105 | 106 | ## License 107 | 108 | [The MIT License](http://opensource.org/licenses/MIT) 109 | 110 | Copyright (c) 2013 Jared Hanson <[http://jaredhanson.net/](http://jaredhanson.net/)> 111 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = function(id) { 2 | var parent = module.parent; 3 | for (; parent; parent = parent.parent) { 4 | try { 5 | return parent.require(id); 6 | } catch(ex) {} 7 | } 8 | throw new Error("Cannot find module '" + id + "' from parent"); 9 | }; 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parent-require", 3 | "version": "1.0.0", 4 | "description": "Require modules from parent modules.", 5 | "keywords": [ 6 | "require", 7 | "module", 8 | "modules" 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/jaredhanson/node-parent-require.git" 13 | }, 14 | "bugs": { 15 | "url": "http://github.com/jaredhanson/node-parent-require/issues" 16 | }, 17 | "author": { 18 | "name": "Jared Hanson", 19 | "email": "jaredhanson@gmail.com", 20 | "url": "http://www.jaredhanson.net/" 21 | }, 22 | "licenses": [ 23 | { 24 | "type": "MIT", 25 | "url": "http://www.opensource.org/licenses/MIT" 26 | } 27 | ], 28 | "main": "./index", 29 | "dependencies": { 30 | }, 31 | "devDependencies": { 32 | "mocha": "1.x.x", 33 | "chai": "1.x.x" 34 | }, 35 | "engines": { 36 | "node": ">= 0.4.0" 37 | }, 38 | "scripts": { 39 | "test": "node_modules/.bin/mocha --reporter spec --require test/bootstrap/node test/*.test.js test/**/*.test.js" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /support/mk/browserify.mk: -------------------------------------------------------------------------------- 1 | BROWSERIFY ?= browserify 2 | BROWSERIFY_MAIN ?= index.js 3 | BROWSERIFY_OUT ?= build/bundle.js 4 | 5 | build-browserify: node_modules 6 | mkdir -p build 7 | $(BROWSERIFY) $(BROWSERIFY_MAIN) -o $(BROWSERIFY_OUT) 8 | 9 | 10 | .PHONY: build-browserify 11 | -------------------------------------------------------------------------------- /support/mk/coveralls.mk: -------------------------------------------------------------------------------- 1 | COVERALLS ?= coveralls 2 | 3 | submit-istanbul-lcov-to-coveralls: 4 | cat $(ISTANBUL_LCOV_INFO_PATH) | $(COVERALLS) 5 | 6 | 7 | .PHONY: submit-istanbul-lcov-to-coveralls 8 | -------------------------------------------------------------------------------- /support/mk/istanbul.mk: -------------------------------------------------------------------------------- 1 | ISTANBUL ?= istanbul 2 | ISTANBUL_OUT ?= ./reports/coverage 3 | ISTANBUL_REPORT ?= lcov 4 | ISTANBUL_HTML_REPORT_PATH ?= $(ISTANBUL_OUT)/lcov-report/index.html 5 | ISTANBUL_LCOV_INFO_PATH ?= $(ISTANBUL_OUT)/lcov.info 6 | 7 | 8 | test-istanbul-mocha: node_modules 9 | NODE_PATH=$(NODE_PATH_TEST) \ 10 | $(ISTANBUL) cover \ 11 | --dir $(ISTANBUL_OUT) --report $(ISTANBUL_REPORT) \ 12 | $(_MOCHA) -- \ 13 | --reporter $(MOCHA_REPORTER) \ 14 | --require $(MOCHA_REQUIRE) $(TESTS) 15 | 16 | view-istanbul-report: 17 | open $(ISTANBUL_HTML_REPORT_PATH) 18 | 19 | 20 | .PHONY: test-istanbul-mocha view-istanbul-report 21 | -------------------------------------------------------------------------------- /support/mk/jshint.mk: -------------------------------------------------------------------------------- 1 | JSHINT ?= jshint 2 | 3 | lint-jshint: 4 | $(JSHINT) $(SOURCES) 5 | 6 | 7 | .PHONY: lint-jshint 8 | -------------------------------------------------------------------------------- /support/mk/mocha.mk: -------------------------------------------------------------------------------- 1 | MOCHA ?= ./node_modules/.bin/mocha 2 | _MOCHA ?= ./node_modules/.bin/_mocha 3 | MOCHA_REPORTER ?= spec 4 | MOCHA_REQUIRE ?= ./test/bootstrap/node 5 | 6 | test-mocha: node_modules 7 | NODE_PATH=$(NODE_PATH_TEST) \ 8 | $(MOCHA) \ 9 | --reporter $(MOCHA_REPORTER) \ 10 | --require $(MOCHA_REQUIRE) $(TESTS) 11 | 12 | 13 | .PHONY: test-mocha 14 | -------------------------------------------------------------------------------- /support/mk/node.mk: -------------------------------------------------------------------------------- 1 | node_modules: 2 | npm install 3 | 4 | clobber-node: 5 | rm -rf node_modules 6 | 7 | 8 | .PHONY: clobber-node 9 | -------------------------------------------------------------------------------- /support/mk/notes.mk: -------------------------------------------------------------------------------- 1 | NOTES ?= 'TODO|FIXME' 2 | 3 | notes: 4 | grep -Ern $(NOTES) $(SOURCES) $(TESTS) 5 | 6 | 7 | .PHONY: notes 8 | -------------------------------------------------------------------------------- /support/mk/testling.mk: -------------------------------------------------------------------------------- 1 | TESTLING ?= testling 2 | 3 | test-testling: node_modules 4 | $(TESTLING) 5 | 6 | 7 | .PHONY: test-testling 8 | -------------------------------------------------------------------------------- /test/bootstrap/node.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | 3 | global.expect = chai.expect; 4 | -------------------------------------------------------------------------------- /test/integration/app.test.js: -------------------------------------------------------------------------------- 1 | var prequire = require('../..') 2 | , fork = require('child_process').fork; 3 | 4 | describe('loading sample app', function() { 5 | 6 | it ('should load correctly', function(done) { 7 | var proc = fork(__dirname + '/../projects/app/app.js'); 8 | proc.on('message', function(msg) { 9 | expect(msg.plugins).to.be.an('array'); 10 | expect(msg.plugins[0]).to.equal('fooPlugin (in Framework v1.0.1)'); 11 | expect(msg.plugins[1]).to.equal('bazPlugin (in Framework v1.0.1)'); 12 | done(); 13 | }); 14 | }); 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /test/projects/app/app.js: -------------------------------------------------------------------------------- 1 | var framework = require('framework') 2 | , foo = require('foo-plugin') 3 | , baz = require('../baz-plugin'); 4 | 5 | framework.use(foo); 6 | framework.use(baz); 7 | 8 | 9 | function listPlugins() { 10 | return framework.pluginInfo(); 11 | } 12 | 13 | //console.log(listPlugins()); 14 | 15 | if (process.send) { 16 | process.send({ plugins: listPlugins() }); 17 | } 18 | -------------------------------------------------------------------------------- /test/projects/app/node_modules/foo-plugin/index.js: -------------------------------------------------------------------------------- 1 | var framework = require('framework'); 2 | 3 | exports.info = function() { 4 | return 'fooPlugin (in Framework v' + framework.version + ')'; 5 | }; 6 | -------------------------------------------------------------------------------- /test/projects/app/node_modules/framework/index.js: -------------------------------------------------------------------------------- 1 | exports.version = '1.0.1'; 2 | 3 | var plugins = []; 4 | 5 | exports.use = function(plugin) { 6 | plugins.push(plugin); 7 | } 8 | 9 | exports.pluginInfo = function() { 10 | var a = []; 11 | for (var i = 0, len = plugins.length; i < len; ++i) { 12 | a.push(plugins[i].info()); 13 | } 14 | return a; 15 | } 16 | -------------------------------------------------------------------------------- /test/projects/baz-plugin/index.js: -------------------------------------------------------------------------------- 1 | try { 2 | var framework = require('framework'); 3 | } catch (_) { 4 | var prequire = require('../../..') 5 | , framework = prequire('framework'); 6 | } 7 | 8 | exports.info = function() { 9 | return 'bazPlugin (in Framework v' + framework.version + ')'; 10 | }; 11 | -------------------------------------------------------------------------------- /test/require.test.js: -------------------------------------------------------------------------------- 1 | var prequire = require('..'); 2 | 3 | describe('parent-require', function() { 4 | 5 | it('should be a function', function() { 6 | expect(prequire).to.be.a('function'); 7 | }); 8 | 9 | it('should throw error if unable to resolve module', function() { 10 | expect(function() { 11 | prequire('foobar'); 12 | }).to.throw(Error, "Cannot find module 'foobar' from parent"); 13 | }); 14 | 15 | }); 16 | --------------------------------------------------------------------------------