otion
Version:
Atomic CSS-in-JS with a featherweight runtime
1 lines • 8.13 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","sources":["../src/getStyleElement.ts","../src/injectors.ts","../src/server.ts"],"sourcesContent":["export const STYLE_ELEMENT_ID = \"__otion\";\n\nexport function getStyleElement(): HTMLStyleElement {\n\t// Hydrate existing style element if available\n\tlet el = document.getElementById(STYLE_ELEMENT_ID) as HTMLStyleElement | null;\n\tif (el) return el;\n\n\t// Create a new one otherwise\n\tel = document.createElement(\"style\");\n\tel.id = STYLE_ELEMENT_ID;\n\n\treturn document.head.appendChild(el);\n}\n","import { getStyleElement } from \"./getStyleElement\";\n\nexport interface InjectorConfig<T> {\n\t/** Sets a cryptographic nonce (number used once) on the enclosing `<style>` tag when generating a page on demand. Useful for enforcing a [Content Security Policy (CSP)](https://developer.mozilla.org/docs/Web/HTTP/CSP). */\n\tnonce?: string;\n\n\t/** Target to insert rules into. */\n\ttarget?: T;\n}\n\nexport type InjectorInstance = {\n\tsheet?: CSSStyleSheet;\n\tinsert(rule: string, index: number): number;\n};\n\ntype VirtualInjectorInstance = InjectorInstance & {\n\tnonce: string | undefined;\n\truleTexts: string[];\n};\n\n/**\n * Creates an injector which collects style rules during server-side rendering.\n */\nexport function VirtualInjector({\n\tnonce,\n\ttarget: ruleTexts = [],\n}: InjectorConfig<string[]> = {}): VirtualInjectorInstance {\n\treturn {\n\t\tnonce,\n\t\truleTexts,\n\n\t\tinsert(rule, index): number {\n\t\t\truleTexts.splice(index, 0, rule);\n\t\t\treturn index;\n\t\t},\n\t};\n}\n\n/**\n * Creates an injector which inserts style rules through the CSS Object Model.\n */\nexport function CSSOMInjector({\n\tnonce,\n\ttarget = getStyleElement().sheet as CSSStyleSheet,\n}: InjectorConfig<CSSStyleSheet>): InjectorInstance {\n\t// eslint-disable-next-line no-param-reassign\n\t(target.ownerNode as HTMLStyleElement).nonce = nonce;\n\n\treturn {\n\t\tsheet: target,\n\n\t\tinsert(rule, index): number {\n\t\t\t// Avoid render failure during production if a rule cannot be parsed\n\t\t\ttry {\n\t\t\t\treturn target.insertRule(rule, index);\n\t\t\t} catch {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t},\n\t};\n}\n\n/**\n * Creates an injector which inserts style rules through the Document Object Model.\n */\nexport function DOMInjector({\n\tnonce,\n\ttarget = getStyleElement(),\n}: InjectorConfig<HTMLStyleElement>): InjectorInstance {\n\t// eslint-disable-next-line no-param-reassign\n\ttarget.nonce = nonce;\n\n\treturn {\n\t\tsheet: target.sheet as CSSStyleSheet,\n\n\t\tinsert(rule, index): number {\n\t\t\ttarget.insertBefore(\n\t\t\t\tdocument.createTextNode(rule),\n\t\t\t\ttarget.childNodes[index],\n\t\t\t);\n\t\t\treturn index;\n\t\t},\n\t};\n}\n\n/**\n * An injector placeholder which performs no operations. Useful for avoiding errors in a non-browser environment.\n */\nexport const NoOpInjector: InjectorInstance = {\n\tinsert(): number {\n\t\treturn 0;\n\t},\n};\n","import { STYLE_ELEMENT_ID } from \"./getStyleElement\";\nimport { VirtualInjector } from \"./injectors\";\n\nexport { VirtualInjector };\n\n/**\n * Transforms an injector's data into `<style>` tag properties. Useful as a base to build custom server-side renderers upon.\n *\n * @param injector Server-side style rule injector.\n *\n * @returns Properties of a `<style>` tag as an object.\n */\nexport function getStyleProps(\n\tinjector: ReturnType<typeof VirtualInjector>,\n): { id: string; nonce: string | undefined; textContent: string } {\n\treturn {\n\t\tid: STYLE_ELEMENT_ID,\n\t\tnonce: injector.nonce,\n\t\ttextContent: injector.ruleTexts.join(\"\"),\n\t};\n}\n\n/**\n * Transforms an injector's data into a `<style>` tag string.\n *\n * @param injector Server-side style rule injector.\n *\n * @returns A stringified `<style>` tag containing server-renderable CSS.\n */\nexport function getStyleTag(\n\tinjector: ReturnType<typeof VirtualInjector>,\n): string {\n\tconst { id, nonce, textContent } = getStyleProps(injector);\n\n\tlet props = `id=\"${id}\"`;\n\tif (nonce) props += ` nonce=\"${nonce}\"`;\n\treturn `<style ${props}>${textContent}</style>`;\n}\n\n/**\n * Filters out style rules which are not statically referenced by the given HTML code.\n *\n * @param injector Server-side style rule injector.\n * @param html HTML code of the underlying page.\n *\n * @returns A copy of the given injector instance with the unused rules filtered out.\n */\nexport function filterOutUnusedRules(\n\tinjector: ReturnType<typeof VirtualInjector>,\n\thtml: string,\n): ReturnType<typeof VirtualInjector> {\n\tconst usedIdentNames = new Set<string>();\n\n\tconst re = /<[^>]+\\s+?class\\s*?=\\s*?(\".+?\"|'.+?'|[^>\\s]+)/gi;\n\tlet matches: string[] | null;\n\n\t// eslint-disable-next-line no-cond-assign\n\twhile ((matches = re.exec(html)) != null) {\n\t\tconst classAttributeValue = matches[1];\n\t\tconst unquotedClassAttributeValue =\n\t\t\tclassAttributeValue[0] === '\"' || classAttributeValue[0] === \"'\"\n\t\t\t\t? classAttributeValue.slice(1, -1)\n\t\t\t\t: classAttributeValue;\n\t\tunquotedClassAttributeValue\n\t\t\t.trim()\n\t\t\t.split(/\\s+/) // Ignore excess white space between class names\n\t\t\t.forEach((className) => usedIdentNames.add(className));\n\t}\n\n\tconst ruleTextsByIdentName = injector.ruleTexts.map((ruleText) => [\n\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t/_[0-9a-z]+/.exec(ruleText)![0],\n\t\truleText,\n\t]);\n\n\treturn {\n\t\t...injector,\n\n\t\truleTexts: ruleTextsByIdentName\n\t\t\t.filter(([identName, ruleText]) => {\n\t\t\t\tif (usedIdentNames.has(identName)) return true;\n\n\t\t\t\t// Only a `@keyframes` name can be referenced by other scoped rules\n\t\t\t\tconst isReferencedByAnOtherUsedRule =\n\t\t\t\t\truleText[0] === \"@\" &&\n\t\t\t\t\truleText[1] === \"k\" &&\n\t\t\t\t\truleTextsByIdentName.some(\n\t\t\t\t\t\t([otherIdentName, otherRuleText]) =>\n\t\t\t\t\t\t\tusedIdentNames.has(otherIdentName) &&\n\t\t\t\t\t\t\totherRuleText.includes(identName),\n\t\t\t\t\t);\n\t\t\t\tif (isReferencedByAnOtherUsedRule) return true;\n\n\t\t\t\treturn false;\n\t\t\t})\n\t\t\t.map(([, ruleText]) => ruleText),\n\t};\n}\n"],"names":[],"mappings":"AAAO,MAAM,gBAAgB,GAAG,SAAzB;;ACoBP;;;;AAGM,SAAU,eAAV,CAA0B;AAC/B,EAAA,KAD+B;AAE/B,EAAA,MAAM,EAAE,SAAS,GAAG;AAFW,IAGF,EAHxB,EAG0B;AAC/B,SAAO;AACN,IAAA,KADM;AAEN,IAAA,SAFM;;AAIN,IAAA,MAAM,CAAC,IAAD,EAAO,KAAP,EAAY;AACjB,MAAA,SAAS,CAAC,MAAV,CAAiB,KAAjB,EAAwB,CAAxB,EAA2B,IAA3B;AACA,aAAO,KAAP;AACA;;AAPK,GAAP;AASA;;AC/BD;;;;;;;;AAOM,SAAU,aAAV,CACL,QADK,EACuC;AAE5C,SAAO;AACN,IAAA,EAAE,EAAE,gBADE;AAEN,IAAA,KAAK,EAAE,QAAQ,CAAC,KAFV;AAGN,IAAA,WAAW,EAAE,QAAQ,CAAC,SAAT,CAAmB,IAAnB,CAAwB,EAAxB;AAHP,GAAP;AAKA;AAED;;;;;;;;AAOM,SAAU,WAAV,CACL,QADK,EACuC;AAE5C,QAAM;AAAE,IAAA,EAAF;AAAM,IAAA,KAAN;AAAa,IAAA;AAAb,MAA6B,aAAa,CAAC,QAAD,CAAhD;AAEA,MAAI,KAAK,GAAG,OAAO,EAAE,GAArB;AACA,MAAI,KAAJ,EAAW,KAAK,IAAI,WAAW,KAAK,GAAzB;AACX,SAAO,UAAU,KAAK,IAAI,WAAW,UAArC;AACA;AAED;;;;;;;;;AAQM,SAAU,oBAAV,CACL,QADK,EAEL,IAFK,EAEO;AAEZ,QAAM,cAAc,GAAG,IAAI,GAAJ,EAAvB;AAEA,QAAM,EAAE,GAAG,iDAAX;AACA,MAAI,OAAJ,CALY;;AAQZ,SAAO,CAAC,OAAO,GAAG,EAAE,CAAC,IAAH,CAAQ,IAAR,CAAX,KAA6B,IAApC,EAA0C;AACzC,UAAM,mBAAmB,GAAG,OAAO,CAAC,CAAD,CAAnC;AACA,UAAM,2BAA2B,GAChC,mBAAmB,CAAC,CAAD,CAAnB,KAA2B,GAA3B,IAAkC,mBAAmB,CAAC,CAAD,CAAnB,KAA2B,GAA7D,GACG,mBAAmB,CAAC,KAApB,CAA0B,CAA1B,EAA6B,CAAC,CAA9B,CADH,GAEG,mBAHJ;AAIA,IAAA,2BAA2B,CACzB,IADF,GAEE,KAFF,CAEQ,KAFR;AAAA,KAGE,OAHF,CAGW,SAAD,IAAe,cAAc,CAAC,GAAf,CAAmB,SAAnB,CAHzB;AAIA;;AAED,QAAM,oBAAoB,GAAG,QAAQ,CAAC,SAAT,CAAmB,GAAnB,CAAwB,QAAD,IAAc;AAEjE,eAAa,IAAb,CAAkB,QAAlB,EAA6B,CAA7B,CAFiE,EAGjE,QAHiE,CAArC,CAA7B;AAMA,SAAO,EACN,GAAG,QADG;AAGN,IAAA,SAAS,EAAE,oBAAoB,CAC7B,MADS,CACF,CAAC,CAAC,SAAD,EAAY,QAAZ,CAAD,KAA0B;AACjC,UAAI,cAAc,CAAC,GAAf,CAAmB,SAAnB,CAAJ,EAAmC,OAAO,IAAP,CADF;;AAIjC,YAAM,6BAA6B,GAClC,QAAQ,CAAC,CAAD,CAAR,KAAgB,GAAhB,IACA,QAAQ,CAAC,CAAD,CAAR,KAAgB,GADhB,IAEA,oBAAoB,CAAC,IAArB,CACC,CAAC,CAAC,cAAD,EAAiB,aAAjB,CAAD,KACC,cAAc,CAAC,GAAf,CAAmB,cAAnB,KACA,aAAa,CAAC,QAAd,CAAuB,SAAvB,CAHF,CAHD;AAQA,UAAI,6BAAJ,EAAmC,OAAO,IAAP;AAEnC,aAAO,KAAP;AACA,KAhBS,EAiBT,GAjBS,CAiBL,CAAC,GAAG,QAAH,CAAD,KAAkB,QAjBb;AAHL,GAAP;AAsBA;;;;"}