@ecopages/postcss-processor 0.2.0-alpha.4 → 0.2.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,21 +6,17 @@ All notable changes to `@ecopages/postcss-processor` are documented here.
6
6
 
7
7
  ## [UNRELEASED] — TBD
8
8
 
9
- ### Bug Fixes
10
-
11
- - Rebuilt tracked stylesheets from fresh PostCSS plugin instances on non-CSS source changes so Tailwind utility generation updates without stale caches or forced reloads.
12
-
13
9
  ### Features
14
10
 
15
- - **Runtime CSS loaders** — Added `css-loader-plugin.ts`, `css-loader.bun.ts`, and `css-runtime-contract.ts` to support runtime CSS loading. CSS is now cached at runtime to avoid redundant processing during HMR (`cbaafea4`, `e7653c9b`).
16
- - **`PostcssProcessor` class** New `postcss-processor.ts` exposes a programmatic API for the processor separate from the plugin DSL.
11
+ - Added runtime CSS loader support and a `PostcssProcessor` class so PostCSS processing can be reused outside the plugin DSL.
12
+ - Added esbuild build adapter registration and dependency graph integration to the PostCSS processor plugin.
17
13
 
18
- ### Refactoring
14
+ ### Bug Fixes
19
15
 
20
- - `plugin.ts` significantly overhauled to integrate with the new build adapter and support build dependency graph registration (`e7653c9b`).
21
- - Test suite updated for esbuild adapter and Node runtime compatibility (`31a44458`).
22
- - Removed unused `@types/postcss` dev dependency.
16
+ - Rebuilt tracked stylesheets from fresh PostCSS plugin instances on non-CSS source changes so Tailwind-style utility generation picks up template edits without stale caches.
17
+ - Applied `transformInput` during direct stylesheet asset processing so Tailwind v4 page CSS keeps injected `@reference` directives and preserves nested BEM selectors in preview/build output.
18
+ - Disabled Tailwind v4 PostCSS optimization in the official plugin preset so preview/build no longer rewrites nested BEM selectors into invalid output.
23
19
 
24
20
  ### Tests
25
21
 
26
- - Updated `plugin.test.ts`, `postcss-processor.test.ts`, and `presets.test.ts` for new plugin contract.
22
+ - Added processor and preset coverage for the runtime CSS loader and build adapter flow.
package/README.md CHANGED
@@ -1,56 +1,32 @@
1
- # PostCSS Processor
1
+ # @ecopages/postcss-processor
2
2
 
3
- This module provides a PostCSS processor plugin for Ecopages and utility functions for processing CSS files and strings using PostCSS. It includes built-in presets for Tailwind CSS (v3 and v4).
3
+ PostCSS processing pipeline for Ecopages. It provides a processor plugin that seamlessly integrates PostCSS into the Ecopages build system, and includes built-in presets for Tailwind CSS (v3 and v4).
4
4
 
5
5
  ## Features
6
6
 
7
- - **Ecopages Processor Plugin**: Seamless integration with Ecopages build system.
8
- - **Tailwind Presets**: Ready-to-use configurations for Tailwind CSS v3 and v4.
9
- - **Automatic Configuration**: Detects `postcss.config.{js,ts,etc}` automatically.
10
- - **Standalone Usage**: Process CSS files or strings directly.
11
- - **Bun Loader**: Automatically registers a Bun loader for importing CSS in TS/JS files.
7
+ - **Ecopages Processor Plugin**: Hook right into the build pipeline.
8
+ - **Tailwind Presets**: Pre-configured pipelines for Tailwind CSS v3 and v4.
9
+ - **Automatic Configuration**: Detects existing `postcss.config.{js,ts,etc}`.
10
+ - **Bun Loader**: Registers a Bun loader for importing CSS in TS/JS files.
12
11
 
13
- ## Install
12
+ ## Installation
14
13
 
15
14
  ```bash
16
15
  bunx jsr add @ecopages/postcss-processor
17
16
  ```
18
17
 
19
- ## Usage with Ecopages
18
+ ## Usage
20
19
 
21
- Integrate the processor into your `eco.config.ts` using one of the available presets.
22
-
23
- ### Tailwind v3 Preset
24
-
25
- Includes `tailwindcss`, `autoprefixer`, `postcss-import`, `cssnano`.
26
-
27
- ```bash
28
- bun add -D tailwindcss@3.4.19
29
- ```
30
-
31
- ```typescript
32
- // eco.config.ts
33
- import { ConfigBuilder } from '@ecopages/core';
34
- import { postcssProcessorPlugin } from '@ecopages/postcss-processor';
35
- import { tailwindV3Preset } from '@ecopages/postcss-processor/presets/tailwind-v3';
36
-
37
- const config = await new ConfigBuilder().setProcessors([postcssProcessorPlugin(tailwindV3Preset())]).build();
38
-
39
- export default config;
40
- ```
20
+ Integrate the processor into your `eco.config.ts` using a preset.
41
21
 
42
22
  ### Tailwind v4 Preset (Recommended)
43
23
 
44
- Includes `@tailwindcss/postcss`, `autoprefixer`, `postcss-nested`, `cssnano`, and handles `@reference` injection for `@apply`.
45
-
46
- ```bash
47
- bun add -D @tailwindcss/postcss tailwindcss
48
- ```
24
+ Requires `@tailwindcss/postcss` and `tailwindcss` to be installed.
49
25
 
50
26
  ```typescript
51
27
  // eco.config.ts
52
28
  import path from 'node:path';
53
- import { ConfigBuilder } from '@ecopages/core';
29
+ import { ConfigBuilder } from '@ecopages/core/config-builder';
54
30
  import { postcssProcessorPlugin } from '@ecopages/postcss-processor';
55
31
  import { tailwindV4Preset } from '@ecopages/postcss-processor/presets/tailwind-v4';
56
32
 
@@ -58,7 +34,7 @@ const config = await new ConfigBuilder()
58
34
  .setProcessors([
59
35
  postcssProcessorPlugin(
60
36
  tailwindV4Preset({
61
- referencePath: path.resolve(import.meta.dir, 'src/styles/app.css'),
37
+ referencePath: path.resolve(import.meta.dirname, 'src/styles/app.css'),
62
38
  }),
63
39
  ),
64
40
  ])
@@ -67,53 +43,41 @@ const config = await new ConfigBuilder()
67
43
  export default config;
68
44
  ```
69
45
 
70
- ### Browser Support
71
-
72
- By default, the presets target a broad range of modern browsers (`>0.3%, not ie 11, not dead, not op_mini all`).
73
-
74
- To override this, add a `browserslist` configuration to your `package.json` or create a `.browserslistrc` file in your project root. The processor will automatically detect and use your custom configuration.
75
-
76
- ### Custom Configuration
77
-
78
- You can also use a standard `postcss.config.js` file or pass plugins manually.
79
-
80
- **Using `postcss.config.js`:**
81
- Create the file in your root, and simply add `postcssProcessorPlugin()` to your config without arguments.
46
+ ### Tailwind v3 Preset
82
47
 
83
- **Manual Configuration:**
48
+ Requires `tailwindcss@3`, `autoprefixer`, `postcss-import`, and `cssnano` to be installed.
84
49
 
85
50
  ```typescript
51
+ // eco.config.ts
52
+ import { ConfigBuilder } from '@ecopages/core/config-builder';
86
53
  import { postcssProcessorPlugin } from '@ecopages/postcss-processor';
87
- import myPlugin from 'postcss-my-plugin';
54
+ import { tailwindV3Preset } from '@ecopages/postcss-processor/presets/tailwind-v3';
55
+
56
+ const config = await new ConfigBuilder().setProcessors([postcssProcessorPlugin(tailwindV3Preset())]).build();
88
57
 
89
- postcssProcessorPlugin({
90
- plugins: {
91
- 'my-plugin': myPlugin(),
92
- },
93
- });
58
+ export default config;
94
59
  ```
95
60
 
96
- **Advanced Configuration:**
61
+ ## Custom Configuration
97
62
 
98
- For advanced use cases, use transformation hooks to modify CSS before or after processing:
63
+ To use your own `postcss.config.js`, simply call `postcssProcessorPlugin()` without arguments.
64
+
65
+ You can also pass raw plugins or transformation hooks manually:
99
66
 
100
67
  ```typescript
101
- import { ConfigBuilder } from '@ecopages/core';
68
+ import { ConfigBuilder } from '@ecopages/core/config-builder';
102
69
  import { postcssProcessorPlugin } from '@ecopages/postcss-processor';
70
+ import myPlugin from 'postcss-my-plugin';
103
71
 
104
72
  const config = await new ConfigBuilder()
105
73
  .setProcessors([
106
74
  postcssProcessorPlugin({
107
- // Define a filter for files to process (defaults to /\.css$/)
108
75
  filter: /\.css$/,
109
- // Provide a function to transform input before processing
110
- transformInput: async (css) => `/* My Custom Header */\n${css}`,
111
- // Provide a function to transform output after processing
112
- transformOutput: async (css) => css.replace('blue', 'red'),
113
- // Explicitly provide plugins (overrides defaults)
114
76
  plugins: {
115
- /* custom plugins */
77
+ 'my-plugin': myPlugin(),
116
78
  },
79
+ transformInput: async (css) => `/* Header */\n${css}`,
80
+ transformOutput: async (css) => css.replace('blue', 'red'),
117
81
  }),
118
82
  ])
119
83
  .build();
@@ -121,9 +85,9 @@ const config = await new ConfigBuilder()
121
85
  export default config;
122
86
  ```
123
87
 
124
- ## Standalone Usage
88
+ ## Standalone Processing
125
89
 
126
- You can use the underlying processor functions directly:
90
+ You can bypass Ecopages entirely and use the processor utilities directly:
127
91
 
128
92
  ```typescript
129
93
  import { PostCssProcessor } from '@ecopages/postcss-processor';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/postcss-processor",
3
- "version": "0.2.0-alpha.4",
3
+ "version": "0.2.0-alpha.7",
4
4
  "description": "Postcss processor, transform string or postcss file to css",
5
5
  "keywords": [
6
6
  "postcss",
@@ -17,8 +17,8 @@
17
17
  "directory": "packages/processors/postcss-processor"
18
18
  },
19
19
  "dependencies": {
20
- "@ecopages/core": "0.2.0-alpha.4",
21
- "@ecopages/file-system": "0.2.0-alpha.4",
20
+ "@ecopages/core": "0.2.0-alpha.7",
21
+ "@ecopages/file-system": "0.2.0-alpha.7",
22
22
  "@ecopages/logger": "latest",
23
23
  "autoprefixer": "^10.4.0",
24
24
  "browserslist": "^4.28.1",
package/src/plugin.d.ts CHANGED
@@ -61,6 +61,7 @@ export interface PostCssProcessorPluginConfig {
61
61
  */
62
62
  export declare class PostCssProcessorPlugin extends Processor<PostCssProcessorPluginConfig> {
63
63
  static DEFAULT_OPTIONS: Required<Pick<PostCssProcessorPluginConfig, 'filter'>>;
64
+ private buildContributionsPrepared;
64
65
  private postcssPlugins;
65
66
  private pluginFactories?;
66
67
  private readonly runtimeCssCache;
@@ -88,7 +89,15 @@ export declare class PostCssProcessorPlugin extends Processor<PostCssProcessorPl
88
89
  get buildPlugins(): EcoBuildPlugin[];
89
90
  get plugins(): EcoBuildPlugin[];
90
91
  /**
91
- * Setup the PostCSS processor.
92
+ * Resolves the configured PostCSS plugin list before config build seals the
93
+ * app manifest.
94
+ *
95
+ * @remarks
96
+ * Runtime setup reuses this prepared list and only performs cache prewarming.
97
+ */
98
+ prepareBuildContributions(): Promise<void>;
99
+ /**
100
+ * Prepares build contributions if not already done and prewarms the runtime CSS cache.
92
101
  */
93
102
  setup(): Promise<void>;
94
103
  /**
package/src/plugin.js CHANGED
@@ -11,6 +11,7 @@ class PostCssProcessorPlugin extends Processor {
11
11
  static DEFAULT_OPTIONS = {
12
12
  filter: /\.css$/
13
13
  };
14
+ buildContributionsPrepared = false;
14
15
  postcssPlugins = [];
15
16
  pluginFactories;
16
17
  runtimeCssCache = /* @__PURE__ */ new Map();
@@ -238,10 +239,24 @@ class PostCssProcessorPlugin extends Processor {
238
239
  ];
239
240
  }
240
241
  /**
241
- * Setup the PostCSS processor.
242
+ * Resolves the configured PostCSS plugin list before config build seals the
243
+ * app manifest.
244
+ *
245
+ * @remarks
246
+ * Runtime setup reuses this prepared list and only performs cache prewarming.
242
247
  */
243
- async setup() {
248
+ async prepareBuildContributions() {
249
+ if (this.buildContributionsPrepared) {
250
+ return;
251
+ }
244
252
  await this.collectPostcssPlugins();
253
+ this.buildContributionsPrepared = true;
254
+ }
255
+ /**
256
+ * Prepares build contributions if not already done and prewarms the runtime CSS cache.
257
+ */
258
+ async setup() {
259
+ await this.prepareBuildContributions();
245
260
  await this.prewarmRuntimeCssCache();
246
261
  }
247
262
  /**
@@ -296,14 +311,15 @@ class PostCssProcessorPlugin extends Processor {
296
311
  } else if (loadedPlugins) {
297
312
  this.pluginFactories = void 0;
298
313
  this.postcssPlugins = loadedPlugins;
299
- } else if (this.options?.pluginFactories) {
300
- logger.debug("Using PostCSS plugin factories provided in processor options.");
301
- this.pluginFactories = this.options.pluginFactories;
302
- this.postcssPlugins = this.materializePluginFactories(this.options.pluginFactories);
303
- } else if (this.options?.plugins) {
304
- logger.debug("Using PostCSS plugins provided in processor options.");
305
- this.pluginFactories = void 0;
306
- this.postcssPlugins = Object.values(this.options.plugins);
314
+ } else if (this.options?.pluginFactories || this.options?.plugins) {
315
+ this.pluginFactories = this.options?.pluginFactories;
316
+ if (this.options?.plugins) {
317
+ logger.debug("Using PostCSS plugins provided in processor options.");
318
+ this.postcssPlugins = Object.values(this.options.plugins);
319
+ } else if (this.options?.pluginFactories) {
320
+ logger.debug("Using PostCSS plugin factories provided in processor options.");
321
+ this.postcssPlugins = this.materializePluginFactories(this.options.pluginFactories);
322
+ }
307
323
  } else {
308
324
  logger.warn(
309
325
  "No PostCSS plugins configured. Use a preset like tailwindV3Preset() or tailwindV4Preset(), provide plugins via options, or create a postcss.config file."
@@ -323,14 +339,19 @@ class PostCssProcessorPlugin extends Processor {
323
339
  * @returns Processed CSS
324
340
  */
325
341
  async process(fileAsString, filePath) {
326
- return await PostCssProcessor.processStringOrBuffer(fileAsString, {
342
+ const input = this.options?.transformInput && filePath ? await this.options.transformInput(fileAsString, filePath) : fileAsString;
343
+ return await PostCssProcessor.processStringOrBuffer(input, {
327
344
  filePath,
328
345
  plugins: this.postcssPlugins,
329
346
  transformOutput: this.options?.transformOutput
330
347
  });
331
348
  }
332
349
  processSync(fileAsString, filePath) {
333
- return PostCssProcessor.processStringOrBufferSync(fileAsString, {
350
+ const input = this.options?.transformInput && filePath ? this.options.transformInput(fileAsString, filePath) : fileAsString;
351
+ if (input instanceof Promise) {
352
+ throw new Error("transformInput must be synchronous when used with processSync");
353
+ }
354
+ return PostCssProcessor.processStringOrBufferSync(input, {
334
355
  filePath,
335
356
  plugins: this.postcssPlugins,
336
357
  transformOutput: this.options?.transformOutput
package/src/plugin.ts CHANGED
@@ -80,6 +80,7 @@ export class PostCssProcessorPlugin extends Processor<PostCssProcessorPluginConf
80
80
  filter: /\.css$/,
81
81
  };
82
82
 
83
+ private buildContributionsPrepared = false;
83
84
  private postcssPlugins: postcss.AcceptedPlugin[] = [];
84
85
  private pluginFactories?: PluginFactoryRecord;
85
86
  private readonly runtimeCssCache = new Map<string, string>();
@@ -360,10 +361,26 @@ export class PostCssProcessorPlugin extends Processor<PostCssProcessorPluginConf
360
361
  }
361
362
 
362
363
  /**
363
- * Setup the PostCSS processor.
364
+ * Resolves the configured PostCSS plugin list before config build seals the
365
+ * app manifest.
366
+ *
367
+ * @remarks
368
+ * Runtime setup reuses this prepared list and only performs cache prewarming.
364
369
  */
365
- async setup(): Promise<void> {
370
+ override async prepareBuildContributions(): Promise<void> {
371
+ if (this.buildContributionsPrepared) {
372
+ return;
373
+ }
374
+
366
375
  await this.collectPostcssPlugins();
376
+ this.buildContributionsPrepared = true;
377
+ }
378
+
379
+ /**
380
+ * Prepares build contributions if not already done and prewarms the runtime CSS cache.
381
+ */
382
+ async setup(): Promise<void> {
383
+ await this.prepareBuildContributions();
367
384
  await this.prewarmRuntimeCssCache();
368
385
  }
369
386
 
@@ -429,14 +446,16 @@ export class PostCssProcessorPlugin extends Processor<PostCssProcessorPluginConf
429
446
  } else if (loadedPlugins) {
430
447
  this.pluginFactories = undefined;
431
448
  this.postcssPlugins = loadedPlugins;
432
- } else if (this.options?.pluginFactories) {
433
- logger.debug('Using PostCSS plugin factories provided in processor options.');
434
- this.pluginFactories = this.options.pluginFactories;
435
- this.postcssPlugins = this.materializePluginFactories(this.options.pluginFactories);
436
- } else if (this.options?.plugins) {
437
- logger.debug('Using PostCSS plugins provided in processor options.');
438
- this.pluginFactories = undefined;
439
- this.postcssPlugins = Object.values(this.options.plugins);
449
+ } else if (this.options?.pluginFactories || this.options?.plugins) {
450
+ this.pluginFactories = this.options?.pluginFactories;
451
+
452
+ if (this.options?.plugins) {
453
+ logger.debug('Using PostCSS plugins provided in processor options.');
454
+ this.postcssPlugins = Object.values(this.options.plugins);
455
+ } else if (this.options?.pluginFactories) {
456
+ logger.debug('Using PostCSS plugin factories provided in processor options.');
457
+ this.postcssPlugins = this.materializePluginFactories(this.options.pluginFactories);
458
+ }
440
459
  } else {
441
460
  logger.warn(
442
461
  'No PostCSS plugins configured. Use a preset like tailwindV3Preset() or tailwindV4Preset(), ' +
@@ -459,7 +478,12 @@ export class PostCssProcessorPlugin extends Processor<PostCssProcessorPluginConf
459
478
  * @returns Processed CSS
460
479
  */
461
480
  async process(fileAsString: string, filePath?: string): Promise<string> {
462
- return await PostCssProcessor.processStringOrBuffer(fileAsString, {
481
+ const input =
482
+ this.options?.transformInput && filePath
483
+ ? await this.options.transformInput(fileAsString, filePath)
484
+ : fileAsString;
485
+
486
+ return await PostCssProcessor.processStringOrBuffer(input, {
463
487
  filePath,
464
488
  plugins: this.postcssPlugins,
465
489
  transformOutput: this.options?.transformOutput,
@@ -467,7 +491,16 @@ export class PostCssProcessorPlugin extends Processor<PostCssProcessorPluginConf
467
491
  }
468
492
 
469
493
  processSync(fileAsString: string, filePath?: string): string {
470
- return PostCssProcessor.processStringOrBufferSync(fileAsString, {
494
+ const input =
495
+ this.options?.transformInput && filePath
496
+ ? this.options.transformInput(fileAsString, filePath)
497
+ : fileAsString;
498
+
499
+ if (input instanceof Promise) {
500
+ throw new Error('transformInput must be synchronous when used with processSync');
501
+ }
502
+
503
+ return PostCssProcessor.processStringOrBufferSync(input, {
471
504
  filePath,
472
505
  plugins: this.postcssPlugins,
473
506
  transformOutput: this.options?.transformOutput,
@@ -14,7 +14,7 @@ function tailwindV4Preset(options) {
14
14
  const pluginFactories = {
15
15
  "postcss-import": () => postcssImport(),
16
16
  "postcss-nested": () => postcssNested(),
17
- "@tailwindcss/postcss": () => tailwindcss(),
17
+ "@tailwindcss/postcss": () => tailwindcss({ optimize: false }),
18
18
  autoprefixer: () => autoprefixer(autoprefixerOptions),
19
19
  cssnano: () => cssnano()
20
20
  };
@@ -68,7 +68,7 @@ export function tailwindV4Preset(options: TailwindV4PresetOptions): PostCssProce
68
68
  const pluginFactories: PluginFactoryRecord = {
69
69
  'postcss-import': () => postcssImport(),
70
70
  'postcss-nested': () => postcssNested(),
71
- '@tailwindcss/postcss': () => tailwindcss(),
71
+ '@tailwindcss/postcss': () => tailwindcss({ optimize: false }),
72
72
  autoprefixer: () => autoprefixer(autoprefixerOptions),
73
73
  cssnano: () => cssnano(),
74
74
  };