@ecopages/postcss-processor 0.2.0-alpha.5 → 0.2.0-alpha.50
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/README.md +32 -68
- package/package.json +9 -9
- package/src/index.d.ts +2 -2
- package/src/index.js +2 -2
- package/src/plugin.d.ts +54 -4
- package/src/plugin.js +145 -17
- package/src/presets/index.d.ts +2 -2
- package/src/presets/index.js +2 -2
- package/src/presets/tailwind-v4.js +5 -1
- package/src/runtime/css-loader-plugin.d.ts +2 -2
- package/src/runtime/css-loader-plugin.js +1 -1
- package/src/runtime/css-loader.bun.d.ts +2 -2
- package/src/runtime/css-loader.bun.js +1 -1
- package/CHANGELOG.md +0 -26
- package/src/index.ts +0 -2
- package/src/plugin.ts +0 -489
- package/src/postcss-processor.ts +0 -157
- package/src/presets/index.ts +0 -7
- package/src/presets/tailwind-v3.ts +0 -72
- package/src/presets/tailwind-v4.ts +0 -122
- package/src/runtime/css-loader-plugin.ts +0 -37
- package/src/runtime/css-loader.bun.ts +0 -30
- package/src/runtime/css-runtime-contract.ts +0 -6
package/src/index.ts
DELETED
package/src/plugin.ts
DELETED
|
@@ -1,489 +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 postcssPlugins: postcss.AcceptedPlugin[] = [];
|
|
84
|
-
private pluginFactories?: PluginFactoryRecord;
|
|
85
|
-
private readonly runtimeCssCache = new Map<string, string>();
|
|
86
|
-
private readonly trackedCssFiles = new Set<string>();
|
|
87
|
-
private watchQueue: Promise<void> = Promise.resolve();
|
|
88
|
-
|
|
89
|
-
private getCssFilter(): RegExp {
|
|
90
|
-
return this.options?.filter ?? PostCssProcessorPlugin.DEFAULT_OPTIONS.filter;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
private resolveProcessedCssPath(filePath: string): string | null {
|
|
94
|
-
if (!this.context) {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const relativePath = path.relative(this.context.srcDir, filePath);
|
|
99
|
-
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return path.join(this.context.distDir, 'assets', relativePath);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
private readProcessedCssFromDist(filePath: string): string | null {
|
|
107
|
-
const outputPath = this.resolveProcessedCssPath(filePath);
|
|
108
|
-
if (!outputPath || !fileSystem.exists(outputPath)) {
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return fileSystem.readFileAsBuffer(outputPath).toString('utf-8');
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
private async persistProcessedCss(filePath: string, css: string): Promise<void> {
|
|
116
|
-
const outputPath = this.resolveProcessedCssPath(filePath);
|
|
117
|
-
if (!outputPath) {
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
fileSystem.ensureDir(path.dirname(outputPath));
|
|
122
|
-
fileSystem.write(outputPath, css);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
private async prewarmRuntimeCssCache(): Promise<void> {
|
|
126
|
-
if (!this.context) {
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const sourceFiles = await fileSystem.glob(['**/*.{css,scss,sass,less}'], {
|
|
131
|
-
cwd: this.context.srcDir,
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
for (const relativePath of sourceFiles) {
|
|
135
|
-
const filePath = path.join(this.context.srcDir, relativePath);
|
|
136
|
-
if (!this.matchesFileFilter(filePath)) {
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
this.trackedCssFiles.add(filePath);
|
|
141
|
-
|
|
142
|
-
const rawContents = await fileSystem.readFile(filePath);
|
|
143
|
-
let transformedInput = rawContents;
|
|
144
|
-
|
|
145
|
-
if (this.options?.transformInput) {
|
|
146
|
-
transformedInput = await this.options.transformInput(rawContents, filePath);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const processed = await this.process(transformedInput, filePath);
|
|
150
|
-
this.runtimeCssCache.set(filePath, processed);
|
|
151
|
-
await this.persistProcessedCss(filePath, processed);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
private transformCssSync(input: CssTransformInput): string {
|
|
156
|
-
const cached = this.runtimeCssCache.get(input.filePath);
|
|
157
|
-
if (cached) {
|
|
158
|
-
return cached;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const persisted = this.readProcessedCssFromDist(input.filePath);
|
|
162
|
-
if (persisted) {
|
|
163
|
-
this.runtimeCssCache.set(input.filePath, persisted);
|
|
164
|
-
return persisted;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const { contents } = input;
|
|
168
|
-
return typeof contents === 'string' ? contents : contents.toString('utf-8');
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
private async transformCssAsync(input: CssTransformInput): Promise<string> {
|
|
172
|
-
const { contents, filePath } = input;
|
|
173
|
-
let transformed: string = typeof contents === 'string' ? contents : contents.toString('utf-8');
|
|
174
|
-
|
|
175
|
-
if (this.options?.transformInput) {
|
|
176
|
-
const result = this.options.transformInput(contents, filePath);
|
|
177
|
-
transformed =
|
|
178
|
-
typeof (result as unknown as Record<string, unknown>).then === 'function'
|
|
179
|
-
? await (result as Promise<string>)
|
|
180
|
-
: (result as string);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const processed = await this.process(transformed, filePath);
|
|
184
|
-
this.runtimeCssCache.set(filePath, processed);
|
|
185
|
-
await this.persistProcessedCss(filePath, processed);
|
|
186
|
-
return processed;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
override matchesFileFilter(filepath: string): boolean {
|
|
190
|
-
const filter = this.options?.filter ?? PostCssProcessorPlugin.DEFAULT_OPTIONS.filter;
|
|
191
|
-
return filter.test(filepath);
|
|
192
|
-
}
|
|
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
|
-
|
|
235
|
-
constructor(
|
|
236
|
-
config: Omit<ProcessorConfig<PostCssProcessorPluginConfig>, 'name' | 'description'> = {
|
|
237
|
-
options: PostCssProcessorPlugin.DEFAULT_OPTIONS,
|
|
238
|
-
},
|
|
239
|
-
) {
|
|
240
|
-
super({
|
|
241
|
-
name: 'ecopages-postcss-processor',
|
|
242
|
-
description: 'A Processor for transforming CSS files using PostCSS.',
|
|
243
|
-
capabilities: [
|
|
244
|
-
{
|
|
245
|
-
kind: 'stylesheet',
|
|
246
|
-
extensions: ['*.{css,scss,sass,less}'],
|
|
247
|
-
},
|
|
248
|
-
],
|
|
249
|
-
watch: {
|
|
250
|
-
paths: [],
|
|
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
|
-
],
|
|
265
|
-
onChange: async ({ path, bridge }) => {
|
|
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
|
-
});
|
|
295
|
-
},
|
|
296
|
-
},
|
|
297
|
-
...config,
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Handles CSS file changes during development.
|
|
303
|
-
* Processes the file and broadcasts a css-update event for hot reloading.
|
|
304
|
-
*/
|
|
305
|
-
private async handleCssChange(filePath: string, bridge: IClientBridge, refreshPlugins = true): Promise<void> {
|
|
306
|
-
if (!this.context) return;
|
|
307
|
-
if (!fileSystem.exists(filePath)) return;
|
|
308
|
-
|
|
309
|
-
try {
|
|
310
|
-
this.trackedCssFiles.add(filePath);
|
|
311
|
-
|
|
312
|
-
if (refreshPlugins) {
|
|
313
|
-
this.refreshConfiguredPlugins();
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
let content = await fileSystem.readFile(filePath);
|
|
317
|
-
|
|
318
|
-
if (this.options?.transformInput) {
|
|
319
|
-
content = await this.options.transformInput(content, filePath);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const processed = await this.process(content, filePath);
|
|
323
|
-
|
|
324
|
-
const cached = this.runtimeCssCache.get(filePath);
|
|
325
|
-
if (cached === processed) {
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
this.runtimeCssCache.set(filePath, processed);
|
|
330
|
-
await this.persistProcessedCss(filePath, processed);
|
|
331
|
-
|
|
332
|
-
bridge.cssUpdate(filePath);
|
|
333
|
-
|
|
334
|
-
logger.debug(`Processed CSS: ${filePath}`);
|
|
335
|
-
} catch (error) {
|
|
336
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
337
|
-
logger.error(`Failed to process CSS: ${filePath}`, errorMessage);
|
|
338
|
-
bridge.error(errorMessage);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
get buildPlugins(): EcoBuildPlugin[] {
|
|
343
|
-
return [
|
|
344
|
-
createCssLoaderPlugin({
|
|
345
|
-
name: 'postcss-processor-build-loader',
|
|
346
|
-
filter: this.getCssFilter(),
|
|
347
|
-
transform: this.transformCssAsync.bind(this),
|
|
348
|
-
}),
|
|
349
|
-
];
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
get plugins(): EcoBuildPlugin[] {
|
|
353
|
-
return [
|
|
354
|
-
createCssLoaderPlugin({
|
|
355
|
-
name: 'postcss-processor-runtime-loader',
|
|
356
|
-
filter: this.getCssFilter(),
|
|
357
|
-
transform: this.transformCssSync.bind(this),
|
|
358
|
-
}),
|
|
359
|
-
];
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* Setup the PostCSS processor.
|
|
364
|
-
*/
|
|
365
|
-
async setup(): Promise<void> {
|
|
366
|
-
await this.collectPostcssPlugins();
|
|
367
|
-
await this.prewarmRuntimeCssCache();
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Get the PostCSS plugins from the options or a config file.
|
|
372
|
-
* Searches for postcss.config.{js,cjs,mjs,ts} in the root directory.
|
|
373
|
-
*/
|
|
374
|
-
private async collectPostcssPlugins(): Promise<void> {
|
|
375
|
-
if (!this.context) {
|
|
376
|
-
throw new Error('Context must be set');
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
const configExtensions = ['js', 'cjs', 'mjs', 'ts'];
|
|
380
|
-
let foundConfigPath: string | undefined;
|
|
381
|
-
let loadedPlugins: postcss.AcceptedPlugin[] | undefined;
|
|
382
|
-
let loadedPluginFactories: PluginFactoryRecord | undefined;
|
|
383
|
-
|
|
384
|
-
for (const ext of configExtensions) {
|
|
385
|
-
const configPath = path.join(this.context.rootDir, `postcss.config.${ext}`);
|
|
386
|
-
if (fileSystem.exists(configPath)) {
|
|
387
|
-
foundConfigPath = configPath;
|
|
388
|
-
break;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
if (foundConfigPath) {
|
|
393
|
-
try {
|
|
394
|
-
logger.debug(`Loading PostCSS config from: ${foundConfigPath}`);
|
|
395
|
-
|
|
396
|
-
const postcssConfigModule = await import(foundConfigPath);
|
|
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
|
-
}
|
|
405
|
-
|
|
406
|
-
if (postcssConfig && typeof postcssConfig.plugins === 'object' && postcssConfig.plugins !== null) {
|
|
407
|
-
if (Array.isArray(postcssConfig.plugins)) {
|
|
408
|
-
loadedPlugins = postcssConfig.plugins;
|
|
409
|
-
} else {
|
|
410
|
-
loadedPlugins = Object.values(postcssConfig.plugins as PluginsRecord);
|
|
411
|
-
}
|
|
412
|
-
logger.debug(`Successfully loaded ${loadedPlugins?.length ?? 0} plugins from config file.`);
|
|
413
|
-
} else {
|
|
414
|
-
logger.warn(
|
|
415
|
-
`PostCSS config file found (${foundConfigPath}), but no valid 'plugins' export detected.`,
|
|
416
|
-
);
|
|
417
|
-
}
|
|
418
|
-
} catch (error: any) {
|
|
419
|
-
logger.error(`Error loading PostCSS config from ${foundConfigPath}: ${error.message}`, error);
|
|
420
|
-
loadedPlugins = undefined;
|
|
421
|
-
}
|
|
422
|
-
} else {
|
|
423
|
-
logger.debug('No PostCSS config file found in root directory.');
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
if (loadedPluginFactories) {
|
|
427
|
-
this.pluginFactories = loadedPluginFactories;
|
|
428
|
-
this.postcssPlugins = this.materializePluginFactories(loadedPluginFactories);
|
|
429
|
-
} else if (loadedPlugins) {
|
|
430
|
-
this.pluginFactories = undefined;
|
|
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);
|
|
436
|
-
} else if (this.options?.plugins) {
|
|
437
|
-
logger.debug('Using PostCSS plugins provided in processor options.');
|
|
438
|
-
this.pluginFactories = undefined;
|
|
439
|
-
this.postcssPlugins = Object.values(this.options.plugins);
|
|
440
|
-
} else {
|
|
441
|
-
logger.warn(
|
|
442
|
-
'No PostCSS plugins configured. Use a preset like tailwindV3Preset() or tailwindV4Preset(), ' +
|
|
443
|
-
'provide plugins via options, or create a postcss.config file.',
|
|
444
|
-
);
|
|
445
|
-
this.pluginFactories = undefined;
|
|
446
|
-
this.postcssPlugins = [];
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
if (!this.postcssPlugins || this.postcssPlugins.length === 0) {
|
|
450
|
-
logger.warn('No PostCSS plugins configured or loaded. CSS processing might be minimal.');
|
|
451
|
-
this.postcssPlugins = [];
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
/**
|
|
456
|
-
* Process CSS content
|
|
457
|
-
* @param fileAsString CSS content as string
|
|
458
|
-
* @param filePath Optional file path for resolving relative imports
|
|
459
|
-
* @returns Processed CSS
|
|
460
|
-
*/
|
|
461
|
-
async process(fileAsString: string, filePath?: string): Promise<string> {
|
|
462
|
-
return await PostCssProcessor.processStringOrBuffer(fileAsString, {
|
|
463
|
-
filePath,
|
|
464
|
-
plugins: this.postcssPlugins,
|
|
465
|
-
transformOutput: this.options?.transformOutput,
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
processSync(fileAsString: string, filePath?: string): string {
|
|
470
|
-
return PostCssProcessor.processStringOrBufferSync(fileAsString, {
|
|
471
|
-
filePath,
|
|
472
|
-
plugins: this.postcssPlugins,
|
|
473
|
-
transformOutput: this.options?.transformOutput,
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
/**
|
|
478
|
-
* Teardown the PostCSS processor.
|
|
479
|
-
*/
|
|
480
|
-
async teardown(): Promise<void> {
|
|
481
|
-
logger.debug('Tearing down PostCSS processor');
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
export const postcssProcessorPlugin = (config?: PostCssProcessorPluginConfig): PostCssProcessorPlugin => {
|
|
486
|
-
return new PostCssProcessorPlugin({
|
|
487
|
-
options: config,
|
|
488
|
-
});
|
|
489
|
-
};
|
package/src/postcss-processor.ts
DELETED
|
@@ -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
|
-
};
|