Make writing a habit together! This is the first day I participated in the “Dig gold Day New Plan · April More text challenge”
About PostCss
When writing CSS attributes in the front end, it is also to be compatible with different browsers, so it is necessary to add different prefixes to the attributes. Sometimes, in order to add an attribute, it is necessary to add 3 to 4 similar attributes just to meet the compatibility of the browser. This will not only increase the workload, but also easy to miss, resulting in style compatibility problems.
As the front end becomes more and more powerful, and we can configure it in compilation tools (Webpack,rollup, etc.) to automatically complete prefixes during compilation, we have more energy to focus on more important areas.
In most cases, this borrows the power of PostCss, which is a CSS style conversion tool using JS plug-ins that can merge CSS styles, convert PX to REM, support variables, handle inline images, etc. In other words, without these small and beautiful plug-ins, Then PostCss doesn’t do anything.
Currently, PostSS has over 200 plug-ins, all of which can be found in the plug-ins list or searchable directory.
How is it used in engineering?
For example, 🌰
Webpack
Webpack. Config. Use js postcss – loader:
module.exports = {
module: {
rules: [{test: /\.css$/,
exclude: /node_modules/,
use: [
{
loader: 'style-loader'}, {loader: 'css-loader'}, {loader: 'postcss-loader'}]}}Copy the code
Create postcss.config.js and configure postcss plug-ins:
module.exports = {
plugins: [
require('autoprefixer'),
require('postcss-px2rem')]}Copy the code
Gulp
Use gulp-postcss and gulp-sourcemaps in Gulp.
gulp.task('css'.() = > {
const postcss = require('gulp-postcss')
const sourcemaps = require('gulp-sourcemaps')
return gulp.src('src/**/*.css')
.pipe( sourcemaps.init() )
.pipe( postcss([ require('autoprefixer'), require('postcss-nested') ]) )
.pipe( sourcemaps.write('. ') )
.pipe( gulp.dest('build/'))})Copy the code
JS API
You can also use the JS API directly
const autoprefixer = require('autoprefixer')
const postcss = require('postcss')
const postcssNested = require('postcss-nested')
const fs = require('fs')
fs.readFile('src/app.css'.(err, css) = > {
postcss([autoprefixer, postcssNested])
.process(css, { from: 'src/app.css'.to: 'dest/app.css' })
.then(result= > {
fs.writeFile('dest/app.css', result.css, () = > true)
if ( result.map ) {
fs.writeFile('dest/app.css.map', result.map.toString(), () = > true)}})})Copy the code
Big play
Postcss-loader for Webpack, gulp-postcss for gulp, but they all use PostCss internally, just adapted for different compilation tools. Then take the most familiar postCSS-loader as an example, his internal source code structure is as follows, it can be seen that postCSS is also used
const path = require('path'); const { getOptions } = require('loader-utils'); const validateOptions = require('schema-utils'); const postcss = require('postcss'); const postcssrc = require('postcss-load-config'); const Warning = require('./Warning.js'); const SyntaxError = require('./Error.js'); const parseOptions = require('./options.js'); function loader(css, map, meta) { const options = Object.assign({}, getOptions(this)); validateOptions(require('./options.json'), options, 'PostCSS Loader'); const cb = this.async(); const file = this.resourcePath; const sourceMap = options.sourceMap; Promise.resolve().then(() => {// webpack built-in argument processing and extension // omit return postcssrc(rc.ctx, rc.path); }).then((config) => {// omit return postcss(plugins).process(CSS, options).then((result) => {//... cb(null, css, map, meta); return null; }); }). Catch ((err) => {// error handling}); } module.exports = loader;Copy the code
API
PostCss takes a CSS file and provides an API to analyze and modify its rules by converting it into an abstract syntax tree. This API can be used by plug-ins to do many useful things, such as automatically finding errors, or inserting browser prefixes.
1. Create a PostCSS instance, pass in the plugins you need, initialize the plugins, and use the plugins to process the CSS file
let processor = require('postcss')
Copy the code
The processor has two attributes and two methods
- Plugins: attributes that the processor receives plug-in parameters
const processor = postcss([autoprefixer, postcssNested])
processor.plugins.length //=> 2
Copy the code
- Version: Indicates the version number of the processor
if (result.processor.version.split('.')[0] ! == '6') { throw new Error('This plugin works only with PostCSS 6') }Copy the code
- Process: method that parses the CSS and returns an instance of promise, with parameters in the code, that accepts two parameters
- CSS: A CSS file to be translated, or a function with the toString() method
- The second argument is an object, processOptions, which can have six properties, annotated in the example
- Use: method to add a plug-in to the processor, usually in four forms (not commonly used)
- Formatting plug-in
- A constructor containing plugincreator.postcss = true,
- A function, PostCSS the first argument is @{link Root} and the second argument is Result.
- Other PostCSS instances, PostCSS will copy the plugin for that instance into this PostCSS instance.
So the general way to use it is as follows
Let processor = require('postcss') const processOptions = {from: 'a.css', 'a.ut.css ', // output path // Parser: The parser setting is mainly used to turn CSS strings into AST // stringifier: The stringifier is used to convert AST to CSS strings // map:, sourceMap: Process.process (CSS, processOptions). Then (result => {console.log(result.css)})Copy the code
🌰
Let’s take a look at the AST format PostCSS converts CSS source code to in action:
const postcss = require('postcss') postcss().process(` @media screen and (min-width: 480px) { body { background-color: lightgreen; */ #app {border: 1px solid #000; } `).then(result => { console.log(result) })Copy the code
Using PostCSS directly, converting CSS source code without using any plug-ins is as follows
{"raws": {"semicolon": false, // raws. There is a semicolon in the same semicolon. "after": "" // raws. "Root ", // Current object type "nodes": [// child node {"raws": {"before": "", // raws. ", // raws.afterName "; // raws.afterName "; "semicolon": false, "after": "\n"}, "type": "atrule", // The current node type" name": "media", // the identity name after @ "source": {// the source field records the start of the @ statement, and the information of the current file "inputId": 0, "start": { "offset": 0, "line": 1, "column": 1 }, "end": { "offset": 94, "line": 5, "column": 1 } }, "params": "Screen and (min - width: 480 px)", / / / / @ identification parameters after "nodes" : [{" raws ": {" before" : "\ n", "between" : ""," semicolon ": true, "after": "\n " }, "type": "rule", "nodes": [ { "raws": { "before": "\n ", "between": ": " }, "type": "decl", "source": { "inputId": 0, "start": { "offset": 58, "line": 3, "column": 9 }, "end": { "offset": 86, "line": 3, "column": 37 } }, "prop": "background-color", "value": "lightgreen" } ], "source": { "inputId": 0, "start": { "offset": 43, "line": 2, "column": 5 }, "end": { "offset": 92, "line": 4, "column": 5 } }, "selector": "Body"}}, {" raws ": {" before" : "\ n", "left" : ""," right ":" "}, "type" : "comment" and "source" / / comment node: { "inputId": 0, "start": { "offset": 96, "line": 6, "column": 1 }, "end": { "offset": 107, "line": 6, "column": 12}}, "text", "this is a comment" / / comment node content}, {" raws ": {" before" : "\ n", "between" : ""," semicolon ": true," after ": "\n" }, "type": "rule", "nodes": [ { "raws": { "before": "\n ", "between": ": " }, "type": "decl", "source": { "inputId": 0, "start": { "offset": 120, "line": 8, "column": 5 }, "end": { "offset": 142, "line": 8, "column": 27}}, "prop" : "border", / / attribute "value" : "1 px solid # 000"}] / / attribute value, the "source" : {" inputId ": 0," start ": {" offset" : 109, "line": 7, "column": 1 }, "end": { "offset": 144, "line": 9, "column": 1 } }, "selector": "# app" / / selector name}], "the source" : {" inputId ": 0," start ": {" offset" : 0, "line" : 1, the "column" : 1}}, "inputs" : {"hasBOM": false, "CSS ": "@media screen and (min-width: 480px) {\n body {\n background-color: \n#app {\n border: 1px solid #000;\n}", "id": "<input CSS VT5Twy>"}]} lightgreen;\n}\n}\n/*Copy the code
You can also use online transfer directly
Next, we introduce the main node types of CSS AST nodes, and the related node attribute tags are in the upper code
- Root: the Root node, Commont, AtRule, and Rule are its children.
- Commont: comment node.
- AtRule: node with @ identifier.
- Rule: indicates the selector node
- Declaration: Each CSS property and its value represents a Declaration
There are also properties and actions for each node type that can be found in the official documentation
After obtaining the AST, we can perform operations on the AST to achieve related functions
Develop a PostCss plug-in
The PostCSS plugin is a JS object, and its basic form and parsing are as follows:
Module.exports = (opts = {}) => {return {postcssPlugin: 'postcss-test', // plugin name, starting with postcss-once (root, postcss) { }, Declaration(decl, postcss) {// postcss is called when traversing the CSS style, where you can get a node of type DECl}, Declaration: {color(decl, postcss) { }}, Comment(Comment, postcss) {// Quick access to AST Comment node (type is Comment)}, AtRule(AtRule, }} module.exports.postcss = trueCopy the code
With the PostCSS plugin format and API understood, we will develop a simple plugin according to the actual needs, there is CSS as follows:
.demo {
font-size: 14px; /*this is a comment*/
color: #ffffff;
}
Copy the code
Requirements are as follows:
Change all hexadecimal # FFFFFF to the CSS built-in color variable white according to the plugin format in Section 3, this development only needs to use Comment and Declaration interface:
// plugin.js
module.exports = (opts = {}) => {
return {
postcssPlugin: 'postcss-test',
Declaration(decl, postcss) {
if (decl.value === '#ffffff') {
decl.value = 'white'
}
},
Comment(comment) {
comment.text = ''
}
}
}
module.exports.postcss = true
Copy the code
Use this plugin in PostCSS:
// index.js
const plugin = require('./plugin.js')
postcss([plugin]).process(`
.demo {
font-size: 14px; /*this is a comment*/
color: #ffffff;
}
`).then(result => {
console.log(result.css)
})
Copy the code
The running results are as follows:
.demo {
font-size: 14px; /**/
color: white;
}
Copy the code
As you can see, the color of the font has been successfully converted, the comment content has been deleted, but the comment identifier still exists. This is because the comment node contains the /**/ content. As long as the comment node still exists in the AST, the PostCSS will restore the content. The AST nodes field needs to be traversed and the nodes whose type is comment need to be deleted. The plug-in source code is modified as follows:
// plugin.js
module.exports = (opts = {}) => {
// Work with options here
// https://postcss.org/api/#plugin
return {
postcssPlugin: 'postcss-test',
Once(root, postcss) {
root.nodes.forEach(node => {
if (node.type === 'rule') {
node.nodes.forEach((n, i) => {
if (n.type === 'comment') {
node.nodes.splice(i, 1)
}
})
}
})
},
Declaration(decl, postcss) {
if (decl.value === '#ffffff') {
decl.value = 'white'
}
}
}
}
module.exports.postcss = true
Copy the code
Run the PostCSS command again, and the result is as follows.
.demo {
font-size: 14px;
color: white;
}
Copy the code
conclusion
PostCss parses CSS into abstract syntax trees (AST) and provides a set of apis to manipulate AST. PostCss uses any number of “plugin” functions to convert CSS styles. 5. Sourcemaps can be generated to keep track of any changes. 6. PostCss is like an ecosystem for dealing with CSS, and no specific plug-in can fully represent PostCss.
Reference article: How to Write your own PostCSS 8 plug-in? (https://zhuanlan.zhihu.com/p/426470972) [Postcss use and principle of analytical] (https://www.jianshu.com/p/183af77a51ec) [the official documentation] (https://www.postcss.com.cn/api/#processor-use)Copy the code