typescript-plugin-styled-components
Version:
TypeScript transformer for improving the debugging experience of styled-components
167 lines (166 loc) • 8.45 kB
JavaScript
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;
;