Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e97b4c3

Browse files
committedMar 10, 2024··
feat: add getJSON option to output CSS modules mapping
This changeset adds a `getJSON` option to output CSS modules mappings to JSON. This value can be a boolean or a function, and it employs similar logic to [postcss-modules#getJSON](https://github.com/madyankin/postcss-modules?tab=readme-ov-file#saving-exported-classes) as a function. This is particularly useful for SSR/SSG/templating languages when CSS modules mappings need to be present at build time. Addresses [#988](#988).
1 parent 24e114a commit e97b4c3

File tree

8 files changed

+334
-1
lines changed

8 files changed

+334
-1
lines changed
 

‎README.md

+73
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,9 @@ type modules =
327327
| "dashesOnly"
328328
| ((name: string) => string);
329329
exportOnlyLocals: boolean;
330+
getJSON:
331+
| string
332+
| ((resourcePath: string, json: object, outputPath: string) => any);
330333
};
331334
```
332335

@@ -592,6 +595,7 @@ module.exports = {
592595
namedExport: true,
593596
exportLocalsConvention: "camelCase",
594597
exportOnlyLocals: false,
598+
getJSON: false,
595599
},
596600
},
597601
},
@@ -1372,6 +1376,75 @@ module.exports = {
13721376
};
13731377
```
13741378

1379+
##### `getJSON`
1380+
1381+
Type:
1382+
1383+
```ts
1384+
type getJSON =
1385+
| boolean
1386+
| ((resourcePath: string, json: object, outputPath: string) => any);
1387+
```
1388+
1389+
Default: `undefined`
1390+
1391+
Enables the outputting of the CSS modules mapping JSON. This can be omitted or set to a falsy value to disable any output.
1392+
1393+
###### `boolean`
1394+
1395+
Possible values:
1396+
1397+
- `true` - writes a JSON file next located in the same directory as the loaded resource file. For example, given a resource file located at /foo/bar/baz.css, this would write the CSS modules mapping JSON to /foo/bar/baz.css.json
1398+
- `false` - disables CSS modules mapping JSON output
1399+
1400+
**webpack.config.js**
1401+
1402+
```js
1403+
module.exports = {
1404+
module: {
1405+
rules: [
1406+
{
1407+
test: /\.css$/i,
1408+
loader: "css-loader",
1409+
options: {
1410+
modules: {
1411+
getJSON: true,
1412+
},
1413+
},
1414+
},
1415+
],
1416+
},
1417+
};
1418+
```
1419+
1420+
###### `function`
1421+
1422+
Enables custom handling of the CSS modules mapping JSON output. The return value of the function is not used for anything internally and is only intended to customize output.
1423+
1424+
**webpack.config.js**
1425+
1426+
```js
1427+
module.exports = {
1428+
module: {
1429+
rules: [
1430+
{
1431+
test: /\.css$/i,
1432+
loader: "css-loader",
1433+
options: {
1434+
modules: {
1435+
getJSON: (resourcePath, json, outputPath) => {
1436+
// `resourcePath` is the original resource file path, e.g., /foo/bar/baz.css
1437+
// `json` is the CSS modules map
1438+
// `outputPath` is the expected output file path, e.g., /foo/bar/baz.css.json
1439+
},
1440+
},
1441+
},
1442+
},
1443+
],
1444+
},
1445+
};
1446+
```
1447+
13751448
### `importLoaders`
13761449

13771450
Type:

‎src/index.js

+12
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
stringifyRequest,
2828
warningFactory,
2929
syntaxErrorFactory,
30+
writeModulesMap,
3031
} from "./utils";
3132

3233
export default async function loader(content, map, meta) {
@@ -274,5 +275,16 @@ export default async function loader(content, map, meta) {
274275
isTemplateLiteralSupported
275276
);
276277

278+
try {
279+
const { getJSON } = options.modules;
280+
if (getJSON) {
281+
await writeModulesMap(getJSON, resourcePath, exports);
282+
}
283+
} catch (error) {
284+
callback(error);
285+
286+
return;
287+
}
288+
277289
callback(null, `${importCode}${moduleCode}${exportCode}`);
278290
}

‎src/options.json

+12
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,18 @@
169169
"description": "Export only locals.",
170170
"link": "https://github.com/webpack-contrib/css-loader#exportonlylocals",
171171
"type": "boolean"
172+
},
173+
"getJSON": {
174+
"description": "Output CSS modules mapping to a JSON file or through a callback.",
175+
"link": "https://github.com/webpack-contrib/css-loader#getJSON",
176+
"anyOf": [
177+
{
178+
"type": "boolean"
179+
},
180+
{
181+
"instanceof": "Function"
182+
}
183+
]
172184
}
173185
}
174186
}

‎src/utils.js

+22
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55
import { fileURLToPath } from "url";
66
import path from "path";
7+
import fsp from "fs/promises";
78

89
import modulesValues from "postcss-modules-values";
910
import localByDefault from "postcss-modules-local-by-default";
@@ -1412,6 +1413,26 @@ function syntaxErrorFactory(error) {
14121413
return obj;
14131414
}
14141415

1416+
async function writeModulesMap(getJSON, resourcePath, exports) {
1417+
const json = exports.reduce((acc, { name, value }) => {
1418+
return { ...acc, [name]: value };
1419+
}, {});
1420+
1421+
const outputPath = path.resolve(
1422+
path.dirname(resourcePath),
1423+
`${path.basename(resourcePath)}.json`
1424+
);
1425+
1426+
if (getJSON === true) {
1427+
// If true, output a JSON CSS modules mapping file in the same directory as the resource
1428+
await fsp.writeFile(outputPath, JSON.stringify(json));
1429+
} else if (typeof getJSON === "function") {
1430+
// If function, call function with call getJSON with similar args as postcss-modules#getJSON
1431+
// https://github.com/madyankin/postcss-modules/tree/master?tab=readme-ov-file#saving-exported-classes
1432+
getJSON(resourcePath, json, outputPath);
1433+
}
1434+
}
1435+
14151436
export {
14161437
normalizeOptions,
14171438
shouldUseModulesPlugins,
@@ -1439,4 +1460,5 @@ export {
14391460
defaultGetLocalIdent,
14401461
warningFactory,
14411462
syntaxErrorFactory,
1463+
writeModulesMap,
14421464
};

‎test/__snapshots__/modules-option.test.js.snap

+132
Original file line numberDiff line numberDiff line change
@@ -1122,6 +1122,75 @@ exports[`"modules" option should emit warning when localIdentName is emoji: erro
11221122

11231123
exports[`"modules" option should emit warning when localIdentName is emoji: warnings 1`] = `Array []`;
11241124

1125+
exports[`"modules" option should invoke the custom getJSON function with getJSON as a function: errors 1`] = `Array []`;
1126+
1127+
exports[`"modules" option should invoke the custom getJSON function with getJSON as a function: mapping 1`] = `
1128+
Object {
1129+
"a": "RT7ktT7mB7tfBR25sJDZ",
1130+
"b": "IZmhTnK9CIeu6ww6Zjbv",
1131+
"c": "PV11nPFlF7mzEgCXkQw4",
1132+
}
1133+
`;
1134+
1135+
exports[`"modules" option should invoke the custom getJSON function with getJSON as a function: module 1`] = `
1136+
"// Imports
1137+
import ___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___ from \\"../../../../src/runtime/noSourceMaps.js\\";
1138+
import ___CSS_LOADER_API_IMPORT___ from \\"../../../../src/runtime/api.js\\";
1139+
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___);
1140+
// Module
1141+
___CSS_LOADER_EXPORT___.push([module.id, \`.RT7ktT7mB7tfBR25sJDZ {
1142+
background-color: aliceblue;
1143+
}
1144+
1145+
.IZmhTnK9CIeu6ww6Zjbv {
1146+
background-color: burlywood;
1147+
}
1148+
1149+
.PV11nPFlF7mzEgCXkQw4 {
1150+
background-color: chartreuse;
1151+
}
1152+
1153+
.d {
1154+
background-color: darkgoldenrod
1155+
}
1156+
\`, \\"\\"]);
1157+
// Exports
1158+
___CSS_LOADER_EXPORT___.locals = {
1159+
\\"a\\": \`RT7ktT7mB7tfBR25sJDZ\`,
1160+
\\"b\\": \`IZmhTnK9CIeu6ww6Zjbv\`,
1161+
\\"c\\": \`PV11nPFlF7mzEgCXkQw4\`
1162+
};
1163+
export default ___CSS_LOADER_EXPORT___;
1164+
"
1165+
`;
1166+
1167+
exports[`"modules" option should invoke the custom getJSON function with getJSON as a function: result 1`] = `
1168+
Array [
1169+
Array [
1170+
"./modules/getJSON/source.css",
1171+
".RT7ktT7mB7tfBR25sJDZ {
1172+
background-color: aliceblue;
1173+
}
1174+
1175+
.IZmhTnK9CIeu6ww6Zjbv {
1176+
background-color: burlywood;
1177+
}
1178+
1179+
.PV11nPFlF7mzEgCXkQw4 {
1180+
background-color: chartreuse;
1181+
}
1182+
1183+
.d {
1184+
background-color: darkgoldenrod
1185+
}
1186+
",
1187+
"",
1188+
],
1189+
]
1190+
`;
1191+
1192+
exports[`"modules" option should invoke the custom getJSON function with getJSON as a function: warnings 1`] = `Array []`;
1193+
11251194
exports[`"modules" option should keep order: errors 1`] = `Array []`;
11261195

11271196
exports[`"modules" option should keep order: module 1`] = `
@@ -1194,6 +1263,69 @@ Array [
11941263

11951264
exports[`"modules" option should keep order: warnings 1`] = `Array []`;
11961265

1266+
exports[`"modules" option should output a co-located CSS modules map file with getJSON as true: errors 1`] = `Array []`;
1267+
1268+
exports[`"modules" option should output a co-located CSS modules map file with getJSON as true: mapping 1`] = `"{\\"a\\":\\"RT7ktT7mB7tfBR25sJDZ\\",\\"b\\":\\"IZmhTnK9CIeu6ww6Zjbv\\",\\"c\\":\\"PV11nPFlF7mzEgCXkQw4\\"}"`;
1269+
1270+
exports[`"modules" option should output a co-located CSS modules map file with getJSON as true: module 1`] = `
1271+
"// Imports
1272+
import ___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___ from \\"../../../../src/runtime/noSourceMaps.js\\";
1273+
import ___CSS_LOADER_API_IMPORT___ from \\"../../../../src/runtime/api.js\\";
1274+
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___);
1275+
// Module
1276+
___CSS_LOADER_EXPORT___.push([module.id, \`.RT7ktT7mB7tfBR25sJDZ {
1277+
background-color: aliceblue;
1278+
}
1279+
1280+
.IZmhTnK9CIeu6ww6Zjbv {
1281+
background-color: burlywood;
1282+
}
1283+
1284+
.PV11nPFlF7mzEgCXkQw4 {
1285+
background-color: chartreuse;
1286+
}
1287+
1288+
.d {
1289+
background-color: darkgoldenrod
1290+
}
1291+
\`, \\"\\"]);
1292+
// Exports
1293+
___CSS_LOADER_EXPORT___.locals = {
1294+
\\"a\\": \`RT7ktT7mB7tfBR25sJDZ\`,
1295+
\\"b\\": \`IZmhTnK9CIeu6ww6Zjbv\`,
1296+
\\"c\\": \`PV11nPFlF7mzEgCXkQw4\`
1297+
};
1298+
export default ___CSS_LOADER_EXPORT___;
1299+
"
1300+
`;
1301+
1302+
exports[`"modules" option should output a co-located CSS modules map file with getJSON as true: result 1`] = `
1303+
Array [
1304+
Array [
1305+
"./modules/getJSON/source.css",
1306+
".RT7ktT7mB7tfBR25sJDZ {
1307+
background-color: aliceblue;
1308+
}
1309+
1310+
.IZmhTnK9CIeu6ww6Zjbv {
1311+
background-color: burlywood;
1312+
}
1313+
1314+
.PV11nPFlF7mzEgCXkQw4 {
1315+
background-color: chartreuse;
1316+
}
1317+
1318+
.d {
1319+
background-color: darkgoldenrod
1320+
}
1321+
",
1322+
"",
1323+
],
1324+
]
1325+
`;
1326+
1327+
exports[`"modules" option should output a co-located CSS modules map file with getJSON as true: warnings 1`] = `Array []`;
1328+
11971329
exports[`"modules" option should resolve absolute path in composes: errors 1`] = `Array []`;
11981330

11991331
exports[`"modules" option should resolve absolute path in composes: module 1`] = `
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.a {
2+
background-color: aliceblue;
3+
}
4+
5+
.b {
6+
background-color: burlywood;
7+
}
8+
9+
.c {
10+
background-color: chartreuse;
11+
}
12+
13+
:global(.d) {
14+
background-color: darkgoldenrod
15+
}
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import css from './source.css';
2+
3+
__export__ = css;
4+
5+
export default css;

0 commit comments

Comments
 (0)
Please sign in to comment.