UNPKG

otion

Version:

Atomic CSS-in-JS with a featherweight runtime

92 lines (79 loc) 2.95 kB
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) }; } export { VirtualInjector, filterOutUnusedRules, getStyleProps, getStyleTag }; //# sourceMappingURL=index.mjs.map