Skip to content

Instantly share code, notes, and snippets.

@0x009922
Last active October 21, 2022 00:59
Show Gist options
  • Save 0x009922/cb0d8763a6e0892e3e2a2be000f59af2 to your computer and use it in GitHub Desktop.
Save 0x009922/cb0d8763a6e0892e3e2a2be000f59af2 to your computer and use it in GitHub Desktop.
import { CamelCase, Simplify } from 'type-fest'
declare const create: <B extends string>(blockName: B) => BlockBuilder<B>
interface BlockBuilder<R extends string, M extends ModifiersArray = [], E extends BuiltElem<any, any>[] = []> {
mod: {
<B extends AnyBlockBuilder, M extends string>(this: B, name: M): ExtendsBlockModifiers<B, M>
<B extends BlockBuilder<any, any>, M extends string, V extends string>(
this: B,
modName: M,
modVal: V,
): ExtendsBlockModifiers<B, M, V>
}
elem: <B extends AnyBlockBuilder, E extends string>(this: B, elemName: E) => ElemBuilder<B, E>
finish: <B extends AnyBlockBuilder>(this: B, mode: 'classic') => BuildBEM<B>
}
type AnyBlockBuilder = BlockBuilder<any, any, any>
interface ElemBuilder<B extends BlockBuilder<any, any>, E extends string, M extends ModifiersArray = []> {
mod: {
<B extends ElemBuilder<any, any, any>, M extends string>(this: B, modName: M): ExtendElemModifiers<B, M>
<B extends ElemBuilder<any, any, any>, M extends string, V extends string>(
this: B,
modName: M,
modVal: V,
): ExtendElemModifiers<B, M, V>
}
finish: <B extends ElemBuilder<any, any, any>>(this: B) => FinishElemBuilder<B>
}
type FinishElemBuilder<B extends ElemBuilder<any, any, any>> = B extends ElemBuilder<
BlockBuilder<infer BlockName, infer BlockModifiers, infer BlockElems>,
infer ElemName,
infer ElemModifiers
>
? BlockBuilder<BlockName, BlockModifiers, [...BlockElems, BuiltElem<ElemName, ElemModifiers>]>
: null
interface BuiltElem<N extends string, M extends ModifiersArray> {
elemName: N
modifiers: M
}
interface Modifier<M extends string, V extends string> {
kind: 'mod'
mod: M
val: V
}
interface BoolModifier<M extends string> {
kind: 'bool'
mod: M
}
type ExtendElemModifiers<E, M extends string, V extends string | null = null> = E extends ElemBuilder<
infer B,
infer E,
infer Current
>
? ElemBuilder<B, E, AddModifier<Current, M, V>>
: never
type ExtendsBlockModifiers<B, M extends string, V extends string | null = null> = B extends BlockBuilder<
infer R,
infer Current,
infer Elems
>
? BlockBuilder<R, AddModifier<Current, M, V>, Elems>
: never
type ModifiersArray = Array<Modifier<any, any> | BoolModifier<any>>
type AddModifier<Current extends ModifiersArray, M extends string, V extends string | null = null> = [
...Current,
V extends string ? Modifier<M, V> : BoolModifier<M>,
]
type BuildBEM<B> = B extends BlockBuilder<infer R, infer M, infer E>
? Simplify<ExpandModifiers<`${CamelCase<R>}_`, M> & ExpandElems<`${CamelCase<R>}__`, E>>
: never
type ExpandModifiers<Pre extends string, M> = M extends [infer M, ...infer Tail]
? (M extends BoolModifier<infer M>
? {
[K in `${Pre}${CamelCase<M>}`]: `${Pre}_${M}`
}
: M extends Modifier<infer M, infer V>
? {
[K in `${Pre}${CamelCase<M>}_${CamelCase<V>}`]: `${Pre}_${M}_${V}`
}
: never) &
ExpandModifiers<Pre, Tail>
: {}
type ExpandElems<Pre extends string, E> = E extends [BuiltElem<infer E, infer M>, ...infer Tail]
? { [K in `${Pre}${CamelCase<E>}`]: string } & ExpandModifiers<`${Pre}${CamelCase<E>}_`, M> & ExpandElems<Pre, Tail>
: {}
type Test1 = ExpandModifiers<'nya', [BoolModifier<'load'>, Modifier<'a', 'b'>]>
type Test2 = ExpandElems<'hoh', []>
type Test3 = ExpandElems<'hoh__', [BuiltElem<'yay', [Modifier<'mod', 'val'>]>]>
type Test4 = FinishElemBuilder<ElemBuilder<BlockBuilder<'block', [], []>, 'elem', []>>
const bem4 = create('block').elem('elem').finish().finish('classic')
const bem = create('my-component')
.mod('loading')
.mod('disabled')
.mod('theme', 'light')
.mod('theme', 'dark')
.elem('input')
.finish()
.elem('button')
.mod('loading')
.mod('bg', '1')
.mod('bg', '2')
.finish()
.finish('classic')
// type `bem.`
import { Simplify } from 'type-fest'
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false
type Expect<T extends true> = T
declare const defineBem: <B extends string>(blockName: B) => BlockBuilder<B>
type BemStyle = 'classic' | 'two-dashes'
type ApplyStyleToModifier<
style extends BemStyle,
m extends ModifierBool<any> | ModifierKeyValue<any, any>,
> = m extends ModifierBool<infer k>
? k
: m extends ModifierKeyValue<infer k, infer v>
? style extends 'classic'
? `${k}_${v}`
: style extends 'two-dashes'
? `${k}--${v}`
: never
: never
type ApplyStyleToModifierPrefix<style extends BemStyle, r extends string> = style extends 'classic'
? `${r}_`
: style extends 'two-dashes'
? `${r}--`
: never
type ApplyStyleToElementPrefix<style extends BemStyle, r extends string> = `${r}__`
interface BlockBuilder<r extends string, mm extends ModifiersArray = [], ee extends BuiltElem<any, any>[] = []> {
mod: {
<m extends string>(key: m): ExtendBlockWithModifier<BlockBuilder<r, mm, ee>, m>
<m extends string, v extends string>(key: m, value: v): ExtendBlockWithModifier<BlockBuilder<r, mm, ee>, m, v>
}
elem: {
<n extends string>(name: n): ExtendBlockWithElement<BlockBuilder<r, mm, ee>, BuiltElem<n, []>>
<n extends string, e extends ElemBuilder<any>>(
name: n,
fn: (elementBuilder: ElemBuilder) => e,
): ExtendBlockWithElement<BlockBuilder<r, mm, ee>, BuildElement<n, e>>
}
build: {
(): BuildClasses<BlockBuilder<r, mm, ee>, 'classic'>
<s extends BemStyle>(style: s): BuildClasses<BlockBuilder<r, mm, ee>, s>
}
}
type AnyBlockBuilder = BlockBuilder<any, any, any>
type ExtendBlockWithElement<b extends AnyBlockBuilder, e extends BuiltElem<any, any>> = b extends BlockBuilder<
infer r,
infer m,
infer ee
>
? BlockBuilder<r, m, [...ee, e]>
: never
type BuildElement<n extends string, b extends ElemBuilder<any>> = b extends ElemBuilder<infer m>
? BuiltElem<n, m>
: never
interface ElemBuilder<mm extends ModifiersArray = []> {
mod: {
<k extends string>(key: k): ExtendElemWithModifier<ElemBuilder<mm>, k>
<k extends string, v extends string>(key: k, value: v): ExtendElemWithModifier<ElemBuilder<mm>, k, v>
}
}
interface BuiltElem<n extends string, m extends ModifiersArray> {
name: n
modifiers: m
}
interface ModifierKeyValue<M extends string, V extends string> {
kind: 'modifier-k-v'
key: M
value: V
}
interface ModifierBool<M extends string> {
kind: 'modifier-bool'
key: M
}
type ExtendElemWithModifier<
e extends ElemBuilder<any>,
k extends string,
v extends string | null = null,
> = e extends ElemBuilder<infer m> ? ElemBuilder<AddModifier<m, k, v>> : never
type ExtendBlockWithModifier<
builder extends AnyBlockBuilder,
key extends string,
value extends string | null = null,
> = builder extends BlockBuilder<infer root, infer modifiers, infer elements>
? BlockBuilder<root, AddModifier<modifiers, key, value>, elements>
: never
type ModifiersArray = Array<ModifierKeyValue<any, any> | ModifierBool<any>>
type AddModifier<m extends ModifiersArray, k extends string, v extends string | null = null> = [
...m,
v extends string ? ModifierKeyValue<k, v> : ModifierBool<k>,
]
type test1 = Expect<Equal<AddModifier<[], 'foo'>, [ModifierBool<'foo'>]>>
type test2 = Expect<Equal<AddModifier<[], 'foo', 'bar'>, [ModifierKeyValue<'foo', 'bar'>]>>
type test3 = Expect<
Equal<AddModifier<[ModifierBool<'foo'>], 'foo', 'bar'>, [ModifierBool<'foo'>, ModifierKeyValue<'foo', 'bar'>]>
>
type BuildClasses<b extends AnyBlockBuilder, style extends BemStyle> = b extends BlockBuilder<
infer r,
infer mm,
infer ee
>
? Simplify<BuildModifierClasses<r, mm, style> & BuildElementsTupleClasses<r, ee, style>>
: never
type test11 = Expect<
Equal<
BuildClasses<
BlockBuilder<'ro-ot', [ModifierKeyValue<'ke-y', 'va-lue'>], [BuiltElem<'elem-1', [ModifierBool<'elem-flag'>]>]>,
'classic'
>,
TupleToRecord<['ro-ot_ke-y_va-lue', 'ro-ot__elem-1', 'ro-ot__elem-1_elem-flag']>
>
>
type BuildModifierSuffixes<m extends ModifiersArray, style extends BemStyle> = m extends [
infer h extends ModifierBool<any> | ModifierKeyValue<any, any>,
...infer t extends ModifiersArray,
]
? [ApplyStyleToModifier<style, h>, ...BuildModifierSuffixes<t, style>]
: []
type test7 = Expect<
Equal<
BuildModifierSuffixes<[ModifierBool<'foo'>, ModifierKeyValue<'my-key', 'my-value'>], 'classic'>,
['foo', 'my-key_my-value']
>
>
type ConcatPrefixTuple<t extends string[], p extends string> = t extends [
infer head extends string,
...infer tail extends string[],
]
? [`${p}${head}`, ...ConcatPrefixTuple<tail, p>]
: []
type test8 = Expect<
Equal<
ConcatPrefixTuple<['foo', 'my-key_my-value'], 'my-component_'>,
['my-component_foo', 'my-component_my-key_my-value']
>
>
type TupleToRecordRecur<t extends any[]> = t extends [infer head extends string, ...infer tail]
? { [K in head]: K } & TupleToRecordRecur<tail>
: {}
type TupleToRecord<t extends any[]> = Simplify<TupleToRecordRecur<t>>
type test9 = Expect<
Equal<
TupleToRecord<['a', 'b', 'c']>,
{
a: 'a'
b: 'b'
c: 'c'
}
>
>
type BuildModifierClasses<r extends string, m extends ModifiersArray, style extends BemStyle> = TupleToRecord<
ConcatPrefixTuple<BuildModifierSuffixes<m, style>, ApplyStyleToModifierPrefix<style, r>>
>
type test10 = Expect<
Equal<
BuildModifierClasses<'r-oot', [ModifierBool<'bool'>, ModifierKeyValue<'key', 'value'>], 'classic'>,
TupleToRecord<['r-oot_bool', 'r-oot_key_value']>
>
>
type BuildElementClasses<r extends string, e extends BuiltElem<any, any>, style extends BemStyle> = e extends BuiltElem<
infer n,
infer mm
>
? `${ApplyStyleToElementPrefix<style, r>}${n}` extends infer elem extends string
? TupleToRecord<
[elem, ...ConcatPrefixTuple<BuildModifierSuffixes<mm, style>, ApplyStyleToModifierPrefix<style, elem>>]
>
: never
: never
type BuildElementsTupleClassesRecur<
r extends string,
ee extends BuiltElem<any, any>[],
style extends BemStyle,
> = ee extends [infer head extends BuiltElem<any, any>, ...infer tail extends BuiltElem<any, any>[]]
? BuildElementClasses<r, head, style> & BuildElementsTupleClassesRecur<r, tail, style>
: {}
type BuildElementsTupleClasses<r extends string, ee extends BuiltElem<any, any>[], style extends BemStyle> = Simplify<
BuildElementsTupleClassesRecur<r, ee, style>
>
type test12 = Expect<
Equal<
BuildElementClasses<'root', BuiltElem<'elem-1', [ModifierKeyValue<'key', 'value'>]>, 'classic'>,
TupleToRecord<['root__elem-1', 'root__elem-1_key_value']>
>
>
const bem4 = defineBem('block').elem('elem').build('classic')
type test5 = Expect<Equal<typeof bem4, TupleToRecord<['block__elem']>>>
const myComponent = defineBem('my-component')
.mod('loading')
.mod('disabled')
.mod('theme', 'light')
.mod('theme', 'dark')
.elem('input')
.elem('button', (b) => b.mod('loading').mod('bg', '1').mod('bg', '2'))
const myComponentClassic = myComponent.build()
type test6 = Expect<
Equal<
typeof myComponentClassic,
TupleToRecord<
[
'my-component_loading',
'my-component_disabled',
'my-component_theme_light',
'my-component_theme_dark',
'my-component__input',
'my-component__button',
'my-component__button_loading',
'my-component__button_bg_1',
'my-component__button_bg_2',
]
>
>
>
const myComponentTwoDashes = myComponent.build('two-dashes')
type test13 = Expect<
Equal<
typeof myComponentTwoDashes,
TupleToRecord<
[
'my-component--loading',
'my-component--disabled',
'my-component--theme--light',
'my-component--theme--dark',
'my-component__input',
'my-component__button',
'my-component__button--loading',
'my-component__button--bg--1',
'my-component__button--bg--2',
]
>
>
>
import { Simplify, CamelCase } from 'type-fest'
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false
type Expect<T extends true> = T
declare const defineBem: <B extends string>(blockName: B) => BlockBuilder<B>
type BemStyle = 'classic' | 'two-dashes'
type RootBlockKey = 'block'
type ApplyStyleToModifier<style extends BemStyle, m extends AnyModifier> = m extends ModifierBool<infer k>
? k
: m extends ModifierKeyValue<infer k, infer v>
? style extends 'classic'
? `${k}_${v}`
: style extends 'two-dashes'
? `${k}--${v}`
: never
: never
type ApplyStyleToModifierPrefix<style extends BemStyle, r extends string> = style extends 'classic'
? `${r}_`
: style extends 'two-dashes'
? `${r}--`
: never
type ApplyStyleToElementPrefix<style extends BemStyle, r extends string> = `${r}__`
interface BlockBuilder<r extends string, mm extends ModifiersArray = [], ee extends BuiltElem<any, any>[] = []> {
mod: {
<b extends AnyBlockBuilder, m extends string>(this: b, key: m): ExtendBlockWithModifier<b, m>
<b extends AnyBlockBuilder, m extends string, v extends string>(this: b, key: m, value: v): ExtendBlockWithModifier<
b,
m,
v
>
}
elem: {
<b extends AnyBlockBuilder, n extends string>(this: b, name: n): ExtendBlockWithElement<b, BuiltElem<n, []>>
<b extends AnyBlockBuilder, n extends string, e extends ElemBuilder<any>>(
this: b,
name: n,
fn: (elementBuilder: ElemBuilder) => e,
): ExtendBlockWithElement<b, BuildElement<n, e>>
}
build: {
(): BuildClasses<BlockBuilder<r, mm, ee>, 'classic'>
<s extends BemStyle>(style: s): BuildClasses<BlockBuilder<r, mm, ee>, s>
}
}
type AnyBlockBuilder = BlockBuilder<any, any, any>
type ExtendBlockWithElement<b extends AnyBlockBuilder, e extends BuiltElem<any, any>> = b extends BlockBuilder<
infer r,
infer m,
infer ee
>
? BlockBuilder<r, m, [...ee, e]>
: never
type BuildElement<n extends string, b extends ElemBuilder<any>> = b extends ElemBuilder<infer m>
? BuiltElem<n, m>
: never
interface ElemBuilder<mm extends ModifiersArray = []> {
mod: {
<b extends AnyElemBuilder, k extends string>(this: b, key: k): ExtendElemWithModifier<b, k>
<b extends AnyElemBuilder, k extends string, v extends string>(this: b, key: k, value: v): ExtendElemWithModifier<
b,
k,
v
>
}
}
type AnyElemBuilder = ElemBuilder<any>
interface BuiltElem<n extends string, m extends ModifiersArray> {
name: n
modifiers: m
}
interface ModifierKeyValue<M extends string, V extends string> {
kind: 'modifier-k-v'
key: M
value: V
}
interface ModifierBool<M extends string> {
kind: 'modifier-bool'
key: M
}
type AnyModifier = ModifierBool<any> | ModifierKeyValue<any, any>
type ExtendElemWithModifier<
e extends ElemBuilder<any>,
k extends string,
v extends string | null = null,
> = e extends ElemBuilder<infer m> ? ElemBuilder<AddModifier<m, k, v>> : never
type ExtendBlockWithModifier<
builder extends AnyBlockBuilder,
key extends string,
value extends string | null = null,
> = builder extends BlockBuilder<infer root, infer modifiers, infer elements>
? BlockBuilder<root, AddModifier<modifiers, key, value>, elements>
: never
type ModifiersArray = Array<ModifierKeyValue<any, any> | ModifierBool<any>>
type AddModifier<m extends ModifiersArray, k extends string, v extends string | null = null> = [
...m,
v extends string ? ModifierKeyValue<k, v> : ModifierBool<k>,
]
type test1 = Expect<Equal<AddModifier<[], 'foo'>, [ModifierBool<'foo'>]>>
type test2 = Expect<Equal<AddModifier<[], 'foo', 'bar'>, [ModifierKeyValue<'foo', 'bar'>]>>
type test3 = Expect<
Equal<AddModifier<[ModifierBool<'foo'>], 'foo', 'bar'>, [ModifierBool<'foo'>, ModifierKeyValue<'foo', 'bar'>]>
>
type BuildClasses<b extends AnyBlockBuilder, style extends BemStyle> = b extends BlockBuilder<
infer r,
infer mm,
infer ee
>
? Simplify<{ [k in RootBlockKey]: r } & BuildModifierClasses<r, mm, style> & BuildElementsTupleClasses<r, ee, style>>
: never
type test11 = Expect<
Equal<
BuildClasses<
BlockBuilder<'ro-ot', [ModifierKeyValue<'ke-y', 'va-lue'>], [BuiltElem<'elem-1', [ModifierBool<'elem-flag'>]>]>,
'classic'
>,
{
block: 'ro-ot'
block_keY_vaLue: 'ro-ot_ke-y_va-lue'
block__elem1: 'ro-ot__elem-1'
block__elem1_elemFlag: 'ro-ot__elem-1_elem-flag'
}
>
>
type BuildModifierSuffixes<m extends ModifiersArray, style extends BemStyle> = m extends [
infer h extends ModifierBool<any> | ModifierKeyValue<any, any>,
...infer t extends ModifiersArray,
]
? [ApplyStyleToModifier<style, h>, ...BuildModifierSuffixes<t, style>]
: []
type test7 = Expect<
Equal<
BuildModifierSuffixes<[ModifierBool<'foo'>, ModifierKeyValue<'my-key', 'my-value'>], 'classic'>,
['foo', 'my-key_my-value']
>
>
type AnyModifierToKeySuffix<t extends AnyModifier> = t extends ModifierBool<infer k>
? CamelCase<k>
: t extends ModifierKeyValue<infer k, infer v>
? `${CamelCase<k>}_${CamelCase<v>}`
: never
type ModifiersToRecordRecur<r extends string, m extends ModifiersArray, style extends BemStyle> = m extends [
infer head extends AnyModifier,
...infer tail extends ModifiersArray,
]
? {
[K in `${RootBlockKey}_${AnyModifierToKeySuffix<head>}`]: `${ApplyStyleToModifierPrefix<
style,
r
>}${ApplyStyleToModifier<style, head>}`
} & ModifiersToRecordRecur<r, tail, style>
: {}
type BuildModifierClasses<r extends string, m extends ModifiersArray, style extends BemStyle> = Simplify<
ModifiersToRecordRecur<r, m, style>
>
type test10 = Expect<
Equal<
BuildModifierClasses<'s-table', [ModifierBool<'bool'>, ModifierKeyValue<'my-key', 'my-value'>], 'classic'>,
{
block_bool: 's-table_bool'
block_myKey_myValue: 's-table_my-key_my-value'
}
>
>
type BuildSingleElementRecord<
style extends BemStyle,
r extends string,
n extends string,
m extends null | AnyModifier = null,
> = {
[k in `${RootBlockKey}__${CamelCase<n>}${m extends AnyModifier
? `_${AnyModifierToKeySuffix<m>}`
: ''}`]: `${ApplyStyleToElementPrefix<style, r>}${m extends AnyModifier
? `${ApplyStyleToModifierPrefix<style, n>}${ApplyStyleToModifier<style, m>}`
: n}`
}
type BuildElementModifierClassesRecur<
r extends string,
n extends string,
mm extends ModifiersArray,
style extends BemStyle,
> = mm extends [infer head extends AnyModifier, ...infer tail extends ModifiersArray]
? BuildSingleElementRecord<style, r, n, head> & BuildElementModifierClassesRecur<r, n, tail, style>
: {}
type BuildElementClasses<r extends string, e extends BuiltElem<any, any>, style extends BemStyle> = e extends BuiltElem<
infer n,
infer mm
>
? Simplify<BuildSingleElementRecord<style, r, n> & BuildElementModifierClassesRecur<r, n, mm, style>>
: never
type BuildElementsTupleClassesRecur<
r extends string,
ee extends BuiltElem<any, any>[],
style extends BemStyle,
> = ee extends [infer head extends BuiltElem<any, any>, ...infer tail extends BuiltElem<any, any>[]]
? BuildElementClasses<r, head, style> & BuildElementsTupleClassesRecur<r, tail, style>
: {}
type BuildElementsTupleClasses<r extends string, ee extends BuiltElem<any, any>[], style extends BemStyle> = Simplify<
BuildElementsTupleClassesRecur<r, ee, style>
>
type test12 = Expect<
Equal<
BuildElementClasses<'s-table', BuiltElem<'elem-1', [ModifierKeyValue<'key', 'value'>]>, 'classic'>,
{
block__elem1: 's-table__elem-1'
block__elem1_key_value: 's-table__elem-1_key_value'
}
>
>
const bem4 = defineBem('block').elem('elem').build('classic')
type test5 = Expect<Equal<typeof bem4, { block: 'block'; block__elem: 'block__elem' }>>
const myComponent = defineBem('my-component')
.mod('loading')
.mod('disabled')
.mod('theme', 'light')
.mod('theme', 'dark')
.elem('input')
.elem('button', (b) => b.mod('loading').mod('bg', '1').mod('bg', '2'))
const myComponentClassic = myComponent.build()
type test6 = Expect<
Equal<
typeof myComponentClassic,
{
block: 'my-component'
block_loading: 'my-component_loading'
block_disabled: 'my-component_disabled'
block_theme_light: 'my-component_theme_light'
block_theme_dark: 'my-component_theme_dark'
block__input: 'my-component__input'
block__button: 'my-component__button'
block__button_loading: 'my-component__button_loading'
block__button_bg_1: 'my-component__button_bg_1'
block__button_bg_2: 'my-component__button_bg_2'
}
>
>
const myComponentTwoDashes = myComponent.build('two-dashes')
type test13 = Expect<
Equal<
typeof myComponentTwoDashes,
{
block: 'my-component'
block_loading: 'my-component--loading'
block_disabled: 'my-component--disabled'
block_theme_light: 'my-component--theme--light'
block_theme_dark: 'my-component--theme--dark'
block__input: 'my-component__input'
block__button: 'my-component__button'
block__button_loading: 'my-component__button--loading'
block__button_bg_1: 'my-component__button--bg--1'
block__button_bg_2: 'my-component__button--bg--2'
}
>
>
@0x009922
Copy link
Author

0x009922 commented Jul 7, 2022

Intellisense:

image

@0x009922
Copy link
Author

How new intellisense works:

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment