112 lines
3 KiB
TypeScript
112 lines
3 KiB
TypeScript
import { IS_BROWSER } from 'fresh/runtime'
|
|
import { styles } from './SmartStylesheetCommon.tsx'
|
|
|
|
/**
|
|
* Generate a css scope for the given component/island based on its name.
|
|
*
|
|
* @param component - Component or island to scope.
|
|
* @returns scope - css scope class.
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* // ./(components|islands)/Button.tsx
|
|
* import type { JSX } from 'preact'
|
|
* import { getStyleScope, useSmartStylesheet } from './SmartStylesheet.tsx'
|
|
*
|
|
* const scope = getStyleScope(Button)
|
|
*
|
|
* export function Button(props: JSX.ButtonHTMLAttributes) {
|
|
* useSmartStylesheet(import.meta, { scope })
|
|
*
|
|
* return <button class={scope} {...props} />
|
|
* }
|
|
* ```
|
|
*/
|
|
export function getStyleScope<T extends { name: Readonly<string> }>(
|
|
component: T,
|
|
): string {
|
|
// generate scope/class hash
|
|
return `_scope_${
|
|
btoa(component.name).slice(-10).replaceAll('=', '_').toLowerCase()
|
|
}`
|
|
}
|
|
|
|
/**
|
|
* Hook to load component/island stylesheet only when
|
|
* it is imported by the served route.
|
|
* For any `./(component|islands)/Element.tsx` it will
|
|
* load the corresponding `./(component|islands)/Element.css`
|
|
*
|
|
* @param meta - Component ImportMeta used to resolve
|
|
* stylesheet path, name and layer.
|
|
* @param options - CSS scope to use (default: none)
|
|
* and css layer ('components' or 'islands') depending of the ImportMeta.
|
|
*
|
|
* @example Basic usage
|
|
* ```ts
|
|
* // ./(components|islands)/Button.tsx
|
|
* import type { JSX } from 'preact'
|
|
* import { useSmartStylesheet } from './SmartStylesheet.tsx'
|
|
*
|
|
* export function Button(props: JSX.ButtonHTMLAttributes) {
|
|
* useSmartStylesheet(import.meta)
|
|
*
|
|
* return <button class={scope} {...props} />
|
|
* }
|
|
* ```
|
|
*
|
|
* @example Use css scope
|
|
* ```ts
|
|
* // ./(components|islands)/Button.tsx
|
|
* import type { JSX } from 'preact'
|
|
* import { getStyleScope, useSmartStylesheet } from './SmartStylesheet.tsx'
|
|
*
|
|
* const scope = getStyleScope(Button)
|
|
*
|
|
* export function Button(props: JSX.ButtonHTMLAttributes) {
|
|
* useSmartStylesheet(import.meta, { scope })
|
|
*
|
|
* return <button class={scope} {...props} />
|
|
* }
|
|
* ```
|
|
*
|
|
* @example Use custom layer
|
|
* ```ts
|
|
* // ./(components|islands)/Button.tsx
|
|
* import type { JSX } from 'preact'
|
|
* import { getStyleScope, useSmartStylesheet } from './SmartStylesheet.tsx'
|
|
*
|
|
* const scope = getStyleScope(Button)
|
|
*
|
|
* export function Button(props: JSX.ButtonHTMLAttributes) {
|
|
* useSmartStylesheet(import.meta, { scope, layer: 'custom' })
|
|
*
|
|
* return <button class={scope} {...props} />
|
|
* }
|
|
* ```
|
|
*/
|
|
export function useSmartStylesheet(
|
|
meta: ImportMeta,
|
|
options?: { scope?: string; layer?: string },
|
|
) {
|
|
if (IS_BROWSER) return
|
|
|
|
// resolve filename
|
|
const css = meta.filename
|
|
?.replace('.tsx', '.css')
|
|
.replace(Deno.cwd(), '')
|
|
.replaceAll('\\', '/')
|
|
|
|
if (!css) return
|
|
if (styles.has(css)) return
|
|
|
|
// set css layer
|
|
const layerValue = options?.layer ?? css.includes('/components/')
|
|
? 'components'
|
|
: css.includes('/islands/')
|
|
? 'islands'
|
|
: undefined
|
|
|
|
styles.set(css, { url: css, scope: options?.scope, layer: layerValue })
|
|
}
|