directr v2.1.3
directr
directr is a JavaScript directive processor command line tool and library.
Install
npm install directr -gUsage
directr [--config config path] [--help] {sourcefiles...}Directives
Directives are special JavaScript comments of the form:
// @@{directiveName} [space separated params...]Example:
// @@myDirective 34 "param2 text"Directive names can contain alphanumeric (a-z0-9), underscore (_),
colon (:) or dollar ($) characters.
Example:
// @@namespace:Directive 34 "param2 text"Directive parameters can be a null, undefined, number, boolean, single or double quoted string, unquoted single word string, an object literal or an array literal, or a global reference.
// @@myDirective null undefined 34.4 false "text with spaces"
// @@otherDirective word String { name: 'object literal', list: [1,2,3] } [ { name: 'array literal' } ]For directives that only accept a single Object or Array literal there is an alternate block-syntax that can be used.
/* @@{directiveName}
Object or Array literal
*/Example:
/* @@myDirective
{
name: 'object literal',
list: [1, 2, 3, 4]
}
*/The following globals are available to be referenced in Object or Array literals or as a plain reference.
- String
- Array
- Boolean
- Date
- Object
- Number
- JSON
- RegExp
When passing an Object or Array literal the following symbols/characters are illegal.
- new
- this
- window
- global
++--- the following characters:
=()
Directive Processors
directr recognizes these comments in your source files and will parse them into objects that look like this.
{
name: '@@directiveName',
params: [param1, param2, ...],
index: 0-based-index,
line: 1-based-number,
position: 1-based-character-position,
file: { name: filePath, text: fileText }
}Each directive encountered can be passed to a processor for processing. You
configure the processors using a JSON file, typically with the name
directiveconfig.json.
{
"directiveProcessors": {
"@@myDirective": "./path/relative/to/directiveconfig.json",
"@@other": "topLevelNodeModule"
}
}directr will load your configuration file from the current directory if not specified on the command line.
Loads directiveconfig.json from the current directory.
directr app.jsLoads your own config with custom file name.
directr --config myconfig.json app.jsSometimes when processing directives you need settings or some options to be
passed to the processor. You do this by setting the context config setting.
{
"directiveProcessors": {
"@@myDirective": {
"module": "./path/relative/to/config",
"context": {
"property1": 45
}
}
}
}Contexts in the config will be merged with the top-level context. The top-level
context always has a project property that is set to the directory containing
the loaded config file or the current working directory if the config was
specified via code.
{
"directiveProcessors": {
"@@myDirective": {
"module": "./path/relative/to/directiveconfig.json",
"context": {
"somePath": "${project}/files/file.js',
"property1": 45
}
}
},
"context": {
"description": "this is the top-level context"
}
}The resulting context object passed to the loaded processor module would be:
{
project: 'path/to/config/dir',
somePath: 'path/to/config/dir/files/file.js',
property1: 45,
description: "this is the top-level context"
}Additionally, any string of the form ${propertyName} will be recursively
expanded to the property's value, using the merged context as the scope.
{
"directiveProcessors": {
"@@myDirective": {
"module": "./path/relative/to/directiveconfig.json",
"context": {
"someFileName": "${webroot}/somedir/out.js"
}
}
},
"context": {
"root": ".."
"webroot": "${root}/wwwroot"
}
}The resulting context object passed to the loaded processor module would be:
{
project: 'path/to/config/dir',
someFileName: "../wwwroot/somedir/out.js",
webroot: "../wwwroot"
root: ".."
}Directive processors are just modules that export a function with the following signature.
function myDirectiveProcessor (directive, context, done)Where directive is the object the directive is parsed into, context is the
merged top-level and processor-level contexts, and done is a standard
error first Node-style callback that should be called when processing is
complete.
// directives/hello.js
module.exports = function helloProcessor (directive, context, done) {
console.log('found @@hello directive in file:', directive.file.name)
done()
}
// directiveconfig.json
{
"directiveProcessors": {
"@@hello": "./directives/hello"
}
}Module Dependencies
Under the hood directr uses the TypeScript
compiler to look for dependencies between your JavaScript and TypeScript
modules. These dependent modules will also have their directives processed. By
using the TypeScript compiler directr can infer dependencies between AMD,
CommanJS and ES6 modules interchangably, as well as be able to handle JSX
syntax. The TypeScript compiler can have options passed to it via the
compilerOptions config setting.
{
"compilerOptions": "./jsconfig.json",
"directiveProcessors": {
"@@hello": "./directives/hello"
}
}The compilerOptions config setting can either be a path relative to the
directiveconfig.json file to a tsconfig.json or jsconfig.json file, or an
object with any of the supported
compiler options.
directr uses the TypeScript compiler just to infer dependencies so no code will be emitted or errors reported. The following compiler options will be overridden to ensure this happens.
- modeulResolution Always set to
"node" - noEmit Always set to
true - allowJs Defaults to
trueif not specified - listFiles Always set to
false - watch Always set to
false - sourceMap Always set to
false - inlineSourceMap Always set to
false - outDir Always set to
null - outFile Always set to
null
NOTE: If you do not want to have TypeScript infer your dependencies then set
the compilerOptions config setting to false.
JSON Schema
If you use an IDE or editor that supports JSON schema files then you can take
advantage of the JSON schema available for the directiveconfig.json config
file. You just need to set $schema in your config file to the schma file.
{
"$schema": "./node_modules/directr/schema/directiveconfig.json",
...
}Now you'll get inline completion and code hints as you type.
Programmatic API
readDirectivesFromText()
readDirectivesFromText(text)
Reads the directives from the specified text string. Returns an array of objects of the form:
{ name, params: [param1, param2] }readDirectivesFromFiles()
readDirectivesFromFiles(fileNames, callback)
Reads the directives from each file specified. Calls callback with an error
as the first argument if an error occurs. Sends an array of objects with the
following form as the second arguemnt to callback on success.
{ name, params: [param1, param2], file: { name, text } }inferDependencies()
inferDependencies(fileNames, compilerOptions, [baseDir])
Infers module dependencies from each specified file name, taking care of
duplicates. Where compilerOptions can either be a path to the TypeScript
compiler config file or an object with compiler options defined. If the
compilerOptions is a string then it is treated as though it were relative
to baseDir. If baseDir is not specified then process.cwd() is its default
value.
processDirectives()
processDirectives(directives, processorMap, context, callback, [baseDir])
Attempts to process all specified directives as returned from one of the
readDirectivesFromXXX functions. Where processorMap is an object that maps
directive names to either a module name, function or an object of the form:
{ module, [context] }; where module is a module name to load or a function
and context is the optional processor-level context to pass to the processor.
context is the top-level context object that will be merged with any
processor-level context before processing a directive. baseDir is the base
directory to resolve relative module names. If baseDir is not specified then
it will default to process.cwd().
Example:
var directr = require('directr')
var files = ['app.js']
var compilerOptions = './jsconfig.json'
var baseDir = process.cwd()
var topLevelContext = {}
var directiveProcessors = {
'@@hello': './directives/hello',
'@@other': function (directive, context, done) {
// TODO: implement
done()
},
'@@something': {
module: function (directive, context, done) {
// TODO: implement
done()
}
}
}
files = directr.inferDependencies(files, compilerOptions, baseDir)
directr.readDirectivesFromFiles(files, function (error, directives) {
if (error) throw error
directr.processDirectives(directives, directiveProcessors, topLevelContext, function (error) {
if (error) throw error
}, baseDir)
})directr()
directr(sourceFiles, configPathOrConfig, callback)
Attempts to process the directives in the specified source files and their
dependencies. Where configPathOrConfig can be a path to the config file
to load or a config object (same properties supported by the config JSON file.)
Example:
var directr = require('directr').directr
directr(['src/app.js'], 'src/directiveconfig.json', function (error) {
if (error) console.error(error.stack)
})