Skip to content

Commit

Permalink
#24 Implement js script for bem. Change core to be more performant
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrii Kirmas committed Mar 10, 2021
1 parent fbb5cec commit 990751b
Show file tree
Hide file tree
Showing 10 changed files with 200 additions and 135 deletions.
10 changes: 5 additions & 5 deletions __tests__/core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ describe(resolver.name, () => {
}
)).toStrictEqual([
"hash", "hashless",
"one",
1,
"true",
"array", "object"
[], {}
]))

it("with hash", () => expect(resolver(
Expand All @@ -62,10 +62,10 @@ describe(resolver.name, () => {
array: [], object: {},
}
)).toStrictEqual([
"HASH", "HASHNESS",
"ONE",
"hash", "HASHNESS",
1,
"TRUE",
"ARRAY", "OBJECT"
[], {}
]))

it("nothing is falsy", () => expect(resolver(
Expand Down
8 changes: 4 additions & 4 deletions __tests__/index.miss-use.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe("ctx", () => {
//TODO Recover //@ts-expect-error
...classNaming()
}).toStrictEqual({
className: ""
className: undefined
}))

it("classnames === null", () => expect({
Expand All @@ -26,7 +26,7 @@ describe("ctx", () => {
//@ts-expect-error
...classNaming({classnames: module_css}, {class1: true})
}).toStrictEqual({
className: ""
className: undefined
}))

it("second ctx assign", () => expect({
Expand All @@ -35,7 +35,7 @@ describe("ctx", () => {
classnames: global_css
})
}).toStrictEqual({
className: "classnames"
className: ""
}))
})

Expand Down Expand Up @@ -79,7 +79,7 @@ describe("multi-arg call", () => {
it("not equal hashes", () => expect({
...classNaming({classnames: {class1: "HASH1"}})({class1: "hash1"})
}).toStrictEqual({
className: "HASH1"
className: "hash1"
}))


Expand Down
41 changes: 41 additions & 0 deletions src/bem.core.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { bem2arr, BemAbsraction } from "./bem.core";

describe(bem2arr.name, () => {
describe("singletons", () => {
const suites = {
"block singleton": [
[{block: false }, ""],
[{block: true }, "block"],
[{block: {} }, "" /* or "block" */],
[{block: {$: undefined }}, "block"],
[{block: {$: false }}, "block"],
[{block: {$: { }}}, "block"],
[{block: {$: {mod: false}}}, "block"],
[{block: {$: {mod: true }}}, "block block--mod"],
[{block: {$: {mod: "" }}}, "block"],
[{block: {$: {mod: "val"}}}, "block block--mod--val"],
],
"element singleton": [
[{block: {el: false }}, ""],
[{block: {el: true }}, "block__el"],
[{block: {el: {} }}, "block__el"],
[{block: {el: {mod: false}}}, "block__el"],
[{block: {el: {mod: true }}}, "block__el block__el--mod"],
[{block: {el: {mod: "" }}}, "block__el"],
[{block: {el: {mod: "val"}}}, "block__el block__el--mod--val"],
],
"block and el combine": [
[{block: {
$: {mod: true},
el: true }}, "block block--mod block__el"]
]
} as Record<string, [BemAbsraction, string][]>

Object.entries(suites).forEach(([title, launches]) => describe(title, () => launches
.forEach(([query, output]) => it(
JSON.stringify(query, (_, v) => v === undefined ? "`undefined`" : v),
() => expect(bem2arr(query).join(" ")).toBe(output))
)
))
})
})
62 changes: 62 additions & 0 deletions src/bem.core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const elementDelimiter = "__"
, modDelimiter = "--"
, blockModKey = "$" as const

export type BemAbsraction = {
[block: string]: boolean | {
[blockModKey]?: {
[mod: string]: boolean | string
}
[el: string]: undefined|boolean | {
[mod: string]: boolean | string
}
}
}

export {
bem2arr
}

function bem2arr(query: BemAbsraction) {
const $return: string[] = []

for (const block in query) {
const blockQ = query[block]

if (!blockQ)
continue
if (typeof blockQ !== "object") {
$return.push(block)
continue
}

for (const el in blockQ) {
const elementQ = blockQ[el]
, element = el === blockModKey ? block : `${block}${elementDelimiter}${el}`

if (!elementQ) {
el === blockModKey && $return.push(element)
continue
}

$return.push(element)

if (typeof elementQ !== "object")
continue

for (const mod in elementQ) {
const modValue: string|boolean = elementQ[mod]
if (!modValue)
continue

$return.push(`${element}${modDelimiter}${mod}${
typeof modValue !== "string"
? ""
: `${modDelimiter}${modValue}`
}`)
}
}
}

return $return
}
64 changes: 27 additions & 37 deletions src/bem.test.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,31 @@
import { bem2arr, BemAbsraction } from "./bem";
import { classBeming } from "./bem";

describe(bem2arr.name, () => {
describe("singletons", () => {
const suites = {
"block singleton": [
[{block: false }, ""],
[{block: true }, "block"],
[{block: {} }, "" /* or "block" */],
[{block: {$: undefined }}, "block"],
[{block: {$: false }}, "block"],
[{block: {$: { }}}, "block"],
[{block: {$: {mod: false}}}, "block"],
[{block: {$: {mod: true }}}, "block block--mod"],
[{block: {$: {mod: "" }}}, "block"],
[{block: {$: {mod: "val"}}}, "block block--mod--val"],
],
"element singleton": [
[{block: {el: false }}, ""],
[{block: {el: true }}, "block__el"],
[{block: {el: {} }}, "block__el"],
[{block: {el: {mod: false}}}, "block__el"],
[{block: {el: {mod: true }}}, "block__el block__el--mod"],
[{block: {el: {mod: "" }}}, "block__el"],
[{block: {el: {mod: "val"}}}, "block__el block__el--mod--val"],
],
"block and el combine": [
[{block: {
$: {mod: true},
el: true }}, "block block--mod block__el"]
]
} as Record<string, [BemAbsraction, string][]>
describe("contexting", () => {
describe("empty", () => {
const bem = classBeming()

Object.entries(suites).forEach(([title, launches]) => describe(title, () => launches
.forEach(([query, output]) => it(
JSON.stringify(query, (_, v) => v === undefined ? "`undefined`" : v),
() => expect(bem2arr(query).join(" ")).toBe(output))
)
))
it("1", () => expect(bem({
"block1": true,
"block2": { "el": true }
})).toStrictEqual({
className: "block1 block2__el"
}))
})

describe("full", () => {
const bem = classBeming({
className: "propagated",
classnames: {
"block1": "hash1",
"block2__el": "hash2"
}
})

it("1", () => expect(bem(true, {
"block1": true,
"block2": { "el": true }
})).toStrictEqual({
className: "propagated hash1 hash2"
}))
})
})
93 changes: 37 additions & 56 deletions src/bem.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,43 @@
const elementDelimiter = "__"
, modDelimiter = "--"
, blockModKey = "$" as const

export type BemAbsraction = {
[block: string]: boolean | {
[blockModKey]?: {
[mod: string]: boolean | string
}
[el: string]: undefined|boolean | {
[mod: string]: boolean | string
}
}
}
import type { BemAbsraction } from "./bem.core";
import type { CssModule } from "./definitions.defs";
import { bem2arr } from "./bem.core";
import { joinWithLead, picker } from "./core"
import { EMPTY_OBJECT } from "./consts.json"

export {
bem2arr
classBeming
}

function bem2arr(query: BemAbsraction) {
const $return: string[] = []

for (const block in query) {
const blockQ = query[block]

if (!blockQ)
continue
if (typeof blockQ !== "object") {
$return.push(block)
continue
}

for (const el in blockQ) {
const elementQ = blockQ[el]
, element = el === blockModKey ? block : `${block}${elementDelimiter}${el}`

if (!elementQ) {
el === blockModKey && $return.push(element)
continue
}

$return.push(element)

if (typeof elementQ !== "object")
continue

for (const mod in elementQ) {
const modValue: string|boolean = elementQ[mod]
if (!modValue)
continue
function classBeming<
Ctx extends {classnames: Source, className?: string},
Source extends CssModule = Ctx["classnames"],
// WithClassName extends boolean = Ctx["className"] extends string ? true : false
>(
context: Ctx = EMPTY_OBJECT as Ctx
) {
//@ts-expect-error
const host = (arg0?, arg1?) => bem(context, arg0, arg1)

return host
}

$return.push(`${element}${modDelimiter}${mod}${
typeof modValue !== "string"
? ""
: `${modDelimiter}${modValue}`
}`)
}
}
}

return $return
function bem<
Source extends CssModule,
>(
{
className,
classnames,
}: {
className?: string,
classnames?: Source,
},
arg0?: boolean | BemAbsraction,
arg1?: BemAbsraction
) {
const source = typeof arg0 === "object" ? arg0 : arg1
, debemed = source && bem2arr(source)
, picked = debemed && picker(classnames, debemed)
, result = joinWithLead(arg0 === true && className, picked)

return {className: result}
}
38 changes: 14 additions & 24 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import type { Falsy } from "./ts-swiss.defs"
import { EMPTY_ARRAY } from "./consts.json"
import { stringifyClassNamed } from "./utils"

const {keys: $keys} = Object

export {
wrapper,
resolver,
Expand Down Expand Up @@ -44,32 +42,24 @@ function resolver(
vocabulary: undefined | Record<string, ClassHash>,
actions: Record<string, ClassHash | boolean>
) {
const keys = $keys(actions)

for (let i = keys.length; i--;) {
const key = keys[i]
, act = actions[key]

//TODO #10 Clarify what behaviour to implement
// https://jsbench.me/q8kltjsdwy
const $return: string[] = []

if (act !== undefined && !act) {
// https://jsbench.me/q8kltjsdwy/
//@ts-expect-error
keys[i] = false
continue
}
// https://jsbench.me/prkm3gn4ji
for (const key in actions) {
const act = actions[key]

const hash = vocabulary?.[key]
if (hash !== undefined)
keys[i] = hash
else if (typeof act === "string")
keys[i] = act
if (act === undefined || act === true)
// https://jsbench.me/p3km3fg4e7
$return.push(key)
else if (act)
// https://jsbench.me/p3km3fg4e7
$return.push(act)
}

// https://jsbench.me/9mklnonq0m
const filtered = keys.filter(x => x)

return filtered.length === 0 ? EMPTY_ARRAY : filtered
return $return.length === 0
? EMPTY_ARRAY
: picker(vocabulary, $return)
}

//TODO Consider returning `undefined` on empty string
Expand Down
Loading

0 comments on commit 990751b

Please sign in to comment.