import * as types from '@babel/types' import generate from '@babel/generator' import traverse, { NodePath } from '@babel/traverse' import { TemplateGenerator } from './type' import { InvalidSyntaxError, ParseError } from './errors' import { isTemplate, parseFile, resolveComponent, buildProps } from './utils' import Path from 'path' export const compile: TemplateGenerator = (filePath) => { const { ast, code } = parseFile(filePath) const importsToRemove = new Set>() if (!isTemplate(ast)) { throw new ParseError(filePath, { message: `${filePath} is not a valid Template file.` }) } traverse(ast, { JSXNamespacedName (path) { throw new InvalidSyntaxError( filePath, { code, start: path.node.loc?.start, end: path.node.loc?.end, message: 'Namespace tags are not supported.' } ) }, JSXSpreadChild (path) { throw new InvalidSyntaxError( filePath, { code, start: path.node.loc?.start, end: path.node.loc?.end, message: 'Spread children are not supported.' } ) }, JSXAttribute (path) { if (types.isJSXElement(path.node.value)) { path.node.value = types.jsxExpressionContainer(path.node.value) } }, JSXElement: { exit (elementPath) { const openingElementPath = elementPath.get('openingElement') const namePath = openingElementPath.get('name') if (!namePath.isJSXIdentifier()) { throw new InvalidSyntaxError( filePath, { code, start: openingElementPath.node.name.loc?.start, end: openingElementPath.node.name.loc?.end, message: `${openingElementPath.node.type} tags are not supported.` } ) } const tagName = namePath.node.name const binding = namePath.scope.getBinding(tagName) if (binding == null || !binding.path.isImportDefaultSpecifier()) { throw new InvalidSyntaxError( filePath, { code, start: namePath.node.loc?.start, end: namePath.node.loc?.end, message: 'Only default imported Templates and Serializables modules can be used as JSX Elements in templates.' } ) } const info = resolveComponent( binding.path, Path.dirname(filePath) ) const props = buildProps( openingElementPath.node.attributes, types.react.buildChildren(elementPath.node) as types.Expression[] ) if (info?.type === 'Serializable') { importsToRemove.add(binding.path) elementPath.replaceWith( types.objectExpression([ types.objectProperty( types.identifier('type'), types.stringLiteral('__RJT_COMPONENT__') ), types.objectProperty( types.identifier('name'), types.stringLiteral(info.name) ), types.objectProperty( types.identifier('props'), props ) ]) ) return } // if (info?.type === 'Template') { // elementPath.replaceWith( // types.callExpression( // types.identifier(tagName), // [props] // ) // ) // return // } throw new ParseError( filePath, { code, start: openingElementPath.node.name.loc?.start, end: openingElementPath.node.name.loc?.end, message: `Cannot resolve component ${tagName}.` } ) } }, JSXFragment: { exit (elementPath) { elementPath.replaceWith( types.objectExpression([ types.objectProperty( types.identifier('type'), types.stringLiteral('__RJT_FRAGMENT__') ), types.objectProperty( types.identifier('children'), types.arrayExpression(types.react.buildChildren(elementPath.node) as types.Expression[]) ) ]) ) } }, ExportDefaultDeclaration: { exit (path) { const declaration = path.get('declaration') if (!declaration.isCallExpression()) { return } importsToRemove.add(path.scope.getBinding('Template')?.path as NodePath) const args = declaration.get('arguments')[0] declaration.replaceWith(args) } } }) for (const importSpecifierPath of importsToRemove) { importSpecifierPath.remove() if ( importSpecifierPath.parentPath.isImportDeclaration() && importSpecifierPath.parentPath.get('specifiers').length === 0 ) { importSpecifierPath.parentPath.remove() } } return `// @ts-nocheck \n\n${generate(ast).code}` }