When developing H5 web pages in daily work, vw units are often used in order to better adapt to different sizes, usually by vw = element size/screen size of design drawing. For example, if an element is 100px wide on a 37.5px design drawing, the resulting VW is 37.5/375 * 100 = 10VW. It is very troublesome to calculate this way every time, so I want to automate this matter

postcss

Postcss converts CSS to AST and provides a mechanism for running plug-ins. This is similar to Babel, except that it handles js and CSS objects. Learn how to develop Babel plugins this time!

Where can I check the AST of CSS? Go to astexplorer.net/ and change the above two configurations to CSS and postCSS

Train of thought to sort out

It’s important to think before you write code. Our goals are as follows:

  1. In order not to affect the use of PX in old projects or where px is necessary, we cannot forcibly convert all px values
  2. Flexible screen size, retention of decimal places and so on

Think about how to do this step by step:

  1. With ast access, we want to match for examplewidth: 300px, take 300 and calculate the value of vw, and change px to vw
  2. There needs to be an identifier so that we can process some of the px that needs to be converted and not others, which I use by defaultwidth: '%300px'In this case, you need to write it as a string (because the editor will error).
  3. Allows parameters to be passed dynamically
  4. Let’s say the result is zeroWidth: 80.00 vwThe integer should automatically remove the decimal point towidth: 80vw, reduce the volume

Began to develop

Since the company uses postCSS 7.x series, I use the syntax of 7. X series plug-in for writing (there is a little difference in the registration of plug-ins above version 8, but it does not affect everything).

If we don’t write any logic, how should we write a basic plugin

module.exports = postcss.plugin('plugin-name'.option= > { 
	return (root, result) = > { 
	   // Run the ast from root here}})Copy the code

Now that we know the basic template of the plugin, how do we manipulate the AST? How many types does postCSS define for us

  • Root: PostCss Processed Css. The entire processing process basically revolves around Root, Commont, AtRule, and Rule as its child nodes.
  • AtRule: indicates the part marked with @. Name indicates the identification name and params indicates the identification parameter. Nodes are other internally contained child nodes, which can be Commont, AtRule, or Rule. This allows us to customize more rules
  • Rule: In the selector style section, a selector represents a Rule, and the style list for the selector nodes is the Declaration constructor
  • Declaration: Css style property,prop is style property, value is style value. You can manually add style attributes to the Rule, or modify prop,value.

For these types, the document provides us with the following methods to find these types of nodes. For details, you can view the document at the address postcss:

  • walk
  • walkAtRules
  • walkComments
  • walkDecls
  • walkRules

Then we should use which to achieve our function, at this time, the role of astExplorer site is reflected, we can first need to deal with CSS in the above generation AST to view,You can see ourwidth: 300pxIn a decL node, there is an outer layer of a rule, which is called.demoThe class

Now I know if I just need to deal with decL nodes.

To have a try

WalkDecls receives a callback, and the decL node given to us by PostCSS will be received in the callback. We will match the value that meets our rules through the regex, calculate and rewrite it

const postcss = require('postcss')
module.exports = postcss.plugin('PLUGIN_NAME'.function (opts) {
  opts = opts || {}

  return function (root, result) {
    root.walkDecls(decl= > {
	  // Decl. Value is 300px in the ast above
      // Match '%300px'/'%-300px' and then process,
      if( / \ '% -? (\d+)px\'/.test(decl.value) ) {
        decl.value = decl.value.replace(/ \ '% -? (\d+)px\'/g.(matchStr) = > {
          // Remove 300 from '%300px'
          const numberPx = matchStr.match(/ -? \d+/g)
          // The size of the design is 375
          const toVw = (numberPx[0] / 375 * 100).toFixed(2)
          // The + is used to make the number 80.00 become 80
          return +toVw + 'vw'})}})Copy the code

Let’s try to run the plugin we wrote. Here I use vue-CLI, which is configured in vue.config.js.

module.exports = {
 css: {
    loaderOptions: {
      postcss: {
        plugins: [
		  // The name of the plug-in we just wrote
          require('./test.postcss.js') ({// Write arguments here})]}}}},Copy the code

If your project is not vue-cli written in postcss.config.js the same goes for require

Take a look at the result

/* Before processing */
.demo {
  width: '%300px';
  height: 400px;
  left: '%10px';
  border: '%-1px' solid black;
}

/* After processing */
.demo {
  width: 80vw;
  height: 400px;
  left: 2.67 vw;
  border: -0.27 vw solid black;
}
Copy the code

Ok succeeded

And then we figured out how to make it flexible and configurable and define three parameters

  • Screen Specifies the screen width. The default value is 375
  • ToFixed Specifies the number of digits reserved for the decimal point, which defaults to 2
  • Identifier Identifier default ‘%’

Let’s modify the code as follows:

const postcss = require('postcss')
module.exports = postcss.plugin('PLUGIN_NAME'.function (opts) {
  opts = opts || {}
  const _screen = opts.screen || 375
  const _toFixed = opts.toFixed || 2
  const _identifier = opts.identifier || The '%'

  const vwRgx = new RegExp(` \ \ '${_identifier}-? (\\d+)px\\'`.'g')


  return function (root, result) {
    root.walkDecls(decl= > {
      if( vwRgx.test(decl.value) ) {
        decl.value = decl.value.replace(vwRgx, (matchStr) = > {
          const numberPx = matchStr.match(/ -? \d+/g)
          const toVw = (numberPx[0] / _screen * 100).toFixed(_toFixed)
          return +toVw + 'vw'})}})Copy the code

Test it out:

The plug-in parameters are as follows

plugins: [
  require('./test.postcss.js') ({screen: 750.toFixed: 4.identifier: 'vw'})]Copy the code

Take a look at the result

/* Before processing */
.demo {
  width: 'vw300px';
  height: 400px;
  left: 'vw10px';
  border: 'vw-1px' solid black;
}

/* After processing */
.demo {
  width: 40vw;
  height: 400px;
  left: 1.3333 vw;
  border: -0.1333 vw solid black;
}
Copy the code

In this way, the basic function of our plug-in is completed, and the processing of @media media query unit is also the same. It is not more than described, but interested in github to view the complete code, the code has been uploaded to Github, welcome star

The plugin is also published in the NPM repository, you can download postCSS-px2VM to use