happy-css-modules 3.1.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/cli.js +6 -0
  2. package/dist/cli.js.map +1 -1
  3. package/dist/emitter/dts.d.ts +4 -3
  4. package/dist/emitter/dts.js +16 -10
  5. package/dist/emitter/dts.js.map +1 -1
  6. package/dist/emitter/dts.test.js +5 -4
  7. package/dist/emitter/dts.test.js.map +1 -1
  8. package/dist/emitter/index.d.ts +10 -2
  9. package/dist/emitter/index.js +6 -6
  10. package/dist/emitter/index.js.map +1 -1
  11. package/dist/emitter/index.test.js +9 -2
  12. package/dist/emitter/index.test.js.map +1 -1
  13. package/dist/emitter/source-map.d.ts +3 -1
  14. package/dist/emitter/source-map.js +3 -2
  15. package/dist/emitter/source-map.js.map +1 -1
  16. package/dist/emitter/source-map.test.js +4 -4
  17. package/dist/emitter/source-map.test.js.map +1 -1
  18. package/dist/locator/index.test.js +1 -2
  19. package/dist/locator/index.test.js.map +1 -1
  20. package/dist/regression-test/issue-168.test.js +1 -2
  21. package/dist/regression-test/issue-168.test.js.map +1 -1
  22. package/dist/runner.d.ts +2 -0
  23. package/dist/runner.js +6 -5
  24. package/dist/runner.js.map +1 -1
  25. package/dist/runner.test.js +29 -11
  26. package/dist/runner.test.js.map +1 -1
  27. package/dist/test-util/util.js +1 -1
  28. package/dist/transformer/index.d.ts +0 -1
  29. package/dist/transformer/less-transformer.test.js +1 -2
  30. package/dist/transformer/less-transformer.test.js.map +1 -1
  31. package/dist/transformer/postcss-transformer.test.js +2 -4
  32. package/dist/transformer/postcss-transformer.test.js.map +1 -1
  33. package/dist/transformer/scss-transformer.js +11 -29
  34. package/dist/transformer/scss-transformer.js.map +1 -1
  35. package/dist/transformer/scss-transformer.test.js +1 -2
  36. package/dist/transformer/scss-transformer.test.js.map +1 -1
  37. package/package.json +3 -3
  38. package/src/__snapshots__/runner.test.ts.snap +1 -1
  39. package/src/cli.ts +6 -0
  40. package/src/emitter/dts.test.ts +7 -4
  41. package/src/emitter/dts.ts +26 -14
  42. package/src/emitter/index.test.ts +11 -2
  43. package/src/emitter/index.ts +17 -4
  44. package/src/emitter/source-map.test.ts +4 -4
  45. package/src/emitter/source-map.ts +8 -2
  46. package/src/locator/index.test.ts +1 -2
  47. package/src/regression-test/issue-168.test.ts +1 -2
  48. package/src/runner.test.ts +35 -11
  49. package/src/runner.ts +9 -4
  50. package/src/test-util/util.ts +1 -1
  51. package/src/transformer/less-transformer.test.ts +1 -2
  52. package/src/transformer/postcss-transformer.test.ts +2 -4
  53. package/src/transformer/scss-transformer.test.ts +1 -2
  54. package/src/transformer/scss-transformer.ts +13 -29
  55. package/dist/library/source-map/index.d.ts +0 -8
  56. package/dist/library/source-map/index.js +0 -5
  57. package/dist/library/source-map/index.js.map +0 -1
  58. package/dist/test-util/jest/resolver.cjs +0 -31
  59. package/dist/test-util/jest/resolver.cjs.map +0 -1
  60. package/dist/test-util/jest/resolver.d.cts +0 -16
  61. package/src/library/README.md +0 -3
  62. package/src/library/source-map/index.ts +0 -26
  63. package/src/test-util/jest/resolver.cjs +0 -31
@@ -3,9 +3,7 @@ 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 fileCacheCore from '@file-cache/core';
7
6
  import type { CreateCacheOptions } from '@file-cache/core';
8
- import { jest } from '@jest/globals';
9
7
  import dedent from 'dedent';
10
8
  import type { RunnerOptions, Watcher } from './runner.js';
11
9
  import { createFixtures, exists, getFixturePath, waitForAsyncTask } from './test-util/util.js';
@@ -13,20 +11,23 @@ import { createFixtures, exists, getFixturePath, waitForAsyncTask } from './test
13
11
  const require = createRequire(import.meta.url);
14
12
 
15
13
  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
- },
22
- }));
14
+ vi.mock(import('@file-cache/core'), async (importOriginal) => {
15
+ const fileCacheCore = await importOriginal();
16
+ return {
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
+ },
22
+ };
23
+ });
23
24
 
24
25
  const { run } = await import('./runner.js');
25
26
 
26
27
  // eslint-disable-next-line @typescript-eslint/no-empty-function
27
- const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
28
+ const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
28
29
  // eslint-disable-next-line @typescript-eslint/no-empty-function
29
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
30
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
30
31
 
31
32
  const defaultOptions: RunnerOptions = {
32
33
  pattern: 'test/**/*.{css,scss}',
@@ -351,3 +352,26 @@ test('support symlink', async () => {
351
352
  `"{"version":3,"sources":["./1.css"],"names":["a"],"mappings":"AAAA;AAAA,E,aAAAA,G,WAAA;AAAA;AAAA","file":"1.css.d.ts","sourceRoot":""}"`,
352
353
  );
353
354
  });
355
+
356
+ test('changes output directory by outDir', async () => {
357
+ createFixtures({
358
+ '/test/1.css': '.a {}',
359
+ });
360
+
361
+ await run({ ...defaultOptions, outDir: getFixturePath('/dist'), cache: false, watch: false });
362
+
363
+ expect(await exists(getFixturePath('/dist/test/1.css.d.ts'))).toBe(true);
364
+ expect(await exists(getFixturePath('/dist/test/1.css.d.ts.map'))).toBe(true);
365
+
366
+ expect(await readFile(getFixturePath('/dist/test/1.css.d.ts'), 'utf8')).toMatchInlineSnapshot(`
367
+ "declare const styles:
368
+ & Readonly<{ "a": string }>
369
+ ;
370
+ export default styles;
371
+ //# sourceMappingURL=./1.css.d.ts.map
372
+ "
373
+ `);
374
+ expect(await readFile(getFixturePath('/dist/test/1.css.d.ts.map'), 'utf8')).toMatchInlineSnapshot(
375
+ `"{"version":3,"sources":["../../test/1.css"],"names":["a"],"mappings":"AAAA;AAAA,E,aAAAA,G,WAAA;AAAA;AAAA","file":"1.css.d.ts","sourceRoot":""}"`,
376
+ );
377
+ });
package/src/runner.ts CHANGED
@@ -2,7 +2,7 @@ import { resolve, relative } from 'path';
2
2
  import * as process from 'process';
3
3
  import { createCache } from '@file-cache/core';
4
4
  import { createNpmPackageKey } from '@file-cache/npm';
5
- import AwaitLock from 'await-lock';
5
+ import { Mutex } from 'async-mutex';
6
6
  import chalk from 'chalk';
7
7
  import * as chokidar from 'chokidar';
8
8
  import { glob } from 'glob';
@@ -79,6 +79,8 @@ export interface RunnerOptions {
79
79
  logLevel?: 'debug' | 'info' | 'silent' | undefined;
80
80
  /** Working directory path. */
81
81
  cwd?: string | undefined;
82
+ /** Output directory for generated files. */
83
+ outDir?: string | undefined;
82
84
  }
83
85
 
84
86
  type OverrideProp<T, K extends keyof T, V extends T[K]> = Omit<T, K> & { [P in K]: V };
@@ -91,8 +93,7 @@ type OverrideProp<T, K extends keyof T, V extends T[K]> = Omit<T, K> & { [P in K
91
93
  export async function run(options: OverrideProp<RunnerOptions, 'watch', true>): Promise<Watcher>;
92
94
  export async function run(options: RunnerOptions): Promise<void>;
93
95
  export async function run(options: RunnerOptions): Promise<Watcher | void> {
94
- // eslint-disable-next-line new-cap
95
- const lock = new AwaitLock.default();
96
+ const lock = new Mutex();
96
97
  const logger = new Logger(options.logLevel ?? 'info');
97
98
 
98
99
  const cwd = options.cwd ?? process.cwd();
@@ -133,13 +134,15 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
133
134
  }
134
135
 
135
136
  // Locator#load cannot be called concurrently. Therefore, it takes a lock and waits.
136
- await lock.acquireAsync();
137
+ await lock.acquire();
137
138
 
138
139
  try {
139
140
  const _isGeneratedFilesExist = await isGeneratedFilesExist(
140
141
  filePath,
141
142
  options.declarationMap,
142
143
  options.arbitraryExtensions ?? DEFAULT_ARBITRARY_EXTENSIONS,
144
+ options.outDir,
145
+ cwd,
143
146
  );
144
147
  const _isChangedFile = await isChangedFile(filePath);
145
148
  // Generate .d.ts and .d.ts.map only when the file has been updated.
@@ -159,6 +162,8 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
159
162
  arbitraryExtensions: options.arbitraryExtensions,
160
163
  },
161
164
  isExternalFile,
165
+ outDir: options.outDir,
166
+ cwd,
162
167
  });
163
168
  logger.info(chalk.green(`${relative(cwd, filePath)} (generated)`));
164
169
 
@@ -10,7 +10,7 @@ import { sleepSync } from '../util.js';
10
10
  export const FIXTURE_DIR_PATH = resolve(
11
11
  realpathSync(tmpdir()),
12
12
  'happy-css-modules/fixtures',
13
- process.env['JEST_WORKER_ID']!,
13
+ process.env['VITEST_POOL_ID']!,
14
14
  );
15
15
 
16
16
  export function createRoot(code: string, from?: string): Root {
@@ -1,11 +1,10 @@
1
- import { jest } from '@jest/globals';
2
1
  import dedent from 'dedent';
3
2
  import { Locator } from '../locator/index.js';
4
3
  import { createFixtures, getFixturePath } from '../test-util/util.js';
5
4
  import { createLessTransformer } from './less-transformer.js';
6
5
 
7
6
  const locator = new Locator({ transformer: createLessTransformer() });
8
- const loadSpy = jest.spyOn(locator, 'load');
7
+ const loadSpy = vi.spyOn(locator, 'load');
9
8
 
10
9
  afterEach(() => {
11
10
  loadSpy.mockClear();
@@ -1,6 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { createRequire } from 'node:module';
3
- import { jest } from '@jest/globals';
4
3
  import dedent from 'dedent';
5
4
  import { Locator } from '../locator/index.js';
6
5
  import { createFixtures, getFixturePath } from '../test-util/util.js';
@@ -60,7 +59,7 @@ test('tracks dependencies that have been pre-bundled by postcss compiler', async
60
59
  postcssConfig: `${uuid}/postcss.config.js`,
61
60
  }),
62
61
  });
63
- const loadSpy = jest.spyOn(locator, 'load');
62
+ const loadSpy = vi.spyOn(locator, 'load');
64
63
  createFixtures({
65
64
  [`/${uuid}/postcss.config.js`]: dedent`
66
65
  module.exports = {
@@ -158,8 +157,7 @@ test('returns false if postcssrc is not found', async () => {
158
157
  await transformer('', {
159
158
  from: getFixturePath('/test/1.css'),
160
159
  isIgnoredSpecifier: () => false,
161
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
162
- resolver: jest.fn() as any,
160
+ resolver: vi.fn(),
163
161
  }),
164
162
  ).toBe(false);
165
163
  });
@@ -1,11 +1,10 @@
1
- import { jest } from '@jest/globals';
2
1
  import dedent from 'dedent';
3
2
  import { Locator } from '../locator/index.js';
4
3
  import { createFixtures, getFixturePath } from '../test-util/util.js';
5
4
  import { createScssTransformer } from './scss-transformer.js';
6
5
 
7
6
  const locator = new Locator({ transformer: createScssTransformer() });
8
- const loadSpy = jest.spyOn(locator, 'load');
7
+ const loadSpy = vi.spyOn(locator, 'load');
9
8
 
10
9
  afterEach(() => {
11
10
  loadSpy.mockClear();
@@ -1,42 +1,26 @@
1
- // NOTE: The workaround for using sass's modern API. happy-css-modules used to use this API.
2
- // However, due to the implementation of custom resolvers, we have switched to the legacy API.
3
- // Therefore, the workaround is now disabled. See
4
- // https://github.com/mizdra/happy-css-modules/issues/65#issuecomment-1229471950 for more information.
5
-
6
- import type { LegacyOptions, LegacyResult } from 'sass';
1
+ import { fileURLToPath, pathToFileURL } from 'node:url';
2
+ import type { FileImporter } from 'sass';
3
+ import type { StrictlyResolver } from '../locator/index.js';
7
4
  import type { Transformer } from './index.js';
8
5
  import { handleImportError } from './index.js';
9
6
 
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
- };
20
- }
7
+ const createFileImporter: (resolver: StrictlyResolver) => FileImporter<'async'> = (resolver) => ({
8
+ async findFileUrl(url, context): Promise<URL> {
9
+ const path = await resolver(url, { request: fileURLToPath(context.containingUrl!) });
10
+ return pathToFileURL(path);
11
+ },
12
+ });
21
13
 
22
14
  export const createScssTransformer: () => Transformer = () => {
23
15
  let sass: typeof import('sass');
24
16
  return async (source, options) => {
25
17
  // eslint-disable-next-line require-atomic-updates
26
18
  sass ??= await import('sass').catch(handleImportError('sass'));
27
- const render = promisifySassRender(sass);
28
- const result = await render({
29
- data: source,
30
- file: options.from,
31
- outFile: 'DUMMY', // Required for sourcemap output.
19
+ const result = await sass.compileStringAsync(source, {
20
+ url: pathToFileURL(options.from),
32
21
  sourceMap: true,
33
- importer: (url, prev, done) => {
34
- options
35
- .resolver(url, { request: prev })
36
- .then((resolved) => done({ file: resolved }))
37
- .catch((e) => done(e));
38
- },
22
+ importers: [createFileImporter(options.resolver)],
39
23
  });
40
- return { css: result.css.toString(), map: result.map!.toString(), dependencies: result.stats.includedFiles };
24
+ return { css: result.css.toString(), map: JSON.stringify(result.sourceMap!), dependencies: result.loadedUrls };
41
25
  };
42
26
  };
@@ -1,8 +0,0 @@
1
- import { SourceNode as OriginalSourceNode, type CodeWithSourceMap } from 'source-map';
2
- type Chunk = string | StrictlyTypedSourceNode | Chunk[];
3
- interface StrictlyTypedSourceNode extends OriginalSourceNode {
4
- new (line: number | null, column: number | null, source: string | null): StrictlyTypedSourceNode;
5
- new (line: number | null, column: number | null, source: string | null, chunk?: Chunk, name?: string): StrictlyTypedSourceNode;
6
- }
7
- declare const SourceNode: StrictlyTypedSourceNode;
8
- export { SourceNode, type CodeWithSourceMap };
@@ -1,5 +0,0 @@
1
- import { SourceNode as OriginalSourceNode } from 'source-map';
2
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
3
- const SourceNode = OriginalSourceNode;
4
- export { SourceNode };
5
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/library/source-map/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,IAAI,kBAAkB,EAA0B,MAAM,YAAY,CAAC;AAsBtF,8DAA8D;AAC9D,MAAM,UAAU,GAA4B,kBAAyB,CAAC;AAEtE,OAAO,EAAE,UAAU,EAA0B,CAAC"}
@@ -1,31 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
4
- const nativeModule = require('node:module');
5
- // workaround for https://github.com/facebook/jest/issues/12270#issuecomment-1194746382
6
- /**
7
- * @typedef {{
8
- * basedir: string;
9
- * conditions?: Array<string>;
10
- * defaultResolver: (path: string, options: ResolverOptions) => string;
11
- * extensions?: Array<string>;
12
- * moduleDirectory?: Array<string>;
13
- * paths?: Array<string>;
14
- * packageFilter?: (pkg: any, file: string, dir: string) => any;
15
- * pathFilter?: (pkg: any, path: string, relativePath: string) => string;
16
- * rootDir?: string;
17
- * }} ResolverOptions
18
- */
19
- /** @type {(path: string, options: ResolverOptions) => string} */
20
- function resolver(module, options) {
21
- const { basedir, defaultResolver } = options;
22
- try {
23
- return defaultResolver(module, options);
24
- // eslint-disable-next-line no-unused-vars
25
- }
26
- catch (_error) {
27
- return nativeModule.createRequire(basedir).resolve(module);
28
- }
29
- }
30
- module.exports = resolver;
31
- //# sourceMappingURL=resolver.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"resolver.cjs","sourceRoot":"","sources":["../../../src/test-util/jest/resolver.cjs"],"names":[],"mappings":";;AAAA,qGAAqG;AACrG,MAAM,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;AAE5C,uFAAuF;AAEvF;;;;;;;;;;;;GAYG;AAEH,iEAAiE;AACjE,SAAS,QAAQ,CAAC,MAAM,EAAE,OAAO;IAC/B,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC;IAC7C,IAAI,CAAC;QACH,OAAO,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACxC,0CAA0C;IAC5C,CAAC;IAAC,OAAO,MAAM,EAAE,CAAC;QAChB,OAAO,YAAY,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7D,CAAC;AACH,CAAC;AAED,MAAM,CAAC,OAAO,GAAG,QAAQ,CAAC"}
@@ -1,16 +0,0 @@
1
- export = resolver;
2
- declare function resolver(path: string, options: ResolverOptions): string;
3
- declare namespace resolver {
4
- export { ResolverOptions };
5
- }
6
- type ResolverOptions = {
7
- basedir: string;
8
- conditions?: Array<string>;
9
- defaultResolver: (path: string, options: ResolverOptions) => string;
10
- extensions?: Array<string>;
11
- moduleDirectory?: Array<string>;
12
- paths?: Array<string>;
13
- packageFilter?: (pkg: any, file: string, dir: string) => any;
14
- pathFilter?: (pkg: any, path: string, relativePath: string) => string;
15
- rootDir?: string;
16
- };
@@ -1,3 +0,0 @@
1
- ## library
2
-
3
- This is a directory to put library forks or wrappers to avoid bugs.
@@ -1,26 +0,0 @@
1
- import { SourceNode as OriginalSourceNode, type CodeWithSourceMap } from 'source-map';
2
-
3
- // TODO: Open PR to mozilla/source-map
4
-
5
- // The type definitions bundled in the source-map package are incorrect.
6
- // Therefore, the type definitions are overwritten here.
7
-
8
- type Chunk = string | StrictlyTypedSourceNode | Chunk[];
9
-
10
- interface StrictlyTypedSourceNode extends OriginalSourceNode {
11
- // eslint-disable-next-line @typescript-eslint/no-misused-new
12
- new (line: number | null, column: number | null, source: string | null): StrictlyTypedSourceNode;
13
- // eslint-disable-next-line @typescript-eslint/no-misused-new
14
- new (
15
- line: number | null,
16
- column: number | null,
17
- source: string | null,
18
- chunk?: Chunk,
19
- name?: string,
20
- ): StrictlyTypedSourceNode;
21
- }
22
-
23
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
- const SourceNode: StrictlyTypedSourceNode = OriginalSourceNode as any;
25
-
26
- export { SourceNode, type CodeWithSourceMap };
@@ -1,31 +0,0 @@
1
- // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
2
- const nativeModule = require('node:module');
3
-
4
- // workaround for https://github.com/facebook/jest/issues/12270#issuecomment-1194746382
5
-
6
- /**
7
- * @typedef {{
8
- * basedir: string;
9
- * conditions?: Array<string>;
10
- * defaultResolver: (path: string, options: ResolverOptions) => string;
11
- * extensions?: Array<string>;
12
- * moduleDirectory?: Array<string>;
13
- * paths?: Array<string>;
14
- * packageFilter?: (pkg: any, file: string, dir: string) => any;
15
- * pathFilter?: (pkg: any, path: string, relativePath: string) => string;
16
- * rootDir?: string;
17
- * }} ResolverOptions
18
- */
19
-
20
- /** @type {(path: string, options: ResolverOptions) => string} */
21
- function resolver(module, options) {
22
- const { basedir, defaultResolver } = options;
23
- try {
24
- return defaultResolver(module, options);
25
- // eslint-disable-next-line no-unused-vars
26
- } catch (_error) {
27
- return nativeModule.createRequire(basedir).resolve(module);
28
- }
29
- }
30
-
31
- module.exports = resolver;