Skip to content

Commit

Permalink
feat: allow String value for the "implementation" option
Browse files Browse the repository at this point in the history
  • Loading branch information
cap-Bernardito committed Jun 10, 2021
1 parent e20e3b7 commit 0d342b1
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 24 deletions.
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -597,12 +597,14 @@ module.exports = {

### `implementation`

Type: `Function`
Type: `Function | String`

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

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

#### Function

**webpack.config.js**

```js
Expand All @@ -626,6 +628,31 @@ module.exports = {
};
```

#### String

**webpack.config.js**

```js
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
use: [
{ loader: "style-loader" },
{ loader: "css-loader" },
{
loader: "postcss-loader",
options: { implementation: require.resolve("postcss") },
},
{ loader: "sass-loader" },
],
},
],
},
};
```

## Examples

### SugarSS
Expand Down
14 changes: 12 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import path from "path";

import postcss from "postcss";
import { satisfies } from "semver";
import postcssPackage from "postcss/package.json";

Expand All @@ -14,6 +13,7 @@ import {
normalizeSourceMap,
normalizeSourceMapAfterPostcss,
findPackageJSONDir,
getPostcssImplementation,
} from "./utils";

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

const postcssFactory = options.implementation || postcss;
const postcssFactory = getPostcssImplementation(this, options.implementation);

if (!postcssFactory) {
callback(
new Error(
`The Postcss implementation "${options.implementation}" not found`
)
);

return;
}

let loadedConfig;

Expand Down
9 changes: 8 additions & 1 deletion src/options.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,14 @@
},
"implementation": {
"description": "The implementation of postcss to use, instead of the locally installed version (https://github.com/postcss/postcss-loader#implementation)",
"instanceof": "Function"
"anyOf": [
{
"type": "string"
},
{
"instanceof": "Function"
}
]
}
},
"additionalProperties": false
Expand Down
22 changes: 22 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -433,11 +433,33 @@ function findPackageJSONDir(cwd, statSync) {
return dir;
}

function getPostcssImplementation(loaderContext, implementation) {
let resolvedImplementation = implementation;

if (!implementation || typeof implementation === "string") {
const postcssImplPkg = implementation || "postcss";

try {
// eslint-disable-next-line import/no-dynamic-require, global-require
resolvedImplementation = require(postcssImplPkg);
} catch (error) {
loaderContext.emitError(error);

// eslint-disable-next-line consistent-return
return;
}
}

// eslint-disable-next-line consistent-return
return resolvedImplementation;
}

export {
loadConfig,
getPostcssOptions,
exec,
normalizeSourceMap,
normalizeSourceMapAfterPostcss,
findPackageJSONDir,
getPostcssImplementation,
};
63 changes: 63 additions & 0 deletions test/__snapshots__/implementation.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`"implementation" option should throw error when unresolved package: errors 1`] = `
Array [
"ModuleBuildError: Module build failed (from \`replaced original path\`):
Error: The Postcss implementation \\"unresolved\\" not found
at Object.loader (/src/index.js:47:7)",
"ModuleError: Module Error (from \`replaced original path\`):
(Emitted value instead of an instance of Error) Error: Cannot find module 'unresolved' from 'src/utils.js'",
]
`;

exports[`"implementation" option should throw error when unresolved package: warnings 1`] = `Array []`;

exports[`"implementation" option should work with a custom instance of PostCSS: css 1`] = `
"a {
color: black;
Expand Down Expand Up @@ -50,3 +62,54 @@ a {
exports[`"implementation" option should work with a custom instance of PostCSS: errors 1`] = `Array []`;

exports[`"implementation" option should work with a custom instance of PostCSS: warnings 1`] = `Array []`;

exports[`"implementation" option should work with implementation is string: css 1`] = `
"a {
color: black;
}
a {
color: red;
}
a {
color: green;
}
a {
color: blue;
}
.class {
-x-border-color: blue blue *;
-x-color: * #fafafa;
}
.class-foo {
-z-border-color: blue blue *;
-z-color: * #fafafa;
}
.phone {
&_title {
width: 500px;
@media (max-width: 500px) {
width: auto;
}
body.is_dark & {
color: white;
}
}
img {
display: block;
}
}
"
`;

exports[`"implementation" option should work with implementation is string: errors 1`] = `Array []`;

exports[`"implementation" option should work with implementation is string: warnings 1`] = `Array []`;
8 changes: 4 additions & 4 deletions test/__snapshots__/loader.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Array [
"ModuleBuildError: Module build failed (from \`replaced original path\`):
Error: Something went wrong.
at Processor.process (/test/loader.test.js:216:26)
at Object.loader (/src/index.js:106:30)",
at Object.loader (/src/index.js:116:30)",
]
`;

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

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

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

Expand Down
38 changes: 24 additions & 14 deletions test/__snapshots__/validate-options.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -38,32 +38,42 @@ exports[`validate options should throw an error on the "execute" option with "te

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

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

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

exports[`validate options should throw an error on the "implementation" option with "1" value 1`] = `
"Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema.
- options.implementation should be an instance of function.
-> The implementation of postcss to use, instead of the locally installed version (https://github.com/postcss/postcss-loader#implementation)"
`;

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

exports[`validate options should throw an error on the "postcssOptions" option with "{"config":[]}" value 1`] = `
Expand Down
22 changes: 22 additions & 0 deletions test/implementation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,28 @@ import {
} from "./helpers";

describe('"implementation" option', () => {
it("should work with implementation is string", async () => {
const compiler = getCompiler("./css/index.js", {
implementation: require.resolve("postcss"),
});
const stats = await compile(compiler);
const codeFromBundle = getCodeFromBundle("style.css", stats);

expect(codeFromBundle.css).toMatchSnapshot("css");
expect(getWarnings(stats)).toMatchSnapshot("warnings");
expect(getErrors(stats)).toMatchSnapshot("errors");
});

it("should throw error when unresolved package", async () => {
const compiler = getCompiler("./css/index.js", {
implementation: "unresolved",
});
const stats = await compile(compiler);

expect(getWarnings(stats)).toMatchSnapshot("warnings");
expect(getErrors(stats)).toMatchSnapshot("errors");
});

it("should work with a custom instance of PostCSS", async () => {
const spy = jest.fn(postcss);
const compiler = getCompiler("./css/index.js", {
Expand Down
4 changes: 2 additions & 2 deletions test/validate-options.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ describe("validate options", () => {
failure: [1, /test/, [], {}, "something"],
},
implementation: {
success: [require("postcss")],
failure: [1, /test/, [], {}, "something"],
success: [require("postcss"), "postcss"],
failure: [1, /test/, [], {}],
},
};

Expand Down

0 comments on commit 0d342b1

Please sign in to comment.