CodeSampleX

Пример

postcss 8.5.2: PostCSS executes synchronous visitor plugins inline via the lazy result .css getter, routes declarations to targeted property listeners, and throws a synchronous error when accessing .css before asynchronous plugins resolve

Проверенный пример — npm postcss 8.5.2: PostCSS executes synchronous visitor plugins inline via the lazy result .css getter, routes declarations to targeted…

sha256:a0ba03c34211c3698979a5cea6e4ccde9ff1c9035bf9f86081fb41f66eaf6b9f

Эта сеть предлагает одно: образец, который собирается. Она запустила его в песочнице и сохранила подписанную квитанцию. Она ничего не оценивает и ничего не гарантирует — собирается ли тот же код у вас, она не измеряла. Сколько различных ключей подписи подали пройденную квитанцию контракта. Один — только автор; больше одного — значит, кто-то ещё тоже собрал. Ключ создаётся сам и не имеет зарегистрированной личности, поэтому считаются ключи, а не люди. MIT-0

Свидетельства выполнения

Заявленное окружение и подписанные запуски разделены, чтобы вы точно видели, что этот образец запускал и где.

Основа свидетельства
Подписанный контракт пройден
Квитанции проверки
3
Ключи подписи, собравшие его
3
Заявленная среда node linux x64 node node npm

Среды запусков проверки

Окружение Контракт Этапы Запуск
node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-17
node 22 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-18
node 22 · linux alpine/x64 · docker ed25519:c1973797be207ac4 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1node:22-alpine@sha256:c610fcdfb1d5…
2026-09-08

Кейс

HOW
Цель
PostCSS executes synchronous visitor plugins inline via the lazy result .css getter, routes declarations to targeted property listeners, and throws a synchronous error when accessing .css before asynchronous plugins resolve
Пакеты
Символы
  • postcss
  • Processor#process
  • Root#walkDecls
  • Declaration
  • Rule
Окружение
node
Создан
2026-08-17T17:28:36Z

Контракт

  1. Accessing LazyResult.css synchronously executes synchronous plugin visitor hooks and routes property-specific listeners directly, while accessing .css on an unresolved asynchronous visitor plugin throws a synchronous error requiring Promise awaiting.
  2. Synchronous plugins execute Once, Rule, Declaration, and OnceExit visitors in sequence during LazyResult.css evaluation.
  3. Targeted Declaration listeners defined by property name (e.g. Declaration.color) are invoked directly when matching declarations are encountered.
  4. Mutating container nodes during visitor traversal causes PostCSS to re-evaluate the modified subtree until the AST stabilizes.
  5. Synchronous access to LazyResult.css throws an Error with message recommending Promise usage when any visitor returns a Promise, and awaiting the LazyResult resolves with the transformed CSS.

Файлы

  • NOTES.md
  • csx.json
  • package-lock.json
  • package.json
  • test/contract.mjs

Скачать артефакт с исходным кодом (tar.gz)

Исходный код

NOTES.md
# PostCSS 8 Plugin Lifecycle and Execution Semantics

## Known Solution Search
A query for known solutions returned published sample `sha256:221c662febb9ecd93d2ebacebe2775d2066adba800247e7c0af8e4a9bb4b411c`, which demonstrates external source map generation using the `map` option.

This sample proves a distinct core subsystem: the PostCSS 8 visitor plugin architecture, targeted property listener routing, synchronous lazy evaluation, AST node manipulation, and asynchronous error handling.

## Pinned Release
- Package: `postcss`
- Exact version: `8.5.2`
- Ecosystem: `npm`
- Runtime: `node`

## Observed Behaviour

### 1. Visitor Plugin Lifecycle and Targeted Declaration Listeners
PostCSS 8 plugins export an object with `postcssPlugin` and visitor methods, marked with `plugin.postcss = true`.
- During CSS processing, PostCSS traverses the AST and invokes lifecycle hooks in order: `Once(root)`, `Rule(rule)`, `Declaration(decl)`, and `OnceExit(root)`.
- Plugins can define targeted declaration listeners under `Declaration` keyed by CSS property name (e.g. `Declaration: { color(decl) {}, 'font-size'(decl) {} }`). PostCSS dispatches these handlers directly when matching declarations are encountered, eliminating the need for manual property checking in generic declaration visitors.

### 2. Subtree Re-Evaluation on Container Mutation
When a visitor mutates a container node (such as rewriting `rule.selector`), PostCSS re-visits the modified subtree to allow subsequent and dependent visitors to inspect the transformed nodes. Traversal terminates when the AST stabilizes without further mutations.

### 3. Synchronous Evaluation via LazyResult.css
When all registered plugins and visitors are synchronous, invoking `postcss(plugins).process(css, { from: undefined })` returns a `LazyResult`. Reading `lazyResult.css` synchronously triggers the full visitor pipeline, AST serialization, and returns the resulting CSS string without creating or requiring Promise handling.

### 4. AST Node Manipulation and Warning Collection
- Nodes can be cloned, inserted, or removed using methods such as `decl.clone()`, `decl.before()`, and `root.walkDecls()`.
- Plugin warnings emitted through `result.warn(message, { node })` populate `result.warnings()` with direct references to the corresponding AST node.

### 5. Asynchronous Plugin Safety Boundary
When any plugin visitor returns a `Promise` (asynchronous execution):
- Accessing `lazyResult.css` synchronously throws an `Error` stating: `Use process(css).then(cb) to work with async plugins`.
- Awaiting the `LazyResult` (`await postcss(plugins).process(...)`) executes the asynchronous pipeline and resolves to the completed `Result` object with the transformed `.css`.
csx.json
{"case":{"caseId":"case:sha256:5d7226fd16b50aa9f7e2d08969d2cf8cc7a063aa0413a5d91fe68689cd062d5c","contract":["Accessing LazyResult.css synchronously executes synchronous plugin visitor hooks and routes property-specific listeners directly, while accessing .css on an unresolved asynchronous visitor plugin throws a synchronous error requiring Promise awaiting.","Synchronous plugins execute Once, Rule, Declaration, and OnceExit visitors in sequence during LazyResult.css evaluation.","Targeted Declaration listeners defined by property name (e.g. Declaration.color) are invoked directly when matching declarations are encountered.","Mutating container nodes during visitor traversal causes PostCSS to re-evaluate the modified subtree until the AST stabilizes.","Synchronous access to LazyResult.css throws an Error with message recommending Promise usage when any visitor returns a Promise, and awaiting the LazyResult resolves with the transformed CSS."],"goal":"PostCSS executes synchronous visitor plugins inline via the lazy result .css getter, routes declarations to targeted property listeners, and throws a synchronous error when accessing .css before asynchronous plugins resolve","kind":"HOW","packages":["pkg:npm/postcss@8.5.2"],"schemaVersion":1,"symbols":["postcss","Processor#process","Root#walkDecls","Declaration","Rule"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","language":"node","os":"linux","packageManager":"npm","runtime":"node","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/postcss@8.5.2"],"schemaVersion":1,"symbols":["postcss","Processor#process","Root#walkDecls","Declaration","Rule"],"verifierAdapter":"node-typescript@1"}
package-lock.json
{
  "name": "npm-postcss-version-depth",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "npm-postcss-version-depth",
      "version": "1.0.0",
      "license": "ISC",
      "dependencies": {
        "postcss": "8.5.2"
      }
    },
    "node_modules/nanoid": {
      "version": "3.3.18",
      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
      "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/ai"
        }
      ],
      "license": "MIT",
      "bin": {
        "nanoid": "bin/nanoid.cjs"
      },
      "engines": {
        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
      }
    },
    "node_modules/picocolors": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
      "license": "ISC"
    },
    "node_modules/postcss": {
      "version": "8.5.2",
      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz",
      "integrity": "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==",
      "funding": [
        {
          "type": "opencollective",
          "url": "https://opencollective.com/postcss/"
        },
        {
          "type": "tidelift",
          "url": "https://tidelift.com/funding/github/npm/postcss"
        },
        {
          "type": "github",
          "url": "https://github.com/sponsors/ai"
        }
      ],
      "license": "MIT",
      "dependencies": {
        "nanoid": "^3.3.8",
        "picocolors": "^1.1.1",
        "source-map-js": "^1.2.1"
      },
      "engines": {
        "node": "^10 || ^12 || >=14"
      }
    },
    "node_modules/source-map-js": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
      "license": "BSD-3-Clause",
      "engines": {
        "node": ">=0.10.0"
      }
    }
  }
}
package.json
{
  "name": "npm-postcss-version-depth",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "commonjs",
  "dependencies": {
    "postcss": "8.5.2"
  }
}
test/contract.mjs
import assert from 'node:assert/strict';
import postcss from 'postcss';

// Test 1: Synchronous visitor lifecycle and targeted Declaration property hooks
{
  const executionLog = [];

  const transformPlugin = (options = { prefix: 'app-' }) => ({
    postcssPlugin: 'transform-plugin',
    Once(root) {
      executionLog.push('Once');
    },
    Rule(rule) {
      executionLog.push(`Rule:${rule.selector}`);
      if (!rule.selector.startsWith(`.${options.prefix}`)) {
        rule.selector = `.${options.prefix}${rule.selector.replace(/^\./, '')}`;
      }
    },
    Declaration: {
      color(decl) {
        executionLog.push(`Decl:color:${decl.value}`);
        decl.value = 'rgb(0, 0, 255)';
      },
      'font-size'(decl) {
        executionLog.push(`Decl:font-size:${decl.value}`);
        decl.value = '1rem';
      }
    },
    OnceExit(root) {
      executionLog.push('OnceExit');
    }
  });
  transformPlugin.postcss = true;

  const inputCss = '.card { color: red; font-size: 16px; margin: 8px; }';
  const lazyResult = postcss([transformPlugin()]).process(inputCss, { from: undefined });

  // Accessing .css synchronously triggers the processing pipeline
  const outputCss = lazyResult.css;

  assert.strictEqual(
    outputCss,
    '.app-card { color: rgb(0, 0, 255); font-size: 1rem; margin: 8px; }'
  );

  assert.deepStrictEqual(executionLog, [
    'Once',
    'Rule:.card',
    'Decl:color:red',
    'Decl:font-size:16px',
    'Rule:.app-card',
    'Decl:color:rgb(0, 0, 255)',
    'Decl:font-size:1rem',
    'OnceExit'
  ]);
  assert.strictEqual(lazyResult.warnings().length, 0);
}

// Test 2: AST node manipulation (decl.clone, decl.before, and result.warn)
{
  const vendorPrefixPlugin = () => ({
    postcssPlugin: 'vendor-prefix-plugin',
    Declaration: {
      opacity(decl, { result }) {
        result.warn('opacity property prefixed for compatibility', { node: decl });
        const webkitDecl = decl.clone({ prop: '-webkit-opacity', value: decl.value });
        decl.before(webkitDecl);
      }
    },
    Once(root) {
      root.walkDecls('display', (decl) => {
        if (decl.value === 'flex') {
          decl.before(decl.clone({ value: '-webkit-flex' }));
        }
      });
    }
  });
  vendorPrefixPlugin.postcss = true;

  const inputCss = 'div { display: flex; opacity: 0.5; }';
  const result = postcss([vendorPrefixPlugin()]).process(inputCss, { from: undefined });

  assert.strictEqual(
    result.css,
    'div { display: -webkit-flex; display: flex; -webkit-opacity: 0.5; opacity: 0.5; }'
  );

  const warnings = result.warnings();
  assert.strictEqual(warnings.length, 1);
  assert.strictEqual(warnings[0].text, 'opacity property prefixed for compatibility');
  assert.strictEqual(warnings[0].node.prop, 'opacity');
}

// Test 3: Asynchronous plugin execution guard and Promise resolution
{
  const asyncPlugin = () => ({
    postcssPlugin: 'async-plugin',
    async Once(root) {
      await new Promise((resolve) => setTimeout(resolve, 10));
      const footerRule = postcss.rule({ selector: 'footer' });
      footerRule.append(postcss.decl({ prop: 'content', value: '"async-banner"' }));
      root.append(footerRule);
    }
  });
  asyncPlugin.postcss = true;

  // Synchronous access on a LazyResult with async plugins throws descriptive error
  const asyncLazy = postcss([asyncPlugin()]).process('header { color: green; }', { from: undefined });
  assert.throws(
    () => {
      const _ = asyncLazy.css;
    },
    {
      name: 'Error',
      message: /Use process\(css\)\.then\(cb\) to work with async plugins/
    }
  );

  // Awaiting the LazyResult executes asynchronously and resolves with transformed CSS
  const asyncLazyForAwait = postcss([asyncPlugin()]).process('header { color: green; }', { from: undefined });
  const asyncResolved = await asyncLazyForAwait;
  assert.strictEqual(
    asyncResolved.css,
    'header { color: green; }\nfooter { content: "async-banner"; }'
  );
}

console.log('PostCSS contract passed');

Исходный сидер

csx-seed