@@ -327,7 +327,17 @@ type modules =
327
327
| " dashesOnly"
328
328
| ((name : string ) => string );
329
329
exportOnlyLocals: boolean ;
330
- getJSON: (resourcePath : string , json : object ) => any ;
330
+ getJSON: ({
331
+ resourcePath,
332
+ imports,
333
+ exports,
334
+ replacements,
335
+ }: {
336
+ resourcePath: string ;
337
+ imports: object ;
338
+ exports: object ;
339
+ replacements: object [];
340
+ }) => any ;
331
341
};
332
342
```
333
343
@@ -593,7 +603,7 @@ module.exports = {
593
603
namedExport: true ,
594
604
exportLocalsConvention: " camelCase" ,
595
605
exportOnlyLocals: false ,
596
- getJSON : (resourcePath , json ) => {},
606
+ getJSON : ({ resourcePath, imports, exports , replacements } ) => {},
597
607
},
598
608
},
599
609
},
@@ -1379,34 +1389,59 @@ module.exports = {
1379
1389
Type:
1380
1390
1381
1391
``` ts
1382
- type getJSON = (resourcePath : string , json : object ) => any ;
1392
+ type getJSON = ({
1393
+ resourcePath,
1394
+ imports,
1395
+ exports,
1396
+ replacements,
1397
+ }: {
1398
+ resourcePath: string ;
1399
+ imports: object [];
1400
+ exports: object [];
1401
+ replacements: object [];
1402
+ }) => any ;
1383
1403
```
1384
1404
1385
1405
Default: ` undefined `
1386
1406
1387
- Enables a callback to output the CSS modules mapping JSON. The callback is invoked with two arguments :
1407
+ Enables a callback to output the CSS modules mapping JSON. The callback is invoked with an object containing the following :
1388
1408
1389
- - ` resourcePath ` : the absolutely path of the original resource, e.g., /foo/bar/baz.css
1390
- - ` json ` : the CSS modules map object, e.g.,
1409
+ - ` resourcePath ` : the absolute path of the original resource, e.g., ` /foo/bar/baz.module.css `
1391
1410
1411
+ - ` imports ` : an array of import objects with data about import types and file paths, e.g.,
1412
+
1413
+ ``` json
1414
+ [
1415
+ {
1416
+ "type" : " icss_import" ,
1417
+ "importName" : " ___CSS_LOADER_ICSS_IMPORT_0___" ,
1418
+ "url" : " \" -!../../../../../node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[4].use[1]!../../../../../node_modules/postcss-loader/dist/cjs.js!../../../../../node_modules/sass-loader/dist/cjs.js!../../../../baz.module.css\" " ,
1419
+ "icss" : true ,
1420
+ "index" : 0
1421
+ }
1422
+ ]
1392
1423
```
1393
- /* baz.css */
1394
1424
1395
- .a {
1396
- background-color: aliceblue;
1397
- }
1425
+ (Note that this will include all imports, not just those relevant to CSS modules.)
1398
1426
1399
- .b {
1400
- background-color: burlywood;
1401
- }
1427
+ - ` exports ` : an array of export objects with exported names and values, e.g.,
1428
+
1429
+ ``` json
1430
+ [
1431
+ {
1432
+ "name" : " main" ,
1433
+ "value" : " D2Oy"
1434
+ }
1435
+ ]
1402
1436
```
1403
1437
1404
- ` json ` will be something like the following (depending on your other ` modules ` settings):
1438
+ - ` replacements ` : an array of import replacement objects used for linking ` imports ` and ` exports ` , e.g.,
1405
1439
1406
- ```
1440
+ ``` json
1407
1441
{
1408
- "a": "a__uRkh1",
1409
- "b": "b__pjFcy"
1442
+ "replacementName" : " ___CSS_LOADER_ICSS_IMPORT_0_REPLACEMENT_0___" ,
1443
+ "importName" : " ___CSS_LOADER_ICSS_IMPORT_0___" ,
1444
+ "localName" : " main"
1410
1445
}
1411
1446
```
1412
1447
@@ -1422,8 +1457,13 @@ module.exports = {
1422
1457
loader: " css-loader" ,
1423
1458
options: {
1424
1459
modules: {
1425
- getJSON : (resourcePath , json ) => {
1460
+ getJSON : ({ resourcePath, exports } ) => {
1426
1461
// synchronously write a .json mapping file in the same directory as the resource
1462
+ const exportsJson = exports .reduce (
1463
+ (acc , { name, value }) => ({ ... acc, [name]: value }),
1464
+ {}
1465
+ );
1466
+
1427
1467
const outputPath = path .resolve (
1428
1468
path .dirname (resourcePath),
1429
1469
` ${ path .basename (resourcePath)} .json`
@@ -1448,7 +1488,12 @@ module.exports = {
1448
1488
loader: " css-loader" ,
1449
1489
options: {
1450
1490
modules: {
1451
- getJSON: async (resourcePath , json ) => {
1491
+ getJSON: async ({ resourcePath, exports }) => {
1492
+ const exportsJson = exports .reduce (
1493
+ (acc , { name, value }) => ({ ... acc, [name]: value }),
1494
+ {}
1495
+ );
1496
+
1452
1497
const outputPath = path .resolve (
1453
1498
path .dirname (resourcePath),
1454
1499
` ${ path .basename (resourcePath)} .json`
@@ -1465,6 +1510,172 @@ module.exports = {
1465
1510
};
1466
1511
```
1467
1512
1513
+ Using ` getJSON ` , it's possible to output a files with all CSS module mappings.
1514
+ In the following example, we use ` getJSON ` to cache canonical mappings and
1515
+ add stand-ins for any composed values (through ` composes ` ), and we use a custom plugin
1516
+ to consolidate the values and output them to a file:
1517
+
1518
+ ``` js
1519
+ const CSS_LOADER_REPLACEMENT_REGEX =
1520
+ / (___CSS_LOADER_ICSS_IMPORT_\d + _REPLACEMENT_\d + ___)/ g ;
1521
+ const REPLACEMENT_REGEX = / ___REPLACEMENT\[ (. *? )\]\[ (. *? )\] ___/ g ;
1522
+ const IDENTIFIER_REGEX = / \[ (. *? )\]\[ (. *? )\] / ;
1523
+ const replacementsMap = {};
1524
+ const canonicalValuesMap = {};
1525
+ const allExportsJson = {};
1526
+
1527
+ function generateIdentifier (resourcePath , localName ) {
1528
+ return ` [${ resourcePath} ][${ localName} ]` ;
1529
+ }
1530
+
1531
+ function addReplacements (resourcePath , imports , exportsJson , replacements ) {
1532
+ const importReplacementsMap = {};
1533
+
1534
+ // create a dict to quickly identify imports and get their absolute stand-in strings in the currently loaded file
1535
+ // e.g., { '___CSS_LOADER_ICSS_IMPORT_0_REPLACEMENT_0___': '___REPLACEMENT[/foo/bar/baz.css][main]___' }
1536
+ importReplacementsMap[resourcePath] = replacements .reduce (
1537
+ (acc , { replacementName, importName, localName }) => {
1538
+ const replacementImportUrl = imports .find (
1539
+ (importData ) => importData .importName === importName
1540
+ ).url ;
1541
+ const relativePathRe = / . * !(. * )"/ ;
1542
+ const [, relativePath ] = replacementImportUrl .match (relativePathRe);
1543
+ const importPath = path .resolve (path .dirname (resourcePath), relativePath);
1544
+ const identifier = generateIdentifier (importPath, localName);
1545
+ return { ... acc, [replacementName]: ` ___REPLACEMENT${ identifier} ___` };
1546
+ },
1547
+ {}
1548
+ );
1549
+
1550
+ // iterate through the raw exports and add stand-in variables
1551
+ // ('___REPLACEMENT[<absolute_path>][<class_name>]___')
1552
+ // to be replaced in the plugin below
1553
+ for (const [localName , classNames ] of Object .entries (exportsJson)) {
1554
+ const identifier = generateIdentifier (resourcePath, localName);
1555
+
1556
+ if (CSS_LOADER_REPLACEMENT_REGEX .test (classNames)) {
1557
+ // if there are any replacements needed in the concatenated class names,
1558
+ // add them all to the replacements map to be replaced altogether later
1559
+ replacementsMap[identifier] = classNames .replaceAll (
1560
+ CSS_LOADER_REPLACEMENT_REGEX ,
1561
+ (_ , replacementName ) => {
1562
+ return importReplacementsMap[resourcePath][replacementName];
1563
+ }
1564
+ );
1565
+ } else {
1566
+ // otherwise, no class names need replacements so we can add them to
1567
+ // canonical values map and all exports JSON verbatim
1568
+ canonicalValuesMap[identifier] = classNames;
1569
+
1570
+ allExportsJson[resourcePath] = allExportsJson[resourcePath] || {};
1571
+ allExportsJson[resourcePath][localName] = classNames;
1572
+ }
1573
+ }
1574
+ }
1575
+
1576
+ function replaceReplacements (classNames ) {
1577
+ const adjustedClassNames = classNames .replaceAll (
1578
+ REPLACEMENT_REGEX ,
1579
+ (_ , resourcePath , localName ) => {
1580
+ const identifier = generateIdentifier (resourcePath, localName);
1581
+ if (identifier in canonicalValuesMap) {
1582
+ return canonicalValuesMap[identifier];
1583
+ }
1584
+
1585
+ // recurse through other stand-in that may be imports
1586
+ const canonicalValue = replaceReplacements (replacementsMap[identifier]);
1587
+ canonicalValuesMap[identifier] = canonicalValue;
1588
+ return canonicalValue;
1589
+ }
1590
+ );
1591
+
1592
+ return adjustedClassNames;
1593
+ }
1594
+
1595
+ module .exports = {
1596
+ module: {
1597
+ rules: [
1598
+ {
1599
+ test: / \. css$ / i ,
1600
+ loader: " css-loader" ,
1601
+ options: {
1602
+ modules: {
1603
+ getJSON : ({ resourcePath, imports, exports , replacements }) => {
1604
+ const exportsJson = exports .reduce (
1605
+ (acc , { name, value }) => ({ ... acc, [name]: value }),
1606
+ {}
1607
+ );
1608
+
1609
+ if (replacements .length > 0 ) {
1610
+ // replacements present --> add stand-in values for absolute paths and local names,
1611
+ // which will be resolved to their canonical values in the plugin below
1612
+ addReplacements (
1613
+ resourcePath,
1614
+ imports,
1615
+ exportsJson,
1616
+ replacements
1617
+ );
1618
+ } else {
1619
+ // no replacements present --> add to canonicalValuesMap verbatim
1620
+ // since all values here are canonical/don't need resolution
1621
+ for (const [key , value ] of Object .entries (exportsJson)) {
1622
+ const id = ` [${ resourcePath} ][${ key} ]` ;
1623
+
1624
+ canonicalValuesMap[id] = value;
1625
+ }
1626
+
1627
+ allExportsJson[resourcePath] = exportsJson;
1628
+ }
1629
+ },
1630
+ },
1631
+ },
1632
+ },
1633
+ ],
1634
+ },
1635
+ plugins: [
1636
+ {
1637
+ apply (compiler ) {
1638
+ compiler .hooks .done .tap (" CssModulesJsonPlugin" , () => {
1639
+ for (const [identifier , classNames ] of Object .entries (
1640
+ replacementsMap
1641
+ )) {
1642
+ const adjustedClassNames = replaceReplacements (classNames);
1643
+ replacementsMap[identifier] = adjustedClassNames;
1644
+ const [, resourcePath , localName ] =
1645
+ identifier .match (IDENTIFIER_REGEX );
1646
+ allExportsJson[resourcePath] = allExportsJson[resourcePath] || {};
1647
+ allExportsJson[resourcePath][localName] = adjustedClassNames;
1648
+ }
1649
+
1650
+ fs .writeFileSync (
1651
+ " ./output.css.json" ,
1652
+ JSON .stringify (allExportsJson, null , 2 ),
1653
+ " utf8"
1654
+ );
1655
+ });
1656
+ },
1657
+ },
1658
+ ],
1659
+ };
1660
+ ```
1661
+
1662
+ In the above, all import aliases are replaced with ` ___REPLACEMENT[<resourcePath>][<localName>]___ ` in ` getJSON ` , and they're resolved in the custom plugin. All CSS mappings are contained in ` allExportsJson ` :
1663
+
1664
+ ``` json
1665
+ {
1666
+ "/foo/bar/baz.module.css" : {
1667
+ "main" : " D2Oy" ,
1668
+ "header" : " thNN"
1669
+ },
1670
+ "/foot/bear/bath.module.css" : {
1671
+ "logo" : " sqiR" ,
1672
+ "info" : " XMyI"
1673
+ }
1674
+ }
1675
+ ```
1676
+
1677
+ This is saved to a local file named ` output.css.json ` .
1678
+
1468
1679
### ` importLoaders `
1469
1680
1470
1681
Type:
0 commit comments