happy-css-modules 0.4.0 → 0.5.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.
Files changed (63) hide show
  1. package/README.md +95 -45
  2. package/dist/cli.js +28 -1
  3. package/dist/cli.js.map +1 -1
  4. package/dist/cli.test.js +11 -0
  5. package/dist/cli.test.js.map +1 -1
  6. package/dist/emitter/dts.js +9 -1
  7. package/dist/emitter/dts.js.map +1 -1
  8. package/dist/index.d.ts +3 -2
  9. package/dist/index.js +2 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/loader/index.test.js +79 -1
  12. package/dist/loader/index.test.js.map +1 -1
  13. package/dist/loader/postcss.d.ts +5 -1
  14. package/dist/loader/postcss.js +26 -30
  15. package/dist/loader/postcss.js.map +1 -1
  16. package/dist/resolver/webpack-resolver.d.ts +12 -2
  17. package/dist/resolver/webpack-resolver.js +12 -3
  18. package/dist/resolver/webpack-resolver.js.map +1 -1
  19. package/dist/resolver/webpack-resolver.test.js +43 -7
  20. package/dist/resolver/webpack-resolver.test.js.map +1 -1
  21. package/dist/runner.d.ts +13 -0
  22. package/dist/runner.js +18 -6
  23. package/dist/runner.js.map +1 -1
  24. package/dist/runner.test.js +35 -8
  25. package/dist/runner.test.js.map +1 -1
  26. package/dist/test/util.d.ts +1 -1
  27. package/dist/test/util.js +15 -8
  28. package/dist/test/util.js.map +1 -1
  29. package/dist/transformer/index.d.ts +3 -1
  30. package/dist/transformer/index.js +7 -3
  31. package/dist/transformer/index.js.map +1 -1
  32. package/dist/transformer/index.test.d.ts +1 -0
  33. package/dist/transformer/index.test.js +66 -0
  34. package/dist/transformer/index.test.js.map +1 -0
  35. package/dist/transformer/less-transformer.test.js +6 -6
  36. package/dist/transformer/postcss-transformer.d.ts +12 -0
  37. package/dist/transformer/postcss-transformer.js +32 -0
  38. package/dist/transformer/postcss-transformer.js.map +1 -0
  39. package/dist/transformer/postcss-transformer.test.d.ts +1 -0
  40. package/dist/transformer/postcss-transformer.test.js +176 -0
  41. package/dist/transformer/postcss-transformer.test.js.map +1 -0
  42. package/dist/transformer/scss-transformer.js +19 -17
  43. package/dist/transformer/scss-transformer.js.map +1 -1
  44. package/dist/transformer/scss-transformer.test.js +8 -8
  45. package/package.json +6 -3
  46. package/src/cli.test.ts +15 -0
  47. package/src/cli.ts +27 -1
  48. package/src/emitter/dts.ts +10 -1
  49. package/src/index.ts +8 -2
  50. package/src/loader/index.test.ts +79 -1
  51. package/src/loader/postcss.ts +42 -40
  52. package/src/resolver/webpack-resolver.test.ts +63 -7
  53. package/src/resolver/webpack-resolver.ts +26 -5
  54. package/src/runner.test.ts +38 -8
  55. package/src/runner.ts +31 -7
  56. package/src/test/util.ts +15 -9
  57. package/src/transformer/index.test.ts +71 -0
  58. package/src/transformer/index.ts +11 -3
  59. package/src/transformer/less-transformer.test.ts +6 -6
  60. package/src/transformer/postcss-transformer.test.ts +188 -0
  61. package/src/transformer/postcss-transformer.ts +57 -0
  62. package/src/transformer/scss-transformer.test.ts +8 -8
  63. package/src/transformer/scss-transformer.ts +25 -27
@@ -41,37 +41,37 @@ test('handles less features', async () => {
41
41
  {
42
42
  name: "b_1",
43
43
  originalLocations: [
44
- { filePath: "<fixtures>/test/2.less", start: { line: 1, column: 1 }, end: { line: 1, column: 3 } },
44
+ { filePath: "<fixtures>/test/2.less", start: { line: 1, column: 1 }, end: { line: 1, column: 4 } },
45
45
  ],
46
46
  },
47
47
  {
48
48
  name: "a_1",
49
49
  originalLocations: [
50
- { filePath: "<fixtures>/test/1.less", start: { line: 2, column: 1 }, end: { line: 2, column: 3 } },
50
+ { filePath: "<fixtures>/test/1.less", start: { line: 2, column: 1 }, end: { line: 2, column: 4 } },
51
51
  ],
52
52
  },
53
53
  {
54
54
  name: "a_2",
55
55
  originalLocations: [
56
- { filePath: "<fixtures>/test/1.less", start: { line: 3, column: 1 }, end: { line: 3, column: 3 } },
56
+ { filePath: "<fixtures>/test/1.less", start: { line: 3, column: 1 }, end: { line: 3, column: 4 } },
57
57
  ],
58
58
  },
59
59
  {
60
60
  name: "a_2_1",
61
61
  originalLocations: [
62
- { filePath: "<fixtures>/test/1.less", start: { line: 6, column: 3 }, end: { line: 6, column: 7 } },
62
+ { filePath: "<fixtures>/test/1.less", start: { line: 6, column: 3 }, end: { line: 6, column: 8 } },
63
63
  ],
64
64
  },
65
65
  {
66
66
  name: "a_2_2",
67
67
  originalLocations: [
68
- { filePath: "<fixtures>/test/1.less", start: { line: 7, column: 3 }, end: { line: 7, column: 7 } },
68
+ { filePath: "<fixtures>/test/1.less", start: { line: 7, column: 3 }, end: { line: 7, column: 8 } },
69
69
  ],
70
70
  },
71
71
  {
72
72
  name: "c",
73
73
  originalLocations: [
74
- { filePath: "<fixtures>/test/3.less", start: { line: 1, column: 1 }, end: { line: 1, column: 1 } },
74
+ { filePath: "<fixtures>/test/3.less", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
75
75
  ],
76
76
  },
77
77
  ],
@@ -0,0 +1,12 @@
1
+ import type { Transformer } from '../index.js';
2
+ export declare type PostcssTransformerOptions = {
3
+ cwd?: string | undefined;
4
+ /**
5
+ * The option compatible with postcss's `--config`. It is a relative or absolute path.
6
+ * @example '.'
7
+ * @example 'postcss.config.js'
8
+ * @example '/home/user/repository/src'
9
+ */
10
+ postcssConfig?: string | undefined;
11
+ };
12
+ export declare const createPostcssTransformer: (postcssTransformerOptions?: PostcssTransformerOptions) => Transformer;
@@ -0,0 +1,32 @@
1
+ import { createRequire } from 'node:module';
2
+ import { resolve } from 'node:path';
3
+ import { default as postcss } from 'postcss';
4
+ const require = createRequire(import.meta.url);
5
+ const postcssrc = require('postcss-load-config');
6
+ function isDependencyMessage(message) {
7
+ return message.type === 'dependency';
8
+ }
9
+ export const createPostcssTransformer = (postcssTransformerOptions) => {
10
+ const cwd = postcssTransformerOptions?.cwd ?? process.cwd();
11
+ const configSearchPath = postcssTransformerOptions?.postcssConfig
12
+ ? resolve(cwd, postcssTransformerOptions?.postcssConfig)
13
+ : cwd;
14
+ return async (source, options) => {
15
+ // NOTE: postcss-load-config cache the configuration file so is is not reloaded.
16
+ const postcssConfig = await postcssrc({ cwd }, configSearchPath).catch((e) => {
17
+ if (e instanceof Error && e.message.includes('No PostCSS Config found'))
18
+ return undefined;
19
+ throw e;
20
+ });
21
+ if (postcssConfig === undefined)
22
+ return false;
23
+ const result = await postcss(postcssConfig.plugins).process(source, {
24
+ ...postcssConfig.options,
25
+ from: options.from,
26
+ map: { inline: false, absolute: true },
27
+ });
28
+ const dependencies = result.messages.filter(isDependencyMessage).map((message) => message.file);
29
+ return { css: result.css, map: result.map, dependencies };
30
+ };
31
+ };
32
+ //# sourceMappingURL=postcss-transformer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postcss-transformer.js","sourceRoot":"","sources":["../../src/transformer/postcss-transformer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,IAAI,OAAO,EAAgB,MAAM,SAAS,CAAC;AAI3D,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,MAAM,SAAS,GAAyC,OAAO,CAAC,qBAAqB,CAAC,CAAC;AAQvF,SAAS,mBAAmB,CAAC,OAAgB;IAC3C,OAAO,OAAO,CAAC,IAAI,KAAK,YAAY,CAAC;AACvC,CAAC;AAaD,MAAM,CAAC,MAAM,wBAAwB,GAA2E,CAC9G,yBAAyB,EACzB,EAAE;IACF,MAAM,GAAG,GAAG,yBAAyB,EAAE,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAC5D,MAAM,gBAAgB,GAAG,yBAAyB,EAAE,aAAa;QAC/D,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,yBAAyB,EAAE,aAAa,CAAC;QACxD,CAAC,CAAC,GAAG,CAAC;IACR,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE;QAC/B,gFAAgF;QAChF,MAAM,aAAa,GAAuB,MAAM,SAAS,CAAC,EAAE,GAAG,EAAE,EAAE,gBAAgB,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;YAC/F,IAAI,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAC;gBAAE,OAAO,SAAS,CAAC;YAC1F,MAAM,CAAC,CAAC;QACV,CAAC,CAAC,CAAC;QACH,IAAI,aAAa,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QAE9C,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE;YAClE,GAAG,aAAa,CAAC,OAAO;YACxB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE;SACvC,CAAC,CAAC;QAEH,MAAM,YAAY,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAEhG,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,YAAY,EAAE,CAAC;IAC5D,CAAC,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,176 @@
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 { Loader } from '../loader/index.js';
6
+ import { createFixtures, getFixturePath } from '../test/util.js';
7
+ import { createPostcssTransformer } from './postcss-transformer.js';
8
+ const cwd = getFixturePath('/');
9
+ const require = createRequire(import.meta.url);
10
+ // NOTE: postcss-load-config caches the configuration file using the path as a key.
11
+ // Therefore, change the path for each test case so that a new configuration file is always used.
12
+ test('handles postcss features', async () => {
13
+ const uuid = randomUUID();
14
+ const loader = new Loader({
15
+ transformer: createPostcssTransformer({
16
+ cwd,
17
+ postcssConfig: `${uuid}/postcss.config.js`,
18
+ }),
19
+ });
20
+ createFixtures({
21
+ [`/${uuid}/postcss.config.js`]: dedent `
22
+ module.exports = {
23
+ plugins: [
24
+ require('${require.resolve('postcss-simple-vars')}'),
25
+ ],
26
+ };
27
+ `,
28
+ '/test/1.css': dedent `
29
+ $prefix: foo;
30
+ .$(prefix)_bar {}
31
+ `,
32
+ });
33
+ const result = await loader.load(getFixturePath('/test/1.css'));
34
+ expect(result).toMatchInlineSnapshot(`
35
+ {
36
+ dependencies: [],
37
+ tokens: [
38
+ {
39
+ name: "foo_bar",
40
+ originalLocations: [
41
+ { filePath: "<fixtures>/test/1.css", start: { line: 2, column: 1 }, end: { line: 2, column: 8 } },
42
+ ],
43
+ },
44
+ ],
45
+ }
46
+ `);
47
+ });
48
+ test('tracks dependencies that have been pre-bundled by postcss compiler', async () => {
49
+ const uuid = randomUUID();
50
+ const loader = new Loader({
51
+ transformer: createPostcssTransformer({
52
+ cwd,
53
+ postcssConfig: `${uuid}/postcss.config.js`,
54
+ }),
55
+ });
56
+ const loadSpy = jest.spyOn(loader, 'load');
57
+ createFixtures({
58
+ [`/${uuid}/postcss.config.js`]: dedent `
59
+ module.exports = {
60
+ plugins: [
61
+ require('${require.resolve('postcss-import')}'),
62
+ ],
63
+ };
64
+ `,
65
+ '/test/1.css': dedent `
66
+ @import './2.css';
67
+ @import './3.css';
68
+ `,
69
+ '/test/2.css': ``,
70
+ '/test/3.css': `@import './4.css'`,
71
+ '/test/4.css': ``,
72
+ });
73
+ const result = await loader.load(getFixturePath('/test/1.css'));
74
+ // The files imported using @import are pre-bundled by the compiler.
75
+ // Therefore, `Loader#load` is not called to process other files.
76
+ expect(loadSpy).toBeCalledTimes(1);
77
+ expect(loadSpy).toHaveBeenNthCalledWith(1, getFixturePath('/test/1.css'));
78
+ // The files pre-bundled by the compiler are also included in `result.dependencies`
79
+ expect(result.dependencies).toStrictEqual(['/test/2.css', '/test/3.css', '/test/4.css'].map(getFixturePath));
80
+ });
81
+ test('resolves specifier using resolver', async () => {
82
+ const uuid = randomUUID();
83
+ const loader = new Loader({
84
+ transformer: createPostcssTransformer({
85
+ cwd,
86
+ postcssConfig: `${uuid}/postcss.config.js`,
87
+ }),
88
+ });
89
+ createFixtures({
90
+ [`/${uuid}/postcss.config.js`]: dedent `
91
+ module.exports = {
92
+ plugins: [
93
+ // When using postcss-import, the resolver of happy-css-modules is ignored.
94
+ // Therefore, we test here without postcss-import.
95
+ // require('${require.resolve('postcss-import')}'),
96
+ ],
97
+ };
98
+ `,
99
+ '/test/1.css': dedent `
100
+ @import 'package';
101
+ `,
102
+ '/node_modules/package/index.css': `.a {}`,
103
+ });
104
+ const result = await loader.load(getFixturePath('/test/1.css'));
105
+ expect(result.dependencies).toStrictEqual(['/node_modules/package/index.css'].map(getFixturePath));
106
+ });
107
+ test('ignores http(s) protocol file', async () => {
108
+ const uuid = randomUUID();
109
+ const loader = new Loader({
110
+ transformer: createPostcssTransformer({
111
+ cwd,
112
+ postcssConfig: `${uuid}/postcss.config.js`,
113
+ }),
114
+ });
115
+ createFixtures({
116
+ [`/${uuid}/postcss.config.js`]: dedent `
117
+ module.exports = {
118
+ plugins: [
119
+ // When using postcss-import, the resolver of happy-css-modules is ignored.
120
+ // Therefore, we test here without postcss-import.
121
+ // require('${require.resolve('postcss-import')}'),
122
+ ],
123
+ };
124
+ `,
125
+ '/test/1.css': dedent `
126
+ @import 'http://example.com/path/http.css';
127
+ @import 'https://example.com/path/https.css';
128
+ `,
129
+ });
130
+ const result = await loader.load(getFixturePath('/test/1.css'));
131
+ expect(result.dependencies).toStrictEqual([]);
132
+ });
133
+ test('returns false if postcssrc is not found', async () => {
134
+ const uuid = randomUUID();
135
+ const transformer = createPostcssTransformer({
136
+ cwd,
137
+ postcssConfig: `${uuid}/postcss.config.js`,
138
+ });
139
+ createFixtures({
140
+ '/test/1.css': dedent `
141
+ @import 'http://example.com/path/http.css';
142
+ @import 'https://example.com/path/https.css';
143
+ `,
144
+ });
145
+ expect(await transformer('', {
146
+ from: getFixturePath('/test/1.css'),
147
+ isIgnoredSpecifier: () => false,
148
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
149
+ resolver: jest.fn(),
150
+ })).toBe(false);
151
+ });
152
+ test('searches config from cwd if postcssConfig option is missing', async () => {
153
+ const uuid = randomUUID();
154
+ const cwd = `/${uuid}`;
155
+ const loader = new Loader({
156
+ transformer: createPostcssTransformer({
157
+ cwd: getFixturePath(cwd),
158
+ }),
159
+ });
160
+ createFixtures({
161
+ [`/${uuid}/postcss.config.js`]: dedent `
162
+ module.exports = {
163
+ plugins: [
164
+ require('${require.resolve('postcss-simple-vars')}'),
165
+ ],
166
+ };
167
+ `,
168
+ '/test/1.css': dedent `
169
+ $prefix: foo;
170
+ .$(prefix)_bar {}
171
+ `,
172
+ });
173
+ const result = await loader.load(getFixturePath('/test/1.css'));
174
+ expect(result.tokens.map((token) => token.name)).toStrictEqual(['foo_bar']);
175
+ });
176
+ //# sourceMappingURL=postcss-transformer.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postcss-transformer.test.js","sourceRoot":"","sources":["../../src/transformer/postcss-transformer.test.ts"],"names":[],"mappings":"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"}
@@ -3,9 +3,25 @@
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
  import { handleImportError } from './index.js';
6
- async function renderSass(sass, source, options) {
7
- return new Promise((resolve, reject) => {
8
- sass.render({
6
+ // For some reason, `util.promisify` does not work. Therefore, use the original promisify.
7
+ function promisifySassRender(sass) {
8
+ return async (options) => {
9
+ return new Promise((resolve, reject) => {
10
+ sass.render(options, (exception, result) => {
11
+ if (exception)
12
+ reject(exception);
13
+ else
14
+ resolve(result);
15
+ });
16
+ });
17
+ };
18
+ }
19
+ export const createScssTransformer = () => {
20
+ let sass;
21
+ return async (source, options) => {
22
+ sass ??= (await import('sass').catch(handleImportError('sass'))).default;
23
+ const render = promisifySassRender(sass);
24
+ const result = await render({
9
25
  data: source,
10
26
  file: options.from,
11
27
  outFile: 'DUMMY',
@@ -16,21 +32,7 @@ async function renderSass(sass, source, options) {
16
32
  .then((resolved) => done({ file: resolved }))
17
33
  .catch((e) => done(e));
18
34
  },
19
- }, (exception, result) => {
20
- if (exception) {
21
- reject(exception);
22
- }
23
- else {
24
- resolve(result);
25
- }
26
35
  });
27
- });
28
- }
29
- export const createScssTransformer = () => {
30
- let sass;
31
- return async (source, options) => {
32
- sass ??= (await import('sass').catch(handleImportError('sass'))).default;
33
- const result = await renderSass(sass, source, options);
34
36
  return { css: result.css.toString(), map: result.map.toString(), dependencies: result.stats.includedFiles };
35
37
  };
36
38
  };
@@ -1 +1 @@
1
- {"version":3,"file":"scss-transformer.js","sourceRoot":"","sources":["../../src/transformer/scss-transformer.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,8FAA8F;AAC9F,iDAAiD;AACjD,sGAAsG;AAItG,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE/C,KAAK,UAAU,UAAU,CAAC,IAA2B,EAAE,MAAc,EAAE,OAA2B;IAChG,OAAO,IAAI,OAAO,CAAe,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnD,IAAI,CAAC,MAAM,CACT;YACE,IAAI,EAAE,MAAM;YACZ,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,OAAO,EAAE,OAAO;YAChB,SAAS,EAAE,IAAI;YACf,QAAQ,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;gBAC5B,OAAO;qBACJ,QAAQ,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;qBAChC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;qBAC5C,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3B,CAAC;SACF,EACD,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE;YACpB,IAAI,SAAS,EAAE;gBACb,MAAM,CAAC,SAAS,CAAC,CAAC;aACnB;iBAAM;gBACL,OAAO,CAAC,MAAO,CAAC,CAAC;aAClB;QACH,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAsB,GAAG,EAAE;IAC3D,IAAI,IAA2B,CAAC;IAChC,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE;QAC/B,IAAI,KAAK,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;QACzE,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;QACvD,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,GAAI,CAAC,QAAQ,EAAE,EAAE,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC;IAC/G,CAAC,CAAC;AACJ,CAAC,CAAC"}
1
+ {"version":3,"file":"scss-transformer.js","sourceRoot":"","sources":["../../src/transformer/scss-transformer.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,8FAA8F;AAC9F,iDAAiD;AACjD,sGAAsG;AAItG,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE/C,0FAA0F;AAC1F,SAAS,mBAAmB,CAAC,IAA2B;IACtD,OAAO,KAAK,EAAE,OAA+B,EAAE,EAAE;QAC/C,OAAO,IAAI,OAAO,CAAe,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACnD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE;gBACzC,IAAI,SAAS;oBAAE,MAAM,CAAC,SAAS,CAAC,CAAC;;oBAC5B,OAAO,CAAC,MAAO,CAAC,CAAC;YACxB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAsB,GAAG,EAAE;IAC3D,IAAI,IAA2B,CAAC;IAChC,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE;QAC/B,IAAI,KAAK,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;QACzE,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC;YAC1B,IAAI,EAAE,MAAM;YACZ,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,OAAO,EAAE,OAAO;YAChB,SAAS,EAAE,IAAI;YACf,QAAQ,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;gBAC5B,OAAO;qBACJ,QAAQ,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;qBAChC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;qBAC5C,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3B,CAAC;SACF,CAAC,CAAC;QACH,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,GAAI,CAAC,QAAQ,EAAE,EAAE,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC;IAC/G,CAAC,CAAC;AACJ,CAAC,CAAC"}
@@ -46,44 +46,44 @@ test('handles sass features', async () => {
46
46
  {
47
47
  name: "b_1",
48
48
  originalLocations: [
49
- { filePath: "<fixtures>/test/2.scss", start: { line: 1, column: 1 }, end: { line: 1, column: 3 } },
49
+ { filePath: "<fixtures>/test/2.scss", start: { line: 1, column: 1 }, end: { line: 1, column: 4 } },
50
50
  ],
51
51
  },
52
52
  {
53
53
  name: "c",
54
54
  originalLocations: [
55
- { filePath: "<fixtures>/test/3.scss", start: { line: 1, column: 1 }, end: { line: 1, column: 1 } },
55
+ { filePath: "<fixtures>/test/3.scss", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
56
56
  ],
57
57
  },
58
58
  {
59
59
  name: "a_1",
60
60
  originalLocations: [
61
- { filePath: "<fixtures>/test/1.scss", start: { line: 3, column: 1 }, end: { line: 3, column: 3 } },
61
+ { filePath: "<fixtures>/test/1.scss", start: { line: 3, column: 1 }, end: { line: 3, column: 4 } },
62
62
  ],
63
63
  },
64
64
  {
65
65
  name: "a_2",
66
66
  originalLocations: [
67
- { filePath: "<fixtures>/test/1.scss", start: { line: 4, column: 1 }, end: { line: 4, column: 3 } },
68
- { filePath: "<fixtures>/test/1.scss", start: { line: 7, column: 3 }, end: { line: 7, column: 5 } },
67
+ { filePath: "<fixtures>/test/1.scss", start: { line: 4, column: 1 }, end: { line: 4, column: 4 } },
68
+ { filePath: "<fixtures>/test/1.scss", start: { line: 7, column: 3 }, end: { line: 7, column: 6 } },
69
69
  ],
70
70
  },
71
71
  {
72
72
  name: "a_2_1",
73
73
  originalLocations: [
74
- { filePath: "<fixtures>/test/1.scss", start: { line: 7, column: 3 }, end: { line: 7, column: 7 } },
74
+ { filePath: "<fixtures>/test/1.scss", start: { line: 7, column: 3 }, end: { line: 7, column: 8 } },
75
75
  ],
76
76
  },
77
77
  {
78
78
  name: "a_2_2",
79
79
  originalLocations: [
80
- { filePath: "<fixtures>/test/1.scss", start: { line: 8, column: 3 }, end: { line: 8, column: 7 } },
80
+ { filePath: "<fixtures>/test/1.scss", start: { line: 8, column: 3 }, end: { line: 8, column: 8 } },
81
81
  ],
82
82
  },
83
83
  {
84
84
  name: "d",
85
85
  originalLocations: [
86
- { filePath: "<fixtures>/test/4.scss", start: { line: 1, column: 1 }, end: { line: 1, column: 1 } },
86
+ { filePath: "<fixtures>/test/4.scss", start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
87
87
  ],
88
88
  },
89
89
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happy-css-modules",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Creates .d.ts files from CSS Modules .css files",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -35,7 +35,8 @@
35
35
  "glob": "^8.0.3",
36
36
  "import-meta-resolve": "^2.1.0",
37
37
  "minimatch": "^5.1.0",
38
- "postcss": "^8.4.14",
38
+ "postcss": "^8.4.17",
39
+ "postcss-load-config": "^4.0.1",
39
40
  "postcss-modules": "^4.3.1",
40
41
  "postcss-selector-parser": "^6.0.10",
41
42
  "postcss-value-parser": "^4.2.0",
@@ -58,7 +59,7 @@
58
59
  "@types/line-column": "^1.0.0",
59
60
  "@types/minimatch": "^5.1.2",
60
61
  "@types/node": "^18.0.6",
61
- "@types/yargs": "^17.0.12",
62
+ "@types/yargs": "^17.0.13",
62
63
  "@typescript-eslint/eslint-plugin": "^5.31.0",
63
64
  "@typescript-eslint/parser": "^5.31.0",
64
65
  "@typescript/server-harness": "^0.2.0",
@@ -71,6 +72,8 @@
71
72
  "less": "^4.1.3",
72
73
  "line-column": "^1.0.2",
73
74
  "npm-run-all": "^4.1.5",
75
+ "postcss-import": "^15.0.0",
76
+ "postcss-simple-vars": "^7.0.0",
74
77
  "prettier": "~2.7.1",
75
78
  "sass": "^1.54.3",
76
79
  "tsc-watch": "^5.0.3",
package/src/cli.test.ts CHANGED
@@ -45,6 +45,21 @@ describe('parseArgv', () => {
45
45
  // Passing a number is treated as a string
46
46
  expect(parseArgv([...baseArgs, '1.css', '--lessIncludePaths', '1']).lessIncludePaths).toStrictEqual(['1']);
47
47
  });
48
+ test('--webpackResolveAlias', () => {
49
+ expect(
50
+ parseArgv([...baseArgs, '1.css', '--webpackResolveAlias', '{ "key1": "value1", "key2": "value2" }'])
51
+ .webpackResolveAlias,
52
+ ).toStrictEqual({
53
+ key1: 'value1',
54
+ key2: 'value2',
55
+ });
56
+ });
57
+ test('--postcssConfig', () => {
58
+ expect(parseArgv([...baseArgs, '1.css', '--postcssConfig', '.']).postcssConfig).toBe('.');
59
+ expect(parseArgv([...baseArgs, '1.css', '--postcssConfig', 'postcss.config.js']).postcssConfig).toBe(
60
+ 'postcss.config.js',
61
+ );
62
+ });
48
63
  test('--silent', () => {
49
64
  expect(parseArgv([...baseArgs, '1.css', '--silent']).silent).toBe(true);
50
65
  expect(parseArgv([...baseArgs, '1.css', '--no-silent']).silent).toBe(false);
package/src/cli.ts CHANGED
@@ -13,7 +13,7 @@ const pkgJson = JSON.parse(readFileSync(resolve(dirname(fileURLToPath(import.met
13
13
  */
14
14
  export function parseArgv(argv: string[]): RunnerOptions {
15
15
  const parsedArgv = yargs(hideBin(argv))
16
- .wrap(Math.min(120, process.stdout.columns))
16
+ .wrap(Math.min(140, process.stdout.columns))
17
17
  .scriptName('hcm')
18
18
  .usage('Create .d.ts and .d.ts.map from CSS modules *.css files.\n\n$0 [options] <glob>')
19
19
  .example("$0 'src/**/*.module.css'", 'Generate .d.ts and .d.ts.map.')
@@ -22,6 +22,7 @@ export function parseArgv(argv: string[]): RunnerOptions {
22
22
  .example("$0 'src/**/*.module.css' --declarationMap=false", 'Generate .d.ts only.')
23
23
  .example("$0 'src/**/*.module.css' --sassLoadPaths=src/style", "Run with sass's `--load-path`.")
24
24
  .example("$0 'src/**/*.module.css' --lessIncludePaths=src/style", "Run with less's `--include-path`.")
25
+ .example('$0 \'src/**/*.module.css\' --webpackResolveAlias=\'{"@": "src"}\'', "Run with webpack's `resolve.alias`.")
25
26
  .detectLocale(false)
26
27
  .option('outDir', {
27
28
  type: 'string',
@@ -52,6 +53,15 @@ export function parseArgv(argv: string[]): RunnerOptions {
52
53
  nargs: 1,
53
54
  describe: "The option compatible with less's `--include-path`.",
54
55
  })
56
+ .option('webpackResolveAlias', {
57
+ string: true,
58
+ describe: "The option compatible with webpack's `resolve.alias`.",
59
+ })
60
+ // TODO: Support --noPostcssConfig option.
61
+ .option('postcssConfig', {
62
+ string: true,
63
+ describe: "The option compatible with postcss's `--config`.",
64
+ })
55
65
  .option('silent', {
56
66
  type: 'boolean',
57
67
  default: false,
@@ -64,6 +74,20 @@ export function parseArgv(argv: string[]): RunnerOptions {
64
74
  const patterns = argv._;
65
75
  // TODO: support multiple patterns
66
76
  if (patterns.length !== 1) throw new Error('Only one pattern is allowed.');
77
+ if (argv.webpackResolveAlias) {
78
+ let parsedWebpackResolveAlias: unknown;
79
+ try {
80
+ parsedWebpackResolveAlias = JSON.parse(argv.webpackResolveAlias);
81
+ } catch (e) {
82
+ throw new Error('--webpackResolveAlias must be a valid JSON string.');
83
+ }
84
+ if (typeof parsedWebpackResolveAlias !== 'object' || parsedWebpackResolveAlias === null)
85
+ throw new Error('--webpackResolveAlias must be an object');
86
+ if (!Object.keys(parsedWebpackResolveAlias).every((key) => typeof key === 'string'))
87
+ throw new Error('--webpackResolveAlias must be an object of string keys');
88
+ if (!Object.values(parsedWebpackResolveAlias).every((value) => typeof value === 'string'))
89
+ throw new Error('--webpackResolveAlias must be an object of string values');
90
+ }
67
91
  return true;
68
92
  })
69
93
  .parseSync();
@@ -76,6 +100,8 @@ export function parseArgv(argv: string[]): RunnerOptions {
76
100
  declarationMap: parsedArgv.declarationMap,
77
101
  sassLoadPaths: parsedArgv.sassLoadPaths?.map((item) => item.toString()),
78
102
  lessIncludePaths: parsedArgv.lessIncludePaths?.map((item) => item.toString()),
103
+ webpackResolveAlias: parsedArgv.webpackResolveAlias ? JSON.parse(parsedArgv.webpackResolveAlias) : undefined,
104
+ postcssConfig: parsedArgv.postcssConfig,
79
105
  silent: parsedArgv.silent,
80
106
  };
81
107
  }
@@ -65,7 +65,16 @@ function generateTokenDeclarations(
65
65
  // This is due to the sourcemap specification. Therefore, we output multiple type definitions
66
66
  // with the same name and assign a separate original position to each.
67
67
 
68
- for (const originalLocation of token.originalLocations) {
68
+ for (let originalLocation of token.originalLocations) {
69
+ if (originalLocation.filePath === undefined) {
70
+ // If the original location is not specified, fallback to the source file.
71
+ originalLocation = {
72
+ filePath: filePath,
73
+ start: { line: 1, column: 1 },
74
+ end: { line: 1, column: 1 },
75
+ };
76
+ }
77
+
69
78
  result.push(
70
79
  originalLocation.filePath === filePath || isExternalFile(originalLocation.filePath)
71
80
  ? new SourceNode(null, null, null, [
package/src/index.ts CHANGED
@@ -1,3 +1,9 @@
1
1
  export { parseArgv } from './cli.js';
2
- export { run } from './runner.js';
3
- export { type Transformer } from './transformer/index.js';
2
+ export { run, type LocalsConvention } from './runner.js';
3
+ export {
4
+ type Transformer,
5
+ type TransformerOptions,
6
+ type TransformResult,
7
+ createDefaultTransformer,
8
+ } from './transformer/index.js';
9
+ export { type Resolver, type ResolverOptions, createDefaultResolver } from './resolver/index.js';
@@ -1,6 +1,8 @@
1
1
  import fs, { readFile, writeFile } from 'fs/promises';
2
+ import { randomUUID } from 'node:crypto';
2
3
  import { jest } from '@jest/globals';
3
4
  import dedent from 'dedent';
5
+ import { createDefaultTransformer } from '../index.js';
4
6
  import { createFixtures, FIXTURE_DIR_PATH, getFixturePath } from '../test/util.js';
5
7
  import { sleepSync } from '../util.js';
6
8
 
@@ -311,7 +313,83 @@ test('throws error the composition of non-existent file', async () => {
311
313
  }).rejects.toThrowError(`Could not resolve './2.css' in '<fixtures>/test/1.css'`);
312
314
  });
313
315
 
314
- test.todo('supports sourcemap file and inline sourcemap');
316
+ describe('supports sourcemap', () => {
317
+ test('restores original locations from sourcemap', async () => {
318
+ const transformer = createDefaultTransformer();
319
+ const loader = new Loader({ transformer });
320
+ createFixtures({
321
+ '/test/1.scss': dedent`
322
+ .nesting {
323
+ dummy: '';
324
+ .nesting_child {
325
+ dummy: '';
326
+ }
327
+ }
328
+ `,
329
+ });
330
+ const result = await loader.load(getFixturePath('/test/1.scss'));
331
+ expect(result).toMatchInlineSnapshot(`
332
+ {
333
+ dependencies: [],
334
+ tokens: [
335
+ {
336
+ name: "nesting",
337
+ originalLocations: [
338
+ { filePath: "<fixtures>/test/1.scss", start: { line: 1, column: 1 }, end: { line: 1, column: 8 } },
339
+ { filePath: "<fixtures>/test/1.scss", start: { line: 3, column: 3 }, end: { line: 3, column: 10 } },
340
+ ],
341
+ },
342
+ {
343
+ name: "nesting_child",
344
+ originalLocations: [
345
+ { filePath: "<fixtures>/test/1.scss", start: { line: 3, column: 3 }, end: { line: 3, column: 16 } },
346
+ ],
347
+ },
348
+ ],
349
+ }
350
+ `);
351
+ });
352
+ test('treats originalLocation as empty if sourcemap is broken', async () => {
353
+ const uuid = randomUUID();
354
+ createFixtures({
355
+ [`/${uuid}/postcss.config.js`]: dedent`
356
+ module.exports = {
357
+ plugins: [],
358
+ };
359
+ `,
360
+ '/test/1.css': dedent`
361
+ .selector_list_a_1, .selector_list_a_2 {}
362
+ /* In postcss, including newlines in the selector list breaks the sourcemap. */
363
+ .selector_list_b_1,
364
+ .selector_list_b_2 {}
365
+ `,
366
+ });
367
+ const transformer = createDefaultTransformer({ postcssConfig: getFixturePath(`/${uuid}/postcss.config.js`) });
368
+ const loader = new Loader({ transformer });
369
+ const result = await loader.load(getFixturePath('/test/1.css'));
370
+ expect(result).toMatchInlineSnapshot(`
371
+ {
372
+ dependencies: [],
373
+ tokens: [
374
+ {
375
+ name: "selector_list_a_1",
376
+ originalLocations: [
377
+ { filePath: "<fixtures>/test/1.css", start: { line: 1, column: 1 }, end: { line: 1, column: 18 } },
378
+ ],
379
+ },
380
+ {
381
+ name: "selector_list_a_2",
382
+ originalLocations: [
383
+ { filePath: "<fixtures>/test/1.css", start: { line: 1, column: 1 }, end: { line: 1, column: 18 } },
384
+ ],
385
+ },
386
+ { name: "selector_list_b_1", originalLocations: [{}] },
387
+ { name: "selector_list_b_2", originalLocations: [{}] },
388
+ ],
389
+ }
390
+ `);
391
+ });
392
+ });
315
393
 
316
394
  test('ignores http(s) protocol file', async () => {
317
395
  createFixtures({