source: trip-planner-front/node_modules/resolve-url-loader/docs/how-it-works.md@ ceaed42

Last change on this file since ceaed42 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 10.7 KB
LineΒ 
1# How it works
2
3## The problem
4
5The `resolve-url-loader` is typically used where SASS source files are transpiled to CSS. CSS being a format that webpack can readily ingest. So let's look at a basic example where the structure is basically CSS but is composed using SASS features.
6
7Working backwards, this is the final CSS we are after. Just a single rule with a single declaration.
8
9```css
10.cool {
11 background-image: url(cool.png);
12}
13```
14
15When using SASS it's common for rules to come from different [partials](https://sass-lang.com/documentation/at-rules/import#partials), and for declarations to be composed using mixins and functions. Consider this more complicated project with imported files.
16
17<img src="detailed-problem.svg" alt="the detailed problem" width="363px" height="651px">
18
19All the subdirectories here contributed something to the rule, so we could reasonably place the asset in any of them. And any of these locations might be the "correct" to our way of thinking.
20
21There could actually be a separate `cool.png` in each of the subdirectories! 🀯 In that case, which one gets used?
22
23The answer: none. 😞 Webpack expects asset paths to be relative to the root SASS file `src/styles.scss`. So for the CSS `url(cool.png)` it will look for `src/cool.png` which is not present. πŸ’₯
24
25All our assets are in subdirecties `src/foo/cool.png` or `src/foo/bar/cool.png` or `src/foo/bar/baz/cool.png`. We need to re-write the `url()` to point to the one we intend. But right now that's pretty ambiguous.
26
27Worse still, Webpack doesn't know any of these nested SCSS files were part of the SASS composition. Meaing it doesn't know there _are_ nested directories in the first place. How do we rewite to something we don't know about?
28
29**The problem:** How to identify contributing directectories and look for the asset in those directories in some well-defined priority order?
30
31**The crux:** How to identify what contributed to the SASS compilation, internally and post factum, but from within Webpack? 😫
32
33## The solution
34
35Sourcemaps! πŸ˜ƒ
36
37Wait, don't run away! Sourcemaps might sound scary, but they solve our problem reasonably well. πŸ‘
38
39The SASS compiler source-map can tell us which original SCSS file contributed each character in the resulting CSS.
40
41The SASS source-map is also something we can access from within Webpack.
42
43### concept
44
45Continuing with the example let's compile SASS on the command line. You can do this several different ways but I prefer [npx](https://blog.npmjs.org/post/162869356040/introducing-npx-an-npm-package-runner).
46
47```sh
48> npx node-sass src/styles.scss --output . --output-style expanded --source-map true
49```
50
51Using the experimental `sourcemap-to-string` package (also in this repository) we can visualise the SASS source on the left vs the output CSS on the right.
52
53```
54src/styles.scss
55-------------------------------------------------------------------------------
56
57src/foo/_partial.scss
58-------------------------------------------------------------------------------
593:01 .coolβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ 1:01 .coolβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘
603:06 β–‘β–‘β–‘β–‘β–‘ β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ 1:06 β–‘β–‘β–‘β–‘β–‘ β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘
613:07 β–‘β–‘β–‘β–‘β–‘β–‘{⏎ 1:07 β–‘β–‘β–‘β–‘β–‘β–‘{⏎
62 @include cool-background-image;⏎ β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘
63 }β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘
64-:-- β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ 3:02 β–‘βŽ
65 β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ ⏎
66 β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ /*# sourceMappingURL=styles.css.ma
67 β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ p */β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘
68
69src/foo/bar/_mixins.scss
70-------------------------------------------------------------------------------
714:03 β–‘β–‘background-imageβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ 2:03 β–‘β–‘background-imageβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘
724:19 β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘: get-url("cool" 2:19 β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘: β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘
73 );⏎ β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘
74 }⏎ β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘
75
76src/foo/bar/baz/_functions.scss
77-------------------------------------------------------------------------------
782:11 β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘url(#β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ 2:21 β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘url(cool.png)β–‘
792:16 β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘{$temp}.png);⏎ 2:34 β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘;
80 }β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ ⏎
81 β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ }β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘
82```
83
84As expected, the pure CSS portions are essentially the same in the source and the output.
85
86Meanwhile the indirect `@mixin` and `funtion` substitutes values into the output. But we can still clearly see where in the source that final value originated from.
87
88### algorithm
89
90Now we know the original SCSS sources we can use a CSS parser such as `postcss` to process all the declaration values that contain `url()` and rewrite any file paths we find there.
91
921. Enumerate all declaration values
932. Split the value into path substrings
943. Evaluate the source-map at that location, find the original source file
954. Rebase the path to that original source file.
96
97For our example, this algorithm will always give us the asset located in the `baz` subdirectory. Clearly evaluating the source-map at just one location is not enough. Any of the directories that contributed source files to the rule-set might be considered the "correct" place to store the asset and all these files contributed different parts of the rule-set, not just the declaration value.
98
99We stop short of evaluating the source-map for _every characer_ in the rule-set and instead we chose a small number of meaningful points.
100
101| | label | sampling location | in the example | implies asset |
102|---|-----------|------------------------------------------|---------------------------|--------------------------------------------------|
103| 1 | subString | start of **argument** to the `url()` | `c` in `cool.png` | `src/foo/bar/baz/cool.png` |
104| 2 | value | start of **value** in the declaration | `u` in `url(...)` | `src/foo/bar/baz/cool.png` |
105| 3 | property | start of **property** in the declaration | `b` in `background-image` | `src/foo/bar/cool.png` |
106| 4 | selector | start of **selector** in the rule-set | `.` in `.selector` | `src/foo/cool.png` |
107
108These locations are tested in order. If an asset of the correct filename is found then we break and use that result.
109
110Note it is a quirk of the example that the `value` and `subString` locations imply the same file. In a more complex example this may not be true.
111
112If necessary the order can be customised or a custom file search (starting at each location) be implemented. Refer to the [advanced features](advanced-features.md).
113
114
115### webpack
116
117To operate on the `sass-loader` output, both **CSS** and **source-map**, we introduce `resolve-url-loader` containing the algorithm above.
118
119The `resolve-url-loader` rewrites asset paths found in `url()` notation using the `postcss` parser.
120
121This webpack configuration outlines some important points.
122
123```javascript
124rules: [
125 {
126 test: /\.scss$/,
127 use: [
128 {
129 loader: 'css-loader' // <-- assets are identified here
130 }, {
131 loader: 'resolve-url-loader' // <-- receives CSS and source-map from SASS compile
132 }, {
133 loader: 'sass-loader',
134 options: {
135 sourceMap: true, // <-- IMPORTANT!
136 sourceMapContents: false
137 }
138 }
139 ],
140 },
141 ...
142 {
143 test: /\.png$/, // <-- assets needs their own loader configuration
144 use: [ ... ]
145 }
146]
147```
148
149Its essential to explicitly configure the `sass-loader` for `sourceMap: true`. That way we definitely get a sourcemap from upstream SASS loader all the time, not just in developement mode or where `devtool` is used.
150
151Once the CSS reaches the `css-loader` webpack becomes aware of each of the asset files and will try to separately load and process them. You will need more Webpack configuration to make that work. Refer to the [troubleshooting docs](troubleshooting.md) before raising an issue.
152
153### beyond...?
154
155The implementation here is limited to the webpack loader but it's plausible the algorithm could be realised as a `postcss` plugin in isolation using the [root.input.map](https://postcss.org/api/#postcss-input) property to access the incomming source-map.
156
157As a separate plugin it could be combined with other plugins in a single `postcss-loader` step. Processing multiple plugins together in this way without reparsing would arguably be more efficient.
158
159However as a Webpack loader we have full access to the loader API and the virtual file-system. This means maximum compatibility with `webpack-dev-server` and the rest of the Webpack ecosystem.
Note: See TracBrowser for help on using the repository browser.