@ecopages/postcss-processor 0.2.0-alpha.1 → 0.2.0-alpha.3
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 +26 -0
- package/package.json +38 -32
- package/src/index.d.ts +2 -0
- package/src/index.js +2 -0
- package/src/plugin.d.ts +112 -0
- package/src/plugin.js +354 -0
- package/src/plugin.ts +140 -5
- package/src/postcss-processor.d.ts +48 -0
- package/src/postcss-processor.js +69 -0
- package/src/presets/index.d.ts +6 -0
- package/src/presets/index.js +6 -0
- package/src/presets/tailwind-v3.d.ts +34 -0
- package/src/presets/tailwind-v3.js +26 -0
- package/src/presets/tailwind-v3.ts +19 -8
- package/src/presets/tailwind-v4.d.ts +47 -0
- package/src/presets/tailwind-v4.js +57 -0
- package/src/presets/tailwind-v4.ts +17 -8
- package/src/runtime/css-loader-plugin.d.ts +9 -0
- package/src/runtime/css-loader-plugin.js +28 -0
- package/src/runtime/css-loader.bun.d.ts +9 -0
- package/src/runtime/css-loader.bun.js +22 -0
- package/src/runtime/css-runtime-contract.d.ts +5 -0
- package/src/runtime/css-runtime-contract.js +0 -0
- package/src/test/css/base.css +0 -3
- package/src/test/css/correct.css +0 -3
- package/src/test/css/error.css +0 -3
- package/src/test/css/external-plugins.css +0 -13
- package/src/test/css/import.css +0 -4
- package/src/test/plugin.test.ts +0 -74
- package/src/test/postcss-processor.test.ts +0 -106
- package/src/test/presets.test.ts +0 -140
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@ecopages/postcss-processor` are documented here.
|
|
4
|
+
|
|
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
|
+
|
|
7
|
+
## [UNRELEASED] — TBD
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
- Rebuilt tracked stylesheets from fresh PostCSS plugin instances on non-CSS source changes so Tailwind utility generation updates without stale caches or forced reloads.
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
- **Runtime CSS loaders** — Added `css-loader-plugin.ts`, `css-loader.bun.ts`, and `css-runtime-contract.ts` to support runtime CSS loading. CSS is now cached at runtime to avoid redundant processing during HMR (`cbaafea4`, `e7653c9b`).
|
|
16
|
+
- **`PostcssProcessor` class** — New `postcss-processor.ts` exposes a programmatic API for the processor separate from the plugin DSL.
|
|
17
|
+
|
|
18
|
+
### Refactoring
|
|
19
|
+
|
|
20
|
+
- `plugin.ts` significantly overhauled to integrate with the new build adapter and support build dependency graph registration (`e7653c9b`).
|
|
21
|
+
- Test suite updated for esbuild adapter and Node runtime compatibility (`31a44458`).
|
|
22
|
+
- Removed unused `@types/postcss` dev dependency.
|
|
23
|
+
|
|
24
|
+
### Tests
|
|
25
|
+
|
|
26
|
+
- Updated `plugin.test.ts`, `postcss-processor.test.ts`, and `presets.test.ts` for new plugin contract.
|
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.3",
|
|
4
4
|
"description": "Postcss processor, transform string or postcss file to css",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"postcss",
|
|
@@ -8,24 +8,17 @@
|
|
|
8
8
|
"css"
|
|
9
9
|
],
|
|
10
10
|
"license": "MIT",
|
|
11
|
-
"main": "./src/postcss-processor.
|
|
11
|
+
"main": "./src/postcss-processor.js",
|
|
12
12
|
"type": "module",
|
|
13
|
-
"types": "./src/postcss-processor.ts",
|
|
14
|
-
"files": [
|
|
15
|
-
"src"
|
|
16
|
-
],
|
|
13
|
+
"types": "./src/postcss-processor.d.ts",
|
|
17
14
|
"repository": {
|
|
18
15
|
"type": "git",
|
|
19
16
|
"url": "https://github.com/ecopages/ecopages.git",
|
|
20
17
|
"directory": "packages/processors/postcss-processor"
|
|
21
18
|
},
|
|
22
|
-
"scripts": {
|
|
23
|
-
"typecheck": "tsc --noEmit",
|
|
24
|
-
"release:jsr": "bunx jsr publish"
|
|
25
|
-
},
|
|
26
19
|
"dependencies": {
|
|
27
|
-
"@ecopages/core": "
|
|
28
|
-
"@ecopages/file-system": "
|
|
20
|
+
"@ecopages/core": "0.2.0-alpha.3",
|
|
21
|
+
"@ecopages/file-system": "0.2.0-alpha.3",
|
|
29
22
|
"@ecopages/logger": "latest",
|
|
30
23
|
"autoprefixer": "^10.4.0",
|
|
31
24
|
"browserslist": "^4.28.1",
|
|
@@ -46,37 +39,50 @@
|
|
|
46
39
|
"optional": true
|
|
47
40
|
}
|
|
48
41
|
},
|
|
49
|
-
"devDependencies": {
|
|
50
|
-
"@tailwindcss/postcss": "^4.1.18",
|
|
51
|
-
"@types/bun": "latest",
|
|
52
|
-
"@types/postcss-import": "^14",
|
|
53
|
-
"postcss-simple-vars": "^7.0.1",
|
|
54
|
-
"tailwindcss": "^3.4.19"
|
|
55
|
-
},
|
|
56
42
|
"exports": {
|
|
57
43
|
".": {
|
|
58
|
-
"default": "./src/index.
|
|
59
|
-
"types": "./src/index.ts"
|
|
44
|
+
"default": "./src/index.js",
|
|
45
|
+
"types": "./src/index.d.ts"
|
|
60
46
|
},
|
|
61
47
|
"./postcss-processor": {
|
|
62
|
-
"default": "./src/postcss-processor.
|
|
63
|
-
"types": "./src/postcss-processor.ts"
|
|
48
|
+
"default": "./src/postcss-processor.js",
|
|
49
|
+
"types": "./src/postcss-processor.d.ts"
|
|
64
50
|
},
|
|
65
51
|
"./plugin": {
|
|
66
|
-
"default": "./src/plugin.
|
|
67
|
-
"types": "./src/plugin.ts"
|
|
52
|
+
"default": "./src/plugin.js",
|
|
53
|
+
"types": "./src/plugin.d.ts"
|
|
68
54
|
},
|
|
69
55
|
"./presets": {
|
|
70
|
-
"default": "./src/presets/index.
|
|
71
|
-
"types": "./src/presets/index.ts"
|
|
56
|
+
"default": "./src/presets/index.js",
|
|
57
|
+
"types": "./src/presets/index.d.ts"
|
|
72
58
|
},
|
|
73
59
|
"./presets/tailwind-v3": {
|
|
74
|
-
"default": "./src/presets/tailwind-v3.
|
|
75
|
-
"types": "./src/presets/tailwind-v3.ts"
|
|
60
|
+
"default": "./src/presets/tailwind-v3.js",
|
|
61
|
+
"types": "./src/presets/tailwind-v3.d.ts"
|
|
76
62
|
},
|
|
77
63
|
"./presets/tailwind-v4": {
|
|
78
|
-
"default": "./src/presets/tailwind-v4.
|
|
79
|
-
"types": "./src/presets/tailwind-v4.ts"
|
|
64
|
+
"default": "./src/presets/tailwind-v4.js",
|
|
65
|
+
"types": "./src/presets/tailwind-v4.d.ts"
|
|
66
|
+
},
|
|
67
|
+
"./postcss-processor.ts": {
|
|
68
|
+
"default": "./src/postcss-processor.js",
|
|
69
|
+
"types": "./src/postcss-processor.d.ts"
|
|
70
|
+
},
|
|
71
|
+
"./plugin.ts": {
|
|
72
|
+
"default": "./src/plugin.js",
|
|
73
|
+
"types": "./src/plugin.d.ts"
|
|
74
|
+
},
|
|
75
|
+
"./presets.ts": {
|
|
76
|
+
"default": "./src/presets/index.js",
|
|
77
|
+
"types": "./src/presets/index.d.ts"
|
|
78
|
+
},
|
|
79
|
+
"./presets/tailwind-v3.ts": {
|
|
80
|
+
"default": "./src/presets/tailwind-v3.js",
|
|
81
|
+
"types": "./src/presets/tailwind-v3.d.ts"
|
|
82
|
+
},
|
|
83
|
+
"./presets/tailwind-v4.ts": {
|
|
84
|
+
"default": "./src/presets/tailwind-v4.js",
|
|
85
|
+
"types": "./src/presets/tailwind-v4.d.ts"
|
|
80
86
|
}
|
|
81
87
|
}
|
|
82
|
-
}
|
|
88
|
+
}
|
package/src/index.d.ts
ADDED
package/src/index.js
ADDED
package/src/plugin.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostCssProcessorPlugin
|
|
3
|
+
* @module @ecopages/postcss-processor
|
|
4
|
+
*/
|
|
5
|
+
import { Processor, type ProcessorConfig } from '@ecopages/core/plugins/processor';
|
|
6
|
+
import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
|
|
7
|
+
import type postcss from 'postcss';
|
|
8
|
+
/**
|
|
9
|
+
* Record of PostCSS plugins keyed by name
|
|
10
|
+
*/
|
|
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>;
|
|
21
|
+
/**
|
|
22
|
+
* Configuration for the PostCSS processor
|
|
23
|
+
*/
|
|
24
|
+
export interface PostCssProcessorPluginConfig {
|
|
25
|
+
/**
|
|
26
|
+
* Regex filter to match files to process
|
|
27
|
+
*/
|
|
28
|
+
filter?: RegExp;
|
|
29
|
+
/**
|
|
30
|
+
* Function to transform the contents of the file.
|
|
31
|
+
* It can be handy to add a custom header or footer to the file.
|
|
32
|
+
* Useful for injecting Tailwind v4 `@reference` directives.
|
|
33
|
+
* @param contents The contents of the file
|
|
34
|
+
* @param filePath The absolute path to the CSS file being processed
|
|
35
|
+
* @returns The transformed contents
|
|
36
|
+
*/
|
|
37
|
+
transformInput?: (contents: string | Buffer, filePath: string) => string | Promise<string>;
|
|
38
|
+
/**
|
|
39
|
+
* Function to transform the output CSS after PostCSS processing.
|
|
40
|
+
* It can be handy to add a custom header or footer to the processed CSS.
|
|
41
|
+
* @param css The processed CSS
|
|
42
|
+
* @returns The transformed CSS
|
|
43
|
+
*/
|
|
44
|
+
transformOutput?: (css: string) => Promise<string> | string;
|
|
45
|
+
/**
|
|
46
|
+
* Custom PostCSS plugins to use instead of the default ones
|
|
47
|
+
* @default undefined (uses default plugins)
|
|
48
|
+
*/
|
|
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;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* PostCssProcessorPlugin
|
|
60
|
+
* A Processor for transforming CSS files.
|
|
61
|
+
*/
|
|
62
|
+
export declare class PostCssProcessorPlugin extends Processor<PostCssProcessorPluginConfig> {
|
|
63
|
+
static DEFAULT_OPTIONS: Required<Pick<PostCssProcessorPluginConfig, 'filter'>>;
|
|
64
|
+
private postcssPlugins;
|
|
65
|
+
private pluginFactories?;
|
|
66
|
+
private readonly runtimeCssCache;
|
|
67
|
+
private readonly trackedCssFiles;
|
|
68
|
+
private watchQueue;
|
|
69
|
+
private getCssFilter;
|
|
70
|
+
private resolveProcessedCssPath;
|
|
71
|
+
private readProcessedCssFromDist;
|
|
72
|
+
private persistProcessedCss;
|
|
73
|
+
private prewarmRuntimeCssCache;
|
|
74
|
+
private transformCssSync;
|
|
75
|
+
private transformCssAsync;
|
|
76
|
+
matchesFileFilter(filepath: string): boolean;
|
|
77
|
+
private materializePluginFactories;
|
|
78
|
+
private refreshConfiguredPlugins;
|
|
79
|
+
private enqueueWatchTask;
|
|
80
|
+
private getTrackedCssFiles;
|
|
81
|
+
private handleDependencyChange;
|
|
82
|
+
constructor(config?: Omit<ProcessorConfig<PostCssProcessorPluginConfig>, 'name' | 'description'>);
|
|
83
|
+
/**
|
|
84
|
+
* Handles CSS file changes during development.
|
|
85
|
+
* Processes the file and broadcasts a css-update event for hot reloading.
|
|
86
|
+
*/
|
|
87
|
+
private handleCssChange;
|
|
88
|
+
get buildPlugins(): EcoBuildPlugin[];
|
|
89
|
+
get plugins(): EcoBuildPlugin[];
|
|
90
|
+
/**
|
|
91
|
+
* Setup the PostCSS processor.
|
|
92
|
+
*/
|
|
93
|
+
setup(): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Get the PostCSS plugins from the options or a config file.
|
|
96
|
+
* Searches for postcss.config.{js,cjs,mjs,ts} in the root directory.
|
|
97
|
+
*/
|
|
98
|
+
private collectPostcssPlugins;
|
|
99
|
+
/**
|
|
100
|
+
* Process CSS content
|
|
101
|
+
* @param fileAsString CSS content as string
|
|
102
|
+
* @param filePath Optional file path for resolving relative imports
|
|
103
|
+
* @returns Processed CSS
|
|
104
|
+
*/
|
|
105
|
+
process(fileAsString: string, filePath?: string): Promise<string>;
|
|
106
|
+
processSync(fileAsString: string, filePath?: string): string;
|
|
107
|
+
/**
|
|
108
|
+
* Teardown the PostCSS processor.
|
|
109
|
+
*/
|
|
110
|
+
teardown(): Promise<void>;
|
|
111
|
+
}
|
|
112
|
+
export declare const postcssProcessorPlugin: (config?: PostCssProcessorPluginConfig) => PostCssProcessorPlugin;
|
package/src/plugin.js
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileSystem } from "@ecopages/file-system";
|
|
3
|
+
import { Processor } from "@ecopages/core/plugins/processor";
|
|
4
|
+
import { Logger } from "@ecopages/logger";
|
|
5
|
+
import { PostCssProcessor } from "./postcss-processor";
|
|
6
|
+
import { createCssLoaderPlugin } from "./runtime/css-loader-plugin";
|
|
7
|
+
const logger = new Logger("[@ecopages/postcss-processor]", {
|
|
8
|
+
debug: process.env.ECOPAGES_LOGGER_DEBUG === "true"
|
|
9
|
+
});
|
|
10
|
+
class PostCssProcessorPlugin extends Processor {
|
|
11
|
+
static DEFAULT_OPTIONS = {
|
|
12
|
+
filter: /\.css$/
|
|
13
|
+
};
|
|
14
|
+
postcssPlugins = [];
|
|
15
|
+
pluginFactories;
|
|
16
|
+
runtimeCssCache = /* @__PURE__ */ new Map();
|
|
17
|
+
trackedCssFiles = /* @__PURE__ */ new Set();
|
|
18
|
+
watchQueue = Promise.resolve();
|
|
19
|
+
getCssFilter() {
|
|
20
|
+
return this.options?.filter ?? PostCssProcessorPlugin.DEFAULT_OPTIONS.filter;
|
|
21
|
+
}
|
|
22
|
+
resolveProcessedCssPath(filePath) {
|
|
23
|
+
if (!this.context) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const relativePath = path.relative(this.context.srcDir, filePath);
|
|
27
|
+
if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return path.join(this.context.distDir, "assets", relativePath);
|
|
31
|
+
}
|
|
32
|
+
readProcessedCssFromDist(filePath) {
|
|
33
|
+
const outputPath = this.resolveProcessedCssPath(filePath);
|
|
34
|
+
if (!outputPath || !fileSystem.exists(outputPath)) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return fileSystem.readFileAsBuffer(outputPath).toString("utf-8");
|
|
38
|
+
}
|
|
39
|
+
async persistProcessedCss(filePath, css) {
|
|
40
|
+
const outputPath = this.resolveProcessedCssPath(filePath);
|
|
41
|
+
if (!outputPath) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
fileSystem.ensureDir(path.dirname(outputPath));
|
|
45
|
+
fileSystem.write(outputPath, css);
|
|
46
|
+
}
|
|
47
|
+
async prewarmRuntimeCssCache() {
|
|
48
|
+
if (!this.context) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const sourceFiles = await fileSystem.glob(["**/*.{css,scss,sass,less}"], {
|
|
52
|
+
cwd: this.context.srcDir
|
|
53
|
+
});
|
|
54
|
+
for (const relativePath of sourceFiles) {
|
|
55
|
+
const filePath = path.join(this.context.srcDir, relativePath);
|
|
56
|
+
if (!this.matchesFileFilter(filePath)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
this.trackedCssFiles.add(filePath);
|
|
60
|
+
const rawContents = await fileSystem.readFile(filePath);
|
|
61
|
+
let transformedInput = rawContents;
|
|
62
|
+
if (this.options?.transformInput) {
|
|
63
|
+
transformedInput = await this.options.transformInput(rawContents, filePath);
|
|
64
|
+
}
|
|
65
|
+
const processed = await this.process(transformedInput, filePath);
|
|
66
|
+
this.runtimeCssCache.set(filePath, processed);
|
|
67
|
+
await this.persistProcessedCss(filePath, processed);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
transformCssSync(input) {
|
|
71
|
+
const cached = this.runtimeCssCache.get(input.filePath);
|
|
72
|
+
if (cached) {
|
|
73
|
+
return cached;
|
|
74
|
+
}
|
|
75
|
+
const persisted = this.readProcessedCssFromDist(input.filePath);
|
|
76
|
+
if (persisted) {
|
|
77
|
+
this.runtimeCssCache.set(input.filePath, persisted);
|
|
78
|
+
return persisted;
|
|
79
|
+
}
|
|
80
|
+
const { contents } = input;
|
|
81
|
+
return typeof contents === "string" ? contents : contents.toString("utf-8");
|
|
82
|
+
}
|
|
83
|
+
async transformCssAsync(input) {
|
|
84
|
+
const { contents, filePath } = input;
|
|
85
|
+
let transformed = typeof contents === "string" ? contents : contents.toString("utf-8");
|
|
86
|
+
if (this.options?.transformInput) {
|
|
87
|
+
const result = this.options.transformInput(contents, filePath);
|
|
88
|
+
transformed = typeof result.then === "function" ? await result : result;
|
|
89
|
+
}
|
|
90
|
+
const processed = await this.process(transformed, filePath);
|
|
91
|
+
this.runtimeCssCache.set(filePath, processed);
|
|
92
|
+
await this.persistProcessedCss(filePath, processed);
|
|
93
|
+
return processed;
|
|
94
|
+
}
|
|
95
|
+
matchesFileFilter(filepath) {
|
|
96
|
+
const filter = this.options?.filter ?? PostCssProcessorPlugin.DEFAULT_OPTIONS.filter;
|
|
97
|
+
return filter.test(filepath);
|
|
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
|
+
}
|
|
131
|
+
constructor(config = {
|
|
132
|
+
options: PostCssProcessorPlugin.DEFAULT_OPTIONS
|
|
133
|
+
}) {
|
|
134
|
+
super({
|
|
135
|
+
name: "ecopages-postcss-processor",
|
|
136
|
+
description: "A Processor for transforming CSS files using PostCSS.",
|
|
137
|
+
capabilities: [
|
|
138
|
+
{
|
|
139
|
+
kind: "stylesheet",
|
|
140
|
+
extensions: ["*.{css,scss,sass,less}"]
|
|
141
|
+
}
|
|
142
|
+
],
|
|
143
|
+
watch: {
|
|
144
|
+
paths: [],
|
|
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
|
+
],
|
|
159
|
+
onChange: async ({ path: path2, bridge }) => {
|
|
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
|
+
});
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
...config
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Handles CSS file changes during development.
|
|
193
|
+
* Processes the file and broadcasts a css-update event for hot reloading.
|
|
194
|
+
*/
|
|
195
|
+
async handleCssChange(filePath, bridge, refreshPlugins = true) {
|
|
196
|
+
if (!this.context) return;
|
|
197
|
+
if (!fileSystem.exists(filePath)) return;
|
|
198
|
+
try {
|
|
199
|
+
this.trackedCssFiles.add(filePath);
|
|
200
|
+
if (refreshPlugins) {
|
|
201
|
+
this.refreshConfiguredPlugins();
|
|
202
|
+
}
|
|
203
|
+
let content = await fileSystem.readFile(filePath);
|
|
204
|
+
if (this.options?.transformInput) {
|
|
205
|
+
content = await this.options.transformInput(content, filePath);
|
|
206
|
+
}
|
|
207
|
+
const processed = await this.process(content, filePath);
|
|
208
|
+
const cached = this.runtimeCssCache.get(filePath);
|
|
209
|
+
if (cached === processed) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
this.runtimeCssCache.set(filePath, processed);
|
|
213
|
+
await this.persistProcessedCss(filePath, processed);
|
|
214
|
+
bridge.cssUpdate(filePath);
|
|
215
|
+
logger.debug(`Processed CSS: ${filePath}`);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
218
|
+
logger.error(`Failed to process CSS: ${filePath}`, errorMessage);
|
|
219
|
+
bridge.error(errorMessage);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
get buildPlugins() {
|
|
223
|
+
return [
|
|
224
|
+
createCssLoaderPlugin({
|
|
225
|
+
name: "postcss-processor-build-loader",
|
|
226
|
+
filter: this.getCssFilter(),
|
|
227
|
+
transform: this.transformCssAsync.bind(this)
|
|
228
|
+
})
|
|
229
|
+
];
|
|
230
|
+
}
|
|
231
|
+
get plugins() {
|
|
232
|
+
return [
|
|
233
|
+
createCssLoaderPlugin({
|
|
234
|
+
name: "postcss-processor-runtime-loader",
|
|
235
|
+
filter: this.getCssFilter(),
|
|
236
|
+
transform: this.transformCssSync.bind(this)
|
|
237
|
+
})
|
|
238
|
+
];
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Setup the PostCSS processor.
|
|
242
|
+
*/
|
|
243
|
+
async setup() {
|
|
244
|
+
await this.collectPostcssPlugins();
|
|
245
|
+
await this.prewarmRuntimeCssCache();
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Get the PostCSS plugins from the options or a config file.
|
|
249
|
+
* Searches for postcss.config.{js,cjs,mjs,ts} in the root directory.
|
|
250
|
+
*/
|
|
251
|
+
async collectPostcssPlugins() {
|
|
252
|
+
if (!this.context) {
|
|
253
|
+
throw new Error("Context must be set");
|
|
254
|
+
}
|
|
255
|
+
const configExtensions = ["js", "cjs", "mjs", "ts"];
|
|
256
|
+
let foundConfigPath;
|
|
257
|
+
let loadedPlugins;
|
|
258
|
+
let loadedPluginFactories;
|
|
259
|
+
for (const ext of configExtensions) {
|
|
260
|
+
const configPath = path.join(this.context.rootDir, `postcss.config.${ext}`);
|
|
261
|
+
if (fileSystem.exists(configPath)) {
|
|
262
|
+
foundConfigPath = configPath;
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (foundConfigPath) {
|
|
267
|
+
try {
|
|
268
|
+
logger.debug(`Loading PostCSS config from: ${foundConfigPath}`);
|
|
269
|
+
const postcssConfigModule = await import(foundConfigPath);
|
|
270
|
+
const postcssConfig = postcssConfigModule.default || postcssConfigModule;
|
|
271
|
+
if (postcssConfig && typeof postcssConfig.pluginFactories === "object" && postcssConfig.pluginFactories !== null) {
|
|
272
|
+
loadedPluginFactories = postcssConfig.pluginFactories;
|
|
273
|
+
}
|
|
274
|
+
if (postcssConfig && typeof postcssConfig.plugins === "object" && postcssConfig.plugins !== null) {
|
|
275
|
+
if (Array.isArray(postcssConfig.plugins)) {
|
|
276
|
+
loadedPlugins = postcssConfig.plugins;
|
|
277
|
+
} else {
|
|
278
|
+
loadedPlugins = Object.values(postcssConfig.plugins);
|
|
279
|
+
}
|
|
280
|
+
logger.debug(`Successfully loaded ${loadedPlugins?.length ?? 0} plugins from config file.`);
|
|
281
|
+
} else {
|
|
282
|
+
logger.warn(
|
|
283
|
+
`PostCSS config file found (${foundConfigPath}), but no valid 'plugins' export detected.`
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
} catch (error) {
|
|
287
|
+
logger.error(`Error loading PostCSS config from ${foundConfigPath}: ${error.message}`, error);
|
|
288
|
+
loadedPlugins = void 0;
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
logger.debug("No PostCSS config file found in root directory.");
|
|
292
|
+
}
|
|
293
|
+
if (loadedPluginFactories) {
|
|
294
|
+
this.pluginFactories = loadedPluginFactories;
|
|
295
|
+
this.postcssPlugins = this.materializePluginFactories(loadedPluginFactories);
|
|
296
|
+
} else if (loadedPlugins) {
|
|
297
|
+
this.pluginFactories = void 0;
|
|
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);
|
|
303
|
+
} else if (this.options?.plugins) {
|
|
304
|
+
logger.debug("Using PostCSS plugins provided in processor options.");
|
|
305
|
+
this.pluginFactories = void 0;
|
|
306
|
+
this.postcssPlugins = Object.values(this.options.plugins);
|
|
307
|
+
} else {
|
|
308
|
+
logger.warn(
|
|
309
|
+
"No PostCSS plugins configured. Use a preset like tailwindV3Preset() or tailwindV4Preset(), provide plugins via options, or create a postcss.config file."
|
|
310
|
+
);
|
|
311
|
+
this.pluginFactories = void 0;
|
|
312
|
+
this.postcssPlugins = [];
|
|
313
|
+
}
|
|
314
|
+
if (!this.postcssPlugins || this.postcssPlugins.length === 0) {
|
|
315
|
+
logger.warn("No PostCSS plugins configured or loaded. CSS processing might be minimal.");
|
|
316
|
+
this.postcssPlugins = [];
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Process CSS content
|
|
321
|
+
* @param fileAsString CSS content as string
|
|
322
|
+
* @param filePath Optional file path for resolving relative imports
|
|
323
|
+
* @returns Processed CSS
|
|
324
|
+
*/
|
|
325
|
+
async process(fileAsString, filePath) {
|
|
326
|
+
return await PostCssProcessor.processStringOrBuffer(fileAsString, {
|
|
327
|
+
filePath,
|
|
328
|
+
plugins: this.postcssPlugins,
|
|
329
|
+
transformOutput: this.options?.transformOutput
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
processSync(fileAsString, filePath) {
|
|
333
|
+
return PostCssProcessor.processStringOrBufferSync(fileAsString, {
|
|
334
|
+
filePath,
|
|
335
|
+
plugins: this.postcssPlugins,
|
|
336
|
+
transformOutput: this.options?.transformOutput
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Teardown the PostCSS processor.
|
|
341
|
+
*/
|
|
342
|
+
async teardown() {
|
|
343
|
+
logger.debug("Tearing down PostCSS processor");
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const postcssProcessorPlugin = (config) => {
|
|
347
|
+
return new PostCssProcessorPlugin({
|
|
348
|
+
options: config
|
|
349
|
+
});
|
|
350
|
+
};
|
|
351
|
+
export {
|
|
352
|
+
PostCssProcessorPlugin,
|
|
353
|
+
postcssProcessorPlugin
|
|
354
|
+
};
|