Skip to content

feat: check postcss versions to avoid using PostCSS 7 #527

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 10, 2021
34 changes: 33 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import path from "path";

import postcss from "postcss";
import { satisfies } from "semver";
import postcssPackage from "postcss/package.json";
Expand All @@ -11,8 +13,13 @@ import {
exec,
normalizeSourceMap,
normalizeSourceMapAfterPostcss,
parsePackageJson,
findPackageJsonDir,
} from "./utils";

let hasExplicitDependencyOnPostCSS = false;
let packageJsonDir;

/**
* **PostCSS Loader**
*
Expand All @@ -26,7 +33,6 @@ import {
*
* @return {callback} callback Result
*/

export default async function loader(content, sourceMap, meta) {
const options = this.getOptions(schema);
const callback = this.async();
Expand Down Expand Up @@ -102,6 +108,32 @@ export default async function loader(content, sourceMap, meta) {
processOptions
);
} catch (error) {
// The `findPackageJsonDir` function returns `string` or `null`.
// This is used to do for caching, that is, an explicit comparison with `undefined`
// is used to make the condition body run once.
if (packageJsonDir === undefined) {
packageJsonDir = findPackageJsonDir(process.cwd(), this.fs.statSync);
}
// Check postcss versions to avoid using PostCSS 7.
// For caching reasons, we use the readFileSync and existsSync functions from the context,
// not the functions from the `fs` module.
if (
!hasExplicitDependencyOnPostCSS &&
postcssFactory().version.startsWith("7.") &&
packageJsonDir
) {
const filePath = path.resolve(packageJsonDir, "package.json");
const pkg = parsePackageJson(filePath, this.fs.readFileSync);
if (!pkg.dependencies.postcss && !pkg.devDependencies.postcss) {
this.emitWarning(
"Add postcss as project dependency. postcss is not a peer dependency for postcss-loader. " +
"Use `npm install postcss` or `yarn add postcss`"
);
} else {
hasExplicitDependencyOnPostCSS = true;
}
}

if (error.file) {
this.addDependency(error.file);
}
Expand Down
23 changes: 23 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -408,10 +408,33 @@ function normalizeSourceMapAfterPostcss(map, resourceContext) {
return newMap;
}

function parsePackageJson(filePath, readFileSync) {
return JSON.parse(readFileSync(filePath, "utf8"));
}

function findPackageJsonDir(cwd, statSync) {
let dir = cwd;
for (;;) {
try {
if (statSync(path.join(dir, "package.json")).isFile()) break;
// eslint-disable-next-line no-empty
} catch (error) {}
const parent = path.dirname(dir);
if (dir === parent) {
dir = null;
break;
}
dir = parent;
}
return dir;
}

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

exports[`check postcss versions to avoid using PostCSS 7 should emit a warning if postcss version is not explicitly specified when the loader is failed: warnings 1`] = `
Array [
"ModuleWarning: Module Warning (from \`replaced original path\`):
(Emitted value instead of an instance of Error) Add postcss as project dependency. postcss is not a peer dependency for postcss-loader. Use \`npm install postcss\` or \`yarn add postcss\`",
]
`;

exports[`loader should emit asset using the "messages" API: errors 1`] = `Array []`;

exports[`loader should emit asset using the "messages" API: warnings 1`] = `Array []`;
Expand Down
45 changes: 45 additions & 0 deletions test/loader.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import path from "path";

import postcss from "postcss";

// eslint-disable-next-line import/no-namespace
import * as utils from "../src/utils";

import {
compile,
getCompiler,
Expand Down Expand Up @@ -198,3 +201,45 @@ describe("loader", () => {
expect(getErrors(stats)).toMatchSnapshot("errors");
});
});

describe("check postcss versions to avoid using PostCSS 7", async () => {
async function getStats() {
const compiler = getCompiler("./css/index.js", {
implementation: (...args) => {
const result = postcss(...args);
result.version = "7.0.0";
result.process = () =>
Promise.reject(new Error("Something went wrong."));
return result;
},
});
return compile(compiler);
}

it("should emit a warning if postcss version is not explicitly specified when the loader is failed", async () => {
jest
.spyOn(utils, "parsePackageJson")
.mockReturnValue({ dependencies: {}, devDependencies: {} });
const stats = await getStats();
expect(getWarnings(stats)).toMatchSnapshot("warnings");
});

it("should not show a warning if postcss version is explicitly defined", async () => {
jest.spyOn(utils, "parsePackageJson").mockReturnValue({
dependencies: {},
devDependencies: { postcss: "8.0.0" },
});
const stats = await getStats();
expect(stats.compilation.warnings.length).toBe(0);
});

it("should not show a warning if the package.json file was not found", async () => {
jest.spyOn(utils, "findPackageJsonDir").mockReturnValue(null);
jest.spyOn(utils, "parsePackageJson").mockReturnValue({
dependencies: {},
devDependencies: { postcss: "8.0.0" },
});
const stats = await getStats();
expect(stats.compilation.warnings.length).toBe(0);
});
});