css-loader
Advanced tools
+16
-0
@@ -174,3 +174,19 @@ "use strict"; | ||
| const exportCode = (0, _utils.getExportCode)(exports, replacements, needToUseIcssPlugin, options, isTemplateLiteralSupported); | ||
| const { | ||
| getJSON | ||
| } = options.modules; | ||
| if (typeof getJSON === "function") { | ||
| try { | ||
| await getJSON({ | ||
| resourcePath, | ||
| imports, | ||
| exports, | ||
| replacements | ||
| }); | ||
| } catch (error) { | ||
| callback(error); | ||
| return; | ||
| } | ||
| } | ||
| callback(null, `${importCode}${moduleCode}${exportCode}`); | ||
| } |
@@ -176,2 +176,7 @@ { | ||
| "type": "boolean" | ||
| }, | ||
| "getJSON": { | ||
| "description": "Allows outputting of CSS modules mapping through a callback.", | ||
| "link": "https://github.com/webpack-contrib/css-loader#getJSON", | ||
| "instanceof": "Function" | ||
| } | ||
@@ -178,0 +183,0 @@ } |
+2
-2
| { | ||
| "name": "css-loader", | ||
| "version": "7.0.0", | ||
| "version": "7.1.0", | ||
| "description": "css loader module for webpack", | ||
@@ -39,3 +39,3 @@ "license": "MIT", | ||
| "test": "npm run test:coverage", | ||
| "prepare": "husky install && npm run build", | ||
| "prepare": "husky && npm run build", | ||
| "release": "standard-version" | ||
@@ -42,0 +42,0 @@ }, |
+264
-6
@@ -50,3 +50,3 @@ <div align="center"> | ||
| ```js | ||
| import css from "file.css"; | ||
| import * as css from "file.css"; | ||
| ``` | ||
@@ -331,2 +331,13 @@ | ||
| exportOnlyLocals: boolean; | ||
| getJSON: ({ | ||
| resourcePath, | ||
| imports, | ||
| exports, | ||
| replacements, | ||
| }: { | ||
| resourcePath: string; | ||
| imports: object[]; | ||
| exports: object[]; | ||
| replacements: object[]; | ||
| }) => Promise<void> | void; | ||
| }; | ||
@@ -609,2 +620,3 @@ ``` | ||
| exportOnlyLocals: false, | ||
| getJSON: ({ resourcePath, imports, exports, replacements }) => {}, | ||
| }, | ||
@@ -1168,7 +1180,7 @@ }, | ||
| // If using `exportLocalsConvention: "as-is"` (default value): | ||
| console.log(styles["foo-baz"], styles.bar); | ||
| // If using `exportLocalsConvention: "camel-case-only"`: | ||
| console.log(styles.fooBaz, styles.bar); | ||
| // If using `exportLocalsConvention: "as-is"`: | ||
| console.log(styles["foo-baz"], styles.bar); | ||
| ``` | ||
@@ -1391,2 +1403,248 @@ | ||
| ##### `getJSON` | ||
| Type: | ||
| ```ts | ||
| type getJSON = ({ | ||
| resourcePath, | ||
| imports, | ||
| exports, | ||
| replacements, | ||
| }: { | ||
| resourcePath: string; | ||
| imports: object[]; | ||
| exports: object[]; | ||
| replacements: object[]; | ||
| }) => Promise<void> | void; | ||
| ``` | ||
| Default: `undefined` | ||
| Enables a callback to output the CSS modules mapping JSON. The callback is invoked with an object containing the following: | ||
| - `resourcePath`: the absolute path of the original resource, e.g., `/foo/bar/baz.module.css` | ||
| - `imports`: an array of import objects with data about import types and file paths, e.g., | ||
| ```json | ||
| [ | ||
| { | ||
| "type": "icss_import", | ||
| "importName": "___CSS_LOADER_ICSS_IMPORT_0___", | ||
| "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\"", | ||
| "icss": true, | ||
| "index": 0 | ||
| } | ||
| ] | ||
| ``` | ||
| (Note that this will include all imports, not just those relevant to CSS modules.) | ||
| - `exports`: an array of export objects with exported names and values, e.g., | ||
| ```json | ||
| [ | ||
| { | ||
| "name": "main", | ||
| "value": "D2Oy" | ||
| } | ||
| ] | ||
| ``` | ||
| - `replacements`: an array of import replacement objects used for linking `imports` and `exports`, e.g., | ||
| ```json | ||
| { | ||
| "replacementName": "___CSS_LOADER_ICSS_IMPORT_0_REPLACEMENT_0___", | ||
| "importName": "___CSS_LOADER_ICSS_IMPORT_0___", | ||
| "localName": "main" | ||
| } | ||
| ``` | ||
| Using `getJSON`, it's possible to output a files with all CSS module mappings. | ||
| In the following example, we use `getJSON` to cache canonical mappings and | ||
| add stand-ins for any composed values (through `composes`), and we use a custom plugin | ||
| to consolidate the values and output them to a file: | ||
| **webpack.config.js** | ||
| ```js | ||
| const path = require("path"); | ||
| const fs = require("fs"); | ||
| const CSS_LOADER_REPLACEMENT_REGEX = | ||
| /(___CSS_LOADER_ICSS_IMPORT_\d+_REPLACEMENT_\d+___)/g; | ||
| const REPLACEMENT_REGEX = /___REPLACEMENT\[(.*?)]\[(.*?)]___/g; | ||
| const IDENTIFIER_REGEX = /\[(.*?)]\[(.*?)]/; | ||
| const replacementsMap = {}; | ||
| const canonicalValuesMap = {}; | ||
| const allExportsJson = {}; | ||
| function generateIdentifier(resourcePath, localName) { | ||
| return `[${resourcePath}][${localName}]`; | ||
| } | ||
| function addReplacements(resourcePath, imports, exportsJson, replacements) { | ||
| const importReplacementsMap = {}; | ||
| // create a dict to quickly identify imports and get their absolute stand-in strings in the currently loaded file | ||
| // e.g., { '___CSS_LOADER_ICSS_IMPORT_0_REPLACEMENT_0___': '___REPLACEMENT[/foo/bar/baz.css][main]___' } | ||
| importReplacementsMap[resourcePath] = replacements.reduce( | ||
| (acc, { replacementName, importName, localName }) => { | ||
| const replacementImportUrl = imports.find( | ||
| (importData) => importData.importName === importName, | ||
| ).url; | ||
| const relativePathRe = /.*!(.*)"/; | ||
| const [, relativePath] = replacementImportUrl.match(relativePathRe); | ||
| const importPath = path.resolve(path.dirname(resourcePath), relativePath); | ||
| const identifier = generateIdentifier(importPath, localName); | ||
| return { ...acc, [replacementName]: `___REPLACEMENT${identifier}___` }; | ||
| }, | ||
| {}, | ||
| ); | ||
| // iterate through the raw exports and add stand-in variables | ||
| // ('___REPLACEMENT[<absolute_path>][<class_name>]___') | ||
| // to be replaced in the plugin below | ||
| for (const [localName, classNames] of Object.entries(exportsJson)) { | ||
| const identifier = generateIdentifier(resourcePath, localName); | ||
| if (CSS_LOADER_REPLACEMENT_REGEX.test(classNames)) { | ||
| // if there are any replacements needed in the concatenated class names, | ||
| // add them all to the replacements map to be replaced altogether later | ||
| replacementsMap[identifier] = classNames.replaceAll( | ||
| CSS_LOADER_REPLACEMENT_REGEX, | ||
| (_, replacementName) => | ||
| importReplacementsMap[resourcePath][replacementName], | ||
| ); | ||
| } else { | ||
| // otherwise, no class names need replacements so we can add them to | ||
| // canonical values map and all exports JSON verbatim | ||
| canonicalValuesMap[identifier] = classNames; | ||
| allExportsJson[resourcePath] = allExportsJson[resourcePath] || {}; | ||
| allExportsJson[resourcePath][localName] = classNames; | ||
| } | ||
| } | ||
| } | ||
| function replaceReplacements(classNames) { | ||
| return classNames.replaceAll( | ||
| REPLACEMENT_REGEX, | ||
| (_, resourcePath, localName) => { | ||
| const identifier = generateIdentifier(resourcePath, localName); | ||
| if (identifier in canonicalValuesMap) { | ||
| return canonicalValuesMap[identifier]; | ||
| } | ||
| // Recurse through other stand-in that may be imports | ||
| const canonicalValue = replaceReplacements(replacementsMap[identifier]); | ||
| canonicalValuesMap[identifier] = canonicalValue; | ||
| return canonicalValue; | ||
| }, | ||
| ); | ||
| } | ||
| function getJSON({ resourcePath, imports, exports, replacements }) { | ||
| const exportsJson = exports.reduce((acc, { name, value }) => { | ||
| return { ...acc, [name]: value }; | ||
| }, {}); | ||
| if (replacements.length > 0) { | ||
| // replacements present --> add stand-in values for absolute paths and local names, | ||
| // which will be resolved to their canonical values in the plugin below | ||
| addReplacements(resourcePath, imports, exportsJson, replacements); | ||
| } else { | ||
| // no replacements present --> add to canonicalValuesMap verbatim | ||
| // since all values here are canonical/don't need resolution | ||
| for (const [key, value] of Object.entries(exportsJson)) { | ||
| const id = `[${resourcePath}][${key}]`; | ||
| canonicalValuesMap[id] = value; | ||
| } | ||
| allExportsJson[resourcePath] = exportsJson; | ||
| } | ||
| } | ||
| class CssModulesJsonPlugin { | ||
| constructor(options) { | ||
| this.options = options; | ||
| } | ||
| // eslint-disable-next-line class-methods-use-this | ||
| apply(compiler) { | ||
| compiler.hooks.emit.tap("CssModulesJsonPlugin", () => { | ||
| for (const [identifier, classNames] of Object.entries(replacementsMap)) { | ||
| const adjustedClassNames = replaceReplacements(classNames); | ||
| replacementsMap[identifier] = adjustedClassNames; | ||
| const [, resourcePath, localName] = identifier.match(IDENTIFIER_REGEX); | ||
| allExportsJson[resourcePath] = allExportsJson[resourcePath] || {}; | ||
| allExportsJson[resourcePath][localName] = adjustedClassNames; | ||
| } | ||
| fs.writeFileSync( | ||
| this.options.filepath, | ||
| JSON.stringify( | ||
| // Make path to be relative to `context` (your project root) | ||
| Object.fromEntries( | ||
| Object.entries(allExportsJson).map((key) => { | ||
| key[0] = path | ||
| .relative(compiler.context, key[0]) | ||
| .replace(/\\/g, "/"); | ||
| return key; | ||
| }), | ||
| ), | ||
| null, | ||
| 2, | ||
| ), | ||
| "utf8", | ||
| ); | ||
| }); | ||
| } | ||
| } | ||
| module.exports = { | ||
| module: { | ||
| rules: [ | ||
| { | ||
| test: /\.css$/i, | ||
| loader: "css-loader", | ||
| options: { modules: { getJSON } }, | ||
| }, | ||
| ], | ||
| }, | ||
| plugins: [ | ||
| new CssModulesJsonPlugin({ | ||
| filepath: path.resolve(__dirname, "./output.css.json"), | ||
| }), | ||
| ], | ||
| }; | ||
| ``` | ||
| 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`: | ||
| ```json | ||
| { | ||
| "foo/bar/baz.module.css": { | ||
| "main": "D2Oy", | ||
| "header": "thNN" | ||
| }, | ||
| "foot/bear/bath.module.css": { | ||
| "logo": "sqiR", | ||
| "info": "XMyI" | ||
| } | ||
| } | ||
| ``` | ||
| This is saved to a local file named `output.css.json`. | ||
| ### `importLoaders` | ||
@@ -2041,4 +2299,4 @@ | ||
| ```jsx | ||
| import svars from "variables.scss"; | ||
| import styles from "Component.module.scss"; | ||
| import * as svars from "variables.scss"; | ||
| import * as styles from "Component.module.scss"; | ||
@@ -2045,0 +2303,0 @@ // Render DOM with CSS modules class name |
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
140449
6.31%2282
0.93%2329
12.46%