@linaria/react
Version:
Blazing fast zero-runtime CSS in JS library
289 lines • 9.29 kB
JavaScript
// 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