Skip to content

Commit

Permalink
fix: avoid mutations of options and config (#470)
Browse files Browse the repository at this point in the history
  • Loading branch information
evilebottnawi committed Sep 7, 2020
1 parent 77449e1 commit cad6f07
Show file tree
Hide file tree
Showing 13 changed files with 114 additions and 79 deletions.
3 changes: 1 addition & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -44,6 +44,7 @@
},
"dependencies": {
"cosmiconfig": "^7.0.0",
"klona": "^2.0.3",
"loader-utils": "^2.0.0",
"schema-utils": "^2.7.1",
"semver": "^7.3.2"
Expand Down
3 changes: 2 additions & 1 deletion src/index.js
Expand Up @@ -45,7 +45,8 @@ export default async function loader(content, sourceMap, meta) {
typeof options.postcssOptions.config === 'undefined'
? true
: options.postcssOptions.config;
let loadedConfig = {};

let loadedConfig;

if (configOption) {
try {
Expand Down
40 changes: 24 additions & 16 deletions src/utils.js
@@ -1,6 +1,7 @@
import path from 'path';
import Module from 'module';

import { klona } from 'klona/full';
import { cosmiconfig } from 'cosmiconfig';

const parentModule = module;
Expand Down Expand Up @@ -41,7 +42,7 @@ async function loadConfig(loaderContext, config) {
try {
stats = await stat(loaderContext.fs, searchPath);
} catch (errorIgnore) {
throw new Error(`No PostCSS Config found in: ${searchPath}`);
throw new Error(`No PostCSS config found in: ${searchPath}`);
}

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

let resultConfig = result.config || {};
loaderContext.addDependency(result.filepath);

if (typeof resultConfig === 'function') {
if (result.isEmpty) {
return result;
}

if (typeof result.config === 'function') {
const api = {
env: process.env.NODE_ENV,
mode: loaderContext.mode,
file: loaderContext.resourcePath,
// For complex use
webpackLoaderContext: loaderContext,
};

resultConfig = resultConfig(api);
result.config = result.config(api);
}

resultConfig.file = result.filepath;

loaderContext.addDependency(resultConfig.file);
result = klona(result);

return resultConfig;
return result;
}

function loadPlugin(plugin, options, file) {
Expand Down Expand Up @@ -160,7 +162,11 @@ function pluginFactory() {
};
}

function getPostcssOptions(loaderContext, config, postcssOptions = {}) {
function getPostcssOptions(
loaderContext,
loadedConfig = {},
postcssOptions = {}
) {
const file = loaderContext.resourcePath;

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

factory(config.plugins);
if (loadedConfig.config && loadedConfig.config.plugins) {
factory(loadedConfig.config.plugins);
}

factory(normalizedPostcssOptions.plugins);

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

const processOptionsFromConfig = { ...config };
const processOptionsFromConfig = loadedConfig.config || {};

if (processOptionsFromConfig.from) {
processOptionsFromConfig.from = path.resolve(
path.dirname(config.file),
path.dirname(loadedConfig.filepath),
processOptionsFromConfig.from
);
}

if (processOptionsFromConfig.to) {
processOptionsFromConfig.to = path.resolve(
path.dirname(config.file),
path.dirname(loadedConfig.filepath),
processOptionsFromConfig.to
);
}

// No need them for processOptions
delete processOptionsFromConfig.plugins;
delete processOptionsFromConfig.file;

const processOptionsFromOptions = { ...normalizedPostcssOptions };
const processOptionsFromOptions = klona(normalizedPostcssOptions);

if (processOptionsFromOptions.from) {
processOptionsFromOptions.from = path.resolve(
Expand Down
2 changes: 1 addition & 1 deletion test/__snapshots__/postcssOptins.test.js.snap
Expand Up @@ -23,7 +23,7 @@ exports[`"postcssOptions" option should throw an error with the "config" option
exports[`"postcssOptions" option should throw an error with the "config" option on the unresolved config: errors 1`] = `
Array [
"ModuleBuildError: Module build failed (from \`replaced original path\`):
Error: No PostCSS Config found in: /test/fixtures/config-scope/css/unresolve.js",
Error: No PostCSS config found in: /test/fixtures/config-scope/css/unresolve.js",
]
`;

Expand Down
115 changes: 56 additions & 59 deletions test/config-autoload.test.js
Expand Up @@ -12,96 +12,93 @@ const loaderContext = {

describe('autoload config', () => {
it('should load ".postcssrc"', async () => {
const expected = (config) => {
expect(config.map).toEqual(false);
expect(config.from).toEqual('./test/rc/fixtures/index.css');
expect(config.to).toEqual('./test/rc/expect/index.css');
expect(Object.keys(config.plugins).length).toEqual(2);
expect(config.file).toEqual(
path.resolve(testDirectory, 'rc', '.postcssrc')
);
};

const config = await loadConfig(
const loadedConfig = await loadConfig(
loaderContext,
path.resolve(testDirectory, 'rc')
);

expected(config);
expect(loadedConfig.config.map).toEqual(false);
expect(loadedConfig.config.from).toEqual('./test/rc/fixtures/index.css');
expect(loadedConfig.config.to).toEqual('./test/rc/expect/index.css');
expect(Object.keys(loadedConfig.config.plugins).length).toEqual(2);
expect(loadedConfig.filepath).toEqual(
path.resolve(testDirectory, 'rc', '.postcssrc')
);
});

it('should load "package.json"', async () => {
const expected = (config) => {
expect(config.parser).toEqual(false);
expect(config.syntax).toEqual(false);
expect(config.map).toEqual(false);
expect(config.from).toEqual('./index.css');
expect(config.to).toEqual('./index.css');
expect(Object.keys(config.plugins).length).toEqual(2);
expect(config.file).toEqual(
path.resolve(testDirectory, 'pkg', 'package.json')
);
};

const config = await loadConfig(
const loadedConfig = await loadConfig(
loaderContext,
path.resolve(testDirectory, 'pkg')
);

expected(config);
expect(loadedConfig.config.parser).toEqual(false);
expect(loadedConfig.config.syntax).toEqual(false);
expect(loadedConfig.config.map).toEqual(false);
expect(loadedConfig.config.from).toEqual('./index.css');
expect(loadedConfig.config.to).toEqual('./index.css');
expect(Object.keys(loadedConfig.config.plugins).length).toEqual(2);
expect(loadedConfig.filepath).toEqual(
path.resolve(testDirectory, 'pkg', 'package.json')
);
});

it('should load "postcss.config.js" with "Object" syntax of plugins', async () => {
const expected = (config) => {
expect(config.map).toEqual(false);
expect(config.from).toEqual(
'./test/fixtures/config-autoload/js/object/index.css'
);
expect(config.to).toEqual(
'./test/fixtures/config-autoload/js/object/expect/index.css'
);
expect(Object.keys(config.plugins).length).toEqual(2);
expect(config.file).toEqual(
path.resolve(testDirectory, 'js/object', 'postcss.config.js')
);
};

const config = await loadConfig(
const loadedConfig = await loadConfig(
loaderContext,
path.resolve(testDirectory, 'js/object')
);

expected(config);
expect(loadedConfig.config.map).toEqual(false);
expect(loadedConfig.config.from).toEqual(
'./test/fixtures/config-autoload/js/object/index.css'
);
expect(loadedConfig.config.to).toEqual(
'./test/fixtures/config-autoload/js/object/expect/index.css'
);
expect(Object.keys(loadedConfig.config.plugins).length).toEqual(2);
expect(loadedConfig.filepath).toEqual(
path.resolve(testDirectory, 'js/object', 'postcss.config.js')
);
});

it('should load "postcss.config.js" with "Array" syntax of plugins', async () => {
const expected = (config) => {
expect(config.map).toEqual(false);
expect(config.from).toEqual(
'./test/fixtures/config-autoload/js/object/index.css'
);
expect(config.to).toEqual(
'./test/fixtures/config-autoload/js/object/expect/index.css'
);
expect(Object.keys(config.plugins).length).toEqual(2);
expect(config.file).toEqual(
path.resolve(testDirectory, 'js/array', 'postcss.config.js')
);
};

const config = await loadConfig(
const loadedConfig = await loadConfig(
loaderContext,
path.resolve(testDirectory, 'js/array')
);

expected(config);
expect(loadedConfig.config.map).toEqual(false);
expect(loadedConfig.config.from).toEqual(
'./test/fixtures/config-autoload/js/object/index.css'
);
expect(loadedConfig.config.to).toEqual(
'./test/fixtures/config-autoload/js/object/expect/index.css'
);
expect(Object.keys(loadedConfig.config.plugins).length).toEqual(2);
expect(loadedConfig.filepath).toEqual(
path.resolve(testDirectory, 'js/array', 'postcss.config.js')
);
});

it('should load empty ".postcssrc"', async () => {
const loadedConfig = await loadConfig(
loaderContext,
path.resolve(testDirectory, 'empty/.postcssrc')
);

// eslint-disable-next-line no-undefined
expect(loadedConfig.config).toEqual(undefined);
expect(loadedConfig.filepath).toEqual(
path.resolve(testDirectory, 'empty/.postcssrc')
);
});

it('should throw an error on "unresolved" config', async () => {
try {
await loadConfig(loaderContext, path.resolve('unresolved'));
} catch (error) {
expect(error.message).toMatch(/^No PostCSS Config found in: (.*)$/);
expect(error.message).toMatch(/^No PostCSS config found in: (.*)$/);
}
});
});
1 change: 1 addition & 0 deletions test/fixtures/config-autoload/empty/.postcssrc
@@ -0,0 +1 @@

7 changes: 7 additions & 0 deletions test/fixtures/config-autoload/empty/expect/index.css
@@ -0,0 +1,7 @@
.import {
color: red;
}

.test {
color: blue;
}
7 changes: 7 additions & 0 deletions test/fixtures/config-autoload/empty/expect/index.sss
@@ -0,0 +1,7 @@
.import {
color: red
}

.test {
color: blue
}
@@ -0,0 +1,3 @@
.import {
color: red;
}
@@ -0,0 +1,2 @@
.import
color: red
5 changes: 5 additions & 0 deletions test/fixtures/config-autoload/empty/fixtures/index.css
@@ -0,0 +1,5 @@
@import "imports/section.css";

.test {
color: blue;
}
4 changes: 4 additions & 0 deletions test/fixtures/config-autoload/empty/fixtures/index.sss
@@ -0,0 +1,4 @@
@import "imports/section.sss"

.test
color: blue

0 comments on commit cad6f07

Please sign in to comment.