├── .gitignore ├── .gitattributes ├── test ├── fixtures │ ├── graph │ │ ├── b.js │ │ └── a.js │ ├── inner.js │ ├── recursive1.js │ ├── file.js │ ├── outer.js │ ├── random.js │ ├── directory │ │ └── file2.js │ ├── recursive2.js │ ├── recursive3.js │ ├── recursiveRandom.js │ ├── hot │ │ ├── not-accepted.js │ │ ├── get-counter-value.js │ │ ├── counter.js │ │ ├── counter-indirect.js │ │ └── loader.js │ ├── recursiveRandom2.js │ ├── circular1.js │ ├── circular2.js │ ├── graph.js │ ├── freeVars.js │ └── loader.js ├── require-not-recursive.js ├── require-recursive.js ├── module-graph.js ├── modules-async.js ├── require-own-cache.js ├── require-context.js ├── commonjs-loaders.js ├── commonjs.js ├── require-substitutions.js ├── amd.js └── hot-replacement.js ├── .travis.yml ├── examples └── simple-server │ ├── style.css │ ├── startup.js │ ├── page.jade │ ├── request-handler.js │ ├── README.md │ └── server.js ├── lib ├── require.webpack.js ├── node.loader.js ├── RequireRoot.js ├── execModule.js ├── Module.js ├── require.js ├── HotRequireContext.js ├── execLoaders.js ├── RequireContext.js └── HotRequireRoot.js ├── bin ├── enhanced-require.js └── hot-watch.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | bin/* eol=lf -------------------------------------------------------------------------------- /test/fixtures/graph/b.js: -------------------------------------------------------------------------------- 1 | module.exports = module; -------------------------------------------------------------------------------- /test/fixtures/inner.js: -------------------------------------------------------------------------------- 1 | module.exports = "inner"; -------------------------------------------------------------------------------- /test/fixtures/recursive1.js: -------------------------------------------------------------------------------- 1 | module.exports = require; -------------------------------------------------------------------------------- /test/fixtures/file.js: -------------------------------------------------------------------------------- 1 | module.exports = {value: "file"}; -------------------------------------------------------------------------------- /test/fixtures/outer.js: -------------------------------------------------------------------------------- 1 | exports.inner = require("./inner"); -------------------------------------------------------------------------------- /test/fixtures/random.js: -------------------------------------------------------------------------------- 1 | module.exports = Math.random(); -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.6 4 | - 0.8 -------------------------------------------------------------------------------- /test/fixtures/directory/file2.js: -------------------------------------------------------------------------------- 1 | module.exports = {value: "file2"}; -------------------------------------------------------------------------------- /test/fixtures/graph/a.js: -------------------------------------------------------------------------------- 1 | module.exports = module; 2 | require("./b"); -------------------------------------------------------------------------------- /test/fixtures/recursive2.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./recursive1"); -------------------------------------------------------------------------------- /test/fixtures/recursive3.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./recursive2"); -------------------------------------------------------------------------------- /test/fixtures/recursiveRandom.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./random"); -------------------------------------------------------------------------------- /test/fixtures/hot/not-accepted.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./counter-value"); -------------------------------------------------------------------------------- /test/fixtures/hot/get-counter-value.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./counter-value"); -------------------------------------------------------------------------------- /test/fixtures/recursiveRandom2.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./recursiveRandom"); -------------------------------------------------------------------------------- /test/fixtures/circular1.js: -------------------------------------------------------------------------------- 1 | module.exports = 1; 2 | module.exports = { two: require("./circular2") }; 3 | -------------------------------------------------------------------------------- /test/fixtures/circular2.js: -------------------------------------------------------------------------------- 1 | module.exports = 2; 2 | module.exports = { one: require("./circular1") }; 3 | -------------------------------------------------------------------------------- /examples/simple-server/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #EEE; 3 | color: #333; 4 | font-family: sans-serif; 5 | } -------------------------------------------------------------------------------- /test/fixtures/graph.js: -------------------------------------------------------------------------------- 1 | exports.module = module; 2 | exports.a = require("./graph/a.js"); 3 | exports.b = require("./graph/b.js"); -------------------------------------------------------------------------------- /examples/simple-server/startup.js: -------------------------------------------------------------------------------- 1 | require("../../")(module, { 2 | recursive: true, 3 | hot: true, 4 | watch: true 5 | })("./server"); -------------------------------------------------------------------------------- /test/fixtures/freeVars.js: -------------------------------------------------------------------------------- 1 | exports.filename = __filename; 2 | exports.dirname = __dirname; 3 | exports.require = require; 4 | // exports.define = define; 5 | exports.module = module; 6 | exports.exports = exports; -------------------------------------------------------------------------------- /lib/require.webpack.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | module.exports = function() { 6 | return require("__webpack_amd_require"); 7 | } -------------------------------------------------------------------------------- /lib/node.loader.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | return "try {process.dlopen(" + JSON.stringify(this.filenames[0]) + ", module.exports); } catch(e) {" + 3 | "throw new Error('Cannot open ' + " + JSON.stringify(this.filenames[0]) + " + ': ' + e);}"; 4 | } -------------------------------------------------------------------------------- /bin/enhanced-require.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require("path"); 4 | var file = process.argv.splice(2, 1)[0].split("!"); 5 | 6 | file.push(path.resolve(file.pop())); 7 | 8 | require("../lib/require")(process.cwd(), { 9 | recursive: true 10 | })(file.join("!")); -------------------------------------------------------------------------------- /bin/hot-watch.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require("path"); 4 | var file = process.argv.splice(2, 1)[0].split("!"); 5 | 6 | file.push(path.resolve(file.pop())); 7 | 8 | require("../lib/require")(process.cwd(), { 9 | recursive: true, 10 | hot: true, 11 | watch: true 12 | })(file.join("!")); -------------------------------------------------------------------------------- /test/fixtures/loader.js: -------------------------------------------------------------------------------- 1 | module.exports = function(source) { 2 | var canAsync = this.async(); 3 | var result = "exports.async = " + JSON.stringify(!!canAsync) + ";\n" + 4 | "exports.loader = " + JSON.stringify(this) + ";\n" + 5 | source; 6 | if(canAsync) canAsync(null, result); 7 | else return result; 8 | } -------------------------------------------------------------------------------- /test/fixtures/hot/counter.js: -------------------------------------------------------------------------------- 1 | var list = []; 2 | 3 | var value = require("./counter-value"); 4 | list.push(value); 5 | 6 | module.exports = list; 7 | 8 | if(module.hot) { 9 | module.hot.accept("./counter-value", function() { 10 | list.push(-value); 11 | value = require("./counter-value"); 12 | list.push(value); 13 | }); 14 | } -------------------------------------------------------------------------------- /test/fixtures/hot/counter-indirect.js: -------------------------------------------------------------------------------- 1 | var list = []; 2 | 3 | var value = require("./get-counter-value"); 4 | list.push(value); 5 | 6 | module.exports = list; 7 | 8 | if(module.hot) { 9 | module.hot.accept("./get-counter-value", function() { 10 | list.push(-value); 11 | value = require("./get-counter-value"); 12 | list.push(value); 13 | }); 14 | } -------------------------------------------------------------------------------- /test/fixtures/hot/loader.js: -------------------------------------------------------------------------------- 1 | var list = []; 2 | 3 | var value = require("raw!./counter-value"); 4 | list.push(value); 5 | 6 | module.exports = list; 7 | 8 | if(module.hot) { 9 | module.hot.accept("raw!./counter-value", function() { 10 | list.push(value.toUpperCase()); 11 | value = require("raw!./counter-value"); 12 | list.push(value); 13 | }); 14 | } -------------------------------------------------------------------------------- /examples/simple-server/page.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | head 3 | meta(http-equiv="refresh", content="1;url=/") 4 | link(rel="stylesheet", href="/style.css", charset="utf-8") 5 | body 6 | h1 Hello World! 7 | p Hot Replacing on server side is cool :) 8 | table 9 | tr 10 | th Require time: 11 | td= requireTime 12 | tr 13 | th Request time: 14 | td= currentTime 15 | -------------------------------------------------------------------------------- /lib/RequireRoot.js: -------------------------------------------------------------------------------- 1 | var RequireContext = require("./RequireContext"); 2 | 3 | function RequireRoot(parent, options) { 4 | this.main = parent; 5 | this.options = options; 6 | this.cache = {}; 7 | this.sourceCache = {}; 8 | this.contentCache = {}; 9 | this.loadingContent = {}; 10 | this.loadingSource = {}; 11 | } 12 | 13 | module.exports = RequireRoot; 14 | 15 | RequireRoot.prototype.createContext = function(module) { 16 | var context = new RequireContext(module, this); 17 | context.createRequire(); 18 | return context; 19 | } 20 | 21 | RequireRoot.prototype.setDependencies = function() {} -------------------------------------------------------------------------------- /examples/simple-server/request-handler.js: -------------------------------------------------------------------------------- 1 | var pageTemplate = require("./page.jade"); 2 | var stylesheet = require("raw!./style.css"); 3 | 4 | var requireTime = new Date(); 5 | 6 | // A simple request handler 7 | module.exports = function(req, res) { 8 | if(req.url == "/") { 9 | res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'}); 10 | res.end(pageTemplate({requireTime: requireTime, currentTime: new Date()})); 11 | } else if(req.url == "/style.css") { 12 | res.writeHead(200, {'Content-Type': 'text/css'}); 13 | res.end(stylesheet); 14 | } else { 15 | res.writeHead(404); 16 | res.end("Not Found"); 17 | } 18 | } -------------------------------------------------------------------------------- /examples/simple-server/README.md: -------------------------------------------------------------------------------- 1 | # simple-server 2 | 3 | ## Startup 4 | 5 | ``` 6 | node ../../bin/hot-watch server.js 7 | ``` 8 | 9 | Or if you have `enhanced-require` installed globally: 10 | 11 | ``` 12 | enhanced-require-hot-watch server.js 13 | ``` 14 | 15 | ## Action 16 | 17 | It opens a server at `http://localhost:8080` which serves the page. 18 | 19 | The page refreshs itself to demostrate changes in the server code. 20 | 21 | You can edit any file from the example. The server will hot replace the code and reflect updates. The http server will not be restarted except if you edit `server.js`. The node process will never be restarted. -------------------------------------------------------------------------------- /test/require-not-recursive.js: -------------------------------------------------------------------------------- 1 | var should = require("should"); 2 | var reqFactory = require("../"); 3 | 4 | describe("require-not-recusive", function() { 5 | var req = reqFactory(module); 6 | 7 | beforeEach(function() { 8 | function clean(obj) { 9 | for(var name in obj) 10 | delete obj[name]; 11 | } 12 | clean(req.cache); 13 | clean(req.contentCache); 14 | clean(req.sourceCache); 15 | }); 16 | 17 | it("should not enhance submodules", function() { 18 | var a = req("./fixtures/recursive2"); 19 | should.exist(a); 20 | a.should.not.have.property("amd"); 21 | a.should.not.have.property("enhanced"); 22 | }); 23 | 24 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "enhanced-require", 3 | "version": "0.4.2", 4 | "author": "Tobias Koppers @sokra", 5 | "description": "Enhance the require function in node.js with support for loaders which preprocess files and really async require (AMD). Enables Hot Code Replacement.", 6 | "dependencies": { 7 | "enhanced-resolve": "0.4.x", 8 | "clone": "0.0.x", 9 | "buffer-equal": "0.0.x" 10 | }, 11 | "licenses": [ 12 | { 13 | "type": "MIT", 14 | "url": "http://www.opensource.org/licenses/mit-license.php" 15 | } 16 | ], 17 | "devDependencies": { 18 | "mocha": "1.3.x", 19 | "should": "1.1.x", 20 | "raw-loader": "0.2.x", 21 | "jade-loader": "0.2.x" 22 | }, 23 | "main": "lib/require", 24 | "bin": { 25 | "enhanced-require": "bin/enhanced-require.js", 26 | "enhanced-require-hot-watch": "bin/hot-watch.js" 27 | }, 28 | "engines": { 29 | "node": ">=0.6" 30 | }, 31 | "homepage": "http://github.com/webpack/enhanced-require", 32 | "scripts": { 33 | "test": "node node_modules/mocha/bin/_mocha --reporter spec" 34 | }, 35 | "license": "MIT" 36 | } -------------------------------------------------------------------------------- /lib/execModule.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | var path = require("path"); 6 | 7 | function stripBOM(content) { 8 | // Remove byte order marker. This catches EF BB BF (the UTF-8 BOM) 9 | // because the buffer-to-string conversion in `fs.readFileSync()` 10 | // translates it to FEFF, the UTF-16 BOM. 11 | if (content.charCodeAt(0) === 0xFEFF) { 12 | content = content.slice(1); 13 | } 14 | return content; 15 | } 16 | 17 | module.exports = function(code, parent, request, filename, enhancedRequire, options, requireRoot) { 18 | var m; 19 | if(enhancedRequire) { 20 | var EnhancedModule = require("./Module"); 21 | m = new EnhancedModule(request, parent, requireRoot); 22 | } else { 23 | var NodeModule = require("module"); 24 | m = new NodeModule(request, parent); 25 | m.paths = NodeModule._nodeModulePaths(path.dirname(filename)); 26 | } 27 | m.filename = filename; 28 | var exec = function() { 29 | m._compile(code, filename); 30 | return m.exports; 31 | } 32 | exec.module = m; 33 | return exec; 34 | } -------------------------------------------------------------------------------- /test/require-recursive.js: -------------------------------------------------------------------------------- 1 | var should = require("should"); 2 | var reqFactory = require("../"); 3 | 4 | describe("require-recusive", function() { 5 | var req = reqFactory(module, { 6 | recursive: true 7 | }); 8 | 9 | beforeEach(function() { 10 | function clean(obj) { 11 | for(var name in obj) 12 | delete obj[name]; 13 | } 14 | clean(req.cache); 15 | clean(req.contentCache); 16 | clean(req.sourceCache); 17 | }); 18 | 19 | it("should enhance submodules (one level)", function() { 20 | var a = req("./fixtures/recursive2"); 21 | should.exist(a); 22 | a.should.have.property("amd").be.equal(req.amd); 23 | a.should.have.property("enhanced").be.equal(req.enhanced); 24 | }); 25 | 26 | it("should enhance submodules (two levels)", function() { 27 | var a = req("./fixtures/recursive3"); 28 | should.exist(a); 29 | a.should.have.property("amd").be.equal(req.amd); 30 | a.should.have.property("enhanced").be.equal(req.enhanced); 31 | }); 32 | 33 | it("should handle circular requires", function() { 34 | var c = req("./fixtures/circular1"); 35 | should.exist(c); 36 | c.should.be.eql({ two: { one: 1 } }); 37 | }); 38 | 39 | }); -------------------------------------------------------------------------------- /examples/simple-server/server.js: -------------------------------------------------------------------------------- 1 | var http = require("http"); 2 | var requestHandler = require("./request-handler"); 3 | 4 | // Create the server 5 | var server = http.createServer(requestHandler); 6 | server.listen(8080, function() { 7 | console.log("Server is listening... :)"); 8 | }); 9 | 10 | if(module.hot) { 11 | 12 | // accept changes of the request handler 13 | module.hot.accept("./request-handler", function() { 14 | require(["./request-handler"], function(newRequestHandler) { 15 | server.removeListener("request", requestHandler); 16 | requestHandler = newRequestHandler; 17 | server.on("request", requestHandler); 18 | }); 19 | }); 20 | 21 | // We care about updates of this file 22 | // This is not required if you only change the request handler 23 | module.hot.accept(); 24 | module.hot.dispose(function() { 25 | // Don't handle requests anymore, but close keep-alive connections 26 | server.removeListener("request", requestHandler); 27 | server.on("request", function(req, res) { 28 | res.writeHead(302, {Connection: "close", Location: req.url}); 29 | res.end("Server updated. Please reconnect."); 30 | }); 31 | // Close the server 32 | server.close(); 33 | }); 34 | } -------------------------------------------------------------------------------- /lib/Module.js: -------------------------------------------------------------------------------- 1 | var runInThisContext = require("vm").runInThisContext; 2 | var path = require("path"); 3 | 4 | var wrapper = ["(function (exports, require, define, module, __filename, __dirname) {", "})"]; 5 | 6 | function stripBOM(content) { 7 | // Remove byte order marker. This catches EF BB BF (the UTF-8 BOM) 8 | // because the buffer-to-string conversion in `fs.readFileSync()` 9 | // translates it to FEFF, the UTF-16 BOM. 10 | if (content.charCodeAt(0) === 0xFEFF) { 11 | content = content.slice(1); 12 | } 13 | return content; 14 | } 15 | 16 | function Module(id, parent, requireRoot) { 17 | this.id = id; 18 | this.parent = parent; 19 | this.children = []; 20 | this.loaded = false; 21 | this.exports = {}; 22 | 23 | this.require = require("./require").factory(this, requireRoot); 24 | } 25 | 26 | module.exports = Module; 27 | 28 | Module.prototype._compile = function(code) { 29 | var wrappedCode = wrapper[0] + stripBOM(code) + wrapper[1]; 30 | var wrappedFunction = runInThisContext(wrappedCode, this.id, this.id == this.filename); 31 | wrappedFunction.call( 32 | this.exports, 33 | this.exports, 34 | this.require, 35 | this.require.define, 36 | this, 37 | this.filename, 38 | path.dirname(this.filename) 39 | ); 40 | this.loaded = true; 41 | } -------------------------------------------------------------------------------- /test/module-graph.js: -------------------------------------------------------------------------------- 1 | var should = require("should"); 2 | var path = require("path"); 3 | var reqFactory = require("../"); 4 | 5 | describe("module-graph", function() { 6 | var req = reqFactory(module, { 7 | recursive: true 8 | }); 9 | 10 | var graphModule = req("./fixtures/graph"); 11 | 12 | it("should have correct ids", function() { 13 | graphModule.module.id.should.be.eql(req.resolve("./fixtures/graph")); 14 | graphModule.a.id.should.be.eql(req.resolve("./fixtures/graph/a")); 15 | graphModule.b.id.should.be.eql(req.resolve("./fixtures/graph/b")); 16 | }); 17 | 18 | it("should have no parents in initial module", function() { 19 | should.exist(graphModule.module.parents); 20 | graphModule.module.parents.should.be.eql([__filename]); 21 | }); 22 | 23 | it("should have two the children", function() { 24 | should.exist(graphModule.module.children); 25 | graphModule.module.children.should.be.eql([graphModule.a, graphModule.b]); 26 | }); 27 | 28 | it("should have one parent in submodule a", function() { 29 | graphModule.a.parents.should.be.eql([req.resolve("./fixtures/graph")]); 30 | }); 31 | 32 | it("should have two parents in submodule b", function() { 33 | graphModule.b.parents.should.be.eql([req.resolve("./fixtures/graph/a"), req.resolve("./fixtures/graph")]); 34 | }); 35 | 36 | it("should have module b as children of a", function() { 37 | graphModule.a.children.should.be.eql([graphModule.b]); 38 | }); 39 | 40 | }); -------------------------------------------------------------------------------- /test/modules-async.js: -------------------------------------------------------------------------------- 1 | var should = require("should"); 2 | var path = require("path"); 3 | var reqFactory = require("../"); 4 | 5 | describe("modules-async", function() { 6 | var req = reqFactory(module); 7 | 8 | beforeEach(function() { 9 | function clean(obj) { 10 | for(var name in obj) 11 | delete obj[name]; 12 | } 13 | clean(req.cache); 14 | clean(req.contentCache); 15 | clean(req.sourceCache); 16 | }); 17 | 18 | it("should ensure the modules", function(done) { 19 | 20 | var id = path.join(__dirname, "fixtures", "file.js"); 21 | 22 | req.ensure(["./fixtures/file", "./fixtures/inner"], function(req2) { 23 | should.exist(req2); 24 | req2.should.be.a("function"); 25 | should.exist(req2.sourceCache[id]); 26 | var file = req2(id); 27 | file.should.be.eql({value: "file"}); 28 | done(); 29 | }); 30 | 31 | }); 32 | 33 | it("should ensure the modules", function(done) { 34 | 35 | var id = path.join(__dirname, "fixtures", "file.js"); 36 | 37 | req.ensure(["./fixtures/file", "./fixtures/file?1"], function(req2, err) { 38 | should.exist(req2); 39 | req2.should.be.a("function"); 40 | should.exist(req2.sourceCache[id]); 41 | should.exist(req2.sourceCache[id+"?1"]); 42 | var file = req2(id); 43 | var file1 = req2(id+"?1"); 44 | file.should.be.eql({value: "file"}); 45 | file1.should.be.eql({value: "file"}); 46 | done(); 47 | }); 48 | 49 | }); 50 | 51 | it("should be executed synchron if empty list", function() { 52 | var executed = false; 53 | req.ensure([], function(req) { 54 | executed = true; 55 | should.exist(req); 56 | req.should.be.a("function"); 57 | }); 58 | executed.should.be.ok; 59 | }); 60 | 61 | }); -------------------------------------------------------------------------------- /test/require-own-cache.js: -------------------------------------------------------------------------------- 1 | var should = require("should"); 2 | var reqFactory = require("../"); 3 | 4 | describe("require-own-cache", function() { 5 | 6 | it("should have independend caches", function(done) { 7 | var req1 = reqFactory(module); 8 | var req2 = reqFactory(module); 9 | var req3 = reqFactory(module); 10 | 11 | var m1 = req1("./fixtures/random"); 12 | var m1b = req1("./fixtures/random"); 13 | var m2 = req2("./fixtures/random"); 14 | var m2b = req2("./fixtures/random"); 15 | var m3 = req3("./fixtures/random"); 16 | var m3b = req3("./fixtures/random"); 17 | 18 | check(); 19 | 20 | var count = 3; 21 | req1(["./fixtures/random", "./fixtures/random"], function(p1, p1b) { 22 | m1 = p1; 23 | m1b = p1b; 24 | if(--count == 0) next(); 25 | }); 26 | req2(["./fixtures/random", "./fixtures/random"], function(p2, p2b) { 27 | m2 = p2; 28 | m2b = p2b; 29 | if(--count == 0) next(); 30 | }); 31 | req3(["./fixtures/random", "./fixtures/random"], function(p3, p3b) { 32 | m3 = p3; 33 | m3b = p3b; 34 | if(--count == 0) next(); 35 | }); 36 | 37 | function next() { 38 | check(); 39 | done(); 40 | } 41 | 42 | function check() { 43 | m1.should.not.be.eql(m2); 44 | m1.should.not.be.eql(m3); 45 | m2.should.not.be.eql(m3); 46 | 47 | m1.should.be.eql(m1b); 48 | m2.should.be.eql(m2b); 49 | m3.should.be.eql(m3b); 50 | } 51 | }); 52 | 53 | it("should be able to combine recursive with a own cache", function() { 54 | var req = reqFactory(module, { 55 | recursive: true 56 | }); 57 | 58 | var a = req("./fixtures/recursiveRandom2"); 59 | var b = req("./fixtures/recursiveRandom"); 60 | var c = req("./fixtures/random"); 61 | 62 | a.should.be.eql(b); 63 | a.should.be.eql(c); 64 | }); 65 | 66 | }); -------------------------------------------------------------------------------- /test/require-context.js: -------------------------------------------------------------------------------- 1 | var should = require("should"); 2 | var path = require("path"); 3 | var reqFactory = require("../"); 4 | 5 | describe("require-context", function() { 6 | var req = reqFactory(module); 7 | var context = req.context("./fixtures") 8 | 9 | beforeEach(function() { 10 | function clean(obj) { 11 | for(var name in obj) 12 | delete obj[name]; 13 | } 14 | clean(req.cache); 15 | clean(req.contentCache); 16 | clean(req.sourceCache); 17 | }); 18 | 19 | it("should be able to require a file without extension", function() { 20 | var a = context("./file"); 21 | should.exist(a); 22 | a.should.be.eql({value: "file"}); 23 | }); 24 | 25 | it("should be able to require a file with extension", function() { 26 | var a = context("./file.js"); 27 | should.exist(a); 28 | a.should.be.eql({value: "file"}); 29 | }); 30 | 31 | it("should be able to require a file in a subdirectory", function() { 32 | var a = context("./directory/file2.js"); 33 | should.exist(a); 34 | a.should.be.eql({value: "file2"}); 35 | }); 36 | 37 | it("should throw an exception if the module does not exists", function() { 38 | (function() { 39 | context("./notExists.js"); 40 | }).should.throw(/Module ".*?" not found/); 41 | }); 42 | 43 | it("should be able to use a loader in require.context", function() { 44 | var context = req.context("raw!./fixtures"); 45 | var a = context("./directory/file2"); 46 | a.should.be.eql("module.exports = {value: \"file2\"};"); 47 | }); 48 | 49 | it("should be able to use context.keys()", function() { 50 | var context = req.context("./fixtures/graph"); 51 | should.exist(context.keys); 52 | context.keys.should.be.a("function"); 53 | var keys = context.keys(); 54 | should.exist(keys); 55 | keys.should.be.eql([ 56 | "./a.js", 57 | "./b.js" 58 | ]); 59 | }); 60 | 61 | it("should be able to use context.keys() with loader", function() { 62 | var context = req.context("raw!./fixtures/graph"); 63 | should.exist(context.keys); 64 | context.keys.should.be.a("function"); 65 | var keys = context.keys(); 66 | should.exist(keys); 67 | keys.should.be.eql([ 68 | "./a.js", 69 | "./b.js" 70 | ]); 71 | }); 72 | 73 | }); -------------------------------------------------------------------------------- /test/commonjs-loaders.js: -------------------------------------------------------------------------------- 1 | var should = require("should"); 2 | var path = require("path"); 3 | var reqFactory = require("../"); 4 | 5 | describe("commonjs-loaders", function() { 6 | var req = reqFactory(module); 7 | 8 | beforeEach(function() { 9 | function clean(obj) { 10 | for(var name in obj) 11 | delete obj[name]; 12 | } 13 | clean(req.cache); 14 | clean(req.contentCache); 15 | clean(req.sourceCache); 16 | }); 17 | 18 | it("should execute a loader", function() { 19 | var outer = req("./fixtures/outer"); 20 | var outerLoaded = req("./fixtures/loader!./fixtures/outer"); 21 | should.exist(outerLoaded); 22 | outerLoaded.should.be.not.equal(outer); 23 | outerLoaded.should.have.property("inner").be.eql("inner"); 24 | outerLoaded.should.have.property("loader").be.a("object"); 25 | outerLoaded.should.have.property("async").be.eql(false); 26 | outerLoaded.loader.should.have.property("request").be.eql(req.resolve("./fixtures/loader!./fixtures/outer")); 27 | outerLoaded.loader.should.have.property("context").be.eql(__dirname); 28 | outerLoaded.loader.should.have.property("filenames").be.eql([req.resolve("./fixtures/outer")]); 29 | }); 30 | 31 | it("should execute a loader without resource", function() { 32 | var loaded = req("./fixtures/loader!"); 33 | should.exist(loaded); 34 | loaded.should.have.property("loader").be.a("object"); 35 | loaded.should.have.property("async").be.eql(false); 36 | loaded.loader.should.have.property("request").be.eql(req.resolve("./fixtures/loader!")); 37 | loaded.loader.should.have.property("context").be.eql(__dirname); 38 | loaded.loader.should.have.property("filenames").be.eql([]); 39 | }); 40 | 41 | it("should execute a loader with only query", function() { 42 | var loaded = req("./fixtures/loader?query2!?query1"); 43 | should.exist(loaded); 44 | loaded.should.have.property("loader").be.a("object"); 45 | loaded.should.have.property("async").be.eql(false); 46 | loaded.loader.should.have.property("request").be.eql(req.resolve("./fixtures/loader?query2!?query1")); 47 | loaded.loader.should.have.property("resourceQuery").be.eql("?query1"); 48 | loaded.loader.should.have.property("query").be.eql("?query2"); 49 | loaded.loader.should.have.property("context").be.eql(__dirname); 50 | loaded.loader.should.have.property("filenames").be.eql([]); 51 | }); 52 | 53 | }); -------------------------------------------------------------------------------- /lib/require.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | var fs = require("fs"); 6 | var path = require("path"); 7 | var clone = require("clone"); 8 | 9 | 10 | /** 11 | * create a require function from a filename 12 | */ 13 | function requireFactory(parent, requireRoot) { 14 | 15 | var requireContext = requireRoot.createContext(parent); 16 | if(requireContext.require.hot && requireRoot.main !== parent) { 17 | parent.hot = requireContext.require.hot; 18 | } 19 | return requireContext.require; 20 | }; 21 | 22 | exports = module.exports = function(parent, options) { 23 | 24 | // Default options 25 | options = options || {}; 26 | if(!options.resolve) options.resolve = {}; 27 | if(!options.resolve.loaders) options.resolve.loaders = []; 28 | options.resolve.loaders.push( 29 | {test: /\.node$/, loader: path.join(__dirname, "node.loader.js")}, 30 | {test: /\.coffee$/, loader: "coffee"}, 31 | {test: /\.json$/, loader: "json"}, 32 | {test: /\.jade$/, loader: "jade"} 33 | ); 34 | if(!options.resolve.extensions) 35 | options.resolve.extensions = ["", ".node", ".er.js", ".js"]; 36 | if(!options.resolve.loaderExtensions) 37 | options.resolve.loaderExtensions = [".er-loader.js", ".loader.js", ".js", ""]; 38 | if(!options.resolve.loaderPostfixes) 39 | options.resolve.loaderPostfixes = ["-er-loader", "-loader", ""]; 40 | if(!options.amd) options.amd = {}; 41 | if(!options.enhanced) options.enhanced = {}; 42 | if(!options.substitutions) options.substitutions = {}; 43 | if(!options.substitutionFactories) options.substitutionFactories = {}; 44 | if(!options.loader) options.loader = {}; 45 | if(options.watchDelay === undefined) options.watchDelay = 400; 46 | if(options.recursive === undefined) options.recursive = options.hot; 47 | 48 | if(options.hot && !options.recursive) throw new Error("hot option depends on recursive option"); 49 | 50 | var requireRoot = new (options.hot ? require("./HotRequireRoot") : require("./RequireRoot"))(parent, options); 51 | 52 | var requireFn = requireFactory(parent, requireRoot); 53 | requireRoot.require = requireFn; 54 | 55 | var substitutions = options.substitutions; 56 | options.substitutions = {}; 57 | for(var key in substitutions) { 58 | options.substitutions[requireFn.resolve(key)] = substitutions[key]; 59 | } 60 | 61 | var substitutionFactories = options.substitutionFactories; 62 | options.substitutionFactories = {}; 63 | for(var key in substitutionFactories) { 64 | options.substitutionFactories[requireFn.resolve(key)] = substitutionFactories[key]; 65 | } 66 | 67 | return requireFn; 68 | }; 69 | exports.factory = requireFactory; 70 | -------------------------------------------------------------------------------- /test/commonjs.js: -------------------------------------------------------------------------------- 1 | var should = require("should"); 2 | var path = require("path"); 3 | var reqFactory = require("../"); 4 | 5 | describe("commonjs", function() { 6 | var req = reqFactory(module); 7 | 8 | beforeEach(function() { 9 | function clean(obj) { 10 | for(var name in obj) 11 | delete obj[name]; 12 | } 13 | clean(req.cache); 14 | clean(req.contentCache); 15 | clean(req.sourceCache); 16 | }); 17 | 18 | it("should form a require function", function() { 19 | 20 | should.exist(req); 21 | req.should.be.a("function"); 22 | req.should.have.property("enhanced").be.a("object"); 23 | req.should.have.property("options").be.a("object"); 24 | req.should.have.property("ensure").be.a("function"); 25 | req.should.have.property("resolve").be.a("function"); 26 | req.should.have.property("cache").be.a("object"); 27 | req.should.have.property("contentCache").be.a("object"); 28 | req.should.have.property("sourceCache").be.a("object"); 29 | 30 | }); 31 | 32 | it("should require a file sync", function() { 33 | var file = req("./fixtures/file.js"); 34 | should.exist(file); 35 | file.should.be.eql({value: "file"}); 36 | file.should.be.equal(req("./fixtures/file")); 37 | }); 38 | 39 | it("should require.resolve a file sync", function() { 40 | var file = req.resolve("./fixtures/file.js"); 41 | should.exist(file); 42 | file.should.be.eql(path.join(__dirname, "fixtures", "file.js")); 43 | file.should.be.equal(req.resolve("./fixtures/file")); 44 | }); 45 | 46 | it("should let the user clean the cache", function() { 47 | var fileId = req.resolve("./fixtures/file"); 48 | var fileA = req(fileId); 49 | delete req.cache[fileId]; 50 | var fileB = req(fileId); 51 | fileA.should.not.be.equal(fileB); 52 | fileA.should.be.eql(fileB); 53 | }); 54 | 55 | it("should be able to require in a required module", function() { 56 | var outer = req("./fixtures/outer"); 57 | outer.should.be.eql({inner: "inner"}); 58 | }); 59 | 60 | it("should fill free vars in required module", function() { 61 | var freeVars = req("./fixtures/freeVars"); 62 | freeVars.should.have.property("filename") 63 | .be.eql(path.join(__dirname, "fixtures", "freeVars.js")); 64 | freeVars.should.have.property("dirname") 65 | .be.eql(path.join(__dirname, "fixtures")); 66 | freeVars.should.have.property("require") 67 | .be.a("function"); 68 | // freeVars.should.have.property("define") 69 | // .be.a("function"); 70 | freeVars.should.have.property("module") 71 | .be.a("object").and.have.property("exports").be.equal(freeVars.exports); 72 | 73 | }); 74 | 75 | it("should handle native modules", function() { 76 | var fs = req("fs"); 77 | should.exist(fs); 78 | fs.should.be.equal(require("fs")); 79 | }); 80 | 81 | }); -------------------------------------------------------------------------------- /lib/HotRequireContext.js: -------------------------------------------------------------------------------- 1 | var RequireContext = require("./RequireContext"); 2 | 3 | function HotRequireContext(parent, root) { 4 | RequireContext.call(this, parent, root); 5 | 6 | this.disposeHandlers = []; 7 | this.acceptDependencies = {}; 8 | this.declineDependencies = {}; 9 | this.acceptUpdate = false; 10 | this.declineUpdate = false; 11 | } 12 | 13 | module.exports = HotRequireContext; 14 | 15 | HotRequireContext.prototype = Object.create(RequireContext.prototype); 16 | 17 | HotRequireContext.prototype.createRequire = function createRequire() { 18 | RequireContext.prototype.createRequire.call(this); 19 | 20 | this.require.hot = {}; 21 | 22 | this.require.hot._requireContext = this; 23 | 24 | if(this.module) this.require.hot.data = this.root.data[this.module.id]; 25 | 26 | this.require.hot.accept = this.accept.bind(this); 27 | this.require.hot.decline = this.decline.bind(this); 28 | this.require.hot.dispose = this.dispose.bind(this); 29 | this.require.hot.addDisposeHandler = this.addDisposeHandler.bind(this); 30 | this.require.hot.removeDisposeHandler = this.removeDisposeHandler.bind(this); 31 | 32 | this.require.hot.setApplyOnUpdate = this.root.setApplyOnUpdate.bind(this.root); 33 | this.require.hot.status = this.root.status.bind(this.root); 34 | // this.require.hot.addStatusHandler = this.root.addStatusHandler.bind(this.root); 35 | // this.require.hot.removeStatusHandler = this.root.removeStatusHandler.bind(this.root); 36 | this.require.hot.check = this.root.check.bind(this.root); 37 | this.require.hot.apply = this.root.apply.bind(this.root); 38 | this.require.hot.stop = this.root.stop.bind(this.root); 39 | } 40 | 41 | HotRequireContext.prototype.accept = function accept(arg1, arg2) { 42 | if(Array.isArray(arg1)) { 43 | // accept(dependencies: string[], callback: () -> void) 44 | // accept updates of some dependencies 45 | var dependencies = arg1; 46 | var callback = arg2; 47 | 48 | dependencies.forEach(function(dep) { 49 | dep = this.require.resolve(dep); 50 | this.acceptDependencies[dep] = callback; 51 | }, this); 52 | } else if(typeof arg1 == "string") { 53 | // accept(dependency: string, callback: () -> void) 54 | return accept.call(this, [arg1], arg2); 55 | } else { 56 | // accept() 57 | // disable bubbling of update event 58 | 59 | this.acceptUpdate = true; 60 | } 61 | } 62 | 63 | HotRequireContext.prototype.decline = function decline(arg) { 64 | if(Array.isArray(arg)) { 65 | // decline(dependencies: string[]) 66 | // decline updates of some dependencies 67 | var dependencies = arg; 68 | 69 | dependencies.forEach(function(dep) { 70 | dep = this.require.resolve(dep); 71 | this.declineDependencies[dep] = true; 72 | }, this); 73 | } else if(typeof arg == "string") { 74 | // decline(dependency: string) 75 | return decline.call(this, [arg]); 76 | } else { 77 | // decline() 78 | // abort update on update event 79 | 80 | this.declineUpdate = true; 81 | } 82 | } 83 | 84 | HotRequireContext.prototype.dispose = 85 | HotRequireContext.prototype.addDisposeHandler = function addDisposeHandler(callback) { 86 | if(this.disposeHandlers.indexOf(callback) >= 0) return false; 87 | this.disposeHandlers.push(callback); 88 | return true; 89 | } 90 | 91 | HotRequireContext.prototype.removeDisposeHandler = function removeDisposeHandler(callback) { 92 | var idx = this.disposeHandlers.indexOf(callback); 93 | if(idx < 0) return false; 94 | this.disposeHandlers.splice(idx, 1); 95 | return true; 96 | } 97 | -------------------------------------------------------------------------------- /test/require-substitutions.js: -------------------------------------------------------------------------------- 1 | var should = require("should"); 2 | var reqFactory = require("../"); 3 | 4 | describe("require-substitutions", function() { 5 | var req = reqFactory(module, { 6 | recursive: true, 7 | substitutions: { 8 | "./fixtures/recursive1": "substitution1", 9 | "./fixtures/random": 0 10 | }, 11 | substitutionFactories: { 12 | "./fixtures/inner": function(require) { 13 | return "extra " + require("./fixtures/inner?query"); 14 | }, 15 | "./fixtures/file": function() { 16 | return {}; 17 | } 18 | } 19 | }); 20 | var innerValue = false; 21 | 22 | beforeEach(function() { 23 | function clean(obj) { 24 | for(var name in obj) 25 | delete obj[name]; 26 | } 27 | clean(req.cache); 28 | clean(req.contentCache); 29 | clean(req.sourceCache); 30 | }); 31 | 32 | it("should load a substitution (zero level)", function() { 33 | var a = req("./fixtures/recursive1"); 34 | should.exist(a); 35 | a.should.be.eql("substitution1"); 36 | }); 37 | 38 | it("should load a substitution (one level)", function() { 39 | var a = req("./fixtures/recursive2"); 40 | should.exist(a); 41 | a.should.be.eql("substitution1"); 42 | }); 43 | 44 | it("should load a substitution (two level)", function() { 45 | var a = req("./fixtures/recursive3"); 46 | should.exist(a); 47 | a.should.be.eql("substitution1"); 48 | }); 49 | 50 | it("should load a substitution also if '0'", function() { 51 | var a = req("./fixtures/random"); 52 | should.exist(a); 53 | a.should.be.eql(0); 54 | }); 55 | 56 | it("should load a substitution factory (zero level)", function() { 57 | var a = req("./fixtures/inner"); 58 | should.exist(a); 59 | a.should.be.eql("extra inner"); 60 | }); 61 | 62 | it("should load a substitution factory (one level)", function() { 63 | var a = req("./fixtures/outer"); 64 | should.exist(a); 65 | a.should.be.eql({inner: "extra inner"}); 66 | }); 67 | 68 | it("should cache a substitution", function() { 69 | var a = req("./fixtures/file"); 70 | var b = req("./fixtures/file"); 71 | should.exist(a); 72 | should.exist(b); 73 | a.should.be.a("object"); 74 | b.should.be.a("object"); 75 | a.should.be.equal(b); 76 | }); 77 | 78 | it("should load a substitution with AMD require", function(done) { 79 | req(["./fixtures/recursive1"], function(a) { 80 | should.exist(a); 81 | a.should.be.eql("substitution1"); 82 | done(); 83 | }); 84 | }); 85 | 86 | it("should load a substitution factory with AMD require", function(done) { 87 | req(["./fixtures/inner"], function(a) { 88 | should.exist(a); 89 | a.should.be.eql("extra inner"); 90 | done(); 91 | }); 92 | }); 93 | 94 | it("should load a substitution with AMD define", function(done) { 95 | req.define("test", ["./fixtures/recursive1"], function(a) { 96 | should.exist(a); 97 | a.should.be.eql("substitution1"); 98 | done(); 99 | }); 100 | }); 101 | 102 | it("should load a substitution with require.ensure", function(done) { 103 | req.ensure(["./fixtures/recursive1"], function(req) { 104 | var a = req("./fixtures/recursive1"); 105 | should.exist(a); 106 | a.should.be.eql("substitution1"); 107 | done(); 108 | }); 109 | }); 110 | 111 | it("should load a substitution with require.context", function() { 112 | var context = req.context("./fixtures"); 113 | var a = context("./recursive1"); 114 | should.exist(a); 115 | a.should.be.eql("substitution1"); 116 | }); 117 | 118 | }); -------------------------------------------------------------------------------- /test/amd.js: -------------------------------------------------------------------------------- 1 | var should = require("should"); 2 | var path = require("path"); 3 | var reqFactory = require("../"); 4 | 5 | describe("amd", function() { 6 | var req = reqFactory(module); 7 | if(typeof define != "function") var define = req.define; 8 | 9 | beforeEach(function() { 10 | function clean(obj) { 11 | for(var name in obj) 12 | delete obj[name]; 13 | } 14 | clean(req.cache); 15 | clean(req.contentCache); 16 | clean(req.sourceCache); 17 | }); 18 | 19 | it("should require a module", function(done) { 20 | req(["./fixtures/file"], function(file) { 21 | should.exist(file); 22 | file.should.be.eql({value: "file"}); 23 | file.should.be.equal(req("./fixtures/file.js")); 24 | done(); 25 | }); 26 | }); 27 | 28 | it("should require async", function(done) { 29 | var sync = false; 30 | req(["./fixtures/file"], function(file) { 31 | sync = true; 32 | done(); 33 | }); 34 | if(sync) throw new Error("require is not async"); 35 | }); 36 | 37 | it("should define a commonjs module", function() { 38 | var sync = false; 39 | define(function(require, exports, mod) { 40 | should.exist(require); 41 | should.exist(exports); 42 | should.exist(mod); 43 | require.should.be.a("function"); 44 | exports.should.be.a("object"); 45 | exports.should.be.equal(module.exports); 46 | mod.should.be.equal(module); 47 | sync = true; 48 | }); 49 | if(!sync) throw new Error("define is not sync"); 50 | }); 51 | 52 | it("should define a module (no deps)", function(done) { 53 | var sync = false; 54 | define("amd", function() { 55 | sync = true; 56 | var obj = {adfjklg:1}; 57 | process.nextTick(function() { 58 | module.exports.should.be.equal(obj); 59 | done(); 60 | }); 61 | return obj; 62 | }); 63 | if(!sync) throw new Error("define is not sync"); 64 | }); 65 | 66 | it("should define a module (no name)", function(done) { 67 | var sync = false; 68 | define(["./fixtures/file", "./fixtures/outer.js"], function(file, outer) { 69 | sync = true; 70 | file.should.be.eql({value: "file"}); 71 | outer.should.be.eql({inner: "inner"}); 72 | var obj = {lhxwer:2}; 73 | process.nextTick(function() { 74 | module.exports.should.be.equal(obj); 75 | done(); 76 | }); 77 | return obj; 78 | }); 79 | if(sync) throw new Error("define is not async"); 80 | }); 81 | 82 | it("should define a module", function(done) { 83 | var sync = false; 84 | define("amd", ["./fixtures/file", "./fixtures/outer.js"], function(file, outer) { 85 | sync = true; 86 | file.should.be.eql({value: "file"}); 87 | outer.should.be.eql({inner: "inner"}); 88 | var obj = {ovhweghr:3}; 89 | process.nextTick(function() { 90 | module.exports.should.be.equal(obj); 91 | done(); 92 | }); 93 | return obj; 94 | }); 95 | if(sync) throw new Error("define is not async"); 96 | }); 97 | 98 | it("should define a module with require and exports dependencies", function(done) { 99 | var sync = false; 100 | define("amd", ["./fixtures/file", "./fixtures/outer.js", "require", "exports"], function(file, outer, req2, exports) { 101 | sync = true; 102 | file.should.be.eql({value: "file"}); 103 | outer.should.be.eql({inner: "inner"}); 104 | req2.should.be.equal(req); 105 | exports.ovhweghr = 3; 106 | process.nextTick(function() { 107 | module.exports.should.be.equal(exports); 108 | module.exports.ovhweghr.should.be.equal(3); 109 | done(); 110 | }); 111 | }); 112 | if(sync) throw new Error("define is not async"); 113 | }); 114 | 115 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # enhanced-require 2 | 3 | More features for node.js require. 4 | 5 | * [loader support](https://github.com/sokra/modules-webpack/wiki/Loader-Specification) 6 | * `require.ensure` 7 | * AMD `require`, `define` (from require.js) 8 | * `require.context` 9 | * [Hot Code Replacement](https://github.com/webpack/enhanced-require/wiki/HCR-Spec) 10 | * module substitutions for mocking 11 | 12 | Asynchron require functions are **really** async. They do not use the sync node.js require, but use a async resolving and async readFile. 13 | 14 | ## Create a enhanced require function 15 | 16 | ``` javascript 17 | var myRequire = require("enhanced-require")(module, { 18 | // options 19 | recursive: true // enable for all modules recursivly 20 | // This replaces the original require function in loaded modules 21 | }); 22 | 23 | // startup your application 24 | myRequire("./startup"); 25 | ``` 26 | 27 | ## Usage 28 | 29 | Than you can use them: 30 | 31 | ``` javascript 32 | // use loaders 33 | var fileContent = require("raw!"+__filename); 34 | 35 | // use loaders automatically 36 | var template = require("./my-template.jade"); 37 | 38 | var html = template({content: fileContent}); 39 | 40 | // use require.context 41 | var directoryRequire = require.context("raw!./subdir"); 42 | var txtFile = directoryRequire("./aFile.txt"); 43 | 44 | // use require.ensure 45 | require.ensure(["./someFile.js"], function(require) { 46 | var someFile = require("./someFile.js"); 47 | }); 48 | 49 | // use AMD define 50 | require.define(["./aDep"], function(aDep) { 51 | aDep.run(); 52 | }); 53 | 54 | // use AMD require 55 | require(["./bDep"], function(bDep) { 56 | bDep.doSomething(); 57 | }); 58 | ``` 59 | 60 | ## Hot Code Replacement 61 | 62 | ``` javascript 63 | require("enhanced-require")(module, { 64 | recursive: true, // enable for all modules 65 | hot: true, // enable hot code replacement 66 | watch: true // watch for changes 67 | })("./startup"); 68 | ``` 69 | 70 | For hot code reloading you need to follow the [hot code reloading spec](https://github.com/webpack/enhanced-require/wiki/HCR-Spec). 71 | 72 | ## Testing/Mocking 73 | 74 | ``` javascript 75 | var er = require("enhanced-require"); 76 | it("should read the config option", function(done) { 77 | var subject = er(module, { 78 | recursive: true, 79 | substitutions: { 80 | // specify the exports of a module directly 81 | "../lib/config.json": { 82 | "test-option": { value: 1234 } 83 | } 84 | }, 85 | substitutionFactories: { 86 | // specify lazy generated exports of a module 87 | "../lib/otherConfig.json": function(require) { 88 | // export the same object as "config.json" 89 | return require("../lib/config.json"); 90 | } 91 | } 92 | })("../lib/subject"); 93 | 94 | var result = subject.getConfigOption("test-option"); 95 | should.exist(result); 96 | result.should.be.eql({ value: 1234 }); 97 | }); 98 | ``` 99 | 100 | ## Options 101 | 102 | ``` javascript 103 | { 104 | recursive: false, 105 | // replace require function in required modules with enhanced require method 106 | 107 | resolve: { 108 | // ... 109 | // see enhanced-resolve 110 | // https://github.com/webpack/enhanced-resolve 111 | }, 112 | 113 | substitutions: {}, 114 | substitutionFactories: {}, 115 | // See above 116 | // Replace modules with mocks 117 | // keys are resolved and have to exist 118 | 119 | amd: {}, 120 | // The require.amd object 121 | 122 | enhanced: {}, 123 | // The require.enhanced object 124 | 125 | loader: {}, 126 | // additional stuff in the loaderContext 127 | 128 | hot: false, 129 | // enable hot code replacement 130 | 131 | watch: false, 132 | // Watch for file changes and issue hot replacement 133 | 134 | watchDelay: 400, 135 | // Time to summarize changes for watching 136 | } 137 | ``` 138 | 139 | ## Future Plans 140 | 141 | * cli tool 142 | 143 | ## License 144 | 145 | Copyright (c) 2012 Tobias Koppers 146 | 147 | MIT (http://www.opensource.org/licenses/mit-license.php) -------------------------------------------------------------------------------- /test/hot-replacement.js: -------------------------------------------------------------------------------- 1 | var should = require("should"); 2 | var path = require("path"); 3 | var fs = require("fs"); 4 | var reqFactory = require("../"); 5 | 6 | describe("hot-replacement", function() { 7 | 8 | var counterValuePath = path.join(__dirname, "fixtures", "hot", "counter-value.js"); 9 | 10 | beforeEach(function() { 11 | writeCounter(1); 12 | }); 13 | 14 | function writeCounter(counter) { 15 | fs.writeFileSync(counterValuePath, "module.exports = " + counter, "utf-8"); 16 | } 17 | 18 | after(function() { 19 | try { 20 | fs.unlinkSync(counterValuePath); 21 | } catch(e) {} 22 | }); 23 | 24 | it("should accept a simple update by manual check", function(done) { 25 | var req = reqFactory(module, { 26 | hot: true, 27 | recursive: true 28 | }); 29 | 30 | var list = req("./fixtures/hot/counter"); 31 | list.should.be.eql([1]); 32 | req.hot.check(function(err, updatedModules) { 33 | if(err) throw err; 34 | should.not.exist(updatedModules); 35 | list.should.be.eql([1]); 36 | writeCounter(2); 37 | list.should.be.eql([1]); 38 | req.hot.check(function(err, updatedModules) { 39 | if(err) throw err; 40 | should.exist(updatedModules); 41 | updatedModules.length.should.be.eql(1); 42 | list.should.be.eql([1, -1, 2]); 43 | req.hot.check(function(err, updatedModules) { 44 | if(err) throw err; 45 | should.not.exist(updatedModules); 46 | list.should.be.eql([1, -1, 2]); 47 | writeCounter(3); 48 | req.hot.check(function(err, updatedModules) { 49 | if(err) throw err; 50 | should.exist(updatedModules); 51 | updatedModules.length.should.be.eql(1); 52 | list.should.be.eql([1, -1, 2, -2, 3]); 53 | done(); 54 | }); 55 | }); 56 | }); 57 | }); 58 | }); 59 | 60 | it("should accept a indirect update by manual check", function(done) { 61 | var req = reqFactory(module, { 62 | hot: true, 63 | recursive: true 64 | }); 65 | 66 | var list = req("./fixtures/hot/counter-indirect"); 67 | list.should.be.eql([1]); 68 | req.hot.check(function(err, updatedModules) { 69 | if(err) throw err; 70 | should.not.exist(updatedModules); 71 | list.should.be.eql([1]); 72 | writeCounter(2); 73 | list.should.be.eql([1]); 74 | req.hot.check(function(err, updatedModules) { 75 | if(err) throw err; 76 | should.exist(updatedModules); 77 | updatedModules.length.should.be.eql(2); 78 | list.should.be.eql([1, -1, 2]); 79 | req.hot.check(function(err, updatedModules) { 80 | if(err) throw err; 81 | should.not.exist(updatedModules); 82 | list.should.be.eql([1, -1, 2]); 83 | writeCounter(3); 84 | req.hot.check(function(err, updatedModules) { 85 | if(err) throw err; 86 | should.exist(updatedModules); 87 | updatedModules.length.should.be.eql(2); 88 | list.should.be.eql([1, -1, 2, -2, 3]); 89 | done(); 90 | }); 91 | }); 92 | }); 93 | }); 94 | }); 95 | 96 | it("should not accept a bubbling update by manual check", function(done) { 97 | var req = reqFactory(module, { 98 | hot: true, 99 | recursive: true 100 | }); 101 | 102 | var fail = req("./fixtures/hot/not-accepted"); 103 | writeCounter(2); 104 | req.hot.check(function(err, updatedModules) { 105 | should.exist(err); 106 | should.not.exist(updatedModules); 107 | err.should.be.instanceOf(Error); 108 | /bubbling/.test(err.toString()).should.be.ok; 109 | done(); 110 | }); 111 | }); 112 | 113 | it("should accept a update of a loaded module by manual check", function(done) { 114 | var req = reqFactory(module, { 115 | hot: true, 116 | recursive: true 117 | }); 118 | 119 | var list = req("./fixtures/hot/loader"); 120 | list.should.be.eql(["module.exports = 1"]); 121 | req.hot.check(function(err, updatedModules) { 122 | if(err) throw err; 123 | should.not.exist(updatedModules); 124 | list.should.be.eql(["module.exports = 1"]); 125 | writeCounter(2); 126 | list.should.be.eql(["module.exports = 1"]); 127 | req.hot.check(function(err, updatedModules) { 128 | if(err) throw err; 129 | should.exist(updatedModules); 130 | updatedModules.length.should.be.eql(1); 131 | list.should.be.eql(["module.exports = 1", "MODULE.EXPORTS = 1", "module.exports = 2"]); 132 | req.hot.check(function(err, updatedModules) { 133 | if(err) throw err; 134 | should.not.exist(updatedModules); 135 | list.should.be.eql(["module.exports = 1", "MODULE.EXPORTS = 1", "module.exports = 2"]); 136 | writeCounter(3); 137 | req.hot.check(function(err, updatedModules) { 138 | if(err) throw err; 139 | should.exist(updatedModules); 140 | updatedModules.length.should.be.eql(1); 141 | list.should.be.eql(["module.exports = 1", "MODULE.EXPORTS = 1", "module.exports = 2", "MODULE.EXPORTS = 2", "module.exports = 3"]); 142 | done(); 143 | }); 144 | }); 145 | }); 146 | }); 147 | }); 148 | 149 | it("should accept a simple update by watch", function(done) { 150 | var req = reqFactory(module, { 151 | hot: true, 152 | recursive: true, 153 | watch: true, 154 | watchDelay: 10 155 | }); 156 | 157 | var list = req("./fixtures/hot/counter"); 158 | list.should.be.eql([1]); 159 | setTimeout(function() { 160 | list.should.be.eql([1]); 161 | writeCounter(2); 162 | setTimeout(function() { 163 | list.should.be.eql([1, -1, 2]); 164 | setTimeout(function() { 165 | list.should.be.eql([1, -1, 2]); 166 | writeCounter(3); 167 | setTimeout(function() { 168 | list.should.be.eql([1, -1, 2, -2, 3]); 169 | req.hot.stop(); 170 | done(); 171 | }, 100); 172 | }, 100); 173 | }, 100); 174 | }, 100); 175 | }); 176 | 177 | it("should wait for apply if applyOnUpdate = false", function(done) { 178 | var req = reqFactory(module, { 179 | hot: true, 180 | recursive: true, 181 | watch: true, 182 | watchDelay: 10 183 | }); 184 | 185 | var list = req("./fixtures/hot/counter"); 186 | req.hot.setApplyOnUpdate(false); 187 | list.should.be.eql([1]); 188 | setTimeout(function() { 189 | list.should.be.eql([1]); 190 | writeCounter(2); 191 | setTimeout(function() { 192 | list.should.be.eql([1]); 193 | req.hot.status().should.be.eql("ready"); 194 | req.hot.apply(function(err, outdatedModules) { 195 | should.not.exist(err); 196 | should.exist(outdatedModules); 197 | outdatedModules.should.have.property("length").be.eql(1); 198 | outdatedModules[0].id.should.be.eql(counterValuePath); 199 | req.hot.status().should.be.eql("watch"); 200 | list.should.be.eql([1, -1, 2]); 201 | writeCounter(3); 202 | setTimeout(function() { 203 | req.hot.status().should.be.eql("ready"); 204 | req.hot.apply(function(err, outdatedModules) { 205 | should.not.exist(err); 206 | should.exist(outdatedModules); 207 | outdatedModules.should.have.property("length").be.eql(1); 208 | outdatedModules[0].id.should.be.eql(counterValuePath); 209 | req.hot.status().should.be.eql("watch"); 210 | list.should.be.eql([1, -1, 2, -2, 3]); 211 | req.hot.stop(); 212 | done(); 213 | }); 214 | }, 100); 215 | }); 216 | }, 100); 217 | }, 100); 218 | }); 219 | 220 | 221 | }); -------------------------------------------------------------------------------- /lib/execLoaders.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | var resolve = require("enhanced-resolve"); 6 | var fs = require("fs"); 7 | var path = require("path"); 8 | 9 | /** 10 | * execLoaders 11 | * @param context {string} the context from which this request is coming 12 | * @param request {string} the compile request string 13 | * @param loaders {{path: String, query: String, module: Boolean}[]} 14 | * the absolute filenames of the loaders 15 | * @param filenames {string[]} the filenames of "contents" 16 | * @param contents {Buffer[]} read contents 17 | * @param loaderContextExtras {object} more stuff in the loader context 18 | * @param dependencyInfo {object} { cacheable: true, files: [] } 19 | * @param options {object} the options of the module system 20 | * @param callback {function} (err, arrayOfResultBuffers, allowCaching) 21 | */ 22 | function execLoaders(context, request, loaders, filenames, contents, loaderContextExtras, dependencyInfo, options, sync, callback) { 23 | var loaderCallObjects, cacheable = true; 24 | if(loaders.length === 0) { 25 | // if no loaders are used, the file content is the resulting code 26 | callback(null, contents, true); 27 | } else { 28 | // try to load all loaders 29 | loaderCallObjects = []; 30 | try { 31 | loaders.forEach(function(l) { 32 | loaderCallObjects.push({ 33 | fn: require(l.path), 34 | path: l.path, 35 | query: l.query 36 | }); 37 | }); 38 | } catch(e) { 39 | callback(e); 40 | return; 41 | } 42 | // iterate over the loaders, asynchron 43 | contents.unshift(null); 44 | nextLoader.apply(null, contents); 45 | } 46 | 47 | function nextLoader(/* err, paramBuffer1, paramBuffer2, ...*/) { 48 | var args = Array.prototype.slice.apply(arguments); 49 | var err = args.shift(); 50 | if(err) { 51 | // a loader emitted an error 52 | callback(err); 53 | return; 54 | } 55 | // if loaders are remaining 56 | if(loaderCallObjects.length > 0) { 57 | var loaderCacheable = false; 58 | var async = false; 59 | var done = false; 60 | try { 61 | // prepare the loader "this" context 62 | // see "Loader Specification" in wiki 63 | function resolveFn(context, path, cb) { 64 | resolve(context, "!"+path, options.resolve, cb); 65 | } 66 | resolveFn.sync = function resolveSync(context, path) { 67 | return resolve.sync(context, "!"+path, options.resolve); 68 | } 69 | var loaderCallObject = loaderCallObjects.pop(); 70 | var loaderContext = { 71 | context: context, 72 | request: request, 73 | filenames: filenames, 74 | exec: function(code, filename) { 75 | var Module = require("module"); 76 | var m = new Module("exec in " + request, module); 77 | m.filename = filenames[0]; 78 | m.paths = Module._nodeModulePaths(path.dirname(filenames[0])); 79 | m._compile(code, filename); 80 | return m.exports; 81 | }, 82 | resolve: resolveFn, 83 | cacheable: function(value) { 84 | if(value === undefined) value = true; 85 | loaderCacheable = value; 86 | }, 87 | dependency: function(filename) { 88 | if(dependencyInfo) 89 | dependencyInfo.files.push(filename); 90 | }, 91 | clearDependencies: function(filename) { 92 | if(dependencyInfo) 93 | dependencyInfo.files.length = 0; 94 | }, 95 | async: function() { 96 | if(sync) return null; 97 | async = true; 98 | return loaderContext.callback; 99 | }, 100 | callback: function(err) { 101 | async = true; 102 | if(done) { 103 | // loader is already "done", so we cannot use the callback function 104 | // for better debugging we print the error on the console 105 | if(err && err.stack) console.error(err.stack); 106 | else if(err) console.error(err); 107 | else console.error(new Error("loader returned multiple times").stack); 108 | return; 109 | } 110 | done = true; 111 | contents = [err]; 112 | for(var i = 1; i < arguments.length; i++) { 113 | var arg = arguments[i]; 114 | if(arg instanceof Buffer) 115 | contents.push(arg); 116 | else if(typeof arg === "string") 117 | contents.push(new Buffer(arg, "utf-8")); 118 | else 119 | contents.push(arg); 120 | } 121 | loaderFinished.apply(null, arguments); 122 | }, 123 | loaderIndex: loaderCallObjects.length, 124 | currentLoaders: loaders.map(resolve.stringify.part), 125 | query: loaderCallObject.query, 126 | debug: options.debug, 127 | minimize: options.minimize, 128 | values: undefined, 129 | options: options, 130 | buffers: args 131 | }; 132 | 133 | // add additional loader context params or functions 134 | if(options.loader) for(var key in options.loader) 135 | loaderContext[key] = options.loader[key]; 136 | 137 | // add additional loader context params or functions 138 | if(loaderContextExtras) for(var key in loaderContextExtras) 139 | loaderContext[key] = loaderContextExtras[key]; 140 | 141 | // convert all parameters to strings if they are Buffers 142 | var params = []; 143 | args.forEach(function(arg) { 144 | if(arg instanceof Buffer) 145 | params.push(arg.toString("utf-8")); 146 | else 147 | params.push(arg); 148 | }); 149 | 150 | // exec to loader 151 | var retVal = loaderCallObject.fn.apply(loaderContext, params); 152 | 153 | // if it isn't asynchron, use the return value 154 | if(!async) { 155 | done = true; 156 | if(retVal instanceof Buffer) 157 | retVal = retVal; 158 | else if(typeof retVal === "string") 159 | retVal = new Buffer(retVal, "utf-8"); 160 | loaderFinished(retVal === undefined ? new Error("loader did not return a value") : null, retVal); 161 | } 162 | 163 | function loaderFinished() { 164 | if(!loaderCacheable && dependencyInfo) 165 | dependencyInfo.cacheable = false; 166 | 167 | nextLoader.apply(null, arguments); 168 | } 169 | } catch(e) { 170 | // ups. loader throwed an exeception 171 | if(!done) { 172 | done = true; 173 | callback(new Error("Loader throwed exeception: " + (typeof e === "object" && e.stack ? e.stack : e))); 174 | } else { 175 | // loader is already "done", so we cannot use the callback function 176 | // for better debugging we print the error on the console 177 | if(typeof e === "object" && e.stack) console.error(e.stack); 178 | else console.error(e); 179 | } 180 | return; 181 | } 182 | } else { 183 | callback(null, args); 184 | } 185 | } 186 | } 187 | 188 | function createSyncCallback() { 189 | var err, result; 190 | function fn(_err, _result) { 191 | err = _err; 192 | result = _result; 193 | } 194 | fn.get = function() { 195 | if(err) throw err; 196 | return result; 197 | } 198 | return fn; 199 | } 200 | 201 | module.exports = function(context, request, loaders, filenames, contents, loaderContextExtras, dependencyInfo, options, callback) { 202 | return execLoaders(context, request, loaders, filenames, contents, loaderContextExtras, dependencyInfo, options, false, callback); 203 | } 204 | module.exports.sync = function(context, request, loaders, filenames, contents, loaderContextExtras, dependencyInfo, options) { 205 | var callback = createSyncCallback(); 206 | execLoaders(context, request, loaders, filenames, contents, loaderContextExtras, dependencyInfo, options, true, callback); 207 | return callback.get(); 208 | } -------------------------------------------------------------------------------- /lib/RequireContext.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | var path = require("path"); 3 | var clone = require("clone"); 4 | var execModule = require("./execModule"); 5 | var execLoaders = require("./execLoaders"); 6 | var resolve = require("enhanced-resolve"); 7 | var hasOwnProperty = Object.prototype.hasOwnProperty; 8 | 9 | 10 | var natives = process.binding("natives"); 11 | function requireNativeModule(name) { 12 | return require(name); 13 | } 14 | function existsNativeModule(name) { 15 | return natives.hasOwnProperty(name); 16 | } 17 | 18 | // Helpers 19 | 20 | function mapAsync(array, fn, callback) { 21 | var count = array.length; 22 | if(count == 0) return callback(null, array); 23 | var results = array.slice(0); 24 | array.forEach(function(item, idx) { 25 | fn(item, function(err, result) { 26 | if(count < 0) return; 27 | if(err) { 28 | count = -1; 29 | return callback(err); 30 | } 31 | results[idx] = result; 32 | count--; 33 | if(count == 0) { 34 | return callback(null, results); 35 | } 36 | }); 37 | }); 38 | } 39 | 40 | function applyToAll(array, callback) { 41 | return function(err, result) { 42 | if(!err && callback) result = callback(result); 43 | array.forEach(function(fn) { 44 | fn(err, result); 45 | }); 46 | } 47 | } 48 | 49 | function addToSet(set, item) { 50 | if(!set) return [item]; 51 | if(set.indexOf(item) >= 0) return set; 52 | set.push(item); 53 | return set; 54 | } 55 | 56 | // the class 57 | 58 | function RequireContext(parent, root) { 59 | this.root = root; 60 | if(typeof parent == "string") { 61 | this.context = parent; 62 | } else { 63 | this.module = parent; 64 | var parsedRequest = parent && resolve.parse(parent.id); 65 | var mod = parsedRequest && (parsedRequest.resource && parsedRequest.resource.path ? parent : root.main); 66 | if(typeof mod == "string") { 67 | this.context = mod; 68 | } else { 69 | parsedRequest = mod && resolve.parse(mod.id); 70 | this.context = (parsedRequest) ? path.dirname(mod.id === "." ? process.argv[1] : parsedRequest.resource.path) : ""; 71 | } 72 | } 73 | } 74 | 75 | module.exports = RequireContext; 76 | 77 | RequireContext.prototype.createRequire = function createRequire() { 78 | var require = this.require = this.theRequire.bind(this); 79 | require.options = this.root.options; 80 | require.cache = this.root.cache; 81 | require.sourceCache = this.root.sourceCache; 82 | require.contentCache = this.root.contentCache; 83 | require.enhanced = this.root.options.enhanced; 84 | require.amd = this.root.options.amd; 85 | 86 | require.resolve = this.theResolve.bind(this); 87 | require.ensure = this.theEnsure.bind(this); 88 | require.context = this.theContext.bind(this); 89 | require.define = this.theDefine.bind(this); 90 | }; 91 | 92 | /** 93 | * any require(string module) - sync require 94 | * void require(array modules, function callback(modules...), [function errorCallback(err)]) - async require 95 | * void require(array modules) - async require 96 | */ 97 | RequireContext.prototype.theRequire = function theRequire(modules, callback, errorCallback) { 98 | var parent = this.module; 99 | var context = this.context; 100 | if(Array.isArray(modules)) { 101 | this.theEnsure(modules, function(req, err) { 102 | if(err && errorCallback) return errorCallback(err); 103 | var reqModules = modules.map(function(n) { return req(n) }); 104 | if(callback) callback.apply(null, reqModules); 105 | }); 106 | 107 | } else { 108 | if(callback) throw new Error("require(string, callback) is not a valid signature. You may want to call require(array, function)."); 109 | 110 | // check native module 111 | if(existsNativeModule(modules)) return requireNativeModule(modules); 112 | 113 | // resolve request 114 | var request = resolve.sync(context, modules, this.root.options.resolve); 115 | 116 | // check in cache 117 | var cache = this.root.cache; 118 | if(cache[request]) { 119 | var m = cache[request]; 120 | if(!m.substitution && parent) { 121 | m.parents = addToSet(m.parents, parent.id); 122 | parent.children = addToSet(parent.children, m); 123 | } 124 | return m.exports; 125 | } 126 | 127 | // check for substitution 128 | if(hasOwnProperty.call(this.root.options.substitutions, request)) { 129 | var substitutionModule = { 130 | id: request, 131 | substitution: true, 132 | exports: this.root.options.substitutions[request] 133 | }; 134 | cache[request] = substitutionModule; 135 | return substitutionModule.exports; 136 | } 137 | if(hasOwnProperty.call(this.root.options.substitutionFactories, request)) { 138 | var substitutionModule = { 139 | id: request, 140 | substitution: true, 141 | exports: {} 142 | }; 143 | cache[request] = substitutionModule; 144 | substitutionModule.exports = this.root.options.substitutionFactories[request](this.root.require); 145 | return substitutionModule.exports; 146 | } 147 | 148 | // split loaders from resource 149 | var requestObj = resolve.parse(request); 150 | var filename = requestObj.resource && requestObj.resource.path; 151 | var loaders = requestObj.loaders || []; 152 | 153 | // check for resource cache 154 | if(filename) { 155 | var content = this.root.contentCache[filename]; 156 | if(!content) { 157 | content = this.root.contentCache[filename] = fs.readFileSync(filename); 158 | } 159 | } 160 | 161 | // execute the loaders 162 | var source = this.root.sourceCache[request]; 163 | if(!source) { 164 | var dependencies = filename ? [filename] : []; 165 | source = 166 | this.root.sourceCache[request] = 167 | execLoaders.sync( 168 | context, 169 | request, 170 | loaders, filename ? [filename] : [], 171 | filename ? [content] : [], 172 | { 173 | loaderType: "loader", 174 | loaders: loaders, 175 | resourceQuery: requestObj.resource && requestObj.resource.query, 176 | enhanced: true 177 | }, 178 | { 179 | cacheable: true, 180 | files: dependencies 181 | }, 182 | this.root.options 183 | )[0].toString("utf-8"); 184 | this.root.setDependencies(request, dependencies); 185 | } 186 | 187 | // load the source code 188 | var exec = execModule( 189 | source, 190 | parent, 191 | request, 192 | filename, 193 | loaders.length > 0 || this.root.options.recursive, 194 | this.root.options, 195 | this.root 196 | ); 197 | 198 | // add to cache 199 | cache[request] = exec.module; 200 | 201 | // make dependency graph 202 | if(parent) { 203 | exec.module.parents = [parent.id]; 204 | parent.children.push(exec.module); 205 | } 206 | 207 | // execute 208 | return exec(); 209 | } 210 | } 211 | 212 | RequireContext.prototype.theResolve = function theResolve(name, callback) { 213 | if(callback) { 214 | if(existsNativeModule(name)) return callback(null, name); 215 | return resolve(this.context, name, this.root.options.resolve, callback); 216 | } else { 217 | if(existsNativeModule(name)) return name; 218 | return resolve.sync(this.context, name, this.root.options.resolve); 219 | } 220 | } 221 | 222 | RequireContext.prototype.theEnsure = function theEnsure(modules, callback) { 223 | var context = this.context; 224 | var options = this.root.options; 225 | var cache = this.root.cache; 226 | var reqFn = this.require; 227 | var contentCache = this.root.contentCache; 228 | var sourceCache = this.root.sourceCache; 229 | var loadingContent = this.root.loadingContent; 230 | var loadingSource = this.root.loadingSource; 231 | var root = this.root; 232 | mapAsync(modules, function(name, callback) { 233 | if(typeof name != "string") return callback(null, name); 234 | if(existsNativeModule(name)) return callback(null, name); 235 | resolve(context, name, options.resolve, callback); 236 | }, function(err, resolvedModules) { 237 | if(err) return callback(err); 238 | mapAsync(resolvedModules, function(request, callback) { 239 | if(typeof request != "string") return callback(); 240 | if(existsNativeModule(request)) return callback(); 241 | if(cache[request]) return callback(); 242 | if(hasOwnProperty.call(options.substitutions, request)) return callback(); 243 | if(hasOwnProperty.call(options.substitutionFactories, request)) return callback(); 244 | if(sourceCache[request]) return callback(); 245 | 246 | // split loaders from resource 247 | var requestObj = resolve.parse(request); 248 | var filename = requestObj.resource && requestObj.resource.path; 249 | var loaders = requestObj.loaders || []; 250 | 251 | if(!filename || contentCache[filename]) return makeSource(null, contentCache[filename]); 252 | return loadContent(); 253 | 254 | function loadContent() { 255 | if(!loadingContent[filename]) { 256 | loadingContent[filename] = [makeSource]; 257 | fs.readFile(filename, applyToAll(loadingContent[filename], function(content) { 258 | if(!contentCache[filename]) 259 | contentCache[filename] = content; 260 | delete loadingContent[filename]; 261 | return contentCache[filename]; 262 | })); 263 | } else 264 | loadingContent[filename].push(makeSource); 265 | } 266 | function makeSource(err, content) { 267 | if(err) return callback(err); 268 | if(!loadingSource[request]) { 269 | loadingSource[request] = [callback]; 270 | var finished = applyToAll(loadingSource[request], function(content) { 271 | if(!sourceCache[request]) 272 | sourceCache[request] = content; 273 | delete loadingSource[request]; 274 | return sourceCache[request]; 275 | }); 276 | var dependencies = filename ? [filename] : []; 277 | execLoaders( 278 | context, 279 | request, 280 | loaders, 281 | filename ? [filename] : [], 282 | filename ? [content] : [], 283 | { 284 | loaderType: "loader", 285 | loaders: loaders, 286 | resourceQuery: requestObj.resource && requestObj.resource.query, 287 | enhanced: true 288 | }, 289 | { 290 | cacheable: true, 291 | files: dependencies 292 | }, 293 | options, 294 | function(err, sources) { 295 | if(err) return finished(err); 296 | root.setDependencies(request, dependencies); 297 | if(sources[0] instanceof Buffer || typeof sources[0] == "string") 298 | finished(null, sources[0].toString("utf-8")); 299 | else 300 | callback(new Error("Loader result is not a Buffer or string")); 301 | } 302 | ); 303 | } else 304 | loadingSource[request].push(callback); 305 | } 306 | }, function(err) { 307 | return callback(reqFn, err); 308 | }) 309 | }); 310 | } 311 | 312 | RequireContext.prototype.theDefine = function theDefine(dependencies, fn, arg3) { 313 | var parent = this.module; 314 | var reqFn = this.require; 315 | var withName = false; 316 | if(typeof dependencies == "string") { 317 | // pop name 318 | dependencies = fn; 319 | fn = arg3; 320 | withName = true; 321 | } 322 | if(Array.isArray(dependencies)) { 323 | dependencies = dependencies.map(function(dep) { 324 | if(dep == "require") return reqFn; 325 | if(dep == "exports") return parent.exports; 326 | return dep; 327 | }); 328 | this.theEnsure(dependencies, function(req, err) { 329 | var exp = fn.apply(null, dependencies.map(function(n) { 330 | if(typeof n != "string") return n; 331 | return req(n); 332 | })); 333 | if(exp !== undefined) parent.exports = exp; 334 | }); 335 | } else if(withName) { 336 | fn = dependencies; 337 | if(typeof fn == "function") 338 | parent.exports = fn(); 339 | else 340 | parent.exports = fn; 341 | } else { 342 | fn = dependencies; 343 | if(typeof fn == "function") 344 | fn(reqFn, parent.exports, parent); 345 | else 346 | parent.exports = fn; 347 | } 348 | } 349 | 350 | RequireContext.prototype.theContext = function theContext(contextName) { 351 | var contextFn = function(name) { 352 | if(typeof name != "string" || name.substr(0, 2) != "./") 353 | throw new Error("A function created by require.context must be called with a string beginning with './'"); 354 | return this.theRequire(contextName + "/" + name); 355 | }.bind(this); 356 | contextFn.keys = function() { 357 | var request = resolve.context.sync(this.context, contextName, this.root.options.resolve); 358 | var requestObj = resolve.parse(request); 359 | var files = fs.readdirSync(requestObj.resource.path); 360 | return files.map(function(file) { return "./" + file; }); 361 | }.bind(this); 362 | return contextFn; 363 | } 364 | 365 | 366 | -------------------------------------------------------------------------------- /lib/HotRequireRoot.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | var path = require("path"); 3 | var bufferEqual = require("buffer-equal"); 4 | var HotRequireContext = require("./HotRequireContext"); 5 | var RequireRoot = require("./RequireRoot"); 6 | 7 | function HotRequireRoot(parent, options) { 8 | RequireRoot.call(this, parent, options); 9 | 10 | this.data = {}; 11 | var status = options.watch ? "watch" : "idle" 12 | Object.defineProperty(this, "_status", { 13 | get: function() { return status }, 14 | set: function(v) { 15 | status = v; 16 | } 17 | }); 18 | 19 | this.dependenciesRequestMap = {}; 20 | this.dependenciesResourceMap = {}; 21 | this.resources = {}; 22 | 23 | this.changedResources = {}; 24 | 25 | this.applyOnUpdate = options.applyOnUpdate !== false 26 | } 27 | 28 | module.exports = HotRequireRoot; 29 | 30 | HotRequireRoot.prototype = Object.create(RequireRoot.prototype); 31 | 32 | HotRequireRoot.prototype.createContext = function createContext(module) { 33 | var context = new HotRequireContext(module, this); 34 | context.createRequire(); 35 | return context; 36 | } 37 | 38 | HotRequireRoot.prototype.setDependencies = function setDependencies(request, dependencies) { 39 | if(this.dependenciesRequestMap[request]) { 40 | Object.keys(this.dependenciesRequestMap[request]).forEach(function(resource) { 41 | delete this.dependenciesResourceMap[resource][request]; 42 | }, this); 43 | } 44 | var map = this.dependenciesRequestMap[request] = {}; 45 | dependencies.forEach(function(resource) { 46 | map[resource] = true; 47 | if(!this.dependenciesResourceMap[resource]) this.dependenciesResourceMap[resource] = {}; 48 | this.dependenciesResourceMap[resource][request] = true; 49 | if(!this.resources[resource]) 50 | this.resources[resource] = this._createResourceLink(resource); 51 | }, this); 52 | } 53 | 54 | //// module.hot //// 55 | 56 | HotRequireRoot.prototype.status = function status() { 57 | return this._status; 58 | }; 59 | 60 | HotRequireRoot.prototype.error = function error() { 61 | return this._error; 62 | }; 63 | 64 | HotRequireRoot.prototype.setApplyOnUpdate = function setApplyOnUpdate(flag) { 65 | this.applyOnUpdate = flag; 66 | } 67 | 68 | HotRequireRoot.prototype.check = function check(callback) { 69 | if(this.status() != "idle") throw new Error("status() must be 'idle', when calling apply()"); 70 | this._enumerateOutdatedResources(function(err) { 71 | if(err) return callback(err); 72 | if(this.outdatedResources.length == 0) { 73 | this._status = "idle"; 74 | return callback(); 75 | } 76 | this._prepareUpdate(function(err) { 77 | if(err) return callback(err); 78 | 79 | if(this.applyOnUpdate) this._apply(callback); 80 | else return callback(null, this.outdatedModules); 81 | }.bind(this)); 82 | }.bind(this)); 83 | } 84 | 85 | HotRequireRoot.prototype.apply = function apply(callback) { 86 | if(this.status() != "ready") throw new Error("status() must be 'ready', when calling apply()"); 87 | this._apply(callback); 88 | } 89 | 90 | HotRequireRoot.prototype.peekWatch = function(callback) { 91 | if(this.status() != "watch-delay") throw new Error("status() must be 'watch-delay', when calling peekWatch()"); 92 | if(this._watchDelayTimeout) clearTimeout(this._watchDelayTimeout); 93 | this._peekWatch(callback); 94 | } 95 | 96 | HotRequireRoot.prototype.stop = function() { 97 | if(this._watchDelayTimeout) clearTimeout(this._watchDelayTimeout); 98 | Object.keys(this.resources).forEach(function(res) { 99 | this._disposeResourceLink(this.resources[res]); 100 | delete this.resources[res]; 101 | }, this); 102 | this.options.watch = false; 103 | } 104 | 105 | //// protected //// 106 | 107 | HotRequireRoot.prototype._createResourceLink = function _createResourceLink(resource) { 108 | if(this.options.watch) { 109 | var readFileEnabled = true; 110 | var onChange = function() { 111 | readFileEnabled = false; 112 | this.changedResources[resource] = (this.changedResources[resource] || 0) + 1; 113 | this._changed(); 114 | }.bind(this); 115 | var currentContent = this.contentCache[resource]; 116 | if(currentContent) { 117 | fs.readFile(resource, function(err, content) { 118 | if(!readFileEnabled) return; 119 | if(err || !content || !bufferEqual(content, currentContent)) 120 | onChange(); 121 | }); 122 | } 123 | return fs.watch(resource, { persistent: false }, onChange) 124 | } else { 125 | return { 126 | mtime: fs.statSync(resource).mtime.getTime(), 127 | content: this.contentCache[resource] 128 | } 129 | } 130 | } 131 | 132 | HotRequireRoot.prototype._disposeResourceLink = function _disposeResourceLink(resourceLink) { 133 | if(this.options.watch) { 134 | resourceLink.close(); 135 | } 136 | } 137 | 138 | HotRequireRoot.prototype._changed = function() { 139 | if(this._status != "watch" && this._status != "watch-delay") return; 140 | if(this.options.watchDelay === 0) return this._peekWatch(); 141 | if(this._status == "watch") this._status = "watch-delay"; 142 | if(this._watchDelayTimeout) clearTimeout(this._watchDelayTimeout); 143 | this._watchDelayTimeout = setTimeout(this._peekWatch.bind(this, this._onAutomaticPeekWatchResult.bind(this)), this.options.watchDelay); 144 | } 145 | 146 | HotRequireRoot.prototype._onAutomaticPeekWatchResult = function(err, result) { 147 | if(typeof this.options.onAutomaticCheck === "function") { 148 | return this.options.onAutomaticCheck(err, result); 149 | } 150 | if(err) throw err; 151 | } 152 | 153 | HotRequireRoot.prototype._peekWatch = function(callback) { 154 | this.currentChangedResources = {}; 155 | Object.keys(this.changedResources).forEach(function(resource) { 156 | this.currentChangedResources[resource] = this.changedResources[resource]; 157 | }, this); 158 | 159 | this.outdatedResources = Object.keys(this.currentChangedResources); 160 | 161 | if(this.outdatedResources.length == 0) { 162 | this._status = "watch"; 163 | return callback(); 164 | } 165 | this._prepareUpdate(function(err) { 166 | if(err) return callback(err); 167 | if(this.applyOnUpdate) this._apply(callback); 168 | else return callback(null, this.outdatedModules); 169 | }.bind(this)); 170 | } 171 | 172 | HotRequireRoot.prototype._enumerateOutdatedResources = function _enumerateOutdatedResources(callback) { 173 | this._status = "check"; 174 | 175 | this.outdatedResources = []; 176 | 177 | var resources = Object.keys(this.resources); 178 | var count = resources.length; 179 | resources.forEach(function(resource) { 180 | var data = this.resources[resource]; 181 | if(data.content) { 182 | fs.readFile(resource, function(err, content) { 183 | if(err || !content || !bufferEqual(content, data.content)) { 184 | this.outdatedResources.push(resource); 185 | data.content = content; 186 | } 187 | return oneDone(); 188 | }.bind(this)) 189 | } else { 190 | fs.stat(resource, function(err, stat) { 191 | if(err || !stat || stat.mtime.getTime() != data.mtime) { 192 | this.outdatedResources.push(resource); 193 | data.mtime = mtime; 194 | } 195 | return oneDone(); 196 | }.bind(this)); 197 | } 198 | }, this); 199 | 200 | function oneDone() { 201 | if(--count == 0) { 202 | return callback(); 203 | } 204 | } 205 | } 206 | 207 | HotRequireRoot.prototype._prepareUpdate = function _prepareUpdate(callback) { 208 | this._status = "prepare"; 209 | 210 | this.outdatedModules = []; 211 | 212 | this.outdatedResources.forEach(function(resource) { 213 | var modules = this.dependenciesResourceMap[resource]; 214 | Object.keys(modules).forEach(function(moduleId) { 215 | var module = this.cache[moduleId]; 216 | if(module) this.outdatedModules.push(module); 217 | }, this); 218 | }, this); 219 | 220 | this.outdatedDependencies = {}; 221 | 222 | var queue = this.outdatedModules.slice(); 223 | while(queue.length > 0) { 224 | var module = queue.pop(); 225 | if(module.hot._requireContext.acceptUpdate) 226 | continue; 227 | if(module.hot._requireContext.declineUpdate) { 228 | this._status = "abort"; 229 | return callback(new Error("Aborted because of self decline")); 230 | } 231 | for(var i = 0; i < module.parents.length; i++) { 232 | var parentId = module.parents[i]; 233 | var parent = this.cache[parentId]; 234 | if(this.outdatedModules.indexOf(parent) >= 0) continue; 235 | if(!parent || parent === this.main) { 236 | this._status = "abort"; 237 | return callback(new Error("Aborted because of bubbling")); 238 | } 239 | if(parent.hot._requireContext.declineDependencies[module.id]) { 240 | this._status = "abort"; 241 | return callback(new Error("Aborted because of declined dependency")); 242 | } 243 | if(parent.hot._requireContext.acceptDependencies[module.id]) { 244 | if(!this.outdatedDependencies[parentId]) this.outdatedDependencies[parentId] = []; 245 | if(this.outdatedDependencies[parentId].indexOf(module) >= 0) continue; 246 | this.outdatedDependencies[parentId].push(module); 247 | continue; 248 | } 249 | delete this.outdatedDependencies[parentId]; 250 | this.outdatedModules.push(parent); 251 | queue.push(parent); 252 | } 253 | } 254 | 255 | this._status = "ready"; 256 | return callback(); 257 | } 258 | 259 | HotRequireRoot.prototype._apply = function _apply(callback) { 260 | this._dispose(function(err) { 261 | if(err) return callback(err); 262 | this._notifyDependencies(function(err) { 263 | if(err) return callback(err); 264 | this._loadSelfAccepted(function(err) { 265 | if(err) return callback(err); 266 | this._finishApply(callback); 267 | }.bind(this)); 268 | }.bind(this)); 269 | }.bind(this)); 270 | } 271 | 272 | HotRequireRoot.prototype._dispose = function _dispose(callback) { 273 | this._status = "dispose"; 274 | 275 | this.outdatedModules.forEach(function(module) { 276 | var data = {}; 277 | module.hot._requireContext.disposeHandlers.forEach(function(cb) { 278 | cb(data); 279 | }); 280 | this.data[module.id] = data; 281 | }, this); 282 | 283 | this.outdatedResources.forEach(function(resource) { 284 | delete this.contentCache[resource]; 285 | this._disposeResourceLink(this.resources[resource]); 286 | delete this.resources[resource]; 287 | }, this); 288 | this.outdatedModules.forEach(function(module) { 289 | delete this.sourceCache[module.id]; 290 | delete this.cache[module.id]; 291 | if(module.children) { 292 | module.children.forEach(function(child) { 293 | var idx = child.parents.indexOf(module.id); 294 | if(idx >= 0) child.parents.splice(idx, 1); 295 | }, this); 296 | } 297 | }, this); 298 | Object.keys(this.outdatedDependencies).forEach(function(moduleId) { 299 | var module = this.cache[moduleId]; 300 | var outdatedDependencies = this.outdatedDependencies[moduleId]; 301 | outdatedDependencies.forEach(function(dependency) { 302 | var idx = module.children.indexOf(dependency); 303 | if(idx >= 0) module.children.splice(idx, 1); 304 | }); 305 | }, this); 306 | 307 | return callback(); 308 | } 309 | 310 | HotRequireRoot.prototype._notifyDependencies = function _notifyDependencies(callback) { 311 | var error = null; 312 | Object.keys(this.outdatedDependencies).forEach(function(moduleId) { 313 | var module = this.cache[moduleId]; 314 | var outdatedDependencies = this.outdatedDependencies[moduleId]; 315 | var callbacks = []; 316 | outdatedDependencies.forEach(function(dependency) { 317 | var cb = module.hot._requireContext.acceptDependencies[dependency.id]; 318 | if(callbacks.indexOf(cb) >= 0) return; 319 | callbacks.push(cb); 320 | }); 321 | callbacks.forEach(function(cb) { 322 | try { 323 | cb(outdatedDependencies); 324 | } catch(err) { 325 | if(!error) 326 | error = this._error = err; 327 | } 328 | }, this); 329 | }, this); 330 | 331 | if(error) { 332 | this._status = "fail"; 333 | return callback(error); 334 | } else { 335 | return callback(); 336 | } 337 | } 338 | 339 | HotRequireRoot.prototype._loadSelfAccepted = function _loadSelfAccepted(callback) { 340 | var error = null; 341 | 342 | var oneDone = function() { 343 | if(--count == 0) next.call(this); 344 | }.bind(this); 345 | 346 | var count = 1; 347 | this.outdatedModules.forEach(function updateSelfAcceptedModules(m) { 348 | if(m.hot._requireContext.acceptUpdate) { 349 | count++; 350 | m.hot._requireContext.require([m.id], oneDone, function(err) { 351 | if(!error) 352 | error = this._error = err; 353 | }.bind(this)); 354 | } 355 | }, this); 356 | oneDone(); 357 | 358 | function next() { 359 | if(error) { 360 | this._status = "fail"; 361 | return callback(error); 362 | } else { 363 | return callback(); 364 | } 365 | } 366 | } 367 | 368 | HotRequireRoot.prototype._finishApply = function(callback) { 369 | if(this.options.watch) { 370 | Object.keys(this.currentChangedResources).forEach(function(resource) { 371 | this.changedResources[resource] -= this.currentChangedResources[resource]; 372 | if(this.changedResources[resource] <= 0) 373 | delete this.changedResources[resource]; 374 | }, this); 375 | 376 | if(Object.keys(this.changedResources).length > 0) { 377 | this._status = "watch-delay"; 378 | } else { 379 | this._status = "watch"; 380 | } 381 | } else { 382 | this._status = "idle"; 383 | } 384 | return callback(null, this.outdatedModules); 385 | } 386 | 387 | 388 | 389 | --------------------------------------------------------------------------------