@ecopages/postcss-processor 0.2.0-alpha.10 → 0.2.0-alpha.11

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
@@ -8,14 +8,12 @@ All notable changes to `@ecopages/postcss-processor` are documented here.
8
8
 
9
9
  ### Features
10
10
 
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.
11
+ - Added reusable runtime CSS loading, a public `PostcssProcessor` class, and build-adapter registration for the plugin.
13
12
 
14
13
  ### Bug Fixes
15
14
 
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.
15
+ - Fixed runtime PostCSS config loading and stylesheet rebuilds for Tailwind-driven template changes.
16
+ - Fixed direct stylesheet processing and preset output so Tailwind v4 preserves injected references and nested BEM selectors.
19
17
 
20
18
  ### Tests
21
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/postcss-processor",
3
- "version": "0.2.0-alpha.10",
3
+ "version": "0.2.0-alpha.11",
4
4
  "description": "Postcss processor, transform string or postcss file to css",
5
5
  "keywords": [
6
6
  "postcss",
@@ -17,7 +17,7 @@
17
17
  "directory": "packages/processors/postcss-processor"
18
18
  },
19
19
  "dependencies": {
20
- "@ecopages/file-system": "0.2.0-alpha.10",
20
+ "@ecopages/file-system": "0.2.0-alpha.11",
21
21
  "@ecopages/logger": "latest",
22
22
  "autoprefixer": "^10.4.0",
23
23
  "browserslist": "^4.28.1",
@@ -27,7 +27,7 @@
27
27
  "postcss-nested": "^7.0.2"
28
28
  },
29
29
  "peerDependencies": {
30
- "@ecopages/core": "0.2.0-alpha.10",
30
+ "@ecopages/core": "0.2.0-alpha.11",
31
31
  "@tailwindcss/postcss": ">=4",
32
32
  "tailwindcss": ">=3"
33
33
  },
package/src/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export * from './plugin';
2
- export * from './postcss-processor';
1
+ export * from './plugin.js';
2
+ export * from './postcss-processor.js';
package/src/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export * from "./plugin";
2
- export * from "./postcss-processor";
1
+ export * from "./plugin.js";
2
+ export * from "./postcss-processor.js";
package/src/plugin.d.ts CHANGED
@@ -67,11 +67,38 @@ export declare class PostCssProcessorPlugin extends Processor<PostCssProcessorPl
67
67
  private readonly runtimeCssCache;
68
68
  private readonly trackedCssFiles;
69
69
  private watchQueue;
70
+ /**
71
+ * Maps an imported CSS file path → set of tracked CSS entry files that import it.
72
+ * Used to resolve which parent entry files need re-processing when a dependency changes.
73
+ */
74
+ private readonly cssDependencyMap;
70
75
  private getCssFilter;
71
76
  private resolveProcessedCssPath;
72
77
  private readProcessedCssFromDist;
73
78
  private persistProcessedCss;
74
79
  private prewarmRuntimeCssCache;
80
+ /**
81
+ * Regex to match CSS @import statements and extract the path.
82
+ * Handles: @import './foo.css'; @import "./foo.css"; @import url('./foo.css');
83
+ */
84
+ private static readonly CSS_IMPORT_REGEX;
85
+ /**
86
+ * Builds the CSS dependency map by scanning tracked CSS files for @import directives.
87
+ * Maps each imported file to the set of tracked entry files that import it (directly or transitively).
88
+ */
89
+ private buildCssDependencyMap;
90
+ /**
91
+ * Extracts resolved absolute paths of CSS files imported via @import in the given CSS content.
92
+ * Recursively follows imports to capture transitive dependencies.
93
+ * It skips bare module imports like @import 'tailwindcss'.
94
+ * It recursively follows imports to capture transitive dependencies.
95
+ */
96
+ private extractCssImports;
97
+ /**
98
+ * Resolves a changed CSS file to its parent entry file(s) if it is an @import dependency.
99
+ * Returns an empty array if the file is not an import dependency (i.e., it's an entry file itself).
100
+ */
101
+ private resolveEntryFiles;
75
102
  private transformCssSync;
76
103
  private transformCssAsync;
77
104
  matchesFileFilter(filepath: string): boolean;
@@ -83,9 +110,15 @@ export declare class PostCssProcessorPlugin extends Processor<PostCssProcessorPl
83
110
  constructor(config?: Omit<ProcessorConfig<PostCssProcessorPluginConfig>, 'name' | 'description'>);
84
111
  /**
85
112
  * Handles CSS file changes during development.
86
- * Processes the file and broadcasts a css-update event for hot reloading.
113
+ * If the file is an @import dependency, re-processes the parent entry file(s) instead.
114
+ * Broadcasts a css-update event for hot reloading.
87
115
  */
88
116
  private handleCssChange;
117
+ /**
118
+ * Processes a CSS file and broadcasts a css-update event.
119
+ * Skips broadcast if the processed output hasn't changed.
120
+ */
121
+ private processAndBroadcast;
89
122
  get buildPlugins(): EcoBuildPlugin[];
90
123
  get plugins(): EcoBuildPlugin[];
91
124
  /**
package/src/plugin.js CHANGED
@@ -2,8 +2,8 @@ import path from "node:path";
2
2
  import { fileSystem } from "@ecopages/file-system";
3
3
  import { Processor } from "@ecopages/core/plugins/processor";
4
4
  import { Logger } from "@ecopages/logger";
5
- import { PostCssProcessor } from "./postcss-processor";
6
- import { createCssLoaderPlugin } from "./runtime/css-loader-plugin";
5
+ import { PostCssProcessor } from "./postcss-processor.js";
6
+ import { createCssLoaderPlugin } from "./runtime/css-loader-plugin.js";
7
7
  const logger = new Logger("[@ecopages/postcss-processor]", {
8
8
  debug: process.env.ECOPAGES_LOGGER_DEBUG === "true"
9
9
  });
@@ -17,6 +17,11 @@ class PostCssProcessorPlugin extends Processor {
17
17
  runtimeCssCache = /* @__PURE__ */ new Map();
18
18
  trackedCssFiles = /* @__PURE__ */ new Set();
19
19
  watchQueue = Promise.resolve();
20
+ /**
21
+ * Maps an imported CSS file path → set of tracked CSS entry files that import it.
22
+ * Used to resolve which parent entry files need re-processing when a dependency changes.
23
+ */
24
+ cssDependencyMap = /* @__PURE__ */ new Map();
20
25
  getCssFilter() {
21
26
  return this.options?.filter ?? PostCssProcessorPlugin.DEFAULT_OPTIONS.filter;
22
27
  }
@@ -67,6 +72,69 @@ class PostCssProcessorPlugin extends Processor {
67
72
  this.runtimeCssCache.set(filePath, processed);
68
73
  await this.persistProcessedCss(filePath, processed);
69
74
  }
75
+ this.buildCssDependencyMap();
76
+ }
77
+ /**
78
+ * Regex to match CSS @import statements and extract the path.
79
+ * Handles: @import './foo.css'; @import "./foo.css"; @import url('./foo.css');
80
+ */
81
+ static CSS_IMPORT_REGEX = /@import\s+(?:url\(\s*)?['"]([^'"]+\.css)['"](?:\s*\))?\s*;/gm;
82
+ /**
83
+ * Builds the CSS dependency map by scanning tracked CSS files for @import directives.
84
+ * Maps each imported file to the set of tracked entry files that import it (directly or transitively).
85
+ */
86
+ buildCssDependencyMap() {
87
+ this.cssDependencyMap.clear();
88
+ for (const entryFile of this.trackedCssFiles) {
89
+ if (!fileSystem.exists(entryFile)) continue;
90
+ const rawContents = fileSystem.readFileAsBuffer(entryFile).toString("utf-8");
91
+ const imports = this.extractCssImports(rawContents, entryFile);
92
+ for (const importedFile of imports) {
93
+ if (!this.cssDependencyMap.has(importedFile)) {
94
+ this.cssDependencyMap.set(importedFile, /* @__PURE__ */ new Set());
95
+ }
96
+ this.cssDependencyMap.get(importedFile).add(entryFile);
97
+ }
98
+ }
99
+ }
100
+ /**
101
+ * Extracts resolved absolute paths of CSS files imported via @import in the given CSS content.
102
+ * Recursively follows imports to capture transitive dependencies.
103
+ * It skips bare module imports like @import 'tailwindcss'.
104
+ * It recursively follows imports to capture transitive dependencies.
105
+ */
106
+ extractCssImports(cssContent, fromFile, visited = /* @__PURE__ */ new Set()) {
107
+ const dir = path.dirname(fromFile);
108
+ const imports = [];
109
+ let match;
110
+ const regex = new RegExp(PostCssProcessorPlugin.CSS_IMPORT_REGEX.source, "gm");
111
+ while ((match = regex.exec(cssContent)) !== null) {
112
+ const importPath = match[1];
113
+ if (!importPath.startsWith(".") && !importPath.startsWith("/")) {
114
+ continue;
115
+ }
116
+ const resolvedPath = path.resolve(dir, importPath);
117
+ if (visited.has(resolvedPath)) continue;
118
+ visited.add(resolvedPath);
119
+ imports.push(resolvedPath);
120
+ if (fileSystem.exists(resolvedPath)) {
121
+ const nestedContent = fileSystem.readFileAsBuffer(resolvedPath).toString("utf-8");
122
+ const nestedImports = this.extractCssImports(nestedContent, resolvedPath, visited);
123
+ imports.push(...nestedImports);
124
+ }
125
+ }
126
+ return imports;
127
+ }
128
+ /**
129
+ * Resolves a changed CSS file to its parent entry file(s) if it is an @import dependency.
130
+ * Returns an empty array if the file is not an import dependency (i.e., it's an entry file itself).
131
+ */
132
+ resolveEntryFiles(filePath) {
133
+ const entries = this.cssDependencyMap.get(filePath);
134
+ if (!entries || entries.size === 0) {
135
+ return [];
136
+ }
137
+ return Array.from(entries);
70
138
  }
71
139
  transformCssSync(input) {
72
140
  const cached = this.runtimeCssCache.get(input.filePath);
@@ -169,6 +237,8 @@ class PostCssProcessorPlugin extends Processor {
169
237
  onCreate: async ({ path: path2, bridge }) => {
170
238
  await this.enqueueWatchTask(async () => {
171
239
  if (this.matchesFileFilter(path2)) {
240
+ this.trackedCssFiles.add(path2);
241
+ this.buildCssDependencyMap();
172
242
  await this.handleCssChange(path2, bridge);
173
243
  return;
174
244
  }
@@ -180,6 +250,8 @@ class PostCssProcessorPlugin extends Processor {
180
250
  if (this.matchesFileFilter(path2)) {
181
251
  this.runtimeCssCache.delete(path2);
182
252
  this.trackedCssFiles.delete(path2);
253
+ this.cssDependencyMap.delete(path2);
254
+ this.buildCssDependencyMap();
183
255
  return;
184
256
  }
185
257
  await this.handleDependencyChange(bridge);
@@ -191,9 +263,28 @@ class PostCssProcessorPlugin extends Processor {
191
263
  }
192
264
  /**
193
265
  * Handles CSS file changes during development.
194
- * Processes the file and broadcasts a css-update event for hot reloading.
266
+ * If the file is an @import dependency, re-processes the parent entry file(s) instead.
267
+ * Broadcasts a css-update event for hot reloading.
195
268
  */
196
269
  async handleCssChange(filePath, bridge, refreshPlugins = true) {
270
+ if (!this.context) return;
271
+ if (!fileSystem.exists(filePath)) return;
272
+ const entryFiles = this.resolveEntryFiles(filePath);
273
+ if (entryFiles.length > 0) {
274
+ logger.debug(`CSS dependency changed: ${filePath}, re-processing ${entryFiles.length} parent(s)`);
275
+ for (const entryFile of entryFiles) {
276
+ this.runtimeCssCache.delete(entryFile);
277
+ await this.processAndBroadcast(entryFile, bridge, refreshPlugins);
278
+ }
279
+ return;
280
+ }
281
+ await this.processAndBroadcast(filePath, bridge, refreshPlugins);
282
+ }
283
+ /**
284
+ * Processes a CSS file and broadcasts a css-update event.
285
+ * Skips broadcast if the processed output hasn't changed.
286
+ */
287
+ async processAndBroadcast(filePath, bridge, refreshPlugins = true) {
197
288
  if (!this.context) return;
198
289
  if (!fileSystem.exists(filePath)) return;
199
290
  try {
@@ -281,7 +372,10 @@ class PostCssProcessorPlugin extends Processor {
281
372
  if (foundConfigPath) {
282
373
  try {
283
374
  logger.debug(`Loading PostCSS config from: ${foundConfigPath}`);
284
- const postcssConfigModule = await import(foundConfigPath);
375
+ const postcssConfigModule = await import(
376
+ /* @vite-ignore */
377
+ foundConfigPath
378
+ );
285
379
  const postcssConfig = postcssConfigModule.default || postcssConfigModule;
286
380
  if (postcssConfig && typeof postcssConfig.pluginFactories === "object" && postcssConfig.pluginFactories !== null) {
287
381
  loadedPluginFactories = postcssConfig.pluginFactories;
@@ -2,5 +2,5 @@
2
2
  * PostCSS Processor Presets
3
3
  * @module @ecopages/postcss-processor/presets
4
4
  */
5
- export { tailwindV3Preset } from './tailwind-v3';
6
- export { tailwindV4Preset, type TailwindV4PresetOptions } from './tailwind-v4';
5
+ export { tailwindV3Preset } from './tailwind-v3.js';
6
+ export { tailwindV4Preset, type TailwindV4PresetOptions } from './tailwind-v4.js';
@@ -1,5 +1,5 @@
1
- import { tailwindV3Preset } from "./tailwind-v3";
2
- import { tailwindV4Preset } from "./tailwind-v4";
1
+ import { tailwindV3Preset } from "./tailwind-v3.js";
2
+ import { tailwindV4Preset } from "./tailwind-v4.js";
3
3
  export {
4
4
  tailwindV3Preset,
5
5
  tailwindV4Preset
@@ -1,5 +1,5 @@
1
1
  import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
2
- import type { CssTransform } from './css-runtime-contract';
2
+ import type { CssTransform } from './css-runtime-contract.js';
3
3
  type CssLoaderOptions = {
4
4
  name: string;
5
5
  filter: RegExp;
@@ -1,4 +1,4 @@
1
- import { getFileAsBuffer } from "../postcss-processor";
1
+ import { getFileAsBuffer } from "../postcss-processor.js";
2
2
  const createCssLoaderPlugin = ({ name, filter, transform }) => ({
3
3
  name,
4
4
  setup(build) {
@@ -1,5 +1,5 @@
1
1
  import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
2
- import type { CssTransform } from './css-runtime-contract';
2
+ import type { CssTransform } from './css-runtime-contract.js';
3
3
  type BunCssLoaderOptions = {
4
4
  name: string;
5
5
  filter: RegExp;
@@ -1,4 +1,4 @@
1
- import { getFileAsBuffer } from "../postcss-processor";
1
+ import { getFileAsBuffer } from "../postcss-processor.js";
2
2
  const createBunCssLoaderPlugin = ({ name, filter, transform }) => ({
3
3
  name,
4
4
  setup(build) {