-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: simplify classy; nix stack tracing
- Loading branch information
1 parent
d48a21e
commit 565126c
Showing
12 changed files
with
311 additions
and
201 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`Classy BEM Expansion Skips pre-scoped classes that end in \`-scss\`. 1`] = `"PRESCOPED"`; |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/* eslint-disable no-param-reassign | ||
*/ | ||
import isObject from "lodash/isPlainObject"; | ||
import flat from "lodash/flattenDeep"; | ||
|
||
export const SEPARATORS = ["-", "_"]; | ||
|
||
const splitClassStrings = (classes) => | ||
flat( | ||
classes.map((c) => { | ||
return typeof c === "string" ? c?.split(/[\s,.]/g) : c; | ||
}) | ||
); | ||
|
||
const expandBEMPartials = (classname, namespace) => { | ||
if (!(classname && namespace)) return classname; | ||
|
||
/* Replace Sass-style root selectors (&) | ||
* with the BEM namespace... | ||
*/ | ||
if (classname[0] === "&") return classname.replace("&", namespace); | ||
|
||
/* Prefix BEM separator "partials" | ||
* with the BEM namespace... | ||
*/ | ||
if (SEPARATORS.includes(classname[0])) { | ||
if (!classname.includes("-scss")) return `${namespace}${classname}`; | ||
} | ||
|
||
return classname; | ||
}; | ||
|
||
export function classy(...args) { | ||
/* When instantiated with the `new` keyword, | ||
* construct a faux-instance of Classy that | ||
* can be reused for its scope and BEM root! | ||
*/ | ||
if (new.target) { | ||
const { bem = "", classes = {}, scope = {} } = args?.[0] || {}; | ||
return (...selectors) => { | ||
const cn = classy({ bem, ...scope, ...classes }, selectors); | ||
return cn || scope?.[bem] || bem; | ||
}; | ||
} | ||
|
||
if (!args.length) return ""; | ||
|
||
/* Shift the first param off the args array. If | ||
* its an object, we treat it as the CSS Module | ||
* hash which we use to auto-scope our selectors! | ||
*/ | ||
let [scope, ...classes] = args; | ||
|
||
/* If the first param isnt a CSS Module hash, | ||
* add the initial arg back in to `classes` | ||
* and set the `scope` to an empty object. | ||
*/ | ||
if (!isObject(scope)) { | ||
classes = [scope, ...classes]; | ||
scope = {}; | ||
} | ||
|
||
/* Pluck the `bem` namespace out of the `scope`. | ||
*/ const { bem = "" } = scope || {}; | ||
|
||
/* Flatten nested arrays. | ||
*/ classes = flat(classes); | ||
|
||
/* Split stringy lists in to arrays. | ||
*/ classes = splitClassStrings(classes); | ||
|
||
return classes | ||
.filter((cn) => typeof cn === "string" && cn) | ||
.map((cn) => { | ||
/* BEM EXPANSIONS | ||
*/ cn = expandBEMPartials(cn, bem); | ||
|
||
/* CSS MODULE AUTO-SCOPING | ||
*/ if (cn in scope) cn = scope[cn]; | ||
|
||
return cn; | ||
}) | ||
.join(" "); | ||
} | ||
|
||
classy.SEPARATORS = SEPARATORS; | ||
|
||
export default classy; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import Classy from "."; | ||
|
||
const mockScope = { | ||
RootElem: "bPDD5MZYqy37J8a5Ed", | ||
RootElem_mod: "3ptteOdB8YbvcENwoY6cnW", | ||
"RootElem-kid": "1c9QFfxxmSn49Hghkx4GmC", | ||
}; | ||
|
||
describe("Classy", () => { | ||
describe("Class List Normalization", () => { | ||
it("Concatenates variable-length arguments.", () => { | ||
expect(Classy("test1", "test2", "test3")).toBe("test1 test2 test3"); | ||
}); | ||
|
||
it("Flattens deeply nested arrays of strings.", () => { | ||
expect(Classy(["test1", ["test2", ["test3"]]])).toBe("test1 test2 test3"); | ||
}); | ||
|
||
it("Expands comma-separated strings.", () => { | ||
expect(Classy("test1,test2, test3")).toBe("test1 test2 test3"); | ||
}); | ||
|
||
it("Expands dot-separated strings.", () => { | ||
expect(Classy(".test1.test2.test3")).toBe("test1 test2 test3"); | ||
}); | ||
|
||
it("Expands space-separated strings.", () => { | ||
expect(Classy("test1 test2 test3")).toBe("test1 test2 test3"); | ||
}); | ||
}); | ||
|
||
describe("Classy Instances", () => { | ||
it("Can be created with the `new` keyword.", () => { | ||
const cn = new Classy({ | ||
classes: mockScope, | ||
}); | ||
expect(cn("RootElem")).toBe("bPDD5MZYqy37J8a5Ed"); | ||
}); | ||
}); | ||
|
||
describe("CSS Module Auto-Scoping", () => { | ||
it("Accepts an initial hash of scoped selectors.", () => { | ||
expect(Classy(mockScope, "RootElem")).toBe("bPDD5MZYqy37J8a5Ed"); | ||
}); | ||
}); | ||
|
||
describe("BEM Expansion", () => { | ||
const bem = new Classy({ | ||
bem: "RootElem", | ||
}); | ||
|
||
it("Replaces Sass-style root selectors (&) with the BEM base.", () => { | ||
expect(bem("&")).toBe("RootElem"); | ||
}); | ||
|
||
it('BEM "-element" partials', () => { | ||
expect(bem("-kid")).toBe("RootElem-kid"); | ||
}); | ||
|
||
it('BEM "_modifier" partials', () => { | ||
expect(bem("_mod")).toBe("RootElem_mod"); | ||
}); | ||
|
||
it("Skips pre-scoped classes that end in `-scss`.", () => { | ||
/** Sometimes we pass pre-scoped classes to classy. In our dev environment, | ||
* the generated classes can start with an underscore, which causes Classy | ||
* to attempt to prefix them with the BEM root! | ||
* @todo: i can't actually remember what/where/why this was a problem; | ||
* is it still happening? there's probably a better fix... | ||
*/ | ||
const prescopedClass = "_1adDfJfhK6H-scss"; | ||
const scope = { [prescopedClass]: "PRESCOPED" }; | ||
expect(Classy(scope, prescopedClass)).toMatchSnapshot(); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.