PostCSS 基本使用 #

命令行使用 #

基本命令 #

bash
# 安装 CLI
npm install postcss-cli --save-dev

# 基本用法
npx postcss input.css -o output.css

# 使用配置文件
npx postcss input.css -o output.css --config

# 指定配置文件路径
npx postcss input.css -o output.css --config ./path/to/config

常用选项 #

bash
# 输入输出
npx postcss input.css -o output.css          # 指定输出文件
npx postcss input.css -d dist                # 指定输出目录
npx postcss src/*.css -d dist                # 处理多个文件

# 监听文件变化
npx postcss input.css -o output.css --watch

# Source Map
npx postcss input.css -o output.css --map    # 生成 source map
npx postcss input.css -o output.css --no-map # 不生成 source map

# 详细输出
npx postcss input.css -o output.css --verbose

# 使用特定插件
npx postcss input.css -o output.css --use autoprefixer

# 使用多个插件
npx postcss input.css -o output.css --use autoprefixer,cssnano

# 传递插件选项
npx postcss input.css -o output.css --use autoprefixer --autoprefixer.browsers "> 1%"

完整 CLI 选项 #

选项 说明
-o, --output 输出文件路径
-d, --dir 输出目录
-w, --watch 监听文件变化
--verbose 显示详细日志
--map 生成 source map
--no-map 不生成 source map
--config 配置文件路径
-u, --use 指定使用的插件
-p, --parser 自定义解析器
-t, --stringifier 自定义生成器
-s, --syntax 自定义语法

监听模式 #

bash
# 监听单个文件
npx postcss input.css -o output.css --watch

# 监听多个文件
npx postcss "src/**/*.css" -d dist --watch

# 结合其他工具
npx postcss input.css -o output.css --watch --verbose

处理多个文件 #

bash
# 处理目录下所有 CSS 文件
npx postcss "src/*.css" -d dist

# 递归处理子目录
npx postcss "src/**/*.css" -d dist

# 保持目录结构
npx postcss "src/**/*.css" -d dist --base src

Node.js API #

基本用法 #

javascript
const postcss = require('postcss')
const autoprefixer = require('autoprefixer')

const css = `
.button {
  display: flex;
  user-select: none;
}
`

// 异步处理
postcss([autoprefixer])
  .process(css, { from: 'input.css', to: 'output.css' })
  .then(result => {
    console.log(result.css)
  })
  .catch(error => {
    console.error(error)
  })

// 同步处理(不推荐)
const result = postcss([autoprefixer]).process(css, { from: undefined })
console.log(result.css)

async/await 用法 #

javascript
const postcss = require('postcss')
const autoprefixer = require('autoprefixer')

async function processCss(css) {
  try {
    const result = await postcss([autoprefixer])
      .process(css, { from: 'input.css', to: 'output.css' })
    
    console.log(result.css)
    
    // 获取 warnings
    result.warnings().forEach(warn => {
      console.warn(warn.toString())
    })
    
    return result.css
  } catch (error) {
    console.error('PostCSS error:', error)
  }
}

处理文件 #

javascript
const postcss = require('postcss')
const autoprefixer = require('autoprefixer')
const fs = require('fs')
const path = require('path')

async function processFile(inputPath, outputPath) {
  const css = fs.readFileSync(inputPath, 'utf8')
  
  const result = await postcss([autoprefixer])
    .process(css, { 
      from: inputPath, 
      to: outputPath,
      map: { inline: false }
    })
  
  // 写入 CSS 文件
  fs.writeFileSync(outputPath, result.css)
  
  // 写入 source map
  if (result.map) {
    fs.writeFileSync(outputPath + '.map', result.map.toString())
  }
}

processFile('src/style.css', 'dist/style.css')

使用配置文件 #

javascript
const postcss = require('postcss')
const postcssLoadConfig = require('postcss-load-config')

async function processWithConfig(css, ctx) {
  // 加载配置
  const config = await postcssLoadConfig(ctx)
  
  // 使用配置处理
  const result = await postcss(config.plugins)
    .process(css, {
      from: ctx.from,
      to: ctx.to,
      map: config.options.map
    })
  
  return result.css
}

processWithConfig(
  '.button { display: flex }',
  { from: 'src/style.css', to: 'dist/style.css' }
)

流式处理 #

javascript
const postcss = require('postcss')
const autoprefixer = require('autoprefixer')
const { Readable, Writable } = require('stream')

const cssStream = new Readable({
  read() {
    this.push('.button { display: flex }')
    this.push(null)
  }
})

const postcssStream = new Writable({
  write(chunk, encoding, callback) {
    const css = chunk.toString()
    
    postcss([autoprefixer])
      .process(css, { from: undefined })
      .then(result => {
        console.log(result.css)
        callback()
      })
      .catch(callback)
  }
})

cssStream.pipe(postcssStream)

PostCSS 对象 #

result 对象 #

javascript
const result = await postcss([autoprefixer])
  .process(css, { from: 'input.css' })

// 结果属性
result.css       // 处理后的 CSS 字符串
result.map       // Source map 对象
result.root      // AST 根节点
result.messages  // 消息数组(警告、依赖等)

// 方法
result.warnings()      // 获取所有警告
result.toString()      // 获取 CSS 字符串(包含 source map 注释)

root 对象(AST) #

javascript
const result = await postcss([autoprefixer])
  .process(css, { from: undefined })

const root = result.root

// 遍历节点
root.walk(node => {
  console.log(node.type)  // 'rule', 'atrule', 'decl', 'comment'
})

// 遍历规则
root.walkRules(rule => {
  console.log(rule.selector)
})

// 遍历声明
root.walkDecls(decl => {
  console.log(decl.prop, ':', decl.value)
})

// 遍历 @ 规则
root.walkAtRules(atRule => {
  console.log(atRule.name, atRule.params)
})

节点操作 #

javascript
const postcss = require('postcss')

const css = '.button { color: red; }'
const root = postcss.parse(css)

// 创建新节点
const newRule = postcss.rule({ selector: '.container' })
const newDecl = postcss.decl({ prop: 'padding', value: '20px' })

newRule.append(newDecl)
root.append(newRule)

// 修改节点
root.walkDecls('color', decl => {
  decl.value = 'blue'
})

// 删除节点
root.walkDecls('color', decl => {
  decl.remove()
})

// 替换节点
root.walkDecls('color', decl => {
  decl.replaceWith(
    postcss.decl({ prop: 'color', value: 'green' })
  )
})

console.log(root.toString())

插件使用 #

基本插件使用 #

javascript
const postcss = require('postcss')
const autoprefixer = require('autoprefixer')
const cssnano = require('cssnano')

const plugins = [
  autoprefixer({
    overrideBrowserslist: ['> 1%', 'last 2 versions']
  }),
  cssnano({
    preset: 'default'
  })
]

const result = await postcss(plugins)
  .process(css, { from: 'input.css' })

条件加载插件 #

javascript
const postcss = require('postcss')
const autoprefixer = require('autoprefixer')
const cssnano = require('cssnano')

const isProduction = process.env.NODE_ENV === 'production'

const plugins = [
  autoprefixer,
  isProduction && cssnano({ preset: 'default' })
].filter(Boolean)

const result = await postcss(plugins)
  .process(css, { from: 'input.css' })

插件选项 #

javascript
const autoprefixer = require('autoprefixer')
const cssnano = require('cssnano')
const postcssPresetEnv = require('postcss-preset-env')

const plugins = [
  // Autoprefixer 选项
  autoprefixer({
    overrideBrowserslist: ['> 1%', 'last 2 versions'],
    grid: true,
    flexbox: 'no-2009'
  }),
  
  // PostCSS Preset Env 选项
  postcssPresetEnv({
    stage: 2,
    features: {
      'nesting-rules': true
    }
  }),
  
  // cssnano 选项
  cssnano({
    preset: ['default', {
      discardComments: { removeAll: true },
      normalizeWhitespace: false
    }]
  })
]

Source Map #

生成 Source Map #

javascript
const result = await postcss([autoprefixer])
  .process(css, {
    from: 'src/style.css',
    to: 'dist/style.css',
    map: true  // 或 map: { inline: true }
  })

console.log(result.css)      // CSS + source map 注释
console.log(result.map)      // source map 对象

外部 Source Map #

javascript
const result = await postcss([autoprefixer])
  .process(css, {
    from: 'src/style.css',
    to: 'dist/style.css',
    map: {
      inline: false,           // 不内联
      prev: null,              // 之前的 source map
      sourcesContent: true,    // 包含源内容
      annotation: true         // 添加注释
    }
  })

// 分别保存
fs.writeFileSync('dist/style.css', result.css)
fs.writeFileSync('dist/style.css.map', result.map.toString())

链式 Source Map #

javascript
// 第一步处理
const result1 = await postcss([plugin1])
  .process(css, {
    from: 'src/style.css',
    to: 'temp/style.css',
    map: { inline: false }
  })

// 第二步处理(使用上一步的 source map)
const result2 = await postcss([plugin2])
  .process(result1.css, {
    from: 'temp/style.css',
    to: 'dist/style.css',
    map: { 
      inline: false,
      prev: result1.map  // 传入上一步的 source map
    }
  })

错误处理 #

捕获错误 #

javascript
try {
  const result = await postcss([autoprefixer])
    .process(css, { from: 'input.css' })
  console.log(result.css)
} catch (error) {
  // PostCSS 错误对象
  console.error(error.message)
  console.error(error.stack)
  
  // 错误位置
  if (error.source) {
    console.error('Error at line:', error.line)
    console.error('Column:', error.column)
    console.error('File:', error.file)
  }
}

自定义错误 #

javascript
const postcss = require('postcss')

const plugin = postcss.plugin('my-plugin', () => {
  return root => {
    root.walkDecls('color', decl => {
      if (decl.value === 'red') {
        throw decl.error('Do not use red color!', {
          plugin: 'my-plugin',
          word: 'red'
        })
      }
    })
  }
})

try {
  await postcss([plugin]).process('.button { color: red }', { from: undefined })
} catch (error) {
  console.error(error.message)  // Do not use red color!
}

警告处理 #

javascript
const result = await postcss([autoprefixer])
  .process(css, { from: 'input.css' })

// 获取所有警告
const warnings = result.warnings()

warnings.forEach(warning => {
  console.log('Type:', warning.type)
  console.log('Text:', warning.text)
  console.log('Line:', warning.line)
  console.log('Column:', warning.column)
  console.log('Plugin:', warning.plugin)
})

实用示例 #

批量处理文件 #

javascript
const postcss = require('postcss')
const autoprefixer = require('autoprefixer')
const cssnano = require('cssnano')
const fs = require('fs')
const path = require('path')
const glob = require('glob')

async function processFiles(pattern, outputDir) {
  const files = glob.sync(pattern)
  
  for (const file of files) {
    const css = fs.readFileSync(file, 'utf8')
    const relativePath = path.relative('src', file)
    const outputPath = path.join(outputDir, relativePath)
    
    // 确保输出目录存在
    fs.mkdirSync(path.dirname(outputPath), { recursive: true })
    
    const result = await postcss([autoprefixer, cssnano])
      .process(css, { 
        from: file, 
        to: outputPath,
        map: { inline: false }
      })
    
    fs.writeFileSync(outputPath, result.css)
    if (result.map) {
      fs.writeFileSync(outputPath + '.map', result.map.toString())
    }
    
    console.log(`Processed: ${file} -> ${outputPath}`)
  }
}

processFiles('src/**/*.css', 'dist')

监听文件变化 #

javascript
const postcss = require('postcss')
const autoprefixer = require('autoprefixer')
const fs = require('fs')
const chokidar = require('chokidar')

async function processFile(filePath) {
  const css = fs.readFileSync(filePath, 'utf8')
  const outputPath = filePath.replace('src', 'dist')
  
  const result = await postcss([autoprefixer])
    .process(css, { from: filePath, to: outputPath })
  
  fs.mkdirSync(path.dirname(outputPath), { recursive: true })
  fs.writeFileSync(outputPath, result.css)
  
  console.log(`Processed: ${filePath}`)
}

const watcher = chokidar.watch('src/**/*.css')

watcher
  .on('add', processFile)
  .on('change', processFile)
  .on('unlink', filePath => {
    const outputPath = filePath.replace('src', 'dist')
    if (fs.existsSync(outputPath)) {
      fs.unlinkSync(outputPath)
    }
  })

提取 CSS 变量 #

javascript
const postcss = require('postcss')

const extractVariables = postcss.plugin('extract-variables', () => {
  const variables = {}
  
  return root => {
    root.walkDecls(decl => {
      if (decl.prop.startsWith('--')) {
        variables[decl.prop] = decl.value
      }
    })
    
    return { variables }
  }
})

const css = `
:root {
  --primary: #007bff;
  --secondary: #6c757d;
}
`

const result = await postcss([extractVariables])
  .process(css, { from: undefined })

console.log(result.variables)
// { '--primary': '#007bff', '--secondary': '#6c757d' }

CSS 统计 #

javascript
const postcss = require('postcss')

const cssStats = postcss.plugin('css-stats', () => {
  let stats = {
    rules: 0,
    selectors: 0,
    declarations: 0,
    properties: {},
    values: {}
  }
  
  return root => {
    root.walkRules(rule => {
      stats.rules++
      stats.selectors += rule.selectors.length
    })
    
    root.walkDecls(decl => {
      stats.declarations++
      stats.properties[decl.prop] = (stats.properties[decl.prop] || 0) + 1
      stats.values[decl.value] = (stats.values[decl.value] || 0) + 1
    })
    
    return { stats }
  }
})

const result = await postcss([cssStats])
  .process(css, { from: undefined })

console.log(result.stats)

调试技巧 #

打印 AST #

javascript
const postcss = require('postcss')

const css = '.button { color: red; }'
const root = postcss.parse(css)

console.log(JSON.stringify(root, null, 2))

节点遍历调试 #

javascript
const postcss = require('postcss')

const css = `
.button {
  color: red;
  padding: 10px;
}
`

const root = postcss.parse(css)

root.walk(node => {
  console.log('Type:', node.type)
  console.log('Source:', node.source)
  console.log('---')
})

插件调试 #

javascript
const debugPlugin = postcss.plugin('debug', () => {
  return {
    postcssPlugin: 'debug',
    Once(root) {
      console.log('Processing CSS...')
    },
    Rule(rule) {
      console.log('Found rule:', rule.selector)
    },
    Declaration(decl) {
      console.log(`  ${decl.prop}: ${decl.value}`)
    }
  }
})
debugPlugin.postcss = true

await postcss([debugPlugin]).process(css, { from: undefined })

下一步 #

现在你已经掌握了 PostCSS 的基本使用方法,接下来学习 插件系统 深入了解 PostCSS 的核心机制!

最后更新:2026-03-28