Skip to content
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

Add support for less module #76

Merged
merged 10 commits into from
Aug 26, 2021
1 change: 1 addition & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
{
"endOfLine": "auto"
}
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ module.exports = {

## Configuration

You can pass an `options` object to configure the loaders and plugins. You can also pass a `modifyLessRule` callback to have full control over the Less webpack rule.
You can pass an `options` object to configure the loaders and plugins(configure _less_ and _less modules_ at the same time). You can also pass a `modifyLessRule`(or `modifyLessModuleRule`) callback to have full control over the Less webpack rule.

- `options.styleLoaderOptions`
- _Default:_ `{}`
Expand All @@ -112,7 +112,16 @@ You can pass an `options` object to configure the loaders and plugins. You can a
- A callback function that receives two arguments: the webpack rule, and the context. You must return an updated rule object.
- `lessRule`:
- `test`: Regex (default: `/\.less$/`)
- `exclude`: Regex (default: `/\.module\.(less)$/`)
- `exclude`: Regex (default: `/\.module\.less$/`)
- `use`: Array of loaders and options.
- `sideEffects`: Boolean (default: same as sass's rule)
- `context`:
- `env`: "development" or "production"
- `paths`: An object with paths, e.g. `appBuild`, `appPath`, `ownNodeModules`
- `options.modifyLessModuleRule(lessRule, context)`
- A callback function that receives two arguments: the webpack rule, and the context. You must return an updated rule object.
- `lessModuleRule`:
- `test`: Regex (default: `/\.module\.less$/`)
- `use`: Array of loaders and options.
- `context`:
- `env`: "development" or "production"
Expand Down
225 changes: 219 additions & 6 deletions lib/craco-less.dev.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const {
applyCracoConfigPlugins,
applyWebpackConfigPlugins,
} = require("@craco/craco/lib/features/plugins");
const getCSSModuleLocalIdent = require("react-dev-utils/getCSSModuleLocalIdent");

const clone = require("clone");

Expand Down Expand Up @@ -68,6 +69,37 @@ test("the webpack config is modified correctly without any options", () => {

expect(lessRule.use[4].loader).toContain(`${path.sep}less-loader`);
expect(lessRule.use[4].options).toEqual({});

const lessModuleRule = oneOfRule.oneOf.find(
(r) => r.test && r.test.toString() === "/\\.module\\.less$/"
);
expect(lessModuleRule).not.toBeUndefined();
expect(lessModuleRule.use[0].loader).toContain(`${path.sep}style-loader`);
expect(lessModuleRule.use[0].options).toEqual({});

expect(lessModuleRule.use[1].loader).toContain(`${path.sep}css-loader`);
expect(lessModuleRule.use[1].options).toEqual({
importLoaders: 3,
sourceMap: webpackConfig.devtool !== false,
modules: {
getLocalIdent: getCSSModuleLocalIdent,
},
});

expect(lessModuleRule.use[2].loader).toContain(`${path.sep}postcss-loader`);
expect(lessModuleRule.use[2].options.ident).toEqual("postcss");
expect(lessModuleRule.use[2].options.plugins).not.toBeUndefined();

expect(lessModuleRule.use[3].loader).toContain(
`${path.sep}resolve-url-loader`
);
expect(lessModuleRule.use[3].options).toEqual({
root: lessModuleRule.use[3].options.root,
sourceMap: webpackConfig.devtool !== false,
});

expect(lessModuleRule.use[4].loader).toContain(`${path.sep}less-loader`);
expect(lessModuleRule.use[4].options).toEqual({});
});

test("the webpack config is modified correctly without any options on Windows", () => {
Expand Down Expand Up @@ -127,6 +159,36 @@ test("the webpack config is modified correctly without any options on Windows",
// We use `require.resolve("less-loader")`, so it's the OS separator here
expect(lessRule.use[4].loader).toContain(`${path.sep}less-loader`);
expect(lessRule.use[4].options).toEqual({});

const lessModuleRule = oneOfRule.oneOf.find(
(r) => r.test && r.test.toString() === "/\\.module\\.less$/"
);
expect(lessModuleRule).not.toBeUndefined();
expect(lessModuleRule.use[0].loader).toContain("\\style-loader");
expect(lessModuleRule.use[0].options).toEqual({});

expect(lessModuleRule.use[1].loader).toContain("\\css-loader");
expect(lessModuleRule.use[1].options).toEqual({
importLoaders: 3,
sourceMap: webpackConfig.devtool !== false,
modules: {
getLocalIdent: getCSSModuleLocalIdent,
},
});

expect(lessModuleRule.use[2].loader).toContain("\\postcss-loader");
expect(lessModuleRule.use[2].options.ident).toEqual("postcss");
expect(lessModuleRule.use[2].options.plugins).not.toBeUndefined();

expect(lessModuleRule.use[3].loader).toContain(`\\resolve-url-loader`);
expect(lessModuleRule.use[3].options).toEqual({
root: lessModuleRule.use[3].options.root,
sourceMap: webpackConfig.devtool !== false,
});

// We use `require.resolve("less-loader")`, so it's the OS separator here
expect(lessModuleRule.use[4].loader).toContain(`${path.sep}less-loader`);
expect(lessModuleRule.use[4].options).toEqual({});
});

test("the webpack config is modified correctly with less-loader options", () => {
Expand Down Expand Up @@ -170,6 +232,31 @@ test("the webpack config is modified correctly with less-loader options", () =>
"@less-variable": "#fff",
},
});

const lessModuleRule = oneOfRule.oneOf.find(
(r) => r.test && r.test.toString() === "/\\.module\\.less$/"
);
expect(lessModuleRule).not.toBeUndefined();

expect(lessModuleRule.use[2].loader).toContain(`${path.sep}postcss-loader`);
expect(lessModuleRule.use[2].options.ident).toEqual("postcss");
expect(lessModuleRule.use[2].options.plugins).not.toBeUndefined();

expect(lessModuleRule.use[3].loader).toContain(
`${path.sep}resolve-url-loader`
);
expect(lessModuleRule.use[3].options).toEqual({
root: lessModuleRule.use[3].options.root,
sourceMap: webpackConfig.devtool !== false,
});

expect(lessModuleRule.use[4].loader).toContain(`${path.sep}less-loader`);
expect(lessModuleRule.use[4].options).toEqual({
javascriptEnabled: true,
modifyVars: {
"@less-variable": "#fff",
},
});
});

test("the webpack config is modified correctly with all loader options", () => {
Expand Down Expand Up @@ -238,6 +325,43 @@ test("the webpack config is modified correctly with all loader options", () => {
"@less-variable": "#fff",
},
});

const lessModuleRule = oneOfRule.oneOf.find(
(r) => r.test && r.test.toString() === "/\\.module\\.less$/"
);
expect(lessModuleRule).not.toBeUndefined();
expect(lessModuleRule.use[0].loader).toContain(`${path.sep}style-loader`);
expect(lessModuleRule.use[0].options).toEqual({
sourceMaps: true,
});

expect(lessModuleRule.use[1].loader).toContain(`${path.sep}css-loader`);
expect(lessModuleRule.use[1].options).toEqual({
modules: true,
importLoaders: 3,
localIdentName: "[local]_[hash:base64:5]",
sourceMap: webpackConfig.devtool !== false,
});

expect(lessModuleRule.use[2].loader).toContain(`${path.sep}postcss-loader`);
expect(lessModuleRule.use[2].options.ident).toEqual("test-ident");
expect(lessModuleRule.use[2].options.plugins).not.toBeUndefined();

expect(lessModuleRule.use[3].loader).toContain(
`${path.sep}resolve-url-loader`
);
expect(lessModuleRule.use[3].options).toEqual({
root: lessModuleRule.use[3].options.root,
sourceMap: webpackConfig.devtool !== false,
});

expect(lessModuleRule.use[4].loader).toContain(`${path.sep}less-loader`);
expect(lessModuleRule.use[4].options).toEqual({
javascriptEnabled: true,
modifyVars: {
"@less-variable": "#fff",
},
});
});

test("the webpack config is modified correctly with the modifyLessRule option", () => {
Expand Down Expand Up @@ -283,6 +407,71 @@ test("the webpack config is modified correctly with the modifyLessRule option",
expect(lessRule.use[4].options).toEqual({});
});

test("the webpack config is modified correctly with the modifyLessModuleRule option", () => {
applyCracoConfigAndOverrideWebpack({
plugins: [
{
plugin: CracoLessPlugin,
options: {
modifyLessModuleRule: (rule, context) => {
if (context.env === "production") {
rule.use[0].options.testOption = "test-value-production";
rule.use[1].options.modules.getLocalIdent =
"test-deep-clone-production";
} else {
rule.use[0].options.testOption = "test-value-development";
rule.use[1].options.modules.getLocalIdent =
"test-deep-clone-development";
}
return rule;
},
},
},
],
});

const oneOfRule = webpackConfig.module.rules.find((r) => r.oneOf);
expect(oneOfRule).not.toBeUndefined();
const lessModuleRule = oneOfRule.oneOf.find(
(r) => r.test && r.test.toString() === "/\\.module\\.less$/"
);
expect(lessModuleRule).not.toBeUndefined();

expect(lessModuleRule.use[0].loader).toContain(`${path.sep}style-loader`);
expect(lessModuleRule.use[0].options.testOption).toEqual(
"test-value-development"
);

expect(lessModuleRule.use[1].options.modules.getLocalIdent).toEqual(
"test-deep-clone-development"
);

expect(lessModuleRule.use[2].loader).toContain(`${path.sep}postcss-loader`);
expect(lessModuleRule.use[2].options.ident).toEqual("postcss");
expect(lessModuleRule.use[2].options.plugins).not.toBeUndefined();

expect(lessModuleRule.use[3].loader).toContain(
`${path.sep}resolve-url-loader`
);
expect(lessModuleRule.use[3].options).toEqual({
root: lessModuleRule.use[3].options.root,
sourceMap: webpackConfig.devtool !== false,
});

expect(lessModuleRule.use[4].loader).toContain(`${path.sep}less-loader`);
expect(lessModuleRule.use[4].options).toEqual({});

const sassModuleRule = oneOfRule.oneOf.find((rule) => {
if (!rule.test) return false;
const test = rule.test.toString();
return test.includes("scss|sass") && test.includes("module");
});

expect(sassModuleRule.use[1].options.modules.getLocalIdent).toEqual(
getCSSModuleLocalIdent
);
});

test("throws an error when we can't find file-loader in the webpack config", () => {
let oneOfRule = webpackConfig.module.rules.find((r) => r.oneOf);
oneOfRule.oneOf = oneOfRule.oneOf.filter(
Expand Down Expand Up @@ -343,9 +532,11 @@ test("throws an error when we can't find the oneOf rules in the webpack config",

test("throws an error when react-scripts adds an unknown webpack rule", () => {
let oneOfRule = webpackConfig.module.rules.find((r) => r.oneOf);
const sassRule = oneOfRule.oneOf.find((rule) =>
rule.test.toString().includes("scss|sass")
);
const sassRule = oneOfRule.oneOf.find((rule) => {
if (!rule.test) return false;
const test = rule.test.toString();
return test.includes("scss|sass") && !test.includes("module");
});
sassRule.use.push({
loader: "/path/to/unknown-loader/index.js",
});
Expand All @@ -364,9 +555,11 @@ test("throws an error when react-scripts adds an unknown webpack rule", () => {

test("throws an error when the sass rule is missing", () => {
let oneOfRule = webpackConfig.module.rules.find((r) => r.oneOf);
oneOfRule.oneOf = oneOfRule.oneOf.filter(
(rule) => !(rule.test && rule.test.toString().includes("scss|sass"))
);
oneOfRule.oneOf = oneOfRule.oneOf.filter((rule) => {
if (!rule.test) return true;
const test = rule.test.toString();
return !(test.includes("scss|sass") && !test.includes("module"));
});
const runTest = () => {
applyCracoConfigAndOverrideWebpack({
plugins: [{ plugin: CracoLessPlugin }],
Expand All @@ -379,3 +572,23 @@ test("throws an error when the sass rule is missing", () => {
)
);
});

test("throws an error when the sass module rule is missing", () => {
let oneOfRule = webpackConfig.module.rules.find((r) => r.oneOf);
oneOfRule.oneOf = oneOfRule.oneOf.filter((rule) => {
if (!rule.test) return true;
const test = rule.test.toString();
return !(test.includes("scss|sass") && test.includes("module"));
});
const runTest = () => {
applyCracoConfigAndOverrideWebpack({
plugins: [{ plugin: CracoLessPlugin }],
});
};
expect(runTest).toThrowError(
new RegExp(
"Can't find the webpack rule to match scss/sass module files in the " +
"development webpack config!"
)
);
});
Loading