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.
- package/README.md +95 -45
- package/dist/cli.js +28 -1
- package/dist/cli.js.map +1 -1
- package/dist/cli.test.js +11 -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/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/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 +13 -0
- package/dist/runner.js +18 -6
- package/dist/runner.js.map +1 -1
- package/dist/runner.test.js +35 -8
- package/dist/runner.test.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 +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 +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 -17
- 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 +15 -0
- package/src/cli.ts +27 -1
- package/src/emitter/dts.ts +10 -1
- package/src/index.ts +8 -2
- package/src/loader/index.test.ts +79 -1
- package/src/loader/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 +38 -8
- package/src/runner.ts +31 -7
- package/src/test/util.ts +15 -9
- package/src/transformer/index.test.ts +71 -0
- package/src/transformer/index.ts +11 -3
- package/src/transformer/less-transformer.test.ts +6 -6
- 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 -27
package/src/loader/postcss.ts
CHANGED
|
@@ -11,14 +11,20 @@ export type Position = {
|
|
|
11
11
|
column: number;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
/** The location of class selector. */
|
|
15
|
-
export type Location =
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
/** The original location of class selector. If the original location is not found, all fields are `undefined`. */
|
|
15
|
+
export type Location =
|
|
16
|
+
| {
|
|
17
|
+
filePath: string;
|
|
18
|
+
/** The inclusive starting position of the node's source (compatible with postcss). */
|
|
19
|
+
start: Position;
|
|
20
|
+
/** The inclusive ending position of the node's source (compatible with postcss). */
|
|
21
|
+
end: Position;
|
|
22
|
+
}
|
|
23
|
+
| {
|
|
24
|
+
filePath: undefined;
|
|
25
|
+
start: undefined;
|
|
26
|
+
end: undefined;
|
|
27
|
+
};
|
|
22
28
|
|
|
23
29
|
function removeDependenciesPlugin(): Plugin {
|
|
24
30
|
return {
|
|
@@ -80,49 +86,45 @@ export function getOriginalLocation(rule: Rule, classSelector: ClassName): Locat
|
|
|
80
86
|
if (rule.source.end === undefined || classSelector.source.end === undefined) throw new Error('Node#end is undefined');
|
|
81
87
|
if (rule.source.input.file === undefined) throw new Error('Node#input.file is undefined');
|
|
82
88
|
|
|
83
|
-
const
|
|
89
|
+
const classSelectorStartPosition = {
|
|
84
90
|
// The line is 1-based.
|
|
85
91
|
line: rule.source.start.line + (classSelector.source.start.line - 1),
|
|
86
92
|
// The column is 1-based.
|
|
87
93
|
column: rule.source.start.column + (classSelector.source.start.column - 1),
|
|
88
94
|
};
|
|
89
|
-
const
|
|
90
|
-
line:
|
|
95
|
+
const classSelectorEndPosition = {
|
|
96
|
+
line: classSelectorStartPosition.line,
|
|
91
97
|
// The column is inclusive.
|
|
92
|
-
column:
|
|
98
|
+
column: classSelectorStartPosition.column + classSelector.value.length,
|
|
93
99
|
};
|
|
94
|
-
|
|
100
|
+
const classSelectorLocation = {
|
|
95
101
|
filePath: rule.source.input.file,
|
|
96
|
-
start,
|
|
97
|
-
end,
|
|
102
|
+
start: classSelectorStartPosition,
|
|
103
|
+
end: classSelectorEndPosition,
|
|
98
104
|
};
|
|
99
105
|
|
|
100
|
-
if (rule.source.input.map)
|
|
101
|
-
const origin = rule.source.input.origin(
|
|
102
|
-
location.start.line,
|
|
103
|
-
// The column of `Input#origin` is 0-based. This behavior is undocumented and probably a postcss's bug.
|
|
104
|
-
// TODO: Open PR to postcss/postcss
|
|
105
|
-
location.start.column - 1,
|
|
106
|
-
);
|
|
107
|
-
if (origin === false) throw new Error('`Input#origin` returned false');
|
|
108
|
-
if (origin.file === undefined) throw new Error('`FilePosition#file` is undefined');
|
|
109
|
-
|
|
110
|
-
location = {
|
|
111
|
-
filePath: origin.file,
|
|
112
|
-
start: {
|
|
113
|
-
line: origin.line,
|
|
114
|
-
// The column of `Input#origin` is 0-based.
|
|
115
|
-
column: origin.column + 1,
|
|
116
|
-
},
|
|
117
|
-
end: {
|
|
118
|
-
line: origin.line,
|
|
119
|
-
// The column of `Input#origin` is 0-based. Also, the column of happy-css-modules is inclusive.
|
|
120
|
-
column: origin.column + 1 + (classSelector.value.length - 1),
|
|
121
|
-
},
|
|
122
|
-
};
|
|
123
|
-
}
|
|
106
|
+
if (!rule.source.input.map) return classSelectorLocation;
|
|
124
107
|
|
|
125
|
-
|
|
108
|
+
const classSelectorOrigin = rule.source.input.origin(
|
|
109
|
+
classSelectorLocation.start.line,
|
|
110
|
+
// The column of `Input#origin` is 0-based. This behavior is undocumented and probably a postcss's bug.
|
|
111
|
+
// TODO: Open PR to postcss/postcss
|
|
112
|
+
classSelectorLocation.start.column - 1,
|
|
113
|
+
);
|
|
114
|
+
if (classSelectorOrigin === false || classSelectorOrigin.file === undefined) {
|
|
115
|
+
return { filePath: undefined, start: undefined, end: undefined };
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
filePath: classSelectorOrigin.file,
|
|
119
|
+
start: {
|
|
120
|
+
line: classSelectorOrigin.line,
|
|
121
|
+
column: classSelectorOrigin.column + 1,
|
|
122
|
+
},
|
|
123
|
+
end: {
|
|
124
|
+
line: classSelectorOrigin.line,
|
|
125
|
+
column: classSelectorOrigin.column + classSelector.value.length + 1,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
function isAtRuleNode(node: Node): node is AtRule {
|
|
@@ -2,7 +2,13 @@ import { createFixtures, getFixturePath } from '../test/util.js';
|
|
|
2
2
|
import { createWebpackResolver } from './webpack-resolver.js';
|
|
3
3
|
|
|
4
4
|
test('resolves specifier with css-loader mechanism', async () => {
|
|
5
|
-
const webpackResolver = createWebpackResolver(
|
|
5
|
+
const webpackResolver = createWebpackResolver({
|
|
6
|
+
cwd: getFixturePath('/'),
|
|
7
|
+
webpackResolveAlias: {
|
|
8
|
+
'@relative': 'test/alias-relative',
|
|
9
|
+
'@absolute': getFixturePath('/test/alias-absolute'),
|
|
10
|
+
},
|
|
11
|
+
});
|
|
6
12
|
const request = getFixturePath('/test/1.css');
|
|
7
13
|
createFixtures({
|
|
8
14
|
'/node_modules/package-1/index.css': `.a {}`,
|
|
@@ -12,6 +18,8 @@ test('resolves specifier with css-loader mechanism', async () => {
|
|
|
12
18
|
'/node_modules/package-4/style.css': `.a {}`,
|
|
13
19
|
'/node_modules/@scoped/package-5/index.css': `.a {}`,
|
|
14
20
|
'/node_modules/package-6/index.css': `.a {}`,
|
|
21
|
+
'/test/alias-relative/alias.css': `.a {}`,
|
|
22
|
+
'/test/alias-absolute/alias.css': `.a {}`,
|
|
15
23
|
});
|
|
16
24
|
expect(await webpackResolver('~package-1/index.css', { request })).toBe(
|
|
17
25
|
getFixturePath('/node_modules/package-1/index.css'),
|
|
@@ -25,22 +33,43 @@ test('resolves specifier with css-loader mechanism', async () => {
|
|
|
25
33
|
expect(await webpackResolver('package-6/index.css', { request })).toBe(
|
|
26
34
|
getFixturePath('/node_modules/package-6/index.css'),
|
|
27
35
|
);
|
|
36
|
+
expect(await webpackResolver('@relative/alias.css', { request })).toBe(
|
|
37
|
+
getFixturePath('/test/alias-relative/alias.css'),
|
|
38
|
+
);
|
|
39
|
+
expect(await webpackResolver('@absolute/alias.css', { request })).toBe(
|
|
40
|
+
getFixturePath('/test/alias-absolute/alias.css'),
|
|
41
|
+
);
|
|
28
42
|
});
|
|
29
43
|
|
|
30
44
|
test('resolves specifier with sass-loader mechanism', async () => {
|
|
31
|
-
const webpackResolver = createWebpackResolver({
|
|
45
|
+
const webpackResolver = createWebpackResolver({
|
|
46
|
+
cwd: getFixturePath('/'),
|
|
47
|
+
sassLoadPaths: ['test/load-paths-relative', getFixturePath('/test/load-paths-absolute')],
|
|
48
|
+
webpackResolveAlias: {
|
|
49
|
+
'@relative': 'test/alias-relative',
|
|
50
|
+
'@absolute': getFixturePath('/test/alias-absolute'),
|
|
51
|
+
},
|
|
52
|
+
});
|
|
32
53
|
const request = getFixturePath('/test/1.scss');
|
|
33
54
|
createFixtures({
|
|
34
55
|
'/node_modules/package-1/index.scss': `.a {}`,
|
|
35
|
-
'/test/
|
|
56
|
+
'/test/load-paths-relative/load-paths-relative.scss': `.a {}`,
|
|
57
|
+
'/test/load-paths-absolute/load-paths-absolute.scss': `.a {}`,
|
|
36
58
|
'/test/_partial-import.scss': `.a {}`,
|
|
59
|
+
'/test/alias-relative/alias.scss': `.a {}`,
|
|
60
|
+
'/test/alias-absolute/alias.scss': `.a {}`,
|
|
37
61
|
});
|
|
38
62
|
expect(await webpackResolver('~package-1/index.scss', { request })).toBe(
|
|
39
63
|
getFixturePath('/node_modules/package-1/index.scss'),
|
|
40
64
|
);
|
|
41
65
|
expect(await webpackResolver('~package-1', { request })).toBe(getFixturePath('/node_modules/package-1/index.scss'));
|
|
42
66
|
// ref: https://github.com/webpack-contrib/sass-loader/blob/bed9fb5799a90020d43f705ea405f85b368621d7/test/scss/import-include-paths.scss#L1
|
|
43
|
-
expect(await webpackResolver('load-paths', { request })).toBe(
|
|
67
|
+
expect(await webpackResolver('load-paths-relative', { request })).toBe(
|
|
68
|
+
getFixturePath('/test/load-paths-relative/load-paths-relative.scss'),
|
|
69
|
+
);
|
|
70
|
+
expect(await webpackResolver('load-paths-absolute', { request })).toBe(
|
|
71
|
+
getFixturePath('/test/load-paths-absolute/load-paths-absolute.scss'),
|
|
72
|
+
);
|
|
44
73
|
// https://sass-lang.com/documentation/at-rules/import#partials
|
|
45
74
|
// https://github.com/webpack-contrib/sass-loader/blob/0e9494074f69a6b6d47efea6c083a02a31a5ae84/test/sass/import-with-underscore.sass
|
|
46
75
|
expect(await webpackResolver('partial-import', { request: getFixturePath('/test/1.scss') })).toBe(
|
|
@@ -49,14 +78,30 @@ test('resolves specifier with sass-loader mechanism', async () => {
|
|
|
49
78
|
expect(await webpackResolver('test/partial-import', { request: getFixturePath('/test') })).toBe(
|
|
50
79
|
getFixturePath('/test/_partial-import.scss'),
|
|
51
80
|
);
|
|
81
|
+
expect(await webpackResolver('@relative/alias.scss', { request })).toBe(
|
|
82
|
+
getFixturePath('/test/alias-relative/alias.scss'),
|
|
83
|
+
);
|
|
84
|
+
expect(await webpackResolver('@absolute/alias.scss', { request })).toBe(
|
|
85
|
+
getFixturePath('/test/alias-absolute/alias.scss'),
|
|
86
|
+
);
|
|
52
87
|
});
|
|
53
88
|
|
|
54
89
|
test('resolves specifier with less-loader mechanism', async () => {
|
|
55
|
-
const webpackResolver = createWebpackResolver({
|
|
90
|
+
const webpackResolver = createWebpackResolver({
|
|
91
|
+
cwd: getFixturePath('/'),
|
|
92
|
+
lessIncludePaths: ['test/include-paths-relative', getFixturePath('/test/include-paths-absolute')],
|
|
93
|
+
webpackResolveAlias: {
|
|
94
|
+
'@relative': 'test/alias-relative',
|
|
95
|
+
'@absolute': getFixturePath('/test/alias-absolute'),
|
|
96
|
+
},
|
|
97
|
+
});
|
|
56
98
|
const request = getFixturePath('/test/1.less');
|
|
57
99
|
createFixtures({
|
|
58
100
|
'/node_modules/package-1/index.less': `.a {}`,
|
|
59
|
-
'/test/
|
|
101
|
+
'/test/include-paths-relative/include-paths-relative.less': `.a {}`,
|
|
102
|
+
'/test/include-paths-absolute/include-paths-absolute.less': `.a {}`,
|
|
103
|
+
'/test/alias-relative/alias.less': `.a {}`,
|
|
104
|
+
'/test/alias-absolute/alias.less': `.a {}`,
|
|
60
105
|
});
|
|
61
106
|
expect(await webpackResolver('~package-1/index.less', { request })).toBe(
|
|
62
107
|
getFixturePath('/node_modules/package-1/index.less'),
|
|
@@ -65,5 +110,16 @@ test('resolves specifier with less-loader mechanism', async () => {
|
|
|
65
110
|
// ref: https://github.com/webpack-contrib/less-loader/blob/81a0d27eb6d18e5dc550a60fc1007fdc77305b78/test/loader.test.js#L248-L253
|
|
66
111
|
// ref: https://github.com/webpack-contrib/less-loader/blob/393147064672ace986ec84aca21f69f0ab819a9c/test/fixtures/import-paths.less#L1
|
|
67
112
|
// ref: https://github.com/webpack-contrib/less-loader/blob/99d80bd290dae50375db6e17c5f56ec33754e258/test/helpers/getCodeFromLess.js#L47-L54
|
|
68
|
-
expect(await webpackResolver('include-paths', { request })).toBe(
|
|
113
|
+
expect(await webpackResolver('include-paths-relative', { request })).toBe(
|
|
114
|
+
getFixturePath('/test/include-paths-relative/include-paths-relative.less'),
|
|
115
|
+
);
|
|
116
|
+
expect(await webpackResolver('include-paths-absolute', { request })).toBe(
|
|
117
|
+
getFixturePath('/test/include-paths-absolute/include-paths-absolute.less'),
|
|
118
|
+
);
|
|
119
|
+
expect(await webpackResolver('@relative/alias.less', { request })).toBe(
|
|
120
|
+
getFixturePath('/test/alias-relative/alias.less'),
|
|
121
|
+
);
|
|
122
|
+
expect(await webpackResolver('@absolute/alias.less', { request })).toBe(
|
|
123
|
+
getFixturePath('/test/alias-absolute/alias.less'),
|
|
124
|
+
);
|
|
69
125
|
});
|
|
@@ -1,25 +1,43 @@
|
|
|
1
|
-
import { basename, dirname, join } from 'path';
|
|
1
|
+
import { basename, dirname, join, resolve } from 'path';
|
|
2
2
|
import enhancedResolve from 'enhanced-resolve';
|
|
3
3
|
import { exists } from '../util.js';
|
|
4
4
|
import type { Resolver } from './index.js';
|
|
5
5
|
|
|
6
6
|
export type WebpackResolverOptions = {
|
|
7
|
+
/** Working directory path. */
|
|
8
|
+
cwd?: string | undefined;
|
|
7
9
|
/**
|
|
8
|
-
* The option compatible with sass's `--load-path`. It is an array of absolute paths.
|
|
10
|
+
* The option compatible with sass's `--load-path`. It is an array of relative or absolute paths.
|
|
11
|
+
* @example ['src/styles']
|
|
9
12
|
* @example ['/home/user/repository/src/styles']
|
|
10
13
|
*/
|
|
11
14
|
sassLoadPaths?: string[] | undefined;
|
|
12
15
|
/**
|
|
13
|
-
* The option compatible with less's `--include-path`. It is an array of absolute paths.
|
|
16
|
+
* The option compatible with less's `--include-path`. It is an array of relative or absolute paths.
|
|
17
|
+
* @example ['src/styles']
|
|
14
18
|
* @example ['/home/user/repository/src/styles']
|
|
15
19
|
*/
|
|
16
20
|
lessIncludePaths?: string[] | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* The option compatible with webpack's `resolve.alias`. It is an object consisting of a pair of alias names and relative or absolute paths.
|
|
23
|
+
* @example { style: 'src/styles', '@': 'src' }
|
|
24
|
+
* @example { style: '/home/user/repository/src/styles', '@': '/home/user/repository/src' }
|
|
25
|
+
*/
|
|
26
|
+
webpackResolveAlias?: Record<string, string> | undefined;
|
|
17
27
|
};
|
|
18
28
|
|
|
19
29
|
// TODO: Support `resolve.alias` for Node.js API
|
|
20
30
|
export const createWebpackResolver: (webpackResolverOptions?: WebpackResolverOptions | undefined) => Resolver = (
|
|
21
31
|
webpackResolverOptions,
|
|
22
32
|
) => {
|
|
33
|
+
const cwd = webpackResolverOptions?.cwd ?? process.cwd();
|
|
34
|
+
const sassLoadPaths = webpackResolverOptions?.sassLoadPaths?.map((path) => resolve(cwd, path));
|
|
35
|
+
const lessIncludePaths = webpackResolverOptions?.lessIncludePaths?.map((path) => resolve(cwd, path));
|
|
36
|
+
const webpackResolveAlias = webpackResolverOptions?.webpackResolveAlias
|
|
37
|
+
? Object.fromEntries(
|
|
38
|
+
Object.entries(webpackResolverOptions?.webpackResolveAlias).map(([key, value]) => [key, resolve(cwd, value)]),
|
|
39
|
+
)
|
|
40
|
+
: undefined;
|
|
23
41
|
/**
|
|
24
42
|
* A resolver compatible with css-loader.
|
|
25
43
|
*
|
|
@@ -33,6 +51,7 @@ export const createWebpackResolver: (webpackResolverOptions?: WebpackResolverOpt
|
|
|
33
51
|
mainFiles: ['index', '...'],
|
|
34
52
|
extensions: ['.css', '...'],
|
|
35
53
|
preferRelative: true,
|
|
54
|
+
alias: webpackResolveAlias,
|
|
36
55
|
});
|
|
37
56
|
|
|
38
57
|
/**
|
|
@@ -48,7 +67,8 @@ export const createWebpackResolver: (webpackResolverOptions?: WebpackResolverOpt
|
|
|
48
67
|
extensions: ['.sass', '.scss', '.css'],
|
|
49
68
|
restrictions: [/\.((sa|sc|c)ss)$/i],
|
|
50
69
|
preferRelative: true,
|
|
51
|
-
|
|
70
|
+
alias: webpackResolveAlias,
|
|
71
|
+
modules: ['node_modules', ...(sassLoadPaths ?? [])],
|
|
52
72
|
});
|
|
53
73
|
|
|
54
74
|
/**
|
|
@@ -63,7 +83,8 @@ export const createWebpackResolver: (webpackResolverOptions?: WebpackResolverOpt
|
|
|
63
83
|
mainFiles: ['index', '...'],
|
|
64
84
|
extensions: ['.less', '.css'],
|
|
65
85
|
preferRelative: true,
|
|
66
|
-
|
|
86
|
+
alias: webpackResolveAlias,
|
|
87
|
+
modules: ['node_modules', ...(lessIncludePaths ?? [])],
|
|
67
88
|
});
|
|
68
89
|
|
|
69
90
|
// NOTE: In theory, `sassLoaderResolver` should only be used when the resolver is called from `sassTransformer`.
|
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);
|
|
@@ -129,27 +133,53 @@ describe('handles external files', () => {
|
|
|
129
133
|
});
|
|
130
134
|
|
|
131
135
|
test('sassLoadPaths', async () => {
|
|
132
|
-
const sassLoadPaths = ['test/relative'
|
|
136
|
+
const sassLoadPaths = ['test/relative'];
|
|
133
137
|
createFixtures({
|
|
134
138
|
'/test/1.scss': dedent`
|
|
135
139
|
@import '2.scss';
|
|
136
|
-
@import '3.scss';
|
|
137
140
|
`,
|
|
138
141
|
'/test/relative/2.scss': `.a { dummy: ''; }`,
|
|
139
|
-
'/test/absolute/3.scss': `.b { dummy: ''; }`,
|
|
140
142
|
});
|
|
141
143
|
await run({ ...defaultOptions, sassLoadPaths }); // not throw
|
|
142
144
|
});
|
|
143
145
|
|
|
144
146
|
test('lessIncludePaths', async () => {
|
|
145
|
-
const lessIncludePaths = ['test/relative'
|
|
147
|
+
const lessIncludePaths = ['test/relative'];
|
|
146
148
|
createFixtures({
|
|
147
149
|
'/test/1.less': dedent`
|
|
148
150
|
@import '2.less';
|
|
149
|
-
@import '3.less';
|
|
150
151
|
`,
|
|
151
152
|
'/test/relative/2.less': `.a { dummy: ''; }`,
|
|
152
|
-
'/test/absolute/3.less': `.b { dummy: ''; }`,
|
|
153
153
|
});
|
|
154
154
|
await run({ ...defaultOptions, lessIncludePaths }); // not throw
|
|
155
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
|
+
`,
|
|
183
|
+
});
|
|
184
|
+
await run({ ...defaultOptions, postcssConfig }); // not throw
|
|
185
|
+
});
|
package/src/runner.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { emitGeneratedFiles } from './emitter/index.js';
|
|
|
9
9
|
import { Loader } from './loader/index.js';
|
|
10
10
|
import type { Resolver } from './resolver/index.js';
|
|
11
11
|
import { createDefaultResolver } from './resolver/index.js';
|
|
12
|
-
import { type Transformer } from './transformer/index.js';
|
|
12
|
+
import { createDefaultTransformer, type Transformer } from './transformer/index.js';
|
|
13
13
|
import { isMatchByGlob } from './util.js';
|
|
14
14
|
|
|
15
15
|
const glob = util.promisify(_glob);
|
|
@@ -40,6 +40,19 @@ export interface RunnerOptions {
|
|
|
40
40
|
* @example ['/home/user/repository/src/styles']
|
|
41
41
|
*/
|
|
42
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;
|
|
43
56
|
/**
|
|
44
57
|
* Silent output. Do not show "files written" messages.
|
|
45
58
|
* @default false
|
|
@@ -61,9 +74,15 @@ export async function run(options: RunnerOptions): Promise<void>;
|
|
|
61
74
|
export async function run(options: RunnerOptions): Promise<Watcher | void> {
|
|
62
75
|
const cwd = options.cwd ?? process.cwd();
|
|
63
76
|
const silent = options.silent ?? false;
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
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 });
|
|
67
86
|
const distOptions = options.outDir
|
|
68
87
|
? {
|
|
69
88
|
rootDir: cwd, // TODO: support `--rootDir` option
|
|
@@ -71,7 +90,7 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
|
|
|
71
90
|
}
|
|
72
91
|
: undefined;
|
|
73
92
|
|
|
74
|
-
const loader = new Loader({ transformer
|
|
93
|
+
const loader = new Loader({ transformer, resolver });
|
|
75
94
|
const isExternalFile = (filePath: string) => {
|
|
76
95
|
return !isMatchByGlob(filePath, options.pattern, { cwd });
|
|
77
96
|
};
|
|
@@ -92,8 +111,13 @@ export async function run(options: RunnerOptions): Promise<Watcher | void> {
|
|
|
92
111
|
isExternalFile,
|
|
93
112
|
});
|
|
94
113
|
} catch (error) {
|
|
95
|
-
|
|
96
|
-
|
|
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
|
+
}
|
|
97
121
|
throw error;
|
|
98
122
|
}
|
|
99
123
|
}
|
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,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
|
};
|
|
@@ -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
|
],
|