otion
Version:
Atomic CSS-in-JS with a featherweight runtime
99 lines (84 loc) • 3.12 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
const STYLE_ELEMENT_ID = "__otion";
/**
* Creates an injector which collects style rules during server-side rendering.
*/
function VirtualInjector({
nonce,
target: ruleTexts = []
} = {}) {
return {
nonce,
ruleTexts,
insert(rule, index) {
ruleTexts.splice(index, 0, rule);
return index;
}
};
}
/**
* Transforms an injector's data into `<style>` tag properties. Useful as a base to build custom server-side renderers upon.
*
* @param injector Server-side style rule injector.
*
* @returns Properties of a `<style>` tag as an object.
*/
function getStyleProps(injector) {
return {
id: STYLE_ELEMENT_ID,
nonce: injector.nonce,
textContent: injector.ruleTexts.join("")
};
}
/**
* Transforms an injector's data into a `<style>` tag string.
*
* @param injector Server-side style rule injector.
*
* @returns A stringified `<style>` tag containing server-renderable CSS.
*/
function getStyleTag(injector) {
const {
id,
nonce,
textContent
} = getStyleProps(injector);
let props = `id="${id}"`;
if (nonce) props += ` nonce="${nonce}"`;
return `<style ${props}>${textContent}</style>`;
}
/**
* Filters out style rules which are not statically referenced by the given HTML code.
*
* @param injector Server-side style rule injector.
* @param html HTML code of the underlying page.
*
* @returns A copy of the given injector instance with the unused rules filtered out.
*/
function filterOutUnusedRules(injector, html) {
const usedIdentNames = new Set();
const re = /<[^>]+\s+?class\s*?=\s*?(".+?"|'.+?'|[^>\s]+)/gi;
let matches; // eslint-disable-next-line no-cond-assign
while ((matches = re.exec(html)) != null) {
const classAttributeValue = matches[1];
const unquotedClassAttributeValue = classAttributeValue[0] === '"' || classAttributeValue[0] === "'" ? classAttributeValue.slice(1, -1) : classAttributeValue;
unquotedClassAttributeValue.trim().split(/\s+/) // Ignore excess white space between class names
.forEach(className => usedIdentNames.add(className));
}
const ruleTextsByIdentName = injector.ruleTexts.map(ruleText => [// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
/_[0-9a-z]+/.exec(ruleText)[0], ruleText]);
return { ...injector,
ruleTexts: ruleTextsByIdentName.filter(([identName, ruleText]) => {
if (usedIdentNames.has(identName)) return true; // Only a `@keyframes` name can be referenced by other scoped rules
const isReferencedByAnOtherUsedRule = ruleText[0] === "@" && ruleText[1] === "k" && ruleTextsByIdentName.some(([otherIdentName, otherRuleText]) => usedIdentNames.has(otherIdentName) && otherRuleText.includes(identName));
if (isReferencedByAnOtherUsedRule) return true;
return false;
}).map(([, ruleText]) => ruleText)
};
}
exports.VirtualInjector = VirtualInjector;
exports.filterOutUnusedRules = filterOutUnusedRules;
exports.getStyleProps = getStyleProps;
exports.getStyleTag = getStyleTag;
//# sourceMappingURL=index.js.map