UNPKG

typescript-plugin-styled-components

Version:

TypeScript transformer for improving the debugging experience of styled-components

167 lines (166 loc) 8.45 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createTransformer = void 0; const path = require("path"); const ts = require("typescript"); const ts_is_kind_1 = require("./ts-is-kind"); const hash_1 = require("./hash"); const minify_1 = require("./minify"); /** Detects that a node represents a styled function * Recognizes the following patterns: * * styled.tag * Component.extend * styled(Component) * styled('tag') * styledFunction.attrs(attributes) */ function isStyledFunction(node, identifiers) { if ((0, ts_is_kind_1.isPropertyAccessExpression)(node)) { if (isStyledObject(node.expression, identifiers)) { return true; } if (isStyledExtendIdentifier(node.name.text, identifiers) && isValidComponent(node.expression)) { return true; } return false; } if ((0, ts_is_kind_1.isCallExpression)(node) && node.arguments.length === 1) { if (isStyledObject(node.expression, identifiers)) { return true; } if (isStyledAttrs(node.expression, identifiers)) { return true; } } return false; } function isStyledObjectIdentifier(name, { styled: styledIdentifiers = ['styled'] }) { return styledIdentifiers.indexOf(name) >= 0; } function isStyledObject(node, identifiers) { return node && (0, ts_is_kind_1.isIdentifier)(node) && isStyledObjectIdentifier(node.text, identifiers); } function isValidComponent(node) { return node && (0, ts_is_kind_1.isIdentifier)(node) && isValidComponentName(node.text); } function isLetter(ch) { return ch.toLowerCase() !== ch.toUpperCase(); } function isValidTagName(name) { return isLetter(name[0]) && name[0] === name[0].toLowerCase(); } function isValidComponentName(name) { return isLetter(name[0]) && name[0] === name[0].toUpperCase(); } function isStyledAttrsIdentifier(name, { attrs: attrsIdentifiers = ['attrs'] }) { return attrsIdentifiers.indexOf(name) >= 0; } function isStyledAttrs(node, identifiers) { return (node && (0, ts_is_kind_1.isPropertyAccessExpression)(node) && isStyledAttrsIdentifier(node.name.text, identifiers) && isStyledFunction(node.expression, identifiers)); } function isStyledKeyframesIdentifier(name, { keyframes = ['keyframes'] }) { return keyframes.indexOf(name) >= 0; } function isStyledCssIdentifier(name, { css = ['css'] }) { return css.indexOf(name) >= 0; } function isStyledCreateGlobalStyleIdentifier(name, { createGlobalStyle = ['createGlobalStyle'] }) { return createGlobalStyle.indexOf(name) >= 0; } function isStyledExtendIdentifier(name, { extend = [] }) { return extend.indexOf(name) >= 0; } function isMinifyableStyledFunction(node, identifiers) { return (isStyledFunction(node, identifiers) || ((0, ts_is_kind_1.isIdentifier)(node) && (isStyledKeyframesIdentifier(node.text, identifiers) || isStyledCssIdentifier(node.text, identifiers) || isStyledCreateGlobalStyleIdentifier(node.text, identifiers)))); } function defaultGetDisplayName(filename, bindingName) { return bindingName; } function createTransformer({ getDisplayName = defaultGetDisplayName, identifiers = {}, ssr = true, displayName = true, minify = false, componentIdPrefix = '' } = {}) { /** * Infers display name of a styled component. * Recognizes the following patterns: * * (const|var|let) ComponentName = styled... * export default styled... */ function getDisplayNameFromNode(node, sourceFile) { if ((0, ts_is_kind_1.isVariableDeclaration)(node) && (0, ts_is_kind_1.isIdentifier)(node.name)) { return (componentIdPrefix ? componentIdPrefix + '-' : '') + getDisplayName(sourceFile.fileName, node.name.text); } if ((0, ts_is_kind_1.isExportAssignment)(node)) { return getDisplayName(sourceFile.fileName, undefined); } return undefined; } function getIdFromNode(node, sourceRoot, position, sourceFile) { if (((0, ts_is_kind_1.isVariableDeclaration)(node) && (0, ts_is_kind_1.isIdentifier)(node.name)) || (0, ts_is_kind_1.isExportAssignment)(node)) { const fileName = sourceFile.fileName; const filePath = sourceRoot ? path.relative(sourceRoot, fileName).replace(path.sep, path.posix.sep) : fileName; return (componentIdPrefix || 'sc') + '-' + (0, hash_1.hash)(`${getDisplayNameFromNode(node, sourceFile)}${filePath}${position}`); } return undefined; } const transformer = (context) => { const { sourceRoot } = context.getCompilerOptions(); return (sourceFile) => { let lastComponentPosition = 0; const withConfig = (node, properties) => properties.length > 0 ? context.factory.createCallExpression(context.factory.createPropertyAccessExpression(node, 'withConfig'), undefined, [context.factory.createObjectLiteralExpression(properties)]) : node; const createDisplayNameConfig = (displayNameValue) => displayNameValue ? [ context.factory.createPropertyAssignment('displayName', context.factory.createStringLiteral(displayNameValue)), ] : []; const createIdConfig = (componentId) => componentId ? [ context.factory.createPropertyAssignment('componentId', context.factory.createStringLiteral(componentId)), ] : []; const transformStyledFunction = (binding, node) => withConfig(node, [ ...(displayName ? createDisplayNameConfig(getDisplayNameFromNode(binding, sourceFile)) : []), ...(ssr ? createIdConfig(getIdFromNode(binding, sourceRoot, ++lastComponentPosition, sourceFile)) : []), ]); const transformTemplateLiteral = (templateLiteral) => minify ? (0, minify_1.minifyTemplate)(templateLiteral, context.factory) : templateLiteral; const transformTaggedTemplateExpression = (node) => isMinifyableStyledFunction(node.tag, identifiers) ? context.factory.updateTaggedTemplateExpression(node, node.tag, node.typeArguments, transformTemplateLiteral(node.template)) : node; const transformBindingExpression = (binding, node) => { if ((0, ts_is_kind_1.isTaggedTemplateExpression)(node) && isStyledFunction(node.tag, identifiers)) { return context.factory.updateTaggedTemplateExpression(node, transformStyledFunction(binding, node.tag), node.typeArguments, transformTemplateLiteral(node.template)); } if ((0, ts_is_kind_1.isCallExpression)(node) && isStyledFunction(node.expression, identifiers)) { return context.factory.updateCallExpression(node, transformStyledFunction(binding, node.expression), node.typeArguments, node.arguments); } }; const updateNode = (node, data, updateFn) => (data ? updateFn(node, data) : undefined); const updateVariableDeclarationInitializer = (node, initializer) => context.factory.updateVariableDeclaration(node, node.name, node.exclamationToken, node.type, initializer); const updateExportAssignmentExpression = (node, expression) => context.factory.updateExportAssignment(node, node.modifiers, expression); const transformNode = (node) => (0, ts_is_kind_1.isVariableDeclaration)(node) && node.initializer ? updateNode(node, transformBindingExpression(node, node.initializer), updateVariableDeclarationInitializer) : (0, ts_is_kind_1.isExportAssignment)(node) ? updateNode(node, transformBindingExpression(node, node.expression), updateExportAssignmentExpression) : minify && (0, ts_is_kind_1.isTaggedTemplateExpression)(node) ? transformTaggedTemplateExpression(node) : undefined; const visitNode = (node) => transformNode(node) || ts.visitEachChild(node, visitNode, context); return ts.visitNode(sourceFile, visitNode); }; }; return transformer; } exports.createTransformer = createTransformer; exports.default = createTransformer;