如何用Stylelint检查厂商前缀及防止流水线生成无前缀CSS?
Great question—preventing unprefixed CSS from slipping into production (especially for IE11) is such a critical guardrail, so let’s break down actionable solutions both with and without Stylelint.
Option 1: Stylelint-Based Reverse Prefix Checking
Stylelint doesn’t have a built-in rule to enforce required vendor prefixes, but you can either use a community plugin or build a custom rule to fill this gap.
Suboption 1a: Use stylelint-no-unsupported-browser-features
This plugin checks if your CSS uses features that aren’t supported by your target browsers—and will throw errors if you’re missing required prefixes for those features. Here’s how to set it up:
- Install the plugin:
npm install stylelint-no-unsupported-browser-features --save-dev - Add it to your Stylelint config, matching your Autoprefixer target browsers:
{ "plugins": ["stylelint-no-unsupported-browser-features"], "rules": { "plugin/no-unsupported-browser-features": [ true, { "browsers": ["IE 11", "last 2 versions"], "severity": "error" } ] } }
Now, if Autoprefixer is disabled and your CSS lacks required prefixes for IE11, Stylelint will throw build-breaking errors.
Suboption 1b: Build a Custom Stylelint Rule
If you need fine-grained control over exactly which properties require prefixes, write a custom Stylelint rule. Here’s a minimal example focused on IE11-critical properties:
// stylelint-rules/require-vendor-prefixes.js module.exports = { ruleName: 'custom/require-vendor-prefixes', messages: { missingPrefix: 'Missing required vendor prefix for property "{{property}}"' }, meta: { type: 'problem', docs: { description: 'Enforce vendor prefixes for critical properties' } }, create(context) { const criticalProperties = ['transform', 'flex', 'grid', 'transition']; const requiredPrefixes = { 'transform': '-ms-transform', 'flex': '-ms-flex', 'grid': '-ms-grid' }; return { Declaration(decl) { const prop = decl.prop.toLowerCase(); if (criticalProperties.includes(prop) && !decl.prop.startsWith('-')) { // Check if the required prefixed property exists in the same rule const ruleNodes = decl.parent.nodes; const hasPrefix = ruleNodes.some(node => node.type === 'decl' && node.prop === requiredPrefixes[prop] ); if (!hasPrefix) { context.report({ node: decl, messageId: 'missingPrefix', data: { property: prop } }); } } } }; } };
Add this rule to your Stylelint config:
{ "plugins": ["./stylelint-rules/require-vendor-prefixes.js"], "rules": { "custom/require-vendor-prefixes": "error" } }
Option 2: Non-Stylelint Guardrails
If you prefer to avoid Stylelint for this check, here are two robust alternatives directly integrated into your build pipeline:
Suboption 2a: PostCSS Validation Plugin
Add a custom PostCSS plugin that runs after Autoprefixer to verify required prefixes exist. If Autoprefixer is disabled, this plugin will throw an error and stop the build:
// postcss-plugins/postcss-require-prefixes.js module.exports = () => { return { postcssPlugin: 'postcss-require-prefixes', Declaration(decl) { const requiredPrefixMap = { 'transform': '-ms-transform', 'flex': '-ms-flex', 'grid': '-ms-grid' }; const prop = decl.prop.toLowerCase(); if (requiredPrefixMap[prop] && !decl.prop.startsWith('-')) { const ruleChildren = decl.parent.nodes; const hasRequiredPrefix = ruleChildren.some(child => child.type === 'Declaration' && child.prop === requiredPrefixMap[prop] ); if (!hasRequiredPrefix) { throw new Error(`❌ Missing required prefix for "${prop}" in ${decl.source.input.file}`); } } } }; }; module.exports.postcss = true;
Update your PostCSS config to include this plugin after Autoprefixer:
// postcss.config.js module.exports = { plugins: [ require('autoprefixer'), require('./postcss-plugins/postcss-require-prefixes') ] };
Suboption 2b: Build-Time Validation Script
Create a Node.js script that scans your final built CSS files for missing prefixes, and exits with an error code if any are found. Add this script to your build workflow to block deployment:
// scripts/validate-css-prefixes.js const fs = require('fs'); const csstree = require('css-tree'); const cssFilePath = './dist/main.css'; // Update to your output CSS path const cssContent = fs.readFileSync(cssFilePath, 'utf8'); const ast = csstree.parse(cssContent); const requiredPrefixMap = { 'transform': '-ms-transform', 'flex': '-ms-flex', 'grid': '-ms-grid' }; let hasErrors = false; csstree.walk(ast, { visit: 'Declaration', enter(node) { const prop = node.property.toLowerCase(); if (requiredPrefixMap[prop] && !node.property.startsWith('-')) { const ruleNodes = node.parent.children; const hasPrefix = Array.from(ruleNodes).some(child => child.type === 'Declaration' && child.property === requiredPrefixMap[prop] ); if (!hasPrefix) { console.error(`❌ Missing prefix for "${prop}" in ${cssFilePath}`); hasErrors = true; } } } }); if (hasErrors) { process.exit(1); // Exit with error code to fail the build }
Update your package.json scripts to run this after build:
{ "scripts": { "build": "webpack", "validate:css": "node ./scripts/validate-css-prefixes.js", "build:prod": "npm run build && npm run validate:css" } }
Bonus: Lock Down Autoprefixer to Prevent Accidental Disabling
Add a check in your Webpack config to ensure Autoprefixer is always present in your PostCSS plugins:
// webpack.config.js const postcssConfig = require('./postcss.config'); const hasAutoprefixer = postcssConfig.plugins.some(plugin => { // Handle both direct require calls and plugin objects return (typeof plugin === 'function' && plugin.postcssPlugin === 'autoprefixer') || (typeof plugin === 'object' && plugin.postcssPlugin === 'autoprefixer'); }); if (!hasAutoprefixer) { throw new Error('❌ Autoprefixer is missing from PostCSS plugins! This is required for IE11 support.'); }
内容的提问来源于stack exchange,提问作者madflow

