require-rewrite

1.0.2 • Public • Published

require-rewrite

Customized module management with include paths, aliases and rewrites.

About

When you work with node.js, you probably know these ugly critters:

const { stringify, printf } = require('../../../../lib/tools/stringtools');
const Model = require('../../../model');
...

That's ugly, it hurts the eye, and it's impossible to maintain. Moving such a file, or worse, a whole folder, can really spoil your day. That's where require-rewrite comes into play.

There are solutions for this, hacky ones, and really clever ones, that do a great job. So why add another one? Well, I believe my solution has a few advantages.

  • require-rewrite is fully package-aware, means, you define your module paths and aliases on a per-package level, and they stay completely separated.
  • You can setup global definitions that are valid for all modules. This allows e.g. quickly switching out an installed package against a development version in some other folder.
  • You can use regular expressions.
  • You can use your own resolver functions.
  • It gives you fine control over the includes, you can add your own module paths before or after the default node module paths (or both).

Usage

npm install require-rewrite

As the first thing in you application, do:

require('require-rewrite')(__dirname);
// or, if you need the API:
// const requireRewrite = require('require-rewrite')(__dirname);

That will initialize require-rewrite for the package the requiring file is part of.

Configuration

The preferred way of using require-rewrite is through a config file, most likely your package.json. Add a section:

"requireRewrite"{
  "map": [],
  "include": []
}

Where:

map contains aliases and regular expressions (optional).
include contains includes (optional).

Aliases

The easiest way to get an alias is for a folder below the location of your package.json:

"requireRewrite"{
  "map": [
    "src/"
  ]
}

Since relative paths are resolved against the location of the config file (package.json here), this maps src to ./src in your project folder.

So when you do a require('src/a'), you will get ./src/a.

To alias a different location, use an array instead of a string:

"requireRewrite"{
  "map": [
    ["src/", "dst/"],
  ]
}

The array contains the [from, to]-values for an alias, so in this case, src/a will resolve to ./dst/a.

Aliases will be evaluated in reversed insertion order, means, last one first. This is, so when you add aliases via the API after the configuration was already read from a config file, those added later take precedence.

Note: It's a good idea to append a / to each from and to, since otherwise a from of lib would match lib/file.js as well as libsomething/file.js, which is probably not what you want.

There can be a third argument in the map-entry: type, which can be alias (default, if not provided) or match for a regular expression match.

Regular expression matches

"requireRewrite"{
  "map": [
    ["^lib/([^/]+)/([^/]+)/(.*)", "lib/$2/$1/$3", "match"],
  ]
}

rewrites lib/lll/ggg/... to lib/ggg/lll/..., lib/fff/zzz/... to lib/zzz/fff/... etc.

Includes

"requireRewrite"{
  "include": [
    "lib", "%", "src"
  ]
}

This adds lib to the list of paths to search for modules, before the default node module paths, and src after the defaults.

Note the '%', which marks the default paths. If you omit that, all paths will be added before the default paths.

Package awareness

Being package-aware means, require-rewrite considers each module part of the package identified by the first package.json (or config file, see below) it finds, when traversing from the module's location up to the root folder.

It will then use the settings from that config file, if any exists, for resolving requires made from that module.

Package awareness has some advantages over simply checking if a module resists in a folder node_modules. First you might have reasons to put some of your files in a node_modules-folder, and second it reflects the way how node-applications are logically structured. You are using packages, not folders.

Both your application and the packages you have installed will be able to use require-rewrite, and each package will have its own, isolated context.

Config files

The following files are considered config-files:

require-rewrite.json
.require-rewrite.json
package.json

They are searched in that order, means, an existing require-rewrite.json takes precedence over .require-rewrite.json, which takes precedence over package.json.

Although you don't have to put your configuration in package.json, it is higly recommended, for the following reason:

If you, or any of your fellow colleagues, need a local configuration that differs from what is in package.json, they can create a local require-rewrite.json, and add that to .gitignore. The local configuration will be used, and the repository stays clean.

But wait - there is more!

Since a configuration file is considered to mark a package-root, you can define separate contexts even for different folders inside your project. So you can e.g. have lib resolved to a different location for your folders frontend and backend:

─ project-root
  ├─ frontend
  │  ├─ lib
  │  ├─ ...
  │  └─ .require-rewrite.json <- resolves 'lib' to src/frontend/lib
  └─ backend
     ├─ lib
     ├─ ...
     └─ .require-rewrite.json <- resolves 'lib' to src/backend/lib

The frontend and backend folders are treated as if they were separate packages and will have their own context, defined via the .require-rewrite.json files.

API

What you get, when you require require-rewrite, is a context for the package your file is part of (that's why you have to pass __dirname). Such a context looks like this:

Context

use(from[, to = from][, type = 'alias'])

The threee arguments are exactly what you find in your config file for each entry:

"requireRewrite"{
  "map": [
    ["src/", "dst/", "alias"],
  ]
}

To do the same via the API, just call:

requireRewrite.use('src/', 'dst/');

And obviously for the one-argument-version:

requireRewrite.use('src/');

Besides that, you can specify the type, which can be alias for a simple string alias, or match for a regular expression match/replace.

And there is a third way to call it, with a function:

const myResolver = (request, parent) => {
  // Resolve request and return a rewritten request
  // which can then be resolved by node.
  // If the request couldn't be resolved,
  // return a falsy value.
};
 
requireRewrite.use(myResolver);

This allows you to completely mess up your application by defining a dynamic resolver, that resolves to different locations based on your application state (or maybe your NODE_ENV).

Remember that resolver are called in reverse order, means, last one added is first one called. So you effectively overwrite existing resolvers for a certian request.

pre, post

Gives you access to the array with the includes before / after default includes. You can manipulate the array directly with array functions like splice().

Note that the paths there, unless absolute, are resolved against the current workdirectory. They are passed as-is to the module resolving process.

Include paths read from configuration files are already resolved against the config file location.

// add some include path
requireRewrite.pre.push(Path.join(__dirname, '..', 'otherlib'));

global

Gives you access to the global context. Whatever you set there is valid for all packages.

Dependents (0)

Package Sidebar

Install

npm i require-rewrite

Weekly Downloads

1

Version

1.0.2

License

MIT

Last publish

Collaborators

  • iunknown68