tailwind-preset-mantine 4.0.0-alpha.5 → 4.0.0
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 +17 -14
- package/package.json +68 -68
- package/src/cli/index.js +5 -3
- package/src/core/generate.js +20 -5
- package/src/core/output.js +363 -28
- package/src/core/theme-dependencies.js +85 -11
- package/src/core/theme-loader.js +46 -9
- package/src/integrations/postcss.d.ts +1 -1
- package/src/integrations/postcss.js +41 -2
- package/src/integrations/vite.d.ts +1 -1
- package/src/integrations/vite.js +29 -14
package/README.md
CHANGED
|
@@ -70,7 +70,7 @@ Note that you don't have to import tailwind or mantine styles, this preset will
|
|
|
70
70
|
|
|
71
71
|
### Custom mantine theme
|
|
72
72
|
|
|
73
|
-
If you have a custom mantine theme (<https://mantine.dev/theming/theme-object/>), the recommended setup is to generate a
|
|
73
|
+
If you have a custom mantine theme (<https://mantine.dev/theming/theme-object/>), the recommended setup is to generate a complete stylesheet from your theme file and import that stylesheet directly.
|
|
74
74
|
|
|
75
75
|
1. Create a theme file (e.g., `mantine-theme.ts`):
|
|
76
76
|
|
|
@@ -109,6 +109,8 @@ export default theme;
|
|
|
109
109
|
|
|
110
110
|
2. Configure the integration to generate a stylesheet from your Mantine theme.
|
|
111
111
|
|
|
112
|
+
By default, the generated file is written next to the theme file with the same basename and a `.css` extension. For example, `./src/mantine-theme.ts` generates `./src/mantine-theme.css`.
|
|
113
|
+
|
|
112
114
|
PostCSS:
|
|
113
115
|
|
|
114
116
|
```js
|
|
@@ -118,8 +120,6 @@ export default {
|
|
|
118
120
|
plugins: [
|
|
119
121
|
mantineTheme({
|
|
120
122
|
input: "./src/mantine-theme.ts",
|
|
121
|
-
output: "./src/mantine-theme.css",
|
|
122
|
-
format: "theme",
|
|
123
123
|
}),
|
|
124
124
|
],
|
|
125
125
|
};
|
|
@@ -135,8 +135,6 @@ export default defineConfig({
|
|
|
135
135
|
plugins: [
|
|
136
136
|
mantineTheme({
|
|
137
137
|
input: "./src/mantine-theme.ts",
|
|
138
|
-
output: "./src/mantine-theme.css",
|
|
139
|
-
format: "theme",
|
|
140
138
|
}),
|
|
141
139
|
],
|
|
142
140
|
});
|
|
@@ -144,14 +142,21 @@ export default defineConfig({
|
|
|
144
142
|
|
|
145
143
|
The Vite integration also watches local modules imported by your Mantine theme file, so updates to split files like `theme/colors.ts` or `theme/spacing.ts` trigger the generated stylesheet to update automatically.
|
|
146
144
|
|
|
147
|
-
3. Import the generated stylesheet
|
|
145
|
+
3. Import the generated stylesheet.
|
|
148
146
|
|
|
149
147
|
```css
|
|
150
|
-
@import "tailwind-preset-mantine";
|
|
151
148
|
@import "./mantine-theme.css";
|
|
152
149
|
```
|
|
153
150
|
|
|
154
|
-
|
|
151
|
+
The generated stylesheet includes the default imports and your merged Mantine theme.
|
|
152
|
+
|
|
153
|
+
#### Integration options
|
|
154
|
+
|
|
155
|
+
| Option | Required | Default | Description |
|
|
156
|
+
|--------|----------|---------|-------------|
|
|
157
|
+
| `input` | Yes | – | Path to the Mantine theme source file |
|
|
158
|
+
| `output` | No | `input` basename with `.css` extension | Path to the generated stylesheet |
|
|
159
|
+
| `format` | No | `theme` | `theme` generates Tailwind aliases only; `standalone` generates Mantine variables plus Tailwind aliases |
|
|
155
160
|
|
|
156
161
|
### Standalone pages without MantineProvider
|
|
157
162
|
|
|
@@ -162,7 +167,6 @@ PostCSS or Vite:
|
|
|
162
167
|
```ts
|
|
163
168
|
mantineTheme({
|
|
164
169
|
input: "./src/mantine-theme.ts",
|
|
165
|
-
output: "./src/mantine-theme.css",
|
|
166
170
|
format: "standalone",
|
|
167
171
|
});
|
|
168
172
|
```
|
|
@@ -180,18 +184,17 @@ If you already render `MantineProvider` on the same page, prefer the default `th
|
|
|
180
184
|
If your setup does not use PostCSS or Vite, you can still generate the theme CSS with the CLI:
|
|
181
185
|
|
|
182
186
|
```bash
|
|
183
|
-
npx tailwind-preset-mantine mantine-theme.ts
|
|
187
|
+
npx tailwind-preset-mantine mantine-theme.ts
|
|
184
188
|
```
|
|
185
189
|
|
|
186
190
|
Options:
|
|
187
|
-
- `-o, --output`: Output file name/location (
|
|
191
|
+
- `-o, --output`: Output file name/location (defaults to the input filename with a `.css` extension)
|
|
188
192
|
- `--format theme|standalone`: Output either Tailwind aliases only (`theme`, default) or Mantine variables plus Tailwind aliases (`standalone`)
|
|
189
193
|
|
|
190
|
-
Then import the generated file
|
|
194
|
+
Then import the generated file:
|
|
191
195
|
|
|
192
196
|
```css
|
|
193
|
-
@import "
|
|
194
|
-
@import "./theme.css";
|
|
197
|
+
@import "./mantine-theme.css";
|
|
195
198
|
```
|
|
196
199
|
|
|
197
200
|
Use `--format standalone` when generating CSS for pages that do not render `MantineProvider`.
|
package/package.json
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
2
|
+
"name": "tailwind-preset-mantine",
|
|
3
|
+
"version": "4.0.0",
|
|
4
|
+
"description": "Integrate Mantine with Tailwind CSS",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mantine",
|
|
7
|
+
"tailwind",
|
|
8
|
+
"preset"
|
|
9
|
+
],
|
|
10
|
+
"homepage": "https://github.com/songkeys/tailwind-preset-mantine#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/songkeys/tailwind-preset-mantine/issues"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/songkeys/tailwind-preset-mantine.git"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"author": "Songkeys",
|
|
20
|
+
"type": "module",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": "./src/index.css",
|
|
23
|
+
"./theme.css": "./src/theme.css",
|
|
24
|
+
"./postcss": {
|
|
25
|
+
"types": "./src/integrations/postcss.d.ts",
|
|
26
|
+
"default": "./src/integrations/postcss.js"
|
|
27
|
+
},
|
|
28
|
+
"./vite": {
|
|
29
|
+
"types": "./src/integrations/vite.d.ts",
|
|
30
|
+
"default": "./src/integrations/vite.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"main": "src/index.css",
|
|
34
|
+
"bin": {
|
|
35
|
+
"tailwind-preset-mantine": "src/cli/index.js"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"src"
|
|
39
|
+
],
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=22.15.0"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"es-module-lexer": "^2.0.0",
|
|
45
|
+
"get-tsconfig": "^4.13.7",
|
|
46
|
+
"postcss": "^8.5.9",
|
|
47
|
+
"tsx": "^4.21.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@biomejs/biome": "^2.4.11",
|
|
51
|
+
"@mantine/core": "^9.0.1",
|
|
52
|
+
"@tailwindcss/postcss": "^4.2.2",
|
|
53
|
+
"@tailwindcss/vite": "^4.2.2",
|
|
54
|
+
"bumpp": "^11.0.1",
|
|
55
|
+
"tailwindcss": "^4.2.2",
|
|
56
|
+
"vite": "^8.0.8"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"@mantine/core": "^7 || ^8 || ^9",
|
|
60
|
+
"tailwindcss": "^4"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"generate": "node scripts/generate.js",
|
|
64
|
+
"lint": "biome check .",
|
|
65
|
+
"lint:fix": "biome check . --write",
|
|
66
|
+
"release": "bumpp",
|
|
67
|
+
"test": "node --test"
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/cli/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { relative } from "node:path";
|
|
2
3
|
import { parseArgs } from "node:util";
|
|
3
4
|
import { validateOutputFormat, writeThemeOutput } from "../core/output.js";
|
|
4
5
|
|
|
@@ -9,7 +10,6 @@ const options = {
|
|
|
9
10
|
output: {
|
|
10
11
|
type: "string",
|
|
11
12
|
short: "o",
|
|
12
|
-
default: "theme.css",
|
|
13
13
|
description: "Output file name",
|
|
14
14
|
},
|
|
15
15
|
format: {
|
|
@@ -41,7 +41,7 @@ try {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
try {
|
|
44
|
-
await writeThemeOutput(
|
|
44
|
+
const { outputPath } = await writeThemeOutput(
|
|
45
45
|
{
|
|
46
46
|
input: inputFile,
|
|
47
47
|
output: outputFile,
|
|
@@ -50,7 +50,9 @@ try {
|
|
|
50
50
|
{ baseDir: pwd },
|
|
51
51
|
);
|
|
52
52
|
|
|
53
|
-
console.log(
|
|
53
|
+
console.log(
|
|
54
|
+
`Successfully generated ${relative(pwd, outputPath) || outputPath}`,
|
|
55
|
+
);
|
|
54
56
|
} catch (error) {
|
|
55
57
|
console.error("Error generating theme:", error.message);
|
|
56
58
|
process.exit(1);
|
package/src/core/generate.js
CHANGED
|
@@ -75,7 +75,6 @@ function generateStandaloneMantineVariables(theme = DEFAULT_THEME) {
|
|
|
75
75
|
* @param {import("@mantine/core").MantineTheme} theme
|
|
76
76
|
*/
|
|
77
77
|
export function generateTheme(theme = DEFAULT_THEME) {
|
|
78
|
-
const isDefault = JSON.stringify(theme) === JSON.stringify(DEFAULT_THEME);
|
|
79
78
|
const mergedTheme = mergeMantineTheme(DEFAULT_THEME, theme);
|
|
80
79
|
|
|
81
80
|
const colorKeys = Object.keys(mergedTheme.colors ?? {});
|
|
@@ -91,12 +90,10 @@ export function generateTheme(theme = DEFAULT_THEME) {
|
|
|
91
90
|
const defaultShadowKey = shadowKeys.includes("xs")
|
|
92
91
|
? "xs"
|
|
93
92
|
: (shadowKeys[0] ?? "xs");
|
|
94
|
-
const darkVariant =
|
|
95
|
-
? `@custom-variant dark (&:where(
|
|
93
|
+
const darkVariant = `@custom-variant dark (&:where(
|
|
96
94
|
[data-mantine-color-scheme="dark"],
|
|
97
95
|
[data-mantine-color-scheme="dark"] *
|
|
98
|
-
))
|
|
99
|
-
: "";
|
|
96
|
+
));`;
|
|
100
97
|
|
|
101
98
|
return `${AUTOGENERATED_COMMENT}${darkVariant}
|
|
102
99
|
|
|
@@ -263,3 +260,21 @@ export function generateStandaloneTheme(theme = DEFAULT_THEME) {
|
|
|
263
260
|
return `${AUTOGENERATED_COMMENT}${standaloneMantineCSS}
|
|
264
261
|
${tailwindThemeCSS}`;
|
|
265
262
|
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* @param {import("@mantine/core").MantineTheme} theme
|
|
266
|
+
* @param {"theme" | "standalone"} [format]
|
|
267
|
+
*/
|
|
268
|
+
export function generateManagedStylesheet(
|
|
269
|
+
theme = DEFAULT_THEME,
|
|
270
|
+
format = "theme",
|
|
271
|
+
) {
|
|
272
|
+
const themeCSS = stripAutogeneratedComment(
|
|
273
|
+
format === "standalone"
|
|
274
|
+
? generateStandaloneTheme(theme)
|
|
275
|
+
: generateTheme(theme),
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
return `${generateDefaultImports()}
|
|
279
|
+
${themeCSS}`;
|
|
280
|
+
}
|
package/src/core/output.js
CHANGED
|
@@ -1,35 +1,61 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import { dirname, resolve } from "node:path";
|
|
1
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
-
import {
|
|
4
|
+
import { createPathsMatcher, getTsconfig } from "get-tsconfig";
|
|
5
|
+
import { generateManagedStylesheet } from "./generate.js";
|
|
5
6
|
import { collectThemeDependencies } from "./theme-dependencies.js";
|
|
6
7
|
import { loadThemeFromFile } from "./theme-loader.js";
|
|
7
8
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const OUTPUT_FORMATS = ["theme", "standalone"];
|
|
10
|
+
const PACKAGE_JSON_FILENAMES = ["package.json"];
|
|
11
|
+
const THEME_SOURCE_EXTENSIONS = [
|
|
12
|
+
".js",
|
|
13
|
+
".jsx",
|
|
14
|
+
".mjs",
|
|
15
|
+
".cjs",
|
|
16
|
+
".json",
|
|
17
|
+
".ts",
|
|
18
|
+
".tsx",
|
|
19
|
+
".mts",
|
|
20
|
+
".cts",
|
|
21
|
+
];
|
|
22
|
+
const JSON_FILE_CACHE = new Map();
|
|
23
|
+
const TSCONFIG_DISCOVERY_CACHE = new Map();
|
|
24
|
+
const TSCONFIG_FS_CACHE = new Map();
|
|
25
|
+
const TSCONFIG_PATHS_MATCHER_CACHE = new Map();
|
|
12
26
|
|
|
13
27
|
function validateOptions(options) {
|
|
14
28
|
if (!options?.input) {
|
|
15
29
|
throw new Error("Missing required `input` option.");
|
|
16
30
|
}
|
|
17
|
-
|
|
18
|
-
if (!options?.output) {
|
|
19
|
-
throw new Error("Missing required `output` option.");
|
|
20
|
-
}
|
|
21
31
|
}
|
|
22
32
|
|
|
23
33
|
export function validateOutputFormat(format = "theme") {
|
|
24
|
-
if (!(format
|
|
34
|
+
if (!OUTPUT_FORMATS.includes(format)) {
|
|
25
35
|
throw new Error(
|
|
26
|
-
`Invalid output format: ${format}. Expected one of: ${
|
|
36
|
+
`Invalid output format: ${format}. Expected one of: ${OUTPUT_FORMATS.join(", ")}`,
|
|
27
37
|
);
|
|
28
38
|
}
|
|
29
39
|
}
|
|
30
40
|
|
|
31
41
|
/**
|
|
32
|
-
* @param {
|
|
42
|
+
* @param {string} inputPath
|
|
43
|
+
* @param {string | undefined} output
|
|
44
|
+
* @param {string} baseDir
|
|
45
|
+
*/
|
|
46
|
+
function resolveOutputPath(inputPath, output, baseDir) {
|
|
47
|
+
if (output) {
|
|
48
|
+
return resolve(baseDir, output);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const inputDirectory = dirname(inputPath);
|
|
52
|
+
const inputFilename = basename(inputPath, extname(inputPath));
|
|
53
|
+
|
|
54
|
+
return join(inputDirectory, `${inputFilename}.css`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {{ input: string, output?: string, format?: "theme" | "standalone" }} options
|
|
33
59
|
* @param {string} [baseDir]
|
|
34
60
|
*/
|
|
35
61
|
export function resolveThemeOutputOptions(options, baseDir = process.cwd()) {
|
|
@@ -37,10 +63,11 @@ export function resolveThemeOutputOptions(options, baseDir = process.cwd()) {
|
|
|
37
63
|
|
|
38
64
|
const format = options.format ?? "theme";
|
|
39
65
|
validateOutputFormat(format);
|
|
66
|
+
const inputPath = resolve(baseDir, options.input);
|
|
40
67
|
|
|
41
68
|
return {
|
|
42
|
-
inputPath
|
|
43
|
-
outputPath:
|
|
69
|
+
inputPath,
|
|
70
|
+
outputPath: resolveOutputPath(inputPath, options.output, baseDir),
|
|
44
71
|
format,
|
|
45
72
|
};
|
|
46
73
|
}
|
|
@@ -50,23 +77,334 @@ export function resolveThemeOutputOptions(options, baseDir = process.cwd()) {
|
|
|
50
77
|
* @param {string} importer
|
|
51
78
|
*/
|
|
52
79
|
async function resolveThemeImport(specifier, importer) {
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
80
|
+
if (specifier.startsWith("/")) {
|
|
81
|
+
return specifier;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
if (specifier.startsWith("file:")) {
|
|
86
|
+
return fileURLToPath(specifier);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (specifier.startsWith(".")) {
|
|
90
|
+
return fileURLToPath(new URL(specifier, pathToFileURL(importer)));
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
// Fall through to config-based alias resolution.
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (specifier.startsWith("#")) {
|
|
97
|
+
const packageJsonPath = await findClosestConfig(
|
|
98
|
+
dirname(importer),
|
|
99
|
+
PACKAGE_JSON_FILENAMES,
|
|
100
|
+
);
|
|
101
|
+
const packageImport = await resolvePackageImport(
|
|
102
|
+
specifier,
|
|
103
|
+
packageJsonPath,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
if (packageImport) {
|
|
107
|
+
return packageImport;
|
|
56
108
|
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return resolveTsconfigImport(specifier, dirname(importer));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function matchSpecifierPattern(specifier, pattern) {
|
|
115
|
+
const wildcardIndex = pattern.indexOf("*");
|
|
116
|
+
|
|
117
|
+
if (wildcardIndex === -1) {
|
|
118
|
+
return specifier === pattern ? "" : null;
|
|
119
|
+
}
|
|
57
120
|
|
|
121
|
+
const prefix = pattern.slice(0, wildcardIndex);
|
|
122
|
+
const suffix = pattern.slice(wildcardIndex + 1);
|
|
123
|
+
|
|
124
|
+
if (!specifier.startsWith(prefix) || !specifier.endsWith(suffix)) {
|
|
58
125
|
return null;
|
|
59
126
|
}
|
|
60
127
|
|
|
128
|
+
return specifier.slice(prefix.length, specifier.length - suffix.length);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function flattenImportTargets(target) {
|
|
132
|
+
if (typeof target === "string") {
|
|
133
|
+
return [target];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (Array.isArray(target)) {
|
|
137
|
+
return target.flatMap((item) => flattenImportTargets(item));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!target || typeof target !== "object") {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const orderedConditions = ["default", "import", "node"];
|
|
145
|
+
const orderedTargets = [];
|
|
146
|
+
|
|
147
|
+
for (const condition of orderedConditions) {
|
|
148
|
+
if (condition in target) {
|
|
149
|
+
orderedTargets.push(target[condition]);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
for (const [condition, value] of Object.entries(target)) {
|
|
154
|
+
if (!orderedConditions.includes(condition)) {
|
|
155
|
+
orderedTargets.push(value);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return orderedTargets.flatMap((value) => flattenImportTargets(value));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function getMappedImportCandidates(
|
|
163
|
+
specifier,
|
|
164
|
+
mappings,
|
|
165
|
+
baseDirectory,
|
|
166
|
+
localOnly,
|
|
167
|
+
) {
|
|
168
|
+
if (!mappings || typeof mappings !== "object") {
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
for (const [pattern, target] of Object.entries(mappings)) {
|
|
173
|
+
const match = matchSpecifierPattern(specifier, pattern);
|
|
174
|
+
|
|
175
|
+
if (match == null) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const resolvedCandidates = [];
|
|
180
|
+
|
|
181
|
+
for (const candidate of flattenImportTargets(target)) {
|
|
182
|
+
if (typeof candidate !== "string") {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (localOnly && !candidate.startsWith("./")) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const resolvedTarget = candidate.includes("*")
|
|
191
|
+
? candidate.replace("*", match)
|
|
192
|
+
: candidate;
|
|
193
|
+
|
|
194
|
+
if (
|
|
195
|
+
/^[a-z]+:/i.test(resolvedTarget) &&
|
|
196
|
+
!resolvedTarget.startsWith("file:")
|
|
197
|
+
) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
resolvedCandidates.push(
|
|
202
|
+
resolvedTarget.startsWith("file:")
|
|
203
|
+
? fileURLToPath(resolvedTarget)
|
|
204
|
+
: resolve(baseDirectory, resolvedTarget),
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return resolvedCandidates;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function pathExists(path) {
|
|
61
215
|
try {
|
|
62
|
-
|
|
216
|
+
await access(path);
|
|
217
|
+
return true;
|
|
63
218
|
} catch {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* @param {string} path
|
|
225
|
+
*/
|
|
226
|
+
async function readJsonFile(path) {
|
|
227
|
+
if (JSON_FILE_CACHE.has(path)) {
|
|
228
|
+
return JSON_FILE_CACHE.get(path);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const json = await readFile(path, "utf8")
|
|
232
|
+
.then((source) => JSON.parse(source))
|
|
233
|
+
.catch(() => null);
|
|
234
|
+
|
|
235
|
+
JSON_FILE_CACHE.set(path, json);
|
|
236
|
+
return json;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* @param {string} startDirectory
|
|
241
|
+
* @param {string[]} filenames
|
|
242
|
+
*/
|
|
243
|
+
async function findClosestConfig(startDirectory, filenames) {
|
|
244
|
+
let currentDirectory = startDirectory;
|
|
245
|
+
|
|
246
|
+
while (true) {
|
|
247
|
+
for (const filename of filenames) {
|
|
248
|
+
const candidate = join(currentDirectory, filename);
|
|
249
|
+
|
|
250
|
+
if (await pathExists(candidate)) {
|
|
251
|
+
return candidate;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const parentDirectory = dirname(currentDirectory);
|
|
256
|
+
|
|
257
|
+
if (parentDirectory === currentDirectory) {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
currentDirectory = parentDirectory;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* @param {string} specifier
|
|
267
|
+
* @param {string | null} packageJsonPath
|
|
268
|
+
*/
|
|
269
|
+
async function resolvePackageImport(specifier, packageJsonPath) {
|
|
270
|
+
if (!packageJsonPath) {
|
|
64
271
|
return null;
|
|
65
272
|
}
|
|
273
|
+
|
|
274
|
+
const packageJson = await readJsonFile(packageJsonPath);
|
|
275
|
+
return resolveFirstExistingThemeSource(
|
|
276
|
+
getMappedImportCandidates(
|
|
277
|
+
specifier,
|
|
278
|
+
packageJson?.imports,
|
|
279
|
+
dirname(packageJsonPath),
|
|
280
|
+
true,
|
|
281
|
+
),
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function getTsconfigPathsMatcher(tsconfigPath) {
|
|
286
|
+
if (TSCONFIG_PATHS_MATCHER_CACHE.has(tsconfigPath.path)) {
|
|
287
|
+
return TSCONFIG_PATHS_MATCHER_CACHE.get(tsconfigPath.path);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
let matcher = null;
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
matcher = createPathsMatcher(tsconfigPath);
|
|
294
|
+
} catch {
|
|
295
|
+
// Ignore unreadable or invalid configs and continue without alias support.
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
TSCONFIG_PATHS_MATCHER_CACHE.set(tsconfigPath.path, matcher);
|
|
299
|
+
return matcher;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function getConfigDistance(startDirectory, configPath) {
|
|
303
|
+
let currentDirectory = resolve(startDirectory);
|
|
304
|
+
const configDirectory = dirname(configPath);
|
|
305
|
+
let distance = 0;
|
|
306
|
+
|
|
307
|
+
while (true) {
|
|
308
|
+
if (currentDirectory === configDirectory) {
|
|
309
|
+
return distance;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const parentDirectory = dirname(currentDirectory);
|
|
313
|
+
|
|
314
|
+
if (parentDirectory === currentDirectory) {
|
|
315
|
+
return Number.POSITIVE_INFINITY;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
currentDirectory = parentDirectory;
|
|
319
|
+
distance += 1;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function getClosestTsconfigResult(startDirectory) {
|
|
324
|
+
if (TSCONFIG_DISCOVERY_CACHE.has(startDirectory)) {
|
|
325
|
+
return TSCONFIG_DISCOVERY_CACHE.get(startDirectory);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const tsconfigResult = getTsconfig(
|
|
329
|
+
startDirectory,
|
|
330
|
+
"tsconfig.json",
|
|
331
|
+
TSCONFIG_FS_CACHE,
|
|
332
|
+
);
|
|
333
|
+
const jsconfigResult = getTsconfig(
|
|
334
|
+
startDirectory,
|
|
335
|
+
"jsconfig.json",
|
|
336
|
+
TSCONFIG_FS_CACHE,
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
let closestResult = tsconfigResult ?? jsconfigResult;
|
|
340
|
+
|
|
341
|
+
if (tsconfigResult && jsconfigResult) {
|
|
342
|
+
const tsconfigDistance = getConfigDistance(
|
|
343
|
+
startDirectory,
|
|
344
|
+
tsconfigResult.path,
|
|
345
|
+
);
|
|
346
|
+
const jsconfigDistance = getConfigDistance(
|
|
347
|
+
startDirectory,
|
|
348
|
+
jsconfigResult.path,
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
closestResult =
|
|
352
|
+
jsconfigDistance < tsconfigDistance ? jsconfigResult : tsconfigResult;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
TSCONFIG_DISCOVERY_CACHE.set(startDirectory, closestResult);
|
|
356
|
+
return closestResult;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function getThemeSourceCandidates(path) {
|
|
360
|
+
const candidates = [path];
|
|
361
|
+
|
|
362
|
+
if (extname(path)) {
|
|
363
|
+
return candidates;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
for (const extension of THEME_SOURCE_EXTENSIONS) {
|
|
367
|
+
candidates.push(`${path}${extension}`);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
for (const extension of THEME_SOURCE_EXTENSIONS) {
|
|
371
|
+
candidates.push(join(path, `index${extension}`));
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return candidates;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
async function resolveFirstExistingThemeSource(candidatePaths) {
|
|
378
|
+
for (const candidatePath of candidatePaths) {
|
|
379
|
+
for (const candidate of getThemeSourceCandidates(candidatePath)) {
|
|
380
|
+
if (await pathExists(candidate)) {
|
|
381
|
+
return candidate;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return null;
|
|
66
387
|
}
|
|
67
388
|
|
|
68
389
|
/**
|
|
69
|
-
* @param {
|
|
390
|
+
* @param {string} specifier
|
|
391
|
+
* @param {string} startDirectory
|
|
392
|
+
*/
|
|
393
|
+
async function resolveTsconfigImport(specifier, startDirectory) {
|
|
394
|
+
const tsconfigResult = getClosestTsconfigResult(startDirectory);
|
|
395
|
+
|
|
396
|
+
if (!tsconfigResult) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const pathsMatcher = getTsconfigPathsMatcher(tsconfigResult);
|
|
401
|
+
return pathsMatcher
|
|
402
|
+
? resolveFirstExistingThemeSource(pathsMatcher(specifier))
|
|
403
|
+
: null;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* @param {{ input: string, output?: string, format?: "theme" | "standalone" }} options
|
|
70
408
|
* @param {{ baseDir?: string, resolveImport?: (specifier: string, importer: string) => Promise<string | null> }} [runtimeOptions]
|
|
71
409
|
*/
|
|
72
410
|
export async function buildThemeOutput(options, runtimeOptions = {}) {
|
|
@@ -76,12 +414,9 @@ export async function buildThemeOutput(options, runtimeOptions = {}) {
|
|
|
76
414
|
options,
|
|
77
415
|
baseDir,
|
|
78
416
|
);
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
absolutePath,
|
|
83
|
-
resolveImport,
|
|
84
|
-
);
|
|
417
|
+
const dependencies = await collectThemeDependencies(inputPath, resolveImport);
|
|
418
|
+
const { absolutePath, theme } = await loadThemeFromFile(inputPath, baseDir);
|
|
419
|
+
const css = generateManagedStylesheet(theme, format);
|
|
85
420
|
|
|
86
421
|
return {
|
|
87
422
|
css,
|
|
@@ -93,7 +428,7 @@ export async function buildThemeOutput(options, runtimeOptions = {}) {
|
|
|
93
428
|
}
|
|
94
429
|
|
|
95
430
|
/**
|
|
96
|
-
* @param {{ input: string, output
|
|
431
|
+
* @param {{ input: string, output?: string, format?: "theme" | "standalone" }} options
|
|
97
432
|
* @param {{ baseDir?: string, resolveImport?: (specifier: string, importer: string) => Promise<string | null> }} [runtimeOptions]
|
|
98
433
|
*/
|
|
99
434
|
export async function writeThemeOutput(options, runtimeOptions = {}) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { extname } from "node:path";
|
|
1
|
+
import { access, readFile } from "node:fs/promises";
|
|
2
|
+
import { extname, join } from "node:path";
|
|
3
3
|
import { init, parse } from "es-module-lexer";
|
|
4
4
|
|
|
5
5
|
const THEME_SOURCE_EXTENSIONS = new Set([
|
|
@@ -7,11 +7,78 @@ const THEME_SOURCE_EXTENSIONS = new Set([
|
|
|
7
7
|
".jsx",
|
|
8
8
|
".mjs",
|
|
9
9
|
".cjs",
|
|
10
|
+
".json",
|
|
10
11
|
".ts",
|
|
11
12
|
".tsx",
|
|
12
13
|
".mts",
|
|
13
14
|
".cts",
|
|
14
15
|
]);
|
|
16
|
+
const COMMONJS_REQUIRE_PATTERN =
|
|
17
|
+
/\brequire\s*\(\s*(['"`])([^'"`\n\r]+)\1\s*\)/g;
|
|
18
|
+
|
|
19
|
+
function normalizeResolvedPath(resolved) {
|
|
20
|
+
return resolved.split("?", 1)[0]?.split("#", 1)[0] ?? resolved;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function fileExists(file) {
|
|
24
|
+
try {
|
|
25
|
+
await access(file);
|
|
26
|
+
return true;
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function resolveThemeSourceDependency(
|
|
33
|
+
specifier,
|
|
34
|
+
importer,
|
|
35
|
+
resolveImport,
|
|
36
|
+
) {
|
|
37
|
+
const resolved = await resolveImport(specifier, importer);
|
|
38
|
+
|
|
39
|
+
if (resolved == null || resolved.startsWith("\0")) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const normalized = normalizeResolvedPath(resolved);
|
|
44
|
+
const extension = extname(normalized);
|
|
45
|
+
|
|
46
|
+
if (THEME_SOURCE_EXTENSIONS.has(extension)) {
|
|
47
|
+
return normalized;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (extension) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const candidateExtension of THEME_SOURCE_EXTENSIONS) {
|
|
55
|
+
const candidate = `${normalized}${candidateExtension}`;
|
|
56
|
+
|
|
57
|
+
if (await fileExists(candidate)) {
|
|
58
|
+
return candidate;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (const candidateExtension of THEME_SOURCE_EXTENSIONS) {
|
|
63
|
+
const candidate = join(normalized, `index${candidateExtension}`);
|
|
64
|
+
|
|
65
|
+
if (await fileExists(candidate)) {
|
|
66
|
+
return candidate;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function collectRequireSpecifiers(source) {
|
|
74
|
+
const specifiers = [];
|
|
75
|
+
|
|
76
|
+
for (const match of source.matchAll(COMMONJS_REQUIRE_PATTERN)) {
|
|
77
|
+
specifiers.push(match[2]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return specifiers;
|
|
81
|
+
}
|
|
15
82
|
|
|
16
83
|
/**
|
|
17
84
|
* @param {string} entryFile
|
|
@@ -32,26 +99,33 @@ export async function collectThemeDependencies(entryFile, resolveImport) {
|
|
|
32
99
|
|
|
33
100
|
const source = await readFile(file, "utf8");
|
|
34
101
|
const [imports] = parse(source);
|
|
102
|
+
const specifiers = new Set();
|
|
35
103
|
|
|
36
104
|
for (const record of imports) {
|
|
37
|
-
if (record.
|
|
105
|
+
if (record.n == null) {
|
|
38
106
|
continue;
|
|
39
107
|
}
|
|
40
108
|
|
|
41
|
-
|
|
109
|
+
specifiers.add(record.n);
|
|
110
|
+
}
|
|
42
111
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
112
|
+
for (const specifier of collectRequireSpecifiers(source)) {
|
|
113
|
+
specifiers.add(specifier);
|
|
114
|
+
}
|
|
46
115
|
|
|
47
|
-
|
|
116
|
+
for (const specifier of specifiers) {
|
|
117
|
+
const dependency = await resolveThemeSourceDependency(
|
|
118
|
+
specifier,
|
|
119
|
+
file,
|
|
120
|
+
resolveImport,
|
|
121
|
+
);
|
|
48
122
|
|
|
49
|
-
if (
|
|
123
|
+
if (dependency == null) {
|
|
50
124
|
continue;
|
|
51
125
|
}
|
|
52
126
|
|
|
53
|
-
dependencies.add(
|
|
54
|
-
await visit(
|
|
127
|
+
dependencies.add(dependency);
|
|
128
|
+
await visit(dependency);
|
|
55
129
|
}
|
|
56
130
|
}
|
|
57
131
|
|
package/src/core/theme-loader.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import "
|
|
2
|
-
import { stat } from "node:fs/promises";
|
|
1
|
+
import { execFile as execFileCallback } from "node:child_process";
|
|
3
2
|
import * as nodeModule from "node:module";
|
|
4
3
|
import { resolve } from "node:path";
|
|
5
|
-
import { pathToFileURL } from "node:url";
|
|
6
|
-
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
|
|
7
|
+
const execFile = promisify(execFileCallback);
|
|
8
|
+
const THIS_FILE = fileURLToPath(import.meta.url);
|
|
9
|
+
const CHILD_RESULT_MARKER = "__TWPM_THEME_RESULT__";
|
|
10
|
+
const require = nodeModule.createRequire(import.meta.url);
|
|
11
|
+
const TSX_LOADER_PATH = require.resolve("tsx");
|
|
7
12
|
const STYLE_EXTENSIONS = [
|
|
8
13
|
".css",
|
|
9
14
|
".scss",
|
|
@@ -66,7 +71,6 @@ function installThemeImportHooks() {
|
|
|
66
71
|
|
|
67
72
|
themeImportHooksInstalled = true;
|
|
68
73
|
|
|
69
|
-
const require = nodeModule.createRequire(import.meta.url);
|
|
70
74
|
const extensions = require.extensions;
|
|
71
75
|
|
|
72
76
|
for (const extension of STYLE_EXTENSIONS) {
|
|
@@ -112,13 +116,11 @@ function installThemeImportHooks() {
|
|
|
112
116
|
* @param {string} themePath
|
|
113
117
|
* @param {string} baseDir
|
|
114
118
|
*/
|
|
115
|
-
|
|
119
|
+
async function loadThemeFromFileInProcess(themePath, baseDir = process.cwd()) {
|
|
116
120
|
installThemeImportHooks();
|
|
117
121
|
|
|
118
122
|
const absolutePath = resolve(baseDir, themePath);
|
|
119
|
-
const
|
|
120
|
-
const { mtimeMs } = await stat(absolutePath);
|
|
121
|
-
const themeModule = await import(`${themeURL.href}?t=${mtimeMs}`);
|
|
123
|
+
const themeModule = await import(pathToFileURL(absolutePath).href);
|
|
122
124
|
const theme = unwrapThemeExport(themeModule);
|
|
123
125
|
|
|
124
126
|
if (!theme) {
|
|
@@ -129,3 +131,38 @@ export async function loadThemeFromFile(themePath, baseDir = process.cwd()) {
|
|
|
129
131
|
|
|
130
132
|
return { absolutePath, theme };
|
|
131
133
|
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @param {string} themePath
|
|
137
|
+
* @param {string} baseDir
|
|
138
|
+
*/
|
|
139
|
+
export async function loadThemeFromFile(themePath, baseDir = process.cwd()) {
|
|
140
|
+
const { stdout } = await execFile(
|
|
141
|
+
process.execPath,
|
|
142
|
+
["--import", TSX_LOADER_PATH, THIS_FILE, "--child", themePath, baseDir],
|
|
143
|
+
{
|
|
144
|
+
cwd: resolve(baseDir),
|
|
145
|
+
maxBuffer: 5 * 1024 * 1024,
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
const markerIndex = stdout.lastIndexOf(CHILD_RESULT_MARKER);
|
|
149
|
+
|
|
150
|
+
if (markerIndex === -1) {
|
|
151
|
+
throw new Error("Theme loader child process did not return a result.");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return JSON.parse(stdout.slice(markerIndex + CHILD_RESULT_MARKER.length));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (process.argv[1] === THIS_FILE && process.argv[2] === "--child") {
|
|
158
|
+
const themePath = process.argv[3];
|
|
159
|
+
const baseDir = process.argv[4] ?? process.cwd();
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const result = await loadThemeFromFileInProcess(themePath, baseDir);
|
|
163
|
+
process.stdout.write(`${CHILD_RESULT_MARKER}${JSON.stringify(result)}`);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.error(error);
|
|
166
|
+
process.exitCode = 1;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
1
3
|
import { writeThemeOutput } from "../core/output.js";
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* @typedef {{
|
|
5
7
|
* input: string;
|
|
6
|
-
* output
|
|
8
|
+
* output?: string;
|
|
7
9
|
* format?: "theme" | "standalone";
|
|
8
10
|
* }} MantineThemePluginOptions
|
|
9
11
|
*/
|
|
@@ -12,11 +14,48 @@ import { writeThemeOutput } from "../core/output.js";
|
|
|
12
14
|
* @param {MantineThemePluginOptions} options
|
|
13
15
|
*/
|
|
14
16
|
function mantineTheme(options) {
|
|
17
|
+
async function resolveBaseDir(result) {
|
|
18
|
+
const candidates = [];
|
|
19
|
+
const cwd = result.opts.cwd ?? process.cwd();
|
|
20
|
+
|
|
21
|
+
if (result.opts.from) {
|
|
22
|
+
let current = resolve(cwd, result.opts.from);
|
|
23
|
+
current = dirname(current);
|
|
24
|
+
|
|
25
|
+
while (true) {
|
|
26
|
+
candidates.push(current);
|
|
27
|
+
const parent = dirname(current);
|
|
28
|
+
|
|
29
|
+
if (parent === current) {
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
current = parent;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
candidates.push(cwd);
|
|
38
|
+
|
|
39
|
+
const dedupedCandidates = [...new Set(candidates)];
|
|
40
|
+
|
|
41
|
+
for (const baseDir of dedupedCandidates) {
|
|
42
|
+
try {
|
|
43
|
+
await access(resolve(baseDir, options.input));
|
|
44
|
+
return baseDir;
|
|
45
|
+
} catch {
|
|
46
|
+
// Try the next candidate directory.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return cwd;
|
|
51
|
+
}
|
|
52
|
+
|
|
15
53
|
return {
|
|
16
54
|
postcssPlugin: "tailwind-preset-mantine",
|
|
17
55
|
async Once(_, { result }) {
|
|
56
|
+
const baseDir = await resolveBaseDir(result);
|
|
18
57
|
const { dependencies } = await writeThemeOutput(options, {
|
|
19
|
-
baseDir
|
|
58
|
+
baseDir,
|
|
20
59
|
});
|
|
21
60
|
|
|
22
61
|
for (const file of dependencies) {
|
package/src/integrations/vite.js
CHANGED
|
@@ -3,7 +3,7 @@ import { writeThemeOutput } from "../core/output.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* @typedef {{
|
|
5
5
|
* input: string;
|
|
6
|
-
* output
|
|
6
|
+
* output?: string;
|
|
7
7
|
* format?: "theme" | "standalone";
|
|
8
8
|
* }} MantineThemePluginOptions
|
|
9
9
|
*/
|
|
@@ -16,22 +16,37 @@ export default function mantineTheme(options) {
|
|
|
16
16
|
let outputPath = "";
|
|
17
17
|
let dependencyFiles = new Set();
|
|
18
18
|
let generatePromise = null;
|
|
19
|
+
let pendingRegeneration = false;
|
|
19
20
|
|
|
20
|
-
async function
|
|
21
|
-
|
|
22
|
-
generatePromise = writeThemeOutput(options, { baseDir: root }).finally(
|
|
23
|
-
() => {
|
|
24
|
-
generatePromise = null;
|
|
25
|
-
},
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const result = await generatePromise;
|
|
21
|
+
async function runGeneration() {
|
|
22
|
+
const result = await writeThemeOutput(options, { baseDir: root });
|
|
30
23
|
outputPath = result.outputPath;
|
|
31
24
|
dependencyFiles = new Set(result.dependencies);
|
|
32
25
|
return result;
|
|
33
26
|
}
|
|
34
27
|
|
|
28
|
+
async function generateThemeOutput({ queueNextRun = false } = {}) {
|
|
29
|
+
if (generatePromise) {
|
|
30
|
+
pendingRegeneration ||= queueNextRun;
|
|
31
|
+
return generatePromise;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
generatePromise = (async () => {
|
|
35
|
+
let result;
|
|
36
|
+
|
|
37
|
+
do {
|
|
38
|
+
pendingRegeneration = false;
|
|
39
|
+
result = await runGeneration();
|
|
40
|
+
} while (pendingRegeneration);
|
|
41
|
+
|
|
42
|
+
return result;
|
|
43
|
+
})().finally(() => {
|
|
44
|
+
generatePromise = null;
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return generatePromise;
|
|
48
|
+
}
|
|
49
|
+
|
|
35
50
|
return {
|
|
36
51
|
name: "tailwind-preset-mantine",
|
|
37
52
|
async configResolved(config) {
|
|
@@ -45,8 +60,8 @@ export default function mantineTheme(options) {
|
|
|
45
60
|
}
|
|
46
61
|
},
|
|
47
62
|
configureServer(server) {
|
|
48
|
-
const refresh = async () => {
|
|
49
|
-
const result = await generateThemeOutput();
|
|
63
|
+
const refresh = async (options = undefined) => {
|
|
64
|
+
const result = await generateThemeOutput(options);
|
|
50
65
|
server.watcher.add([...result.dependencies]);
|
|
51
66
|
};
|
|
52
67
|
|
|
@@ -55,7 +70,7 @@ export default function mantineTheme(options) {
|
|
|
55
70
|
return;
|
|
56
71
|
}
|
|
57
72
|
|
|
58
|
-
await refresh();
|
|
73
|
+
await refresh({ queueNextRun: true });
|
|
59
74
|
};
|
|
60
75
|
|
|
61
76
|
server.watcher.on("change", handleFileChange);
|