directr
v2.1.3
Published
directr is a JavaScript directive processor command line tool and library.
Downloads
34
Maintainers
Readme
directr
directr is a JavaScript directive processor command line tool and library.
Install
npm install directr -g
Usage
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.js
Loads your own config with custom file name.
directr --config myconfig.json app.js
Sometimes 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
true
if 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)
})