Skip to content

Commit 43941b2

Browse files
bendytreesontek
authored andcommitted
added option to fix relative urls
1 parent 2129a2a commit 43941b2

File tree

6 files changed

+270
-10
lines changed

6 files changed

+270
-10
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
/node_modules
1+
/node_modules
2+
.idea/

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ By default, the style-loader appends `<style>` elements to the end of the `<head
7272

7373
If defined, the style-loader will re-use a single `<style>` element, instead of adding/removing individual elements for each required module. **Note:** this option is on by default in IE9, which has strict limitations on the number of style tags allowed on a page. You can enable or disable it with the singleton query parameter (`?singleton` or `?-singleton`).
7474

75+
#### `fixUrls`
76+
77+
If fixUrls and sourceMaps are both enabled, relative urls will be converted to absolute urls right before the css is injected into the page. This resolves [an issue](https://github.com/webpack/style-loader/pull/96) where relative resources fail to load when source maps are enabled. You can enable it with the fixUrls query parameter (`?fixUrls`).
78+
7579
#### `attrs`
7680

7781
If defined, style-loader will attach given attributes with their values on `<style>` / `<link>` element.
@@ -113,7 +117,7 @@ So the recommended configuration for webpack is:
113117
}
114118
```
115119

116-
**Note** about source maps support and assets referenced with `url`: when style loader is used with ?sourceMap option, the CSS modules will be generated as `Blob`s, so relative paths don't work (they would be relative to `chrome:blob` or `chrome:devtools`). In order for assets to maintain correct paths setting `output.publicPath` property of webpack configuration must be set, so that absolute paths are generated.
120+
**Note** about source maps support and assets referenced with `url`: when style loader is used with ?sourceMap option, the CSS modules will be generated as `Blob`s, so relative paths don't work (they would be relative to `chrome:blob` or `chrome:devtools`). In order for assets to maintain correct paths setting `output.publicPath` property of webpack configuration must be set, so that absolute paths are generated. Alternatively you can enable the `fixUrls` option mentioned above.
117121

118122
<h2 align="center">Contributing</h2>
119123

addStyles.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ var stylesInDom = {},
1818
}),
1919
singletonElement = null,
2020
singletonCounter = 0,
21-
styleElementsInsertedAtTop = [];
21+
styleElementsInsertedAtTop = [],
22+
fixUrls = require("./fixUrls");
2223

2324
module.exports = function(list, options) {
2425
if(typeof DEBUG !== "undefined" && DEBUG) {
@@ -59,7 +60,7 @@ module.exports = function(list, options) {
5960
}
6061
}
6162
};
62-
}
63+
};
6364

6465
function addStylesToDom(styles, options) {
6566
for(var i = 0; i < styles.length; i++) {
@@ -168,7 +169,7 @@ function addStyle(obj, options) {
168169
typeof Blob === "function" &&
169170
typeof btoa === "function") {
170171
styleElement = createLinkElement(options);
171-
update = updateLink.bind(null, styleElement);
172+
update = updateLink.bind(null, styleElement, options);
172173
remove = function() {
173174
removeStyleElement(styleElement);
174175
if(styleElement.href)
@@ -239,10 +240,19 @@ function applyToTag(styleElement, obj) {
239240
}
240241
}
241242

242-
function updateLink(linkElement, obj) {
243+
function updateLink(linkElement, options, obj) {
243244
var css = obj.css;
244245
var sourceMap = obj.sourceMap;
245246

247+
/* If fixUrls isn't defined, but sourcemaps are enabled and there is no publicPath defined
248+
then lets turn fixUrls on by default. Otherwise default to the fixUrls option directly
249+
*/
250+
const autoFixUrls = options.fixUrls === undefined && sourceMap;
251+
252+
if (options.fixUrls || autoFixUrls){
253+
css = fixUrls(css);
254+
}
255+
246256
if(sourceMap) {
247257
// http://stackoverflow.com/a/26603875
248258
css += "\n/*# sourceMappingURL=data:application/json;base64," + btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))) + " */";

fixUrls.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
2+
/**
3+
* When source maps are enabled, `style-loader` uses a link element with a data-uri to
4+
* embed the css on the page. This breaks all relative urls because now they are relative to a
5+
* bundle instead of the current page.
6+
*
7+
* One solution is to only use full urls, but that may be impossible.
8+
*
9+
* Instead, this function "fixes" the relative urls to be absolute according to the current page location.
10+
*
11+
* A rudimentary test suite is located at `test/fixUrls.js` and can be run via the `npm test` command.
12+
*
13+
*/
14+
module.exports = function (css) {
15+
// get current location
16+
var location = typeof window !== "undefined" && window.location;
17+
18+
if (!location) {
19+
throw new Error("fixUrls requires window.location");
20+
}
21+
22+
// blank or null?
23+
if (!css || typeof css !== "string") {
24+
return css;
25+
}
26+
27+
var baseUrl = location.protocol + "//" + location.host;
28+
var currentDir = baseUrl + location.pathname.replace(/\/[^\/]*$/, "/");
29+
30+
// convert each url(...)
31+
var fixedCss = css.replace(/url *\( *(.+?) *\)/g, function(fullMatch, origUrl) {
32+
// strip quotes (if they exist)
33+
var unquotedOrigUrl = origUrl
34+
.replace(/^"(.*)"$/, function(o, $1){ return $1; })
35+
.replace(/^'(.*)'$/, function(o, $1){ return $1; });
36+
37+
// already a full url? no change
38+
if (/^(#|data:|http:\/\/|https:\/\/|file:\/\/\/)/i.test(unquotedOrigUrl)) {
39+
return fullMatch;
40+
}
41+
42+
// convert the url to a full url
43+
var newUrl;
44+
45+
if (unquotedOrigUrl.indexOf("//") === 0) {
46+
//TODO: should we add protocol?
47+
newUrl = unquotedOrigUrl;
48+
} else if (unquotedOrigUrl.indexOf("/") === 0) {
49+
// path should be relative to the base url
50+
newUrl = baseUrl + unquotedOrigUrl; // already starts with '/'
51+
} else {
52+
// path should be relative to current directory
53+
newUrl = currentDir + unquotedOrigUrl.replace(/^\.\//, ""); // Strip leading './'
54+
}
55+
56+
// send back the fixed url(...)
57+
return "url(" + JSON.stringify(newUrl) + ")";
58+
});
59+
60+
// send back the fixed css
61+
return fixedCss;
62+
};

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
"version": "0.13.2",
44
"author": "Tobias Koppers @sokra",
55
"description": "style loader module for webpack",
6+
"scripts": {
7+
"test": "mocha",
8+
"travis:test": "yarn run test"
9+
},
610
"devDependencies": {
711
"css-loader": "~0.8.0",
812
"file-loader": "^0.10.1",
@@ -18,9 +22,5 @@
1822
"license": "MIT",
1923
"dependencies": {
2024
"loader-utils": "^1.0.2"
21-
},
22-
"scripts": {
23-
"test": "mocha",
24-
"travis:test": "yarn run test"
2525
}
2626
}

test/fixUrlsTest.js

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
// Node v4 requires "use strict" to allow block scoped let & const
2+
"use strict";
3+
var assert = require("assert");
4+
var url = require('url');
5+
6+
describe("fix urls tests", function() {
7+
var fixUrls = require("../fixUrls");
8+
var defaultUrl = "https://x.y.z/a/b.html";
9+
10+
beforeEach(function() {
11+
global.window = {
12+
location: url.parse(defaultUrl)
13+
};
14+
});
15+
16+
var assertUrl = function (origCss, expectedCss, specialUrl) {
17+
if (specialUrl) {
18+
global.window = {
19+
location: url.parse(specialUrl)
20+
};
21+
}
22+
var resultCss = fixUrls(origCss, specialUrl || defaultUrl);
23+
expectedCss = expectedCss || origCss;
24+
25+
assert.equal(resultCss, expectedCss);
26+
};
27+
28+
// no change
29+
it("Null css is not modified", function() {
30+
assertUrl(null)
31+
});
32+
33+
it("Blank css is not modified", function() { assertUrl("") });
34+
35+
it("No url is not modified", function () { assertUrl("body { }") });
36+
37+
it("Full url isn't changed (no quotes)", function() {
38+
assertUrl("body { background-image:url(http://example.com/bg.jpg); }")
39+
});
40+
41+
it("Full url isn't changed (no quotes, spaces)", function() {
42+
assertUrl("body { background-image:url ( http://example.com/bg.jpg ); }");
43+
});
44+
45+
it("Full url isn't changed (double quotes)", function() {
46+
assertUrl("body { background-image:url(\"http://example.com/bg.jpg\"); }")
47+
});
48+
49+
it("Full url isn't changed (double quotes, spaces)", function() {
50+
assertUrl("body { background-image:url ( \"http://example.com/bg.jpg\" ); }")
51+
});
52+
53+
it("Full url isn't changed (single quotes)", function() {
54+
assertUrl("body { background-image:url('http://example.com/bg.jpg'); }")
55+
});
56+
57+
it("Full url isn't changed (single quotes, spaces)", function() {
58+
assertUrl("body { background-image:url ( 'http://example.com/bg.jpg' ); }")
59+
});
60+
61+
it("Multiple full urls are not changed", function() {
62+
assertUrl(
63+
"body { background-image:url(http://example.com/bg.jpg); }\ndiv.main { background-image:url ( 'https://www.anothersite.com/another.png' ); }"
64+
);
65+
});
66+
67+
it("Http url isn't changed", function() {
68+
assertUrl("body { background-image:url(http://example.com/bg.jpg); }");
69+
});
70+
71+
it("Https url isn't changed", function() {
72+
assertUrl("body { background-image:url(https://example.com/bg.jpg); }");
73+
});
74+
75+
it("HTTPS url isn't changed", function() {
76+
assertUrl("body { background-image:url(HTTPS://example.com/bg.jpg); }")
77+
});
78+
79+
it("File url isn't changed", function() {
80+
assertUrl("body { background-image:url(file:///example.com/bg.jpg); }")
81+
});
82+
83+
it("Double slash url isn't changed", function() {
84+
assertUrl(
85+
"body { background-image:url(//example.com/bg.jpg); }",
86+
"body { background-image:url(\"//example.com/bg.jpg\"); }"
87+
)
88+
});
89+
90+
it("Image data uri url isn't changed", function() {
91+
assertUrl("body { background-image:url(data:image/png;base64,qsrwABYuwNkimqm3gAAAABJRU5ErkJggg==); }")
92+
});
93+
94+
it("Font data uri url isn't changed", function() {
95+
assertUrl(
96+
"body { background-image:url(data:application/x-font-woff;charset=utf-8;base64,qsrwABYuwNkimqm3gAAAABJRU5ErkJggg); }"
97+
);
98+
});
99+
100+
// relative urls
101+
it("Relative url", function() {
102+
assertUrl(
103+
"body { background-image:url(bg.jpg); }",
104+
"body { background-image:url(\"https://x.y.z/a/bg.jpg\"); }"
105+
);
106+
});
107+
108+
it("Relative url with path", function() {
109+
assertUrl(
110+
"body { background-image:url(c/d/bg.jpg); }",
111+
"body { background-image:url(\"https://x.y.z/a/c/d/bg.jpg\"); }"
112+
);
113+
});
114+
it("Relative url with dot slash", function() {
115+
assertUrl(
116+
"body { background-image:url(./c/d/bg.jpg); }",
117+
"body { background-image:url(\"https://x.y.z/a/c/d/bg.jpg\"); }"
118+
);
119+
});
120+
121+
it("Multiple relative urls", function() {
122+
assertUrl(
123+
"body { background-image:url(bg.jpg); }\ndiv.main { background-image:url(./c/d/bg.jpg); }",
124+
"body { background-image:url(\"https://x.y.z/a/bg.jpg\"); }\ndiv.main { background-image:url(\"https://x.y.z/a/c/d/bg.jpg\"); }"
125+
);
126+
});
127+
it("Relative url that looks like data-uri", function() {
128+
assertUrl(
129+
"body { background-image:url(data/image/png.base64); }",
130+
"body { background-image:url(\"https://x.y.z/a/data/image/png.base64\"); }"
131+
);
132+
});
133+
134+
// urls with hashes
135+
it("Relative url with hash are not changed", function() {
136+
assertUrl("body { background-image:url(#bg.jpg); }");
137+
});
138+
139+
// rooted urls
140+
it("Rooted url", function() {
141+
assertUrl(
142+
"body { background-image:url(/bg.jpg); }",
143+
"body { background-image:url(\"https://x.y.z/bg.jpg\"); }"
144+
);
145+
});
146+
it("Rooted url with path", function() {
147+
assertUrl(
148+
"body { background-image:url(/a/b/bg.jpg); }",
149+
"body { background-image:url(\"https://x.y.z/a/b/bg.jpg\"); }"
150+
);
151+
});
152+
153+
//special locations
154+
it("Location with no path, filename only", function() {
155+
assertUrl(
156+
"body { background-image:url(bg.jpg); }",
157+
"body { background-image:url(\"http://x.y.z/bg.jpg\"); }",
158+
"http://x.y.z"
159+
);
160+
});
161+
162+
it("Location with no path, path with filename", function() {
163+
assertUrl(
164+
"body { background-image:url(a/bg.jpg); }",
165+
"body { background-image:url(\"http://x.y.z/a/bg.jpg\"); }",
166+
"http://x.y.z"
167+
);
168+
});
169+
it("Location with no path, rel path with filename", function() {
170+
assertUrl(
171+
"body { background-image:url(./a/bg.jpg); }",
172+
"body { background-image:url(\"http://x.y.z/a/bg.jpg\"); }",
173+
"http://x.y.z"
174+
);
175+
});
176+
it("Location with no path, root filename", function() {
177+
assertUrl(
178+
"body { background-image:url(/a/bg.jpg); }",
179+
"body { background-image:url(\"http://x.y.z/a/bg.jpg\"); }",
180+
"http://x.y.z"
181+
);
182+
});
183+
});

0 commit comments

Comments
 (0)