Skip to content

Commit cad6f07

Browse files
fix: avoid mutations of options and config (#470)
1 parent 77449e1 commit cad6f07

File tree

13 files changed

+114
-79
lines changed

13 files changed

+114
-79
lines changed

Diff for: package-lock.json

+1-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
},
4545
"dependencies": {
4646
"cosmiconfig": "^7.0.0",
47+
"klona": "^2.0.3",
4748
"loader-utils": "^2.0.0",
4849
"schema-utils": "^2.7.1",
4950
"semver": "^7.3.2"

Diff for: src/index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ export default async function loader(content, sourceMap, meta) {
4545
typeof options.postcssOptions.config === 'undefined'
4646
? true
4747
: options.postcssOptions.config;
48-
let loadedConfig = {};
48+
49+
let loadedConfig;
4950

5051
if (configOption) {
5152
try {

Diff for: src/utils.js

+24-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import path from 'path';
22
import Module from 'module';
33

4+
import { klona } from 'klona/full';
45
import { cosmiconfig } from 'cosmiconfig';
56

67
const parentModule = module;
@@ -41,7 +42,7 @@ async function loadConfig(loaderContext, config) {
4142
try {
4243
stats = await stat(loaderContext.fs, searchPath);
4344
} catch (errorIgnore) {
44-
throw new Error(`No PostCSS Config found in: ${searchPath}`);
45+
throw new Error(`No PostCSS config found in: ${searchPath}`);
4546
}
4647

4748
const explorer = cosmiconfig('postcss');
@@ -62,25 +63,26 @@ async function loadConfig(loaderContext, config) {
6263
return {};
6364
}
6465

65-
let resultConfig = result.config || {};
66+
loaderContext.addDependency(result.filepath);
6667

67-
if (typeof resultConfig === 'function') {
68+
if (result.isEmpty) {
69+
return result;
70+
}
71+
72+
if (typeof result.config === 'function') {
6873
const api = {
69-
env: process.env.NODE_ENV,
7074
mode: loaderContext.mode,
7175
file: loaderContext.resourcePath,
7276
// For complex use
7377
webpackLoaderContext: loaderContext,
7478
};
7579

76-
resultConfig = resultConfig(api);
80+
result.config = result.config(api);
7781
}
7882

79-
resultConfig.file = result.filepath;
80-
81-
loaderContext.addDependency(resultConfig.file);
83+
result = klona(result);
8284

83-
return resultConfig;
85+
return result;
8486
}
8587

8688
function loadPlugin(plugin, options, file) {
@@ -160,7 +162,11 @@ function pluginFactory() {
160162
};
161163
}
162164

163-
function getPostcssOptions(loaderContext, config, postcssOptions = {}) {
165+
function getPostcssOptions(
166+
loaderContext,
167+
loadedConfig = {},
168+
postcssOptions = {}
169+
) {
164170
const file = loaderContext.resourcePath;
165171

166172
let normalizedPostcssOptions = postcssOptions;
@@ -174,7 +180,10 @@ function getPostcssOptions(loaderContext, config, postcssOptions = {}) {
174180
try {
175181
const factory = pluginFactory();
176182

177-
factory(config.plugins);
183+
if (loadedConfig.config && loadedConfig.config.plugins) {
184+
factory(loadedConfig.config.plugins);
185+
}
186+
178187
factory(normalizedPostcssOptions.plugins);
179188

180189
plugins = [...factory()].map((item) => {
@@ -190,27 +199,26 @@ function getPostcssOptions(loaderContext, config, postcssOptions = {}) {
190199
loaderContext.emitError(error);
191200
}
192201

193-
const processOptionsFromConfig = { ...config };
202+
const processOptionsFromConfig = loadedConfig.config || {};
194203

195204
if (processOptionsFromConfig.from) {
196205
processOptionsFromConfig.from = path.resolve(
197-
path.dirname(config.file),
206+
path.dirname(loadedConfig.filepath),
198207
processOptionsFromConfig.from
199208
);
200209
}
201210

202211
if (processOptionsFromConfig.to) {
203212
processOptionsFromConfig.to = path.resolve(
204-
path.dirname(config.file),
213+
path.dirname(loadedConfig.filepath),
205214
processOptionsFromConfig.to
206215
);
207216
}
208217

209218
// No need them for processOptions
210219
delete processOptionsFromConfig.plugins;
211-
delete processOptionsFromConfig.file;
212220

213-
const processOptionsFromOptions = { ...normalizedPostcssOptions };
221+
const processOptionsFromOptions = klona(normalizedPostcssOptions);
214222

215223
if (processOptionsFromOptions.from) {
216224
processOptionsFromOptions.from = path.resolve(

Diff for: test/__snapshots__/postcssOptins.test.js.snap

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ exports[`"postcssOptions" option should throw an error with the "config" option
2323
exports[`"postcssOptions" option should throw an error with the "config" option on the unresolved config: errors 1`] = `
2424
Array [
2525
"ModuleBuildError: Module build failed (from \`replaced original path\`):
26-
Error: No PostCSS Config found in: /test/fixtures/config-scope/css/unresolve.js",
26+
Error: No PostCSS config found in: /test/fixtures/config-scope/css/unresolve.js",
2727
]
2828
`;
2929

Diff for: test/config-autoload.test.js

+56-59
Original file line numberDiff line numberDiff line change
@@ -12,96 +12,93 @@ const loaderContext = {
1212

1313
describe('autoload config', () => {
1414
it('should load ".postcssrc"', async () => {
15-
const expected = (config) => {
16-
expect(config.map).toEqual(false);
17-
expect(config.from).toEqual('./test/rc/fixtures/index.css');
18-
expect(config.to).toEqual('./test/rc/expect/index.css');
19-
expect(Object.keys(config.plugins).length).toEqual(2);
20-
expect(config.file).toEqual(
21-
path.resolve(testDirectory, 'rc', '.postcssrc')
22-
);
23-
};
24-
25-
const config = await loadConfig(
15+
const loadedConfig = await loadConfig(
2616
loaderContext,
2717
path.resolve(testDirectory, 'rc')
2818
);
2919

30-
expected(config);
20+
expect(loadedConfig.config.map).toEqual(false);
21+
expect(loadedConfig.config.from).toEqual('./test/rc/fixtures/index.css');
22+
expect(loadedConfig.config.to).toEqual('./test/rc/expect/index.css');
23+
expect(Object.keys(loadedConfig.config.plugins).length).toEqual(2);
24+
expect(loadedConfig.filepath).toEqual(
25+
path.resolve(testDirectory, 'rc', '.postcssrc')
26+
);
3127
});
3228

3329
it('should load "package.json"', async () => {
34-
const expected = (config) => {
35-
expect(config.parser).toEqual(false);
36-
expect(config.syntax).toEqual(false);
37-
expect(config.map).toEqual(false);
38-
expect(config.from).toEqual('./index.css');
39-
expect(config.to).toEqual('./index.css');
40-
expect(Object.keys(config.plugins).length).toEqual(2);
41-
expect(config.file).toEqual(
42-
path.resolve(testDirectory, 'pkg', 'package.json')
43-
);
44-
};
45-
46-
const config = await loadConfig(
30+
const loadedConfig = await loadConfig(
4731
loaderContext,
4832
path.resolve(testDirectory, 'pkg')
4933
);
5034

51-
expected(config);
35+
expect(loadedConfig.config.parser).toEqual(false);
36+
expect(loadedConfig.config.syntax).toEqual(false);
37+
expect(loadedConfig.config.map).toEqual(false);
38+
expect(loadedConfig.config.from).toEqual('./index.css');
39+
expect(loadedConfig.config.to).toEqual('./index.css');
40+
expect(Object.keys(loadedConfig.config.plugins).length).toEqual(2);
41+
expect(loadedConfig.filepath).toEqual(
42+
path.resolve(testDirectory, 'pkg', 'package.json')
43+
);
5244
});
5345

5446
it('should load "postcss.config.js" with "Object" syntax of plugins', async () => {
55-
const expected = (config) => {
56-
expect(config.map).toEqual(false);
57-
expect(config.from).toEqual(
58-
'./test/fixtures/config-autoload/js/object/index.css'
59-
);
60-
expect(config.to).toEqual(
61-
'./test/fixtures/config-autoload/js/object/expect/index.css'
62-
);
63-
expect(Object.keys(config.plugins).length).toEqual(2);
64-
expect(config.file).toEqual(
65-
path.resolve(testDirectory, 'js/object', 'postcss.config.js')
66-
);
67-
};
68-
69-
const config = await loadConfig(
47+
const loadedConfig = await loadConfig(
7048
loaderContext,
7149
path.resolve(testDirectory, 'js/object')
7250
);
7351

74-
expected(config);
52+
expect(loadedConfig.config.map).toEqual(false);
53+
expect(loadedConfig.config.from).toEqual(
54+
'./test/fixtures/config-autoload/js/object/index.css'
55+
);
56+
expect(loadedConfig.config.to).toEqual(
57+
'./test/fixtures/config-autoload/js/object/expect/index.css'
58+
);
59+
expect(Object.keys(loadedConfig.config.plugins).length).toEqual(2);
60+
expect(loadedConfig.filepath).toEqual(
61+
path.resolve(testDirectory, 'js/object', 'postcss.config.js')
62+
);
7563
});
7664

7765
it('should load "postcss.config.js" with "Array" syntax of plugins', async () => {
78-
const expected = (config) => {
79-
expect(config.map).toEqual(false);
80-
expect(config.from).toEqual(
81-
'./test/fixtures/config-autoload/js/object/index.css'
82-
);
83-
expect(config.to).toEqual(
84-
'./test/fixtures/config-autoload/js/object/expect/index.css'
85-
);
86-
expect(Object.keys(config.plugins).length).toEqual(2);
87-
expect(config.file).toEqual(
88-
path.resolve(testDirectory, 'js/array', 'postcss.config.js')
89-
);
90-
};
91-
92-
const config = await loadConfig(
66+
const loadedConfig = await loadConfig(
9367
loaderContext,
9468
path.resolve(testDirectory, 'js/array')
9569
);
9670

97-
expected(config);
71+
expect(loadedConfig.config.map).toEqual(false);
72+
expect(loadedConfig.config.from).toEqual(
73+
'./test/fixtures/config-autoload/js/object/index.css'
74+
);
75+
expect(loadedConfig.config.to).toEqual(
76+
'./test/fixtures/config-autoload/js/object/expect/index.css'
77+
);
78+
expect(Object.keys(loadedConfig.config.plugins).length).toEqual(2);
79+
expect(loadedConfig.filepath).toEqual(
80+
path.resolve(testDirectory, 'js/array', 'postcss.config.js')
81+
);
82+
});
83+
84+
it('should load empty ".postcssrc"', async () => {
85+
const loadedConfig = await loadConfig(
86+
loaderContext,
87+
path.resolve(testDirectory, 'empty/.postcssrc')
88+
);
89+
90+
// eslint-disable-next-line no-undefined
91+
expect(loadedConfig.config).toEqual(undefined);
92+
expect(loadedConfig.filepath).toEqual(
93+
path.resolve(testDirectory, 'empty/.postcssrc')
94+
);
9895
});
9996

10097
it('should throw an error on "unresolved" config', async () => {
10198
try {
10299
await loadConfig(loaderContext, path.resolve('unresolved'));
103100
} catch (error) {
104-
expect(error.message).toMatch(/^No PostCSS Config found in: (.*)$/);
101+
expect(error.message).toMatch(/^No PostCSS config found in: (.*)$/);
105102
}
106103
});
107104
});

Diff for: test/fixtures/config-autoload/empty/.postcssrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

Diff for: test/fixtures/config-autoload/empty/expect/index.css

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.import {
2+
color: red;
3+
}
4+
5+
.test {
6+
color: blue;
7+
}

Diff for: test/fixtures/config-autoload/empty/expect/index.sss

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.import {
2+
color: red
3+
}
4+
5+
.test {
6+
color: blue
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.import {
2+
color: red;
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.import
2+
color: red
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@import "imports/section.css";
2+
3+
.test {
4+
color: blue;
5+
}
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@import "imports/section.sss"
2+
3+
.test
4+
color: blue

0 commit comments

Comments
 (0)