Skip to content
aMarCruz edited this page Dec 28, 2018 · 4 revisions
[whitespace]<prefix>#<keyword>[<space>[<expresion>]][//[comment]]

[whitespace] - Zero or more spaces or tabs are allowed before the start of the directive.

<prefix> - Usually the start of a comment, see more in Starting a directive.

#<keyword> - One of the jscc keywords.

<space> - One or more spaces or tabs. Only required if the line has any text following the keyword.

<expression> - Ignored for else and endif, required for others keywords.

[//[comment]] - The '//' starts a jscc comment, anything from there is discarded.

jscc directives must start in its own line and cannot spawn multiple lines, the end of line signals the end of the directive.

The prefix can be configured.

Starting a directive

You open a jscc directive with a configurable sequence of characters (prefixes) after the first optional spaces and before the sign #.

The predefined prefixes are ['//', '/*', '<!--'], which work fine with highlighters of html or JavaScript-like code (including C#), but you can use them in another file types as long as jscc is run before any compiler.

The directive ends at the end of the line (Unix, Mac, or Windows).

Once evaluated, the line containing the directive is removed.

Example

//#set _DEBUG = process.env.NODE_ENV !== 'production'

//#if _DEBUG
console.log('Debug mode on.')
//#else
console.log('Debug mode off.')
//#endif

The directive of the first line defines the _DEBUG memvar using an expression.

The following directive checks the value of _DEBUG and, if it is falsy, removes the following line, if not, the line between the #else and the #endif is removed.

Multiline Comments

You can use multi-line comments, but jscc is language agnostic and does not know about that, so it is necessary to close the multiline comment through a jscc comment, as in this JS fragment:

/*#if _FOO //*/

...or in this equivalent HTML:

<!--#if _FOO //-->

I know this is verbose, but avoids having complex rules in jscc.

Variable Names (varnames)

Use '_' followed by one digit or uppercase letter, followed by zero or more underscores, digits or uppercase letters.

Good: '_DEBUG', '_1', '_TOP_A'

Bad: '__DEBUG', '_$1', 'TOP_A'

Predefined varnames

There's two predefined varnames that you can use like any other:

_FILE

This is the filename of the file being processed, relative to the current working directory, with path separators normalized to '/'.

_VERSION

You can set a string value for this varname through the option.values passed to jscc. If not, it will take the value of the version property in the first package.json file that is in the current working directory or in a higher level directory.

Variable Substitution

To replace varnames in the processed code, prefix the varnames with '$'.

The replacement uses the current (raw) value of the variable.

Think about '$' as a required "paste" token which allows you join jscc values in your code:

//#set _BAR = 'bar'
//#set _BAZ = 'baz'
let s = 'foo$_BAR$_BAZ';  // s = 'foobarbaz'

To the right, the implicit delimiter is any character except underscore, digits, or letters (i.e. \w):

The replacement skips non-existent varnames and property identifiers, so this does not works:

//#set _BAR = 'bar'
let s = 'foo$_BARbaz'   // not replaced, '_BARbaz' does not exists

Date and RegExp

Date instances are replaced with its JSON representation or 'NaN' if the date is invalid, RegExp instances are replaced with its source property:

//#set _D1 = new Date(Date.UTC(2018,1,1))
//#set _D2 = new Date(NaN)
console.log(new Date('$_D1').toUTCString()) // ⇒ 'Thu, 01 Feb 2018 00:00:00 GMT'
console.log(new Date('$_D2').toUTCString()) // ⇒ 'Inavlid Date'

//#set _R new RegExp(/.*/g)
console.log(/$_R/g)                         // ⇒ '/.*/g'

As you can see, the flags of the regexes are lost in the output.

Objects and Arrays

Objects are handled in a special way.

  • If you follow the object's name with one or more properties, chained by dots, they will be replaced with the value of the last property.
  • The replacement stops with the first primitive value found in the property chain (including undefined).
  • If you omit the properties, or the last property is an object, its ouput is the same as the JSON.stringify method:
//#set _OBJ = { foo: { bar: 'Bar' } }

console.log('$_OBJ')                // ⇒ '{"foo":{"bar":"Bar"}}'
console.log('$_OBJ.foo')            // ⇒ '{"bar":"Bar"}'
console.log('$_OBJ.foo.bar')        // ⇒ 'Bar'
console.log('$_OBJ.foo.bar.baz')    // ⇒ 'Bar.baz'
console.log('$_OBJ.bar')            // ⇒ 'undefined'

For properties to be recognized, its names cannot contain the character '$'.

You must also use dot notation for array elements:

//#set _ARR = ['foo', 'bar']

console.log('$_ARR')                // ⇒ '["foo","bar"]'
console.log('$_ARR.0')              // ⇒ 'foo'
console.log('$_ARR.1')              // ⇒ 'bar'
console.log('$_ARR.2')              // ⇒ 'undefined'
console.log('$_ARR[0]')             // ⇒ '["foo","bar"][0]'

Expressions

Expressions in jscc are simple JavaScript and has similar rules:

  • Expressions are evaluated in the context of the jscc variables (a shallow copy of options.values).
  • Non-defined varnames are replaced with the undefined value.
  • Varnames in quoted strings or in regexes are not evaluated.
  • Expressions are not transpiled, use the native JS supported by your environment.

You don't need prefix the varnames with this. as jscc does it for you. If the varname does not exists in this, it is prefixed with global. (jscc is a NodeJS tool).

Examples:

//#set _FOO = ('foo' + 'bar').toUpperCase()
//#set _BAR = _FOO === 'FOOBAR' ? 'Yes' : 'No'
//#set _BAZ = '_BAR'

console.log('$_BAR')             // ⇒ `yes`
console.log('$_BAZ')             // ⇒ `_BAR`

Unlike the C preprocessor, jscc variables are fully dynamics:

//#set _FOO = 1
//#set _BAR = _FOO + 2

console.log($_BAR)              // ⇒ 3

//#set _FOO = 5

console.log($_FOO)              // ⇒ 5
console.log($_BAR)              // ⇒ 3

Hidden Blocks

This code is ok to jscc, but will generate issues with linters:

//#ifdef _MODULES
export default
//#else
module.exports =
//#endif
function () {
  return 'whatever'
}

jscc has a feature that allows you hide the block:

/*#ifdef _MODULES
export default
//#else */
module.exports =
//#endif
function () {
  return 'whatever'
}

Solved.

How it Works

The above code works because the predefined opening characters includes /* and to jscc it is the same as // (or any defined prefix), but for JavaScript this opens a multi-line comment. It's up the third line where this JS comment closes.

Leaving the JS behavior aside, jscc works as expected, if _MODULES is to trueish, the line with export default is preserved, if not, module.exports = is.

The */ closing the multiline comment, as anything following an #else, is ignored.

From the jscc perspective, both examples are functionally identical.