Skip to content

Commit

Permalink
Merge pull request #76 from fanck0605/master
Browse files Browse the repository at this point in the history
Add support for less module
  • Loading branch information
ndbroadbent authored Aug 26, 2021
2 parents 043ce28 + 071a4d2 commit 03312c1
Show file tree
Hide file tree
Showing 8 changed files with 679 additions and 108 deletions.
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

0 comments on commit 03312c1

Please sign in to comment.