Skip to content

Commit 34e9b21

Browse files
authored
Merge pull request #117 from bholloway/remove-orphan-CR-character
Remove orphan CR characters
2 parents ec651ca + 85e6d8a commit 34e9b21

File tree

6 files changed

+175
-16
lines changed

6 files changed

+175
-16
lines changed

packages/resolve-url-loader/README.md

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -110,16 +110,17 @@ Refer to `test` directory for full webpack configurations (as used in automated
110110

111111
## Options
112112

113-
| option | type | default | | description |
114-
|------------|----------------------------|-------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
115-
|`engine` | `'rework'`<br/>`'postcss'` | `'postcss'` | | The css parser engine. |
116-
|`sourceMap` | boolean | `false` | | Generate a source-map. |
117-
|`keepQuery` | boolean | `false` | | Keep query-string and/or hash suffixes.<br/>e.g. `url('./MyFont.eot?#iefix')`<br/>Be aware downstream loaders may remove query-string or hash. |
118-
|`debug` | boolean | `false` | | Display debug information. |
119-
|`silent` | boolean | `false` | | Do **not** display warnings. |
120-
|`root` | string | _unset_ | | Similar to the (now defunct) option in `css-loader`.<br/>This string, possibly empty, is prepended to absolute URIs.<br/>Absolute URIs are only processed if this option is set. |
121-
|`join` | function | _inbuilt_ | advanced | Custom join function.<br/>Use custom javascript to fix asset paths on a per-case basis.<br/>Refer to the default implementation for more information. |
122-
|`absolute` | boolean | `false` | useless | Forces URIs to be output as absolute file paths.<br/>This is retained for historical compatibility but is likely to be removed in the future, so let me know if you use it. |
113+
| option | type | default | | description |
114+
|-------------|----------------------------|-------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
115+
| `engine` | `'rework'`<br/>`'postcss'` | `'postcss'` | | The css parser engine. |
116+
| `sourceMap` | boolean | `false` | | Generate a source-map. |
117+
| `keepQuery` | boolean | `false` | | Keep query-string and/or hash suffixes.<br/>e.g. `url('./MyFont.eot?#iefix')`<br/>Be aware downstream loaders may remove query-string or hash. |
118+
| `removeCR` | boolean | `false` | | Convert orphan CR to whitespace (postcss only).<br/>See known issues below. |
119+
| `debug` | boolean | `false` | | Display debug information. |
120+
| `silent` | boolean | `false` | | Do **not** display warnings. |
121+
| `root` | string | _unset_ | | Similar to the (now defunct) option in `css-loader`.<br/>This string, possibly empty, is prepended to absolute URIs.<br/>Absolute URIs are only processed if this option is set. |
122+
| `join` | function | _inbuilt_ | advanced | Custom join function.<br/>Use custom javascript to fix asset paths on a per-case basis.<br/>Refer to the default implementation for more information. |
123+
| `absolute` | boolean | `false` | useless | Forces URIs to be output as absolute file paths.<br/>This is retained for historical compatibility but is likely to be removed in the future, so let me know if you use it. |
123124

124125
## How it works
125126

@@ -160,6 +161,8 @@ All `webpack1`-`webpack4` with contemporaneous loaders/plugins.
160161
161162
Refer to `test` directory for full webpack configurations (as used in automated tests).
162163
164+
Some edge cases with `libsass` on `Windows` (see below).
165+
163166
### Engines
164167
165168
The `engine:postcss` is by far the more reliable option.
@@ -180,6 +183,35 @@ However recall that any paths that _are_ processed will have windows back-slash
180183
181184
It can also be useful to process absolute URIs if you have a custom `join` function and want to process all paths. However this is perhaps better done with some separate `postcss` plugin.
182185
186+
### Windows line breaks
187+
188+
Normal windows linebreaks are `CRLF`. But sometimes libsass will output single `CR` characters.
189+
190+
This problem is specific to multiline declarations. Refer to the [libsass bug #2693](https://github.com/sass/libsass/issues/2693).
191+
192+
If you have _any_ such multiline declarations preceding `url()` statements it will fail your build.
193+
194+
Libsass doesn't consider these orphan `CR` to be newlines but `postcss` engine does. The result being an offset in source-map line-numbers which crashes `resolve-url-loader`.
195+
196+
```
197+
Module build failed: Error: resolve-url-loader: CSS error
198+
source-map information is not available at url() declaration
199+
```
200+
201+
Some users find the node-sass `linefeed` option solves the problem.
202+
203+
**Solutions**
204+
* Try the node-sass [linefeed](https://github.com/sass/node-sass#linefeed--v300) option by way of `sass-loader`.
205+
206+
**Work arounds**
207+
* Enable `removeCR` option [here](#option).
208+
* Remove linebreaks in declarations.
209+
210+
**Diagnosis**
211+
1. Run a stand-alone sass build `npx node-sass index.scss output.css`
212+
2. Use a hex editor to check line endings `Format-Hex output.css`
213+
3. Expect `0DOA` (or desired) line endings. Single `0D` confirms this problem.
214+
183215
## Getting help
184216
185217
Webpack is difficult to configure but extremely rewarding.

packages/resolve-url-loader/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ function resolveUrlLoader(content, sourceMap) {
5050
silent : false,
5151
absolute : false,
5252
keepQuery: false,
53+
removeCR : false,
5354
root : false,
5455
debug : false,
5556
join : joinFn.defaultJoin
@@ -165,7 +166,8 @@ function resolveUrlLoader(content, sourceMap) {
165166
outputSourceMap : !!options.sourceMap,
166167
transformDeclaration: valueProcessor(loader.resourcePath, options),
167168
absSourceMap : absSourceMap,
168-
sourceMapConsumer : sourceMapConsumer
169+
sourceMapConsumer : sourceMapConsumer,
170+
removeCR : options.removeCR
169171
}))
170172
.catch(onFailure)
171173
.then(onSuccess);

packages/resolve-url-loader/lib/engine/postcss.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,34 @@
44
*/
55
'use strict';
66

7-
var path = require('path'),
7+
var os = require('os'),
8+
path = require('path'),
89
postcss = require('postcss');
910

1011
var fileProtocol = require('../file-protocol');
1112

13+
var ORPHAN_CR_REGEX = /\r(?!\n)(.|\n)?/g;
14+
1215
/**
1316
* Process the given CSS content into reworked CSS content.
1417
*
1518
* @param {string} sourceFile The absolute path of the file being processed
1619
* @param {string} sourceContent CSS content without source-map
1720
* @param {{outputSourceMap: boolean, transformDeclaration:function, absSourceMap:object,
18-
* sourceMapConsumer:object}} params Named parameters
21+
* sourceMapConsumer:object, removeCR:boolean}} params Named parameters
1922
* @return {{content: string, map: object}} Reworked CSS and optional source-map
2023
*/
2124
function process(sourceFile, sourceContent, params) {
25+
// #107 libsass emits orphan CR not considered newline, postcss does consider newline (content vs source-map mismatch)
26+
var correctedContent = params.removeCR && (os.EOL !== '\r') ?
27+
sourceContent.replace(ORPHAN_CR_REGEX, ' $1') :
28+
sourceContent;
2229

2330
// prepend file protocol to all sources to avoid problems with source map
2431
return postcss([
2532
postcss.plugin('postcss-resolve-url', postcssPlugin)
2633
])
27-
.process(sourceContent, {
34+
.process(correctedContent, {
2835
from: fileProtocol.prepend(sourceFile),
2936
map : params.outputSourceMap && {
3037
prev : !!params.absSourceMap && fileProtocol.prepend(params.absSourceMap),
@@ -69,7 +76,10 @@ function process(sourceFile, sourceContent, params) {
6976
}
7077
// source-map present but invalid entry
7178
else if (params.sourceMapConsumer) {
72-
throw new Error('source-map information is not available at url() declaration');
79+
throw new Error(
80+
'source-map information is not available at url() declaration ' +
81+
(ORPHAN_CR_REGEX.test(sourceContent) ? '(found orphan CR, try removeCR option)' : '(no orphan CR found)')
82+
);
7383
}
7484
}
7585
}

packages/resolve-url-loader/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "resolve-url-loader",
3-
"version": "3.0.1",
3+
"version": "3.1.0-beta.2",
44
"description": "Webpack loader that resolves relative paths in url() statements based on the original source file",
55
"main": "index.js",
66
"repository": {

test/cases/common/tests.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,20 @@ exports.testKeepQuery = (...rest) =>
101101
)
102102
);
103103

104+
exports.testRemoveCR = (...rest) =>
105+
test(
106+
'removeCR=true',
107+
layer()(
108+
env({
109+
LOADER_QUERY: 'removeCR',
110+
LOADER_OPTIONS: {removeCR: true},
111+
OUTPUT: 'remove-CR'
112+
}),
113+
...rest,
114+
test('validate', assertStderr('options.removeCR')(1)`removeCR: true`)
115+
)
116+
);
117+
104118
exports.testRoot = (...rest) =>
105119
test(
106120
'root=empty',
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
'use strict';
2+
3+
const {join} = require('path');
4+
const outdent = require('outdent');
5+
const {test, layer, fs, env, cwd} = require('test-my-cli');
6+
7+
const {withCacheBase} = require('../lib/higher-order');
8+
const {testDefault, testRemoveCR} = require('./common/tests');
9+
const {
10+
buildDevNormal, buildDevBail, buildDevNoUrl, buildProdNormal, buildProdBail, buildProdNoUrl, buildProdNoDevtool
11+
} = require('./common/builds');
12+
const {
13+
onlyMeta, assertWebpackOk, assertWebpackNotOk, assertStdout, assertNoErrors, assertNoMessages
14+
} = require('../lib/assert');
15+
16+
// Allow 1-4 errors
17+
// - known-issue in extract-text-plugin, failed loaders will rerun webpack>=2
18+
// - webpack may repeat errors with a header line taken from the parent loader
19+
const assertCssError = assertStdout('error')([1, 4])`
20+
^[ ]*ERROR[^\n]*
21+
([^\n]+\n){0,2}[^\n]*resolve-url-loader:[ ]*CSS error
22+
[ ]+source-map information is not available at url\(\) declaration \(found orphan CR, try removeCR option\)
23+
`;
24+
25+
module.exports = test(
26+
'orphan-carriage-return',
27+
layer('orphan-carriage-return')(
28+
cwd('.'),
29+
fs({
30+
'package.json': withCacheBase('package.json'),
31+
'webpack.config.js': withCacheBase('webpack.config.js'),
32+
'node_modules': withCacheBase('node_modules'),
33+
'src/index.scss': outdent`
34+
.some-class-name {
35+
font-size: calc(${'\r'}
36+
(1px)${'\r'}
37+
);
38+
background-image: url(data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==);
39+
}
40+
`
41+
}),
42+
env({
43+
ENTRY: join('src', 'index.scss')
44+
}),
45+
testDefault(
46+
onlyMeta('meta.version.webpack == 1')(
47+
buildDevBail(
48+
assertWebpackNotOk
49+
),
50+
buildDevNormal(
51+
assertWebpackOk,
52+
assertCssError
53+
),
54+
buildProdBail(
55+
assertWebpackNotOk
56+
),
57+
buildProdNormal(
58+
assertWebpackOk,
59+
assertCssError
60+
)
61+
),
62+
onlyMeta('meta.version.webpack > 1')(
63+
buildDevNormal(
64+
assertWebpackNotOk,
65+
assertCssError
66+
),
67+
buildProdNormal(
68+
assertWebpackNotOk,
69+
assertCssError
70+
)
71+
)
72+
),
73+
testRemoveCR(
74+
buildDevNormal(
75+
assertWebpackOk,
76+
assertNoErrors,
77+
assertNoMessages
78+
),
79+
buildDevNoUrl(
80+
assertWebpackOk,
81+
assertNoErrors,
82+
assertNoMessages
83+
),
84+
buildProdNormal(
85+
assertWebpackOk,
86+
assertNoErrors,
87+
assertNoMessages
88+
),
89+
buildProdNoUrl(
90+
assertWebpackOk,
91+
assertNoErrors,
92+
assertNoMessages
93+
),
94+
buildProdNoDevtool(
95+
assertWebpackOk,
96+
assertNoErrors,
97+
assertNoMessages
98+
)
99+
)
100+
)
101+
);

0 commit comments

Comments
 (0)