@riotjs/compiler
Advanced tools
Comparing version 4.0.0-alpha.9 to 4.0.0-alpha.10
# Compiler Changes | ||
### v4.0.0-alpha.10 | ||
- Update enhance sourcemaps generation | ||
- Change second arguments for the pre/post processors. The `meta` object will contain info about the current compilation | ||
### v4.0.0-alpha.9 | ||
@@ -4,0 +8,0 @@ - Fix move `recast` into the package dependencies |
/* Riot Compiler WIP, @license MIT */ | ||
import recast from 'recast'; | ||
import recastUtil from 'recast/lib/util'; | ||
import { SourceMapGenerator } from 'source-map'; | ||
import recast from 'recast'; | ||
import curry from 'curri'; | ||
@@ -14,2 +14,14 @@ import globalScope from 'globals'; | ||
const types = recast.types; | ||
const builders = types.builders; | ||
const namedTypes = types.namedTypes; | ||
function nullNode() { | ||
return builders.literal(null) | ||
} | ||
function simplePropertyNode(key, value) { | ||
return builders.property('init', builders.literal(key), value, false) | ||
} | ||
/** | ||
@@ -74,15 +86,17 @@ * Detect node js environements | ||
* @param { SourceMapGenerator } data.map - source map generated along with the code | ||
* @param { Object } options - user options, probably containing the path to the source file | ||
* @param { Object } meta - compilation meta infomration | ||
* @returns { Output } output container object | ||
*/ | ||
function createOutput(data, options) { | ||
const output = Object.seal({ | ||
function createOutput(data, meta) { | ||
const output = { | ||
...Output, | ||
...data, | ||
meta: { options } | ||
}); | ||
meta | ||
}; | ||
if (!output.map && options && options.file) Object.assign(output, { | ||
map: createSourcemap({ file: options.file }) | ||
}); | ||
if (!output.map && meta && meta.options && meta.options.file) | ||
return { | ||
...output, | ||
map: createSourcemap({ file: meta.options.file }) | ||
} | ||
@@ -95,9 +109,9 @@ return output | ||
* @param { Function } compiler - function needed to generate the output code | ||
* @param { Object } options - options to pass to the compilert | ||
* @param { Object } meta - compilation meta information | ||
* @param { string } source - source code | ||
* @returns { Promise<Output> } output - the result of the compiler | ||
*/ | ||
async function transform(compiler, options, source) { | ||
const result = await (compiler ? compiler(source, options) : { code: source }); | ||
return createOutput(result, options) | ||
async function transform(compiler, meta, source) { | ||
const result = await (compiler ? compiler(source, meta) : { code: source }); | ||
return createOutput(result, meta) | ||
} | ||
@@ -134,15 +148,15 @@ | ||
* @param { Output } compilerOutput - output generated by the compiler | ||
* @param { Object } options - user options received by the compiler | ||
* @param { Object } meta - compiling meta information | ||
* @returns { Promise<Output> } object containing output code and source map | ||
*/ | ||
async function execute(compilerOutput, options) { | ||
async function execute(compilerOutput, meta) { | ||
return Array.from(postprocessors).reduce(async function(acc, postprocessor) { | ||
const { code, map } = await acc; | ||
const output = await postprocessor(code, options); | ||
const output = await postprocessor(code, meta); | ||
return { | ||
code: output.code, | ||
map: composeSourcemaps(output.map, map) | ||
map: composeSourcemaps(map, output.map) | ||
} | ||
}, Promise.resolve(createOutput(compilerOutput, options))) | ||
}, Promise.resolve(createOutput(compilerOutput, meta))) | ||
} | ||
@@ -194,25 +208,13 @@ | ||
* @param { string } name - unique preprocessor id | ||
* @param { Object } options - preprocessor options | ||
* @param { Object } meta - preprocessor meta information | ||
* @param { string } source - source code | ||
* @returns { Promise<Output> } object containing a sourcemap and a code string | ||
*/ | ||
async function execute$1(type, name, options, source) { | ||
async function execute$1(type, name, meta, source) { | ||
if (!preprocessors[type]) preprocessorTypeError(type); | ||
if (!preprocessors[type].has(name)) preprocessorNameNotFoundError(name); | ||
return await transform(preprocessors[type].get(name), options, source) | ||
return await transform(preprocessors[type].get(name), meta, source) | ||
} | ||
/** | ||
* Parse a js source to generate the AST | ||
* @param {string} source - javascript source | ||
* @param {Object} options - parser options | ||
* @returns {AST} AST tree | ||
*/ | ||
function generateAST(source, options) { | ||
return recast.parse(source, { | ||
...options | ||
}) | ||
} | ||
const ATTRIBUTE_TYPE_NAME = 'type'; | ||
@@ -244,42 +246,6 @@ | ||
/** | ||
* Count spaces before first character of a string | ||
* @param { string } string - target string | ||
* @returns { number } amount of spaces before the first char | ||
*/ | ||
function getColum(string) { | ||
const spacesAmount = string.search(/\S/); | ||
return spacesAmount > 0 ? spacesAmount : 0 | ||
} | ||
const LINES_RE = /\r\n?|\n/g; | ||
/** | ||
* Split a string into a rows array generated from its EOL matches | ||
* @param { string } string [description] | ||
* @returns { Array } array containing all the string rows | ||
*/ | ||
function splitStringByEOL(string) { | ||
return string.split(LINES_RE) | ||
} | ||
/** | ||
* Get the line and the column of a source text based on its position in the string | ||
* @param { string } string - target string | ||
* @param { number } position - target position | ||
* @returns { Object } object containing the source text line and column | ||
*/ | ||
function getLineAndColumnByPosition(string, position) { | ||
const lines = splitStringByEOL(string.slice(0, position)); | ||
return { | ||
line: lines.length, | ||
column: getColum(lines[lines.length - 1]) | ||
} | ||
} | ||
/** | ||
* Preprocess a riot parser node | ||
* @param { string } preprocessorType - either css, js | ||
* @param { string } preprocessorName - preprocessor id | ||
* @param { Object } options - options that will be passed to the compiler | ||
* @param { Object } meta - compilation meta information | ||
* @param { string } source - tag source code | ||
@@ -289,9 +255,7 @@ * @param { RiotParser.nodeTypes } node - css node detected by the parser | ||
*/ | ||
async function preprocess(preprocessorType, preprocessorName, options, source, node) { | ||
const { column } = getLineAndColumnByPosition(source, node.start); | ||
const offsetTop = '\n'.repeat(column); | ||
const code = `${offsetTop}\n${node.text}`; | ||
async function preprocess(preprocessorType, preprocessorName, meta, source, node) { | ||
const code = node.text; | ||
return await (preprocessorName ? | ||
execute$1(preprocessorType, preprocessorName, options, code) : | ||
execute$1(preprocessorType, preprocessorName, meta, code) : | ||
{ code } | ||
@@ -301,6 +265,2 @@ ) | ||
const types = recast.types; | ||
const builders = types.builders; | ||
const namedTypes = types.namedTypes; | ||
/** | ||
@@ -370,23 +330,24 @@ * Source for creating regexes matching valid quoted, single-line JavaScript strings. | ||
* @param { string } source - original component source code | ||
* @param { Object } options - user options | ||
* @param { Object } meta - compilation meta information | ||
* @param { AST } ast - current AST output | ||
* @returns { AST } the AST generated | ||
*/ | ||
async function css(sourceNode, source, options, ast) { | ||
async function css(sourceNode, source, meta, ast) { | ||
const preprocessorName = getPreprocessorTypeByAttribute(sourceNode); | ||
const { options } = meta; | ||
const cssNode = sourceNode.text; | ||
const preprocessorOutput = await preprocess('css', preprocessorName, options, source, cssNode); | ||
const preprocessorOutput = await preprocess('css', preprocessorName, meta, source, cssNode); | ||
const cssCode = (options.scopedCss ? | ||
scopedCSS(options.tagName, preprocessorOutput.code) : | ||
scopedCSS(meta.tagName, preprocessorOutput.code) : | ||
preprocessorOutput.code | ||
).trim(); | ||
const generatedCss = generateAST(`\`${cssCode}\``, { | ||
sourceFileName: options.file, | ||
inputSourceMap: preprocessorOutput.map | ||
}); | ||
types.visit(ast, { | ||
visitProperty(path) { | ||
if (path.value.key.name === TAG_CSS_PROPERTY) { | ||
path.value.value = generatedCss.program.body[0].expression; | ||
if (path.value.key.value === TAG_CSS_PROPERTY) { | ||
path.value.value = builders.templateLiteral( | ||
[builders.templateElement({ raw: cssCode, cooked: '' }, false)], | ||
[] | ||
); | ||
return false | ||
@@ -402,25 +363,52 @@ } | ||
const LINES_RE = /\r\n?|\n/g; | ||
/** | ||
* Create a raw sourcemap for a single riot parser node | ||
* @param {RiotParser.Node} node - riot parser node | ||
* @param {string} sourceFile - component source file | ||
* @param {string} sourceCode - original source code | ||
* @returns {SourceMapGenerator} source map generated | ||
* Split a string into a rows array generated from its EOL matches | ||
* @param { string } string [description] | ||
* @returns { Array } array containing all the string rows | ||
*/ | ||
function createNodeSourcemap(node, sourceFile, sourceCode) { | ||
const sourcemap = createSourcemap({ file: sourceFile }) | ||
function splitStringByEOL(string) { | ||
return string.split(LINES_RE) | ||
} | ||
;[node.start, node.end].forEach(position => { | ||
const location = getLineAndColumnByPosition(sourceCode, position); | ||
/** | ||
* Get the line and the column of a source text based on its position in the string | ||
* @param { string } string - target string | ||
* @param { number } position - target position | ||
* @returns { Object } object containing the source text line and column | ||
*/ | ||
function getLineAndColumnByPosition(string, position) { | ||
const lines = splitStringByEOL(string.slice(0, position)); | ||
sourcemap.addMapping({ | ||
source: sourceFile, | ||
generated: location, | ||
original: location | ||
}); | ||
}); | ||
return { | ||
line: lines.length, | ||
column: lines[lines.length - 1].length | ||
} | ||
} | ||
return sourcemap | ||
/** | ||
* Add the offset to the code that must be parsed in order to generate properly the sourcemaps | ||
* @param {string} input - input string | ||
* @param {string} source - original source code | ||
* @param {RiotParser.Node} node - node that we are going to transform | ||
* @return {string} the input string with the offset properly set | ||
*/ | ||
function addLineOffset(input, source, node) { | ||
const {column, line} = getLineAndColumnByPosition(source, node.start); | ||
return `${'\n'.repeat(line - 1)}${' '.repeat(column + 1)}${input}` | ||
} | ||
/** | ||
* Parse a js source to generate the AST | ||
* @param {string} source - javascript source | ||
* @param {Object} options - parser options | ||
* @returns {AST} AST tree | ||
*/ | ||
function generateAST(source, options) { | ||
return recast.parse(source, { | ||
...options | ||
}) | ||
} | ||
const browserAPIs = Object.keys(globalScope.browser); | ||
@@ -466,3 +454,3 @@ const builtinAPIs = Object.keys(globalScope.builtin); | ||
function getProgramBody(ast) { | ||
return ast.program.body | ||
return ast.body || ast.program.body | ||
} | ||
@@ -479,3 +467,3 @@ | ||
visitProperty(path) { | ||
if (path.value.key.name === TAG_LOGIC_PROPERTY) { | ||
if (path.value.key.value === TAG_LOGIC_PROPERTY) { | ||
path.value.value = exportDefaultNode.declaration; | ||
@@ -496,22 +484,21 @@ return false | ||
* @param { string } source - original component source code | ||
* @param { Object } options - user options | ||
* @param { Object } meta - compilation meta information | ||
* @param { AST } ast - current AST output | ||
* @returns { AST } the AST generated | ||
*/ | ||
async function javascript(sourceNode, source, options, ast) { | ||
async function javascript(sourceNode, source, meta, ast) { | ||
const preprocessorName = getPreprocessorTypeByAttribute(sourceNode); | ||
const javascriptNode = sourceNode.text; | ||
const preprocessorOutput = await preprocess('js', preprocessorName, options, source, javascriptNode); | ||
const jsInputSourceMap = composeSourcemaps( | ||
createNodeSourcemap(sourceNode, options.file, source), | ||
preprocessorOutput.map | ||
); | ||
const generatedAst = generateAST(preprocessorOutput.code, { | ||
sourceFileName: options.file, | ||
inputSourceMap: jsInputSourceMap | ||
}); | ||
const { options } = meta; | ||
const preprocessorOutput = await preprocess('js', preprocessorName, meta, source, javascriptNode); | ||
const generatedAst = generateAST( | ||
addLineOffset(preprocessorOutput.code, source, sourceNode), { | ||
sourceFileName: options.file | ||
}); | ||
const generatedAstBody = getProgramBody(generatedAst); | ||
const bodyWithoutExportDefault = filterNonExportDefaultStatements(generatedAstBody); | ||
const exportDefaultNode = findExportDefaultStatement(generatedAstBody); | ||
const outputBody = getProgramBody(ast); | ||
@@ -581,10 +568,2 @@ | ||
function nullNode() { | ||
return builders.literal(null) | ||
} | ||
function simplePropertyNode(key, value) { | ||
return builders.property('init', builders.literal(key), value, false) | ||
} | ||
/* eslint-disable */ | ||
@@ -594,2 +573,22 @@ // source: https://30secondsofcode.org/function#compose | ||
/** | ||
* Generate the pure immutable string chunks from a RiotParser.Node.Text | ||
* @param {RiotParser.Node.Text} node - riot parser text node | ||
* @param {string} sourceCode sourceCode - source code | ||
* @returns {Array} array containing the immutable string chunks | ||
*/ | ||
function generateLiteralStringChunksFromNode(node, sourceCode) { | ||
return node.expressions.reduce((chunks, expression, index) => { | ||
const start = index ? node.expressions[index - 1].end : node.start; | ||
chunks.push(sourceCode.substring(start, expression.start)); | ||
// add the tail to the string | ||
if (index === node.expressions.length - 1) | ||
chunks.push(sourceCode.substring(expression.end, node.end)); | ||
return chunks | ||
}, []) | ||
} | ||
const scope = builders.identifier(SCOPE); | ||
@@ -724,5 +723,8 @@ const getName = node => node && node.name ? node.name : node; | ||
function createASTFromExpression(expression, sourceFile, sourceCode) { | ||
return generateAST(`(${expression.text})`, { | ||
sourceFileName: sourceFile, | ||
inputSourceMap: sourceFile && createNodeSourcemap(expression, sourceFile, sourceCode) | ||
const code = sourceFile ? | ||
addLineOffset(expression.text, sourceCode, expression) : | ||
expression.text; | ||
return generateAST(`(${code})`, { | ||
sourceFileName: sourceFile | ||
}) | ||
@@ -748,3 +750,2 @@ } | ||
if (!isExpressionStatement(firstNode)) { | ||
@@ -805,2 +806,17 @@ panic(`The each directives supported should be of type "ExpressionStatement",you have provided a "${firstNode.type}"`); | ||
/** | ||
* Wrap the ast generated in a function call providing the scope argument | ||
* @param {Object} ast - function body | ||
* @returns {FunctionExpresion} function having the scope argument injected | ||
*/ | ||
function wrapASTInFunctionWithScope(ast) { | ||
return builders.functionExpression( | ||
null, | ||
[scope], | ||
builders.blockStatement([builders.returnStatement( | ||
ast | ||
)]) | ||
) | ||
} | ||
/** | ||
* Convert any parser option to a valid template one | ||
@@ -819,23 +835,25 @@ * @param { RiotParser.Node.Expression } expression - expression parsed by the riot parser | ||
function toScopedFunction(expression, sourceFile, sourceCode) { | ||
const ast = createASTFromExpression(expression, sourceFile, sourceCode); | ||
const generatedAST = updateNodesScope(ast); | ||
const astBody = generatedAST.program.body; | ||
const expressionAST = astBody[0] ? astBody[0].expression : astBody; | ||
return compose( | ||
wrapASTInFunctionWithScope, | ||
transformExpression, | ||
)(expression, sourceFile, sourceCode) | ||
} | ||
return builders.functionExpression( | ||
null, | ||
[scope], | ||
builders.blockStatement([builders.returnStatement( | ||
expressionAST | ||
)]) | ||
) | ||
function transformExpression(expression, sourceFile, sourceCode) { | ||
return compose( | ||
getExpressionAST, | ||
updateNodesScope, | ||
createASTFromExpression | ||
)(expression, sourceFile, sourceCode) | ||
} | ||
/** | ||
* Wrap a string in a template literal expression | ||
* @param {string} string - target string | ||
* @returns {string} a template literal | ||
* Get the parsed AST expression of riot expression node | ||
* @param {AST.Program} sourceAST - raw node parsed | ||
* @returns {AST.Expression} program expression output | ||
*/ | ||
function wrapInBacktick(string) { | ||
return `\`${string}\`` | ||
function getExpressionAST(sourceAST) { | ||
const astBody = sourceAST.program.body; | ||
return astBody[0] ? astBody[0].expression : astBody | ||
} | ||
@@ -847,22 +865,16 @@ | ||
* @param {RiotParser.Node} node - riot parser node | ||
* @returns {string} either a string representing a template literal or simply the first expression matched | ||
* @param {string} sourceFile - original tag file | ||
* @param {string} sourceCode - original tag source code | ||
* @returns { Object } a FunctionExpression object | ||
*/ | ||
function mergeNodeExpressions(node) { | ||
if (node.expressions.length === 1) { | ||
return node.expressions[0].text | ||
} | ||
function mergeNodeExpressions(node, sourceFile, sourceCode) { | ||
if (node.expressions.length === 1) | ||
return transformExpression(node.expressions[0], sourceFile, sourceCode) | ||
const charAddedProIteration = 3; // ${} | ||
const pureStringChunks = generateLiteralStringChunksFromNode(node, sourceCode); | ||
// a tricky way to merge nodes containing multiple expressions in the same string | ||
const values = node.expressions.reduce((string, expression, index) => { | ||
const bracketsLength = expression.end - expression.start - expression.text.length; | ||
const offset = index * (charAddedProIteration - bracketsLength); | ||
const expressionStart = expression.start - node.start + offset; | ||
const expressionEnd = expression.end - node.start + offset; | ||
return `${string.substring(0, expressionStart)}$\{${expression.text}}${string.substring(expressionEnd)}` | ||
}, node.text); | ||
return wrapInBacktick(values) | ||
return builders.templateLiteral( | ||
pureStringChunks.map(str => builders.templateElement({ raw: str, cooked: '' }, false)), | ||
node.expressions.map(expression => transformExpression(expression, sourceFile, sourceCode)) | ||
) | ||
} | ||
@@ -1337,3 +1349,3 @@ | ||
] : | ||
build(createRootNode(sourceNode), sourceCode, sourceCode) | ||
build(createRootNode(sourceNode), sourceFile, sourceCode) | ||
), | ||
@@ -1379,3 +1391,3 @@ ...createSelectorProperties(selectorAttribute), | ||
] : | ||
build(createRootNode(sourceNode), sourceCode, sourceCode) | ||
build(createRootNode(sourceNode), sourceFile, sourceCode) | ||
) | ||
@@ -1432,7 +1444,5 @@ ]) | ||
BINDING_EVALUATE_KEY, | ||
toScopedFunction({ | ||
start: sourceNode.start, | ||
end: sourceNode.end, | ||
text: mergeNodeExpressions(sourceNode) | ||
}, sourceFile, sourceCode) | ||
wrapASTInFunctionWithScope( | ||
mergeNodeExpressions(sourceNode, sourceFile, sourceCode) | ||
) | ||
) | ||
@@ -1662,3 +1672,3 @@ ]) | ||
visitProperty(path) { | ||
if (path.value.key.name === TAG_TEMPLATE_PROPERTY) { | ||
if (path.value.key.value === TAG_TEMPLATE_PROPERTY) { | ||
path.value.value = builders.functionExpression( | ||
@@ -1699,7 +1709,8 @@ null, | ||
* @param { string } source - original component source code | ||
* @param { Object } options - user options | ||
* @param { Object } meta - compilation meta information | ||
* @param { AST } ast - current AST output | ||
* @returns { AST } the AST generated | ||
*/ | ||
async function template(sourceNode, source, options, ast) { | ||
async function template(sourceNode, source, meta, ast) { | ||
const { options } = meta; | ||
return extendTemplateProperty(ast, options.file, source, sourceNode) | ||
@@ -1716,4 +1727,2 @@ } | ||
* Create the initial AST | ||
* @param { Sourcemap } map - initial sourcemap | ||
* @param { string } file - path to the original source file | ||
* @returns { AST } the initial AST | ||
@@ -1724,8 +1733,20 @@ * | ||
*/ | ||
function createInitialInput(map, file) { | ||
const code = `export default { ${TAG_CSS_PROPERTY}: null, ${TAG_LOGIC_PROPERTY}: null, ${TAG_TEMPLATE_PROPERTY}: null }`; | ||
return generateAST(code, { | ||
sourceFileName: file, | ||
inputSourceMap: map | ||
}) | ||
function createInitialInput() { | ||
/* | ||
generates | ||
export default { | ||
${TAG_CSS_PROPERTY}: null, | ||
${TAG_LOGIC_PROPERTY}: null, | ||
${TAG_TEMPLATE_PROPERTY}: null | ||
} | ||
*/ | ||
return builders.program([ | ||
builders.exportDefaultDeclaration( | ||
builders.objectExpression([ | ||
simplePropertyNode(TAG_CSS_PROPERTY, nullNode()), | ||
simplePropertyNode(TAG_LOGIC_PROPERTY, nullNode()), | ||
simplePropertyNode(TAG_TEMPLATE_PROPERTY, nullNode()) | ||
]) | ||
)] | ||
) | ||
} | ||
@@ -1744,7 +1765,12 @@ | ||
}; | ||
const meta = { | ||
source, | ||
options: opts | ||
}; | ||
const { code, map } = await execute$1('template', opts.template, opts, source); | ||
const { code, map } = await execute$1('template', opts.template, meta, source); | ||
const { template: template$$1, css: css$$1, javascript: javascript$$1 } = riotParser(opts).parse(code).output; | ||
const meta = { | ||
options: opts, | ||
// extend the meta object with the result of the parsing | ||
Object.assign(meta, { | ||
tagName: template$$1.name, | ||
@@ -1756,13 +1782,14 @@ fragments: { | ||
} | ||
}; | ||
}); | ||
return ruit( | ||
createInitialInput(map), | ||
hookGenerator(css, css$$1, code, opts), | ||
hookGenerator(javascript, javascript$$1, code, opts), | ||
hookGenerator(template, template$$1, code, opts), | ||
createInitialInput(), | ||
hookGenerator(css, css$$1, code, meta), | ||
hookGenerator(javascript, javascript$$1, code, meta), | ||
hookGenerator(template, template$$1, code, meta), | ||
ast => recast.print(ast, { | ||
sourceMapName: 'map.json' | ||
sourceMapName: 'map.json', | ||
inputSourcemap: map | ||
}), | ||
result => execute(result, opts), | ||
result => execute(result, meta), | ||
result => ({ | ||
@@ -1780,6 +1807,6 @@ ...result, | ||
* @param { string } source - component source code | ||
* @param { Object } options - compiling options | ||
* @param { Object } meta - compilation meta information | ||
* @returns { Promise<Output> } object containing output code and source map | ||
*/ | ||
function hookGenerator(transformer, sourceNode, source, options) { | ||
function hookGenerator(transformer, sourceNode, source, meta) { | ||
if (!sourceNode || (sourceNode.nodes && !sourceNode.nodes.length)) { | ||
@@ -1789,3 +1816,3 @@ return result => result | ||
return curry(transformer)(sourceNode, source, options) | ||
return curry(transformer)(sourceNode, source, meta) | ||
} | ||
@@ -1792,0 +1819,0 @@ |
@@ -8,5 +8,5 @@ /* Riot Compiler WIP, @license MIT */ | ||
var recast = _interopDefault(require('recast')); | ||
var recastUtil = _interopDefault(require('recast/lib/util')); | ||
var sourceMap = require('source-map'); | ||
var recast = _interopDefault(require('recast')); | ||
var curry = _interopDefault(require('curri')); | ||
@@ -22,2 +22,14 @@ var globalScope = _interopDefault(require('globals')); | ||
const types = recast.types; | ||
const builders = types.builders; | ||
const namedTypes = types.namedTypes; | ||
function nullNode() { | ||
return builders.literal(null) | ||
} | ||
function simplePropertyNode(key, value) { | ||
return builders.property('init', builders.literal(key), value, false) | ||
} | ||
/** | ||
@@ -82,15 +94,17 @@ * Detect node js environements | ||
* @param { SourceMapGenerator } data.map - source map generated along with the code | ||
* @param { Object } options - user options, probably containing the path to the source file | ||
* @param { Object } meta - compilation meta infomration | ||
* @returns { Output } output container object | ||
*/ | ||
function createOutput(data, options) { | ||
const output = Object.seal({ | ||
function createOutput(data, meta) { | ||
const output = { | ||
...Output, | ||
...data, | ||
meta: { options } | ||
}); | ||
meta | ||
}; | ||
if (!output.map && options && options.file) Object.assign(output, { | ||
map: createSourcemap({ file: options.file }) | ||
}); | ||
if (!output.map && meta && meta.options && meta.options.file) | ||
return { | ||
...output, | ||
map: createSourcemap({ file: meta.options.file }) | ||
} | ||
@@ -103,9 +117,9 @@ return output | ||
* @param { Function } compiler - function needed to generate the output code | ||
* @param { Object } options - options to pass to the compilert | ||
* @param { Object } meta - compilation meta information | ||
* @param { string } source - source code | ||
* @returns { Promise<Output> } output - the result of the compiler | ||
*/ | ||
async function transform(compiler, options, source) { | ||
const result = await (compiler ? compiler(source, options) : { code: source }); | ||
return createOutput(result, options) | ||
async function transform(compiler, meta, source) { | ||
const result = await (compiler ? compiler(source, meta) : { code: source }); | ||
return createOutput(result, meta) | ||
} | ||
@@ -142,15 +156,15 @@ | ||
* @param { Output } compilerOutput - output generated by the compiler | ||
* @param { Object } options - user options received by the compiler | ||
* @param { Object } meta - compiling meta information | ||
* @returns { Promise<Output> } object containing output code and source map | ||
*/ | ||
async function execute(compilerOutput, options) { | ||
async function execute(compilerOutput, meta) { | ||
return Array.from(postprocessors).reduce(async function(acc, postprocessor) { | ||
const { code, map } = await acc; | ||
const output = await postprocessor(code, options); | ||
const output = await postprocessor(code, meta); | ||
return { | ||
code: output.code, | ||
map: composeSourcemaps(output.map, map) | ||
map: composeSourcemaps(map, output.map) | ||
} | ||
}, Promise.resolve(createOutput(compilerOutput, options))) | ||
}, Promise.resolve(createOutput(compilerOutput, meta))) | ||
} | ||
@@ -202,25 +216,13 @@ | ||
* @param { string } name - unique preprocessor id | ||
* @param { Object } options - preprocessor options | ||
* @param { Object } meta - preprocessor meta information | ||
* @param { string } source - source code | ||
* @returns { Promise<Output> } object containing a sourcemap and a code string | ||
*/ | ||
async function execute$1(type, name, options, source) { | ||
async function execute$1(type, name, meta, source) { | ||
if (!preprocessors[type]) preprocessorTypeError(type); | ||
if (!preprocessors[type].has(name)) preprocessorNameNotFoundError(name); | ||
return await transform(preprocessors[type].get(name), options, source) | ||
return await transform(preprocessors[type].get(name), meta, source) | ||
} | ||
/** | ||
* Parse a js source to generate the AST | ||
* @param {string} source - javascript source | ||
* @param {Object} options - parser options | ||
* @returns {AST} AST tree | ||
*/ | ||
function generateAST(source, options) { | ||
return recast.parse(source, { | ||
...options | ||
}) | ||
} | ||
const ATTRIBUTE_TYPE_NAME = 'type'; | ||
@@ -252,42 +254,6 @@ | ||
/** | ||
* Count spaces before first character of a string | ||
* @param { string } string - target string | ||
* @returns { number } amount of spaces before the first char | ||
*/ | ||
function getColum(string) { | ||
const spacesAmount = string.search(/\S/); | ||
return spacesAmount > 0 ? spacesAmount : 0 | ||
} | ||
const LINES_RE = /\r\n?|\n/g; | ||
/** | ||
* Split a string into a rows array generated from its EOL matches | ||
* @param { string } string [description] | ||
* @returns { Array } array containing all the string rows | ||
*/ | ||
function splitStringByEOL(string) { | ||
return string.split(LINES_RE) | ||
} | ||
/** | ||
* Get the line and the column of a source text based on its position in the string | ||
* @param { string } string - target string | ||
* @param { number } position - target position | ||
* @returns { Object } object containing the source text line and column | ||
*/ | ||
function getLineAndColumnByPosition(string, position) { | ||
const lines = splitStringByEOL(string.slice(0, position)); | ||
return { | ||
line: lines.length, | ||
column: getColum(lines[lines.length - 1]) | ||
} | ||
} | ||
/** | ||
* Preprocess a riot parser node | ||
* @param { string } preprocessorType - either css, js | ||
* @param { string } preprocessorName - preprocessor id | ||
* @param { Object } options - options that will be passed to the compiler | ||
* @param { Object } meta - compilation meta information | ||
* @param { string } source - tag source code | ||
@@ -297,9 +263,7 @@ * @param { RiotParser.nodeTypes } node - css node detected by the parser | ||
*/ | ||
async function preprocess(preprocessorType, preprocessorName, options, source, node) { | ||
const { column } = getLineAndColumnByPosition(source, node.start); | ||
const offsetTop = '\n'.repeat(column); | ||
const code = `${offsetTop}\n${node.text}`; | ||
async function preprocess(preprocessorType, preprocessorName, meta, source, node) { | ||
const code = node.text; | ||
return await (preprocessorName ? | ||
execute$1(preprocessorType, preprocessorName, options, code) : | ||
execute$1(preprocessorType, preprocessorName, meta, code) : | ||
{ code } | ||
@@ -309,6 +273,2 @@ ) | ||
const types = recast.types; | ||
const builders = types.builders; | ||
const namedTypes = types.namedTypes; | ||
/** | ||
@@ -378,23 +338,24 @@ * Source for creating regexes matching valid quoted, single-line JavaScript strings. | ||
* @param { string } source - original component source code | ||
* @param { Object } options - user options | ||
* @param { Object } meta - compilation meta information | ||
* @param { AST } ast - current AST output | ||
* @returns { AST } the AST generated | ||
*/ | ||
async function css(sourceNode, source, options, ast) { | ||
async function css(sourceNode, source, meta, ast) { | ||
const preprocessorName = getPreprocessorTypeByAttribute(sourceNode); | ||
const { options } = meta; | ||
const cssNode = sourceNode.text; | ||
const preprocessorOutput = await preprocess('css', preprocessorName, options, source, cssNode); | ||
const preprocessorOutput = await preprocess('css', preprocessorName, meta, source, cssNode); | ||
const cssCode = (options.scopedCss ? | ||
scopedCSS(options.tagName, preprocessorOutput.code) : | ||
scopedCSS(meta.tagName, preprocessorOutput.code) : | ||
preprocessorOutput.code | ||
).trim(); | ||
const generatedCss = generateAST(`\`${cssCode}\``, { | ||
sourceFileName: options.file, | ||
inputSourceMap: preprocessorOutput.map | ||
}); | ||
types.visit(ast, { | ||
visitProperty(path) { | ||
if (path.value.key.name === TAG_CSS_PROPERTY) { | ||
path.value.value = generatedCss.program.body[0].expression; | ||
if (path.value.key.value === TAG_CSS_PROPERTY) { | ||
path.value.value = builders.templateLiteral( | ||
[builders.templateElement({ raw: cssCode, cooked: '' }, false)], | ||
[] | ||
); | ||
return false | ||
@@ -410,25 +371,52 @@ } | ||
const LINES_RE = /\r\n?|\n/g; | ||
/** | ||
* Create a raw sourcemap for a single riot parser node | ||
* @param {RiotParser.Node} node - riot parser node | ||
* @param {string} sourceFile - component source file | ||
* @param {string} sourceCode - original source code | ||
* @returns {SourceMapGenerator} source map generated | ||
* Split a string into a rows array generated from its EOL matches | ||
* @param { string } string [description] | ||
* @returns { Array } array containing all the string rows | ||
*/ | ||
function createNodeSourcemap(node, sourceFile, sourceCode) { | ||
const sourcemap = createSourcemap({ file: sourceFile }) | ||
function splitStringByEOL(string) { | ||
return string.split(LINES_RE) | ||
} | ||
;[node.start, node.end].forEach(position => { | ||
const location = getLineAndColumnByPosition(sourceCode, position); | ||
/** | ||
* Get the line and the column of a source text based on its position in the string | ||
* @param { string } string - target string | ||
* @param { number } position - target position | ||
* @returns { Object } object containing the source text line and column | ||
*/ | ||
function getLineAndColumnByPosition(string, position) { | ||
const lines = splitStringByEOL(string.slice(0, position)); | ||
sourcemap.addMapping({ | ||
source: sourceFile, | ||
generated: location, | ||
original: location | ||
}); | ||
}); | ||
return { | ||
line: lines.length, | ||
column: lines[lines.length - 1].length | ||
} | ||
} | ||
return sourcemap | ||
/** | ||
* Add the offset to the code that must be parsed in order to generate properly the sourcemaps | ||
* @param {string} input - input string | ||
* @param {string} source - original source code | ||
* @param {RiotParser.Node} node - node that we are going to transform | ||
* @return {string} the input string with the offset properly set | ||
*/ | ||
function addLineOffset(input, source, node) { | ||
const {column, line} = getLineAndColumnByPosition(source, node.start); | ||
return `${'\n'.repeat(line - 1)}${' '.repeat(column + 1)}${input}` | ||
} | ||
/** | ||
* Parse a js source to generate the AST | ||
* @param {string} source - javascript source | ||
* @param {Object} options - parser options | ||
* @returns {AST} AST tree | ||
*/ | ||
function generateAST(source, options) { | ||
return recast.parse(source, { | ||
...options | ||
}) | ||
} | ||
const browserAPIs = Object.keys(globalScope.browser); | ||
@@ -474,3 +462,3 @@ const builtinAPIs = Object.keys(globalScope.builtin); | ||
function getProgramBody(ast) { | ||
return ast.program.body | ||
return ast.body || ast.program.body | ||
} | ||
@@ -487,3 +475,3 @@ | ||
visitProperty(path) { | ||
if (path.value.key.name === TAG_LOGIC_PROPERTY) { | ||
if (path.value.key.value === TAG_LOGIC_PROPERTY) { | ||
path.value.value = exportDefaultNode.declaration; | ||
@@ -504,22 +492,21 @@ return false | ||
* @param { string } source - original component source code | ||
* @param { Object } options - user options | ||
* @param { Object } meta - compilation meta information | ||
* @param { AST } ast - current AST output | ||
* @returns { AST } the AST generated | ||
*/ | ||
async function javascript(sourceNode, source, options, ast) { | ||
async function javascript(sourceNode, source, meta, ast) { | ||
const preprocessorName = getPreprocessorTypeByAttribute(sourceNode); | ||
const javascriptNode = sourceNode.text; | ||
const preprocessorOutput = await preprocess('js', preprocessorName, options, source, javascriptNode); | ||
const jsInputSourceMap = composeSourcemaps( | ||
createNodeSourcemap(sourceNode, options.file, source), | ||
preprocessorOutput.map | ||
); | ||
const generatedAst = generateAST(preprocessorOutput.code, { | ||
sourceFileName: options.file, | ||
inputSourceMap: jsInputSourceMap | ||
}); | ||
const { options } = meta; | ||
const preprocessorOutput = await preprocess('js', preprocessorName, meta, source, javascriptNode); | ||
const generatedAst = generateAST( | ||
addLineOffset(preprocessorOutput.code, source, sourceNode), { | ||
sourceFileName: options.file | ||
}); | ||
const generatedAstBody = getProgramBody(generatedAst); | ||
const bodyWithoutExportDefault = filterNonExportDefaultStatements(generatedAstBody); | ||
const exportDefaultNode = findExportDefaultStatement(generatedAstBody); | ||
const outputBody = getProgramBody(ast); | ||
@@ -589,10 +576,2 @@ | ||
function nullNode() { | ||
return builders.literal(null) | ||
} | ||
function simplePropertyNode(key, value) { | ||
return builders.property('init', builders.literal(key), value, false) | ||
} | ||
/* eslint-disable */ | ||
@@ -602,2 +581,22 @@ // source: https://30secondsofcode.org/function#compose | ||
/** | ||
* Generate the pure immutable string chunks from a RiotParser.Node.Text | ||
* @param {RiotParser.Node.Text} node - riot parser text node | ||
* @param {string} sourceCode sourceCode - source code | ||
* @returns {Array} array containing the immutable string chunks | ||
*/ | ||
function generateLiteralStringChunksFromNode(node, sourceCode) { | ||
return node.expressions.reduce((chunks, expression, index) => { | ||
const start = index ? node.expressions[index - 1].end : node.start; | ||
chunks.push(sourceCode.substring(start, expression.start)); | ||
// add the tail to the string | ||
if (index === node.expressions.length - 1) | ||
chunks.push(sourceCode.substring(expression.end, node.end)); | ||
return chunks | ||
}, []) | ||
} | ||
const scope = builders.identifier(SCOPE); | ||
@@ -732,5 +731,8 @@ const getName = node => node && node.name ? node.name : node; | ||
function createASTFromExpression(expression, sourceFile, sourceCode) { | ||
return generateAST(`(${expression.text})`, { | ||
sourceFileName: sourceFile, | ||
inputSourceMap: sourceFile && createNodeSourcemap(expression, sourceFile, sourceCode) | ||
const code = sourceFile ? | ||
addLineOffset(expression.text, sourceCode, expression) : | ||
expression.text; | ||
return generateAST(`(${code})`, { | ||
sourceFileName: sourceFile | ||
}) | ||
@@ -756,3 +758,2 @@ } | ||
if (!isExpressionStatement(firstNode)) { | ||
@@ -813,2 +814,17 @@ panic(`The each directives supported should be of type "ExpressionStatement",you have provided a "${firstNode.type}"`); | ||
/** | ||
* Wrap the ast generated in a function call providing the scope argument | ||
* @param {Object} ast - function body | ||
* @returns {FunctionExpresion} function having the scope argument injected | ||
*/ | ||
function wrapASTInFunctionWithScope(ast) { | ||
return builders.functionExpression( | ||
null, | ||
[scope], | ||
builders.blockStatement([builders.returnStatement( | ||
ast | ||
)]) | ||
) | ||
} | ||
/** | ||
* Convert any parser option to a valid template one | ||
@@ -827,23 +843,25 @@ * @param { RiotParser.Node.Expression } expression - expression parsed by the riot parser | ||
function toScopedFunction(expression, sourceFile, sourceCode) { | ||
const ast = createASTFromExpression(expression, sourceFile, sourceCode); | ||
const generatedAST = updateNodesScope(ast); | ||
const astBody = generatedAST.program.body; | ||
const expressionAST = astBody[0] ? astBody[0].expression : astBody; | ||
return compose( | ||
wrapASTInFunctionWithScope, | ||
transformExpression, | ||
)(expression, sourceFile, sourceCode) | ||
} | ||
return builders.functionExpression( | ||
null, | ||
[scope], | ||
builders.blockStatement([builders.returnStatement( | ||
expressionAST | ||
)]) | ||
) | ||
function transformExpression(expression, sourceFile, sourceCode) { | ||
return compose( | ||
getExpressionAST, | ||
updateNodesScope, | ||
createASTFromExpression | ||
)(expression, sourceFile, sourceCode) | ||
} | ||
/** | ||
* Wrap a string in a template literal expression | ||
* @param {string} string - target string | ||
* @returns {string} a template literal | ||
* Get the parsed AST expression of riot expression node | ||
* @param {AST.Program} sourceAST - raw node parsed | ||
* @returns {AST.Expression} program expression output | ||
*/ | ||
function wrapInBacktick(string) { | ||
return `\`${string}\`` | ||
function getExpressionAST(sourceAST) { | ||
const astBody = sourceAST.program.body; | ||
return astBody[0] ? astBody[0].expression : astBody | ||
} | ||
@@ -855,22 +873,16 @@ | ||
* @param {RiotParser.Node} node - riot parser node | ||
* @returns {string} either a string representing a template literal or simply the first expression matched | ||
* @param {string} sourceFile - original tag file | ||
* @param {string} sourceCode - original tag source code | ||
* @returns { Object } a FunctionExpression object | ||
*/ | ||
function mergeNodeExpressions(node) { | ||
if (node.expressions.length === 1) { | ||
return node.expressions[0].text | ||
} | ||
function mergeNodeExpressions(node, sourceFile, sourceCode) { | ||
if (node.expressions.length === 1) | ||
return transformExpression(node.expressions[0], sourceFile, sourceCode) | ||
const charAddedProIteration = 3; // ${} | ||
const pureStringChunks = generateLiteralStringChunksFromNode(node, sourceCode); | ||
// a tricky way to merge nodes containing multiple expressions in the same string | ||
const values = node.expressions.reduce((string, expression, index) => { | ||
const bracketsLength = expression.end - expression.start - expression.text.length; | ||
const offset = index * (charAddedProIteration - bracketsLength); | ||
const expressionStart = expression.start - node.start + offset; | ||
const expressionEnd = expression.end - node.start + offset; | ||
return `${string.substring(0, expressionStart)}$\{${expression.text}}${string.substring(expressionEnd)}` | ||
}, node.text); | ||
return wrapInBacktick(values) | ||
return builders.templateLiteral( | ||
pureStringChunks.map(str => builders.templateElement({ raw: str, cooked: '' }, false)), | ||
node.expressions.map(expression => transformExpression(expression, sourceFile, sourceCode)) | ||
) | ||
} | ||
@@ -1345,3 +1357,3 @@ | ||
] : | ||
build(createRootNode(sourceNode), sourceCode, sourceCode) | ||
build(createRootNode(sourceNode), sourceFile, sourceCode) | ||
), | ||
@@ -1387,3 +1399,3 @@ ...createSelectorProperties(selectorAttribute), | ||
] : | ||
build(createRootNode(sourceNode), sourceCode, sourceCode) | ||
build(createRootNode(sourceNode), sourceFile, sourceCode) | ||
) | ||
@@ -1440,7 +1452,5 @@ ]) | ||
BINDING_EVALUATE_KEY, | ||
toScopedFunction({ | ||
start: sourceNode.start, | ||
end: sourceNode.end, | ||
text: mergeNodeExpressions(sourceNode) | ||
}, sourceFile, sourceCode) | ||
wrapASTInFunctionWithScope( | ||
mergeNodeExpressions(sourceNode, sourceFile, sourceCode) | ||
) | ||
) | ||
@@ -1670,3 +1680,3 @@ ]) | ||
visitProperty(path) { | ||
if (path.value.key.name === TAG_TEMPLATE_PROPERTY) { | ||
if (path.value.key.value === TAG_TEMPLATE_PROPERTY) { | ||
path.value.value = builders.functionExpression( | ||
@@ -1707,7 +1717,8 @@ null, | ||
* @param { string } source - original component source code | ||
* @param { Object } options - user options | ||
* @param { Object } meta - compilation meta information | ||
* @param { AST } ast - current AST output | ||
* @returns { AST } the AST generated | ||
*/ | ||
async function template(sourceNode, source, options, ast) { | ||
async function template(sourceNode, source, meta, ast) { | ||
const { options } = meta; | ||
return extendTemplateProperty(ast, options.file, source, sourceNode) | ||
@@ -1724,4 +1735,2 @@ } | ||
* Create the initial AST | ||
* @param { Sourcemap } map - initial sourcemap | ||
* @param { string } file - path to the original source file | ||
* @returns { AST } the initial AST | ||
@@ -1732,8 +1741,20 @@ * | ||
*/ | ||
function createInitialInput(map, file) { | ||
const code = `export default { ${TAG_CSS_PROPERTY}: null, ${TAG_LOGIC_PROPERTY}: null, ${TAG_TEMPLATE_PROPERTY}: null }`; | ||
return generateAST(code, { | ||
sourceFileName: file, | ||
inputSourceMap: map | ||
}) | ||
function createInitialInput() { | ||
/* | ||
generates | ||
export default { | ||
${TAG_CSS_PROPERTY}: null, | ||
${TAG_LOGIC_PROPERTY}: null, | ||
${TAG_TEMPLATE_PROPERTY}: null | ||
} | ||
*/ | ||
return builders.program([ | ||
builders.exportDefaultDeclaration( | ||
builders.objectExpression([ | ||
simplePropertyNode(TAG_CSS_PROPERTY, nullNode()), | ||
simplePropertyNode(TAG_LOGIC_PROPERTY, nullNode()), | ||
simplePropertyNode(TAG_TEMPLATE_PROPERTY, nullNode()) | ||
]) | ||
)] | ||
) | ||
} | ||
@@ -1752,7 +1773,12 @@ | ||
}; | ||
const meta = { | ||
source, | ||
options: opts | ||
}; | ||
const { code, map } = await execute$1('template', opts.template, opts, source); | ||
const { code, map } = await execute$1('template', opts.template, meta, source); | ||
const { template: template$$1, css: css$$1, javascript: javascript$$1 } = riotParser__default(opts).parse(code).output; | ||
const meta = { | ||
options: opts, | ||
// extend the meta object with the result of the parsing | ||
Object.assign(meta, { | ||
tagName: template$$1.name, | ||
@@ -1764,13 +1790,14 @@ fragments: { | ||
} | ||
}; | ||
}); | ||
return ruit( | ||
createInitialInput(map), | ||
hookGenerator(css, css$$1, code, opts), | ||
hookGenerator(javascript, javascript$$1, code, opts), | ||
hookGenerator(template, template$$1, code, opts), | ||
createInitialInput(), | ||
hookGenerator(css, css$$1, code, meta), | ||
hookGenerator(javascript, javascript$$1, code, meta), | ||
hookGenerator(template, template$$1, code, meta), | ||
ast => recast.print(ast, { | ||
sourceMapName: 'map.json' | ||
sourceMapName: 'map.json', | ||
inputSourcemap: map | ||
}), | ||
result => execute(result, opts), | ||
result => execute(result, meta), | ||
result => ({ | ||
@@ -1788,6 +1815,6 @@ ...result, | ||
* @param { string } source - component source code | ||
* @param { Object } options - compiling options | ||
* @param { Object } meta - compilation meta information | ||
* @returns { Promise<Output> } object containing output code and source map | ||
*/ | ||
function hookGenerator(transformer, sourceNode, source, options) { | ||
function hookGenerator(transformer, sourceNode, source, meta) { | ||
if (!sourceNode || (sourceNode.nodes && !sourceNode.nodes.length)) { | ||
@@ -1797,3 +1824,3 @@ return result => result | ||
return curry(transformer)(sourceNode, source, options) | ||
return curry(transformer)(sourceNode, source, meta) | ||
} | ||
@@ -1800,0 +1827,0 @@ |
{ | ||
"name": "@riotjs/compiler", | ||
"version": "4.0.0-alpha.9", | ||
"version": "4.0.0-alpha.10", | ||
"description": "Compiler for riot .tag files", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -32,3 +32,4 @@ [![Build Status][travis-image]][travis-url] | ||
// process your tag template before it will be compiled | ||
registerPreprocessor('template', 'pug', async function(code, { file }) { | ||
registerPreprocessor('template', 'pug', async function(code, { meta }) { | ||
const { file } = meta | ||
console.log('your file path is:', file) | ||
@@ -39,3 +40,4 @@ return await pug.compile(code) | ||
// your compiler output will pass from here | ||
registerPostprocessor(async function(code, { file }) { | ||
registerPostprocessor(async function(code, { meta }) { | ||
const { file } = meta | ||
console.log('your file path is:', file) | ||
@@ -42,0 +44,0 @@ return await buble.transform(code) |
@@ -0,6 +1,5 @@ | ||
import {builders, types} from '../../utils/build-types' | ||
import {TAG_CSS_PROPERTY} from '../constants' | ||
import generateAST from '../../utils/generate-ast' | ||
import getPreprocessorTypeByAttribute from '../../utils/get-preprocessor-type-by-attribute' | ||
import preprocess from '../../utils/preprocess-node' | ||
import {types} from '../../utils/build-types' | ||
@@ -71,23 +70,24 @@ /** | ||
* @param { string } source - original component source code | ||
* @param { Object } options - user options | ||
* @param { Object } meta - compilation meta information | ||
* @param { AST } ast - current AST output | ||
* @returns { AST } the AST generated | ||
*/ | ||
export default async function css(sourceNode, source, options, ast) { | ||
export default async function css(sourceNode, source, meta, ast) { | ||
const preprocessorName = getPreprocessorTypeByAttribute(sourceNode) | ||
const { options } = meta | ||
const cssNode = sourceNode.text | ||
const preprocessorOutput = await preprocess('css', preprocessorName, options, source, cssNode) | ||
const preprocessorOutput = await preprocess('css', preprocessorName, meta, source, cssNode) | ||
const cssCode = (options.scopedCss ? | ||
scopedCSS(options.tagName, preprocessorOutput.code) : | ||
scopedCSS(meta.tagName, preprocessorOutput.code) : | ||
preprocessorOutput.code | ||
).trim() | ||
const generatedCss = generateAST(`\`${cssCode}\``, { | ||
sourceFileName: options.file, | ||
inputSourceMap: preprocessorOutput.map | ||
}) | ||
types.visit(ast, { | ||
visitProperty(path) { | ||
if (path.value.key.name === TAG_CSS_PROPERTY) { | ||
path.value.value = generatedCss.program.body[0].expression | ||
if (path.value.key.value === TAG_CSS_PROPERTY) { | ||
path.value.value = builders.templateLiteral( | ||
[builders.templateElement({ raw: cssCode, cooked: '' }, false)], | ||
[] | ||
) | ||
return false | ||
@@ -94,0 +94,0 @@ } |
import {TAG_LOGIC_PROPERTY} from '../constants' | ||
import composeSourcemaps from '../../utils/compose-sourcemaps' | ||
import createNodeSourcemap from '../../utils/create-node-sourcemap' | ||
import addLinesOffset from '../../utils/add-lines-offset' | ||
import generateAST from '../../utils/generate-ast' | ||
@@ -34,3 +33,3 @@ import getPreprocessorTypeByAttribute from '../../utils/get-preprocessor-type-by-attribute' | ||
function getProgramBody(ast) { | ||
return ast.program.body | ||
return ast.body || ast.program.body | ||
} | ||
@@ -47,3 +46,3 @@ | ||
visitProperty(path) { | ||
if (path.value.key.name === TAG_LOGIC_PROPERTY) { | ||
if (path.value.key.value === TAG_LOGIC_PROPERTY) { | ||
path.value.value = exportDefaultNode.declaration | ||
@@ -64,22 +63,21 @@ return false | ||
* @param { string } source - original component source code | ||
* @param { Object } options - user options | ||
* @param { Object } meta - compilation meta information | ||
* @param { AST } ast - current AST output | ||
* @returns { AST } the AST generated | ||
*/ | ||
export default async function javascript(sourceNode, source, options, ast) { | ||
export default async function javascript(sourceNode, source, meta, ast) { | ||
const preprocessorName = getPreprocessorTypeByAttribute(sourceNode) | ||
const javascriptNode = sourceNode.text | ||
const preprocessorOutput = await preprocess('js', preprocessorName, options, source, javascriptNode) | ||
const jsInputSourceMap = composeSourcemaps( | ||
createNodeSourcemap(sourceNode, options.file, source), | ||
preprocessorOutput.map | ||
) | ||
const generatedAst = generateAST(preprocessorOutput.code, { | ||
sourceFileName: options.file, | ||
inputSourceMap: jsInputSourceMap | ||
}) | ||
const { options } = meta | ||
const preprocessorOutput = await preprocess('js', preprocessorName, meta, source, javascriptNode) | ||
const generatedAst = generateAST( | ||
addLinesOffset(preprocessorOutput.code, source, sourceNode), { | ||
sourceFileName: options.file | ||
}) | ||
const generatedAstBody = getProgramBody(generatedAst) | ||
const bodyWithoutExportDefault = filterNonExportDefaultStatements(generatedAstBody) | ||
const exportDefaultNode = findExportDefaultStatement(generatedAstBody) | ||
const outputBody = getProgramBody(ast) | ||
@@ -86,0 +84,0 @@ |
@@ -65,3 +65,3 @@ import { | ||
] : | ||
build(createRootNode(sourceNode), sourceCode, sourceCode) | ||
build(createRootNode(sourceNode), sourceFile, sourceCode) | ||
), | ||
@@ -68,0 +68,0 @@ ...createSelectorProperties(selectorAttribute), |
@@ -56,5 +56,5 @@ import { | ||
] : | ||
build(createRootNode(sourceNode), sourceCode, sourceCode) | ||
build(createRootNode(sourceNode), sourceFile, sourceCode) | ||
) | ||
]) | ||
} |
@@ -8,6 +8,3 @@ import { | ||
} from '../constants' | ||
import { | ||
mergeNodeExpressions, | ||
toScopedFunction | ||
} from '../utils' | ||
import {mergeNodeExpressions,wrapASTInFunctionWithScope} from '../utils' | ||
import {builders} from '../../../utils/build-types' | ||
@@ -39,9 +36,7 @@ import {simplePropertyNode} from '../../../utils/custom-ast-nodes' | ||
BINDING_EVALUATE_KEY, | ||
toScopedFunction({ | ||
start: sourceNode.start, | ||
end: sourceNode.end, | ||
text: mergeNodeExpressions(sourceNode) | ||
}, sourceFile, sourceCode) | ||
wrapASTInFunctionWithScope( | ||
mergeNodeExpressions(sourceNode, sourceFile, sourceCode) | ||
) | ||
) | ||
]) | ||
} |
@@ -18,3 +18,3 @@ import {BINDING_TYPES, EXPRESSION_TYPES, GET_COMPONENT_FN, TEMPLATE_FN} from './constants' | ||
visitProperty(path) { | ||
if (path.value.key.name === TAG_TEMPLATE_PROPERTY) { | ||
if (path.value.key.value === TAG_TEMPLATE_PROPERTY) { | ||
path.value.value = builders.functionExpression( | ||
@@ -55,8 +55,9 @@ null, | ||
* @param { string } source - original component source code | ||
* @param { Object } options - user options | ||
* @param { Object } meta - compilation meta information | ||
* @param { AST } ast - current AST output | ||
* @returns { AST } the AST generated | ||
*/ | ||
export default async function template(sourceNode, source, options, ast) { | ||
export default async function template(sourceNode, source, meta, ast) { | ||
const { options } = meta | ||
return extendTemplateProperty(ast, options.file, source, sourceNode) | ||
} |
@@ -33,6 +33,7 @@ import { | ||
import {nullNode, simplePropertyNode} from '../../utils/custom-ast-nodes' | ||
import addLinesOffset from '../../utils/add-lines-offset' | ||
import compose from '../../utils/compose' | ||
import createNodeSourcemap from '../../utils/create-node-sourcemap' | ||
import curry from 'curri' | ||
import generateAST from '../../utils/generate-ast' | ||
import generateLiteralStringChunksFromNode from '../../utils/generate-literal-string-chunk-from-node' | ||
import {nodeTypes} from '@riotjs/parser' | ||
@@ -171,5 +172,8 @@ import panic from '../../utils/panic' | ||
export function createASTFromExpression(expression, sourceFile, sourceCode) { | ||
return generateAST(`(${expression.text})`, { | ||
sourceFileName: sourceFile, | ||
inputSourceMap: sourceFile && createNodeSourcemap(expression, sourceFile, sourceCode) | ||
const code = sourceFile ? | ||
addLinesOffset(expression.text, sourceCode, expression) : | ||
expression.text | ||
return generateAST(`(${code})`, { | ||
sourceFileName: sourceFile | ||
}) | ||
@@ -195,3 +199,2 @@ } | ||
if (!isExpressionStatement(firstNode)) { | ||
@@ -252,2 +255,17 @@ panic(`The each directives supported should be of type "ExpressionStatement",you have provided a "${firstNode.type}"`) | ||
/** | ||
* Wrap the ast generated in a function call providing the scope argument | ||
* @param {Object} ast - function body | ||
* @returns {FunctionExpresion} function having the scope argument injected | ||
*/ | ||
export function wrapASTInFunctionWithScope(ast) { | ||
return builders.functionExpression( | ||
null, | ||
[scope], | ||
builders.blockStatement([builders.returnStatement( | ||
ast | ||
)]) | ||
) | ||
} | ||
/** | ||
* Convert any parser option to a valid template one | ||
@@ -266,23 +284,25 @@ * @param { RiotParser.Node.Expression } expression - expression parsed by the riot parser | ||
export function toScopedFunction(expression, sourceFile, sourceCode) { | ||
const ast = createASTFromExpression(expression, sourceFile, sourceCode) | ||
const generatedAST = updateNodesScope(ast) | ||
const astBody = generatedAST.program.body | ||
const expressionAST = astBody[0] ? astBody[0].expression : astBody | ||
return compose( | ||
wrapASTInFunctionWithScope, | ||
transformExpression, | ||
)(expression, sourceFile, sourceCode) | ||
} | ||
return builders.functionExpression( | ||
null, | ||
[scope], | ||
builders.blockStatement([builders.returnStatement( | ||
expressionAST | ||
)]) | ||
) | ||
export function transformExpression(expression, sourceFile, sourceCode) { | ||
return compose( | ||
getExpressionAST, | ||
updateNodesScope, | ||
createASTFromExpression | ||
)(expression, sourceFile, sourceCode) | ||
} | ||
/** | ||
* Wrap a string in a template literal expression | ||
* @param {string} string - target string | ||
* @returns {string} a template literal | ||
* Get the parsed AST expression of riot expression node | ||
* @param {AST.Program} sourceAST - raw node parsed | ||
* @returns {AST.Expression} program expression output | ||
*/ | ||
export function wrapInBacktick(string) { | ||
return `\`${string}\`` | ||
export function getExpressionAST(sourceAST) { | ||
const astBody = sourceAST.program.body | ||
return astBody[0] ? astBody[0].expression : astBody | ||
} | ||
@@ -294,22 +314,16 @@ | ||
* @param {RiotParser.Node} node - riot parser node | ||
* @returns {string} either a string representing a template literal or simply the first expression matched | ||
* @param {string} sourceFile - original tag file | ||
* @param {string} sourceCode - original tag source code | ||
* @returns { Object } a FunctionExpression object | ||
*/ | ||
export function mergeNodeExpressions(node) { | ||
if (node.expressions.length === 1) { | ||
return node.expressions[0].text | ||
} | ||
export function mergeNodeExpressions(node, sourceFile, sourceCode) { | ||
if (node.expressions.length === 1) | ||
return transformExpression(node.expressions[0], sourceFile, sourceCode) | ||
const charAddedProIteration = 3 // ${} | ||
const pureStringChunks = generateLiteralStringChunksFromNode(node, sourceCode) | ||
// a tricky way to merge nodes containing multiple expressions in the same string | ||
const values = node.expressions.reduce((string, expression, index) => { | ||
const bracketsLength = expression.end - expression.start - expression.text.length | ||
const offset = index * (charAddedProIteration - bracketsLength) | ||
const expressionStart = expression.start - node.start + offset | ||
const expressionEnd = expression.end - node.start + offset | ||
return `${string.substring(0, expressionStart)}$\{${expression.text}}${string.substring(expressionEnd)}` | ||
}, node.text) | ||
return wrapInBacktick(values) | ||
return builders.templateLiteral( | ||
pureStringChunks.map(str => builders.templateElement({ raw: str, cooked: '' }, false)), | ||
node.expressions.map(expression => transformExpression(expression, sourceFile, sourceCode)) | ||
) | ||
} | ||
@@ -316,0 +330,0 @@ |
import { TAG_CSS_PROPERTY, TAG_LOGIC_PROPERTY, TAG_TEMPLATE_PROPERTY} from './generators/constants' | ||
import { nullNode, simplePropertyNode } from './utils/custom-ast-nodes' | ||
import { register as registerPostproc, execute as runPostprocessors } from './postprocessors' | ||
import { register as registerPreproc, execute as runPreprocessor } from './preprocessors' | ||
import {builders} from './utils/build-types' | ||
import cssGenerator from './generators/css/index' | ||
import curry from 'curri' | ||
import generateAST from './utils/generate-ast' | ||
import javascriptGenerator from './generators/javascript/index' | ||
@@ -21,4 +22,2 @@ import recast from 'recast' | ||
* Create the initial AST | ||
* @param { Sourcemap } map - initial sourcemap | ||
* @param { string } file - path to the original source file | ||
* @returns { AST } the initial AST | ||
@@ -29,8 +28,20 @@ * | ||
*/ | ||
export function createInitialInput(map, file) { | ||
const code = `export default { ${TAG_CSS_PROPERTY}: null, ${TAG_LOGIC_PROPERTY}: null, ${TAG_TEMPLATE_PROPERTY}: null }` | ||
return generateAST(code, { | ||
sourceFileName: file, | ||
inputSourceMap: map | ||
}) | ||
export function createInitialInput() { | ||
/* | ||
generates | ||
export default { | ||
${TAG_CSS_PROPERTY}: null, | ||
${TAG_LOGIC_PROPERTY}: null, | ||
${TAG_TEMPLATE_PROPERTY}: null | ||
} | ||
*/ | ||
return builders.program([ | ||
builders.exportDefaultDeclaration( | ||
builders.objectExpression([ | ||
simplePropertyNode(TAG_CSS_PROPERTY, nullNode()), | ||
simplePropertyNode(TAG_LOGIC_PROPERTY, nullNode()), | ||
simplePropertyNode(TAG_TEMPLATE_PROPERTY, nullNode()) | ||
]) | ||
)] | ||
) | ||
} | ||
@@ -49,7 +60,12 @@ | ||
} | ||
const meta = { | ||
source, | ||
options: opts | ||
} | ||
const { code, map } = await runPreprocessor('template', opts.template, opts, source) | ||
const { code, map } = await runPreprocessor('template', opts.template, meta, source) | ||
const { template, css, javascript } = riotParser(opts).parse(code).output | ||
const meta = { | ||
options: opts, | ||
// extend the meta object with the result of the parsing | ||
Object.assign(meta, { | ||
tagName: template.name, | ||
@@ -61,13 +77,14 @@ fragments: { | ||
} | ||
} | ||
}) | ||
return ruit( | ||
createInitialInput(map), | ||
hookGenerator(cssGenerator, css, code, opts), | ||
hookGenerator(javascriptGenerator, javascript, code, opts), | ||
hookGenerator(templateGenerator, template, code, opts), | ||
createInitialInput(), | ||
hookGenerator(cssGenerator, css, code, meta), | ||
hookGenerator(javascriptGenerator, javascript, code, meta), | ||
hookGenerator(templateGenerator, template, code, meta), | ||
ast => recast.print(ast, { | ||
sourceMapName: 'map.json' | ||
sourceMapName: 'map.json', | ||
inputSourcemap: map | ||
}), | ||
result => runPostprocessors(result, opts), | ||
result => runPostprocessors(result, meta), | ||
result => ({ | ||
@@ -85,6 +102,6 @@ ...result, | ||
* @param { string } source - component source code | ||
* @param { Object } options - compiling options | ||
* @param { Object } meta - compilation meta information | ||
* @returns { Promise<Output> } object containing output code and source map | ||
*/ | ||
function hookGenerator(transformer, sourceNode, source, options) { | ||
function hookGenerator(transformer, sourceNode, source, meta) { | ||
if (!sourceNode || (sourceNode.nodes && !sourceNode.nodes.length)) { | ||
@@ -94,3 +111,3 @@ return result => result | ||
return curry(transformer)(sourceNode, source, options) | ||
return curry(transformer)(sourceNode, source, meta) | ||
} | ||
@@ -97,0 +114,0 @@ |
@@ -40,15 +40,15 @@ import composeSourcemaps from './utils/compose-sourcemaps' | ||
* @param { Output } compilerOutput - output generated by the compiler | ||
* @param { Object } options - user options received by the compiler | ||
* @param { Object } meta - compiling meta information | ||
* @returns { Promise<Output> } object containing output code and source map | ||
*/ | ||
export async function execute(compilerOutput, options) { | ||
export async function execute(compilerOutput, meta) { | ||
return Array.from(postprocessors).reduce(async function(acc, postprocessor) { | ||
const { code, map } = await acc | ||
const output = await postprocessor(code, options) | ||
const output = await postprocessor(code, meta) | ||
return { | ||
code: output.code, | ||
map: composeSourcemaps(output.map, map) | ||
map: composeSourcemaps(map, output.map) | ||
} | ||
}, Promise.resolve(createOutput(compilerOutput, options))) | ||
}, Promise.resolve(createOutput(compilerOutput, meta))) | ||
} |
@@ -64,11 +64,11 @@ import panic from './utils/panic' | ||
* @param { string } name - unique preprocessor id | ||
* @param { Object } options - preprocessor options | ||
* @param { Object } meta - preprocessor meta information | ||
* @param { string } source - source code | ||
* @returns { Promise<Output> } object containing a sourcemap and a code string | ||
*/ | ||
export async function execute(type, name, options, source) { | ||
export async function execute(type, name, meta, source) { | ||
if (!preprocessors[type]) preprocessorTypeError(type) | ||
if (!preprocessors[type].has(name)) preprocessorNameNotFoundError(name) | ||
return await transform(preprocessors[type].get(name), options, source) | ||
return await transform(preprocessors[type].get(name), meta, source) | ||
} |
@@ -16,15 +16,17 @@ import createSourcemap from './utils/create-sourcemap' | ||
* @param { SourceMapGenerator } data.map - source map generated along with the code | ||
* @param { Object } options - user options, probably containing the path to the source file | ||
* @param { Object } meta - compilation meta infomration | ||
* @returns { Output } output container object | ||
*/ | ||
export function createOutput(data, options) { | ||
const output = Object.seal({ | ||
export function createOutput(data, meta) { | ||
const output = { | ||
...Output, | ||
...data, | ||
meta: { options } | ||
}) | ||
meta | ||
} | ||
if (!output.map && options && options.file) Object.assign(output, { | ||
map: createSourcemap({ file: options.file }) | ||
}) | ||
if (!output.map && meta && meta.options && meta.options.file) | ||
return { | ||
...output, | ||
map: createSourcemap({ file: meta.options.file }) | ||
} | ||
@@ -37,9 +39,9 @@ return output | ||
* @param { Function } compiler - function needed to generate the output code | ||
* @param { Object } options - options to pass to the compilert | ||
* @param { Object } meta - compilation meta information | ||
* @param { string } source - source code | ||
* @returns { Promise<Output> } output - the result of the compiler | ||
*/ | ||
export async function transform(compiler, options, source) { | ||
const result = await (compiler ? compiler(source, options) : { code: source }) | ||
return createOutput(result, options) | ||
export async function transform(compiler, meta, source) { | ||
const result = await (compiler ? compiler(source, meta) : { code: source }) | ||
return createOutput(result, meta) | ||
} |
@@ -1,2 +0,1 @@ | ||
import getColumn from './get-column' | ||
import splitStringByEOL from './split-string-by-EOL' | ||
@@ -15,4 +14,4 @@ | ||
line: lines.length, | ||
column: getColumn(lines[lines.length - 1]) | ||
column: lines[lines.length - 1].length | ||
} | ||
} |
@@ -1,2 +0,1 @@ | ||
import getLineAndColumnByPosition from './get-line-and-column-by-position' | ||
import {execute as runPreprocessor} from '../preprocessors' | ||
@@ -8,3 +7,3 @@ | ||
* @param { string } preprocessorName - preprocessor id | ||
* @param { Object } options - options that will be passed to the compiler | ||
* @param { Object } meta - compilation meta information | ||
* @param { string } source - tag source code | ||
@@ -14,11 +13,9 @@ * @param { RiotParser.nodeTypes } node - css node detected by the parser | ||
*/ | ||
export default async function preprocess(preprocessorType, preprocessorName, options, source, node) { | ||
const { column } = getLineAndColumnByPosition(source, node.start) | ||
const offsetTop = '\n'.repeat(column) | ||
const code = `${offsetTop}\n${node.text}` | ||
export default async function preprocess(preprocessorType, preprocessorName, meta, source, node) { | ||
const code = node.text | ||
return await (preprocessorName ? | ||
runPreprocessor(preprocessorType, preprocessorName, options, code) : | ||
runPreprocessor(preprocessorType, preprocessorName, meta, code) : | ||
{ code } | ||
) | ||
} |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
46
31335
96
1226935