@@ -327,7 +327,17 @@ type modules =
327327 | " dashesOnly"
328328 | ((name : string ) => string );
329329 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 ;
331341 };
332342```
333343
@@ -593,7 +603,7 @@ module.exports = {
593603 namedExport: true ,
594604 exportLocalsConvention: " camelCase" ,
595605 exportOnlyLocals: false ,
596- getJSON : (resourcePath , json ) => {},
606+ getJSON : ({ resourcePath, imports, exports , replacements } ) => {},
597607 },
598608 },
599609 },
@@ -1379,34 +1389,59 @@ module.exports = {
13791389Type:
13801390
13811391``` 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 ;
13831403```
13841404
13851405Default: ` undefined `
13861406
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 :
13881408
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 `
13911410
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+ ]
13921423```
1393- /* baz.css */
13941424
1395- .a {
1396- background-color: aliceblue;
1397- }
1425+ (Note that this will include all imports, not just those relevant to CSS modules.)
13981426
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+ ]
14021436```
14031437
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.,
14051439
1406- ```
1440+ ``` json
14071441{
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"
14101445}
14111446```
14121447
@@ -1422,8 +1457,13 @@ module.exports = {
14221457 loader: " css-loader" ,
14231458 options: {
14241459 modules: {
1425- getJSON : (resourcePath , json ) => {
1460+ getJSON : ({ resourcePath, exports } ) => {
14261461 // 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+
14271467 const outputPath = path .resolve (
14281468 path .dirname (resourcePath),
14291469 ` ${ path .basename (resourcePath)} .json`
@@ -1448,7 +1488,12 @@ module.exports = {
14481488 loader: " css-loader" ,
14491489 options: {
14501490 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+
14521497 const outputPath = path .resolve (
14531498 path .dirname (resourcePath),
14541499 ` ${ path .basename (resourcePath)} .json`
@@ -1465,6 +1510,172 @@ module.exports = {
14651510};
14661511```
14671512
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+
14681679### ` importLoaders `
14691680
14701681Type:
0 commit comments