@ecopages/postcss-processor 0.2.0-alpha.2 → 0.2.0-alpha.4
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 +4 -0
- package/package.json +3 -3
- package/src/plugin.d.ts +24 -0
- package/src/plugin.js +102 -5
- package/src/plugin.ts +140 -5
- package/src/presets/tailwind-v3.d.ts +2 -0
- package/src/presets/tailwind-v3.js +10 -7
- package/src/presets/tailwind-v3.ts +19 -8
- package/src/presets/tailwind-v4.d.ts +2 -0
- package/src/presets/tailwind-v4.js +13 -7
- package/src/presets/tailwind-v4.ts +17 -8
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,10 @@ 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
|
+
|
|
9
13
|
### Features
|
|
10
14
|
|
|
11
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`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecopages/postcss-processor",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.4",
|
|
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.
|
|
21
|
-
"@ecopages/file-system": "0.2.0-alpha.
|
|
20
|
+
"@ecopages/core": "0.2.0-alpha.4",
|
|
21
|
+
"@ecopages/file-system": "0.2.0-alpha.4",
|
|
22
22
|
"@ecopages/logger": "latest",
|
|
23
23
|
"autoprefixer": "^10.4.0",
|
|
24
24
|
"browserslist": "^4.28.1",
|
package/src/plugin.d.ts
CHANGED
|
@@ -9,6 +9,15 @@ import type postcss from 'postcss';
|
|
|
9
9
|
* Record of PostCSS plugins keyed by name
|
|
10
10
|
*/
|
|
11
11
|
export type PluginsRecord = Record<string, postcss.AcceptedPlugin>;
|
|
12
|
+
/**
|
|
13
|
+
* Lazily creates PostCSS plugins.
|
|
14
|
+
*
|
|
15
|
+
* This is primarily used in development when a non-CSS file change forces the
|
|
16
|
+
* processor to rebuild tracked stylesheets. Some plugins, including Tailwind,
|
|
17
|
+
* keep internal caches in long-lived plugin instances, so recreating them is
|
|
18
|
+
* required to pick up newly discovered classes.
|
|
19
|
+
*/
|
|
20
|
+
export type PluginFactoryRecord = Record<string, () => postcss.AcceptedPlugin>;
|
|
12
21
|
/**
|
|
13
22
|
* Configuration for the PostCSS processor
|
|
14
23
|
*/
|
|
@@ -38,6 +47,13 @@ export interface PostCssProcessorPluginConfig {
|
|
|
38
47
|
* @default undefined (uses default plugins)
|
|
39
48
|
*/
|
|
40
49
|
plugins?: PluginsRecord;
|
|
50
|
+
/**
|
|
51
|
+
* Factory functions for recreating stateful PostCSS plugins.
|
|
52
|
+
*
|
|
53
|
+
* When provided, Ecopages uses these factories to build a fresh plugin list
|
|
54
|
+
* for dependency-driven stylesheet rebuilds during development.
|
|
55
|
+
*/
|
|
56
|
+
pluginFactories?: PluginFactoryRecord;
|
|
41
57
|
}
|
|
42
58
|
/**
|
|
43
59
|
* PostCssProcessorPlugin
|
|
@@ -46,7 +62,10 @@ export interface PostCssProcessorPluginConfig {
|
|
|
46
62
|
export declare class PostCssProcessorPlugin extends Processor<PostCssProcessorPluginConfig> {
|
|
47
63
|
static DEFAULT_OPTIONS: Required<Pick<PostCssProcessorPluginConfig, 'filter'>>;
|
|
48
64
|
private postcssPlugins;
|
|
65
|
+
private pluginFactories?;
|
|
49
66
|
private readonly runtimeCssCache;
|
|
67
|
+
private readonly trackedCssFiles;
|
|
68
|
+
private watchQueue;
|
|
50
69
|
private getCssFilter;
|
|
51
70
|
private resolveProcessedCssPath;
|
|
52
71
|
private readProcessedCssFromDist;
|
|
@@ -55,6 +74,11 @@ export declare class PostCssProcessorPlugin extends Processor<PostCssProcessorPl
|
|
|
55
74
|
private transformCssSync;
|
|
56
75
|
private transformCssAsync;
|
|
57
76
|
matchesFileFilter(filepath: string): boolean;
|
|
77
|
+
private materializePluginFactories;
|
|
78
|
+
private refreshConfiguredPlugins;
|
|
79
|
+
private enqueueWatchTask;
|
|
80
|
+
private getTrackedCssFiles;
|
|
81
|
+
private handleDependencyChange;
|
|
58
82
|
constructor(config?: Omit<ProcessorConfig<PostCssProcessorPluginConfig>, 'name' | 'description'>);
|
|
59
83
|
/**
|
|
60
84
|
* Handles CSS file changes during development.
|
package/src/plugin.js
CHANGED
|
@@ -12,7 +12,10 @@ class PostCssProcessorPlugin extends Processor {
|
|
|
12
12
|
filter: /\.css$/
|
|
13
13
|
};
|
|
14
14
|
postcssPlugins = [];
|
|
15
|
+
pluginFactories;
|
|
15
16
|
runtimeCssCache = /* @__PURE__ */ new Map();
|
|
17
|
+
trackedCssFiles = /* @__PURE__ */ new Set();
|
|
18
|
+
watchQueue = Promise.resolve();
|
|
16
19
|
getCssFilter() {
|
|
17
20
|
return this.options?.filter ?? PostCssProcessorPlugin.DEFAULT_OPTIONS.filter;
|
|
18
21
|
}
|
|
@@ -53,6 +56,7 @@ class PostCssProcessorPlugin extends Processor {
|
|
|
53
56
|
if (!this.matchesFileFilter(filePath)) {
|
|
54
57
|
continue;
|
|
55
58
|
}
|
|
59
|
+
this.trackedCssFiles.add(filePath);
|
|
56
60
|
const rawContents = await fileSystem.readFile(filePath);
|
|
57
61
|
let transformedInput = rawContents;
|
|
58
62
|
if (this.options?.transformInput) {
|
|
@@ -92,6 +96,38 @@ class PostCssProcessorPlugin extends Processor {
|
|
|
92
96
|
const filter = this.options?.filter ?? PostCssProcessorPlugin.DEFAULT_OPTIONS.filter;
|
|
93
97
|
return filter.test(filepath);
|
|
94
98
|
}
|
|
99
|
+
materializePluginFactories(pluginFactories) {
|
|
100
|
+
return Object.values(pluginFactories).map((factory) => factory());
|
|
101
|
+
}
|
|
102
|
+
refreshConfiguredPlugins() {
|
|
103
|
+
if (!this.pluginFactories) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this.postcssPlugins = this.materializePluginFactories(this.pluginFactories);
|
|
107
|
+
}
|
|
108
|
+
enqueueWatchTask(task) {
|
|
109
|
+
const queuedTask = this.watchQueue.then(task, task);
|
|
110
|
+
this.watchQueue = queuedTask.catch(() => void 0);
|
|
111
|
+
return queuedTask;
|
|
112
|
+
}
|
|
113
|
+
getTrackedCssFiles() {
|
|
114
|
+
return Array.from(this.trackedCssFiles).filter(
|
|
115
|
+
(filePath) => this.matchesFileFilter(filePath) && fileSystem.exists(filePath)
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
async handleDependencyChange(bridge) {
|
|
119
|
+
if (!this.context) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const cssFiles = this.getTrackedCssFiles();
|
|
123
|
+
if (cssFiles.length === 0) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
this.refreshConfiguredPlugins();
|
|
127
|
+
for (const cssFilePath of cssFiles) {
|
|
128
|
+
await this.handleCssChange(cssFilePath, bridge, false);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
95
131
|
constructor(config = {
|
|
96
132
|
options: PostCssProcessorPlugin.DEFAULT_OPTIONS
|
|
97
133
|
}) {
|
|
@@ -101,14 +137,52 @@ class PostCssProcessorPlugin extends Processor {
|
|
|
101
137
|
capabilities: [
|
|
102
138
|
{
|
|
103
139
|
kind: "stylesheet",
|
|
104
|
-
extensions: ["*.css"]
|
|
140
|
+
extensions: ["*.{css,scss,sass,less}"]
|
|
105
141
|
}
|
|
106
142
|
],
|
|
107
143
|
watch: {
|
|
108
144
|
paths: [],
|
|
109
|
-
extensions: [
|
|
145
|
+
extensions: [
|
|
146
|
+
".css",
|
|
147
|
+
".scss",
|
|
148
|
+
".sass",
|
|
149
|
+
".less",
|
|
150
|
+
".tsx",
|
|
151
|
+
".ts",
|
|
152
|
+
".jsx",
|
|
153
|
+
".js",
|
|
154
|
+
".mdx",
|
|
155
|
+
".html",
|
|
156
|
+
".svelte",
|
|
157
|
+
".vue"
|
|
158
|
+
],
|
|
110
159
|
onChange: async ({ path: path2, bridge }) => {
|
|
111
|
-
await this.
|
|
160
|
+
await this.enqueueWatchTask(async () => {
|
|
161
|
+
if (this.matchesFileFilter(path2)) {
|
|
162
|
+
await this.handleCssChange(path2, bridge);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
await this.handleDependencyChange(bridge);
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
onCreate: async ({ path: path2, bridge }) => {
|
|
169
|
+
await this.enqueueWatchTask(async () => {
|
|
170
|
+
if (this.matchesFileFilter(path2)) {
|
|
171
|
+
await this.handleCssChange(path2, bridge);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
await this.handleDependencyChange(bridge);
|
|
175
|
+
});
|
|
176
|
+
},
|
|
177
|
+
onDelete: async ({ path: path2, bridge }) => {
|
|
178
|
+
await this.enqueueWatchTask(async () => {
|
|
179
|
+
if (this.matchesFileFilter(path2)) {
|
|
180
|
+
this.runtimeCssCache.delete(path2);
|
|
181
|
+
this.trackedCssFiles.delete(path2);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
await this.handleDependencyChange(bridge);
|
|
185
|
+
});
|
|
112
186
|
}
|
|
113
187
|
},
|
|
114
188
|
...config
|
|
@@ -118,14 +192,23 @@ class PostCssProcessorPlugin extends Processor {
|
|
|
118
192
|
* Handles CSS file changes during development.
|
|
119
193
|
* Processes the file and broadcasts a css-update event for hot reloading.
|
|
120
194
|
*/
|
|
121
|
-
async handleCssChange(filePath, bridge) {
|
|
195
|
+
async handleCssChange(filePath, bridge, refreshPlugins = true) {
|
|
122
196
|
if (!this.context) return;
|
|
197
|
+
if (!fileSystem.exists(filePath)) return;
|
|
123
198
|
try {
|
|
199
|
+
this.trackedCssFiles.add(filePath);
|
|
200
|
+
if (refreshPlugins) {
|
|
201
|
+
this.refreshConfiguredPlugins();
|
|
202
|
+
}
|
|
124
203
|
let content = await fileSystem.readFile(filePath);
|
|
125
204
|
if (this.options?.transformInput) {
|
|
126
205
|
content = await this.options.transformInput(content, filePath);
|
|
127
206
|
}
|
|
128
207
|
const processed = await this.process(content, filePath);
|
|
208
|
+
const cached = this.runtimeCssCache.get(filePath);
|
|
209
|
+
if (cached === processed) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
129
212
|
this.runtimeCssCache.set(filePath, processed);
|
|
130
213
|
await this.persistProcessedCss(filePath, processed);
|
|
131
214
|
bridge.cssUpdate(filePath);
|
|
@@ -172,6 +255,7 @@ class PostCssProcessorPlugin extends Processor {
|
|
|
172
255
|
const configExtensions = ["js", "cjs", "mjs", "ts"];
|
|
173
256
|
let foundConfigPath;
|
|
174
257
|
let loadedPlugins;
|
|
258
|
+
let loadedPluginFactories;
|
|
175
259
|
for (const ext of configExtensions) {
|
|
176
260
|
const configPath = path.join(this.context.rootDir, `postcss.config.${ext}`);
|
|
177
261
|
if (fileSystem.exists(configPath)) {
|
|
@@ -184,6 +268,9 @@ class PostCssProcessorPlugin extends Processor {
|
|
|
184
268
|
logger.debug(`Loading PostCSS config from: ${foundConfigPath}`);
|
|
185
269
|
const postcssConfigModule = await import(foundConfigPath);
|
|
186
270
|
const postcssConfig = postcssConfigModule.default || postcssConfigModule;
|
|
271
|
+
if (postcssConfig && typeof postcssConfig.pluginFactories === "object" && postcssConfig.pluginFactories !== null) {
|
|
272
|
+
loadedPluginFactories = postcssConfig.pluginFactories;
|
|
273
|
+
}
|
|
187
274
|
if (postcssConfig && typeof postcssConfig.plugins === "object" && postcssConfig.plugins !== null) {
|
|
188
275
|
if (Array.isArray(postcssConfig.plugins)) {
|
|
189
276
|
loadedPlugins = postcssConfig.plugins;
|
|
@@ -203,15 +290,25 @@ class PostCssProcessorPlugin extends Processor {
|
|
|
203
290
|
} else {
|
|
204
291
|
logger.debug("No PostCSS config file found in root directory.");
|
|
205
292
|
}
|
|
206
|
-
if (
|
|
293
|
+
if (loadedPluginFactories) {
|
|
294
|
+
this.pluginFactories = loadedPluginFactories;
|
|
295
|
+
this.postcssPlugins = this.materializePluginFactories(loadedPluginFactories);
|
|
296
|
+
} else if (loadedPlugins) {
|
|
297
|
+
this.pluginFactories = void 0;
|
|
207
298
|
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);
|
|
208
303
|
} else if (this.options?.plugins) {
|
|
209
304
|
logger.debug("Using PostCSS plugins provided in processor options.");
|
|
305
|
+
this.pluginFactories = void 0;
|
|
210
306
|
this.postcssPlugins = Object.values(this.options.plugins);
|
|
211
307
|
} else {
|
|
212
308
|
logger.warn(
|
|
213
309
|
"No PostCSS plugins configured. Use a preset like tailwindV3Preset() or tailwindV4Preset(), provide plugins via options, or create a postcss.config file."
|
|
214
310
|
);
|
|
311
|
+
this.pluginFactories = void 0;
|
|
215
312
|
this.postcssPlugins = [];
|
|
216
313
|
}
|
|
217
314
|
if (!this.postcssPlugins || this.postcssPlugins.length === 0) {
|
package/src/plugin.ts
CHANGED
|
@@ -23,6 +23,16 @@ const logger = new Logger('[@ecopages/postcss-processor]', {
|
|
|
23
23
|
*/
|
|
24
24
|
export type PluginsRecord = Record<string, postcss.AcceptedPlugin>;
|
|
25
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
|
+
|
|
26
36
|
/**
|
|
27
37
|
* Configuration for the PostCSS processor
|
|
28
38
|
*/
|
|
@@ -52,6 +62,13 @@ export interface PostCssProcessorPluginConfig {
|
|
|
52
62
|
* @default undefined (uses default plugins)
|
|
53
63
|
*/
|
|
54
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;
|
|
55
72
|
}
|
|
56
73
|
|
|
57
74
|
/**
|
|
@@ -64,7 +81,10 @@ export class PostCssProcessorPlugin extends Processor<PostCssProcessorPluginConf
|
|
|
64
81
|
};
|
|
65
82
|
|
|
66
83
|
private postcssPlugins: postcss.AcceptedPlugin[] = [];
|
|
84
|
+
private pluginFactories?: PluginFactoryRecord;
|
|
67
85
|
private readonly runtimeCssCache = new Map<string, string>();
|
|
86
|
+
private readonly trackedCssFiles = new Set<string>();
|
|
87
|
+
private watchQueue: Promise<void> = Promise.resolve();
|
|
68
88
|
|
|
69
89
|
private getCssFilter(): RegExp {
|
|
70
90
|
return this.options?.filter ?? PostCssProcessorPlugin.DEFAULT_OPTIONS.filter;
|
|
@@ -117,6 +137,8 @@ export class PostCssProcessorPlugin extends Processor<PostCssProcessorPluginConf
|
|
|
117
137
|
continue;
|
|
118
138
|
}
|
|
119
139
|
|
|
140
|
+
this.trackedCssFiles.add(filePath);
|
|
141
|
+
|
|
120
142
|
const rawContents = await fileSystem.readFile(filePath);
|
|
121
143
|
let transformedInput = rawContents;
|
|
122
144
|
|
|
@@ -169,6 +191,47 @@ export class PostCssProcessorPlugin extends Processor<PostCssProcessorPluginConf
|
|
|
169
191
|
return filter.test(filepath);
|
|
170
192
|
}
|
|
171
193
|
|
|
194
|
+
private materializePluginFactories(pluginFactories: PluginFactoryRecord): postcss.AcceptedPlugin[] {
|
|
195
|
+
return Object.values(pluginFactories).map((factory) => factory());
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private refreshConfiguredPlugins(): void {
|
|
199
|
+
if (!this.pluginFactories) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
this.postcssPlugins = this.materializePluginFactories(this.pluginFactories);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private enqueueWatchTask(task: () => Promise<void>): Promise<void> {
|
|
207
|
+
const queuedTask = this.watchQueue.then(task, task);
|
|
208
|
+
this.watchQueue = queuedTask.catch(() => undefined);
|
|
209
|
+
return queuedTask;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private getTrackedCssFiles(): string[] {
|
|
213
|
+
return Array.from(this.trackedCssFiles).filter(
|
|
214
|
+
(filePath) => this.matchesFileFilter(filePath) && fileSystem.exists(filePath),
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private async handleDependencyChange(bridge: IClientBridge): Promise<void> {
|
|
219
|
+
if (!this.context) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const cssFiles = this.getTrackedCssFiles();
|
|
224
|
+
if (cssFiles.length === 0) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
this.refreshConfiguredPlugins();
|
|
229
|
+
|
|
230
|
+
for (const cssFilePath of cssFiles) {
|
|
231
|
+
await this.handleCssChange(cssFilePath, bridge, false);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
172
235
|
constructor(
|
|
173
236
|
config: Omit<ProcessorConfig<PostCssProcessorPluginConfig>, 'name' | 'description'> = {
|
|
174
237
|
options: PostCssProcessorPlugin.DEFAULT_OPTIONS,
|
|
@@ -180,14 +243,55 @@ export class PostCssProcessorPlugin extends Processor<PostCssProcessorPluginConf
|
|
|
180
243
|
capabilities: [
|
|
181
244
|
{
|
|
182
245
|
kind: 'stylesheet',
|
|
183
|
-
extensions: ['*.css'],
|
|
246
|
+
extensions: ['*.{css,scss,sass,less}'],
|
|
184
247
|
},
|
|
185
248
|
],
|
|
186
249
|
watch: {
|
|
187
250
|
paths: [],
|
|
188
|
-
extensions: [
|
|
251
|
+
extensions: [
|
|
252
|
+
'.css',
|
|
253
|
+
'.scss',
|
|
254
|
+
'.sass',
|
|
255
|
+
'.less',
|
|
256
|
+
'.tsx',
|
|
257
|
+
'.ts',
|
|
258
|
+
'.jsx',
|
|
259
|
+
'.js',
|
|
260
|
+
'.mdx',
|
|
261
|
+
'.html',
|
|
262
|
+
'.svelte',
|
|
263
|
+
'.vue',
|
|
264
|
+
],
|
|
189
265
|
onChange: async ({ path, bridge }) => {
|
|
190
|
-
await this.
|
|
266
|
+
await this.enqueueWatchTask(async () => {
|
|
267
|
+
if (this.matchesFileFilter(path)) {
|
|
268
|
+
await this.handleCssChange(path, bridge);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
await this.handleDependencyChange(bridge);
|
|
273
|
+
});
|
|
274
|
+
},
|
|
275
|
+
onCreate: async ({ path, bridge }) => {
|
|
276
|
+
await this.enqueueWatchTask(async () => {
|
|
277
|
+
if (this.matchesFileFilter(path)) {
|
|
278
|
+
await this.handleCssChange(path, bridge);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
await this.handleDependencyChange(bridge);
|
|
283
|
+
});
|
|
284
|
+
},
|
|
285
|
+
onDelete: async ({ path, bridge }) => {
|
|
286
|
+
await this.enqueueWatchTask(async () => {
|
|
287
|
+
if (this.matchesFileFilter(path)) {
|
|
288
|
+
this.runtimeCssCache.delete(path);
|
|
289
|
+
this.trackedCssFiles.delete(path);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
await this.handleDependencyChange(bridge);
|
|
294
|
+
});
|
|
191
295
|
},
|
|
192
296
|
},
|
|
193
297
|
...config,
|
|
@@ -198,10 +302,17 @@ export class PostCssProcessorPlugin extends Processor<PostCssProcessorPluginConf
|
|
|
198
302
|
* Handles CSS file changes during development.
|
|
199
303
|
* Processes the file and broadcasts a css-update event for hot reloading.
|
|
200
304
|
*/
|
|
201
|
-
private async handleCssChange(filePath: string, bridge: IClientBridge): Promise<void> {
|
|
305
|
+
private async handleCssChange(filePath: string, bridge: IClientBridge, refreshPlugins = true): Promise<void> {
|
|
202
306
|
if (!this.context) return;
|
|
307
|
+
if (!fileSystem.exists(filePath)) return;
|
|
203
308
|
|
|
204
309
|
try {
|
|
310
|
+
this.trackedCssFiles.add(filePath);
|
|
311
|
+
|
|
312
|
+
if (refreshPlugins) {
|
|
313
|
+
this.refreshConfiguredPlugins();
|
|
314
|
+
}
|
|
315
|
+
|
|
205
316
|
let content = await fileSystem.readFile(filePath);
|
|
206
317
|
|
|
207
318
|
if (this.options?.transformInput) {
|
|
@@ -209,6 +320,12 @@ export class PostCssProcessorPlugin extends Processor<PostCssProcessorPluginConf
|
|
|
209
320
|
}
|
|
210
321
|
|
|
211
322
|
const processed = await this.process(content, filePath);
|
|
323
|
+
|
|
324
|
+
const cached = this.runtimeCssCache.get(filePath);
|
|
325
|
+
if (cached === processed) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
212
329
|
this.runtimeCssCache.set(filePath, processed);
|
|
213
330
|
await this.persistProcessedCss(filePath, processed);
|
|
214
331
|
|
|
@@ -262,6 +379,7 @@ export class PostCssProcessorPlugin extends Processor<PostCssProcessorPluginConf
|
|
|
262
379
|
const configExtensions = ['js', 'cjs', 'mjs', 'ts'];
|
|
263
380
|
let foundConfigPath: string | undefined;
|
|
264
381
|
let loadedPlugins: postcss.AcceptedPlugin[] | undefined;
|
|
382
|
+
let loadedPluginFactories: PluginFactoryRecord | undefined;
|
|
265
383
|
|
|
266
384
|
for (const ext of configExtensions) {
|
|
267
385
|
const configPath = path.join(this.context.rootDir, `postcss.config.${ext}`);
|
|
@@ -277,6 +395,13 @@ export class PostCssProcessorPlugin extends Processor<PostCssProcessorPluginConf
|
|
|
277
395
|
|
|
278
396
|
const postcssConfigModule = await import(foundConfigPath);
|
|
279
397
|
const postcssConfig = postcssConfigModule.default || postcssConfigModule;
|
|
398
|
+
if (
|
|
399
|
+
postcssConfig &&
|
|
400
|
+
typeof postcssConfig.pluginFactories === 'object' &&
|
|
401
|
+
postcssConfig.pluginFactories !== null
|
|
402
|
+
) {
|
|
403
|
+
loadedPluginFactories = postcssConfig.pluginFactories as PluginFactoryRecord;
|
|
404
|
+
}
|
|
280
405
|
|
|
281
406
|
if (postcssConfig && typeof postcssConfig.plugins === 'object' && postcssConfig.plugins !== null) {
|
|
282
407
|
if (Array.isArray(postcssConfig.plugins)) {
|
|
@@ -298,16 +423,26 @@ export class PostCssProcessorPlugin extends Processor<PostCssProcessorPluginConf
|
|
|
298
423
|
logger.debug('No PostCSS config file found in root directory.');
|
|
299
424
|
}
|
|
300
425
|
|
|
301
|
-
if (
|
|
426
|
+
if (loadedPluginFactories) {
|
|
427
|
+
this.pluginFactories = loadedPluginFactories;
|
|
428
|
+
this.postcssPlugins = this.materializePluginFactories(loadedPluginFactories);
|
|
429
|
+
} else if (loadedPlugins) {
|
|
430
|
+
this.pluginFactories = undefined;
|
|
302
431
|
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);
|
|
303
436
|
} else if (this.options?.plugins) {
|
|
304
437
|
logger.debug('Using PostCSS plugins provided in processor options.');
|
|
438
|
+
this.pluginFactories = undefined;
|
|
305
439
|
this.postcssPlugins = Object.values(this.options.plugins);
|
|
306
440
|
} else {
|
|
307
441
|
logger.warn(
|
|
308
442
|
'No PostCSS plugins configured. Use a preset like tailwindV3Preset() or tailwindV4Preset(), ' +
|
|
309
443
|
'provide plugins via options, or create a postcss.config file.',
|
|
310
444
|
);
|
|
445
|
+
this.pluginFactories = undefined;
|
|
311
446
|
this.postcssPlugins = [];
|
|
312
447
|
}
|
|
313
448
|
|
|
@@ -12,6 +12,8 @@ import type { PostCssProcessorPluginConfig } from '../plugin.js';
|
|
|
12
12
|
* Features:
|
|
13
13
|
* - Uses classic Tailwind v3 plugin stack
|
|
14
14
|
* - Includes postcss-import, tailwindcss/nesting, tailwindcss, autoprefixer, cssnano
|
|
15
|
+
* - Returns both `plugins` for immediate use and `pluginFactories` so Ecopages
|
|
16
|
+
* can recreate fresh Tailwind/PostCSS plugin instances on dependency-driven rebuilds
|
|
15
17
|
*
|
|
16
18
|
* @example
|
|
17
19
|
* ```typescript
|
|
@@ -9,14 +9,17 @@ function tailwindV3Preset() {
|
|
|
9
9
|
const autoprefixerOptions = browserslistConfig ? {} : {
|
|
10
10
|
overrideBrowserslist: [">0.3%", "not ie 11", "not dead", "not op_mini all"]
|
|
11
11
|
};
|
|
12
|
-
const
|
|
13
|
-
"postcss-import": postcssImport(),
|
|
14
|
-
"tailwindcss/nesting": tailwindcssNesting(),
|
|
15
|
-
tailwindcss: tailwindcss(),
|
|
16
|
-
autoprefixer: autoprefixer(autoprefixerOptions),
|
|
17
|
-
cssnano: cssnano()
|
|
12
|
+
const pluginFactories = {
|
|
13
|
+
"postcss-import": () => postcssImport(),
|
|
14
|
+
"tailwindcss/nesting": () => tailwindcssNesting(),
|
|
15
|
+
tailwindcss: () => tailwindcss(),
|
|
16
|
+
autoprefixer: () => autoprefixer(autoprefixerOptions),
|
|
17
|
+
cssnano: () => cssnano()
|
|
18
18
|
};
|
|
19
|
-
|
|
19
|
+
const plugins = Object.fromEntries(
|
|
20
|
+
Object.entries(pluginFactories).map(([name, factory]) => [name, factory()])
|
|
21
|
+
);
|
|
22
|
+
return { plugins, pluginFactories };
|
|
20
23
|
}
|
|
21
24
|
export {
|
|
22
25
|
tailwindV3Preset
|
|
@@ -13,7 +13,7 @@ import type postcss from 'postcss';
|
|
|
13
13
|
import postcssImport from 'postcss-import';
|
|
14
14
|
import tailwindcss from 'tailwindcss';
|
|
15
15
|
import tailwindcssNesting from 'tailwindcss/nesting/index.js';
|
|
16
|
-
import type { PostCssProcessorPluginConfig } from '../plugin.ts';
|
|
16
|
+
import type { PluginFactoryRecord, PostCssProcessorPluginConfig } from '../plugin.ts';
|
|
17
17
|
|
|
18
18
|
type PluginsRecord = Record<string, postcss.AcceptedPlugin>;
|
|
19
19
|
|
|
@@ -23,6 +23,8 @@ type PluginsRecord = Record<string, postcss.AcceptedPlugin>;
|
|
|
23
23
|
* Features:
|
|
24
24
|
* - Uses classic Tailwind v3 plugin stack
|
|
25
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
|
|
26
28
|
*
|
|
27
29
|
* @example
|
|
28
30
|
* ```typescript
|
|
@@ -49,13 +51,22 @@ export function tailwindV3Preset(): PostCssProcessorPluginConfig {
|
|
|
49
51
|
overrideBrowserslist: ['>0.3%', 'not ie 11', 'not dead', 'not op_mini all'],
|
|
50
52
|
};
|
|
51
53
|
|
|
52
|
-
const
|
|
53
|
-
'postcss-import': postcssImport(),
|
|
54
|
-
'tailwindcss/nesting': tailwindcssNesting(),
|
|
55
|
-
tailwindcss: tailwindcss(),
|
|
56
|
-
autoprefixer: autoprefixer(autoprefixerOptions),
|
|
57
|
-
cssnano: cssnano(),
|
|
54
|
+
const pluginFactories: PluginFactoryRecord = {
|
|
55
|
+
'postcss-import': () => postcssImport(),
|
|
56
|
+
'tailwindcss/nesting': () => tailwindcssNesting(),
|
|
57
|
+
tailwindcss: () => tailwindcss(),
|
|
58
|
+
autoprefixer: () => autoprefixer(autoprefixerOptions),
|
|
59
|
+
cssnano: () => cssnano(),
|
|
58
60
|
};
|
|
59
61
|
|
|
60
|
-
|
|
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 };
|
|
61
72
|
}
|
|
@@ -23,6 +23,8 @@ export interface TailwindV4PresetOptions {
|
|
|
23
23
|
* - Uses `@tailwindcss/postcss` plugin (v4)
|
|
24
24
|
* - Automatically injects `@reference` headers for `@apply` support
|
|
25
25
|
* - Includes cssnano for CSS minification
|
|
26
|
+
* - Returns both `plugins` for immediate use and `pluginFactories` so Ecopages
|
|
27
|
+
* can recreate fresh Tailwind/PostCSS plugin instances on dependency-driven rebuilds
|
|
26
28
|
*
|
|
27
29
|
* @example
|
|
28
30
|
* ```typescript
|
|
@@ -11,14 +11,20 @@ function tailwindV4Preset(options) {
|
|
|
11
11
|
const autoprefixerOptions = browserslistConfig ? {} : {
|
|
12
12
|
overrideBrowserslist: [">0.3%", "not ie 11", "not dead", "not op_mini all"]
|
|
13
13
|
};
|
|
14
|
+
const pluginFactories = {
|
|
15
|
+
"postcss-import": () => postcssImport(),
|
|
16
|
+
"postcss-nested": () => postcssNested(),
|
|
17
|
+
"@tailwindcss/postcss": () => tailwindcss(),
|
|
18
|
+
autoprefixer: () => autoprefixer(autoprefixerOptions),
|
|
19
|
+
cssnano: () => cssnano()
|
|
20
|
+
};
|
|
14
21
|
return {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
},
|
|
22
|
+
/**
|
|
23
|
+
* Instantiate the initial plugin list for the active processor instance.
|
|
24
|
+
* Fresh instances can later be recreated from `pluginFactories`.
|
|
25
|
+
*/
|
|
26
|
+
plugins: Object.fromEntries(Object.entries(pluginFactories).map(([name, factory]) => [name, factory()])),
|
|
27
|
+
pluginFactories,
|
|
22
28
|
transformInput: async (contents, filePath) => {
|
|
23
29
|
const css = contents instanceof Buffer ? contents.toString("utf-8") : contents;
|
|
24
30
|
const normalizedFilePath = path.resolve(filePath);
|
|
@@ -13,7 +13,7 @@ import cssnano from 'cssnano';
|
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import postcssImport from 'postcss-import';
|
|
15
15
|
import postcssNested from 'postcss-nested';
|
|
16
|
-
import type { PostCssProcessorPluginConfig } from '../plugin.ts';
|
|
16
|
+
import type { PluginFactoryRecord, PostCssProcessorPluginConfig } from '../plugin.ts';
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Options for Tailwind v4 preset
|
|
@@ -33,6 +33,8 @@ export interface TailwindV4PresetOptions {
|
|
|
33
33
|
* - Uses `@tailwindcss/postcss` plugin (v4)
|
|
34
34
|
* - Automatically injects `@reference` headers for `@apply` support
|
|
35
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
|
|
36
38
|
*
|
|
37
39
|
* @example
|
|
38
40
|
* ```typescript
|
|
@@ -63,14 +65,21 @@ export function tailwindV4Preset(options: TailwindV4PresetOptions): PostCssProce
|
|
|
63
65
|
overrideBrowserslist: ['>0.3%', 'not ie 11', 'not dead', 'not op_mini all'],
|
|
64
66
|
};
|
|
65
67
|
|
|
68
|
+
const pluginFactories: PluginFactoryRecord = {
|
|
69
|
+
'postcss-import': () => postcssImport(),
|
|
70
|
+
'postcss-nested': () => postcssNested(),
|
|
71
|
+
'@tailwindcss/postcss': () => tailwindcss(),
|
|
72
|
+
autoprefixer: () => autoprefixer(autoprefixerOptions),
|
|
73
|
+
cssnano: () => cssnano(),
|
|
74
|
+
};
|
|
75
|
+
|
|
66
76
|
return {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
},
|
|
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,
|
|
74
83
|
transformInput: async (contents: string | Buffer, filePath: string): Promise<string> => {
|
|
75
84
|
const css = contents instanceof Buffer ? contents.toString('utf-8') : (contents as string);
|
|
76
85
|
const normalizedFilePath = path.resolve(filePath);
|