This article mainly discusses the automatic implementation of atomized CSS implementation scheme, some VSCode related knowledge.
Project Git address: github.com/balabalapup…
Project background
Intra-group CSS uses custom atomized stylesheets, but the common drawback of these stylesheets is that they are a bit of a memory load, which is especially hard on newcomers.
Moreover, this kind of granular convention often causes problems in real development, so it is foolish to write the styles that you can’t remember in CSS first, and then fill them in the template when they are finished. I suspect that a significant number of users will write this if they are not familiar with atomic style sheets, or turn to the documentation to compare them.
Vscode-auto-atomic-css is a vscode plugin that automatically fills the
Why use atomized CSS when it’s hard to use?
Forced consistency
In small teams or background products with weak constraints, there will be a lot of fuzzy margin/padding boundary. The use of CSS can be limited by atomizing the unified specification, and if the design team has a mature & rarely changed design specification, Atomization is probably the most efficient CSS solution. And even larger companies can inadvertently produce different CSS.
- GitLab: 402 text colors, 239 background colors, 59 font sizes
- Buffer: 124 text colors, 86 background colors, 54 font sizes
- HelpScout: 198 text colors, 133 background colors, 67 font sizes
- Gumroad: 91 text colors, 28 background colors, 48 font sizes
- Stripe: 189 text colors, 90 background colors, 35 font sizes
- GitHub: 163 text colors, 147 background colors, 56 font sizes
- ConvertKit: 128 text colors, 124 background colors, 70 font sizes
Especially in the BEM specification, which has been heavily used in the past, this is almost always the case more or less as the project size increases. To put that in perspective, the Facebook homepage was only 72KB after refactoring. Take a look at the following three companies’ CSS file sizes and the number of classes in their CSS.
There are many solutions to the CSS specification. We can achieve a uniform style through a fixed UI framework, but that is not the best solution. Atomized CSS decouples HTML from CSS. In plain English, future styles are written as defined classes.
“Separation of concerns” in CSS
When you think about the relationship between HTML and CSS in terms of “separation of concerns”, it’s very black and white.
The relationship between CSS and HTML can be roughly divided into two forms
- Separation of concerns (CSS depends on HTML)
-
Defining the name of a class according to its content can be thought of as an HTML-controlled hook, through which CSS styles are associated. The resulting CSS is an independent state, and the HTML does not care about the specific style. CSS is written on the hook.
-
So in this case, HTML can be reconfigured with a hook, and CSS is almost impossible to reuse.
- Mixed concerns (HTML depends on CSS)
-
Instead of defining class names by content, CSS classes are content agnostic, so you can think of CSS as determining hooks. HTML selects hooks at creation time, which can be thought of as a more granular CSS class for heavy reuse.
-
Now the HTML is not independent, because when I write the HTML I want to know exactly what hook classes are available to me, and I combine them to make a style.
The limit of mixed concerns is atomizing CSS, leaving the styling hooks entirely to THE CSS.
For an idea of separation of concerns, consider the difference between HTML writing in Vue and React. Template syntax in Vue is typical of separation of concerns.
Utility-first & Component-first
Building complex components from a constrained set of primitive utilities.
In fact, this title is not very accurate, in addition to practical priority, our solution is absolutely not strong correlation with the component content so simple opposition, in the development of CSS at various stages, derived from a variety of CSS design patterns, such as OOCSS, BEM and so on the layered concept, interested in the following STAGES of THE development of CSS.
The concept of utility first is also a top priority for CSS frameworks today. The first sentence of Tailwind’s core features is about utility first, so there’s no need to go into more atomization. For details, please refer to the official website.
Even with utility-first thinking, we can group the created atomic style classes into a conceptual class when defining components. This behavior is also widely used in CSS today, such as the extract component section in Tailwind.
Technology selection
Now that you’ve decided to export the class in the
webpack loader
The first step is to make it clear that WebPack can handle this requirement, which is a reference to the implementation of Broke – CSS. The style is written in the template and translated by loader at compile time.
It does work, but there are a few things to think about
-
Is it appropriate to give loader full control of page modification style? What should be done in case of error deletion?
-
Some classes may be common to other files. How are these classes handled at compile time?
In addition, handing control of styling to loader is always worrying about the impact on the online environment, and it is not worth the loss if the online styling is messed up for a style fix plug-in.
VSCode plug-in
Therefore, vscode-auto-atomic-CSS achieves automatic atomization through VScode. Here, a separate atomization scheme is selected for each separate CSS class to ensure the independence of repair. Secondly, if there is an exception in the plug-in, it will not affect the online environment, where is wrong to change where.
auto-atomic-cssDesign ideas
If you want to atomize, you have to find the location where the atomic style is stored, and you have to take the style out and convert it to an object format that is easy to find, assuming that because the original atomic style is in this form
.fz-12{
font-size: '12px'
}
Copy the code
For example, if we want to find the font size with the key of font size 12, we need to look up each object to see if the font size is 12, and then extract the object name. If transformed into the following format can be easily retrieved when searching.
Once the atomized stylesheet is out, it’s time to start VSCode plug-in development. This will only be atomized when the mouse clicks on the specific class in the
You also need to consider that if you click on a nested class, the inner subclass will also need to be converted, so you need to make a deep nested object here. This replaces the subclasses of the current class with the HTML when the style is replaced.
Now that you have the current class style, and you have the atomic style, it’s time to modify the HTML code in the template syntax.
We can roughly predict the HTML range of the current page by determining the position of the first and last
tags. We can extract this HTML from document.gettext (templateRaneg). This allows you to convert HTML into an AST tree structure. Finally, you need to recursively traverse the AST tree to find out whether the class has a corresponding class in the CSS object being processed, and then replace it in turn.
auto-atomic-cssCode logic
The main technical problems to be solved in one-bond atomization are as follows:
- You need to
style
In the tagCSSThe style is converted to the form of an object suitable for lookup style
In the labelCSSClass nesting problem,Less/SasstheseCSSExtension languages are supportedCSSDeep nesting of
- When users focus on the upper CSS class, they need to transform the inner class contained in the current class.
- When users focus on the underlying CSS class, they need to determine the scope of the current class to avoid error overwriting in HTML.
- HTML class nesting problem
- HTML markup language is converted to AST in the browser. How to put nested CSS classes into HTML tags that also have nested relationships
The VScode plugin logic starts at activate, and the auto-atomic-css entry is provideCodeActions at activate. ProvideCodeActions passes in a new user focus Range whenever the user changes focus.
provideCodeActions | |
---|---|
The input | describe |
Documents:TextDocument | The document from which the command is invoked. |
Scope:Range 、 Selection | A selector or scope for invoking a command. |
Context:CodeActionContext | A context that carries additional information. |
Token:CancellationToken | Cancel the token. |
The return value | describe |
ProviderResult<CommandT[]> | A series of code actions, such as quick fixes or refactoring. |
// src/extension.ts
export async function activate(context: vscode.ExtensionContext) {
const actionsProvider = vscode.languages.registerCodeActionsProvider(
"vue".new AutoAtomicCss(),
{
providedCodeActionKinds: [vscode.CodeActionKind.QuickFix],
}
);
context.subscriptions.push(actionsProvider);
}
export class AutoAtomicCss implements vscode.CodeActionProvider {
provideCodeActions(
document: vscode.TextDocument,
range: vscode.Range
): vscode.CodeAction[] {
// 1. Determine whether the current focus is a class in style
if(! isAtStartOfSmiley(document, range))return;
// ...}}export function isAtStartOfSmiley(
document: vscode.TextDocument,
range: vscode.Range
) {
const start = range.start;
const line = document.lineAt(start.line);
const { text = "" } = line;
var reg = /^.[(\w)-]+\s{$/;
return reg.test(text.trim());
}
Copy the code
ProvideCodeActions has several goals to achieve
- Gets the current focus class and the complete CSS style code that contains it, and finally converts the style to an object format.
- Get the atomic style file from your storage path according to the atomic style file, and convert the atomic class in the public file into an easy to find object format.
- Replaces matching style key pairs in the current and nested classes with atomic class objects.
- Transform HTML into an AST structure, recursively looking for tags in HTML that can be atomized.
- Edit repair logic, output atomized HTML structure and CSS structure, replace the original text content.
A few key steps need to be highlighted here
Focus classes are converted to objects
To get the full style of the current focus class, we need to determine its context. Suppose we click on the Demo-div class, look up to determine the scope of the Demo-div class, and look down to determine how many other nested classes are inside the Demo-div class. Auto-atomic-css will replace them all together.
The current version only does the downward recursion, to do the upward lookup need to intercept the entire style tag, all CSS into objects and cache, the idea is the same.
// Get the full CSS style of the focus class
function getClassInStyle(document, range) {
let lineCount = range.start.line;
while() {
const { text: _text } = document.lineAt(lineCount);
// The end of the current class is not found
lineCount ++;
}
// Find the end node and print the current range
let newRange: vscode.Range = new vscode.Range(
range.start.translate(0, -range.start.character),
range.end.translate(styleLineCount - range.end.line - 1.0));return newRange
}
// Convert the truncated string from the focus class to an object
function handleSplitNameAndAttribute(resultText: string){
// .class { prop: value} TO ",class": { "prop": "value" }
const styleObject: DeepObjectType = transJSONText.replaceAll(/... /,...). ;return styleObject;
}
Copy the code
The public atom class is compared to the focus class
The first step is to convert the common atomic class into an easy to find format, and then recursively insert the selected CSS class into the converted object. Store the modified classes in fixedClassName.
function dfsConvertCSS(cssObjet, styleObject) {
const analyzedClassLabel: AnalyzedClassLabelType = {
fixedClassName: name, // class name string e.g. '.demo fz-14 mr-8 ml-8'
notFixedCss: {}, // No atom-style class can be found after comparing with the public atomic class, this part will remain in CSS
children: {}, // Child nodes in the focus class
};
{.demo1: {},.demo2: {}}}
currentLayerCSS.forEach((_item) = > {
const key = cssObjet[_item];
// Find if the current style attribute can be compared in the public style library
if (key in styleStore[_item]) {
// There is a comparison result, put the result into fixedClassName
analyzedClassLabel.fixedClassName =
analyzedClassLabel.fixedClassName.concat(
styleStore[_item][key]
);
} else {
// No result is stored in notFixedCssanalyzedClassLabel.notFixedCss[_item] = key; }}); }Copy the code
Convert HTML strings to each other
There are many ways to replace HTML, but the main use here is HTML to AST.
The repair logic is organized by comparing the results with the AST structure generated by HTML.
const edit = new vscode.WorkspaceEdit();
// classInStyleRange: Style CSS repair result
edit.replace(document.uri, classInStyleRange, resultString); createFix(...) ;function createFix(
document: vscode.TextDocument, // vscode context
textEditor: vscode.TextEditor, // The currently open file editor
convertedCssStyle: ConvertedCssType, // Compare the results
edit: vscode.WorkspaceEdit // fix.edit Fixes the location where the logic is stored
){
// 1. Get the scope of the template tag.
const templateRange = new vscode.Range(
new vscode.Position(sl, 0),
new vscode.Position(el + 1.0));// 2. Change the characters generated by the template tag to the AST structure.
const currentPageTemplace = document.getText(templateRange);
const htmlToAst: AstType[] = _HTML.parse(currentPageTemplace);
// 3. Recursive AST, compare the class in AST with the result object, find the appropriate class directly modify
handleChangeHtmlAst(htmlToAst[index], targetMainAttribute, targetMainClass);
// 4. AST Results are converted back to a string
const astToString = _HTML.stringify(htmlToAst);
// 5. Put the result into the editor
edit.replace(document.uri, templateRange, astToString);
return edit;
}
Copy the code
Output repair logic
Here, replace the edit logic with WorkspaceEdit.
new vscode.WorkspaceEdit().replace | |
---|---|
The scope of | describe |
uri: Uri | Resource identification, which can be obtained from document.uri |
range: Range | Scope of modification |
newText: string | Modified text |
provideCodeActions(document: vscode.TextDocument, range: vscode.Range ): vscode.CodeAction[] {
const edit = new vscode.WorkspaceEdit();
edit.replace(document.uri, classInStyleRange, resultString);
constreplaceWithSFixedStyle: vscode.WorkspaceEdit = createFix(...) ;const fix = new vscode.CodeAction('this class can convert to atomic css',vscode.CodeActionKind.QuickFix);
fix.edit = replaceWithSFixedStyle;
return [fix];
}
Copy the code
conclusion
So far, the execution logic of VScode-auto-atomic-CSS is finished. Currently, VScode-auto-atomic-CSS is still version 0.0.1. If I am not busy, I can deal with the bugs in my personal test by the end of this month. The current bugs mainly include the following points:
- HTML ASTgenerate
string
In a string, part of the newline character is invalid and can be temporarily passedESLint 或 PrettierRepair. style
classThe logic for looking up ranges in the tag is not yet written, but soon.
If you want to extend it later, you have to consider the selectors in the style that need to be overwritten. But in itself, the products in the style tag are intermediates. After atomization, these classes don’t need to exist, so overwriting or not overwriting the selectors doesn’t affect the function itself.
The atomized repair logic varies from person to person.