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 property…

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