UNPKG

@linaria/react

Version:

Blazing fast zero-runtime CSS in JS library

289 lines 9.29 kB
// src/processors/styled.ts import { readFileSync } from "fs"; import { dirname, join, posix } from "path"; import { buildSlug, TaggedTemplateProcessor, validateParams, toValidCSSIdentifier } from "@wyw-in-js/processor-utils"; import { findPackageJSON, hasEvalMeta, slugify, ValueType } from "@wyw-in-js/shared"; import { minimatch } from "minimatch"; import html from "react-html-attributes"; import { sync as resolveSync } from "resolve"; var isNotNull = (x) => x !== null; var allTagsSet = /* @__PURE__ */ new Set([...html.elements.html, html.elements.svg]); var singleQuotedStringLiteral = (value) => ({ type: "StringLiteral", value, extra: { rawValue: value, raw: `'${value}'` } }); var StyledProcessor = class extends TaggedTemplateProcessor { component; #variableIdx = 0; #variablesCache = /* @__PURE__ */ new Map(); constructor(params, ...args) { validateParams( params, ["callee", "*", "..."], TaggedTemplateProcessor.SKIP ); validateParams( params, ["callee", ["call", "member"], ["template", "call"]], "Invalid usage of `styled` tag" ); const [tag, tagOp, template] = params; if (template[0] === "call") { throw TaggedTemplateProcessor.SKIP; } super([tag, template], ...args); let component; if (tagOp[0] === "call" && tagOp.length === 2) { const value = tagOp[1]; if (value.kind === ValueType.FUNCTION) { component = "FunctionalComponent"; } else if (value.kind === ValueType.CONST) { component = typeof value.value === "string" ? value.value : void 0; } else { if (value.importedFrom?.length) { const selfPkg = findPackageJSON(".", this.context.filename); const isSomeMatched = value.importedFrom.some((importedFrom) => { const importedPkg = ( // If package.json is not found, assume it's a local package findPackageJSON(importedFrom, this.context.filename) ?? selfPkg ); if (importedPkg) { const packageJSON = JSON.parse(readFileSync(importedPkg, "utf8")); const mask = packageJSON?.linaria?.components; if (importedPkg === selfPkg && mask === void 0) { return true; } if (mask) { const packageDir = dirname(importedPkg); const fullMask = join(packageDir, mask).replace( /\\/g, posix.sep ); try { const fileWithComponent = resolveSync(importedFrom, { basedir: dirname(this.context.filename), extensions: this.options.extensions }); return minimatch(fileWithComponent, fullMask); } catch (e) { console.warn( `Can't resolve ${importedFrom} from ${this.context.filename}. If ${value.source} is another styled component, it should be resolvable with default Node.js resolver. If it's not, please exclude it from the linaria.components mask in package.json.` ); return false; } } } return false; }); if (!isSomeMatched) { component = { node: value.ex, nonLinaria: true, source: value.source }; } } if (component === void 0) { component = { node: value.ex, source: value.source }; this.dependencies.push(value); } } } if (tagOp[0] === "member") { [, component] = tagOp; } if (!component) { throw new Error("Invalid usage of `styled` tag"); } this.component = component; } get asSelector() { return `.${this.className}`; } get value() { const t = this.astService; const extendsNode = typeof this.component === "string" || this.component.nonLinaria ? null : this.component.node.name; return t.objectExpression([ t.objectProperty( t.stringLiteral("displayName"), t.stringLiteral(this.displayName) ), t.objectProperty( t.stringLiteral("__wyw_meta"), t.objectExpression([ t.objectProperty( t.stringLiteral("className"), t.stringLiteral(this.className) ), t.objectProperty( t.stringLiteral("extends"), extendsNode ? t.callExpression(t.identifier(extendsNode), []) : t.nullLiteral() ) ]) ) ]); } get tagExpression() { const t = this.astService; return t.callExpression(this.callee, [this.tagExpressionArgument]); } get tagExpressionArgument() { const t = this.astService; if (typeof this.component === "string") { if (this.component === "FunctionalComponent") { return t.arrowFunctionExpression([], t.blockStatement([])); } return singleQuotedStringLiteral(this.component); } return t.callExpression(t.identifier(this.component.node.name), []); } addInterpolation(node, precedingCss, source, unit = "") { const id = this.getVariableId(source, unit, precedingCss); this.interpolations.push({ id, node, source, unit }); return id; } doEvaltimeReplacement() { this.replacer(this.value, false); } doRuntimeReplacement() { const t = this.astService; const props = this.getProps(); this.replacer( t.callExpression(this.tagExpression, [this.getTagComponentProps(props)]), true ); } extractRules(valueCache, cssText, loc) { const rules = {}; let selector = `.${this.className}`; let value = typeof this.component === "string" || this.component.nonLinaria ? null : valueCache.get(this.component.node.name); while (hasEvalMeta(value)) { selector += `.${value.__wyw_meta.className}`; value = value.__wyw_meta.extends; } rules[selector] = { cssText, className: this.className, displayName: this.displayName, start: loc?.start ?? null }; return rules; } toString() { const res = (arg) => `${this.tagSourceCode()}(${arg})\`\u2026\``; if (typeof this.component === "string") { if (this.component === "FunctionalComponent") { return res("() => {\u2026}"); } return res(`'${this.component}'`); } return res(this.component.source); } getCustomVariableId(source, unit, precedingCss) { const context = this.getVariableContext(source, unit, precedingCss); const customSlugFn = this.options.variableNameSlug; if (!customSlugFn) { return void 0; } return typeof customSlugFn === "function" ? customSlugFn(context) : buildSlug(customSlugFn, { ...context }); } getProps() { const propsObj = { name: this.displayName, class: this.className, propsAsIs: typeof this.component !== "string" || !allTagsSet.has(this.component) }; if (this.interpolations.length) { propsObj.vars = {}; this.interpolations.forEach(({ id, unit, node }) => { const items = [this.astService.callExpression(node, [])]; if (unit) { items.push(this.astService.stringLiteral(unit)); } propsObj.vars[id] = items; }); } return propsObj; } getTagComponentProps(props) { const t = this.astService; const propExpressions = Object.entries(props).map(([key, value]) => { if (value === void 0) { return null; } const keyNode = t.identifier(key); if (value === null) { return t.objectProperty(keyNode, t.nullLiteral()); } if (typeof value === "string") { return t.objectProperty(keyNode, t.stringLiteral(value)); } if (typeof value === "boolean") { return t.objectProperty(keyNode, t.booleanLiteral(value)); } const vars = Object.entries(value).map(([propName, propValue]) => { return t.objectProperty( t.stringLiteral(propName), t.arrayExpression(propValue) ); }); return t.objectProperty(keyNode, t.objectExpression(vars)); }).filter(isNotNull); return t.objectExpression(propExpressions); } getVariableContext(source, unit, precedingCss) { const getIndex = () => { return this.#variableIdx++; }; return { componentName: this.displayName, componentSlug: this.slug, get index() { return getIndex(); }, precedingCss, processor: this.constructor.name, source, unit, valueSlug: slugify(source + unit) }; } getVariableId(source, unit, precedingCss) { const value = source + unit; if (!this.#variablesCache.has(value)) { const id = this.getCustomVariableId(source, unit, precedingCss); if (id) { return toValidCSSIdentifier(id); } const context = this.getVariableContext(source, unit, precedingCss); this.#variablesCache.set(value, `${this.slug}-${context.index}`); } return this.#variablesCache.get(value); } }; export { StyledProcessor as default }; //# sourceMappingURL=styled.mjs.map