happy-css-modules 2.1.2 → 3.0.1
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 +5 -0
- package/dist/cli.js.map +1 -1
- package/dist/emitter/dts.js +8 -7
- package/dist/emitter/dts.js.map +1 -1
- package/dist/emitter/dts.test.js +1 -1
- package/dist/emitter/dts.test.js.map +1 -1
- package/dist/emitter/file-system.js.map +1 -1
- package/dist/emitter/index.js +1 -1
- package/dist/emitter/index.js.map +1 -1
- package/dist/emitter/source-map.js +2 -2
- package/dist/emitter/source-map.js.map +1 -1
- package/dist/emitter/source-map.test.js +2 -2
- package/dist/emitter/source-map.test.js.map +1 -1
- package/dist/integration-test/go-to-definition.test.js +1 -36
- package/dist/integration-test/go-to-definition.test.js.map +1 -1
- package/dist/locator/index.d.ts +1 -1
- package/dist/locator/index.js +6 -16
- package/dist/locator/index.js.map +1 -1
- package/dist/locator/index.test.js +23 -119
- package/dist/locator/index.test.js.map +1 -1
- package/dist/locator/postcss.d.ts +1 -12
- package/dist/locator/postcss.js +3 -42
- package/dist/locator/postcss.js.map +1 -1
- package/dist/locator/postcss.test.js +7 -53
- package/dist/locator/postcss.test.js.map +1 -1
- package/dist/logger.js.map +1 -1
- package/dist/regression-test/issue-168.test.js.map +1 -1
- package/dist/resolver/index.d.ts +1 -1
- package/dist/resolver/index.js +2 -0
- package/dist/resolver/index.js.map +1 -1
- package/dist/resolver/node-resolver.js +2 -2
- package/dist/resolver/node-resolver.js.map +1 -1
- package/dist/resolver/webpack-resolver.js +7 -8
- package/dist/resolver/webpack-resolver.js.map +1 -1
- package/dist/runner.js +9 -7
- package/dist/runner.js.map +1 -1
- package/dist/runner.test.js +16 -15
- package/dist/runner.test.js.map +1 -1
- package/dist/test-util/jest/resolver.cjs +3 -2
- package/dist/test-util/jest/resolver.cjs.map +1 -1
- package/dist/test-util/tsserver.d.ts +2 -2
- package/dist/test-util/tsserver.js +6 -7
- package/dist/test-util/tsserver.js.map +1 -1
- package/dist/test-util/util.d.ts +1 -2
- package/dist/test-util/util.js +3 -4
- package/dist/test-util/util.js.map +1 -1
- package/dist/transformer/index.d.ts +1 -1
- package/dist/transformer/index.js.map +1 -1
- package/dist/transformer/less-transformer.js +1 -0
- package/dist/transformer/less-transformer.js.map +1 -1
- package/dist/transformer/less-transformer.test.js +1 -9
- package/dist/transformer/less-transformer.test.js.map +1 -1
- package/dist/transformer/scss-transformer.js +3 -3
- package/dist/transformer/scss-transformer.js.map +1 -1
- package/dist/transformer/scss-transformer.test.js +1 -9
- package/dist/transformer/scss-transformer.test.js.map +1 -1
- package/dist/util.d.ts +1 -1
- package/dist/util.js +10 -7
- package/dist/util.js.map +1 -1
- package/package.json +17 -17
- package/src/emitter/dts.test.ts +1 -1
- package/src/emitter/dts.ts +10 -9
- package/src/emitter/index.ts +1 -1
- package/src/emitter/source-map.test.ts +2 -2
- package/src/emitter/source-map.ts +2 -2
- package/src/integration-test/go-to-definition.test.ts +1 -36
- package/src/locator/index.test.ts +23 -124
- package/src/locator/index.ts +7 -23
- package/src/locator/postcss.test.ts +7 -74
- package/src/locator/postcss.ts +3 -44
- package/src/resolver/index.ts +3 -1
- package/src/resolver/node-resolver.ts +2 -2
- package/src/resolver/webpack-resolver.ts +7 -8
- package/src/runner.test.ts +17 -17
- package/src/runner.ts +9 -8
- package/src/test-util/jest/resolver.cjs +3 -2
- package/src/test-util/tsserver.ts +7 -11
- package/src/test-util/util.ts +4 -6
- package/src/transformer/index.ts +1 -1
- package/src/transformer/less-transformer.test.ts +1 -9
- package/src/transformer/less-transformer.ts +1 -0
- package/src/transformer/postcss-transformer.ts +1 -1
- package/src/transformer/scss-transformer.test.ts +1 -9
- package/src/transformer/scss-transformer.ts +2 -2
- package/src/util.ts +10 -7
package/src/locator/index.ts
CHANGED
|
@@ -4,14 +4,7 @@ import type { Resolver } from '../resolver/index.js';
|
|
|
4
4
|
import { createDefaultResolver } from '../resolver/index.js';
|
|
5
5
|
import { createDefaultTransformer, type Transformer } from '../transformer/index.js';
|
|
6
6
|
import { unique, uniqueBy } from '../util.js';
|
|
7
|
-
import {
|
|
8
|
-
getOriginalLocation,
|
|
9
|
-
generateLocalTokenNames,
|
|
10
|
-
parseAtImport,
|
|
11
|
-
type Location,
|
|
12
|
-
parseComposesDeclarationWithFromUrl,
|
|
13
|
-
collectNodes,
|
|
14
|
-
} from './postcss.js';
|
|
7
|
+
import { getOriginalLocation, generateLocalTokenNames, parseAtImport, type Location, collectNodes } from './postcss.js';
|
|
15
8
|
|
|
16
9
|
export { collectNodes, type Location } from './postcss.js';
|
|
17
10
|
|
|
@@ -38,7 +31,7 @@ type CacheEntry = {
|
|
|
38
31
|
|
|
39
32
|
/** The result of `Locator#load`. */
|
|
40
33
|
export type LoadResult = {
|
|
41
|
-
/** The path of the file imported from the source file with `@import
|
|
34
|
+
/** The path of the file imported from the source file with `@import`. */
|
|
42
35
|
dependencies: string[];
|
|
43
36
|
/** The tokens exported by the source file. */
|
|
44
37
|
tokens: Token[];
|
|
@@ -98,6 +91,7 @@ export class Locator {
|
|
|
98
91
|
for (const dependency of dependencies) {
|
|
99
92
|
const entry = this.cache.get(dependency);
|
|
100
93
|
if (!entry) return true;
|
|
94
|
+
// eslint-disable-next-line no-await-in-loop
|
|
101
95
|
const mtime = (await stat(dependency)).mtime.getTime();
|
|
102
96
|
if (entry.mtime !== mtime) return true;
|
|
103
97
|
}
|
|
@@ -124,7 +118,7 @@ export class Locator {
|
|
|
124
118
|
dependencies: result.dependencies
|
|
125
119
|
.map((dep) => {
|
|
126
120
|
if (typeof dep === 'string') return dep;
|
|
127
|
-
if (dep.protocol !== 'file:') throw new Error(
|
|
121
|
+
if (dep.protocol !== 'file:') throw new Error(`Unsupported protocol: ${dep.protocol}`);
|
|
128
122
|
return dep.pathname;
|
|
129
123
|
})
|
|
130
124
|
.filter((dep) => {
|
|
@@ -164,14 +158,16 @@ export class Locator {
|
|
|
164
158
|
|
|
165
159
|
const tokens: Token[] = [];
|
|
166
160
|
|
|
167
|
-
const { atImports, classSelectors
|
|
161
|
+
const { atImports, classSelectors } = collectNodes(ast);
|
|
168
162
|
|
|
169
163
|
// Load imported sheets recursively.
|
|
170
164
|
for (const atImport of atImports) {
|
|
171
165
|
const importedSheetPath = parseAtImport(atImport);
|
|
172
166
|
if (!importedSheetPath) continue;
|
|
173
167
|
if (isIgnoredSpecifier(importedSheetPath)) continue;
|
|
168
|
+
// eslint-disable-next-line no-await-in-loop
|
|
174
169
|
const from = await this.resolver(importedSheetPath, { request: filePath });
|
|
170
|
+
// eslint-disable-next-line no-await-in-loop
|
|
175
171
|
const result = await this._load(from);
|
|
176
172
|
const externalTokens = result.tokens;
|
|
177
173
|
dependencies.push(from, ...result.dependencies);
|
|
@@ -192,18 +188,6 @@ export class Locator {
|
|
|
192
188
|
});
|
|
193
189
|
}
|
|
194
190
|
|
|
195
|
-
// Load imported tokens by the names recursively.
|
|
196
|
-
for (const composesDeclaration of composesDeclarations) {
|
|
197
|
-
const declarationDetail = parseComposesDeclarationWithFromUrl(composesDeclaration);
|
|
198
|
-
if (!declarationDetail) continue;
|
|
199
|
-
if (isIgnoredSpecifier(declarationDetail.from)) continue;
|
|
200
|
-
const from = await this.resolver(declarationDetail.from, { request: filePath });
|
|
201
|
-
const result = await this._load(from);
|
|
202
|
-
const externalTokens = result.tokens.filter((token) => declarationDetail.tokenNames.includes(token.name));
|
|
203
|
-
dependencies.push(from, ...result.dependencies);
|
|
204
|
-
tokens.push(...externalTokens);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
191
|
const result: LoadResult = {
|
|
208
192
|
dependencies: unique(dependencies).filter((dep) => dep !== filePath),
|
|
209
193
|
tokens: normalizeTokens(tokens),
|
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
import dedent from 'dedent';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
createClassSelectors,
|
|
5
|
-
createAtImports,
|
|
6
|
-
createComposesDeclarations,
|
|
7
|
-
createFixtures,
|
|
8
|
-
} from '../test-util/util.js';
|
|
9
|
-
import {
|
|
10
|
-
generateLocalTokenNames,
|
|
11
|
-
getOriginalLocation,
|
|
12
|
-
parseAtImport,
|
|
13
|
-
parseComposesDeclarationWithFromUrl,
|
|
14
|
-
collectNodes,
|
|
15
|
-
} from './postcss.js';
|
|
2
|
+
import { createRoot, createClassSelectors, createAtImports, createFixtures } from '../test-util/util.js';
|
|
3
|
+
import { generateLocalTokenNames, getOriginalLocation, parseAtImport, collectNodes } from './postcss.js';
|
|
16
4
|
|
|
17
5
|
describe('generateLocalTokenNames', () => {
|
|
18
6
|
test('basic', async () => {
|
|
@@ -39,10 +27,6 @@ describe('generateLocalTokenNames', () => {
|
|
|
39
27
|
.local_class_name_3 {}
|
|
40
28
|
}
|
|
41
29
|
:local(.local_class_name_4) {}
|
|
42
|
-
.composes_target {}
|
|
43
|
-
.composes {
|
|
44
|
-
composes: composes_target;
|
|
45
|
-
}
|
|
46
30
|
`),
|
|
47
31
|
),
|
|
48
32
|
).toStrictEqual([
|
|
@@ -62,8 +46,6 @@ describe('generateLocalTokenNames', () => {
|
|
|
62
46
|
'local_class_name_2',
|
|
63
47
|
'local_class_name_3',
|
|
64
48
|
'local_class_name_4',
|
|
65
|
-
'composes_target',
|
|
66
|
-
'composes',
|
|
67
49
|
]);
|
|
68
50
|
});
|
|
69
51
|
test('does not track styles imported by @import in other file because it is not a local token', async () => {
|
|
@@ -236,23 +218,6 @@ describe('getOriginalLocation', () => {
|
|
|
236
218
|
`{ filePath: "/test/test.css", start: { line: 6, column: 8 }, end: { line: 6, column: 26 } }`,
|
|
237
219
|
);
|
|
238
220
|
});
|
|
239
|
-
test('composes', () => {
|
|
240
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
241
|
-
const [composes_target, composes] = createClassSelectors(
|
|
242
|
-
createRoot(dedent`
|
|
243
|
-
.composes_target {}
|
|
244
|
-
.composes {
|
|
245
|
-
composes: composes_target;
|
|
246
|
-
}
|
|
247
|
-
`),
|
|
248
|
-
);
|
|
249
|
-
expect(getOriginalLocation(composes_target!.rule, composes_target!.classSelector)).toMatchInlineSnapshot(
|
|
250
|
-
`{ filePath: "/test/test.css", start: { line: 1, column: 1 }, end: { line: 1, column: 16 } }`,
|
|
251
|
-
);
|
|
252
|
-
expect(getOriginalLocation(composes!.rule, composes!.classSelector)).toMatchInlineSnapshot(
|
|
253
|
-
`{ filePath: "/test/test.css", start: { line: 2, column: 1 }, end: { line: 2, column: 9 } }`,
|
|
254
|
-
);
|
|
255
|
-
});
|
|
256
221
|
test('with_newline', () => {
|
|
257
222
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
258
223
|
const [with_newline_1, with_newline_2, with_newline_3] = createClassSelectors(
|
|
@@ -280,23 +245,20 @@ test('collectNodes', () => {
|
|
|
280
245
|
@import;
|
|
281
246
|
@import "test.css";
|
|
282
247
|
@ignored;
|
|
283
|
-
.a { ignored: "ignored";
|
|
284
|
-
.b { ignored: "ignored";
|
|
248
|
+
.a { ignored: "ignored"; }
|
|
249
|
+
.b { ignored: "ignored"; }
|
|
285
250
|
`);
|
|
286
251
|
|
|
287
|
-
const { atImports, classSelectors
|
|
252
|
+
const { atImports, classSelectors } = collectNodes(ast);
|
|
288
253
|
|
|
289
254
|
expect(atImports).toHaveLength(2);
|
|
290
255
|
expect(atImports[0]!.toString()).toEqual('@import');
|
|
291
256
|
expect(atImports[1]!.toString()).toEqual('@import "test.css"');
|
|
292
257
|
expect(classSelectors).toHaveLength(2);
|
|
293
|
-
expect(classSelectors[0]!.rule.toString()).toEqual('.a { ignored: "ignored";
|
|
258
|
+
expect(classSelectors[0]!.rule.toString()).toEqual('.a { ignored: "ignored"; }');
|
|
294
259
|
expect(classSelectors[0]!.classSelector.toString()).toEqual('.a');
|
|
295
|
-
expect(classSelectors[1]!.rule.toString()).toEqual('.b { ignored: "ignored";
|
|
260
|
+
expect(classSelectors[1]!.rule.toString()).toEqual('.b { ignored: "ignored"; }');
|
|
296
261
|
expect(classSelectors[1]!.classSelector.toString()).toEqual('.b');
|
|
297
|
-
expect(composesDeclarations).toHaveLength(2);
|
|
298
|
-
expect(composesDeclarations[0]!.toString()).toEqual('composes: a');
|
|
299
|
-
expect(composesDeclarations[1]!.toString()).toEqual('composes: b');
|
|
300
262
|
});
|
|
301
263
|
|
|
302
264
|
test('parseAtImport', () => {
|
|
@@ -315,32 +277,3 @@ test('parseAtImport', () => {
|
|
|
315
277
|
expect(parseAtImport(atImports[3]!)).toBe('test.css');
|
|
316
278
|
expect(parseAtImport(atImports[4]!)).toBe('test.css');
|
|
317
279
|
});
|
|
318
|
-
|
|
319
|
-
test('parseComposesDeclarationWithFromUrl', () => {
|
|
320
|
-
const composesDeclarations = createComposesDeclarations(
|
|
321
|
-
createRoot(dedent`
|
|
322
|
-
.a {
|
|
323
|
-
composes: a;
|
|
324
|
-
composes: a b c;
|
|
325
|
-
composes: a from "test.css";
|
|
326
|
-
composes: a b c from "test.css";
|
|
327
|
-
composes: from from from from "test.css";
|
|
328
|
-
/* NOTE: CSS Modules do not support '... from url("test.css")'. */
|
|
329
|
-
}
|
|
330
|
-
`),
|
|
331
|
-
);
|
|
332
|
-
expect(parseComposesDeclarationWithFromUrl(composesDeclarations[0]!)).toStrictEqual(undefined);
|
|
333
|
-
expect(parseComposesDeclarationWithFromUrl(composesDeclarations[1]!)).toStrictEqual(undefined);
|
|
334
|
-
expect(parseComposesDeclarationWithFromUrl(composesDeclarations[2]!)).toStrictEqual({
|
|
335
|
-
from: 'test.css',
|
|
336
|
-
tokenNames: ['a'],
|
|
337
|
-
});
|
|
338
|
-
expect(parseComposesDeclarationWithFromUrl(composesDeclarations[3]!)).toStrictEqual({
|
|
339
|
-
from: 'test.css',
|
|
340
|
-
tokenNames: ['a', 'b', 'c'],
|
|
341
|
-
});
|
|
342
|
-
expect(parseComposesDeclarationWithFromUrl(composesDeclarations[4]!)).toStrictEqual({
|
|
343
|
-
from: 'test.css',
|
|
344
|
-
tokenNames: ['from', 'from', 'from'], // do not deduplicate.
|
|
345
|
-
});
|
|
346
|
-
});
|
package/src/locator/postcss.ts
CHANGED
|
@@ -54,9 +54,9 @@ export async function generateLocalTokenNames(ast: Root): Promise<string[]> {
|
|
|
54
54
|
postcss
|
|
55
55
|
.default()
|
|
56
56
|
// postcss-modules collects tokens (i.e., includes external tokens) by following
|
|
57
|
-
// the dependencies specified in the @import
|
|
57
|
+
// the dependencies specified in the @import.
|
|
58
58
|
// However, we do not want `generateLocalTokenNames` to return external tokens.
|
|
59
|
-
// So we remove the @import
|
|
59
|
+
// So we remove the @import beforehand.
|
|
60
60
|
.use(removeDependenciesPlugin())
|
|
61
61
|
.use(
|
|
62
62
|
modules({
|
|
@@ -154,7 +154,6 @@ function isComposesDeclaration(node: Node): node is Declaration {
|
|
|
154
154
|
type CollectNodesResult = {
|
|
155
155
|
atImports: AtRule[];
|
|
156
156
|
classSelectors: { rule: Rule; classSelector: ClassName }[];
|
|
157
|
-
composesDeclarations: Declaration[];
|
|
158
157
|
};
|
|
159
158
|
|
|
160
159
|
/**
|
|
@@ -164,7 +163,6 @@ type CollectNodesResult = {
|
|
|
164
163
|
export function collectNodes(ast: Root): CollectNodesResult {
|
|
165
164
|
const atImports: AtRule[] = [];
|
|
166
165
|
const classSelectors: { rule: Rule; classSelector: ClassName }[] = [];
|
|
167
|
-
const composesDeclarations: Declaration[] = [];
|
|
168
166
|
ast.walk((node) => {
|
|
169
167
|
if (isAtImportNode(node)) {
|
|
170
168
|
atImports.push(node);
|
|
@@ -183,11 +181,9 @@ export function collectNodes(ast: Root): CollectNodesResult {
|
|
|
183
181
|
}
|
|
184
182
|
});
|
|
185
183
|
}).processSync(node);
|
|
186
|
-
} else if (isComposesDeclaration(node)) {
|
|
187
|
-
composesDeclarations.push(node);
|
|
188
184
|
}
|
|
189
185
|
});
|
|
190
|
-
return { atImports, classSelectors
|
|
186
|
+
return { atImports, classSelectors };
|
|
191
187
|
}
|
|
192
188
|
|
|
193
189
|
/**
|
|
@@ -206,40 +202,3 @@ export function parseAtImport(atImport: AtRule): string | undefined {
|
|
|
206
202
|
}
|
|
207
203
|
return undefined;
|
|
208
204
|
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Parse `composes` declaration with `from <url>`.
|
|
212
|
-
* If the declaration is not found or do not have `from <url>`, return `undefined`.
|
|
213
|
-
* @param composesDeclaration The `composes` declaration to parse.
|
|
214
|
-
* @returns The information of the declaration.
|
|
215
|
-
*/
|
|
216
|
-
export function parseComposesDeclarationWithFromUrl(
|
|
217
|
-
composesDeclaration: Declaration,
|
|
218
|
-
): { from: string; tokenNames: string[] } | undefined {
|
|
219
|
-
// NOTE: `composes` property syntax is...
|
|
220
|
-
// - syntax: `composes: <class-name> [...<class-name>] [from <url>];`
|
|
221
|
-
// - variables:
|
|
222
|
-
// - `<class-name>`: `<sting>`
|
|
223
|
-
// - `<url>`: `<string>`
|
|
224
|
-
// - ref:
|
|
225
|
-
// - https://github.com/css-modules/css-modules#composition
|
|
226
|
-
// - https://github.com/css-modules/css-modules#composing-from-other-files
|
|
227
|
-
// - https://github.com/css-modules/postcss-modules-extract-imports#specification
|
|
228
|
-
|
|
229
|
-
const nodes = valueParser(composesDeclaration.value).nodes;
|
|
230
|
-
if (nodes.length < 5) return undefined;
|
|
231
|
-
|
|
232
|
-
const classNamesOrSpaces = nodes.slice(0, -3);
|
|
233
|
-
const [from, , url] = nodes.slice(-3);
|
|
234
|
-
|
|
235
|
-
const classNames = classNamesOrSpaces.filter((node) => node.type === 'word');
|
|
236
|
-
|
|
237
|
-
// validate nodes
|
|
238
|
-
if (from === undefined) return undefined;
|
|
239
|
-
if (from.type !== 'word' || from.value !== 'from') return undefined;
|
|
240
|
-
if (url === undefined) return undefined;
|
|
241
|
-
if (url.type !== 'string') return undefined;
|
|
242
|
-
if (classNames.length === 0) return undefined;
|
|
243
|
-
|
|
244
|
-
return { from: url.value, tokenNames: classNames.map((node) => node.value) };
|
|
245
|
-
}
|
package/src/resolver/index.ts
CHANGED
|
@@ -11,7 +11,7 @@ export type ResolverOptions = {
|
|
|
11
11
|
/**
|
|
12
12
|
* The function to resolve the path of the imported file.
|
|
13
13
|
* @returns The resolved path of the imported file. `false` means to skip resolving.
|
|
14
|
-
|
|
14
|
+
*/
|
|
15
15
|
export type Resolver = (specifier: string, options: ResolverOptions) => string | false | Promise<string | false>;
|
|
16
16
|
|
|
17
17
|
export type DefaultResolverOptions = WebpackResolverOptions;
|
|
@@ -43,8 +43,10 @@ export const createDefaultResolver: (defaultResolverOptions?: DefaultResolverOpt
|
|
|
43
43
|
return async (specifier, options) => {
|
|
44
44
|
for (const resolver of resolvers) {
|
|
45
45
|
try {
|
|
46
|
+
// eslint-disable-next-line no-await-in-loop
|
|
46
47
|
const resolved = await resolver(specifier, options);
|
|
47
48
|
if (resolved !== false) {
|
|
49
|
+
// eslint-disable-next-line no-await-in-loop
|
|
48
50
|
const isExists = await exists(resolved);
|
|
49
51
|
if (isExists) return resolved;
|
|
50
52
|
}
|
|
@@ -2,6 +2,6 @@ import { fileURLToPath, pathToFileURL } from 'url';
|
|
|
2
2
|
import { resolve } from 'import-meta-resolve';
|
|
3
3
|
import type { Resolver } from './index.js';
|
|
4
4
|
|
|
5
|
-
export const createNodeResolver: () => Resolver = () =>
|
|
6
|
-
return fileURLToPath(
|
|
5
|
+
export const createNodeResolver: () => Resolver = () => (specifier, options) => {
|
|
6
|
+
return fileURLToPath(resolve(specifier, pathToFileURL(options.request).href));
|
|
7
7
|
};
|
|
@@ -44,14 +44,13 @@ export const createWebpackResolver: (webpackResolverOptions?: WebpackResolverOpt
|
|
|
44
44
|
* @see https://github.com/webpack-contrib/css-loader/blob/897e7dd250ccdb0d31e6c66d4cd0d009f2022a85/src/plugins/postcss-import-parser.js#L228-L235
|
|
45
45
|
*/
|
|
46
46
|
const cssLoaderResolver = enhancedResolve.create.sync({
|
|
47
|
-
dependencyType: 'css',
|
|
48
47
|
conditionNames: ['style'],
|
|
49
48
|
// We are not sure how "..." affects behavior...
|
|
50
49
|
mainFields: ['css', 'style', 'main', '...'],
|
|
51
50
|
mainFiles: ['index', '...'],
|
|
52
51
|
extensions: ['.css', '...'],
|
|
53
52
|
preferRelative: true,
|
|
54
|
-
alias: webpackResolveAlias,
|
|
53
|
+
alias: webpackResolveAlias ?? {},
|
|
55
54
|
});
|
|
56
55
|
|
|
57
56
|
/**
|
|
@@ -60,14 +59,13 @@ export const createWebpackResolver: (webpackResolverOptions?: WebpackResolverOpt
|
|
|
60
59
|
* @see https://github.com/webpack-contrib/sass-loader/blob/49a578a218574ddc92a597c7e365b6c21960717e/src/utils.js#L531-L539
|
|
61
60
|
*/
|
|
62
61
|
const sassLoaderResolver = enhancedResolve.create.sync({
|
|
63
|
-
dependencyType: 'sass',
|
|
64
62
|
conditionNames: ['sass', 'style'],
|
|
65
63
|
mainFields: ['sass', 'style', 'main', '...'],
|
|
66
64
|
mainFiles: ['_index', 'index', '...'],
|
|
67
65
|
extensions: ['.sass', '.scss', '.css'],
|
|
68
|
-
restrictions: [/\.((sa|sc|c)ss)$/
|
|
66
|
+
restrictions: [/\.((sa|sc|c)ss)$/iu],
|
|
69
67
|
preferRelative: true,
|
|
70
|
-
alias: webpackResolveAlias,
|
|
68
|
+
alias: webpackResolveAlias ?? {},
|
|
71
69
|
modules: ['node_modules', ...(sassLoadPaths ?? [])],
|
|
72
70
|
});
|
|
73
71
|
|
|
@@ -77,13 +75,12 @@ export const createWebpackResolver: (webpackResolverOptions?: WebpackResolverOpt
|
|
|
77
75
|
* @see https://github.com/webpack-contrib/less-loader/blob/d74f740c100c4006b00dfb3e02c6d5aaf8713519/src/utils.js#L35-L42
|
|
78
76
|
*/
|
|
79
77
|
const lessLoaderResolver = enhancedResolve.create.sync({
|
|
80
|
-
dependencyType: 'less',
|
|
81
78
|
conditionNames: ['less', 'style'],
|
|
82
79
|
mainFields: ['less', 'style', 'main', '...'],
|
|
83
80
|
mainFiles: ['index', '...'],
|
|
84
81
|
extensions: ['.less', '.css'],
|
|
85
82
|
preferRelative: true,
|
|
86
|
-
alias: webpackResolveAlias,
|
|
83
|
+
alias: webpackResolveAlias ?? {},
|
|
87
84
|
modules: ['node_modules', ...(lessIncludePaths ?? [])],
|
|
88
85
|
});
|
|
89
86
|
|
|
@@ -96,6 +93,7 @@ export const createWebpackResolver: (webpackResolverOptions?: WebpackResolverOpt
|
|
|
96
93
|
// ref: https://github.com/webpack-contrib/css-loader/blob/5e6cf91fd3f0c8b5fb4b91197b98dc56abdef4bf/src/utils.js#L92-L95
|
|
97
94
|
// ref: https://github.com/webpack-contrib/sass-loader/blob/49a578a218574ddc92a597c7e365b6c21960717e/src/utils.js#L368-L370
|
|
98
95
|
// ref: https://github.com/webpack-contrib/less-loader/blob/d74f740c100c4006b00dfb3e02c6d5aaf8713519/src/utils.js#L72-L75
|
|
96
|
+
// eslint-disable-next-line no-param-reassign
|
|
99
97
|
if (specifier.startsWith('~')) specifier = specifier.slice(1);
|
|
100
98
|
|
|
101
99
|
for (const resolver of resolvers) {
|
|
@@ -104,13 +102,14 @@ export const createWebpackResolver: (webpackResolverOptions?: WebpackResolverOpt
|
|
|
104
102
|
? // Support partial import for sass
|
|
105
103
|
// https://sass-lang.com/documentation/at-rules/import#partials
|
|
106
104
|
// https://github.com/webpack-contrib/sass-loader/blob/0e9494074f69a6b6d47efea6c083a02a31a5ae84/test/sass/import-with-underscore.sass
|
|
107
|
-
[join(dirname(specifier),
|
|
105
|
+
[join(dirname(specifier), `_${basename(specifier)}`), specifier]
|
|
108
106
|
: [specifier];
|
|
109
107
|
|
|
110
108
|
for (const specifierVariant of specifierVariants) {
|
|
111
109
|
try {
|
|
112
110
|
const resolved = resolver(dirname(options.request), specifierVariant);
|
|
113
111
|
if (resolved !== false) {
|
|
112
|
+
// eslint-disable-next-line no-await-in-loop
|
|
114
113
|
const isExists = await exists(resolved);
|
|
115
114
|
if (isExists) return resolved;
|
|
116
115
|
}
|
package/src/runner.test.ts
CHANGED
|
@@ -3,23 +3,22 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { dirname, join, resolve } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
-
import * as
|
|
6
|
+
import * as fileCacheCore from '@file-cache/core';
|
|
7
|
+
import type { CreateCacheOptions } from '@file-cache/core';
|
|
7
8
|
import { jest } from '@jest/globals';
|
|
8
9
|
import dedent from 'dedent';
|
|
9
10
|
import type { RunnerOptions, Watcher } from './runner.js';
|
|
10
11
|
import { createFixtures, exists, getFixturePath, waitForAsyncTask } from './test-util/util.js';
|
|
11
12
|
|
|
12
|
-
const packageRootDir = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
13
|
-
|
|
14
13
|
const require = createRequire(import.meta.url);
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
const uuid = randomUUID();
|
|
16
|
+
jest.unstable_mockModule('@file-cache/core', () => ({
|
|
17
|
+
...fileCacheCore, // Inherit native functions
|
|
18
|
+
createCache: async (options: CreateCacheOptions) => {
|
|
19
|
+
options.keys.push(() => uuid); // Add a random key to avoid cache collision
|
|
20
|
+
return fileCacheCore.createCache(options);
|
|
21
|
+
},
|
|
23
22
|
}));
|
|
24
23
|
|
|
25
24
|
const { run } = await import('./runner.js');
|
|
@@ -144,6 +143,7 @@ test('uses cache in watch mode', async () => {
|
|
|
144
143
|
await waitForAsyncTask(500); // Wait until the file is written
|
|
145
144
|
|
|
146
145
|
// The updated 1.css will be processed, and the non-updated 2.css will be skipped.
|
|
146
|
+
// eslint-disable-next-line require-atomic-updates
|
|
147
147
|
watcher = await run({ ...defaultOptions, declarationMap: true, logLevel: 'debug', cache: true, watch: true });
|
|
148
148
|
await waitForAsyncTask(1000); // Wait until the watcher is ready
|
|
149
149
|
expect(consoleLogSpy).toBeCalledTimes(3);
|
|
@@ -187,7 +187,7 @@ test('does not emit declaration map if declarationMap is false', async () => {
|
|
|
187
187
|
});
|
|
188
188
|
await run({ ...defaultOptions, declarationMap: false });
|
|
189
189
|
await expect(readFile(getFixturePath('/test/1.css.d.ts'), 'utf8')).resolves.not.toThrow();
|
|
190
|
-
await expect(readFile(getFixturePath('/test/1.css.d.ts.map'), 'utf8')).rejects.toThrow(/ENOENT/);
|
|
190
|
+
await expect(readFile(getFixturePath('/test/1.css.d.ts.map'), 'utf8')).rejects.toThrow(/ENOENT/u);
|
|
191
191
|
});
|
|
192
192
|
test('supports transformer', async () => {
|
|
193
193
|
createFixtures({
|
|
@@ -205,17 +205,17 @@ test('watches for changes in files', async () => {
|
|
|
205
205
|
|
|
206
206
|
await writeFile(getFixturePath('/test/1.css'), '.a-1 {}');
|
|
207
207
|
await waitForAsyncTask(500); // Wait until the file is written
|
|
208
|
-
expect(await readFile(getFixturePath('/test/1.css.d.ts'), 'utf8')).toMatch(/a-1/);
|
|
208
|
+
expect(await readFile(getFixturePath('/test/1.css.d.ts'), 'utf8')).toMatch(/a-1/u);
|
|
209
209
|
|
|
210
210
|
await writeFile(getFixturePath('/test/1.css'), '.a-2 {}');
|
|
211
211
|
await waitForAsyncTask(500); // Wait until the file is written
|
|
212
|
-
expect(await readFile(getFixturePath('/test/1.css.d.ts'), 'utf8')).toMatch(/a-2/);
|
|
212
|
+
expect(await readFile(getFixturePath('/test/1.css.d.ts'), 'utf8')).toMatch(/a-2/u);
|
|
213
213
|
|
|
214
214
|
await writeFile(getFixturePath('/test/2.css'), '.b {}');
|
|
215
215
|
await writeFile(getFixturePath('/test/3.css'), '.c {}');
|
|
216
216
|
await waitForAsyncTask(500); // Wait until the file is written
|
|
217
|
-
expect(await readFile(getFixturePath('/test/2.css.d.ts'), 'utf8')).toMatch(/b/);
|
|
218
|
-
expect(await readFile(getFixturePath('/test/3.css.d.ts'), 'utf8')).toMatch(/c/);
|
|
217
|
+
expect(await readFile(getFixturePath('/test/2.css.d.ts'), 'utf8')).toMatch(/b/u);
|
|
218
|
+
expect(await readFile(getFixturePath('/test/3.css.d.ts'), 'utf8')).toMatch(/c/u);
|
|
219
219
|
});
|
|
220
220
|
test('returns an error if the file fails to process in non-watch mode', async () => {
|
|
221
221
|
createFixtures({
|
|
@@ -230,8 +230,8 @@ test('returns an error if the file fails to process in non-watch mode', async ()
|
|
|
230
230
|
const error = maybeError as AggregateError;
|
|
231
231
|
expect(error.message).toMatchInlineSnapshot(`"Failed to process files"`);
|
|
232
232
|
expect(error.errors).toHaveLength(2);
|
|
233
|
-
expect(error.errors[0]).toMatchInlineSnapshot(`<fixtures>/test/
|
|
234
|
-
expect(error.errors[1]).toMatchInlineSnapshot(`<fixtures>/test/
|
|
233
|
+
expect(error.errors[0]).toMatchInlineSnapshot(`<fixtures>/test/3.css:1:1: Unknown word`);
|
|
234
|
+
expect(error.errors[1]).toMatchInlineSnapshot(`<fixtures>/test/2.css:1:1: Unknown word`);
|
|
235
235
|
|
|
236
236
|
// The valid files are emitted.
|
|
237
237
|
expect(await exists(getFixturePath('/test/1.css.d.ts'))).toBe(true);
|
package/src/runner.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { resolve, relative } from 'path';
|
|
2
2
|
import * as process from 'process';
|
|
3
|
-
import * as util from 'util';
|
|
4
3
|
import { createCache } from '@file-cache/core';
|
|
5
4
|
import { createNpmPackageKey } from '@file-cache/npm';
|
|
6
5
|
import AwaitLock from 'await-lock';
|
|
7
6
|
import chalk from 'chalk';
|
|
8
7
|
import * as chokidar from 'chokidar';
|
|
9
|
-
import
|
|
8
|
+
import { glob } from 'glob';
|
|
10
9
|
import { DEFAULT_ARBITRARY_EXTENSIONS } from './config.js';
|
|
11
10
|
import { isGeneratedFilesExist, emitGeneratedFiles } from './emitter/index.js';
|
|
12
11
|
import { Locator } from './locator/index.js';
|
|
@@ -16,8 +15,6 @@ import { createDefaultResolver } from './resolver/index.js';
|
|
|
16
15
|
import { createDefaultTransformer, type Transformer } from './transformer/index.js';
|
|
17
16
|
import { getInstalledPeerDependencies, isMatchByGlob } from './util.js';
|
|
18
17
|
|
|
19
|
-
const glob = util.promisify(_glob);
|
|
20
|
-
|
|
21
18
|
export type Watcher = {
|
|
22
19
|
close: () => Promise<void>;
|
|
23
20
|
};
|
|
@@ -94,6 +91,7 @@ type OverrideProp<T, K extends keyof T, V extends T[K]> = Omit<T, K> & { [P in K
|
|
|
94
91
|
export async function run(options: OverrideProp<RunnerOptions, 'watch', true>): Promise<Watcher>;
|
|
95
92
|
export async function run(options: RunnerOptions): Promise<void>;
|
|
96
93
|
export async function run(options: RunnerOptions): Promise<Watcher | void> {
|
|
94
|
+
// eslint-disable-next-line new-cap
|
|
97
95
|
const lock = new AwaitLock.default();
|
|
98
96
|
const logger = new Logger(options.logLevel ?? 'info');
|
|
99
97
|
|
|
@@ -108,8 +106,9 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
|
|
|
108
106
|
});
|
|
109
107
|
const transformer = options.transformer ?? createDefaultTransformer({ cwd, postcssConfig: options.postcssConfig });
|
|
110
108
|
|
|
111
|
-
const installedPeerDependencies =
|
|
109
|
+
const installedPeerDependencies = getInstalledPeerDependencies();
|
|
112
110
|
const cache = await createCache({
|
|
111
|
+
name: 'happy-css-modules',
|
|
113
112
|
mode: options.cacheStrategy ?? 'content',
|
|
114
113
|
keys: [
|
|
115
114
|
() => createNpmPackageKey(['happy-css-modules', ...installedPeerDependencies]),
|
|
@@ -128,6 +127,7 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
|
|
|
128
127
|
async function processFile(filePath: string) {
|
|
129
128
|
async function isChangedFile(filePath: string) {
|
|
130
129
|
const result = await cache.getAndUpdateCache(filePath);
|
|
130
|
+
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
|
131
131
|
if (result.error) throw result.error;
|
|
132
132
|
return result.changed;
|
|
133
133
|
}
|
|
@@ -175,6 +175,7 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
|
|
|
175
175
|
|
|
176
176
|
const errors: unknown[] = [];
|
|
177
177
|
for (const filePath of filePaths) {
|
|
178
|
+
// eslint-disable-next-line no-await-in-loop
|
|
178
179
|
await processFile(filePath).catch((e) => errors.push(e));
|
|
179
180
|
}
|
|
180
181
|
|
|
@@ -184,13 +185,13 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
|
|
|
184
185
|
}
|
|
185
186
|
|
|
186
187
|
if (!options.watch) {
|
|
187
|
-
logger.info(
|
|
188
|
+
logger.info(`Generate .d.ts for ${options.pattern}...`);
|
|
188
189
|
await processAllFiles();
|
|
189
190
|
// Write cache state to file for persistence
|
|
190
191
|
} else {
|
|
191
192
|
// First, watch files.
|
|
192
|
-
logger.info(
|
|
193
|
-
const watcher = chokidar.watch([options.pattern.replace(/\\/
|
|
193
|
+
logger.info(`Watch ${options.pattern}...`);
|
|
194
|
+
const watcher = chokidar.watch([options.pattern.replace(/\\/gu, '/')], { cwd });
|
|
194
195
|
watcher.on('all', (eventName, relativeFilePath) => {
|
|
195
196
|
const filePath = resolve(cwd, relativeFilePath);
|
|
196
197
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
|
|
1
2
|
const nativeModule = require('node:module');
|
|
2
3
|
|
|
3
4
|
// workaround for https://github.com/facebook/jest/issues/12270#issuecomment-1194746382
|
|
@@ -14,7 +15,7 @@ const nativeModule = require('node:module');
|
|
|
14
15
|
* pathFilter?: (pkg: any, path: string, relativePath: string) => string;
|
|
15
16
|
* rootDir?: string;
|
|
16
17
|
* }} ResolverOptions
|
|
17
|
-
|
|
18
|
+
*/
|
|
18
19
|
|
|
19
20
|
/** @type {(path: string, options: ResolverOptions) => string} */
|
|
20
21
|
function resolver(module, options) {
|
|
@@ -22,7 +23,7 @@ function resolver(module, options) {
|
|
|
22
23
|
try {
|
|
23
24
|
return defaultResolver(module, options);
|
|
24
25
|
// eslint-disable-next-line no-unused-vars
|
|
25
|
-
} catch (
|
|
26
|
+
} catch (_error) {
|
|
26
27
|
return nativeModule.createRequire(basedir).resolve(module);
|
|
27
28
|
}
|
|
28
29
|
}
|
|
@@ -2,14 +2,12 @@ import { readFileSync } from 'fs';
|
|
|
2
2
|
import { mkdir, writeFile as nativeWriteFile } from 'fs/promises';
|
|
3
3
|
import { dirname } from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
|
-
import { promisify } from 'util';
|
|
6
5
|
import serverHarness from '@typescript/server-harness';
|
|
7
|
-
import
|
|
6
|
+
import { glob } from 'glob';
|
|
8
7
|
import { resolve } from 'import-meta-resolve';
|
|
9
8
|
import lineColumn from 'line-column';
|
|
10
9
|
import type { server } from 'typescript/lib/tsserverlibrary.js';
|
|
11
10
|
import { getFixturePath } from './util.js';
|
|
12
|
-
const glob = promisify(_glob);
|
|
13
11
|
|
|
14
12
|
async function writeFile(path: string, content: string): Promise<void> {
|
|
15
13
|
await mkdir(dirname(path), { recursive: true });
|
|
@@ -37,14 +35,11 @@ type Definition = {
|
|
|
37
35
|
};
|
|
38
36
|
};
|
|
39
37
|
|
|
40
|
-
export
|
|
41
|
-
const server = serverHarness.launchServer(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
'--disableAutomaticTypingAcquisition',
|
|
46
|
-
],
|
|
47
|
-
);
|
|
38
|
+
export function createTSServer() {
|
|
39
|
+
const server = serverHarness.launchServer(fileURLToPath(resolve('typescript/lib/tsserver.js', import.meta.url)), [
|
|
40
|
+
// ATA generates some extra network traffic and isn't usually relevant when profiling
|
|
41
|
+
'--disableAutomaticTypingAcquisition',
|
|
42
|
+
]);
|
|
48
43
|
|
|
49
44
|
return {
|
|
50
45
|
async getIdentifierDefinitions(filePath: string, identifier: string): Promise<Definition[]> {
|
|
@@ -67,6 +62,7 @@ export async function createTSServer() {
|
|
|
67
62
|
const results: { identifier: string; definitions: Definition[] }[] = [];
|
|
68
63
|
|
|
69
64
|
for (let i = 0; i < identifiers.length; i++) {
|
|
65
|
+
// eslint-disable-next-line no-await-in-loop
|
|
70
66
|
const response: server.protocol.DefinitionResponse = await server.message({
|
|
71
67
|
seq: 0,
|
|
72
68
|
type: 'request',
|
package/src/test-util/util.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { constants, mkdirSync, realpathSync, rmSync, writeFileSync } from 'fs';
|
|
|
2
2
|
import { access } from 'fs/promises';
|
|
3
3
|
import { tmpdir } from 'os';
|
|
4
4
|
import { dirname, join, resolve } from 'path';
|
|
5
|
-
import postcss, { type Root, type Rule, type AtRule
|
|
5
|
+
import postcss, { type Root, type Rule, type AtRule } from 'postcss';
|
|
6
6
|
import { type ClassName } from 'postcss-selector-parser';
|
|
7
7
|
import { type Token, collectNodes, type Location } from '../locator/index.js';
|
|
8
8
|
import { sleepSync } from '../util.js';
|
|
@@ -25,10 +25,6 @@ export function createClassSelectors(root: Root): { rule: Rule; classSelector: C
|
|
|
25
25
|
return collectNodes(root).classSelectors;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export function createComposesDeclarations(root: Root): Declaration[] {
|
|
29
|
-
return collectNodes(root).composesDeclarations;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
28
|
export function fakeToken(args: {
|
|
33
29
|
name: Token['name'];
|
|
34
30
|
originalLocations: { filePath?: Location['filePath']; start?: Location['start'] }[];
|
|
@@ -53,7 +49,9 @@ export function fakeToken(args: {
|
|
|
53
49
|
}
|
|
54
50
|
|
|
55
51
|
export async function waitForAsyncTask(ms?: number): Promise<void> {
|
|
56
|
-
await new Promise((resolve) =>
|
|
52
|
+
await new Promise((resolve) => {
|
|
53
|
+
setTimeout(resolve, ms ?? 0);
|
|
54
|
+
});
|
|
57
55
|
}
|
|
58
56
|
|
|
59
57
|
export async function exists(path: string): Promise<boolean> {
|
package/src/transformer/index.ts
CHANGED