@@ -337,7 +337,7 @@ type modules =
337
337
imports: object [];
338
338
exports: object [];
339
339
replacements: object [];
340
- }) => any ;
340
+ }) => Promise < void > | void ;
341
341
};
342
342
```
343
343
@@ -1411,7 +1411,7 @@ type getJSON = ({
1411
1411
imports: object [];
1412
1412
exports: object [];
1413
1413
replacements: object [];
1414
- }) => any ;
1414
+ }) => Promise < void > | void ;
1415
1415
```
1416
1416
1417
1417
Default: ` undefined `
@@ -1457,81 +1457,21 @@ Enables a callback to output the CSS modules mapping JSON. The callback is invok
1457
1457
}
1458
1458
```
1459
1459
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
-
1525
1460
Using ` getJSON ` , it's possible to output a files with all CSS module mappings.
1526
1461
In the following example, we use ` getJSON ` to cache canonical mappings and
1527
1462
add stand-ins for any composed values (through ` composes ` ), and we use a custom plugin
1528
1463
to consolidate the values and output them to a file:
1529
1464
1465
+ ** webpack.config.js**
1466
+
1530
1467
``` js
1468
+ const path = require (" path" );
1469
+ const fs = require (" fs" );
1470
+
1531
1471
const CSS_LOADER_REPLACEMENT_REGEX =
1532
1472
/ (___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 = / \[ (. *? )]\[ (. *? )]/ ;
1535
1475
const replacementsMap = {};
1536
1476
const canonicalValuesMap = {};
1537
1477
const allExportsJson = {};
@@ -1570,9 +1510,8 @@ function addReplacements(resourcePath, imports, exportsJson, replacements) {
1570
1510
// add them all to the replacements map to be replaced altogether later
1571
1511
replacementsMap[identifier] = classNames .replaceAll (
1572
1512
CSS_LOADER_REPLACEMENT_REGEX ,
1573
- (_ , replacementName ) => {
1574
- return importReplacementsMap[resourcePath][replacementName];
1575
- },
1513
+ (_ , replacementName ) =>
1514
+ importReplacementsMap[resourcePath][replacementName],
1576
1515
);
1577
1516
} else {
1578
1517
// otherwise, no class names need replacements so we can add them to
@@ -1586,22 +1525,86 @@ function addReplacements(resourcePath, imports, exportsJson, replacements) {
1586
1525
}
1587
1526
1588
1527
function replaceReplacements (classNames ) {
1589
- const adjustedClassNames = classNames .replaceAll (
1528
+ return classNames .replaceAll (
1590
1529
REPLACEMENT_REGEX ,
1591
1530
(_ , resourcePath , localName ) => {
1592
1531
const identifier = generateIdentifier (resourcePath, localName);
1532
+
1593
1533
if (identifier in canonicalValuesMap) {
1594
1534
return canonicalValuesMap[identifier];
1595
1535
}
1596
1536
1597
- // recurse through other stand-in that may be imports
1537
+ // Recurse through other stand-in that may be imports
1598
1538
const canonicalValue = replaceReplacements (replacementsMap[identifier]);
1539
+
1599
1540
canonicalValuesMap[identifier] = canonicalValue;
1541
+
1600
1542
return canonicalValue;
1601
1543
},
1602
1544
);
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
+ }
1603
1564
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
+ }
1605
1608
}
1606
1609
1607
1610
module .exports = {
@@ -1610,63 +1613,14 @@ module.exports = {
1610
1613
{
1611
1614
test: / \. css$ / i ,
1612
1615
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 } },
1644
1617
},
1645
1618
],
1646
1619
},
1647
1620
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
+ }),
1670
1624
],
1671
1625
};
1672
1626
```
@@ -1675,11 +1629,11 @@ In the above, all import aliases are replaced with `___REPLACEMENT[<resourcePath
1675
1629
1676
1630
``` json
1677
1631
{
1678
- "/ foo/bar/baz.module.css" : {
1632
+ "foo/bar/baz.module.css" : {
1679
1633
"main" : " D2Oy" ,
1680
1634
"header" : " thNN"
1681
1635
},
1682
- "/ foot/bear/bath.module.css" : {
1636
+ "foot/bear/bath.module.css" : {
1683
1637
"logo" : " sqiR" ,
1684
1638
"info" : " XMyI"
1685
1639
}
0 commit comments