Skip to content

Commit 5319687

Browse files
ramithachriseppstein
authored andcommitted
feat: Making bem-to-blocks asynchronous.
- This can now take in user input for blocks whose names cannot be determined
1 parent 20c1f10 commit 5319687

File tree

6 files changed

+287
-116
lines changed

6 files changed

+287
-116
lines changed

packages/@css-blocks/bem-to-blocks/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
"yarn": "1.21.0"
5656
},
5757
"devDependencies": {
58-
"@types/inquirer": "^6.5.0"
58+
"@types/inquirer": "^6.5.0",
59+
"@types/sinon": "^7.5.1",
60+
"sinon": "^8.0.4"
5961
}
6062
}

packages/@css-blocks/bem-to-blocks/src/index.ts

+42-20
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import * as postcss from "postcss";
44
import * as parser from "postcss-selector-parser";
55
import * as vars from "postcss-simple-vars";
66

7-
import { BemSelector, BlockClassSelector } from "./interface";
7+
import { BemSelector, BlockClassSelector, SelectorBemObject } from "./interface";
8+
import { getBemNamesFromUser } from "./userInput";
89
import { findLcsMap } from "./utils";
910
export declare type PostcssAny = unknown;
1011

@@ -19,19 +20,22 @@ const COMMON_PREFIXES_FOR_MODIFIERS = ["is"];
1920
export function convertBemToBlocks(files: Array<string>): Promise<void>[] {
2021
let promises: Promise<void>[] = [];
2122
files.forEach(file => {
22-
fs.readFile(file, (_err, css) => {
23-
let output = postcss([
23+
fs.readFile(file, async (_err, css) => {
24+
postcss([
2425
// Using postcss-simple-vars to pass the fileName to the plugin
2526
vars({
26-
variables: () => {return {fileName: path.relative(process.cwd(), file)};
27+
variables: () => {return {fileName: path.relative(process.cwd(), file)}; },
2728
}),
2829
bemToBlocksPlugin,
2930
])
30-
.process(css, { from: file });
31-
// rewrite the file with the processed output
32-
const parsedFilePath = path.parse(file);
33-
const blockFilePath = Object.assign(parsedFilePath, {ext: `.block${parsedFilePath.ext}`, base: undefined} );
34-
promises.push(fs.writeFile(path.format(blockFilePath), output.toString()));
31+
.process(css, { from: file })
32+
.then(output => {
33+
// rewrite the file with the processed output
34+
const parsedFilePath = path.parse(file);
35+
const blockFilePath = Object.assign(parsedFilePath, {ext: `.block${parsedFilePath.ext}`, base: undefined} );
36+
promises.push(fs.writeFile(path.format(blockFilePath), output.toString()));
37+
}).catch(e => {throw (e); });
38+
3539
});
3640
});
3741
return promises;
@@ -141,32 +145,46 @@ export function constructBlocksMap(bemSelectorCache: BemSelectorMap): BemToBlock
141145
export const bemToBlocksPlugin: postcss.Plugin<PostcssAny> = postcss.plugin("bem-to-blocks-plugin", (options) => {
142146
options = options || {};
143147

144-
return (root, result) => {
145-
let fileName = result.messages.filter(varObj => {return varObj.name === "fileName"; })[0].value;
148+
return async (root, result) => {
149+
let fileNameObject = result.messages.find(varObj => {return varObj.name === "fileName"; });
150+
let fileName = fileNameObject ? fileNameObject.value : undefined;
146151

147152
const bemSelectorCache: BemSelectorMap = new Map();
148153

149-
const buildCache: parser.ProcessorFn = (selectors) => {
154+
const buildCache: parser.ProcessorFn = async (selectors) => {
155+
let promises: Promise<SelectorBemObject>[] = [];
156+
150157
selectors.walk((selector) => {
151158
// only iterate through classes
152159
if (parser.isClassName(selector)) {
153160
try {
154-
let bemSelector = new BemSelector(selector.value, fileName);
161+
let bemSelector = new BemSelector(selector.value);
155162
if (bemSelector.block) {
156163
// add it to the cache so it's available for the next pass
157164
bemSelectorCache.set(selector.value, bemSelector);
158165
}
159166
} catch (e) {
160-
if (selector.parent) {
161-
selector.parent.insertBefore(selector, parser.comment({value: `ERROR: ${e.message}`, spaces: {before: "/* ", after: " */\n"}}));
162-
}
167+
// if the selector does not have a block value, get it from the user
168+
promises.push(getBemNamesFromUser(selector.value, fileName));
169+
// if (selector.parent) {
170+
// selector.parent.insertBefore(selector, parser.comment({value: `ERROR: ${e.message}`, spaces: {before: "/* ", after: " */\n"}}));
171+
// }
163172
}
164173
}
165174
});
166-
return selectors.toString();
175+
176+
// wait for all the user input
177+
let answers = await Promise.all(promises);
178+
179+
// add all the user defined blocks to the cache
180+
for (let userBem of answers) {
181+
let selector = userBem.selector;
182+
bemSelectorCache.set(selector, new BemSelector(selector, userBem.bemObj));
183+
}
167184
};
168185

169186
const rewriteSelectors: parser.ProcessorFn = (selectors) => {
187+
170188
selectors.walk((selector) => {
171189
// we only need to modify class names. We can ignore everything else,
172190
// like existing attributes, pseudo selectors, comments, imports,
@@ -224,12 +242,16 @@ export const bemToBlocksPlugin: postcss.Plugin<PostcssAny> = postcss.plugin("bem
224242
};
225243

226244
// in this pass, we collect all the selectors
227-
root.walkRules(rule => {
228-
rule.selector = parser(buildCache).processSync(rule);
245+
let bemToBlockClassMap: BemToBlockClassMap;
246+
247+
let buildCachePromises: unknown[] = [];
248+
root.walkRules(async (rule) => {
249+
buildCachePromises.push(parser(buildCache).process(rule));
229250
});
251+
await Promise.all(buildCachePromises);
230252

231253
// convert selectors to block selectors
232-
let bemToBlockClassMap: BemToBlockClassMap = constructBlocksMap(bemSelectorCache);
254+
bemToBlockClassMap = constructBlocksMap(bemSelectorCache);
233255

234256
// rewrite into a CSS block
235257
root.walkRules(rule => {

packages/@css-blocks/bem-to-blocks/src/interface.ts

+10-22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import * as inquirer from "inquirer";
2-
31
import { parseBemSelector } from "./utils";
42

53
export interface BemObject {
@@ -8,6 +6,11 @@ export interface BemObject {
86
modifier?: string;
97
}
108

9+
export interface SelectorBemObject {
10+
selector: string;
11+
bemObj: BemObject;
12+
}
13+
1114
interface BlockClassName {
1215
class?: string;
1316
state?: string;
@@ -18,32 +21,17 @@ export class BemSelector {
1821
block: string;
1922
element?: string;
2023
modifier?: string;
21-
constructor(selector: string, fileName?: string) {
22-
let bemObject = parseBemSelector(selector);
24+
constructor(selector: string, bemObject?: BemObject) {
25+
if (!bemObject) {
26+
bemObject = parseBemSelector(selector) || undefined;
27+
}
2328
if (bemObject && bemObject.block) {
2429
this.block = bemObject.block;
2530
// strip the syntax elements
2631
this.element = bemObject.element ? bemObject.element.replace(/^__/, "") : undefined;
2732
this.modifier = bemObject.modifier ? bemObject.modifier.replace(/^--/, "") : undefined;
2833
} else {
29-
inquirer.prompt({
30-
message: `.${selector}${fileName ? ` in ${fileName} ` : ""} does not follow BEM conventions. Enter the block name for this class`,
31-
name: "block",
32-
}).then(blockAns => {
33-
if (blockAns.block) {
34-
inquirer.prompt({
35-
message: `Enter the element name for .${selector}`,
36-
name: "element",
37-
});
38-
}
39-
}).then(elemAns => {
40-
inquirer.prompt({
41-
message: `Enter the modifier for .${selector}`,
42-
name: "modifier",
43-
});
44-
}).catch(err => console.log(err));
45-
46-
return ();
34+
throw new Error(`${selector} does not follow BEM`);
4735
}
4836
}
4937
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import * as inquirer from "inquirer";
2+
3+
import { SelectorBemObject } from "./interface";
4+
5+
export async function getBemNamesFromUser(selector: string, fileName: string): Promise<SelectorBemObject> {
6+
let {block} = await inquirer.prompt({
7+
message: `.${selector}${fileName ? ` in ${fileName} ` : ""} does not follow BEM conventions. Enter the block name for this class (required): `,
8+
name: "block",
9+
validate: (input) => {
10+
// check that the block is not an empty string
11+
return input !== "";
12+
},
13+
});
14+
15+
let {element} = await inquirer.prompt({
16+
message: `Enter the element name for .${selector} (optional): `,
17+
name: "element",
18+
});
19+
20+
let {modifier} = await inquirer.prompt({
21+
message: `Enter the modifier name for .${selector} (optional): `,
22+
name: "modifier",
23+
});
24+
25+
return {selector: selector, bemObj: {block, element, modifier}};
26+
27+
// .then(blockAns => {
28+
// bemName.block = blockAns.block;
29+
// if (blockAns.block) {
30+
// // get the element
31+
// inquirer.prompt({
32+
// message: `Enter the element name for .${selector} (optional): `,
33+
// name: "element",
34+
// }).then(ans => {
35+
// bemName.element = ans.element.length > 0 ? ans.element : undefined;
36+
// // get the modifier name
37+
// inquirer.prompt({
38+
// message: `Enter the modifier name for .${selector} (optional): `,
39+
// name: "modifier",
40+
// }).then(ans => {
41+
// bemName.modifier = ans.modifier.length > 0 ? ans.modifier : undefined;
42+
// return bemName;
43+
// }).catch(e => { throw e; });
44+
// }).catch(e => { throw e; });
45+
// }
46+
// }).catch(e => { throw e; });
47+
}

0 commit comments

Comments
 (0)