Skip to content

Instantly share code, notes, and snippets.

@joepie91
Created November 12, 2019 00:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joepie91/606cd5a48987c484bce027c10f268282 to your computer and use it in GitHub Desktop.
Save joepie91/606cd5a48987c484bce027c10f268282 to your computer and use it in GitHub Desktop.
css-loader internals notes
Loader utils
- parseString: Parse a given string as if it were a JSON-encoded string, mapping single-quote string boundaries to double-quote boundaries or just flat-out making up those boundaries, so that JSON.parse doesn't complain. If cannot be parsed as JSON, just return the string as-is. Seems to be used to decode escape codes in a variety of (non-JSON) strings.
- urlToRequest: "Converts some resource URL to a webpack module request."
- isUrlRequest: "Before call urlToRequest you need call isUrlRequest to ensure it is requestable url"
Docs here: https://www.npmjs.com/package/icss-utils
=====================
PostCSS
Node types:
- root: The CSS file itself?
- atrule: At-rule, like a media query?
- rule: Regular rule, like `#foo .bar { ... }`?
- decl: Property declaration, like `color: black`
- comment: Comment, like `/* foo bar */`
ICSS utils:
- replaceValueSymbols: takes a (value, replacementMap), extracts all identifiers (\w + dash sequences), and literal-replaces each one that exists as a key in the `replacementMap` to the corresponding value.
- replaceSymbols: takes a (PostCSS AST, replacementMap), and calls replaceValueSymbols for each thing that needs checking for replacements (selectors, at-rule selectors, property values).
- extractICSS: takes a PostCSS AST, and return an {icssImports, icssExports} that contains, respectively, all the import and export statements in the AST.
Imports are a (sourcePath => (localKey => remoteKey)) mapping.
Exports are just an (exportedKey => exportedValue) mapping. The `exportedValue` could (and probably will) just be a class name!
The original statements are removed from the AST.
=======================
PostCSS ecosystem
postcss-modules-local-by-default
(changes assumed untagged default from global to local)
untagged class names -> :local class names
:global class names -> untagged class names
postcss-modules-scope
(assumed global)
:local class names -> mangled class names + :export statements for the mapping
:global class names -> untagged class names
untagged class names -> untagged class names
postcss-icss-selectors
Depending on mode, either:
(assumed local)
untagged class names -> mangled class names + :export statements for the mapping?
:local class names -> mangled class names + :export statements for the mapping?
:global class names -> untagged class names
or:
(assumed global)
:local class names -> mangled class names + :export statements for the mapping?
:global class names -> untagged class names
untagged class names -> untagged class names
postcss-icss-composes
composes -> :export statement with concatenated class names
postcss-modules-extract-imports
external composes -> :import statement + local composes (referencing the temp-generated localKey)
postcss-modules-resolve-imports
:import statements -> directly inline the imported keys into the CSS wherever the localKey is used, consuming the :import statement
postcss-icss-values
@value statements -> :export statements for those values + values directly inlined where variable names referenced
postcss-modules-values
... same?
Custom css-loader plugins
icss-parser
- Extracts (and removes) all :import/:export statements from the CSS
- For each :import(url) { remoteKey -> localKey }
- index += 1
- Store mapping in importReplacements: remoteKey -> ___CSS_LOADER_IMPORT___${index}___
- Store `icss-import` entry: { url, index, export: localKey }
- Get importItemCode for {url, media}
- Store `import` entry: { import: importItemCode, item: { url, media } }
? If not already queued/processed, queue a bundler import + process of `url` (TODO: Look up in index.js what these `import`-type messages actually do)
- Apply all importReplacements to the AST using replaceSymbols
- For each :export { exportedKey -> exportedValue }
- If any identifier in `exportedValue` exists in `importReplacements`, replace it with the replacement value (this is probably to handle re-exports, because replaceSymbols would presumably ignore :export statements)
- Get exportItemCode for `name`
- Store `export` entry: { export: exportItemCode, item: { name: exportedKey, value: exportedValue } }
import-parser
NOTE: This parses @import rules, *not* :import rules! The :import rules are handled by the icss-parser.
- Extracts (and removes) all top-level @import statements from the CSS
- For each @import(url [mediaQuery])
- Get a list of argument AST nodes using `postcss-value-parser`
- Extract the URL at the start, stringifying together a bunch of non-string (unquoted) nodes if necessary -> url
- Extract the media specification after that, if one exists (resulting in an empty string if not) -> media
- Return {url, media}
- Filter down the [{url, media}] list to unique entries only
- For each {url, media}
- Get importItemCode for {url, media}
- Store `import` entry: { import: importItemCode }
url-parser
TODO
Custom css-loader utilities
getImportItemCode(item)
NOTE: exports.push / exports.i / etc. refer to the "runtime API" in src/runtime/api.js, which gets injected(?) into the output
- If the item is a URL (ie. cannot be bundled)
- Return `exports.push([ module.id, "@import url($url);", "$media" ]);`
- Else
- Return `exports.i(require("${toBundlerRequest(url)}"), "$media");`
getExportItemCode(item)
- Return `module.exports = { ... object of mappings ... }`
Runtime API
- exports.push(entry): Add single [moduleId, content?, mediaQuery] entry
- exports.i(entries, mediaQuery): Batch-add multiple [moduleId, content?, mediaQuery] entries, except for those whose moduleId has already been added, and adding the `mediaQuery` constraint (if any) to each item
====================
css-loader plugin pipeline:
- postcss-modules-values
@value statements -> :export statements for those values + values directly inlined where variable names referenced
- postcss-modules-local-by-default
(changes assumed untagged default from global to local)
untagged class names -> :local class names
:global class names -> untagged class names
- postcss-modules-extract-imports
external composes -> :import statement + local composes (referencing the temp-generated localKey)
- postcss-modules-scope
:local class names -> mangled class names + :export statements for the mapping
:global class names -> untagged class names
untagged class names -> untagged class names
By this point, CSS contains:
1) :import statements from the original input
2) :import statements generated from the external composes (aliased to auto-generated untagged class names)
3) :export statements from the original input
4) :export statements generated from the @value statements
5) :export statements generated from the mangled :local/untagged class names
6) untagged class names, generated from the :global class names
7) mangled class names, generated from the :local/untagged class names
8) local composes from the original input
8) local composes, referencing auto-generated local aliases, generated from the external composes
then, the custom plugin pipeline:
- icss-parser
:import statements -> `icss-import` messages { url, index, export: localKey } + unique-filtered `import` messages { import, item: { url, media } }
:import'ed identifiers in regular CSS -> ___CSS_LOADER_IMPORT___${index}___ placeholders
re-:export'ed :import'ed identifiers -> ___CSS_LOADER_IMPORT___${index}___ placeholders
:export statements -> `export` messages { export, item: { name, value } }
- import-parser
- url-parser
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment