Skip to content

Instantly share code, notes, and snippets.

@mattmccray
Last active November 28, 2021 21:31
Show Gist options
  • Save mattmccray/8b0b67fa93a3d021d7c6e6e1773b6db7 to your computer and use it in GitHub Desktop.
Save mattmccray/8b0b67fa93a3d021d7c6e6e1773b6db7 to your computer and use it in GitHub Desktop.
Simple and tiny CSS in JS
interface IConfiguration {
append: 'each' | 'batch'
debug: boolean
appendTo: Element
replaceRegExp: RegExp
}
let _namePrefix = 'css'
let _pendingStyles: string | null = null
let _config: IConfiguration = {
debug: false,
append: 'batch',
appendTo: document.head,
replaceRegExp: /&/g,
}
const _elemBuilder = () => document.createElement('style')
const SUPPORTS_CONSTRUCTABLE_STYLESHEETS = (() => {
try {
var sheet = new CSSStyleSheet()
return (
'adoptedStyleSheets' in document &&
'replace' in sheet
)
}
catch (err) {
// console.warn("Constructable StyleSheets not supported by this browser.")
return false
}
})();
export function configure(options: IConfiguration) {
_config = Object.assign({}, _config, options)
}
export function css(template: TemplateStringsArray, ...params: any[]): string {
const filename = _getFilename()
const id = `${_namePrefix}_${uid()}`
const className = `.${id}`
let styles = _config.debug ? `/* Source: ${filename} */\n` : ''
for (let i = 0; i < params.length; i++) {
styles += _substituteClassname(template[i], className);
styles += String(params[i])
}
if (template.length > params.length) {
styles += _substituteClassname(template[template.length - 1], className);
}
if (_config.debug) console.trace(_config.append, styles)
_appendStyles(styles)
return id;
}
export function assignName(name: string, builder: () => string): string {
const previousPrefix = _namePrefix
_namePrefix = name
const className = builder()
_namePrefix = previousPrefix
return className
}
css.config = configure
css.withName = assignName
export default css
export function classBuilder<T extends string>(id: string) {
let apply = (...extra: (T | Partial<Record<T, any>>)[]) => {
let className = [id]
extra.forEach(item => {
if (!!item) return
else if (typeof item === 'string') className.push(item)
else Object.keys(item).forEach(key => {
if (item[key]) className.push(key)
})
})
return className.join(' ')
}
apply.toString = () => id
return apply
}
function _substituteClassname(source: string, className: string): string {
return source.replace(_config.replaceRegExp, className)
}
function _appendStyles(styles: string, createElem: () => HTMLStyleElement = _elemBuilder) {
if (_config.append == 'each') {
_createAndAppendStyleElement(styles, createElem)
}
else {
if (_pendingStyles === null) {
_pendingStyles = styles
setTimeout(() => {
_createAndAppendStyleElement(_pendingStyles, createElem)
_pendingStyles = null
}, 0)
}
else {
_pendingStyles = _pendingStyles + `\n` + styles
}
}
}
function _createAndAppendStyleElement(styles: string, createElem: () => HTMLStyleElement) {
if (SUPPORTS_CONSTRUCTABLE_STYLESHEETS && !_config.debug) {
const stylesheet: any = new CSSStyleSheet()
stylesheet.replace(styles)
//@ts-ignore
document.adoptedStyleSheets = [...document.adoptedStyleSheets, stylesheet]
return
}
const target = createElem()
target.setAttribute('type', 'text/css')
target.setAttribute('id', `css_container_${uid()}`)
target.innerHTML = styles
_config.appendTo.appendChild(target)
if (_config.debug) console.trace({ styles })
}
const _startingDate = new Date(2021, 0, 1).getTime()
let _prevUid = 0
export function uid(radix: number = 36): string {
let now = Date.now() - _startingDate
while (now <= _prevUid) { now += 1 }
_prevUid = now
return _prevUid.toString(radix)
}
// Only works well with Chromium browsers...
function _getFilename() {
let stack = _getStack()
let filename = 'unknown'
try {
let line = stack.split('\n')[4].trim()
let parts = line.split('/')
filename = parts[parts.length - 1]
filename = filename.split("?")[0]
}
catch (ignore) { }
return filename
}
function _getStack() {
let stack = ''
try {
throw new Error('test')
}
catch (err) {
stack = err.stack ?? ''
}
return stack
}
import { css } from './css'
export const MyComponent = (props: {}) => {
return (
<div className={MyComponent.className}></div>
)
}
MyComponent.className = css`
& {
padding: 12px;
}
`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment