-
Notifications
You must be signed in to change notification settings - Fork 9
Syntax
[whitespace]<comment-start>#<keyword>[<whitespace>[<expresion>]][//[jscc-comment]]
[whitespace]
- Only tabs and spaces are allowed before the start of the directive.
<comment-start>
- By default '//'
or '/*'
. Warning: Only the start is significant (see Multiline Comments).
//#<keyword>
- jscc keyword, with no spaces between the pound sign and comment-start
.
<whitespace>
- 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 comment, anything from there is discarded.
Directives must start in its own line and cannot spawn multiple lines, the end of line signals the end of the directive.
The sequence of characters starting jss directives can be configured.
Mustly, you will use simple constructions like this:
//#set _DEBUG = 1 // this is a comment
/*#if _DEBUG // closing multiline comment */
//#if process.env.devmode === 'production'
//#set _DEBUG 0 // the `=` is optional
//#else <- anything after `else` or `endif` will be ignored
/* eslint-disable no-console */
console.log('Debug mode on.')
//#endif
//#endif _DEBUG // here, _DEBUG is ignored as well
You open a jscc directive with a (configurable) sequence of characters after the first optional spaces and before the pound sign (#
) of the directive.
It sequence defaults to ['//', '/*']
, that works fine with highlighters of JavaScript-like code (including C#), but you can use this in any source.
The directive ends at the end of the line (Unix, Mac, or Windows).
Once evaluated, the line containing the directive is removed.
You can use multiline comments, but jscc don't know about that, so it is necessary to set the closing sequence in a jscc comment like this (for JS):
/*#if _FOO //*/
This is too verbose, I know (and do not recommended it), but avoids having complex rules and parsers in jscc. Also, allows do some tricks like hide blocks.
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'
Prefix the varname to be replaced with '$'
.
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, and letters (i.e. \w
):
The replacement skips non-existent varnames and property identifiers, so this does not works:
// not replaced, 'b' is not a delimiter and jscc see `$_BARbaz` here
let s = 'foo$_BARbaz'
Objects are handled in a special way. If you follow the object's name with an existing property, this is replaced with the property value.
If you omit the property, or not exists, the ouput is the same from JSON.stringify
:
//#set _OBJ = { tag: 1 }
let s = _OBJ.tag // s = 1
let o = _OBJ // o = {"tag":1}
let x = _OBJ.foo // x = {"tag":1}.foo
Only root properties (first level) are recognized, and their names cannot contains any '$'
.
Other object, including arrays, are replaced with the JSON.stringify
result.
Expressions in jscc are simple JavaScript and has simple rules:
- Expressions are evaluated in the context of the jscc variables (shallow copy of
options.values
) - Non-defined varnames are replaced with the
undefined
value - Varnames inside quotes or 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 and if a variable is not in this
but exists in the global
object (at compile-time), the later is used.
Examples:
//#set _FOO = ('foo' + 'bar').toUpperCase()
//#set _BAR = _FOO === 'FOOBAR' ? 'Yes' : 'No'
//#set _BAZ = '_BAR'
console.log('$_BAR') // print `yes`
console.log('$_BAZ') // print `_BAR`
Unlike the C preprocessor, jscc variables are fully dynamics:
//#set _FOO = 1
//#set _BAR = 2
//#set _BAZ = _FOO + _BAR
//---> now, _BAZ value is 3
//#set _FOO = /abc/
//---> _FOO was redefined, _BAZ remains 3
let foo = $_BAZ // foo = 3
let baz = $_FOO.test('abc') // baz = /abc/.test('abc')
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 one block:
/*#ifdef _MODULES
export default
//#else // */
module.exports =
//#endif
function () {
return 'whatever'
}
Solved.
How works?
The above code works because the predefined opening sequences of directives is //
and /*
, but the only closing sequences is another //
or the end of line.
So, from the jscc perpective, the first /*
becomes the same as //
and the directive ends with the second //
, the staring of the jscc own comment.
From there, all works as expected, if _MODULES
is to trueish, the line with export default
is included, if not, module.exports =
is.
The */
closing the multiline comment is not seen by jscc.
* The space inside "// */"
is for the preview of my editor (VS Code), jscc does not require it.