@@ -327,6 +327,17 @@ type modules =
327
327
| " dashes-only"
328
328
| ((name : string ) => string );
329
329
exportOnlyLocals: boolean ;
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 ;
330
341
};
331
342
```
332
343
@@ -604,6 +615,7 @@ module.exports = {
604
615
namedExport: true ,
605
616
exportLocalsConvention: " as-is" ,
606
617
exportOnlyLocals: false ,
618
+ getJSON : ({ resourcePath, imports, exports , replacements }) => {},
607
619
},
608
620
},
609
621
},
@@ -1384,6 +1396,298 @@ module.exports = {
1384
1396
};
1385
1397
```
1386
1398
1399
+ ##### ` getJSON `
1400
+
1401
+ Type:
1402
+
1403
+ ``` ts
1404
+ type getJSON = ({
1405
+ resourcePath,
1406
+ imports,
1407
+ exports,
1408
+ replacements,
1409
+ }: {
1410
+ resourcePath: string ;
1411
+ imports: object [];
1412
+ exports: object [];
1413
+ replacements: object [];
1414
+ }) => any ;
1415
+ ```
1416
+
1417
+ Default: ` undefined `
1418
+
1419
+ Enables a callback to output the CSS modules mapping JSON. The callback is invoked with an object containing the following:
1420
+
1421
+ - ` resourcePath ` : the absolute path of the original resource, e.g., ` /foo/bar/baz.module.css `
1422
+
1423
+ - ` imports ` : an array of import objects with data about import types and file paths, e.g.,
1424
+
1425
+ ``` json
1426
+ [
1427
+ {
1428
+ "type" : " icss_import" ,
1429
+ "importName" : " ___CSS_LOADER_ICSS_IMPORT_0___" ,
1430
+ "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\" " ,
1431
+ "icss" : true ,
1432
+ "index" : 0
1433
+ }
1434
+ ]
1435
+ ```
1436
+
1437
+ (Note that this will include all imports, not just those relevant to CSS modules.)
1438
+
1439
+ - ` exports ` : an array of export objects with exported names and values, e.g.,
1440
+
1441
+ ``` json
1442
+ [
1443
+ {
1444
+ "name" : " main" ,
1445
+ "value" : " D2Oy"
1446
+ }
1447
+ ]
1448
+ ```
1449
+
1450
+ - ` replacements ` : an array of import replacement objects used for linking ` imports ` and ` exports ` , e.g.,
1451
+
1452
+ ``` json
1453
+ {
1454
+ "replacementName" : " ___CSS_LOADER_ICSS_IMPORT_0_REPLACEMENT_0___" ,
1455
+ "importName" : " ___CSS_LOADER_ICSS_IMPORT_0___" ,
1456
+ "localName" : " main"
1457
+ }
1458
+ ```
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
+ Using ` getJSON ` , it's possible to output a files with all CSS module mappings.
1526
+ In the following example, we use ` getJSON ` to cache canonical mappings and
1527
+ add stand-ins for any composed values (through ` composes ` ), and we use a custom plugin
1528
+ to consolidate the values and output them to a file:
1529
+
1530
+ ``` js
1531
+ const CSS_LOADER_REPLACEMENT_REGEX =
1532
+ / (___CSS_LOADER_ICSS_IMPORT_\d + _REPLACEMENT_\d + ___)/ g ;
1533
+ const REPLACEMENT_REGEX = / ___REPLACEMENT\[ (. *? )\]\[ (. *? )\] ___/ g ;
1534
+ const IDENTIFIER_REGEX = / \[ (. *? )\]\[ (. *? )\] / ;
1535
+ const replacementsMap = {};
1536
+ const canonicalValuesMap = {};
1537
+ const allExportsJson = {};
1538
+
1539
+ function generateIdentifier (resourcePath , localName ) {
1540
+ return ` [${ resourcePath} ][${ localName} ]` ;
1541
+ }
1542
+
1543
+ function addReplacements (resourcePath , imports , exportsJson , replacements ) {
1544
+ const importReplacementsMap = {};
1545
+
1546
+ // create a dict to quickly identify imports and get their absolute stand-in strings in the currently loaded file
1547
+ // e.g., { '___CSS_LOADER_ICSS_IMPORT_0_REPLACEMENT_0___': '___REPLACEMENT[/foo/bar/baz.css][main]___' }
1548
+ importReplacementsMap[resourcePath] = replacements .reduce (
1549
+ (acc , { replacementName, importName, localName }) => {
1550
+ const replacementImportUrl = imports .find (
1551
+ (importData ) => importData .importName === importName,
1552
+ ).url ;
1553
+ const relativePathRe = / . * !(. * )"/ ;
1554
+ const [, relativePath ] = replacementImportUrl .match (relativePathRe);
1555
+ const importPath = path .resolve (path .dirname (resourcePath), relativePath);
1556
+ const identifier = generateIdentifier (importPath, localName);
1557
+ return { ... acc, [replacementName]: ` ___REPLACEMENT${ identifier} ___` };
1558
+ },
1559
+ {},
1560
+ );
1561
+
1562
+ // iterate through the raw exports and add stand-in variables
1563
+ // ('___REPLACEMENT[<absolute_path>][<class_name>]___')
1564
+ // to be replaced in the plugin below
1565
+ for (const [localName , classNames ] of Object .entries (exportsJson)) {
1566
+ const identifier = generateIdentifier (resourcePath, localName);
1567
+
1568
+ if (CSS_LOADER_REPLACEMENT_REGEX .test (classNames)) {
1569
+ // if there are any replacements needed in the concatenated class names,
1570
+ // add them all to the replacements map to be replaced altogether later
1571
+ replacementsMap[identifier] = classNames .replaceAll (
1572
+ CSS_LOADER_REPLACEMENT_REGEX ,
1573
+ (_ , replacementName ) => {
1574
+ return importReplacementsMap[resourcePath][replacementName];
1575
+ },
1576
+ );
1577
+ } else {
1578
+ // otherwise, no class names need replacements so we can add them to
1579
+ // canonical values map and all exports JSON verbatim
1580
+ canonicalValuesMap[identifier] = classNames;
1581
+
1582
+ allExportsJson[resourcePath] = allExportsJson[resourcePath] || {};
1583
+ allExportsJson[resourcePath][localName] = classNames;
1584
+ }
1585
+ }
1586
+ }
1587
+
1588
+ function replaceReplacements (classNames ) {
1589
+ const adjustedClassNames = classNames .replaceAll (
1590
+ REPLACEMENT_REGEX ,
1591
+ (_ , resourcePath , localName ) => {
1592
+ const identifier = generateIdentifier (resourcePath, localName);
1593
+ if (identifier in canonicalValuesMap) {
1594
+ return canonicalValuesMap[identifier];
1595
+ }
1596
+
1597
+ // recurse through other stand-in that may be imports
1598
+ const canonicalValue = replaceReplacements (replacementsMap[identifier]);
1599
+ canonicalValuesMap[identifier] = canonicalValue;
1600
+ return canonicalValue;
1601
+ },
1602
+ );
1603
+
1604
+ return adjustedClassNames;
1605
+ }
1606
+
1607
+ module .exports = {
1608
+ module: {
1609
+ rules: [
1610
+ {
1611
+ test: / \. css$ / i ,
1612
+ 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
+ },
1644
+ },
1645
+ ],
1646
+ },
1647
+ 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
+ },
1670
+ ],
1671
+ };
1672
+ ```
1673
+
1674
+ 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 ` :
1675
+
1676
+ ``` json
1677
+ {
1678
+ "/foo/bar/baz.module.css" : {
1679
+ "main" : " D2Oy" ,
1680
+ "header" : " thNN"
1681
+ },
1682
+ "/foot/bear/bath.module.css" : {
1683
+ "logo" : " sqiR" ,
1684
+ "info" : " XMyI"
1685
+ }
1686
+ }
1687
+ ```
1688
+
1689
+ This is saved to a local file named ` output.css.json ` .
1690
+
1387
1691
### ` importLoaders `
1388
1692
1389
1693
Type:
0 commit comments