require-packages
v0.0.6
Published
Patch "require" function to apply special rules of modules availability for each other
Downloads
1
Maintainers
Readme
require-package
Restrict access to modules for "require"
The Need
There are different types of application architecture.
The worst of them is so-called "pass-through" architecture. When you just put all you files to several common folders - models/, views/, controllers/, maybe, templates/ and so on - and then mix all of them together. A huge folders, with dozens of files, without any hints about where each of them can be used.
Anything can be require
d from anywhere. Models turns to a scrapyard of very different methods, each used (maybe, just once) in some particular view.
Views, used for a different scenarios, have a multiple entry points and also acquires a lot of methods, not related to each other.
Files, files, files... what do they do?
This is exactly what is called "can't see a forest behind the trees".
All this is unreadable. Unsupported. And becomes only worse while project grows.
And project goes to hell.
Alternatively, there is a "widget-based" (or something like this) architecture. When business-logic is separated by independent "widgets" ("packages", "components", call it as you like). When all views, models and templates, required for particular task, are grouped together, and no external modules uses them.
Unfortunately, this can be done only by conventions inside a team. Because you can always require
any module you want.
And team can change, and new people comes, and someone of them wants just to programming, motherfucker, without all your "architectures" - and we have same problems again.
I'm tired of this. Enough. I want to know that if I have some completed piece of logic in my folder, no one never can interrupt there and use it for something it is not designed for. I want to be sure of it.
So here is a little tool to make modules usage some more controllable.
Disclaimer: it is a "foolproof"-tool. You definitely don't need it if all your team understands what do they do. Otherwise, if you want to apply some safeguards to your code usage - welcome.
So how should it be?
Group business-logic in independent packages.
No package files should be available from outside of package, except of the "main" module, which exposes only really required "public API" to the outer world. Optionally, can be more "public" files, but they should be explicitly listed.
Explicit external dependencies. Always there are external dependencies - utilities, helpers, etc, which are used project-wide. But nothing more should be available from inside package - it should be maximally independent from the outer world.
Of course, packages can be nested. In this case, need a way to control whether children can use parent's modules.
Well, is it already implemented somewhere?
Of course, but only partially.
browserify As it uses same algorithm as in Node.js, there is no need to explain anything.
RequireJS It has a built-in packages support
Both of these tools provides you ability to organize packages in your code, but they don't restrict access to package files.
That's why additional tool required.
Installation and Usage
Common installation case
What this plugin actually does is wrapping global require
function to provide additional logic.
It is agnostic to real project structure and your loader's internal mechanisms - it is guided only by it's own config and incoming module path.
To use it, in common case you should do following:
Load your chosen loader script, so
require
function is available.Load
require-package.js
script.Somewhere in entry point of your application do this:
require.packages.init([
"packages/mySafePackage"
]);
That's it. From this moment, any module located inside packages/mySafePackage
folder is not available from the outer world, except of packages/mySafePackage/index
module.
And all of them can require only modules, located inside this folder - no external dependencies.
Initialization can be done only once, to guarantee that no one overrides initial packages logic somewhere in the depths.
Detailed packages configuration is described below.
Loader-specific installation cases
Brunch
Put require-package.js
in vendor
folder, so it will not be wrapped to a module.
Then you have two options:
- Mark packages config also as vendor file - so it wil have direct access to global
require
function. Probably, you want to do this by extendingconventions.vendor
property in your brunch config.
exports.config =
files:
javascripts:
joinTo:
"app.js": /^app\//
"vendor.js": /^vendor\//
order:
before: [
# the only requirement is an order of these two files
"vendor/require-package.js"
"app/packages.js"
]
conventions:
vendor: [
/(^bower_components|vendor)[\\/]/ # this is brunch defaults
"app/packages.js" # here is our config
]
- Wrap packages config to a regular module to load it manually somewhere in your application.
In this case, remember, that each module uses it's own local
require
function, so packages config should directly use global one:
require.register("app/packages", function(exports, require, module) {
window.require.packages.init([ ... ]);
});
LMD
For LMD there is a separate plugin.
Just install it as it is described in LMD documentation.
Then, put your packages config in a module and load it where you want. There is no need to access "global require" from a module or something like this.
It's not a problem to use also another LMD plugins, which also wraps require
function (like these, for example) - while they care about copying all custom properties to wrapper.
RequireJS
Probably, for basics it should work with RequireJS in the same way as with Brunch.
But, as RequireJS has own packages config, it can be really confusing to have two different configurations in different places.
Also, RequireJS has an advanced paths mapping system,
which can't be processed by require-package
wrapper.
So, using both of these tools can be confusing and inconsistent. So it is not recommended, and I didn't test whether they can really work together.
Browserify
Here all is simple: it is impossible to use require-package
with browserify :)
There are absolutely no ways to interrupt in it's internal loading mechanism.
Though, browserify does not need it - it is very cool itself.
Package options
Each package is an object with following properties:
location
: String|RegExp|Function\Array
===
Where the package is.
Definition can be whatever you want - like anymatch, but without globbing.
If you don't want to customize other package parameters, you can define package as location only. Following are all correct package definitions:
require.packages.init([
{
location: "some/path/to/package"
},
/^packages\//,
[
function(path) { return path.slice(0, 4) === 'pkg_'; }
]
]);
As plugin does not know project structure, it "learns" while you require new modules, by comparing path to required module with location from config.
Algorithm is following:
First are always checked packages with "exact" location - that is, where location is just a string,
They are checked starting from the closest possible package. That is, if you have packages locations
root
,root/subdir
,root/subdir/deepdir
, and you require pathroot/someModule
, than comparing will start fromroot/subdir
- so each module will be assigned to the deepest possible package.If no exact location found, than path is compared with "multipath" packages - where location is RegExp, Function, or array of that things.
Multipath packages are checked in order they listed in config. Be warn with this: for example, if you have two definitions -
JavaScript require.packages.init([ /^packages\//, { location: /^packages\/(first|second)Package/, ... some custom properties here ... } ]);
, and you require module "packages/firstPackage" - it will be recognized as/^packages\//
, which has no custom properties, so it's behavior will be not as you want.If one of multipath packages matches to module path, this path is recorded as new "exact" package.
If nothing matches, path is recorded as "out-of-package".
Of course, all results are cached, so for each particular module search is not performed each time you require
it.
main
: String
Default: "index"
===
Name of the package main file, which will be loaded when you require
entire package directory.
external
: String|RegExp|Function\Array
Default: false
===
List of "out-of-package" modules, allowed to be require
d from inside this package.
By default, package can not require anything external.
public
: String|RegExp|Function\Array
Default: false
===
List of internal package modules, allowed to be require
d from outside of this package (that is, both from "out-of-package" modules and from inside other packages).
By default, nothing is available - only main file can be loaded.
packages
: String|RegExp|Function\Array
Default: false
===
Packages, nested to current one.
Nested packages can have own nested ones, and so on, without limitations.
There is something special about nested packages:
Only direct parent can access them.
public
property implies only on it. For any other module neitherpublic
, normain
file of nested package are not available. It is similar to Node.js algorithm - search is not performed downside by file tree.They can't have
external
property. Only the topmost package in tree can specify external dependencies. For the outer world, topmost package is a single unit (no one knows what it has inside), so only it's own configuration should determine what it depends from.They can't
require
amain
file of any parent package, no matter whether it matches to list of available modules (see the next option). Because parent package represents a logic of top level, where child package is just a one small part.
protected
: String|RegExp|Function\Array
Default: false
===
List of modules, available for packages nested to current one.
By default, nothing is available. It differs from Node.js, but it is done for the same purpose - guarantees, that no one will require
something you don't expect.
It is assumed that not so much parent's modules will be required for children - it should be some basic classes, mostly models/collections, describing data structures, specific for package.
Also, by default, children can require only direct parent's modules. It also can be changed.
===
Important note: for all currently listed options paths are relative to package location.
That is, if you have packages parent
and parent/child
, and want to make module parent/child/myModule
public, you should do it like this:
JavaScript
require.packages.init([
{
location: "parent",
packages: [
{
location: "child"
public: "myModule" // <== relative to "child". Do not specify full path
parent/child/myModule.
}
]
}
]);
===
inheritable
: Object
Default: JavaScript
{
"main": false,
"public": false,
"packages": false,
"protected": false,
"inheritable": true
}
===
What properties of parent's package it's nested packages will inherit.
You may want to create a packages tree where all packages follows same rules - for example, have their public files in "public" subfolder, etc. In this case, it will be annoying to define such rules on each new nesting level. Instead, you may set rules for topmost package in tree, and allow for children to inherit them.
The rules are simple:
- If package has own value for property, it will be used.
- Otherwise, if property is allowed to inherit, it will be taken from direct parent package.
- Otherwise global default value will be used.
As you can see, inheritable
hash contains all of the options above, except of external
- because, as you remember, it is denied for children to have own external dependencies.
Values are just boolean, indicating whether inheritance is allowed.
Also, note, that inheritable
hash contains "inheritable" property itself, with default to true.
This means, that each new child allows for his to children to inherit his properties. You can set it to false and so break the inheritance chain - the next children will use only global defaults.
Global options
You can also change some global plugin options, using require.packages.configure()
method.
Just like init
method, configure
could be called only once, so no one module will suddenly override your settings.
Following options are available:
packageDefaults
: Object
===
Here you can override default value for any option from package options section.
It uses deep extending, so you can selectively override some options from inheritable
hash.
JavaScript
require.packages.configure({
"packageDefaults": {
"main": "main", // use main file like in RequireJS packages
"external": ["utils", /^lib\//], // set allowed externals for all packages (means, only top-level packages, of course)
"inheritable": {
"protected": true // and allow for all children to copy their parent's logic for 'protected' files
}
}
});
allowRemoteProtected
: Boolean
Default: false
===
Whether it is allowed for nested packages to require
protected
modules not only from direct parent package, but from any parent up to the top of three.