Skip to content

Commit 15f793d

Browse files
docs: update logic (#1587)
1 parent 9c165a4 commit 15f793d

File tree

5 files changed

+396
-130
lines changed

5 files changed

+396
-130
lines changed

README.md

+84-130
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ type modules =
337337
imports: object[];
338338
exports: object[];
339339
replacements: object[];
340-
}) => any;
340+
}) => Promise<void> | void;
341341
};
342342
```
343343

@@ -1411,7 +1411,7 @@ type getJSON = ({
14111411
imports: object[];
14121412
exports: object[];
14131413
replacements: object[];
1414-
}) => any;
1414+
}) => Promise<void> | void;
14151415
```
14161416

14171417
Default: `undefined`
@@ -1457,81 +1457,21 @@ Enables a callback to output the CSS modules mapping JSON. The callback is invok
14571457
}
14581458
```
14591459

1460-
**webpack.config.js**
1461-
1462-
```js
1463-
// supports a synchronous callback
1464-
module.exports = {
1465-
module: {
1466-
rules: [
1467-
{
1468-
test: /\.css$/i,
1469-
loader: "css-loader",
1470-
options: {
1471-
modules: {
1472-
getJSON: ({ resourcePath, exports }) => {
1473-
// synchronously write a .json mapping file in the same directory as the resource
1474-
const exportsJson = exports.reduce(
1475-
(acc, { name, value }) => ({ ...acc, [name]: value }),
1476-
{},
1477-
);
1478-
1479-
const outputPath = path.resolve(
1480-
path.dirname(resourcePath),
1481-
`${path.basename(resourcePath)}.json`,
1482-
);
1483-
1484-
const fs = require("fs");
1485-
fs.writeFileSync(outputPath, JSON.stringify(json));
1486-
},
1487-
},
1488-
},
1489-
},
1490-
],
1491-
},
1492-
};
1493-
1494-
// supports an asynchronous callback
1495-
module.exports = {
1496-
module: {
1497-
rules: [
1498-
{
1499-
test: /\.css$/i,
1500-
loader: "css-loader",
1501-
options: {
1502-
modules: {
1503-
getJSON: async ({ resourcePath, exports }) => {
1504-
const exportsJson = exports.reduce(
1505-
(acc, { name, value }) => ({ ...acc, [name]: value }),
1506-
{},
1507-
);
1508-
1509-
const outputPath = path.resolve(
1510-
path.dirname(resourcePath),
1511-
`${path.basename(resourcePath)}.json`,
1512-
);
1513-
1514-
const fsp = require("fs/promises");
1515-
await fsp.writeFile(outputPath, JSON.stringify(json));
1516-
},
1517-
},
1518-
},
1519-
},
1520-
],
1521-
},
1522-
};
1523-
```
1524-
15251460
Using `getJSON`, it's possible to output a files with all CSS module mappings.
15261461
In the following example, we use `getJSON` to cache canonical mappings and
15271462
add stand-ins for any composed values (through `composes`), and we use a custom plugin
15281463
to consolidate the values and output them to a file:
15291464

1465+
**webpack.config.js**
1466+
15301467
```js
1468+
const path = require("path");
1469+
const fs = require("fs");
1470+
15311471
const CSS_LOADER_REPLACEMENT_REGEX =
15321472
/(___CSS_LOADER_ICSS_IMPORT_\d+_REPLACEMENT_\d+___)/g;
1533-
const REPLACEMENT_REGEX = /___REPLACEMENT\[(.*?)\]\[(.*?)\]___/g;
1534-
const IDENTIFIER_REGEX = /\[(.*?)\]\[(.*?)\]/;
1473+
const REPLACEMENT_REGEX = /___REPLACEMENT\[(.*?)]\[(.*?)]___/g;
1474+
const IDENTIFIER_REGEX = /\[(.*?)]\[(.*?)]/;
15351475
const replacementsMap = {};
15361476
const canonicalValuesMap = {};
15371477
const allExportsJson = {};
@@ -1570,9 +1510,8 @@ function addReplacements(resourcePath, imports, exportsJson, replacements) {
15701510
// add them all to the replacements map to be replaced altogether later
15711511
replacementsMap[identifier] = classNames.replaceAll(
15721512
CSS_LOADER_REPLACEMENT_REGEX,
1573-
(_, replacementName) => {
1574-
return importReplacementsMap[resourcePath][replacementName];
1575-
},
1513+
(_, replacementName) =>
1514+
importReplacementsMap[resourcePath][replacementName],
15761515
);
15771516
} else {
15781517
// otherwise, no class names need replacements so we can add them to
@@ -1586,22 +1525,86 @@ function addReplacements(resourcePath, imports, exportsJson, replacements) {
15861525
}
15871526

15881527
function replaceReplacements(classNames) {
1589-
const adjustedClassNames = classNames.replaceAll(
1528+
return classNames.replaceAll(
15901529
REPLACEMENT_REGEX,
15911530
(_, resourcePath, localName) => {
15921531
const identifier = generateIdentifier(resourcePath, localName);
1532+
15931533
if (identifier in canonicalValuesMap) {
15941534
return canonicalValuesMap[identifier];
15951535
}
15961536

1597-
// recurse through other stand-in that may be imports
1537+
// Recurse through other stand-in that may be imports
15981538
const canonicalValue = replaceReplacements(replacementsMap[identifier]);
1539+
15991540
canonicalValuesMap[identifier] = canonicalValue;
1541+
16001542
return canonicalValue;
16011543
},
16021544
);
1545+
}
1546+
1547+
function getJSON({ resourcePath, imports, exports, replacements }) {
1548+
const exportsJson = exports.reduce((acc, { name, value }) => {
1549+
return { ...acc, [name]: value };
1550+
}, {});
1551+
1552+
if (replacements.length > 0) {
1553+
// replacements present --> add stand-in values for absolute paths and local names,
1554+
// which will be resolved to their canonical values in the plugin below
1555+
addReplacements(resourcePath, imports, exportsJson, replacements);
1556+
} else {
1557+
// no replacements present --> add to canonicalValuesMap verbatim
1558+
// since all values here are canonical/don't need resolution
1559+
for (const [key, value] of Object.entries(exportsJson)) {
1560+
const id = `[${resourcePath}][${key}]`;
1561+
1562+
canonicalValuesMap[id] = value;
1563+
}
16031564

1604-
return adjustedClassNames;
1565+
allExportsJson[resourcePath] = exportsJson;
1566+
}
1567+
}
1568+
1569+
class CssModulesJsonPlugin {
1570+
constructor(options) {
1571+
this.options = options;
1572+
}
1573+
1574+
// eslint-disable-next-line class-methods-use-this
1575+
apply(compiler) {
1576+
compiler.hooks.emit.tap("CssModulesJsonPlugin", () => {
1577+
for (const [identifier, classNames] of Object.entries(replacementsMap)) {
1578+
const adjustedClassNames = replaceReplacements(classNames);
1579+
1580+
replacementsMap[identifier] = adjustedClassNames;
1581+
1582+
const [, resourcePath, localName] = identifier.match(IDENTIFIER_REGEX);
1583+
1584+
allExportsJson[resourcePath] = allExportsJson[resourcePath] || {};
1585+
allExportsJson[resourcePath][localName] = adjustedClassNames;
1586+
}
1587+
1588+
fs.writeFileSync(
1589+
this.options.filepath,
1590+
JSON.stringify(
1591+
// Make path to be relative to `context` (your project root)
1592+
Object.fromEntries(
1593+
Object.entries(allExportsJson).map((key) => {
1594+
key[0] = path
1595+
.relative(compiler.context, key[0])
1596+
.replace(/\\/g, "/");
1597+
1598+
return key;
1599+
}),
1600+
),
1601+
null,
1602+
2,
1603+
),
1604+
"utf8",
1605+
);
1606+
});
1607+
}
16051608
}
16061609

16071610
module.exports = {
@@ -1610,63 +1613,14 @@ module.exports = {
16101613
{
16111614
test: /\.css$/i,
16121615
loader: "css-loader",
1613-
options: {
1614-
modules: {
1615-
getJSON: ({ resourcePath, imports, exports, replacements }) => {
1616-
const exportsJson = exports.reduce(
1617-
(acc, { name, value }) => ({ ...acc, [name]: value }),
1618-
{},
1619-
);
1620-
1621-
if (replacements.length > 0) {
1622-
// replacements present --> add stand-in values for absolute paths and local names,
1623-
// which will be resolved to their canonical values in the plugin below
1624-
addReplacements(
1625-
resourcePath,
1626-
imports,
1627-
exportsJson,
1628-
replacements,
1629-
);
1630-
} else {
1631-
// no replacements present --> add to canonicalValuesMap verbatim
1632-
// since all values here are canonical/don't need resolution
1633-
for (const [key, value] of Object.entries(exportsJson)) {
1634-
const id = `[${resourcePath}][${key}]`;
1635-
1636-
canonicalValuesMap[id] = value;
1637-
}
1638-
1639-
allExportsJson[resourcePath] = exportsJson;
1640-
}
1641-
},
1642-
},
1643-
},
1616+
options: { modules: { getJSON } },
16441617
},
16451618
],
16461619
},
16471620
plugins: [
1648-
{
1649-
apply(compiler) {
1650-
compiler.hooks.done.tap("CssModulesJsonPlugin", () => {
1651-
for (const [identifier, classNames] of Object.entries(
1652-
replacementsMap,
1653-
)) {
1654-
const adjustedClassNames = replaceReplacements(classNames);
1655-
replacementsMap[identifier] = adjustedClassNames;
1656-
const [, resourcePath, localName] =
1657-
identifier.match(IDENTIFIER_REGEX);
1658-
allExportsJson[resourcePath] = allExportsJson[resourcePath] || {};
1659-
allExportsJson[resourcePath][localName] = adjustedClassNames;
1660-
}
1661-
1662-
fs.writeFileSync(
1663-
"./output.css.json",
1664-
JSON.stringify(allExportsJson, null, 2),
1665-
"utf8",
1666-
);
1667-
});
1668-
},
1669-
},
1621+
new CssModulesJsonPlugin({
1622+
filepath: path.resolve(__dirname, "./output.css.json"),
1623+
}),
16701624
],
16711625
};
16721626
```
@@ -1675,11 +1629,11 @@ In the above, all import aliases are replaced with `___REPLACEMENT[<resourcePath
16751629

16761630
```json
16771631
{
1678-
"/foo/bar/baz.module.css": {
1632+
"foo/bar/baz.module.css": {
16791633
"main": "D2Oy",
16801634
"header": "thNN"
16811635
},
1682-
"/foot/bear/bath.module.css": {
1636+
"foot/bear/bath.module.css": {
16831637
"logo": "sqiR",
16841638
"info": "XMyI"
16851639
}

src/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ export default async function loader(content, map, meta) {
274274
);
275275

276276
const { getJSON } = options.modules;
277+
277278
if (typeof getJSON === "function") {
278279
try {
279280
await getJSON({ resourcePath, imports, exports, replacements });

0 commit comments

Comments
 (0)