Skip to content

Commit 0d342b1

Browse files
feat: allow String value for the "implementation" option
1 parent e20e3b7 commit 0d342b1

9 files changed

+185
-24
lines changed

README.md

+28-1
Original file line numberDiff line numberDiff line change
@@ -597,12 +597,14 @@ module.exports = {
597597

598598
### `implementation`
599599

600-
Type: `Function`
600+
Type: `Function | String`
601601

602602
The special `implementation` option determines which implementation of PostCSS to use. Overrides the locally installed `peerDependency` version of `postcss`.
603603

604604
**This option is only really useful for downstream tooling authors to ease the PostCSS 7-to-8 transition.**
605605

606+
#### Function
607+
606608
**webpack.config.js**
607609

608610
```js
@@ -626,6 +628,31 @@ module.exports = {
626628
};
627629
```
628630

631+
#### String
632+
633+
**webpack.config.js**
634+
635+
```js
636+
module.exports = {
637+
module: {
638+
rules: [
639+
{
640+
test: /\.css$/i,
641+
use: [
642+
{ loader: "style-loader" },
643+
{ loader: "css-loader" },
644+
{
645+
loader: "postcss-loader",
646+
options: { implementation: require.resolve("postcss") },
647+
},
648+
{ loader: "sass-loader" },
649+
],
650+
},
651+
],
652+
},
653+
};
654+
```
655+
629656
## Examples
630657

631658
### SugarSS

src/index.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import path from "path";
22

3-
import postcss from "postcss";
43
import { satisfies } from "semver";
54
import postcssPackage from "postcss/package.json";
65

@@ -14,6 +13,7 @@ import {
1413
normalizeSourceMap,
1514
normalizeSourceMapAfterPostcss,
1615
findPackageJSONDir,
16+
getPostcssImplementation,
1717
} from "./utils";
1818

1919
let hasExplicitDependencyOnPostCSS = false;
@@ -40,7 +40,17 @@ export default async function loader(content, sourceMap, meta) {
4040
? true
4141
: options.postcssOptions.config;
4242

43-
const postcssFactory = options.implementation || postcss;
43+
const postcssFactory = getPostcssImplementation(this, options.implementation);
44+
45+
if (!postcssFactory) {
46+
callback(
47+
new Error(
48+
`The Postcss implementation "${options.implementation}" not found`
49+
)
50+
);
51+
52+
return;
53+
}
4454

4555
let loadedConfig;
4656

src/options.json

+8-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,14 @@
3939
},
4040
"implementation": {
4141
"description": "The implementation of postcss to use, instead of the locally installed version (https://github.com/postcss/postcss-loader#implementation)",
42-
"instanceof": "Function"
42+
"anyOf": [
43+
{
44+
"type": "string"
45+
},
46+
{
47+
"instanceof": "Function"
48+
}
49+
]
4350
}
4451
},
4552
"additionalProperties": false

src/utils.js

+22
Original file line numberDiff line numberDiff line change
@@ -433,11 +433,33 @@ function findPackageJSONDir(cwd, statSync) {
433433
return dir;
434434
}
435435

436+
function getPostcssImplementation(loaderContext, implementation) {
437+
let resolvedImplementation = implementation;
438+
439+
if (!implementation || typeof implementation === "string") {
440+
const postcssImplPkg = implementation || "postcss";
441+
442+
try {
443+
// eslint-disable-next-line import/no-dynamic-require, global-require
444+
resolvedImplementation = require(postcssImplPkg);
445+
} catch (error) {
446+
loaderContext.emitError(error);
447+
448+
// eslint-disable-next-line consistent-return
449+
return;
450+
}
451+
}
452+
453+
// eslint-disable-next-line consistent-return
454+
return resolvedImplementation;
455+
}
456+
436457
export {
437458
loadConfig,
438459
getPostcssOptions,
439460
exec,
440461
normalizeSourceMap,
441462
normalizeSourceMapAfterPostcss,
442463
findPackageJSONDir,
464+
getPostcssImplementation,
443465
};

test/__snapshots__/implementation.test.js.snap

+63
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`"implementation" option should throw error when unresolved package: errors 1`] = `
4+
Array [
5+
"ModuleBuildError: Module build failed (from \`replaced original path\`):
6+
Error: The Postcss implementation \\"unresolved\\" not found
7+
at Object.loader (/src/index.js:47:7)",
8+
"ModuleError: Module Error (from \`replaced original path\`):
9+
(Emitted value instead of an instance of Error) Error: Cannot find module 'unresolved' from 'src/utils.js'",
10+
]
11+
`;
12+
13+
exports[`"implementation" option should throw error when unresolved package: warnings 1`] = `Array []`;
14+
315
exports[`"implementation" option should work with a custom instance of PostCSS: css 1`] = `
416
"a {
517
color: black;
@@ -50,3 +62,54 @@ a {
5062
exports[`"implementation" option should work with a custom instance of PostCSS: errors 1`] = `Array []`;
5163

5264
exports[`"implementation" option should work with a custom instance of PostCSS: warnings 1`] = `Array []`;
65+
66+
exports[`"implementation" option should work with implementation is string: css 1`] = `
67+
"a {
68+
color: black;
69+
}
70+
71+
a {
72+
color: red;
73+
}
74+
75+
a {
76+
color: green;
77+
}
78+
79+
a {
80+
color: blue;
81+
}
82+
83+
.class {
84+
-x-border-color: blue blue *;
85+
-x-color: * #fafafa;
86+
}
87+
88+
.class-foo {
89+
-z-border-color: blue blue *;
90+
-z-color: * #fafafa;
91+
}
92+
93+
.phone {
94+
&_title {
95+
width: 500px;
96+
97+
@media (max-width: 500px) {
98+
width: auto;
99+
}
100+
101+
body.is_dark & {
102+
color: white;
103+
}
104+
}
105+
106+
img {
107+
display: block;
108+
}
109+
}
110+
"
111+
`;
112+
113+
exports[`"implementation" option should work with implementation is string: errors 1`] = `Array []`;
114+
115+
exports[`"implementation" option should work with implementation is string: warnings 1`] = `Array []`;

test/__snapshots__/loader.test.js.snap

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Array [
55
"ModuleBuildError: Module build failed (from \`replaced original path\`):
66
Error: Something went wrong.
77
at Processor.process (/test/loader.test.js:216:26)
8-
at Object.loader (/src/index.js:106:30)",
8+
at Object.loader (/src/index.js:116:30)",
99
]
1010
`;
1111

@@ -21,7 +21,7 @@ Array [
2121
"ModuleBuildError: Module build failed (from \`replaced original path\`):
2222
Error: Something went wrong.
2323
at Processor.process (/test/loader.test.js:300:26)
24-
at Object.loader (/src/index.js:106:30)",
24+
at Object.loader (/src/index.js:116:30)",
2525
]
2626
`;
2727

@@ -32,7 +32,7 @@ Array [
3232
"ModuleBuildError: Module build failed (from \`replaced original path\`):
3333
Error: Something went wrong.
3434
at Processor.process (/test/loader.test.js:245:26)
35-
at Object.loader (/src/index.js:106:30)",
35+
at Object.loader (/src/index.js:116:30)",
3636
]
3737
`;
3838

@@ -43,7 +43,7 @@ Array [
4343
"ModuleBuildError: Module build failed (from \`replaced original path\`):
4444
Error: Something went wrong.
4545
at Processor.process (/test/loader.test.js:274:26)
46-
at Object.loader (/src/index.js:106:30)",
46+
at Object.loader (/src/index.js:116:30)",
4747
]
4848
`;
4949

test/__snapshots__/validate-options.test.js.snap

+24-14
Original file line numberDiff line numberDiff line change
@@ -38,32 +38,42 @@ exports[`validate options should throw an error on the "execute" option with "te
3838

3939
exports[`validate options should throw an error on the "implementation" option with "/test/" value 1`] = `
4040
"Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema.
41-
- options.implementation should be an instance of function.
42-
-> The implementation of postcss to use, instead of the locally installed version (https://github.com/postcss/postcss-loader#implementation)"
41+
- options.implementation should be one of these:
42+
string | function
43+
-> The implementation of postcss to use, instead of the locally installed version (https://github.com/postcss/postcss-loader#implementation)
44+
Details:
45+
* options.implementation should be a string.
46+
* options.implementation should be an instance of function."
4347
`;
4448

4549
exports[`validate options should throw an error on the "implementation" option with "[]" value 1`] = `
4650
"Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema.
47-
- options.implementation should be an instance of function.
48-
-> The implementation of postcss to use, instead of the locally installed version (https://github.com/postcss/postcss-loader#implementation)"
51+
- options.implementation should be one of these:
52+
string | function
53+
-> The implementation of postcss to use, instead of the locally installed version (https://github.com/postcss/postcss-loader#implementation)
54+
Details:
55+
* options.implementation should be a string.
56+
* options.implementation should be an instance of function."
4957
`;
5058

5159
exports[`validate options should throw an error on the "implementation" option with "{}" value 1`] = `
5260
"Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema.
53-
- options.implementation should be an instance of function.
54-
-> The implementation of postcss to use, instead of the locally installed version (https://github.com/postcss/postcss-loader#implementation)"
61+
- options.implementation should be one of these:
62+
string | function
63+
-> The implementation of postcss to use, instead of the locally installed version (https://github.com/postcss/postcss-loader#implementation)
64+
Details:
65+
* options.implementation should be a string.
66+
* options.implementation should be an instance of function."
5567
`;
5668

5769
exports[`validate options should throw an error on the "implementation" option with "1" value 1`] = `
5870
"Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema.
59-
- options.implementation should be an instance of function.
60-
-> The implementation of postcss to use, instead of the locally installed version (https://github.com/postcss/postcss-loader#implementation)"
61-
`;
62-
63-
exports[`validate options should throw an error on the "implementation" option with "something" value 1`] = `
64-
"Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema.
65-
- options.implementation should be an instance of function.
66-
-> The implementation of postcss to use, instead of the locally installed version (https://github.com/postcss/postcss-loader#implementation)"
71+
- options.implementation should be one of these:
72+
string | function
73+
-> The implementation of postcss to use, instead of the locally installed version (https://github.com/postcss/postcss-loader#implementation)
74+
Details:
75+
* options.implementation should be a string.
76+
* options.implementation should be an instance of function."
6777
`;
6878

6979
exports[`validate options should throw an error on the "postcssOptions" option with "{"config":[]}" value 1`] = `

test/implementation.test.js

+22
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,28 @@ import {
99
} from "./helpers";
1010

1111
describe('"implementation" option', () => {
12+
it("should work with implementation is string", async () => {
13+
const compiler = getCompiler("./css/index.js", {
14+
implementation: require.resolve("postcss"),
15+
});
16+
const stats = await compile(compiler);
17+
const codeFromBundle = getCodeFromBundle("style.css", stats);
18+
19+
expect(codeFromBundle.css).toMatchSnapshot("css");
20+
expect(getWarnings(stats)).toMatchSnapshot("warnings");
21+
expect(getErrors(stats)).toMatchSnapshot("errors");
22+
});
23+
24+
it("should throw error when unresolved package", async () => {
25+
const compiler = getCompiler("./css/index.js", {
26+
implementation: "unresolved",
27+
});
28+
const stats = await compile(compiler);
29+
30+
expect(getWarnings(stats)).toMatchSnapshot("warnings");
31+
expect(getErrors(stats)).toMatchSnapshot("errors");
32+
});
33+
1234
it("should work with a custom instance of PostCSS", async () => {
1335
const spy = jest.fn(postcss);
1436
const compiler = getCompiler("./css/index.js", {

test/validate-options.test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ describe("validate options", () => {
5151
failure: [1, /test/, [], {}, "something"],
5252
},
5353
implementation: {
54-
success: [require("postcss")],
55-
failure: [1, /test/, [], {}, "something"],
54+
success: [require("postcss"), "postcss"],
55+
failure: [1, /test/, [], {}],
5656
},
5757
};
5858

0 commit comments

Comments
 (0)