happy-css-modules 0.3.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.
- package/README.md +95 -48
- package/dist/cli.js +42 -4
- package/dist/cli.js.map +1 -1
- package/dist/cli.test.js +21 -0
- package/dist/cli.test.js.map +1 -1
- package/dist/emitter/dts.js +9 -1
- package/dist/emitter/dts.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/integration-test/go-to-definition.test.js +9 -8
- package/dist/integration-test/go-to-definition.test.js.map +1 -1
- package/dist/loader/index.test.js +79 -1
- package/dist/loader/index.test.js.map +1 -1
- package/dist/loader/postcss.d.ts +5 -1
- package/dist/loader/postcss.js +26 -30
- package/dist/loader/postcss.js.map +1 -1
- package/dist/resolver/index.d.ts +3 -1
- package/dist/resolver/index.js +17 -14
- package/dist/resolver/index.js.map +1 -1
- package/dist/resolver/webpack-resolver.d.ts +23 -1
- package/dist/resolver/webpack-resolver.js +82 -59
- package/dist/resolver/webpack-resolver.js.map +1 -1
- package/dist/resolver/webpack-resolver.test.js +70 -7
- package/dist/resolver/webpack-resolver.test.js.map +1 -1
- package/dist/runner.d.ts +25 -0
- package/dist/runner.js +30 -12
- package/dist/runner.js.map +1 -1
- package/dist/runner.test.js +62 -11
- package/dist/runner.test.js.map +1 -1
- package/dist/test/tsserver.d.ts +10 -6
- package/dist/test/tsserver.js +94 -85
- package/dist/test/tsserver.js.map +1 -1
- package/dist/test/util.d.ts +1 -1
- package/dist/test/util.js +15 -8
- package/dist/test/util.js.map +1 -1
- package/dist/transformer/index.d.ts +3 -1
- package/dist/transformer/index.js +15 -9
- 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.js +1 -2
- package/dist/transformer/less-transformer.js.map +1 -1
- package/dist/transformer/less-transformer.test.js +6 -6
- 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 -64
- package/dist/transformer/scss-transformer.js.map +1 -1
- package/dist/transformer/scss-transformer.test.js +8 -8
- package/package.json +6 -3
- package/src/cli.test.ts +29 -0
- package/src/cli.ts +41 -4
- package/src/emitter/dts.ts +10 -1
- package/src/index.ts +8 -2
- package/src/integration-test/go-to-definition.test.ts +10 -8
- package/src/loader/index.test.ts +79 -1
- package/src/loader/postcss.ts +42 -40
- package/src/resolver/index.ts +21 -12
- package/src/resolver/webpack-resolver.test.ts +100 -8
- package/src/resolver/webpack-resolver.ts +111 -57
- package/src/runner.test.ts +67 -11
- package/src/runner.ts +56 -13
- package/src/test/tsserver.ts +106 -129
- package/src/test/util.ts +15 -9
- package/src/transformer/index.test.ts +71 -0
- package/src/transformer/index.ts +18 -8
- package/src/transformer/less-transformer.test.ts +6 -6
- package/src/transformer/less-transformer.ts +1 -2
- package/src/transformer/postcss-transformer.test.ts +188 -0
- package/src/transformer/postcss-transformer.ts +57 -0
- package/src/transformer/scss-transformer.test.ts +8 -8
- package/src/transformer/scss-transformer.ts +25 -78
package/src/runner.test.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { readFile, writeFile } from 'fs/promises';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
2
4
|
import { jest } from '@jest/globals';
|
|
3
5
|
import chalk from 'chalk';
|
|
4
6
|
import dedent from 'dedent';
|
|
@@ -7,6 +9,8 @@ import type { Watcher } from './runner.js';
|
|
|
7
9
|
import { run } from './runner.js';
|
|
8
10
|
import { createFixtures, exists, getFixturePath, waitForAsyncTask } from './test/util.js';
|
|
9
11
|
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
|
|
10
14
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
11
15
|
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
12
16
|
|
|
@@ -87,9 +91,9 @@ test('returns an error if the file fails to process in non-watch mode', async ()
|
|
|
87
91
|
// The error is logged to console.error.
|
|
88
92
|
expect(consoleErrorSpy).toHaveBeenCalledTimes(2);
|
|
89
93
|
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
|
90
|
-
expect(consoleErrorSpy).toHaveBeenNthCalledWith(1, chalk.red('[Error] ' + error.errors[0]));
|
|
94
|
+
expect(consoleErrorSpy).toHaveBeenNthCalledWith(1, chalk.red('[Error] ' + error.errors[0]!.stack));
|
|
91
95
|
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
|
92
|
-
expect(consoleErrorSpy).toHaveBeenNthCalledWith(2, chalk.red('[Error] ' + error.errors[1]));
|
|
96
|
+
expect(consoleErrorSpy).toHaveBeenNthCalledWith(2, chalk.red('[Error] ' + error.errors[1].stack));
|
|
93
97
|
|
|
94
98
|
// The valid files are emitted.
|
|
95
99
|
expect(await exists(getFixturePath('/test/1.css.d.ts'))).toBe(true);
|
|
@@ -116,14 +120,66 @@ describe('handles external files', () => {
|
|
|
116
120
|
test('treats imported tokens from external files the same as local tokens', async () => {
|
|
117
121
|
await run({ ...defaultOptions });
|
|
118
122
|
expect(await readFile(getFixturePath('/test/1.css.d.ts'), 'utf8')).toMatchInlineSnapshot(`
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
123
|
+
"declare const styles:
|
|
124
|
+
& Readonly<Pick<(typeof import("./2.css"))["default"], "b">>
|
|
125
|
+
& Readonly<{ "c": string }>
|
|
126
|
+
& Readonly<{ "a": string }>
|
|
127
|
+
;
|
|
128
|
+
export default styles;
|
|
129
|
+
//# sourceMappingURL=./1.css.d.ts.map
|
|
130
|
+
"
|
|
131
|
+
`);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('sassLoadPaths', async () => {
|
|
136
|
+
const sassLoadPaths = ['test/relative'];
|
|
137
|
+
createFixtures({
|
|
138
|
+
'/test/1.scss': dedent`
|
|
139
|
+
@import '2.scss';
|
|
140
|
+
`,
|
|
141
|
+
'/test/relative/2.scss': `.a { dummy: ''; }`,
|
|
142
|
+
});
|
|
143
|
+
await run({ ...defaultOptions, sassLoadPaths }); // not throw
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('lessIncludePaths', async () => {
|
|
147
|
+
const lessIncludePaths = ['test/relative'];
|
|
148
|
+
createFixtures({
|
|
149
|
+
'/test/1.less': dedent`
|
|
150
|
+
@import '2.less';
|
|
151
|
+
`,
|
|
152
|
+
'/test/relative/2.less': `.a { dummy: ''; }`,
|
|
153
|
+
});
|
|
154
|
+
await run({ ...defaultOptions, lessIncludePaths }); // not throw
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('webpackResolveAlias', async () => {
|
|
158
|
+
const webpackResolveAlias = { '@relative': 'test/relative' };
|
|
159
|
+
createFixtures({
|
|
160
|
+
'/test/1.less': dedent`
|
|
161
|
+
@import '@relative/2.less';
|
|
162
|
+
`,
|
|
163
|
+
'/test/relative/2.less': `.a { dummy: ''; }`,
|
|
164
|
+
});
|
|
165
|
+
await run({ ...defaultOptions, webpackResolveAlias }); // not throw
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('postcssConfig', async () => {
|
|
169
|
+
const uuid = randomUUID();
|
|
170
|
+
const postcssConfig = `${uuid}/postcss.config.js`;
|
|
171
|
+
createFixtures({
|
|
172
|
+
[`/${uuid}/postcss.config.js`]: dedent`
|
|
173
|
+
module.exports = {
|
|
174
|
+
plugins: [
|
|
175
|
+
require('${require.resolve('postcss-simple-vars')}'),
|
|
176
|
+
],
|
|
177
|
+
};
|
|
178
|
+
`,
|
|
179
|
+
'/test/1.css': dedent`
|
|
180
|
+
$prefix: foo;
|
|
181
|
+
.$(prefix)_bar {}
|
|
182
|
+
`,
|
|
128
183
|
});
|
|
184
|
+
await run({ ...defaultOptions, postcssConfig }); // not throw
|
|
129
185
|
});
|
package/src/runner.ts
CHANGED
|
@@ -8,7 +8,8 @@ import _glob from 'glob';
|
|
|
8
8
|
import { emitGeneratedFiles } from './emitter/index.js';
|
|
9
9
|
import { Loader } from './loader/index.js';
|
|
10
10
|
import type { Resolver } from './resolver/index.js';
|
|
11
|
-
import {
|
|
11
|
+
import { createDefaultResolver } from './resolver/index.js';
|
|
12
|
+
import { createDefaultTransformer, type Transformer } from './transformer/index.js';
|
|
12
13
|
import { isMatchByGlob } from './util.js';
|
|
13
14
|
|
|
14
15
|
const glob = util.promisify(_glob);
|
|
@@ -27,6 +28,31 @@ export interface RunnerOptions {
|
|
|
27
28
|
declarationMap?: boolean | undefined;
|
|
28
29
|
transformer?: Transformer | undefined;
|
|
29
30
|
resolver?: Resolver | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* The option compatible with sass's `--load-path`. It is an array of relative or absolute paths.
|
|
33
|
+
* @example ['src/styles']
|
|
34
|
+
* @example ['/home/user/repository/src/styles']
|
|
35
|
+
*/
|
|
36
|
+
sassLoadPaths?: string[] | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* The option compatible with less's `--include-path`. It is an array of relative or absolute paths.
|
|
39
|
+
* @example ['src/styles']
|
|
40
|
+
* @example ['/home/user/repository/src/styles']
|
|
41
|
+
*/
|
|
42
|
+
lessIncludePaths?: string[] | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* The option compatible with webpack's `resolve.alias`. It is an object consisting of a pair of alias names and relative or absolute paths.
|
|
45
|
+
* @example { style: 'src/styles', '@': 'src' }
|
|
46
|
+
* @example { style: '/home/user/repository/src/styles', '@': '/home/user/repository/src' }
|
|
47
|
+
*/
|
|
48
|
+
webpackResolveAlias?: Record<string, string> | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* The option compatible with postcss's `--config`. It is a relative or absolute path.
|
|
51
|
+
* @example '.'
|
|
52
|
+
* @example 'postcss.config.js'
|
|
53
|
+
* @example '/home/user/repository/src'
|
|
54
|
+
*/
|
|
55
|
+
postcssConfig?: string | undefined;
|
|
30
56
|
/**
|
|
31
57
|
* Silent output. Do not show "files written" messages.
|
|
32
58
|
* @default false
|
|
@@ -46,15 +72,27 @@ type OverrideProp<T, K extends keyof T, V extends T[K]> = Omit<T, K> & { [P in K
|
|
|
46
72
|
export async function run(options: OverrideProp<RunnerOptions, 'watch', true>): Promise<Watcher>;
|
|
47
73
|
export async function run(options: RunnerOptions): Promise<void>;
|
|
48
74
|
export async function run(options: RunnerOptions): Promise<Watcher | void> {
|
|
49
|
-
const
|
|
75
|
+
const cwd = options.cwd ?? process.cwd();
|
|
76
|
+
const silent = options.silent ?? false;
|
|
77
|
+
const resolver =
|
|
78
|
+
options.resolver ??
|
|
79
|
+
createDefaultResolver({
|
|
80
|
+
cwd,
|
|
81
|
+
sassLoadPaths: options.sassLoadPaths,
|
|
82
|
+
lessIncludePaths: options.lessIncludePaths,
|
|
83
|
+
webpackResolveAlias: options.webpackResolveAlias,
|
|
84
|
+
});
|
|
85
|
+
const transformer = options.transformer ?? createDefaultTransformer({ cwd, postcssConfig: options.postcssConfig });
|
|
50
86
|
const distOptions = options.outDir
|
|
51
87
|
? {
|
|
52
|
-
rootDir:
|
|
88
|
+
rootDir: cwd, // TODO: support `--rootDir` option
|
|
53
89
|
outDir: options.outDir,
|
|
54
90
|
}
|
|
55
91
|
: undefined;
|
|
92
|
+
|
|
93
|
+
const loader = new Loader({ transformer, resolver });
|
|
56
94
|
const isExternalFile = (filePath: string) => {
|
|
57
|
-
return !isMatchByGlob(filePath, options.pattern, { cwd
|
|
95
|
+
return !isMatchByGlob(filePath, options.pattern, { cwd });
|
|
58
96
|
};
|
|
59
97
|
|
|
60
98
|
async function processFile(filePath: string) {
|
|
@@ -68,32 +106,37 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
|
|
|
68
106
|
dtsFormatOptions: {
|
|
69
107
|
localsConvention: options.localsConvention,
|
|
70
108
|
},
|
|
71
|
-
silent
|
|
72
|
-
cwd
|
|
109
|
+
silent,
|
|
110
|
+
cwd,
|
|
73
111
|
isExternalFile,
|
|
74
112
|
});
|
|
75
113
|
} catch (error) {
|
|
76
|
-
|
|
77
|
-
|
|
114
|
+
if (error instanceof Error) {
|
|
115
|
+
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
|
116
|
+
console.error(chalk.red('[Error] ' + error.stack));
|
|
117
|
+
} else {
|
|
118
|
+
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
|
119
|
+
console.error(chalk.red('[Error] ' + error));
|
|
120
|
+
}
|
|
78
121
|
throw error;
|
|
79
122
|
}
|
|
80
123
|
}
|
|
81
124
|
|
|
82
125
|
if (options.watch) {
|
|
83
|
-
if (!
|
|
84
|
-
const watcher = chokidar.watch([options.pattern.replace(/\\/g, '/')],
|
|
126
|
+
if (!silent) console.log('Watch ' + options.pattern + '...');
|
|
127
|
+
const watcher = chokidar.watch([options.pattern.replace(/\\/g, '/')], { cwd });
|
|
85
128
|
watcher.on('all', (eventName, filePath) => {
|
|
86
129
|
if (eventName === 'add' || eventName === 'change') {
|
|
87
|
-
processFile(resolve(
|
|
130
|
+
processFile(resolve(cwd, filePath)).catch(() => {
|
|
88
131
|
// TODO: Emit a error by `Watcher#onerror`
|
|
89
132
|
});
|
|
90
133
|
}
|
|
91
134
|
});
|
|
92
135
|
return { close: async () => watcher.close() };
|
|
93
136
|
} else {
|
|
94
|
-
const filePaths = (await glob(options.pattern, { dot: true, cwd
|
|
137
|
+
const filePaths = (await glob(options.pattern, { dot: true, cwd }))
|
|
95
138
|
// convert relative path to absolute path
|
|
96
|
-
.map((file) => resolve(
|
|
139
|
+
.map((file) => resolve(cwd, file));
|
|
97
140
|
|
|
98
141
|
// TODO: Use `@file-cache/core` to process only files that have changed
|
|
99
142
|
const errors: unknown[] = [];
|
package/src/test/tsserver.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
|
+
import { mkdir, writeFile as nativeWriteFile } from 'fs/promises';
|
|
3
|
+
import { dirname } from 'path';
|
|
2
4
|
import { fileURLToPath } from 'url';
|
|
5
|
+
import { promisify } from 'util';
|
|
3
6
|
import serverHarness from '@typescript/server-harness';
|
|
7
|
+
import _glob from 'glob';
|
|
4
8
|
import { resolve } from 'import-meta-resolve';
|
|
5
9
|
import lineColumn from 'line-column';
|
|
10
|
+
import type { UpdateOpenRequest, DefinitionResponse, DefinitionRequest } from 'typescript/lib/protocol.js';
|
|
6
11
|
import { getFixturePath } from './util.js';
|
|
7
12
|
|
|
8
|
-
|
|
13
|
+
const glob = promisify(_glob);
|
|
14
|
+
|
|
15
|
+
async function writeFile(path: string, content: string): Promise<void> {
|
|
16
|
+
await mkdir(dirname(path), { recursive: true });
|
|
17
|
+
return nativeWriteFile(path, content, 'utf8');
|
|
18
|
+
}
|
|
9
19
|
|
|
10
20
|
type Definition = {
|
|
11
21
|
/** The path of the destination file */
|
|
@@ -28,42 +38,7 @@ type Definition = {
|
|
|
28
38
|
};
|
|
29
39
|
};
|
|
30
40
|
|
|
31
|
-
|
|
32
|
-
seq: number;
|
|
33
|
-
type: 'response';
|
|
34
|
-
command: 'definition';
|
|
35
|
-
success: boolean;
|
|
36
|
-
body: [
|
|
37
|
-
{
|
|
38
|
-
/** The path of the destination file */
|
|
39
|
-
file: string;
|
|
40
|
-
/** inclusive */
|
|
41
|
-
start: {
|
|
42
|
-
/** line, 1-based */
|
|
43
|
-
line: number;
|
|
44
|
-
/** column, 1-based */
|
|
45
|
-
offset: number;
|
|
46
|
-
};
|
|
47
|
-
/** exclusive */
|
|
48
|
-
end: {
|
|
49
|
-
/** line, 1-based */
|
|
50
|
-
line: number;
|
|
51
|
-
/** column, 1-based */
|
|
52
|
-
offset: number;
|
|
53
|
-
};
|
|
54
|
-
},
|
|
55
|
-
];
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
export async function getIdentifierDefinitions(filePath: string, identifier: string): Promise<Definition[]> {
|
|
59
|
-
const results = await getMultipleIdentifierDefinitions(filePath, [identifier]);
|
|
60
|
-
return results[0]!.definitions;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export async function getMultipleIdentifierDefinitions(
|
|
64
|
-
filePath: string,
|
|
65
|
-
identifiers: string[],
|
|
66
|
-
): Promise<{ identifier: string; definitions: Definition[] }[]> {
|
|
41
|
+
export async function createTSServer() {
|
|
67
42
|
const server = serverHarness.launchServer(
|
|
68
43
|
fileURLToPath(await resolve('typescript/lib/tsserver.js', import.meta.url)),
|
|
69
44
|
[
|
|
@@ -72,105 +47,107 @@ export async function getMultipleIdentifierDefinitions(
|
|
|
72
47
|
],
|
|
73
48
|
);
|
|
74
49
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
].join('\n');
|
|
80
|
-
|
|
81
|
-
await server.message({
|
|
82
|
-
type: 'request',
|
|
83
|
-
command: 'updateOpen',
|
|
84
|
-
arguments: {
|
|
85
|
-
changedFiles: [],
|
|
86
|
-
closedFiles: [],
|
|
87
|
-
openFiles: [
|
|
88
|
-
{
|
|
89
|
-
file: tmpFilePath,
|
|
90
|
-
fileContent: tmpFileContent,
|
|
91
|
-
projectRootPath: getFixturePath('/server-harness'),
|
|
92
|
-
scriptKindName: 'TS', // It's easy to get this wrong when copy-pasting
|
|
93
|
-
},
|
|
94
|
-
],
|
|
50
|
+
return {
|
|
51
|
+
async getIdentifierDefinitions(filePath: string, identifier: string): Promise<Definition[]> {
|
|
52
|
+
const results = await this.getMultipleIdentifierDefinitions(filePath, [identifier]);
|
|
53
|
+
return results[0]!.definitions;
|
|
95
54
|
},
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
55
|
+
async getMultipleIdentifierDefinitions(
|
|
56
|
+
filePath: string,
|
|
57
|
+
identifiers: string[],
|
|
58
|
+
): Promise<{ identifier: string; definitions: Definition[] }[]> {
|
|
59
|
+
const tmpFilePath = getFixturePath('/server-harness/tmp.ts');
|
|
60
|
+
const tmpFileContent = [
|
|
61
|
+
`import styles from '${filePath}';`,
|
|
62
|
+
...identifiers.map((identifier) => `styles.${identifier};`),
|
|
63
|
+
].join('\n');
|
|
64
|
+
await writeFile(tmpFilePath, tmpFileContent);
|
|
65
|
+
|
|
66
|
+
await this.refreshCache();
|
|
67
|
+
|
|
68
|
+
const results: { identifier: string; definitions: Definition[] }[] = [];
|
|
69
|
+
|
|
70
|
+
for (let i = 0; i < identifiers.length; i++) {
|
|
71
|
+
const response: DefinitionResponse = await server.message({
|
|
72
|
+
seq: 0,
|
|
73
|
+
type: 'request',
|
|
74
|
+
command: 'definition',
|
|
75
|
+
arguments: {
|
|
76
|
+
file: tmpFilePath,
|
|
77
|
+
line: i + 2, // line, 1-based
|
|
78
|
+
offset: 8, // column, 1-based
|
|
79
|
+
},
|
|
80
|
+
} as DefinitionRequest);
|
|
81
|
+
const definitions: Definition[] = response.body!.map((definition) => {
|
|
82
|
+
const { file, start, end } = definition;
|
|
83
|
+
const fileContent = readFileSync(file, 'utf-8');
|
|
84
|
+
const startIndex = lineColumn(fileContent).toIndex(start.line, start.offset);
|
|
85
|
+
const endIndex = lineColumn(fileContent).toIndex(end.line, end.offset);
|
|
86
|
+
const text = fileContent.slice(startIndex, endIndex);
|
|
87
|
+
return { file, text, start, end };
|
|
88
|
+
});
|
|
89
|
+
results.push({ identifier: identifiers[i]!, definitions });
|
|
90
|
+
}
|
|
91
|
+
return results;
|
|
92
|
+
},
|
|
93
|
+
async getModuleDefinitions(filePath: string): Promise<Definition[]> {
|
|
94
|
+
await this.refreshCache();
|
|
122
95
|
|
|
123
|
-
|
|
124
|
-
}
|
|
96
|
+
const tmpFilePath = getFixturePath('/server-harness/tmp.ts');
|
|
97
|
+
const tmpFileContent = `import styles from '${filePath}';`;
|
|
125
98
|
|
|
126
|
-
|
|
127
|
-
const server = serverHarness.launchServer(
|
|
128
|
-
fileURLToPath(await resolve('typescript/lib/tsserver.js', import.meta.url)),
|
|
129
|
-
[
|
|
130
|
-
// ATA generates some extra network traffic and isn't usually relevant when profiling
|
|
131
|
-
'--disableAutomaticTypingAcquisition',
|
|
132
|
-
],
|
|
133
|
-
);
|
|
99
|
+
await writeFile(tmpFilePath, tmpFileContent);
|
|
134
100
|
|
|
135
|
-
|
|
136
|
-
const tmpFileContent = `import styles from '${filePath}';`;
|
|
101
|
+
await this.refreshCache();
|
|
137
102
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
closedFiles: [],
|
|
144
|
-
openFiles: [
|
|
145
|
-
{
|
|
103
|
+
const response: DefinitionResponse = await server.message({
|
|
104
|
+
seq: 0,
|
|
105
|
+
type: 'request',
|
|
106
|
+
command: 'definition',
|
|
107
|
+
arguments: {
|
|
146
108
|
file: tmpFilePath,
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
scriptKindName: 'TS', // It's easy to get this wrong when copy-pasting
|
|
109
|
+
line: 1, // line, 1-based
|
|
110
|
+
offset: 20, // column, 1-based
|
|
150
111
|
},
|
|
151
|
-
|
|
112
|
+
} as DefinitionRequest);
|
|
113
|
+
const definitions: Definition[] = response.body!.map((definition) => {
|
|
114
|
+
const { file, start, end } = definition;
|
|
115
|
+
const fileContent = readFileSync(file, 'utf-8');
|
|
116
|
+
const startIndex = lineColumn(fileContent).toIndex(start.line, start.offset);
|
|
117
|
+
const endIndex = lineColumn(fileContent).toIndex(end.line, end.offset);
|
|
118
|
+
const text = fileContent.slice(startIndex, endIndex);
|
|
119
|
+
return { file, text, start, end };
|
|
120
|
+
});
|
|
121
|
+
return definitions;
|
|
152
122
|
},
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
123
|
+
async refreshCache() {
|
|
124
|
+
// tsserver caches the contents of opened files.
|
|
125
|
+
// When a file is updated, its cache remains with the old content.
|
|
126
|
+
// Therefore we need to overwrite the cache with the latest content.
|
|
127
|
+
|
|
128
|
+
const fixtureFilePaths = await glob(getFixturePath('/**/*.ts'), { dot: true });
|
|
129
|
+
// latest contents
|
|
130
|
+
const openFiles: UpdateOpenRequest['arguments']['openFiles'] = fixtureFilePaths.map((filePath) => ({
|
|
131
|
+
file: filePath,
|
|
132
|
+
fileContent: readFileSync(filePath, 'utf-8'),
|
|
133
|
+
projectRootPath: getFixturePath('/server-harness'),
|
|
134
|
+
scriptKindName: 'TS', // It's easy to get this wrong when copy-pasting
|
|
135
|
+
}));
|
|
136
|
+
|
|
137
|
+
// override the cache
|
|
138
|
+
await server.message({
|
|
139
|
+
seq: 0,
|
|
140
|
+
type: 'request',
|
|
141
|
+
command: 'updateOpen',
|
|
142
|
+
arguments: {
|
|
143
|
+
changedFiles: [],
|
|
144
|
+
closedFiles: [],
|
|
145
|
+
openFiles,
|
|
146
|
+
},
|
|
147
|
+
} as UpdateOpenRequest);
|
|
162
148
|
},
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const startIndex = lineColumn(fileContent).toIndex(start.line, start.offset);
|
|
168
|
-
const endIndex = lineColumn(fileContent).toIndex(end.line, end.offset);
|
|
169
|
-
const text = fileContent.slice(startIndex, endIndex);
|
|
170
|
-
return { file, text, start, end };
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
await server.message({ command: 'exit' });
|
|
174
|
-
|
|
175
|
-
return definitions;
|
|
149
|
+
exit: async () => {
|
|
150
|
+
await server.message({ command: 'exit' });
|
|
151
|
+
},
|
|
152
|
+
};
|
|
176
153
|
}
|
package/src/test/util.ts
CHANGED
|
@@ -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 { Loader } from '../loader/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 loader = new Loader({ 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 loader.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 loader.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 loader1 = new Loader({ transformer: createDefaultTransformer() });
|
|
42
|
+
createFixtures({
|
|
43
|
+
'/test/1.css': dedent`
|
|
44
|
+
$prefix: foo;
|
|
45
|
+
.$(prefix)_bar {}
|
|
46
|
+
`,
|
|
47
|
+
});
|
|
48
|
+
const result1 = await loader1.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 loader2 = new Loader({
|
|
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 loader2.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
1
|
import type { StrictlyResolver } from '../loader/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,14 +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();
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
const postcssTransformer = createPostcssTransformer(defaultTransformerOptions);
|
|
49
|
+
return async (source, options) => {
|
|
50
|
+
if (options.from.endsWith('.scss')) {
|
|
51
|
+
return scssTransformer(source, options);
|
|
52
|
+
} else if (options.from.endsWith('.less')) {
|
|
53
|
+
return lessTransformer(source, options);
|
|
54
|
+
} else {
|
|
55
|
+
// TODO: Support multi-stage transformations by sass and less.
|
|
56
|
+
return postcssTransformer(source, options);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
49
59
|
};
|