├── .gitignore ├── .npmrc ├── .travis.yml ├── DockerTemplate ├── MIT-license.md ├── README.md ├── index.js ├── package.json └── test ├── dummy.js ├── jsTest.js ├── terminal-test.js ├── use-case-nested.js └── use-case.js /.gitignore: -------------------------------------------------------------------------------- 1 | cover/ 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://registry.npmjs.org/ 2 | save-exact=true 3 | progress=false 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | notifications: 7 | email: true 8 | node_js: 9 | - '6' 10 | before_script: 11 | - npm prune 12 | after_success: 13 | - npm run semantic-release 14 | branches: 15 | except: 16 | - /^v\d+\.\d+\.\d+$/ 17 | -------------------------------------------------------------------------------- /DockerTemplate: -------------------------------------------------------------------------------- 1 | FROM mhart/alpine-node:$VERSION 2 | RUN mkdir -p /usr/src/app 3 | WORKDIR /usr/src/app 4 | COPY package.json . 5 | # Adding extra tools 6 | RUN apk add --update git 7 | RUN npm install 8 | COPY . . 9 | CMD npm test 10 | -------------------------------------------------------------------------------- /MIT-license.md: -------------------------------------------------------------------------------- 1 | Copyright 2014 Gleb Bahmutov 2 | http://github.com/bahmutov/dotdot 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-hook 2 | 3 | > Run source transform function on Node require 4 | 5 | [![NPM][node-hook-icon]][node-hook-url] 6 | 7 | [![Build status][node-hook-ci-image]][node-hook-ci-url] 8 | [![dependencies][node-hook-dependencies-image]][node-hook-dependencies-url] 9 | [![devdependencies][node-hook-devdependencies-image]][node-hook-devdependencies-url] 10 | [![semantic-release][semantic-image] ][semantic-url] 11 | 12 | ## Install and use 13 | 14 | ```sh 15 | npm install --save node-hook 16 | ``` 17 | 18 | Before loading desired *.js* files, install hook 19 | 20 | ```js 21 | var hook = require('node-hook'); 22 | 23 | function logLoadedFilename(source, filename) { 24 | return 'console.log("' + filename + '");\n' + source; 25 | } 26 | hook.hook('.js', logLoadedFilename); 27 | require('./dummy'); 28 | // prints fulle dummy.js filename, runs dummy.js 29 | 30 | hook.unhook('.js'); // removes your own transform 31 | ``` 32 | 33 | **remember:** Nodejs caches compiled modules, so if the transform is not 34 | working, you might need to delete the cached entry in `require.cache`, 35 | then call `require(filename)` again to force reload. 36 | 37 | Related: Node require replacement [really-need](https://github.com/bahmutov/really-need). 38 | 39 | You can hook several transformers thanks to the [code](https://github.com/bahmutov/node-hook/pull/2) 40 | submitted by [djulien](https://github.com/djulien) 41 | 42 | ## Existing transform 43 | 44 | You can get the current transform and run any source through it. For example 45 | to see how the current source looks when loaded but before evaluated 46 | 47 | ```js 48 | const filename = resolve('./call-foo.js') 49 | const transform = Module._extensions['.js'] 50 | const fakeModule = { 51 | _compile: source => { 52 | console.log('transformed code') 53 | console.log(source) 54 | } 55 | } 56 | transform(fakeModule, filename) 57 | ``` 58 | 59 | ## Small print 60 | 61 | Author: Gleb Bahmutov © 2013 62 | 63 | * [Changelog](History.md) 64 | * [@bahmutov](https://twitter.com/bahmutov) 65 | * [glebbahmutov.com](http://glebbahmutov.com) 66 | * [blog](http://glebbahmutov.com/blog/) 67 | 68 | License: [MIT](MIT-license.md) - do anything with the code, 69 | but don't blame me if it does not work. 70 | 71 | Support: if you find any problems with this module, email / tweet / open issue on Github 72 | 73 | [node-hook-icon]: https://nodei.co/npm/node-hook.svg?downloads=true 74 | [node-hook-url]: https://npmjs.org/package/node-hook 75 | [node-hook-ci-image]: https://travis-ci.org/bahmutov/node-hook.svg?branch=master 76 | [node-hook-ci-url]: https://travis-ci.org/bahmutov/node-hook 77 | [node-hook-dependencies-image]: https://david-dm.org/bahmutov/node-hook.svg 78 | [node-hook-dependencies-url]: https://david-dm.org/bahmutov/node-hook 79 | [node-hook-devdependencies-image]: https://david-dm.org/bahmutov/node-hook/dev-status.svg 80 | [node-hook-devdependencies-url]: https://david-dm.org/bahmutov/node-hook#info=devDependencies 81 | [semantic-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg 82 | [semantic-url]: https://github.com/semantic-release/semantic-release 83 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // based on https://github.com/gotwarlost/istanbul/blob/master/lib/hook.js 4 | 5 | /* 6 | Copyright (c) 2012, Yahoo! Inc. All rights reserved. 7 | Copyrights licensed under the New BSD License. 8 | See the accompanying LICENSE file for terms. 9 | */ 10 | 11 | const fs = require('fs') 12 | let Module = require('module') 13 | 14 | // dummy definition in case module is not available: 15 | if (!Module) Module = {} 16 | if (!Module._extensions) { console.log('dummy module def'); Module._extensions = [] } 17 | 18 | const originalLoaders = {} 19 | const nestedTransforms = {} // allow nested transforms 20 | 21 | function warn (message) { 22 | console.log('🔥 ', message) 23 | } 24 | 25 | const verify = { 26 | extension: function (str) { 27 | if (typeof str !== 'string') { 28 | throw new Error('expected string extension, have ' + str) 29 | } 30 | if (str[0] !== '.') { 31 | throw new Error('Extension should start with dot, for example .js, have ' + str) 32 | } 33 | }, 34 | transform: function (fn) { 35 | if (typeof fn !== 'function') { 36 | throw new Error('Transform should be a function, have ' + fn) 37 | } 38 | } 39 | } 40 | 41 | function hook (extension, transform, options) { 42 | options = options || {} 43 | if (typeof extension === 'function' && 44 | typeof transform === 'undefined') { 45 | transform = extension 46 | extension = '.js' 47 | } 48 | if (options.verbose) { 49 | console.log('hooking transform', transform.name, 'for', extension) 50 | } 51 | 52 | verify.extension(extension) 53 | verify.transform(transform) 54 | 55 | if (!nestedTransforms[extension]) { 56 | nestedTransforms[extension] = [] 57 | } 58 | if (!nestedTransforms[extension].length) { 59 | // only store the first one -DJ 60 | originalLoaders[extension] = Module._extensions[extension] 61 | } 62 | nestedTransforms[extension].push(transform) // allow nested transforms -DJ 63 | 64 | Module._extensions[extension] = function (module, filename) { 65 | if (options.verbose) { 66 | console.log('transforming', filename) 67 | } 68 | var source = fs.readFileSync(filename, 'utf8') 69 | var ret = null // transform(source, filename); 70 | nestedTransforms[extension].every(function (nested) { 71 | // nesting order performs earlier first, later last 72 | ret = nested(source, filename) 73 | if (ret === undefined) { 74 | warn('source transform returned undefined for file ' + filename) 75 | } 76 | source = ret + '' // convert to string and keep going 77 | return true // continue 78 | }) 79 | if (typeof ret === 'string') { 80 | module._compile(ret, filename) 81 | } else if (options.verbose) { 82 | console.error('transforming source from', filename, 'has not returned a string') 83 | } 84 | } 85 | if (options.verbose) { 86 | console.log('hooked function') 87 | } 88 | } 89 | 90 | function unhook (extension) { 91 | console.log('unhooking require hook for', extension) 92 | if (typeof extension === 'undefined') { 93 | extension = '.js' 94 | } 95 | verify.extension(extension) 96 | if (nestedTransforms[extension] && nestedTransforms[extension].length) { 97 | nestedTransforms[extension].pop() 98 | if (!nestedTransforms[extension].length) { 99 | // restore original only once 100 | Module._extensions[extension] = originalLoaders[extension] 101 | } 102 | } else { 103 | warn('Hmm, trying to unhook extension for ' + extension + 104 | ' it has not been registered') 105 | } 106 | } 107 | 108 | module.exports = { 109 | hook: hook, 110 | unhook: unhook 111 | } 112 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-hook", 3 | "description": "Run source transform function on Node require", 4 | "version": "0.0.0-development", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "gt test/jsTest.js --output", 8 | "terminal-test": "node test/terminal-test.js", 9 | "use-case": "node test/use-case.js & node test/use-case-nested.js", 10 | "semantic-release": "semantic-release pre && npm publish && semantic-release post", 11 | "issues": "git-issues", 12 | "check": "autochecker 0.12.9 4.0 5.0 6", 13 | "check-0.12": "autochecker 0.12.6", 14 | "commit": "commit-wizard", 15 | "size": "t=\"$(npm pack .)\"; wc -c \"${t}\"; tar tvf \"${t}\"; rm \"${t}\";", 16 | "lint": "standard --verbose --fix index.js", 17 | "pretest": "npm run lint" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/bahmutov/node-hook.git" 22 | }, 23 | "keywords": [ 24 | "require", 25 | "node", 26 | "load", 27 | "hook", 28 | "transform" 29 | ], 30 | "author": "Gleb Bahmutov ", 31 | "license": "MIT", 32 | "devDependencies": { 33 | "condition-node-version": "1.3.0", 34 | "git-issues": "1.3.1", 35 | "github-post-release": "1.12.1", 36 | "gt": "0.10.0", 37 | "pre-git": "3.15.0", 38 | "semantic-release": "^6.3.6", 39 | "simple-commit-message": "3.2.0", 40 | "standard": "10.0.2" 41 | }, 42 | "files": [ 43 | "index.js", 44 | "MIT-license.md" 45 | ], 46 | "config": { 47 | "pre-git": { 48 | "commit-msg": "simple", 49 | "pre-commit": [ 50 | "npm test", 51 | "npm run use-case" 52 | ], 53 | "pre-push": [ 54 | "npm run size" 55 | ], 56 | "post-commit": [], 57 | "post-checkout": [], 58 | "post-merge": [] 59 | } 60 | }, 61 | "release": { 62 | "verifyConditions": { 63 | "path": "condition-node-version", 64 | "node": "6" 65 | }, 66 | "analyzeCommits": "simple-commit-message", 67 | "generateNotes": "github-post-release" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/dummy.js: -------------------------------------------------------------------------------- 1 | function add(a, b) { return a + b; } 2 | module.exports = add; 3 | -------------------------------------------------------------------------------- /test/jsTest.js: -------------------------------------------------------------------------------- 1 | var hook = require('../index'); 2 | var transformCalled = false; 3 | 4 | gt.module('hooking to js load', { 5 | setup: function () { 6 | transformCalled = false; 7 | } 8 | }); 9 | 10 | function transform(source, filename) { 11 | if (typeof source !== 'string') { 12 | throw new Error('expected source string, got ' + source); 13 | } 14 | if (typeof filename !== 'string') { 15 | throw new Error('expected filename string, got ' + filename); 16 | } 17 | transformCalled = true; 18 | return source; 19 | } 20 | 21 | gt.test('basics', function () { 22 | gt.object(hook, 'returns a function'); 23 | hook.hook('.js', transform, { 24 | verbose: true 25 | }); 26 | require('./dummy'); 27 | gt.ok(transformCalled, 'transform function was called'); 28 | 29 | hook.unhook('.js') 30 | }); 31 | -------------------------------------------------------------------------------- /test/terminal-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | console.log('checking terminal resolution'); 4 | console.log('is terminal?', process.stdout.isTTY); 5 | if (process.stdout.isTTY) { 6 | console.log('terminal window size', 7 | process.stdout.getWindowSize()); 8 | } 9 | -------------------------------------------------------------------------------- /test/use-case-nested.js: -------------------------------------------------------------------------------- 1 | var hook = require('../index'); 2 | var path = require('path'); 3 | 4 | function outer_hook(source, filename) { 5 | console.log('wedge +10 into filename', path.relative(__dirname, filename)); 6 | source = source.replace(/return a \+ b/, "return a + b + 10"); 7 | // console.log(source); 8 | return source; 9 | } 10 | 11 | function inner_hook(source, filename) { 12 | console.log('wedge *3 into filename', path.relative(__dirname, filename)); 13 | source = source.replace(/return a \+ b/, "return a + b * 3"); 14 | // console.log(source); 15 | return source; 16 | } 17 | 18 | hook.hook('.js', outer_hook); //+ 10 19 | hook.hook('.js', inner_hook); //* 3 20 | 21 | var add = require('./dummy'); 22 | 23 | console.assert(typeof add === 'function', 'got a function'); 24 | //NOTE: 25 | //if inner_hook runs after outer_hook, result will be 2 + 3 * 3 + 10 = 21 26 | //if outer_hook runs after inner_hook, result will be 2 + 3 + 10 * 3 = 35 27 | //if only outer_hook runs, result will be 2 + 3 + 10 = 15 28 | //if only inner_hook runs, result will be 2 + 3 * 3 = 11 29 | //since inner_hook is attached last, it should run after outer_hook 30 | console.log("result: ", add(2, 3)); 31 | console.assert(add(2, 3) === 21, 'computed correct function'); 32 | 33 | console.log('nested test: all seems good'); 34 | 35 | //eof 36 | 37 | -------------------------------------------------------------------------------- /test/use-case.js: -------------------------------------------------------------------------------- 1 | var hook = require('../index'); 2 | function request(source, filename) { 3 | console.log('loading filename', filename); 4 | return source; 5 | } 6 | hook.hook('.js', request); 7 | var add = require('./dummy'); 8 | console.assert(typeof add === 'function', 'got a function'); 9 | console.assert(add(2, 3) === 5, 'computed correct function'); 10 | console.log('all seems good'); 11 | --------------------------------------------------------------------------------