npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

require-injector

v5.3.2

Published

Resolve and replacing require() function in both NodeJS and browser side CommonJS packing tool like Webpack and Browserify.

Downloads

21

Readme

 ╦═╗ ╔═╗ ╔═╗  ╦ ╦ ╦ ╦═╗ ╔═╗      ╦ ╔╗╔  ╦ ╔═╗ ╔═╗ ╔╦╗ ╔═╗ ╦═╗ 
 ╠╦╝ ║╣  ║═╬╗ ║ ║ ║ ╠╦╝ ║╣       ║ ║║║  ║ ║╣  ║    ║  ║ ║ ╠╦╝ 
 ╩╚═ ╚═╝ ╚═╝╚ ╚═╝ ╩ ╩╚═ ╚═╝      ╩ ╝╚╝ ╚╝ ╚═╝ ╚═╝  ╩  ╚═╝ ╩╚═

require-injector

Travis CI

Injecting and replacing require() and import statement in both NodeJS and browser side JS or Typescript file via packing tool like Webpack.

When it is used for Node, it is a little bit like app-module-path, when it is used for browser environment JS bundle tool, it is like Webpack resolve.alias configuration, but more fine-grained.

You may use it as a simple IoC container, which helps you decouple modules. Or if you just want to replace some third-party package's dependency without doing git-fork and create a whole new package.

Be aware that Node.js ECMAScript modules is not supported at this moment.

Installation

npm install require-injector

Node project example

Assume you have project structure like below,

/
├─── src
│    ├─── dir1
│    │     ├─── sub-dir
│    │     │      └─── feature1-1.js
│    │     └─── feature1.js
│    └─── dir2
│          └─── feature2.js
├─── app.js
├─── utils
│      └─── index.js
└─── node_modules
      ├─── module1/index.js, package.json, ...
      └─── module2/index.js, package.json, ...

Injection for local files

In src/dir1/some1.js, there is require() calling to module1

var m1 = require('module1');

You can inject this require('module1') with the exports value from module2.

In your entry js file app.js:

const Injector = require('require-injector').default;

let rj = new Injector({basedir: __dirname});
rj.fromDir('src/dir1')
  .substitute('module1', 'module2');

Or in Typescript

import Injector from 'require-injector';
let rj = new Injector({basedir: __dirname});
rj.fromDir('src/dir1')
  .substitute('module1', 'module2');

From then on, any file require('module1') will actually be requiring module2 instead.

Also you can inject it with a value returned from a lazy factory function, or just give a value to it;

rj.fromDir(['src/dir1', 'src/dir2'])
	.factory('module1', function(file) { return something;})
	.value('module2', 123);

No relative path needed in require()

You may don't need require messy relative path anymore. Image you have a common utils always be required by different feature folders. Same effect like app-module-path

// In app.js
const Injector = require('require-injector').default;
let rj = new Injector();
rj.fromDir(['src/dir1', 'src/dir2']).factory('_utils', function() {
	return require('./utils');
});

Now you have a common fake module name called _utils to be required from dir1,dir2 In dir1/feature1.js

// var utils = require('../utils');
var utils = require('_utils');

In dir1/sub-dir/feature1-1.js

// var utils = require('../../utils');
var utils = require('_utils');

Since v2.0.0 hierarchical directors setting is supported. Injector setting can be configured on different level directories, lower level directory's setting takes precedence.

rj.fromDir('src/dir1')
	.substitute('module1', 'module2');
rj.fromDir('src')
	.substitute('module1', 'module3');

All files under src/dir1 will be injected with 'module2'. Any of other files from src/**/* will be injected with 'module3'.

Injection for Node packages

You can setup injection for JS file of specific packages, e.g. module1

...
rj.fromPackage('module1')
	.substitute('module1-dependencyA', 'anotherPackage');
// If module1 is a Browserify package
rj.fromPackage('module1', require('browser-resolve').sync)
    .substitute('module1-dependencyA', 'anotherPackage');

Browserify transform

If you are packing files to browser side by Browserify,

let Injector = require('require-injector').default;
let rj = new Injector({noNode: true});
rj.fromPackage('...')
...
var browserify = require('browserify');
var b = browserify();
b.transform(rj.transform, {global: true});

It uses acorn JavaScript language parser to parse each JS file and replace line of require("matchedModule"). Set noNode to true to disable injection on NodeJS modules, only work as a replacer.

Browserify from command line

browserify entry.js --global-transform
 [ require-injector/transform --inject inject.js ]

inject.js is where you initialize require-injector settings

var rj = require('require-injector').getInstance({noNode: true});

rj.fromDir('folderA')
	.value('moduleX', 'anotherPackage')
	.factory('moduleY', function() {return something;})
	.substitute('moduleZ', 'anotherPackage');

rj.fromPackage('moduleB')
	...

Webpack loader

Requires Webpack version above 2.0

The whole module is a Webpack loader, you can use it in webpack.config.js. Consider it as more advanced solution for Webpack resolve.alias option.

e.g. You want to resolve jquery to zepto only for a part of source code.

Also we provide a css-loader to replace "import" syntax:

  • @import "npm://<package>/path"
  • @import "~<package>/path"
  • LESS like @import (reference | ...) syntax But only work for .substitute(), .alias() to replace dependency, and replaceCode(<package>, '') to delete dependency.
var browserInjector = rj({noNode: true});
browserInjector.fromDir('src/mobile').substitute('jquery', 'zepto');
module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [{loader: 'require-injector', options: {injector:  browserInjector}],
        parser: {
          amd: false
          // Currently we only support CommonJS style, so you need to disable `amd` mode for safe if you don't use AMD.
        }
      },
	  {
		  test: /\.css$/,
		  use: [
			  ...
			  {loader: 'require-injector/css-loader', options: {
								injector: browserInjector
			  }}
		  ]
	  }
  }
};

Webpack-like split loading module replacement: require.ensure()

.substitute() works for call expression like require.ensure()
Injection setup in gulp script for Webpack like below,

rj({noNode: true, basedir: __dirname});
rj.fromDir('dir1').substitute('replaceMe', 'module1');

In browser side file

require.ensure(['replaceMe', 'module2'], function() {
	....
})

will be replaced to

require.ensure(['module1', 'module2'], function() {
	....
})

Replacement

You can write your own replacement function for Browserify or Webpack, just call .injectToFile(),

var fs = require('fs');
var code = fs.readFileSync(filePath, 'utf8');
var replacedCode = rj.injectToFile(filePath, code);
fs.writeFileSync(filePath, replacedCode);

Solution for NodeJS and browser environment

  • For NodeJS, the injector kidnaps Node's native API Module.prototype.require(), so that each require() call goes to injector's control, it returns injecting value according to callee file's id (file path).

  • For browsers, if you are packing your code by any tool like Browsersify and Webpack, this module plays a role of tranform, loader or replacer, parsing JS code and replacing require() expression with stringified injecting value.

You can even create 2 instances, one for Node injection, another for browser side replacement, so they don't interfere each other.

// Node injection
var rj = require('require-injector');
var rjNode = rj();
rjNode.fromPackage('feature1')
	.substitute('dependency1', 'module1');

var rjReplace = rj({noNode: true});
rjReplace.fromPackage('feature2')
	.substitute('dependency2', 'module2');

ES6 Import syntax and import async syntax

Since v3.0.0, it also supports replacing import syntax to work with Webpack2, you no longer need to use babel to translate import syntax to require() before require-injector kicks in, which is also for enabling Webpack tree-shaking function.

import defaultMember from "module-name";
import * as name from "module-name";
import { member } from "module-name";
import { member as alias } from "module-name";
import { member1 , member2 } from "module-name";
import { member1 , member2 as alias2 , [...] } from "module-name";
import defaultMember, { member [ , [...] ] } from "module-name";
import defaultMember, * as name from "module-name";
import "module-name";

Even asyc import syntax import("module-name")

Support as Webapck loader to replace Typescript file

You can either put this loader either behide or before babel loader, it can recoganize

Injection for server side Swig template

We also extend injection function to resource type other than Javascript, if you are using server side Swig template engine, this injector can work with swig-package-tmpl-loader injection

Injector API

require('require-injector')( {object} opts )

Must call this function at as beginning as possible of your entry script. It kidnaps Node's native API Module.prototype.require(), so every require() call actually goes to its management.

Parameters

opts: optional, global options:

  • opts.basedir: {string} default is process.cwd(), used to resolve relative path in .fromDir(path)
  • opts.resolve: {function(id)}, default isreolve.sync, you may also use Node API require.resolve or browser-resolve.sync
  • opts.resolveOpts: {object} set a global resolve options which is for .fromPackage(path, opts)
  • noNode: {boolean} default is false, if you only use it as a replacer like Browserify's transform or Webpack's loader, you don't want injection work on NodeJS side, no kidnapping on Module.prototype.require, just set this property to true. And this will turn default opts.resolve to require('browser-resolve').sync.
  • opts.debug: {boolean} if true, log4js will be enabled to print out logs

fromPackage( {string|array} nodePackageName, {basedir: string} opts)

Adding one or multiple packages to injection setting, all files under this package's directory will be injectable. This function calls .fromDir() internally.

Parameters
  • nodePackageName: Node package's name or array of multiple package names

  • opts: optional, {basedir} default is current directory process.cwd(), lookup package from basedir/node_modules/<pkg>, basedir/../node_modules/<pkg>, ...,

Underneath, it uses resolve to locate package's root directory, which mean it could not only be a Node package, but also a Browser side package which has a "browser" property instead of "main" property in package.json, you may use browserResolve.sync instead of resolve.

returns chainable FactoryMap

fromDir( {string|array} directory)

Adding one or multiple directories to injection setting, all files under this directory will be injectable.

The internal directory list are sorted and can be binary searched when Module.prototype.require() is called against each file. So the performance of dynamic injection should be not bad

Parameters
  • directory: if this is a relative path, you must call requireInjector({basedir: rootDir}) to tell a base directory returns chainable FactoryMap

transform(filePath)

A Browserify JS file transform function to replace require() expression with proper injection.

// add to Browserify as a transform
browserify.transform(rj.transform, {global: true});

returns through-stream

injectToFile({string} filePath, {string} code, {object} ast)

Here "inject" is actually "replacement". Parsing a matched file to Esprima AST tree, looking for matched require(module) expression and replacing them with proper values, expression.

Parameters
  • filePath: file path
  • code: content of file
  • ast: optional, if you have already parsed code toesrima AST tree with {range: true} option, pass it to this function which helps to speed up process by skip parsing again.

returns replaced source code, if there is no injectable require(), same source code will be returned.

cleanup()

Remove all packages and directories set by .fromDir() and .fromPackage(), also release Module.prototype.require(), injection will stop working.

Events

require-injector extends Node Events module.

"inject" event

Emitted when a Node module is matched to be injected with something.

rj.on('inject', moduleId => {});

"replace" event

Emitted when injectToFile is called on injector.

rj.on('replace', (moduleName: string, replacement: string) => {});

"ast" event

In replacement mode, requir-injector use Acorn or Typescript engine to parse JS/JSX, TS/TSX file into AST object, if you want to reuse this AST object, add listerner to this event

FactoryMap API

substitute({string|RegExp} requiredModule, {string|function} newModule)

Or alias(requiredModule, newModule)

Replacing a required module with requiring another module.

Also support npm://package reference in Swig template tags include and import, check this out swig-package-tmpl-loader injection

It works very like Webpack's resolve.alias, it also matches module name which is consist of node package name and specific path

e.g. When injector is configured as

rj.fromDir('.').alias('moduleA', 'moduleB');

Then the file contains require('moduleA/foo/bar.js') will be replaced with require('moduleB/foo/bar.js')

Parameters
  • requiredModule: the original module name which is required for, it can't be relative file path, only supports absolute path, a package name or Regular Expression.

    Package name like lodash/throttle also works, as long as it can be resolved to same absolute path all the time.

  • newModule: the new module name that is replaced with.
    If newModule is a function, it will be passed in 2 parameters: sourceFilePath and regexpExecResult, and must return string value of replaced module name.

returns chainable FactoryMap

factory({string|RegExp} requiredModule, {function} factory)

Replacing a required module with a function returned value.

Not work for require.ensure()

Parameters
  • requiredModule: the original module name which is required for, it can't be a relative file path.

  • factory: A function invoked with 1 argument: sourceFilePath and returns a value which then will replace the original module of requiredModule.

    Note: In browser side replacement mode, it replaces entire require('requiredModule') expression in source code with Immediately-Invoked Function Expression (IIFE) of the factory function.toString():

    // require('requiredModule'); ->
    '(' + factory.toString() + ')(sourceFilePath, regexpExecResult)';

    In replacement mode, parameter sourceFilePath will be null by default, since this would expose original source file path of your file system, if you still want to obtain sourceFilePath, set option .enableFactoryParamFile to true

    The factory eventually stands in source code, not NodeJS runtime. Thus you can not have any reference to any closure variable in factory function.

returns chainable FactoryMap

replaceCode({string|RegExp} moduleName, {string | function} jsCode)

Arbitrary JS code replacement

Only work in replacement mode, not NodeJs side

import Injector from 'require-injector';
var rjReplace = new Injector({noNode: true});
rjReplace.fromPackage([packageA...])
	.replaceCode('foobar', JSON.stringify({foo: 'bar'}));

In which "var foobar = require('foobar');" is replaced with:

var  foobar = {"foo": "bar"};

value({string|RegExp} requiredModule, {*|function} value)

Replacing a required module with any object or primitive value.

Not work for require.ensure()

Parameters
  • requiredModule: the original module name which is required for, it can't be a relative file path.

  • value: the value to replace requiredModule exports.

    When .injectToFile() is called or .transform is used for Browserify, meaning it is not a Node environment, the solution is actually replacing entire require('requiredModule')‘’ expression with result of JSON.stringify(value).

    Sometimes, the value is variable reference, you wouldn't want JSON.stringify for it, you can use an object expression:

    • {string} value.replacement: The replaced string literal as variable expression, same as what .replaceCode() does.
    • {object} value.value: Node require injection value
    rj.fromDir('dir1')
    .value('replaceMe', {
    	replacement: 'window.jQuery', // for Browserify transform
    	value: cheerio   // for Node require() injection
    })

    If value is a function, it will be passed in 2 parameters: sourceFilePath and regexpExecResult, and must return some value.

returns chainable FactoryMap

swigTemplateDir({string} packageName, {string} dir)

Replace npm://package reference in Swig template tags include and import, check this out swig-package-tmpl-loader injection


Now you can require some cool fake module name in your code, and inject/replace them with the real package or value later.

All have been covered by tests.

Anyone willing to contribute for Webpack loader will be very welcome.