@ecopages/postcss-processor 0.2.0-alpha.8 → 0.2.1

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
@@ -4,18 +4,16 @@ All notable changes to `@ecopages/postcss-processor` are documented here.
4
4
 
5
5
  > **Note:** Changelog tracking begins at version `0.2.0`. Changes prior to this release are not recorded here but are available in the git history.
6
6
 
7
- ## [UNRELEASED] — TBD
7
+ ## [0.2.1] — 2026-04-16
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.8",
3
+ "version": "0.2.1",
4
4
  "description": "Postcss processor, transform string or postcss file to css",
5
5
  "keywords": [
6
6
  "postcss",
@@ -17,17 +17,17 @@
17
17
  "directory": "packages/processors/postcss-processor"
18
18
  },
19
19
  "dependencies": {
20
- "@ecopages/core": "0.2.0-alpha.8",
21
- "@ecopages/file-system": "0.2.0-alpha.8",
22
- "@ecopages/logger": "latest",
20
+ "@ecopages/file-system": "0.2.1",
21
+ "@ecopages/logger": "^0.2.3",
23
22
  "autoprefixer": "^10.4.0",
24
23
  "browserslist": "^4.28.1",
25
- "cssnano": "^6.0.0",
24
+ "cssnano": "^7.1.4",
26
25
  "postcss": "^8.4.32",
27
- "postcss-import": "^15.0.0",
26
+ "postcss-import": "^16.1.1",
28
27
  "postcss-nested": "^7.0.2"
29
28
  },
30
29
  "peerDependencies": {
30
+ "@ecopages/core": "0.2.1",
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) {
package/src/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export * from './plugin';
2
- export * from './postcss-processor';
package/src/plugin.ts DELETED
@@ -1,522 +0,0 @@
1
- /**
2
- * PostCssProcessorPlugin
3
- * @module @ecopages/postcss-processor
4
- */
5
-
6
- import path from 'node:path';
7
- import type { IClientBridge } from '@ecopages/core';
8
- import { fileSystem } from '@ecopages/file-system';
9
- import { Processor, type ProcessorConfig } from '@ecopages/core/plugins/processor';
10
- import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
11
- import { Logger } from '@ecopages/logger';
12
- import type postcss from 'postcss';
13
- import { PostCssProcessor } from './postcss-processor';
14
- import { createCssLoaderPlugin } from './runtime/css-loader-plugin';
15
- import type { CssTransformInput } from './runtime/css-runtime-contract';
16
-
17
- const logger = new Logger('[@ecopages/postcss-processor]', {
18
- debug: process.env.ECOPAGES_LOGGER_DEBUG === 'true',
19
- });
20
-
21
- /**
22
- * Record of PostCSS plugins keyed by name
23
- */
24
- export type PluginsRecord = Record<string, postcss.AcceptedPlugin>;
25
-
26
- /**
27
- * Lazily creates PostCSS plugins.
28
- *
29
- * This is primarily used in development when a non-CSS file change forces the
30
- * processor to rebuild tracked stylesheets. Some plugins, including Tailwind,
31
- * keep internal caches in long-lived plugin instances, so recreating them is
32
- * required to pick up newly discovered classes.
33
- */
34
- export type PluginFactoryRecord = Record<string, () => postcss.AcceptedPlugin>;
35
-
36
- /**
37
- * Configuration for the PostCSS processor
38
- */
39
- export interface PostCssProcessorPluginConfig {
40
- /**
41
- * Regex filter to match files to process
42
- */
43
- filter?: RegExp;
44
- /**
45
- * Function to transform the contents of the file.
46
- * It can be handy to add a custom header or footer to the file.
47
- * Useful for injecting Tailwind v4 `@reference` directives.
48
- * @param contents The contents of the file
49
- * @param filePath The absolute path to the CSS file being processed
50
- * @returns The transformed contents
51
- */
52
- transformInput?: (contents: string | Buffer, filePath: string) => string | Promise<string>;
53
- /**
54
- * Function to transform the output CSS after PostCSS processing.
55
- * It can be handy to add a custom header or footer to the processed CSS.
56
- * @param css The processed CSS
57
- * @returns The transformed CSS
58
- */
59
- transformOutput?: (css: string) => Promise<string> | string;
60
- /**
61
- * Custom PostCSS plugins to use instead of the default ones
62
- * @default undefined (uses default plugins)
63
- */
64
- plugins?: PluginsRecord;
65
- /**
66
- * Factory functions for recreating stateful PostCSS plugins.
67
- *
68
- * When provided, Ecopages uses these factories to build a fresh plugin list
69
- * for dependency-driven stylesheet rebuilds during development.
70
- */
71
- pluginFactories?: PluginFactoryRecord;
72
- }
73
-
74
- /**
75
- * PostCssProcessorPlugin
76
- * A Processor for transforming CSS files.
77
- */
78
- export class PostCssProcessorPlugin extends Processor<PostCssProcessorPluginConfig> {
79
- static DEFAULT_OPTIONS: Required<Pick<PostCssProcessorPluginConfig, 'filter'>> = {
80
- filter: /\.css$/,
81
- };
82
-
83
- private buildContributionsPrepared = false;
84
- private postcssPlugins: postcss.AcceptedPlugin[] = [];
85
- private pluginFactories?: PluginFactoryRecord;
86
- private readonly runtimeCssCache = new Map<string, string>();
87
- private readonly trackedCssFiles = new Set<string>();
88
- private watchQueue: Promise<void> = Promise.resolve();
89
-
90
- private getCssFilter(): RegExp {
91
- return this.options?.filter ?? PostCssProcessorPlugin.DEFAULT_OPTIONS.filter;
92
- }
93
-
94
- private resolveProcessedCssPath(filePath: string): string | null {
95
- if (!this.context) {
96
- return null;
97
- }
98
-
99
- const relativePath = path.relative(this.context.srcDir, filePath);
100
- if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
101
- return null;
102
- }
103
-
104
- return path.join(this.context.distDir, 'assets', relativePath);
105
- }
106
-
107
- private readProcessedCssFromDist(filePath: string): string | null {
108
- const outputPath = this.resolveProcessedCssPath(filePath);
109
- if (!outputPath || !fileSystem.exists(outputPath)) {
110
- return null;
111
- }
112
-
113
- return fileSystem.readFileAsBuffer(outputPath).toString('utf-8');
114
- }
115
-
116
- private async persistProcessedCss(filePath: string, css: string): Promise<void> {
117
- const outputPath = this.resolveProcessedCssPath(filePath);
118
- if (!outputPath) {
119
- return;
120
- }
121
-
122
- fileSystem.ensureDir(path.dirname(outputPath));
123
- fileSystem.write(outputPath, css);
124
- }
125
-
126
- private async prewarmRuntimeCssCache(): Promise<void> {
127
- if (!this.context) {
128
- return;
129
- }
130
-
131
- const sourceFiles = await fileSystem.glob(['**/*.{css,scss,sass,less}'], {
132
- cwd: this.context.srcDir,
133
- });
134
-
135
- for (const relativePath of sourceFiles) {
136
- const filePath = path.join(this.context.srcDir, relativePath);
137
- if (!this.matchesFileFilter(filePath)) {
138
- continue;
139
- }
140
-
141
- this.trackedCssFiles.add(filePath);
142
-
143
- const rawContents = await fileSystem.readFile(filePath);
144
- let transformedInput = rawContents;
145
-
146
- if (this.options?.transformInput) {
147
- transformedInput = await this.options.transformInput(rawContents, filePath);
148
- }
149
-
150
- const processed = await this.process(transformedInput, filePath);
151
- this.runtimeCssCache.set(filePath, processed);
152
- await this.persistProcessedCss(filePath, processed);
153
- }
154
- }
155
-
156
- private transformCssSync(input: CssTransformInput): string {
157
- const cached = this.runtimeCssCache.get(input.filePath);
158
- if (cached) {
159
- return cached;
160
- }
161
-
162
- const persisted = this.readProcessedCssFromDist(input.filePath);
163
- if (persisted) {
164
- this.runtimeCssCache.set(input.filePath, persisted);
165
- return persisted;
166
- }
167
-
168
- const { contents } = input;
169
- return typeof contents === 'string' ? contents : contents.toString('utf-8');
170
- }
171
-
172
- private async transformCssAsync(input: CssTransformInput): Promise<string> {
173
- const { contents, filePath } = input;
174
- let transformed: string = typeof contents === 'string' ? contents : contents.toString('utf-8');
175
-
176
- if (this.options?.transformInput) {
177
- const result = this.options.transformInput(contents, filePath);
178
- transformed =
179
- typeof (result as unknown as Record<string, unknown>).then === 'function'
180
- ? await (result as Promise<string>)
181
- : (result as string);
182
- }
183
-
184
- const processed = await this.process(transformed, filePath);
185
- this.runtimeCssCache.set(filePath, processed);
186
- await this.persistProcessedCss(filePath, processed);
187
- return processed;
188
- }
189
-
190
- override matchesFileFilter(filepath: string): boolean {
191
- const filter = this.options?.filter ?? PostCssProcessorPlugin.DEFAULT_OPTIONS.filter;
192
- return filter.test(filepath);
193
- }
194
-
195
- private materializePluginFactories(pluginFactories: PluginFactoryRecord): postcss.AcceptedPlugin[] {
196
- return Object.values(pluginFactories).map((factory) => factory());
197
- }
198
-
199
- private refreshConfiguredPlugins(): void {
200
- if (!this.pluginFactories) {
201
- return;
202
- }
203
-
204
- this.postcssPlugins = this.materializePluginFactories(this.pluginFactories);
205
- }
206
-
207
- private enqueueWatchTask(task: () => Promise<void>): Promise<void> {
208
- const queuedTask = this.watchQueue.then(task, task);
209
- this.watchQueue = queuedTask.catch(() => undefined);
210
- return queuedTask;
211
- }
212
-
213
- private getTrackedCssFiles(): string[] {
214
- return Array.from(this.trackedCssFiles).filter(
215
- (filePath) => this.matchesFileFilter(filePath) && fileSystem.exists(filePath),
216
- );
217
- }
218
-
219
- private async handleDependencyChange(bridge: IClientBridge): Promise<void> {
220
- if (!this.context) {
221
- return;
222
- }
223
-
224
- const cssFiles = this.getTrackedCssFiles();
225
- if (cssFiles.length === 0) {
226
- return;
227
- }
228
-
229
- this.refreshConfiguredPlugins();
230
-
231
- for (const cssFilePath of cssFiles) {
232
- await this.handleCssChange(cssFilePath, bridge, false);
233
- }
234
- }
235
-
236
- constructor(
237
- config: Omit<ProcessorConfig<PostCssProcessorPluginConfig>, 'name' | 'description'> = {
238
- options: PostCssProcessorPlugin.DEFAULT_OPTIONS,
239
- },
240
- ) {
241
- super({
242
- name: 'ecopages-postcss-processor',
243
- description: 'A Processor for transforming CSS files using PostCSS.',
244
- capabilities: [
245
- {
246
- kind: 'stylesheet',
247
- extensions: ['*.{css,scss,sass,less}'],
248
- },
249
- ],
250
- watch: {
251
- paths: [],
252
- extensions: [
253
- '.css',
254
- '.scss',
255
- '.sass',
256
- '.less',
257
- '.tsx',
258
- '.ts',
259
- '.jsx',
260
- '.js',
261
- '.mdx',
262
- '.html',
263
- '.svelte',
264
- '.vue',
265
- ],
266
- onChange: async ({ path, bridge }) => {
267
- await this.enqueueWatchTask(async () => {
268
- if (this.matchesFileFilter(path)) {
269
- await this.handleCssChange(path, bridge);
270
- return;
271
- }
272
-
273
- await this.handleDependencyChange(bridge);
274
- });
275
- },
276
- onCreate: async ({ path, bridge }) => {
277
- await this.enqueueWatchTask(async () => {
278
- if (this.matchesFileFilter(path)) {
279
- await this.handleCssChange(path, bridge);
280
- return;
281
- }
282
-
283
- await this.handleDependencyChange(bridge);
284
- });
285
- },
286
- onDelete: async ({ path, bridge }) => {
287
- await this.enqueueWatchTask(async () => {
288
- if (this.matchesFileFilter(path)) {
289
- this.runtimeCssCache.delete(path);
290
- this.trackedCssFiles.delete(path);
291
- return;
292
- }
293
-
294
- await this.handleDependencyChange(bridge);
295
- });
296
- },
297
- },
298
- ...config,
299
- });
300
- }
301
-
302
- /**
303
- * Handles CSS file changes during development.
304
- * Processes the file and broadcasts a css-update event for hot reloading.
305
- */
306
- private async handleCssChange(filePath: string, bridge: IClientBridge, refreshPlugins = true): Promise<void> {
307
- if (!this.context) return;
308
- if (!fileSystem.exists(filePath)) return;
309
-
310
- try {
311
- this.trackedCssFiles.add(filePath);
312
-
313
- if (refreshPlugins) {
314
- this.refreshConfiguredPlugins();
315
- }
316
-
317
- let content = await fileSystem.readFile(filePath);
318
-
319
- if (this.options?.transformInput) {
320
- content = await this.options.transformInput(content, filePath);
321
- }
322
-
323
- const processed = await this.process(content, filePath);
324
-
325
- const cached = this.runtimeCssCache.get(filePath);
326
- if (cached === processed) {
327
- return;
328
- }
329
-
330
- this.runtimeCssCache.set(filePath, processed);
331
- await this.persistProcessedCss(filePath, processed);
332
-
333
- bridge.cssUpdate(filePath);
334
-
335
- logger.debug(`Processed CSS: ${filePath}`);
336
- } catch (error) {
337
- const errorMessage = error instanceof Error ? error.message : String(error);
338
- logger.error(`Failed to process CSS: ${filePath}`, errorMessage);
339
- bridge.error(errorMessage);
340
- }
341
- }
342
-
343
- get buildPlugins(): EcoBuildPlugin[] {
344
- return [
345
- createCssLoaderPlugin({
346
- name: 'postcss-processor-build-loader',
347
- filter: this.getCssFilter(),
348
- transform: this.transformCssAsync.bind(this),
349
- }),
350
- ];
351
- }
352
-
353
- get plugins(): EcoBuildPlugin[] {
354
- return [
355
- createCssLoaderPlugin({
356
- name: 'postcss-processor-runtime-loader',
357
- filter: this.getCssFilter(),
358
- transform: this.transformCssSync.bind(this),
359
- }),
360
- ];
361
- }
362
-
363
- /**
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.
369
- */
370
- override async prepareBuildContributions(): Promise<void> {
371
- if (this.buildContributionsPrepared) {
372
- return;
373
- }
374
-
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();
384
- await this.prewarmRuntimeCssCache();
385
- }
386
-
387
- /**
388
- * Get the PostCSS plugins from the options or a config file.
389
- * Searches for postcss.config.{js,cjs,mjs,ts} in the root directory.
390
- */
391
- private async collectPostcssPlugins(): Promise<void> {
392
- if (!this.context) {
393
- throw new Error('Context must be set');
394
- }
395
-
396
- const configExtensions = ['js', 'cjs', 'mjs', 'ts'];
397
- let foundConfigPath: string | undefined;
398
- let loadedPlugins: postcss.AcceptedPlugin[] | undefined;
399
- let loadedPluginFactories: PluginFactoryRecord | undefined;
400
-
401
- for (const ext of configExtensions) {
402
- const configPath = path.join(this.context.rootDir, `postcss.config.${ext}`);
403
- if (fileSystem.exists(configPath)) {
404
- foundConfigPath = configPath;
405
- break;
406
- }
407
- }
408
-
409
- if (foundConfigPath) {
410
- try {
411
- logger.debug(`Loading PostCSS config from: ${foundConfigPath}`);
412
-
413
- const postcssConfigModule = await import(foundConfigPath);
414
- const postcssConfig = postcssConfigModule.default || postcssConfigModule;
415
- if (
416
- postcssConfig &&
417
- typeof postcssConfig.pluginFactories === 'object' &&
418
- postcssConfig.pluginFactories !== null
419
- ) {
420
- loadedPluginFactories = postcssConfig.pluginFactories as PluginFactoryRecord;
421
- }
422
-
423
- if (postcssConfig && typeof postcssConfig.plugins === 'object' && postcssConfig.plugins !== null) {
424
- if (Array.isArray(postcssConfig.plugins)) {
425
- loadedPlugins = postcssConfig.plugins;
426
- } else {
427
- loadedPlugins = Object.values(postcssConfig.plugins as PluginsRecord);
428
- }
429
- logger.debug(`Successfully loaded ${loadedPlugins?.length ?? 0} plugins from config file.`);
430
- } else {
431
- logger.warn(
432
- `PostCSS config file found (${foundConfigPath}), but no valid 'plugins' export detected.`,
433
- );
434
- }
435
- } catch (error: any) {
436
- logger.error(`Error loading PostCSS config from ${foundConfigPath}: ${error.message}`, error);
437
- loadedPlugins = undefined;
438
- }
439
- } else {
440
- logger.debug('No PostCSS config file found in root directory.');
441
- }
442
-
443
- if (loadedPluginFactories) {
444
- this.pluginFactories = loadedPluginFactories;
445
- this.postcssPlugins = this.materializePluginFactories(loadedPluginFactories);
446
- } else if (loadedPlugins) {
447
- this.pluginFactories = undefined;
448
- this.postcssPlugins = loadedPlugins;
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
- }
459
- } else {
460
- logger.warn(
461
- 'No PostCSS plugins configured. Use a preset like tailwindV3Preset() or tailwindV4Preset(), ' +
462
- 'provide plugins via options, or create a postcss.config file.',
463
- );
464
- this.pluginFactories = undefined;
465
- this.postcssPlugins = [];
466
- }
467
-
468
- if (!this.postcssPlugins || this.postcssPlugins.length === 0) {
469
- logger.warn('No PostCSS plugins configured or loaded. CSS processing might be minimal.');
470
- this.postcssPlugins = [];
471
- }
472
- }
473
-
474
- /**
475
- * Process CSS content
476
- * @param fileAsString CSS content as string
477
- * @param filePath Optional file path for resolving relative imports
478
- * @returns Processed CSS
479
- */
480
- async process(fileAsString: string, filePath?: string): Promise<string> {
481
- const input =
482
- this.options?.transformInput && filePath
483
- ? await this.options.transformInput(fileAsString, filePath)
484
- : fileAsString;
485
-
486
- return await PostCssProcessor.processStringOrBuffer(input, {
487
- filePath,
488
- plugins: this.postcssPlugins,
489
- transformOutput: this.options?.transformOutput,
490
- });
491
- }
492
-
493
- processSync(fileAsString: string, filePath?: string): string {
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, {
504
- filePath,
505
- plugins: this.postcssPlugins,
506
- transformOutput: this.options?.transformOutput,
507
- });
508
- }
509
-
510
- /**
511
- * Teardown the PostCSS processor.
512
- */
513
- async teardown(): Promise<void> {
514
- logger.debug('Tearing down PostCSS processor');
515
- }
516
- }
517
-
518
- export const postcssProcessorPlugin = (config?: PostCssProcessorPluginConfig): PostCssProcessorPlugin => {
519
- return new PostCssProcessorPlugin({
520
- options: config,
521
- });
522
- };
@@ -1,157 +0,0 @@
1
- /**
2
- * This module contains the PostCSS Processor
3
- * @module
4
- */
5
-
6
- import { existsSync, readFileSync } from 'node:fs';
7
- import { Logger } from '@ecopages/logger';
8
- import postcss from 'postcss';
9
-
10
- /**
11
- * PostCSS Processor Options
12
- */
13
- export type PostCssProcessorOptions = {
14
- plugins?: postcss.AcceptedPlugin[];
15
- /**
16
- * Optional file path for resolving relative imports
17
- */
18
- filePath?: string;
19
- /**
20
- * Optional callback to transform the output CSS
21
- * @param css The processed CSS
22
- * @returns The transformed CSS
23
- */
24
- transformOutput?: (css: string) => string | Promise<string>;
25
- };
26
-
27
- /**
28
- * ProcessPath
29
- * @param path string
30
- * @param options {@link PostCssProcessorOptions}
31
- * @returns string
32
- */
33
- export type ProcessPath = (path: string, options?: PostCssProcessorOptions) => Promise<string>;
34
-
35
- /**
36
- * ProcessStringOrBuffer
37
- * @param contents string | Buffer
38
- * @param options {@link PostCssProcessorOptions}
39
- * @returns string
40
- */
41
- export type ProcessStringOrBuffer = (contents: string | Buffer, options?: PostCssProcessorOptions) => Promise<string>;
42
-
43
- const appLogger = new Logger('[@ecopages/postcss-processor]');
44
-
45
- export function getFileAsBuffer(path: string): Buffer {
46
- try {
47
- if (!existsSync(path)) {
48
- throw new Error(`File: ${path} not found`);
49
- }
50
- return readFileSync(path);
51
- } catch (error) {
52
- const errorMessage = error instanceof Error ? error.message : String(error);
53
- throw new Error(`[ecopages] Error reading file: ${path}, ${errorMessage}`);
54
- }
55
- }
56
-
57
- const getPlugins = (options?: PostCssProcessorOptions): postcss.AcceptedPlugin[] => {
58
- if (!options || !options.plugins) return [];
59
- return Array.isArray(options.plugins) ? options.plugins : Object.values(options.plugins);
60
- };
61
-
62
- /**
63
- * It processes the given path using PostCSS
64
- * @param path string
65
- * @param options {@link PostCssProcessorOptions}
66
- * @returns string
67
- *
68
- * @example
69
- * ```ts
70
- * PostCssProcessor.processPath('path/to/file.css').then((processedCss) => {
71
- * console.log(processedCss);
72
- * });\n */
73
- const processPath: ProcessPath = async (path, options) => {
74
- const contents = getFileAsBuffer(path);
75
-
76
- return postcss(getPlugins(options))
77
- .process(contents, { from: path })
78
- .then((result) => result.css)
79
- .catch((error) => {
80
- appLogger.error('Error processing file with PostCssProcessor', error.message);
81
- return '';
82
- });
83
- };
84
-
85
- /**
86
- * It processes the given string or buffer using PostCSS
87
- * @param contents string | Buffer
88
- * @param options {@link PostCssProcessorOptions}
89
- * @returns string
90
- *
91
- * @example
92
- * ```ts
93
- * const css = `body { @apply bg-blue-500; }`;
94
- *
95
- * PostCssProcessor.processString(css).then((processedCss) => {
96
- * console.log(processedCss);
97
- * });
98
- * ```
99
- */
100
- const processStringOrBuffer: ProcessStringOrBuffer = async (contents, options) => {
101
- if (!contents) return '';
102
-
103
- return postcss(getPlugins(options))
104
- .process(contents, { from: options?.filePath })
105
- .then(async (result) => {
106
- let css = result.css;
107
- if (options?.transformOutput) {
108
- css = await options.transformOutput(css);
109
- }
110
- return css;
111
- })
112
- .catch((error) => {
113
- appLogger.error('Error processing string or buffer with PostCssProcessor', error.message);
114
- return '';
115
- });
116
- };
117
-
118
- export type ProcessStringOrBufferSync = (contents: string | Buffer, options?: PostCssProcessorOptions) => string;
119
-
120
- const processStringOrBufferSync: ProcessStringOrBufferSync = (contents, options) => {
121
- if (!contents) return '';
122
-
123
- try {
124
- const result = postcss(getPlugins(options)).process(contents, { from: options?.filePath });
125
- let css = result.css;
126
- if (options?.transformOutput) {
127
- const output = options.transformOutput(css);
128
- if (output instanceof Promise) {
129
- throw new Error('transformOutput must be synchronous when used with processStringOrBufferSync');
130
- }
131
- css = output;
132
- }
133
- return css;
134
- } catch (error) {
135
- if (error instanceof Error && error.message.includes('transformOutput must be synchronous')) {
136
- throw error;
137
- }
138
- appLogger.error('Error processing string or buffer with PostCssProcessor', (error as Error).message);
139
- return '';
140
- }
141
- };
142
-
143
- /**
144
- * PostCSS Processor
145
- * - {@link processPath} : It processes the given path using PostCSS
146
- * - {@link processStringOrBuffer}: It processes the given string or buffer using PostCSS
147
- * - {@link processStringOrBufferSync}: It processes the given string or buffer synchronously using PostCSS (requires all plugins to be sync)
148
- */
149
- export const PostCssProcessor: {
150
- processPath: ProcessPath;
151
- processStringOrBuffer: ProcessStringOrBuffer;
152
- processStringOrBufferSync: ProcessStringOrBufferSync;
153
- } = {
154
- processPath,
155
- processStringOrBuffer,
156
- processStringOrBufferSync,
157
- };
@@ -1,7 +0,0 @@
1
- /**
2
- * PostCSS Processor Presets
3
- * @module @ecopages/postcss-processor/presets
4
- */
5
-
6
- export { tailwindV3Preset } from './tailwind-v3';
7
- export { tailwindV4Preset, type TailwindV4PresetOptions } from './tailwind-v4';
@@ -1,72 +0,0 @@
1
- /**
2
- * Tailwind CSS v3 Preset for PostCSS Processor
3
- * @module @ecopages/postcss-processor/presets/tailwind-v3
4
- *
5
- * Requires: tailwindcss, postcss-import, autoprefixer, cssnano
6
- * Install: bun add tailwindcss postcss-import autoprefixer cssnano
7
- */
8
-
9
- import autoprefixer from 'autoprefixer';
10
- import browserslist from 'browserslist';
11
- import cssnano from 'cssnano';
12
- import type postcss from 'postcss';
13
- import postcssImport from 'postcss-import';
14
- import tailwindcss from 'tailwindcss';
15
- import tailwindcssNesting from 'tailwindcss/nesting/index.js';
16
- import type { PluginFactoryRecord, PostCssProcessorPluginConfig } from '../plugin.ts';
17
-
18
- type PluginsRecord = Record<string, postcss.AcceptedPlugin>;
19
-
20
- /**
21
- * Creates a PostCSS processor config preset for Tailwind CSS v3.
22
- *
23
- * Features:
24
- * - Uses classic Tailwind v3 plugin stack
25
- * - Includes postcss-import, tailwindcss/nesting, tailwindcss, autoprefixer, cssnano
26
- * - Returns both `plugins` for immediate use and `pluginFactories` so Ecopages
27
- * can recreate fresh Tailwind/PostCSS plugin instances on dependency-driven rebuilds
28
- *
29
- * @example
30
- * ```typescript
31
- * import { postcssProcessorPlugin } from '@ecopages/postcss-processor';
32
- * import { tailwindV3Preset } from '@ecopages/postcss-processor/presets';
33
- *
34
- * // Basic usage
35
- * postcssProcessorPlugin(tailwindV3Preset())
36
- *
37
- * // Extend with additional plugins
38
- * const preset = tailwindV3Preset();
39
- * postcssProcessorPlugin({
40
- * ...preset,
41
- * plugins: { ...preset.plugins, myPlugin: myPlugin() },
42
- * })
43
- * ```
44
- */
45
- export function tailwindV3Preset(): PostCssProcessorPluginConfig {
46
- // Check if browserslist config exists
47
- const browserslistConfig = browserslist.loadConfig({ path: process.cwd() });
48
- const autoprefixerOptions = browserslistConfig
49
- ? {}
50
- : {
51
- overrideBrowserslist: ['>0.3%', 'not ie 11', 'not dead', 'not op_mini all'],
52
- };
53
-
54
- const pluginFactories: PluginFactoryRecord = {
55
- 'postcss-import': () => postcssImport(),
56
- 'tailwindcss/nesting': () => tailwindcssNesting(),
57
- tailwindcss: () => tailwindcss(),
58
- autoprefixer: () => autoprefixer(autoprefixerOptions),
59
- cssnano: () => cssnano(),
60
- };
61
-
62
- const plugins: PluginsRecord = Object.fromEntries(
63
- Object.entries(pluginFactories).map(([name, factory]) => [name, factory()]),
64
- ) as PluginsRecord;
65
-
66
- /**
67
- * Keep both forms:
68
- * - `plugins` are used immediately by the processor
69
- * - `pluginFactories` let the processor recreate fresh plugin instances later
70
- */
71
- return { plugins, pluginFactories };
72
- }
@@ -1,122 +0,0 @@
1
- /**
2
- * Tailwind CSS v4 Preset for PostCSS Processor
3
- * @module @ecopages/postcss-processor/presets/tailwind-v4
4
- *
5
- * Requires: @tailwindcss/postcss, cssnano
6
- * Install: bun add @tailwindcss/postcss cssnano
7
- */
8
-
9
- import tailwindcss from '@tailwindcss/postcss';
10
- import autoprefixer from 'autoprefixer';
11
- import browserslist from 'browserslist';
12
- import cssnano from 'cssnano';
13
- import path from 'node:path';
14
- import postcssImport from 'postcss-import';
15
- import postcssNested from 'postcss-nested';
16
- import type { PluginFactoryRecord, PostCssProcessorPluginConfig } from '../plugin.ts';
17
-
18
- /**
19
- * Options for Tailwind v4 preset
20
- */
21
- export interface TailwindV4PresetOptions {
22
- /**
23
- * Absolute path to the main Tailwind CSS file containing `@import "tailwindcss"`.
24
- * Used to calculate relative @reference paths for CSS files using @apply.
25
- */
26
- referencePath: string;
27
- }
28
-
29
- /**
30
- * Creates a PostCSS processor config preset for Tailwind CSS v4.
31
- *
32
- * Features:
33
- * - Uses `@tailwindcss/postcss` plugin (v4)
34
- * - Automatically injects `@reference` headers for `@apply` support
35
- * - Includes cssnano for CSS minification
36
- * - Returns both `plugins` for immediate use and `pluginFactories` so Ecopages
37
- * can recreate fresh Tailwind/PostCSS plugin instances on dependency-driven rebuilds
38
- *
39
- * @example
40
- * ```typescript
41
- * import { postcssProcessorPlugin } from '@ecopages/postcss-processor';
42
- * import { tailwindV4Preset } from '@ecopages/postcss-processor/presets';
43
- *
44
- * // Basic usage
45
- * postcssProcessorPlugin(tailwindV4Preset({
46
- * referencePath: path.resolve(import.meta.dir, 'src/styles/tailwind.css'),
47
- * }))
48
- *
49
- * // Extend with additional plugins
50
- * const preset = tailwindV4Preset({ referencePath });
51
- * postcssProcessorPlugin({
52
- * ...preset,
53
- * plugins: { ...preset.plugins, myPlugin: myPlugin() },
54
- * })
55
- * ```
56
- */
57
- export function tailwindV4Preset(options: TailwindV4PresetOptions): PostCssProcessorPluginConfig {
58
- const { referencePath } = options;
59
-
60
- // Check if browserslist config exists
61
- const browserslistConfig = browserslist.loadConfig({ path: process.cwd() });
62
- const autoprefixerOptions = browserslistConfig
63
- ? {}
64
- : {
65
- overrideBrowserslist: ['>0.3%', 'not ie 11', 'not dead', 'not op_mini all'],
66
- };
67
-
68
- const pluginFactories: PluginFactoryRecord = {
69
- 'postcss-import': () => postcssImport(),
70
- 'postcss-nested': () => postcssNested(),
71
- '@tailwindcss/postcss': () => tailwindcss({ optimize: false }),
72
- autoprefixer: () => autoprefixer(autoprefixerOptions),
73
- cssnano: () => cssnano(),
74
- };
75
-
76
- return {
77
- /**
78
- * Instantiate the initial plugin list for the active processor instance.
79
- * Fresh instances can later be recreated from `pluginFactories`.
80
- */
81
- plugins: Object.fromEntries(Object.entries(pluginFactories).map(([name, factory]) => [name, factory()])),
82
- pluginFactories,
83
- transformInput: async (contents: string | Buffer, filePath: string): Promise<string> => {
84
- const css = contents instanceof Buffer ? contents.toString('utf-8') : (contents as string);
85
- const normalizedFilePath = path.resolve(filePath);
86
- const normalizedReferencePath = path.resolve(referencePath);
87
-
88
- /** Skip transformation for the main tailwind entry file */
89
- if (normalizedFilePath === normalizedReferencePath) {
90
- return css;
91
- }
92
-
93
- /** Skip if file already has an @reference directive */
94
- if (/^\s*@reference\s+/m.test(css)) {
95
- return css;
96
- }
97
-
98
- const relativePath = path.relative(path.dirname(filePath), referencePath);
99
-
100
- /** Skip if file already imports the referencePath */
101
- if (css.includes(`@import '${relativePath}'`) || css.includes(`@import "${relativePath}"`)) {
102
- return css;
103
- }
104
-
105
- /**
106
- * Replace `@import 'tailwindcss'` with an import to the referencePath.
107
- * This ensures custom theme variables are available for @apply directives.
108
- */
109
- const tailwindImportPattern = /^@import\s+['"]tailwindcss(?:\/[^'"]*)?['"];?\s*$/m;
110
- if (tailwindImportPattern.test(css)) {
111
- return css.replace(tailwindImportPattern, `@import '${relativePath}';`);
112
- }
113
-
114
- /** If file uses @apply but has no tailwind import, add @reference */
115
- if (css.includes('@apply')) {
116
- return `@reference "${relativePath}";\n\n${css}`;
117
- }
118
-
119
- return css;
120
- },
121
- };
122
- }
@@ -1,37 +0,0 @@
1
- import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
2
-
3
- import { getFileAsBuffer } from '../postcss-processor';
4
- import type { CssTransform } from './css-runtime-contract';
5
-
6
- type CssLoaderOptions = {
7
- name: string;
8
- filter: RegExp;
9
- transform: CssTransform;
10
- };
11
-
12
- export const createCssLoaderPlugin = ({ name, filter, transform }: CssLoaderOptions): EcoBuildPlugin => ({
13
- name,
14
- setup(build) {
15
- build.onLoad({ filter }, (args) => {
16
- const rawFile = getFileAsBuffer(args.path);
17
- const css = transform({
18
- contents: rawFile,
19
- filePath: args.path,
20
- });
21
-
22
- if (css instanceof Promise) {
23
- return css.then((resolved) => ({
24
- exports: { default: resolved },
25
- loader: 'object' as const,
26
- }));
27
- }
28
-
29
- return {
30
- exports: {
31
- default: css as string,
32
- },
33
- loader: 'object' as const,
34
- };
35
- });
36
- },
37
- });
@@ -1,30 +0,0 @@
1
- import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
2
-
3
- import { getFileAsBuffer } from '../postcss-processor';
4
- import type { CssTransform } from './css-runtime-contract';
5
-
6
- type BunCssLoaderOptions = {
7
- name: string;
8
- filter: RegExp;
9
- transform: CssTransform;
10
- };
11
-
12
- export const createBunCssLoaderPlugin = ({ name, filter, transform }: BunCssLoaderOptions): EcoBuildPlugin => ({
13
- name,
14
- setup(build) {
15
- build.onLoad({ filter }, async (args) => {
16
- const rawFile = await getFileAsBuffer(args.path);
17
- const css = await transform({
18
- contents: rawFile,
19
- filePath: args.path,
20
- });
21
-
22
- return {
23
- exports: {
24
- default: css,
25
- },
26
- loader: 'object',
27
- };
28
- });
29
- },
30
- });
@@ -1,6 +0,0 @@
1
- export type CssTransformInput = {
2
- contents: string | Buffer;
3
- filePath: string;
4
- };
5
-
6
- export type CssTransform = (input: CssTransformInput) => string | Promise<string>;