
PostCSS 8 完全指南用 JavaScript 插件转换与增强 CSS 的工程化实战【免费下载链接】postcssTransforming styles with JS plugins项目地址: https://gitcode.com/gh_mirrors/po/postcss导读PostCSS 是一个用 JavaScript 插件对样式进行转换的工具它把 CSS 解析成抽象语法树AST再交给插件完成 Lint、变量与 mixin 支持、未来语法转译、图片内联等一切可编程操作。本文以本仓库 README.md 为骨架结合 lib/ 下的真实源码与 docs/ 文档系统讲解 PostCSS 的核心原理、插件生态、多语法支持以及它在 Webpack、Parcel、Gulp、CLI、CSS-in-JS 等场景下的完整接入方案。读完本文你将掌握从两步接入到JS API 深度调用、再到自己写一个插件的完整能力。一、PostCSS 是什么PostCSS 是一个用 JS 插件转换样式的工具。这些插件可以完成大量日常工作Lint 你的 CSS检查错误、规范风格支持变量和 mixin如 Sass 风格的语法糖转译未来的 CSS 语法把草案语法编译为当前浏览器可用的代码内联图片、处理字体、生成雪碧图等。按 README.md 的说明PostCSS 被 Wikipedia、Twitter、Alibaba、JetBrains 等行业领导者使用其生态中的 [Autoprefixer] 和 [Stylelint] 是最流行的 CSS 工具之二[Autoprefixer] 自动添加厂商前缀Stylelint 是模块化样式表 Linter。关键定位PostCSS 不是又一个预处理器而是一个CSS 编译器框架。它不强迫你使用某套特定语法而是把 CSS 拆解为可编程的 AST让你或生态中的 200 插件用 JavaScript 以任意方式分析和修改它。本仓库当前版本为8.5.26见 package.json许可证为 MIT。核心工作流PostCSS 的工作流可以概括为四步读取接收一段 CSS 字符串或文件解析将其转换为 [Abstract Syntax Tree]抽象语法树即 AST转换按顺序运行插件数组每个插件以监听器visitor方式遍历、修改 AST输出把修改后的 AST 重新序列化为 CSS 字符串并可附带 Source Map。在 lib/postcss.js 的入口函数中这个流程的骨架清晰可见function postcss(...plugins) { if (plugins.length 1 Array.isArray(plugins[0])) { plugins plugins[0] } return new Processor(plugins) }传入的插件数组会被交给Processor由Processor.process()驱动解析、插件执行与字符串化详见下文源码级原理一节。二、源码级原理AST 与处理管线2.1 AST 节点类型PostCSS 把 CSS 解析成节点树核心节点类型如下可对照 docs/writing-a-plugin.md 与 lib/ 目录下的类实现节点类型类文件含义与示例Rootlib/root.js树顶节点代表整个 CSS 文件AtRulelib/at-rule.js以开头的语句如charset UTF-8、media (screen) {}Rulelib/rule.js选择器加声明块如input, button {}Declarationlib/declaration.js键值对如color: blackCommentlib/comment.js独立注释选择器、at-rule 参数和值内部的注释则存放在节点的raws属性中Documentlib/document.js多根文档容器用于 SCSS/Less 等多文件场景在 lib/lazy-result.js 的TYPE_TO_CLASS_NAME映射中可以看到这些类型的内部名称const TYPE_TO_CLASS_NAME { atrule: AtRule, comment: Comment, decl: Declaration, document: Document, root: Root, rule: Rule }2.2 解析parse()与Parserlib/parse.js 是解析入口它先构造Input封装 CSS 字符串与文件信息再交给Parser执行真正的词法与语法分析最终返回parser.rootfunction parse(css, opts) { let input new Input(css, opts) let parser new Parser(input) try { parser.parse() } catch (e) { // ... } return parser.root }值得注意的一个细节当解析失败且opts.from指向.scss、.sass或.less文件时lib/parse.js 会追加友好提示提醒你用postcss-scss、postcss-sass或postcss-less解析器重试——这正是多语法支持在错误处理层面的体现。2.3 插件标准化Processor.normalize()lib/processor.js 中的normalize()负责把各种形态的插件统一为函数数组。它支持四种写法i.postcss true函数式插件直接调用i()得到插件对象i.postcss为真解包出i.postcss兼容旧式postcss.plugin创建的插件对象且含plugins数组递归展开嵌套插件组对象且含postcssPlugin现代插件对象直接入队。若传入的是{ parse, stringify }形态的对象语法包lib/processor.js 会抛出明确错误提示应通过syntax/parser/stringifier选项而非插件数组来使用语法。非法值则会报xxx is not a PostCSS plugin。2.4 惰性执行与零工作优化Processor.process(css, opts)有两个出口lib/processor.jsprocess(css, opts {}) { if ( !this.plugins.length !opts.parser !opts.stringifier !opts.syntax ) { return new NoWorkResult(this, css, opts) } else { return new LazyResult(this, css, opts) } }NoWorkResult当没有插件、也没自定义 parser/stringifier/syntax 时直接走 lib/no-work-result.js 的零开销路径LazyResult常规路径见 lib/lazy-result.js。它采用惰性求值——result.css等 getter 首次访问时才真正执行解析、插件遍历与序列化。2.5 插件事件模型插件监听器通过事件驱动lib/lazy-result.js 定义了完整的事件名Once、OnceExit、Root、RootExit、AtRule、AtRuleExit、Rule、RuleExit、Declaration、DeclarationExit、Comment、CommentExit、Document、DocumentExit以及动态生成监听器的prepare。getEvents()lib/lazy-result.js揭示了事件派发的细节对于decl节点会以prop小写作为二级键对于atrule以name小写作为二级键。因此插件既可以监听Declaration(decl)也可以精确监听Declaration: { color: decl {} }或AtRule: { media: atRule {} }。重新访问机制插件修改过的节点会被再次访问其父节点也会因子节点变更而被重新访问只有Once与OnceExit不会被重复调用。这一点是编写无副作用插件时必须牢记的详见第五节。2.6Result与消息通道处理结果由 lib/result.js 的Result类承载核心成员包括result.css输出的 CSS 字符串result.mapSource Map 对象若启用result.messages插件之间的消息数组类型如warning、dependency、dir-dependencyresult.warn(text, opts)产生一条警告并压入messagesresult.warnings()过滤出所有warning类型的消息。toString()直接返回this.css所以result可以当字符串用。三、插件生态总览PostCSS 的价值绝大部分来自插件生态。按 docs/plugins.md 的分类插件被划分为 Control、Packs、Future CSS Syntax、Fallbacks、Language Extensions、Colors、Images and Fonts、Grids、Optimizations、Shortcuts、Others、Analysis、Reporters、Fun 等十余个大类README.md 则精选了一批最能展示 PostCSS 能力的插件。3.1 解决全局 CSS 问题[postcss-use]允许直接在 CSS 里显式启用 PostCSS 插件且只对当前文件生效[postcss-modules] /react-css-modules自动隔离组件内的选择器CSS Modules 方案[postcss-autoreset]作为全局 reset 的替代方案更适合可隔离组件[postcss-initial]添加all: initial支持一键重置所有继承样式cq-prolyfill添加容器查询container query支持让样式响应父容器宽度。3.2 今天就用上未来的 CSS[autoprefixer]基于 Can I Use 数据自动添加厂商前缀[postcss-preset-env]把未来 CSS 特性转换为目标浏览器可理解的代码并按目标浏览器/运行环境决定所需的 polyfill详见 docs/plugins.md 的 Packs 分类。3.3 更好的 CSS 可读性[postcss-nested]像 Sass 一样展开嵌套规则[postcss-sorting]对规则与 at-rule 的内容排序[postcss-utilities]内置最常用的快捷方式与辅助 mixin以util规则形式提供short新增并扩展大量简写属性。3.4 图片与字体[postcss-url]重写url()的基准路径、内联或复制资源[postcss-sprites]由样式表生成雪碧图font-magician自动生成所需的全部font-face规则[postcss-inline-svg]内联 SVG 并可定制其样式[postcss-write-svg]直接在 CSS 中编写简单 SVGwebp-in-css/avif-in-css在 CSS background 中使用 WebP / AVIF 格式。3.5 Lintersstylelint模块化样式表 Linter内部即基于 PostCSS 构建stylefmt按stylelint规则自动格式化 CSS 的工具doiuse基于 Can I Use 数据检查浏览器支持情况colorguard帮助维护一致的配色。3.6 其他常用插件cssnano模块化 CSS 压缩器lost功能丰富的calc()网格系统rtlcss为从右到左RTL语言镜像样式。完整的插件目录200 插件含 Control、Colors、Grids、Optimizations、Fun 等全部类别见仓库内的 docs/plugins.md。如果你想开发新插件可以参考 docs/writing-a-plugin.md 与 docs/guidelines/plugin.md。四、多语法支持SyntaxesPostCSS 不仅可以处理标准 CSS还能处理任何语法——只要为其编写 parser 和/或 stringifier 即可扩展。常见语法包包括缩进语法sugarss类似 Sass/Stylus 的缩进语法自动切换postcss-syntax按文件扩展名自动切换语法HTML 类文件postcss-html解析style标签中的样式Markdownpostcss-markdown解析代码块中的样式CSS-in-JSpostcss-styled-syntax解析 styled-components 等模板字符串、postcss-jsxJSX 模板/对象字面量、postcss-styled预处理器语法postcss-scss、postcss-sass、postcss-less注意这三个不负责把 SCSS/Sass/Less 编译为 CSSpostcss-less-engine则不同它使用真正的 Less.js 求值把 Less编译为 CSSJS 样式postcss-js在 JS 中书写样式或转换 React Inline Styles、Radium、JSS容错与呈现postcss-safe-parser查找并修复 CSS 语法错误、midas把 CSS 字符串转换为高亮 HTML。在代码层面语法通过 Options 中的syntaxparser stringifier 组合、parser、stringifier注入而不是作为插件传入——这一点在 lib/processor.js 的报错信息中也有明确说明。五、快速上手两步开始按 README.md 的 Usage 章节接入 PostCSS 只需两步找到并安装你构建工具对应的 PostCSS 扩展loader / plugin / runner见下文各小节选择插件并加入你的 PostCSS 处理流程。绝大多数 runner 接受的配置形态一致一个插件数组 一个选项对象详见Options一节。六、构建工具集成实战6.1 CSS-in-JSastroturfREADME 推荐使用 [astroturf] 配合 PostCSS 处理 CSS-in-JS。在webpack.config.js中module.exports { module: { rules: [ { test: /\.css$/, use: [style-loader, postcss-loader] }, { test: /\.jsx?$/, use: [babel-loader, astroturf/loader] } ] } }随后创建postcss.config.js/** type {import(postcss-load-config).Config} */ const config { plugins: [require(autoprefixer), require(postcss-nested)] } module.exports config6.2 Parcel[Parcel] 内置 PostCSS 支持开箱即用 Autoprefixer 与 cssnano。如果想更换插件在项目根目录创建postcss.config.js即可/** type {import(postcss-load-config).Config} */ const config { plugins: [require(autoprefixer), require(postcss-nested)] } module.exports configParcel 甚至会为你自动安装这些插件。6.3 Webpack在webpack.config.js中使用 [postcss-loader]module.exports { module: { rules: [ { test: /\.css$/, exclude: /node_modules/, use: [ { loader: style-loader }, { loader: css-loader, options: { importLoaders: 1 } }, { loader: postcss-loader } ] } ] } }然后同样创建postcss.config.js内容与上文一致。注意 loader 链的顺序style-loader→css-loader→postcss-loaderPostCSS 处于最接近 CSS 源码的位置先完成转译再交给css-loader处理import/url()。6.4 Gulp使用 [gulp-postcss] 与gulp-sourcemapsgulp.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/)) })6.5 npm Scripts / CLI通过 [postcss-cli] 可以在命令行或 npm scripts 中使用 PostCSSpostcss --use autoprefixer -o main.css css/*.css这条命令对css/*.css应用autoprefixer插件并输出到main.css。6.6 浏览器环境如需在浏览器中编译 CSS 字符串例如 CodePen 之类的在线编辑工具使用 [Browserify] 或 [webpack] 把 PostCSS 及其插件打包为单个文件即可。这一点在 package.json 的browser字段中也有体现——打包时会把terminal-highlight、source-map-js、path、url、fs等 Node 环境依赖替换为false避免浏览器端报错。若要在浏览器中处理 React Inline Styles、JSS、Radium 等 CSS-in-JS 对象使用 [postcss-js] 转换样式对象const postcss require(postcss-js) const prefixer postcss.sync([require(autoprefixer)]) prefixer({ display: flex }) // { display: [-webkit-box, -webkit-flex, -ms-flexbox, flex] }6.7 更多 RunnersPostCSS 官方与社区为几乎所有主流工具提供了 runnerREADME.md 中列出的包括 Gruntlodder/grunt-postcss、HTMLposthtml-postcss、Styluspoststylus、Rolluprollup-plugin-postcss、Brunchpostcss-brunch、Broccolibroccoli-postcss、Meteor、ENBenb-postcss、Taskrtaskr-postcss、Startstart-postcss、Connect/Expresspostcss-middleware以及 Svelte Preprocessorsvelte-preprocess。所有 runner 都应遵循 docs/guidelines/runner.md 中的规范。七、JS API 深入对于其余环境Node 脚本、自定义构建流程、测试等可以直接使用 JS API。完整示例README.md 原样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) } }) })要点拆解postcss([...plugins])返回Processor实例见 lib/postcss.jsprocessor.process(css, options)返回LazyResult它是 thenableresult.css为输出字符串、result.map为 Source Map在 CommonJS 下require(postcss)直接得到入口函数ESM 环境则使用 lib/postcss.mjs见 package.json 的exports字段。另外lib/postcss.js 还导出了一系列工具与类常用有postcss.parse(css, opts)把 CSS 字符串解析为Root节点postcss.stringify(node)把节点树序列化为 CSS 字符串postcss.list用于解析逗号分隔值、空格分隔值等的工具lib/list.jspostcss.fromJSON从 JSON 恢复节点树lib/fromJSON.js节点工厂postcss.rule()、postcss.decl()、postcss.atRule()、postcss.comment()、postcss.root()、postcss.document()类引用postcss.Processor、postcss.Result、postcss.Root、postcss.Rule、postcss.Declaration、postcss.AtRule、postcss.Comment、postcss.Warning、postcss.CssSyntaxError、postcss.Input、postcss.Node、postcss.Container、postcss.Document。注意旧版 APIpostcss.plugin(name, initializer)已被废弃lib/postcss.js 会在调用时打印迁移提示新插件请使用返回{ postcssPlugin: 名称, ...listeners }对象的现代写法。7.1 Options 详解绝大多数 runner 接受两个参数插件数组与选项对象。常用选项README.md Options 章节选项说明syntax提供语法 parser 与 stringifier 的对象parser特殊语法解析器例如 SCSS 语法包stringifier特殊语法输出生成器例如 MidasmapSource Map 选项from输入文件名多数 runner 自动设置to输出文件名多数 runner 自动设置关于 Source Map 的详细选项说明可参阅仓库内 docs/source-maps.md。from选项还有一个实用副作用当解析失败时lib/parse.js 会根据from的扩展名给出该用哪个语法包的提示。7.2 把警告当作错误某些场景下我们希望任何来自 PostCSS 或插件的警告都直接让构建失败以保证警告不被忽视、避免潜在 bug。虽然 PostCSS 本身没有warnings as errors选项但只需在插件数组末尾追加postcss-fail-on-warn插件即可module.exports { plugins: [require(autoprefixer), require(postcss-fail-on-warn)] }原理上postcss-fail-on-warn读取result.warnings()由 lib/result.js 提供并抛出错误从而中断构建。八、编写你自己的 PostCSS 插件想真正掌握 PostCSS动手写一个插件是最好的方式。完整指南见 docs/writing-a-plugin.md这里提炼核心脉络。8.1 插件的基本形态现代插件是一个返回插件对象含postcssPlugin标识的工厂函数并用postcss true标记const plugin (opts {}) { // 插件创建器校验选项或准备共享状态 return { postcssPlugin: PLUGIN NAME // 插件监听器 } } plugin.postcss true export default pluginTypeScript 中将创建器类型标注为PluginCreatorpostcss true才能通过类型检查import type { PluginCreator } from postcss export interface PluginNameOptions { // 插件选项类型 } const plugin: PluginCreatorPluginNameOptions (opts {}) { return { postcssPlugin: PLUGIN NAME // 插件监听器 } } plugin.postcss true export default plugin这种形态如何被识别回顾 lib/processor.js 的normalize()i.postcss true时调用工厂得到对象再匹配postcssPlugin字段入队。8.2 查找节点监听器多数插件的逻辑是先找到什么再改什么。按类型监听module.exports (opts {}) { return { postcssPlugin: PLUGIN NAME, Once(root) { // 每个文件调用一次因为每个文件只有一个 Root }, Declaration(decl) { // 所有 declaration 节点 } } } module.exports.postcss true按名称精确定位利用前面讲过的prop/name二级键机制Declaration: { color: decl { // 所有 color 声明 }, *: decl { // 所有声明 } }, AtRule: { media: atRule { // 所有 media at-rule } }复杂值的解析可以配合社区专用解析器选择器解析器、值解析器、媒体查询解析器等但记得先用轻量检查如String#includes()过滤避免对每个节点都跑重解析。事件分为enter与exit两类Once、Root、AtRule、Rule在子节点处理之前调用OnceExit、RootExit、AtRuleExit、RuleExit在子节点全部处理之后调用。跨监听器共享数据可以用prepare(result)动态生成监听器module.exports (opts {}) { return { postcssPlugin: vars-collector, prepare(result) { const variables {} return { Declaration(node) { if (node.variable) { variables[node.prop] node.value } }, OnceExit() { console.log(variables) } } } } }8.3 修改节点PostCSS 提供 DOM 风格的 API 来增删改节点next()、parent、Container#some、append、cloneBefore等。监听器第二参数会传入节点构造器Declaration (node, { Rule }) { let newRule new Rule({ selector: a, source: node.source }) node.root().append(newRule) newRule.append(node) }新增节点时务必复制Node#source才能生成正确的 Source Map。警惕无限循环因为修改过的节点会被重新访问直接给节点添加子节点会触发死循环。两种防御手段// 方式一检查是否已处理 Declaration: { will-change: decl { if (decl.parent.some(decl decl.prop transform)) { decl.cloneBefore({ prop: transform, value: translate3d(0, 0, 0) }) } } } // 方式二用 Symbol 标记 const processed Symbol(processed) Rule(rule) { if (!rule[processed]) { process(rule) rule[processed] true } }8.4 警告、依赖与错误在监听器里通过result产生警告Declaration: { bad: (decl, { result }) { decl.warn(result, Deprecated property bad) } }如果插件依赖其他文件向result.messages推送dependency消息即可告知 runnerwebpack、Gulp 等在文件变化时重建AtRule: { import: (atRule, { result }) { const importedFile parseImport(atRule) result.messages.push({ type: dependency, plugin: postcss-import, file: importedFile, parent: result.opts.from }) } }依赖是目录时改用dir-dependency消息含dir字段。遇到语法错误可抛出decl.error()生成的错误携带word等定位信息。8.5 发布与维护README.md 与 docs/writing-a-plugin.md 都强调插件开发很容易欢迎提交新插件发布公开 npm 包时建议遵循安全的发布流程并在发布后把插件登记到 docs/plugins.md 的插件目录中。九、编辑器与 IDE 集成PostCSS 语法在主流编辑器中均有支持详见 README.md 的 Editors IDE Integration 章节VS Codecsstools.postcss扩展提供 PostCSS 支持smallcase.postcssense为全局类名提供 IntellisenseSublime TextSyntax-highlighting-for-PostCSS提供语法高亮Vimpostcss.vim提供语法高亮WebStorm 及其他 JetBrains IDE安装官方 PostCSS 插件即可获得支持。十、测试、工程化与支持本仓库自身的工程实践也值得参考测试采用uvu运行器 ts-node测试覆盖 test/ 下的postcss.test.ts、processor.test.ts、parse.test.ts、stringify.test.ts、lazy-result.test.ts、map.test.ts、visitor.test.ts等另有test/integration.js集成测试与test/fuzzing/fuzz_parse.js模糊测试体积预算package.json 的size-limit配置约束lib/postcss.js不超过 16.5 KB保证核心库足够轻量运行环境engines字段要求node ^10 || ^12 || 14依赖极简运行时仅依赖nanoidID 生成、picocolors终端着色、source-map-jsSource Map 生成这也是它能同时在 Node 与浏览器环境运行的原因。安全方面发现安全漏洞请按 SECURITY.md 与 docs/INCIDENT_RESPONSE.md 的流程报告项目还提供了 THREAT_MODEL.md 威胁模型文档供安全研究者参考。企业级商业支持与维护可通过 Tidelift 订阅获取。结语从两步接入到200 插件生态再到源码级 AST 管线与手写插件PostCSS 的价值在于把 CSS 处理变成了一件完全可编程的事情解析为 AST → 插件逐个变换 → 重新序列化。无论是为旧浏览器补前缀、把未来语法编译到今天可用、做 CSS-in-JS 转换还是构建自己的样式检查规则PostCSS 都提供了统一、可组合、可测试的框架。本文所涉及的源码入口、插件目录与开发指南都可以在本仓库的 lib/、docs/plugins.md、docs/writing-a-plugin.md 与 test/ 中进一步研读。【免费下载链接】postcssTransforming styles with JS plugins项目地址: https://gitcode.com/gh_mirrors/po/postcss创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考