Advanced CSS-in-TS



Hello, my name is Dmitry Karlovsky and I am ... the author of one of the first frameworks entirely written in typescript - $ mol . It makes the most of static typing capabilities. And today we will talk about the most rigid static fixing of styles.



This is a transcript of a talk at PiterJS # 46 . You can either watch a video recording , or open it in the presentation interface , or read as an article ...





$my_profile, : . : .







..



$my_profile $mol_view sub /
    <= Menu $my_panel
    <= Details $my_panel

$my_panel $mol_view sub /
    <= Head $mol_view
    <= Body $mol_scroll


. , , typescript ..



interface $my_profile extends $mol_view {
    Menu(): $my_panel
    Details(): $my_panel
} )

interface $my_panel extends $mol_view {
    Head(): $mol_view
    Body(): $mol_scroll
} )


— , . , , . .



DOM



, , DOM-. , DOM-. dom_name. , , . , , , .



interface $my_profile extends $mol_view {
    Menu(): $my_panel
    Details(): $my_panel
} )

interface $my_panel extends $mol_view {
    Head(): $mol_view
    Body(): $mol_scroll
} )


<mol_view
    mol_view
    my_panel_body
    my_profile_details_body
    >

    <mol_button_major
        mol_view
        mol_button
        mol_button_major
        my_profile_signup
        >


:



mol_view — , , , reset , , .

my_panel_body — Body $my_panel.

my_profile_details_body — $my_panel Details $my_profile.





, . , , .



<mol_view
    mol_view
    my_panel_body
    my_profile_details_body
    >

    <mol_button_major
        mol_view
        mol_button
        mol_button_major
        my_profile_signup
        >


[my_profile_details_body] {
    overflow: 'overlay';
}

[my_profile_details_body] [mol_button] {
    border-radius: .5rem;
}


CSS . , . , . , .





CSS.



[my_profile_details_body] {
    overflow: 'overlay';
}

[my_profile_details_body] [mol_button] {
    border-radius: .5rem;
}


$mol_style_define( $my_profile , {
    Details: {
        Body: {
            overflow: 'overlay',
            $mol_button: {
                border: {
                    radius: rem(.5),
                },
            },
        },
    },
} )


$mol_style_define, StyleSheet. $my_profile JSON, , Details Body -, — -.



CSSOM: DevTools



: CSSOM, CSS style. , Chrome Dev Tools , . .





, aphrodite, — CSS. , , $mol_view.



CSS — . 3 . ..



CSSStyleDeclaration:



CSSStyleDeclaration, . 500 , string.



type CSSStyleDeclaration = {
    display: string
    // 500 lines
}


, - , .



{
    display: 'black' // 
}


csstype:



csstype, MDN. ..



type DisplayProperty =
| Globals
| DisplayOutside
| DisplayInside
| DisplayInternal
| DisplayLegacy
| "contents" | "list-item" | "none"
| string


, , ..



type DisplayProperty = string


, , .



csstype@3:



, "" — string , - :



type Display =
| Globals
| DisplayOutside
| DisplayInside
| DisplayInternal
| DisplayLegacy
| "contents" | "list-item" | "none"
| (string & {})


, ..



{
    display: 'black' // 
}




, . Properties ..



interface Properties {

    /**
     * Whether an element is treated as a block or inline element
     * and the layout used for its children, such as flow layout, grid or flex.
     */
    display?: 'block' | 'inline' | 'none' | ... | Common

    // etc
}

type Common = 'inherit' | 'initial' | 'unset'




, , , 500 CSS-.







CSS , ..



overflow: {
    x: 'auto' ,
    y: 'scroll',
    anchor: 'none',
}


interface Properties {
    overflow? : {
        x?:  Overflow | Common
        y?:  Overflow | Common
        anchor?: 'auto' | 'none' | Common
    }
}

type Overflow = 'visible' | 'hidden' | ... | Common




, (1px, 2rem) (calc(1rem + 1px)).



interface Properties {
    width?: Size
    height?: Size
}

type Size =
| 'auto' | 'max-content' | 'min-content' | 'fit-content'
| Length | Common

type Length = 0 | Unit< 'rem' | ... | '%' > | Func<'calc'>




, .



class Unit< Lit extends string > {

    constructor(
        readonly val: number,
        readonly lit: Lit,
    ) { }

    toString() {
        return this.val + this.lit
    }

}


, , , , , .



function rem( val : number ) {
    return new Unit( val , 'rem' )
}

{
    width: rem(1) // Unit<'rem'>
}




. , .



Func<
    Name extends string,
    Value = unknown,
> {

    constructor(
        readonly name: Name,
        readonly val: Value,
    ) { }

    toString() {
        return `${ this.name }(${ this.val })`
    }

}


function calc( val : string ) {
    return new Func( 'calc' , val )
}

{
    // Func< 'calc' , string >
    width: calc( '1px + 1em' )
}




CSS . , margin 1 4 . 1 2 - , : 4, margin-left — , 3, . , , — . , .



interface Properties {
    margin?: Directions<Length>
    padding?: Directions<Length>
}

type Directions< Value > =
| Value
| [ Value , Value ]
| {
    top?: Value ,
    right?: Value ,
    bottom?: Value ,
    left?: Value ,
}


margin: rem(.5)

padding: [ 0 , rem(.5) ]

margin: {
    top: 0,
    right: rem(.5),
    bottom: rem(.5),
    left: rem(.5),
}




$mol_colors — . ..



type Color =
| keyof typeof $mol_colors
| 'transparent' | 'currentcolor'
| $mol_style_func< 'hsla' | 'rgba' | 'var' >

color?: Color | Common


{
    color: 'snow',
    background: {
        color: hsla( 0 , 0 , 50 , .1 ),
    },
}


hsl rgb , hsla rgba — , .





, . , ..



background?: {
    image?: [ $mol_style_func<'url'> ][]
}


background: {
    image: [
        [url('/foo.svg')],
        [url('/bar.svg')],
    ],
},


, , : , . (, ), . .





— ..



box?: {

    shadow?: readonly {
        inset: boolean
        x: Length
        y: Length
        blur: Length
        spread: Length
        color: Color
    }[]

}


box: {
    shadow: [
        {
            inset: true,
            x: 0,
            y: 0,
            blur: rem(.5),
            spread: 0,
            color: 'black',
        },
    ],
},


, , . , . — , , , , .



-



, . $my_profile, Details, $my_panel, Body, $mol_scroll. , , $my_profile.



interface $my_profile {
    Details(): $my_panel
} )

interface $my_panel {
    Body(): $mol_scroll
} )


$mol_style_define( $my_profile , {
    padding: rem(1),

    Details: {
        margin: 'auto',

        Body: {
            overflow: 'scroll',
        },
    },
} )


, , .



-



$mol_type_pick, , , , .



$mol_type_pick<
    $my_profile,
    ()=> $mol_view,
>


interface $my_profile extends $mol_view {
    title(): string
    Menu(): $my_panel
    Details(): $my_panel
} )


{
    Menu(): $my_panel
    Details(): $my_panel
}


, title, . . Menu Details .



-



, . , , — .



interface $my_profile {
    Menu(): $my_panel
    Details(): $my_panel
} )

interface $mol_button {
} )


$mol_style_define( $my_profile , {

    $mol_button: {
        color: 'red',
    },

    Details: {
        $mol_button: {
            color: 'blue',
        },
    },
} )




, , $. $mol_type_pick, .



type $mol_view_all = $mol_type_pick<
    typeof $,
    $mol_view,
>


namespace $ {
    export class $mol_view {}
    export class $my_panel {}
    export class $mol_tree {}
}


{
    $mol_view: typeof $mol_view
    $my_panel: typeof $my_panel
}




, - . — .



function $mol_style_define<
    View extends typeof $mol_view,
    Config extends Styles< View >
>(
    view : View,
    config : Config
)


type Config< View extends $mol_view > = {
    $my_panel: {
        $my_panel: {
            ...
        }
        $my_deck: {
            $my_panel: {
                ...
            }
        }
        ...
    }
    ...
}


, , . . , .





, , .







, JSON, , . .



function $mol_style_define<
    View extends typeof $mol_view,
    Config extends StylesGuard<
        View,
        Config,
    >
>(
    view: View,
    config: Config
)


{
    Details: {
        foo: 'bar',
    },
}


{
    Details: {
        foo: never,
    },
}


. , , .





never , , , .







never , .





, , , — - , , , .





, . $mol_view .



attr *
    ^
    mol_link_current <= current false


attr() {
    return {
        ... super.attr(),
        mol_link_current: this.current(),
    }
}




: -> -> -> (, ).



{
    '@': {
        mol_link_current: {
            true: {
                zIndex: 1
            }
        }
    }
}




, ..



{
    ':hover': {
        zIndex: 1
    }
}




-. , CSS, - .



{
    $mol_scroll: {
        overflow: 'scroll',
        '@media': {
            'print': {
                overflow: 'visible',
            },
        },
    },
}




, .



{
    '>': {

        $mol_view: {
            margin: rem(1),
        },

        $mol_button: {
            margin: rem(0),
        },

    },
}








  • ( )
  • ( 10 500)
  • calc ( )


$mol



, $mol, mol_style_all, CSS-.



import {
    $mol_style_unit,
    $mol_style_func,
    $mol_style_properties,
} from 'mol_style_all'

const { em , rem } = $mol_style_unit
const { calc } = $mol_style_func

const props : $mol_style_properties = {
    margin: [ em(1) , rem(1) ],
    height: calc('100% - 1rem'),
}


codesandbox.io/s/molstyleall-ked9t



, , CSS, , $mol_view. , , mam_mol — , .



...



, , . , , , ..











  • . . , , , pixel-perfect.
  • ! , .
  • , .
  • , .



All Articles