tailwind-preset-mantine 4.0.0-alpha.6 → 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 +1 -1
- package/package.json +68 -68
- package/src/core/generate.js +2 -5
- package/src/core/output.js +335 -10
- package/src/core/theme-dependencies.js +85 -11
- package/src/core/theme-loader.js +46 -9
- package/src/integrations/postcss.js +40 -1
- package/src/integrations/vite.js +28 -13
package/README.md
CHANGED
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/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
|
|
package/src/core/output.js
CHANGED
|
@@ -1,11 +1,28 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
1
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
+
import { createPathsMatcher, getTsconfig } from "get-tsconfig";
|
|
4
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
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();
|
|
9
26
|
|
|
10
27
|
function validateOptions(options) {
|
|
11
28
|
if (!options?.input) {
|
|
@@ -60,19 +77,330 @@ export function resolveThemeOutputOptions(options, baseDir = process.cwd()) {
|
|
|
60
77
|
* @param {string} importer
|
|
61
78
|
*/
|
|
62
79
|
async function resolveThemeImport(specifier, importer) {
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
|
|
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;
|
|
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
|
+
}
|
|
120
|
+
|
|
121
|
+
const prefix = pattern.slice(0, wildcardIndex);
|
|
122
|
+
const suffix = pattern.slice(wildcardIndex + 1);
|
|
123
|
+
|
|
124
|
+
if (!specifier.startsWith(prefix) || !specifier.endsWith(suffix)) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
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) {
|
|
215
|
+
try {
|
|
216
|
+
await access(path);
|
|
217
|
+
return true;
|
|
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;
|
|
66
259
|
}
|
|
67
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) {
|
|
68
271
|
return null;
|
|
69
272
|
}
|
|
70
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
|
+
|
|
71
292
|
try {
|
|
72
|
-
|
|
293
|
+
matcher = createPathsMatcher(tsconfigPath);
|
|
73
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;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* @param {string} specifier
|
|
391
|
+
* @param {string} startDirectory
|
|
392
|
+
*/
|
|
393
|
+
async function resolveTsconfigImport(specifier, startDirectory) {
|
|
394
|
+
const tsconfigResult = getClosestTsconfigResult(startDirectory);
|
|
395
|
+
|
|
396
|
+
if (!tsconfigResult) {
|
|
74
397
|
return null;
|
|
75
398
|
}
|
|
399
|
+
|
|
400
|
+
const pathsMatcher = getTsconfigPathsMatcher(tsconfigResult);
|
|
401
|
+
return pathsMatcher
|
|
402
|
+
? resolveFirstExistingThemeSource(pathsMatcher(specifier))
|
|
403
|
+
: null;
|
|
76
404
|
}
|
|
77
405
|
|
|
78
406
|
/**
|
|
@@ -86,12 +414,9 @@ export async function buildThemeOutput(options, runtimeOptions = {}) {
|
|
|
86
414
|
options,
|
|
87
415
|
baseDir,
|
|
88
416
|
);
|
|
89
|
-
const
|
|
417
|
+
const dependencies = await collectThemeDependencies(inputPath, resolveImport);
|
|
418
|
+
const { absolutePath, theme } = await loadThemeFromFile(inputPath, baseDir);
|
|
90
419
|
const css = generateManagedStylesheet(theme, format);
|
|
91
|
-
const dependencies = await collectThemeDependencies(
|
|
92
|
-
absolutePath,
|
|
93
|
-
resolveImport,
|
|
94
|
-
);
|
|
95
420
|
|
|
96
421
|
return {
|
|
97
422
|
css,
|
|
@@ -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,3 +1,5 @@
|
|
|
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
|
/**
|
|
@@ -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
|
@@ -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);
|