happy-css-modules 0.4.0 → 0.6.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 +172 -53
- package/dist/cli.js +45 -11
- package/dist/cli.js.map +1 -1
- package/dist/cli.test.js +20 -4
- package/dist/cli.test.js.map +1 -1
- package/dist/emitter/dts.d.ts +3 -4
- package/dist/emitter/dts.js +13 -15
- package/dist/emitter/dts.js.map +1 -1
- package/dist/emitter/dts.test.js +7 -10
- package/dist/emitter/dts.test.js.map +1 -1
- package/dist/emitter/index.d.ts +6 -15
- package/dist/emitter/index.js +18 -13
- package/dist/emitter/index.js.map +1 -1
- package/dist/emitter/index.test.js +0 -50
- package/dist/emitter/index.test.js.map +1 -1
- package/dist/emitter/source-map.d.ts +1 -3
- package/dist/emitter/source-map.js +2 -3
- package/dist/emitter/source-map.js.map +1 -1
- package/dist/emitter/source-map.test.js +1 -4
- package/dist/emitter/source-map.test.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/integration-test/go-to-definition.test.js +1 -0
- package/dist/integration-test/go-to-definition.test.js.map +1 -1
- package/dist/{loader → locator}/index.d.ts +6 -4
- package/dist/{loader → locator}/index.js +13 -5
- package/dist/locator/index.js.map +1 -0
- package/dist/{loader → locator}/index.test.d.ts +0 -0
- package/dist/{loader → locator}/index.test.js +100 -14
- package/dist/locator/index.test.js.map +1 -0
- package/dist/{loader → locator}/postcss.d.ts +5 -1
- package/dist/{loader → locator}/postcss.js +26 -30
- package/dist/{loader → locator}/postcss.js.map +1 -1
- package/dist/{loader → locator}/postcss.test.d.ts +0 -0
- package/dist/{loader → locator}/postcss.test.js +0 -0
- package/dist/locator/postcss.test.js.map +1 -0
- package/dist/resolver/webpack-resolver.d.ts +12 -2
- package/dist/resolver/webpack-resolver.js +12 -3
- package/dist/resolver/webpack-resolver.js.map +1 -1
- package/dist/resolver/webpack-resolver.test.js +43 -7
- package/dist/resolver/webpack-resolver.test.js.map +1 -1
- package/dist/runner.d.ts +23 -1
- package/dist/runner.js +62 -23
- package/dist/runner.js.map +1 -1
- package/dist/runner.test.js +90 -11
- package/dist/runner.test.js.map +1 -1
- package/dist/test/util.d.ts +2 -2
- package/dist/test/util.js +16 -9
- package/dist/test/util.js.map +1 -1
- package/dist/transformer/index.d.ts +4 -2
- package/dist/transformer/index.js +7 -3
- package/dist/transformer/index.js.map +1 -1
- package/dist/transformer/index.test.d.ts +1 -0
- package/dist/transformer/index.test.js +66 -0
- package/dist/transformer/index.test.js.map +1 -0
- package/dist/transformer/less-transformer.test.js +14 -14
- package/dist/transformer/less-transformer.test.js.map +1 -1
- package/dist/transformer/postcss-transformer.d.ts +12 -0
- package/dist/transformer/postcss-transformer.js +32 -0
- package/dist/transformer/postcss-transformer.js.map +1 -0
- package/dist/transformer/postcss-transformer.test.d.ts +1 -0
- package/dist/transformer/postcss-transformer.test.js +176 -0
- package/dist/transformer/postcss-transformer.test.js.map +1 -0
- package/dist/transformer/scss-transformer.js +19 -17
- package/dist/transformer/scss-transformer.js.map +1 -1
- package/dist/transformer/scss-transformer.test.js +16 -16
- package/dist/transformer/scss-transformer.test.js.map +1 -1
- package/dist/util.d.ts +2 -0
- package/dist/util.js +19 -2
- package/dist/util.js.map +1 -1
- package/package.json +10 -9
- package/src/cli.test.ts +24 -4
- package/src/cli.ts +44 -12
- package/src/emitter/dts.test.ts +7 -12
- package/src/emitter/dts.ts +15 -15
- package/src/emitter/index.test.ts +0 -52
- package/src/emitter/index.ts +22 -29
- package/src/emitter/source-map.test.ts +1 -6
- package/src/emitter/source-map.ts +3 -4
- package/src/index.ts +9 -2
- package/src/integration-test/go-to-definition.test.ts +1 -0
- package/src/{loader → locator}/index.test.ts +101 -14
- package/src/{loader → locator}/index.ts +16 -8
- package/src/{loader → locator}/postcss.test.ts +0 -0
- package/src/{loader → locator}/postcss.ts +42 -40
- package/src/resolver/webpack-resolver.test.ts +63 -7
- package/src/resolver/webpack-resolver.ts +26 -5
- package/src/runner.test.ts +103 -11
- package/src/runner.ts +83 -25
- package/src/test/util.ts +16 -10
- package/src/transformer/index.test.ts +71 -0
- package/src/transformer/index.ts +12 -4
- package/src/transformer/less-transformer.test.ts +14 -14
- package/src/transformer/postcss-transformer.test.ts +188 -0
- package/src/transformer/postcss-transformer.ts +57 -0
- package/src/transformer/scss-transformer.test.ts +16 -16
- package/src/transformer/scss-transformer.ts +25 -27
- package/src/util.ts +21 -2
- package/dist/loader/index.js.map +0 -1
- package/dist/loader/index.test.js.map +0 -1
- package/dist/loader/postcss.test.js.map +0 -1
package/src/test/util.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { tmpdir } from 'os';
|
|
|
4
4
|
import { dirname, join, resolve } from 'path';
|
|
5
5
|
import postcss, { type Root, type Rule, type AtRule, type Declaration } from 'postcss';
|
|
6
6
|
import { type ClassName } from 'postcss-selector-parser';
|
|
7
|
-
import { type Token, collectNodes, type Location } from '../
|
|
7
|
+
import { type Token, collectNodes, type Location } from '../locator/index.js';
|
|
8
8
|
import { sleepSync } from '../util.js';
|
|
9
9
|
|
|
10
10
|
export const FIXTURE_DIR_PATH = resolve(
|
|
@@ -31,18 +31,24 @@ export function createComposesDeclarations(root: Root): Declaration[] {
|
|
|
31
31
|
|
|
32
32
|
export function fakeToken(args: {
|
|
33
33
|
name: Token['name'];
|
|
34
|
-
originalLocations: { filePath?: Location['filePath']; start
|
|
34
|
+
originalLocations: { filePath?: Location['filePath']; start?: Location['start'] }[];
|
|
35
35
|
}): Token {
|
|
36
36
|
return {
|
|
37
37
|
name: args.name,
|
|
38
|
-
originalLocations: args.originalLocations.map((location) =>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
originalLocations: args.originalLocations.map((location) => {
|
|
39
|
+
if (location.filePath === undefined || location.start === undefined) {
|
|
40
|
+
return { filePath: undefined, start: undefined, end: undefined };
|
|
41
|
+
} else {
|
|
42
|
+
return {
|
|
43
|
+
filePath: location.filePath ?? getFixturePath('/test/1.css'),
|
|
44
|
+
start: location.start,
|
|
45
|
+
end: {
|
|
46
|
+
line: location.start.line,
|
|
47
|
+
column: location.start.column + args.name.length - 1,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}),
|
|
46
52
|
};
|
|
47
53
|
}
|
|
48
54
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import dedent from 'dedent';
|
|
4
|
+
import { Locator } from '../locator/index.js';
|
|
5
|
+
import { createFixtures, getFixturePath } from '../test/util.js';
|
|
6
|
+
import { createDefaultTransformer } from './index.js';
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
|
|
10
|
+
const cwd = getFixturePath('/');
|
|
11
|
+
const locator = new Locator({ transformer: createDefaultTransformer({ cwd }) });
|
|
12
|
+
|
|
13
|
+
test('processes .scss with scss transformer', async () => {
|
|
14
|
+
createFixtures({
|
|
15
|
+
'/test/1.scss': dedent`
|
|
16
|
+
.a {
|
|
17
|
+
// scss feature test (nesting)
|
|
18
|
+
.a_1 { dummy: ''; }
|
|
19
|
+
}
|
|
20
|
+
`,
|
|
21
|
+
});
|
|
22
|
+
const result = await locator.load(getFixturePath('/test/1.scss'));
|
|
23
|
+
expect(result.tokens.map((token) => token.name)).toStrictEqual(['a', 'a_1']);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('processes .less with less transformer', async () => {
|
|
27
|
+
createFixtures({
|
|
28
|
+
'/test/1.less': dedent`
|
|
29
|
+
.a {
|
|
30
|
+
// less feature test (nesting)
|
|
31
|
+
.a_1 { dummy: ''; }
|
|
32
|
+
}
|
|
33
|
+
`,
|
|
34
|
+
});
|
|
35
|
+
const result = await locator.load(getFixturePath('/test/1.less'));
|
|
36
|
+
expect(result.tokens.map((token) => token.name)).toStrictEqual(['a', 'a_1']);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('processes .css with postcss transformer if postcssrc is found', async () => {
|
|
40
|
+
// if postcssrc is not found
|
|
41
|
+
const locator1 = new Locator({ transformer: createDefaultTransformer() });
|
|
42
|
+
createFixtures({
|
|
43
|
+
'/test/1.css': dedent`
|
|
44
|
+
$prefix: foo;
|
|
45
|
+
.$(prefix)_bar {}
|
|
46
|
+
`,
|
|
47
|
+
});
|
|
48
|
+
const result1 = await locator1.load(getFixturePath('/test/1.css'));
|
|
49
|
+
expect(result1.tokens.map((token) => token.name)).toStrictEqual(['$(prefix)']);
|
|
50
|
+
|
|
51
|
+
// if postcssrc is found
|
|
52
|
+
const uuid = randomUUID();
|
|
53
|
+
const locator2 = new Locator({
|
|
54
|
+
transformer: createDefaultTransformer({ cwd, postcssConfig: `${uuid}/postcss.config.js` }),
|
|
55
|
+
});
|
|
56
|
+
createFixtures({
|
|
57
|
+
[`/${uuid}/postcss.config.js`]: dedent`
|
|
58
|
+
module.exports = {
|
|
59
|
+
plugins: [
|
|
60
|
+
require('${require.resolve('postcss-simple-vars')}'),
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
`,
|
|
64
|
+
'/test/1.css': dedent`
|
|
65
|
+
$prefix: foo;
|
|
66
|
+
.$(prefix)_bar {}
|
|
67
|
+
`,
|
|
68
|
+
});
|
|
69
|
+
const result2 = await locator2.load(getFixturePath('/test/1.css'));
|
|
70
|
+
expect(result2.tokens.map((token) => token.name)).toStrictEqual(['foo_bar']);
|
|
71
|
+
});
|
package/src/transformer/index.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import type { StrictlyResolver } from '../
|
|
1
|
+
import type { StrictlyResolver } from '../locator/index.js';
|
|
2
2
|
import { createLessTransformer } from './less-transformer.js';
|
|
3
|
+
import type { PostcssTransformerOptions } from './postcss-transformer.js';
|
|
4
|
+
import { createPostcssTransformer } from './postcss-transformer.js';
|
|
3
5
|
import { createScssTransformer } from './scss-transformer.js';
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -36,16 +38,22 @@ export const handleImportError = (packageName: string) => (e: unknown) => {
|
|
|
36
38
|
throw e;
|
|
37
39
|
};
|
|
38
40
|
|
|
39
|
-
export
|
|
41
|
+
export type DefaultTransformerOptions = PostcssTransformerOptions;
|
|
42
|
+
|
|
43
|
+
export const createDefaultTransformer: (defaultTransformerOptions?: DefaultTransformerOptions) => Transformer = (
|
|
44
|
+
defaultTransformerOptions,
|
|
45
|
+
) => {
|
|
40
46
|
const scssTransformer = createScssTransformer();
|
|
41
47
|
const lessTransformer = createLessTransformer();
|
|
48
|
+
const postcssTransformer = createPostcssTransformer(defaultTransformerOptions);
|
|
42
49
|
return async (source, options) => {
|
|
43
50
|
if (options.from.endsWith('.scss')) {
|
|
44
51
|
return scssTransformer(source, options);
|
|
45
52
|
} else if (options.from.endsWith('.less')) {
|
|
46
53
|
return lessTransformer(source, options);
|
|
54
|
+
} else {
|
|
55
|
+
// TODO: Support multi-stage transformations by sass and less.
|
|
56
|
+
return postcssTransformer(source, options);
|
|
47
57
|
}
|
|
48
|
-
// TODO: support postcss
|
|
49
|
-
return false;
|
|
50
58
|
};
|
|
51
59
|
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { jest } from '@jest/globals';
|
|
2
2
|
import dedent from 'dedent';
|
|
3
|
-
import {
|
|
3
|
+
import { Locator } from '../locator/index.js';
|
|
4
4
|
import { createFixtures, getFixturePath } from '../test/util.js';
|
|
5
5
|
import { createLessTransformer } from './less-transformer.js';
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
const loadSpy = jest.spyOn(
|
|
7
|
+
const locator = new Locator({ transformer: createLessTransformer() });
|
|
8
|
+
const loadSpy = jest.spyOn(locator, 'load');
|
|
9
9
|
|
|
10
10
|
afterEach(() => {
|
|
11
11
|
loadSpy.mockClear();
|
|
@@ -35,7 +35,7 @@ test('handles less features', async () => {
|
|
|
35
35
|
.c { dummy: ''; }
|
|
36
36
|
`,
|
|
37
37
|
});
|
|
38
|
-
const result = await
|
|
38
|
+
const result = await locator.load(getFixturePath('/test/1.less'));
|
|
39
39
|
|
|
40
40
|
// FIXME: The end position of 'a_2_2' is incorrect.
|
|
41
41
|
expect(result).toMatchInlineSnapshot(`
|
|
@@ -45,37 +45,37 @@ test('handles less features', async () => {
|
|
|
45
45
|
{
|
|
46
46
|
name: "b_1",
|
|
47
47
|
originalLocations: [
|
|
48
|
-
{ filePath: "<fixtures>/test/2.less", start: { line: 1, column: 1 }, end: { line: 1, column:
|
|
48
|
+
{ filePath: "<fixtures>/test/2.less", start: { line: 1, column: 1 }, end: { line: 1, column: 4 } },
|
|
49
49
|
],
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
name: "a_1",
|
|
53
53
|
originalLocations: [
|
|
54
|
-
{ filePath: "<fixtures>/test/1.less", start: { line: 2, column: 1 }, end: { line: 2, column:
|
|
54
|
+
{ filePath: "<fixtures>/test/1.less", start: { line: 2, column: 1 }, end: { line: 2, column: 4 } },
|
|
55
55
|
],
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
name: "a_2",
|
|
59
59
|
originalLocations: [
|
|
60
|
-
{ filePath: "<fixtures>/test/1.less", start: { line: 3, column: 1 }, end: { line: 3, column:
|
|
60
|
+
{ filePath: "<fixtures>/test/1.less", start: { line: 3, column: 1 }, end: { line: 3, column: 4 } },
|
|
61
61
|
],
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
64
|
name: "a_2_1",
|
|
65
65
|
originalLocations: [
|
|
66
|
-
{ filePath: "<fixtures>/test/1.less", start: { line: 6, column: 3 }, end: { line: 6, column:
|
|
66
|
+
{ filePath: "<fixtures>/test/1.less", start: { line: 6, column: 3 }, end: { line: 6, column: 8 } },
|
|
67
67
|
],
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
name: "a_2_2",
|
|
71
71
|
originalLocations: [
|
|
72
|
-
{ filePath: "<fixtures>/test/1.less", start: { line: 7, column: 3 }, end: { line: 7, column:
|
|
72
|
+
{ filePath: "<fixtures>/test/1.less", start: { line: 7, column: 3 }, end: { line: 7, column: 8 } },
|
|
73
73
|
],
|
|
74
74
|
},
|
|
75
75
|
{
|
|
76
76
|
name: "c",
|
|
77
77
|
originalLocations: [
|
|
78
|
-
{ filePath: "<fixtures>/test/3.less", start: { line: 1, column: 1 }, end: { line: 1, column:
|
|
78
|
+
{ filePath: "<fixtures>/test/3.less", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
|
|
79
79
|
],
|
|
80
80
|
},
|
|
81
81
|
],
|
|
@@ -97,10 +97,10 @@ test('tracks dependencies that have been pre-bundled by less compiler', async ()
|
|
|
97
97
|
'/test/4.less': dedent`
|
|
98
98
|
`,
|
|
99
99
|
});
|
|
100
|
-
const result = await
|
|
100
|
+
const result = await locator.load(getFixturePath('/test/1.less'));
|
|
101
101
|
|
|
102
102
|
// The files imported using @import are pre-bundled by the compiler.
|
|
103
|
-
// Therefore, `
|
|
103
|
+
// Therefore, `Locator#load` is not called to process other files.
|
|
104
104
|
expect(loadSpy).toBeCalledTimes(1);
|
|
105
105
|
expect(loadSpy).toHaveBeenNthCalledWith(1, getFixturePath('/test/1.less'));
|
|
106
106
|
|
|
@@ -121,7 +121,7 @@ test('resolves specifier using resolver', async () => {
|
|
|
121
121
|
'/node_modules/package-1/index.css': `.a {}`,
|
|
122
122
|
'/node_modules/package-2/index.less': `.a {}`,
|
|
123
123
|
});
|
|
124
|
-
const result = await
|
|
124
|
+
const result = await locator.load(getFixturePath('/test/1.less'));
|
|
125
125
|
// eslint-disable-next-line @typescript-eslint/require-array-sort-compare
|
|
126
126
|
expect(result.dependencies.sort()).toStrictEqual(
|
|
127
127
|
// eslint-disable-next-line @typescript-eslint/require-array-sort-compare
|
|
@@ -137,6 +137,6 @@ test('ignores http(s) protocol file', async () => {
|
|
|
137
137
|
@import 'https://example.com/path/less.less';
|
|
138
138
|
`,
|
|
139
139
|
});
|
|
140
|
-
const result = await
|
|
140
|
+
const result = await locator.load(getFixturePath('/test/1.less'));
|
|
141
141
|
expect(result.dependencies).toStrictEqual([]);
|
|
142
142
|
});
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { jest } from '@jest/globals';
|
|
4
|
+
import dedent from 'dedent';
|
|
5
|
+
import { Locator } from '../locator/index.js';
|
|
6
|
+
import { createFixtures, getFixturePath } from '../test/util.js';
|
|
7
|
+
import { createPostcssTransformer } from './postcss-transformer.js';
|
|
8
|
+
|
|
9
|
+
const cwd = getFixturePath('/');
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
|
|
12
|
+
// NOTE: postcss-load-config caches the configuration file using the path as a key.
|
|
13
|
+
// Therefore, change the path for each test case so that a new configuration file is always used.
|
|
14
|
+
|
|
15
|
+
test('handles postcss features', async () => {
|
|
16
|
+
const uuid = randomUUID();
|
|
17
|
+
const locator = new Locator({
|
|
18
|
+
transformer: createPostcssTransformer({
|
|
19
|
+
cwd,
|
|
20
|
+
postcssConfig: `${uuid}/postcss.config.js`,
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
createFixtures({
|
|
24
|
+
[`/${uuid}/postcss.config.js`]: dedent`
|
|
25
|
+
module.exports = {
|
|
26
|
+
plugins: [
|
|
27
|
+
require('${require.resolve('postcss-simple-vars')}'),
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
`,
|
|
31
|
+
'/test/1.css': dedent`
|
|
32
|
+
$prefix: foo;
|
|
33
|
+
.$(prefix)_bar {}
|
|
34
|
+
`,
|
|
35
|
+
});
|
|
36
|
+
const result = await locator.load(getFixturePath('/test/1.css'));
|
|
37
|
+
|
|
38
|
+
expect(result).toMatchInlineSnapshot(`
|
|
39
|
+
{
|
|
40
|
+
dependencies: [],
|
|
41
|
+
tokens: [
|
|
42
|
+
{
|
|
43
|
+
name: "foo_bar",
|
|
44
|
+
originalLocations: [
|
|
45
|
+
{ filePath: "<fixtures>/test/1.css", start: { line: 2, column: 1 }, end: { line: 2, column: 8 } },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
}
|
|
50
|
+
`);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('tracks dependencies that have been pre-bundled by postcss compiler', async () => {
|
|
54
|
+
const uuid = randomUUID();
|
|
55
|
+
const locator = new Locator({
|
|
56
|
+
transformer: createPostcssTransformer({
|
|
57
|
+
cwd,
|
|
58
|
+
postcssConfig: `${uuid}/postcss.config.js`,
|
|
59
|
+
}),
|
|
60
|
+
});
|
|
61
|
+
const loadSpy = jest.spyOn(locator, 'load');
|
|
62
|
+
createFixtures({
|
|
63
|
+
[`/${uuid}/postcss.config.js`]: dedent`
|
|
64
|
+
module.exports = {
|
|
65
|
+
plugins: [
|
|
66
|
+
require('${require.resolve('postcss-import')}'),
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
`,
|
|
70
|
+
'/test/1.css': dedent`
|
|
71
|
+
@import './2.css';
|
|
72
|
+
@import './3.css';
|
|
73
|
+
`,
|
|
74
|
+
'/test/2.css': ``,
|
|
75
|
+
'/test/3.css': `@import './4.css'`,
|
|
76
|
+
'/test/4.css': ``,
|
|
77
|
+
});
|
|
78
|
+
const result = await locator.load(getFixturePath('/test/1.css'));
|
|
79
|
+
|
|
80
|
+
// The files imported using @import are pre-bundled by the compiler.
|
|
81
|
+
// Therefore, `Locator#load` is not called to process other files.
|
|
82
|
+
expect(loadSpy).toBeCalledTimes(1);
|
|
83
|
+
expect(loadSpy).toHaveBeenNthCalledWith(1, getFixturePath('/test/1.css'));
|
|
84
|
+
|
|
85
|
+
// The files pre-bundled by the compiler are also included in `result.dependencies`
|
|
86
|
+
expect(result.dependencies).toStrictEqual(['/test/2.css', '/test/3.css', '/test/4.css'].map(getFixturePath));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('resolves specifier using resolver', async () => {
|
|
90
|
+
const uuid = randomUUID();
|
|
91
|
+
const locator = new Locator({
|
|
92
|
+
transformer: createPostcssTransformer({
|
|
93
|
+
cwd,
|
|
94
|
+
postcssConfig: `${uuid}/postcss.config.js`,
|
|
95
|
+
}),
|
|
96
|
+
});
|
|
97
|
+
createFixtures({
|
|
98
|
+
[`/${uuid}/postcss.config.js`]: dedent`
|
|
99
|
+
module.exports = {
|
|
100
|
+
plugins: [
|
|
101
|
+
// When using postcss-import, the resolver of happy-css-modules is ignored.
|
|
102
|
+
// Therefore, we test here without postcss-import.
|
|
103
|
+
// require('${require.resolve('postcss-import')}'),
|
|
104
|
+
],
|
|
105
|
+
};
|
|
106
|
+
`,
|
|
107
|
+
'/test/1.css': dedent`
|
|
108
|
+
@import 'package';
|
|
109
|
+
`,
|
|
110
|
+
'/node_modules/package/index.css': `.a {}`,
|
|
111
|
+
});
|
|
112
|
+
const result = await locator.load(getFixturePath('/test/1.css'));
|
|
113
|
+
expect(result.dependencies).toStrictEqual(['/node_modules/package/index.css'].map(getFixturePath));
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('ignores http(s) protocol file', async () => {
|
|
117
|
+
const uuid = randomUUID();
|
|
118
|
+
const locator = new Locator({
|
|
119
|
+
transformer: createPostcssTransformer({
|
|
120
|
+
cwd,
|
|
121
|
+
postcssConfig: `${uuid}/postcss.config.js`,
|
|
122
|
+
}),
|
|
123
|
+
});
|
|
124
|
+
createFixtures({
|
|
125
|
+
[`/${uuid}/postcss.config.js`]: dedent`
|
|
126
|
+
module.exports = {
|
|
127
|
+
plugins: [
|
|
128
|
+
// When using postcss-import, the resolver of happy-css-modules is ignored.
|
|
129
|
+
// Therefore, we test here without postcss-import.
|
|
130
|
+
// require('${require.resolve('postcss-import')}'),
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
`,
|
|
134
|
+
'/test/1.css': dedent`
|
|
135
|
+
@import 'http://example.com/path/http.css';
|
|
136
|
+
@import 'https://example.com/path/https.css';
|
|
137
|
+
`,
|
|
138
|
+
});
|
|
139
|
+
const result = await locator.load(getFixturePath('/test/1.css'));
|
|
140
|
+
expect(result.dependencies).toStrictEqual([]);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('returns false if postcssrc is not found', async () => {
|
|
144
|
+
const uuid = randomUUID();
|
|
145
|
+
const transformer = createPostcssTransformer({
|
|
146
|
+
cwd,
|
|
147
|
+
postcssConfig: `${uuid}/postcss.config.js`,
|
|
148
|
+
});
|
|
149
|
+
createFixtures({
|
|
150
|
+
'/test/1.css': dedent`
|
|
151
|
+
@import 'http://example.com/path/http.css';
|
|
152
|
+
@import 'https://example.com/path/https.css';
|
|
153
|
+
`,
|
|
154
|
+
});
|
|
155
|
+
expect(
|
|
156
|
+
await transformer('', {
|
|
157
|
+
from: getFixturePath('/test/1.css'),
|
|
158
|
+
isIgnoredSpecifier: () => false,
|
|
159
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
160
|
+
resolver: jest.fn() as any,
|
|
161
|
+
}),
|
|
162
|
+
).toBe(false);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('searches config from cwd if postcssConfig option is missing', async () => {
|
|
166
|
+
const uuid = randomUUID();
|
|
167
|
+
const cwd = `/${uuid}`;
|
|
168
|
+
const locator = new Locator({
|
|
169
|
+
transformer: createPostcssTransformer({
|
|
170
|
+
cwd: getFixturePath(cwd),
|
|
171
|
+
}),
|
|
172
|
+
});
|
|
173
|
+
createFixtures({
|
|
174
|
+
[`/${uuid}/postcss.config.js`]: dedent`
|
|
175
|
+
module.exports = {
|
|
176
|
+
plugins: [
|
|
177
|
+
require('${require.resolve('postcss-simple-vars')}'),
|
|
178
|
+
],
|
|
179
|
+
};
|
|
180
|
+
`,
|
|
181
|
+
'/test/1.css': dedent`
|
|
182
|
+
$prefix: foo;
|
|
183
|
+
.$(prefix)_bar {}
|
|
184
|
+
`,
|
|
185
|
+
});
|
|
186
|
+
const result = await locator.load(getFixturePath('/test/1.css'));
|
|
187
|
+
expect(result.tokens.map((token) => token.name)).toStrictEqual(['foo_bar']);
|
|
188
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { default as postcss, type Message } from 'postcss';
|
|
4
|
+
import type { Result } from 'postcss-load-config';
|
|
5
|
+
import type { Transformer } from '../index.js';
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
|
|
9
|
+
const postcssrc: typeof import('postcss-load-config') = require('postcss-load-config');
|
|
10
|
+
|
|
11
|
+
//ref: https://github.com/postcss/postcss-import#dependency-message-support
|
|
12
|
+
interface DependencyMessage extends Message {
|
|
13
|
+
type: 'dependency';
|
|
14
|
+
file: string;
|
|
15
|
+
parent: string;
|
|
16
|
+
}
|
|
17
|
+
function isDependencyMessage(message: Message): message is DependencyMessage {
|
|
18
|
+
return message.type === 'dependency';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type PostcssTransformerOptions = {
|
|
22
|
+
cwd?: string | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* The option compatible with postcss's `--config`. It is a relative or absolute path.
|
|
25
|
+
* @example '.'
|
|
26
|
+
* @example 'postcss.config.js'
|
|
27
|
+
* @example '/home/user/repository/src'
|
|
28
|
+
*/
|
|
29
|
+
postcssConfig?: string | undefined;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const createPostcssTransformer: (postcssTransformerOptions?: PostcssTransformerOptions) => Transformer = (
|
|
33
|
+
postcssTransformerOptions,
|
|
34
|
+
) => {
|
|
35
|
+
const cwd = postcssTransformerOptions?.cwd ?? process.cwd();
|
|
36
|
+
const configSearchPath = postcssTransformerOptions?.postcssConfig
|
|
37
|
+
? resolve(cwd, postcssTransformerOptions?.postcssConfig)
|
|
38
|
+
: cwd;
|
|
39
|
+
return async (source, options) => {
|
|
40
|
+
// NOTE: postcss-load-config cache the configuration file so is is not reloaded.
|
|
41
|
+
const postcssConfig: Result | undefined = await postcssrc({ cwd }, configSearchPath).catch((e) => {
|
|
42
|
+
if (e instanceof Error && e.message.includes('No PostCSS Config found')) return undefined;
|
|
43
|
+
throw e;
|
|
44
|
+
});
|
|
45
|
+
if (postcssConfig === undefined) return false;
|
|
46
|
+
|
|
47
|
+
const result = await postcss(postcssConfig.plugins).process(source, {
|
|
48
|
+
...postcssConfig.options,
|
|
49
|
+
from: options.from,
|
|
50
|
+
map: { inline: false, absolute: true },
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const dependencies = result.messages.filter(isDependencyMessage).map((message) => message.file);
|
|
54
|
+
|
|
55
|
+
return { css: result.css, map: result.map, dependencies };
|
|
56
|
+
};
|
|
57
|
+
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { jest } from '@jest/globals';
|
|
2
2
|
import dedent from 'dedent';
|
|
3
|
-
import {
|
|
3
|
+
import { Locator } from '../locator/index.js';
|
|
4
4
|
import { createFixtures, getFixturePath } from '../test/util.js';
|
|
5
5
|
import { createScssTransformer } from './scss-transformer.js';
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
const loadSpy = jest.spyOn(
|
|
7
|
+
const locator = new Locator({ transformer: createScssTransformer() });
|
|
8
|
+
const loadSpy = jest.spyOn(locator, 'load');
|
|
9
9
|
|
|
10
10
|
afterEach(() => {
|
|
11
11
|
loadSpy.mockClear();
|
|
@@ -37,7 +37,7 @@ test('handles sass features', async () => {
|
|
|
37
37
|
.d { dummy: ''; }
|
|
38
38
|
`,
|
|
39
39
|
});
|
|
40
|
-
const result = await
|
|
40
|
+
const result = await locator.load(getFixturePath('/test/1.scss'));
|
|
41
41
|
|
|
42
42
|
// NOTE: There should be only one originalLocations for 'a_2', but there are multiple.
|
|
43
43
|
// This is probably due to an incorrect sourcemap output by the sass compiler.
|
|
@@ -51,44 +51,44 @@ test('handles sass features', async () => {
|
|
|
51
51
|
{
|
|
52
52
|
name: "b_1",
|
|
53
53
|
originalLocations: [
|
|
54
|
-
{ filePath: "<fixtures>/test/2.scss", start: { line: 1, column: 1 }, end: { line: 1, column:
|
|
54
|
+
{ filePath: "<fixtures>/test/2.scss", start: { line: 1, column: 1 }, end: { line: 1, column: 4 } },
|
|
55
55
|
],
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
name: "c",
|
|
59
59
|
originalLocations: [
|
|
60
|
-
{ filePath: "<fixtures>/test/3.scss", start: { line: 1, column: 1 }, end: { line: 1, column:
|
|
60
|
+
{ filePath: "<fixtures>/test/3.scss", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
|
|
61
61
|
],
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
64
|
name: "a_1",
|
|
65
65
|
originalLocations: [
|
|
66
|
-
{ filePath: "<fixtures>/test/1.scss", start: { line: 3, column: 1 }, end: { line: 3, column:
|
|
66
|
+
{ filePath: "<fixtures>/test/1.scss", start: { line: 3, column: 1 }, end: { line: 3, column: 4 } },
|
|
67
67
|
],
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
name: "a_2",
|
|
71
71
|
originalLocations: [
|
|
72
|
-
{ filePath: "<fixtures>/test/1.scss", start: { line: 4, column: 1 }, end: { line: 4, column:
|
|
73
|
-
{ filePath: "<fixtures>/test/1.scss", start: { line: 7, column: 3 }, end: { line: 7, column:
|
|
72
|
+
{ filePath: "<fixtures>/test/1.scss", start: { line: 4, column: 1 }, end: { line: 4, column: 4 } },
|
|
73
|
+
{ filePath: "<fixtures>/test/1.scss", start: { line: 7, column: 3 }, end: { line: 7, column: 6 } },
|
|
74
74
|
],
|
|
75
75
|
},
|
|
76
76
|
{
|
|
77
77
|
name: "a_2_1",
|
|
78
78
|
originalLocations: [
|
|
79
|
-
{ filePath: "<fixtures>/test/1.scss", start: { line: 7, column: 3 }, end: { line: 7, column:
|
|
79
|
+
{ filePath: "<fixtures>/test/1.scss", start: { line: 7, column: 3 }, end: { line: 7, column: 8 } },
|
|
80
80
|
],
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
83
|
name: "a_2_2",
|
|
84
84
|
originalLocations: [
|
|
85
|
-
{ filePath: "<fixtures>/test/1.scss", start: { line: 8, column: 3 }, end: { line: 8, column:
|
|
85
|
+
{ filePath: "<fixtures>/test/1.scss", start: { line: 8, column: 3 }, end: { line: 8, column: 8 } },
|
|
86
86
|
],
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
89
|
name: "d",
|
|
90
90
|
originalLocations: [
|
|
91
|
-
{ filePath: "<fixtures>/test/4.scss", start: { line: 1, column: 1 }, end: { line: 1, column:
|
|
91
|
+
{ filePath: "<fixtures>/test/4.scss", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
|
|
92
92
|
],
|
|
93
93
|
},
|
|
94
94
|
],
|
|
@@ -110,10 +110,10 @@ test('tracks dependencies that have been pre-bundled by sass compiler', async ()
|
|
|
110
110
|
'/test/4.scss': dedent`
|
|
111
111
|
`,
|
|
112
112
|
});
|
|
113
|
-
const result = await
|
|
113
|
+
const result = await locator.load(getFixturePath('/test/1.scss'));
|
|
114
114
|
|
|
115
115
|
// The files imported using @import are pre-bundled by the compiler.
|
|
116
|
-
// Therefore, `
|
|
116
|
+
// Therefore, `Locator#load` is not called to process other files.
|
|
117
117
|
expect(loadSpy).toBeCalledTimes(1);
|
|
118
118
|
expect(loadSpy).toHaveBeenNthCalledWith(1, getFixturePath('/test/1.scss'));
|
|
119
119
|
|
|
@@ -130,7 +130,7 @@ test('resolves specifier using resolver', async () => {
|
|
|
130
130
|
'/node_modules/package-1/index.css': `.a {}`,
|
|
131
131
|
'/node_modules/package-2/index.scss': `.a {}`,
|
|
132
132
|
});
|
|
133
|
-
const result = await
|
|
133
|
+
const result = await locator.load(getFixturePath('/test/1.scss'));
|
|
134
134
|
expect(result.dependencies).toStrictEqual(
|
|
135
135
|
['/node_modules/package-1/index.css', '/node_modules/package-2/index.scss'].map(getFixturePath),
|
|
136
136
|
);
|
|
@@ -144,6 +144,6 @@ test('ignores http(s) protocol file', async () => {
|
|
|
144
144
|
@import 'https://example.com/path/scss.scss';
|
|
145
145
|
`,
|
|
146
146
|
});
|
|
147
|
-
const result = await
|
|
147
|
+
const result = await locator.load(getFixturePath('/test/1.scss'));
|
|
148
148
|
expect(result.dependencies).toStrictEqual([]);
|
|
149
149
|
});
|
|
@@ -3,41 +3,39 @@
|
|
|
3
3
|
// Therefore, the workaround is now disabled. See
|
|
4
4
|
// https://github.com/mizdra/happy-css-modules/issues/65#issuecomment-1229471950 for more information.
|
|
5
5
|
|
|
6
|
-
import type { LegacyResult } from 'sass';
|
|
7
|
-
import type { Transformer
|
|
6
|
+
import type { LegacyOptions, LegacyResult } from 'sass';
|
|
7
|
+
import type { Transformer } from './index.js';
|
|
8
8
|
import { handleImportError } from './index.js';
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
.resolver(url, { request: prev })
|
|
21
|
-
.then((resolved) => done({ file: resolved }))
|
|
22
|
-
.catch((e) => done(e));
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
(exception, result) => {
|
|
26
|
-
if (exception) {
|
|
27
|
-
reject(exception);
|
|
28
|
-
} else {
|
|
29
|
-
resolve(result!);
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
);
|
|
33
|
-
});
|
|
10
|
+
// For some reason, `util.promisify` does not work. Therefore, use the original promisify.
|
|
11
|
+
function promisifySassRender(sass: typeof import('sass')) {
|
|
12
|
+
return async (options: LegacyOptions<'async'>) => {
|
|
13
|
+
return new Promise<LegacyResult>((resolve, reject) => {
|
|
14
|
+
sass.render(options, (exception, result) => {
|
|
15
|
+
if (exception) reject(exception);
|
|
16
|
+
else resolve(result!);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
};
|
|
34
20
|
}
|
|
35
21
|
|
|
36
22
|
export const createScssTransformer: () => Transformer = () => {
|
|
37
23
|
let sass: typeof import('sass');
|
|
38
24
|
return async (source, options) => {
|
|
39
25
|
sass ??= (await import('sass').catch(handleImportError('sass'))).default;
|
|
40
|
-
const
|
|
26
|
+
const render = promisifySassRender(sass);
|
|
27
|
+
const result = await render({
|
|
28
|
+
data: source,
|
|
29
|
+
file: options.from,
|
|
30
|
+
outFile: 'DUMMY', // Required for sourcemap output.
|
|
31
|
+
sourceMap: true,
|
|
32
|
+
importer: (url, prev, done) => {
|
|
33
|
+
options
|
|
34
|
+
.resolver(url, { request: prev })
|
|
35
|
+
.then((resolved) => done({ file: resolved }))
|
|
36
|
+
.catch((e) => done(e));
|
|
37
|
+
},
|
|
38
|
+
});
|
|
41
39
|
return { css: result.css.toString(), map: result.map!.toString(), dependencies: result.stats.includedFiles };
|
|
42
40
|
};
|
|
43
41
|
};
|