preface

Due to the limitation of package size of small program and the continuous increase of business iteration, the reduction of package size has always been a pain point that troubles business development. So I started trying to reduce package size in different directions, and here I’m optimizing the size of CSS stylesheet files.

Overall, the volume of CSS style sheets in the whole project is relatively large, and some styles are repetitive, so we began to consider the way of sharing Class to achieve the purpose of reducing package size, so we began to explore and experiment from this perspective.

About atomizing CSS

Atomized CSS is a style of CSS architecture that favors small, single-purpose classes and is named after visual effects.

Atomized CSS can also be referred to as CSS utility classes. There are already CSS frameworks such as Tailwind CSS, Windi CSS, and Tachyons on the market. Some UI libraries also provide built-in CSS utility classes such as Bootstrap.

Traditional scheme

The traditional atomized CSS solution is to provide all the CSS utility classes you might need in your project up front and use them directly at project development time. This is similar to the static utility classes provided by the Bootstrap library. Therefore, the programme mainly has the following problems:

  • Large CSS stylesheet files (which is obviously not desirable in applets)
  • The CSS does not support dynamic generation of tool classes

Generate solutions on demand

There are Tailwind CSS and Windi CSS on the market. Tailwind CSS supports on-demand generation only after JIT mode is provided. The process for generating tool class files is as follows:

  1. Scanning the use of tools in the project;
  2. Generate tool classes on demand based on the scan of the first step;
  3. Generate stylesheet files;

Both Windi CSS and Tailwind JIT use pre-scanned source code to support HMR.

Finally, Windi CSS framework was selected as the atomized CSS scheme in the project, mainly for the following reasons:

  • The Tailwind CSS development environment does not support JIT in reference to the implementation of other projects;
  • Tailwind CSS dynamic utility classes need to be used[]Enveloping, which is not supported in WXSS files;
  • Windi CSS is perfectly compatible with Tailwind CSS and has many additional features;
  • Windi CSS supports more flexible extension of dynamic style generation rules;

Windi CSS and Tailwind CSS frameworks perform well in Web projects. This article is for small program projects only. If you are a Web project, all the capabilities of the framework are a very good development experience.

Windi CSS framework introduction and use

Windi CSS official document cn.windicss.org/

By scanning HTML and CSS to generate utilities on demand, Windi CSS aims to provide a faster loading experience and faster HMR in development

Installation and introduction

For small programs, because it is based on the cross-platform framework for development, so we can choose the Webpack plug-in windicss- Webpack-plugin provided.

The VS Code plug-in Windi CSS Intellisense is also provided to support automatic prompt and improve development efficiency.

The installation

npm i windicss-webpack-plugin -D
# yarn add windicss-webpack-plugin -D
Copy the code

The introduction of

Import the plug-in into the plugins configuration item of the Webpack configuration file

const WindiCSSWebpackPlugin = require('windicss-webpack-plugin')

export default {
  // ...
  plugins: [
    new WindiCSSWebpackPlugin()
  ]
}
Copy the code

Introduce windi.css in entry files or files that are loaded only once

<! --js-->
import 'windi.css'

<! --mpx-->
<style src="windi.css"></style>
Copy the code

It doesn’t have to be all in the applet, it’s included in windi.css

  • windi-base.css: Initial base style;
  • windi-components.css: Mainly generate the Stylesheet for Shortcuts;
  • windi-utilities.css: all utility class stylesheets used;

You don’t really need to use the Windi-base.css style sheet in the applet, so you just need to introduce two other stylesheets that can be broken down to override the existing hierarchy with custom CSS.

<! --js-->
import 'windi-components.css'
import 'windi-utilities.css'

<! --mpx-->
<style src="windi-components.css"></style>
<style src="windi-utilities.css"></style>
Copy the code

Ability to support

Most of the features provided by Windi CSS cannot be used in small programs and are redundant due to the limitations of the syntax of the small program framework.

Available capability:

  • Automatic value derivation;
  • Shortcuts.
  • Visual analyzer;

Capabilities not recommended or unavailable:

  • Variable modifier group (unavailable) : Compiled code stylesheets do not support WXSS file formats; (Translatable)
  • Instruction (not supported) : compiled production code is the same as handwritten code, and CSS preprocessors (such as SCSS, LESS) are not the same@applyUse together;
  • Attribute mode (unavailable) : Possibly due to applets template, not supported<view>;
  • Responsive design (not recommended) : This capability is not required on the mobile terminal.
  • Dark mode (not recommended) : this capability is not required on mobile terminals.
  • RTL (not recommended); Try to unify the writing method;
  • Important prefix (not supported) : WXSS files with symbols that are not supported will be reported; (Introduction to wechat small program WXSS)

Windi configuration

Add a file named windi.config.ts to the project root directory.

Windi configuration default value: github.com/windicss/wi…

When the server starts, Windi will scan your code and extract the utility classes for use. The configuration in Windi CSS is almost identical to that in Tailwind CSS, but with additional enhancements, so you can also refer to Tailwind CSS.

The following configuration is mainly for applets.

// defineConfig is a help function with type hints that can be ignored if not needed
import { defineConfig } from 'windicss/helpers'

export default defineConfig({
  prefixer: false.// Automatically compatible with platform browsers (no)
  prefix: 'c-'.// Class name style prefix (to prevent style contamination)
  extract: {
    // Scan the file range
    include: ['src/**/*.{css,html,mpx}'].// Ignore scanning folders
    exclude: ['node_modules'.'.git'.'dist'],},// Shortcuts className does not need a prefix
  shortcuts: {
    / / sample
    'flex-center': 'flex items-center justify-center'
  },
  theme: {
    screens: null.// Media query (not required)
    animationTimingFunction: null.// Animation render function (not required)
    /* Overwrite or add default values... * /
    extend: {
        /* Extend theme presets... * /}},plugins: [
    /* Introduce extension toolclass plugins... * /].corePlugins: [
    / * core plugin Settings, supports all the core plug-in 1 at https://github.com/windicss/windicss/blob/main/src/interfaces.ts#L98. Allows you to completely disable utility classes generated by Windi by default by setting them to empty arrays [] 2. If you only want to use the listed default utility class, set it to an array and list the core plugins you want to use, such as ['margin', 'padding'] 3. If you only want to disable the specified core plug-in, set it to an object and list the core plug-in to disable, such as {float: false} */]})Copy the code

Have a problem

  • Since the Official Windi documentation is relatively light on configuration items, you can learn more from the Windi source code and Tailwind website.
  • On Windows, stylesheet files generated by the Windi build tool are named with:Is an invalid character in the Windows operating system and causes file generation failure. The current alternative scheme can passWindi CSS CLIYou can customize the output file name.

The project practice

Returning to the theme, the atomized CSS scheme is used to reduce package size. The Windi CSS framework is not designed to do this, but to provide a better development experience, so we need to experiment in small programs to verify whether this solution can achieve the purpose of reducing package size.

In the previous three rounds of experiments, the experimental process of the overall package volume is to accumulate and transform a certain amount of code in each round to demonstrate whether the package volume decreases gradually with the increase of the coverage of tool classes

rounds The main package volume Tool style sheet volume
The first round A significant increase in A significant increase in
The second round This is less than the previous round, but an overall increase Slowly increase
In the third round Overall decrease Slowly increase

As a result, we achieved our goal and started to modify a small part of the business. However, when we looked at the generated stylesheet file, we found that there was still room for reduction. Due to the style generation rules of the Windi CSS framework, there were several main points:

  • Background and font color generation code is more than we write directly;
/* Background color */
.bg-white {
  --tw-bg-opacity: 1;
  background-color: rgba(255.255.255.var(--tw-bg-opacity));
}
/* Text color */
.text-gray-500 {
  --tw-text-opacity: 1;
  color: rgba(107.114.128.var(--tw-text-opacity));
}
Copy the code
  • Dynamic sizing Settings need to be addedrpxUnit; (By default, is not addedremUnits)
/* Width size set */
.w-24 {
  width: 6rem;
}
Copy the code
  • Spacing cannot be abbreviated;
.py-10 {
  padding-top: 2.5 rem;
  padding-bottom: 2.5 rem;
}
.my-12 {
  margin-top: 3rem;
  margin-bottom: 3rem;
}
Copy the code

The first thing to understand is why using atomized CSS can reduce package size is that we can use shorter Class utility classes and longer Style styles. Then there are the benefits of the framework:

  • Windi CSS framework provides a visual analyzer that can analyze the use of all tool classes to manage and optimize the use of CSS;
  • Provides dynamic tool class generation capability, do not need to manually add static tool class;

To address the above problem, you need to use the ability of the Extended utility classes provided by the Windi CSS framework to customize plug-ins for applets.

Windi CSS framework plug-in development

On the plug-in development of Windi framework, may refer to: cn.windicss.org/plugins/int…

The introduction and instructions in the document are actually quite brief, with only a few simple examples for our reference. This section focuses on adding dynamic utility classes and customizing a plug-in for a small program project. The first goal was to reduce package size, and around that goal we needed to re-customize the style generation rules for some of the utility classes. Therefore, the following utility classes need to be recustomized:

  • Default units for size/spacing/layout/positioning/border;
  • Extended margin and padding rules; (for example, support.m-2_4generatemargin: 2rpx 4rpx
  • Font/background color generation rules;

First, a quick look at the basic usage

import plugin from 'windicss/plugin'

plugin(({ addDynamic }) = > {
  addDynamic('filter'.({ Utility, Style }) = > {
    return Utility.handler
      .handleStatic(Style('filter'))
      .createProperty(['-webkit-filter'.'filter'])})})Copy the code

The Utility object

It’s important to understand what properties and methods a Utility object contains for us to use. Specific can see its source github.com/windicss/wi…

Suppose you use the.-placeholder real-gray-300 utility class

attribute type describe The return value
raw String Class Specifies the name of the utility class -placeholder-real-gray-300
class String The class tools .-placeholder-real-gray-300
isNegative Boolean Check if it’s negative true
absolute String Absolute value of tool class placeholder-real-gray-300
identifier String First word of tool class placeholder
key String Tool keyword placeholder-real-gray
center String Median utility class value real-gray
amount String The numerical 300
body String Tool class body real-gray-300
match Function Re match utility class
clone Function Copy the utility classUtilityThe sample
handler Function Set of processing functions

Utility.handler

The API provided by Utility. Handler makes it easy to process the scanned Utility classes and generate the desired style. The following apis are provided:

export type Handler = {
  utility: Utility value? : string_amount: string opacity? : string |undefinedcolor? : colorCallbackhandleStatic: (map? : { [key: string]: string | string[] } | unknown, callback? : (str: string) => string |undefined
  ) = > Handler
  handleBody: (map? : { [key: string]: string | string[] } | unknown, callback? : (str: string) => string |undefined
  ) = > Handler
  handleNumber: (start? : number, end? : number, type? :'int' | 'float', callback? : (number: number) => string |undefined
  ) = > Handler
  handleString: (callback: (string: string) => string | undefined) = > Handler
  handleSpacing: () = > Handler
  handleSquareBrackets: (callback? : (number: string) => string |undefined
  ) = > Handler
  handleTime: (start? : number, end? : number, type? :'int' | 'float', callback? : (milliseconds: number) => string |undefined
  ) = > Handler
  handleColor: (map? : colorObject | unknown) = > Handler
  handleOpacity: (map? : DictStr | unknown) = > Handler
  handleFraction: (callback? : (fraction: string) => string |undefined
  ) = > Handler
  handleNxl: (callback? : (number: number) => string |undefined
  ) = > Handler
  handleSize: (callback? : (size: string) => string |undefined
  ) = > Handler
  handleVariable: (callback? : (variable: string) => string |undefined
  ) = > Handler
  handleNegative: (callback? : (value: string) => string |undefined
  ) = > Handler
  createProperty: (name: string | string[], callback? : (value: string) => string) = > Property | undefined
  createStyle: (
    selector: string,
    callback: (value: string) => Property | Property[] | undefined
  ) = > Style | undefined
  createColorValue: (opacityValue? : string |undefined
  ) = > string | undefined
  createColorStyle: (selector: string, property: string | string[], opacityVariable? : string |undefined, wrapRGB? : boolean) = > Style | undefined
  callback: (
    func: (value: string) => Property | Style | Style[] | undefined
  ) = > Property | Style | Style[] | undefined
}
Copy the code

See github.com/windicss/wi…

Here is a simple example that deals with the default unit of length set to RPX

// set size unit
const handleGeneratorUnit = function(style, prop) {
  return function({ Utility, Style }) {
    return Utility.handler
      .handleStatic(Style(style))
      .handleSize()
      .handleNumber(1.undefined.'int'.(number) = > `${number}rpx`)
      .handleNegative()
      .createProperty(prop)
  }
}
Copy the code

The main processing operations are as follows:

  1. Identify the need to handle to the utility class and set the global Settings to the property name;
  2. Check whether the value issizeIf so, returnUtility.amount;
  3. Returns an integer with values ranging from 1 to infinityrpxThe size of a unit;
  4. Whether the processing is negative, if so, add a negative sign;
  5. Create properties;

Windi external to provide the creation of tool class method is very many, and very flexible, if you need to develop a complex tool class plug-in, you can refer to the source code:

  • Dynamic tool class correlation
  • The official plug-in

The resources

  • Reimagine atomized CSS