merge 0.16.1 style loader into simple-universal-style-loader

master
terence chow 2017-03-29 22:08:42 +01:00
commit 696bcbe133
19 changed files with 4144 additions and 45 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Treats the lock file as binary & prevents conflict hell
yarn.lock -diff

15
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,15 @@
<!-- Before creating an issue please make sure you are using the latest version of style-loader. -->
**Do you want to request a *feature* or report a *bug*?**
<!-- Please ask questions on StackOverflow or the webpack Gitter (https://gitter.im/webpack/webpack). Questions will be closed. -->
**What is the current behavior?**
**If the current behavior is a bug, please provide the steps to reproduce.**
<!-- A great way to do this is to provide your configuration via a GitHub gist. -->
**What is the expected behavior?**
**If this is a feature request, what is motivation or use case for changing the behavior?**
**Please mention other relevant information such as your webpack version, Node.js version and Operating System.**

18
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,18 @@
<!-- Thanks for submitting a pull request! Please provide enough information so that others can review your pull request. -->
**What kind of change does this PR introduce?**
<!-- E.g. a bugfix, feature, refactoring, build related change, etc… -->
**Did you add tests for your changes?**
**If relevant, did you update the README?**
**Summary**
<!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? -->
<!-- Try to link to an open issue for more information. -->
**Does this PR introduce a breaking change?**
<!-- If this PR introduces a breaking change, please describe the impact and a migration path for existing applications. -->
**Other information**

2
.gitignore vendored
View File

@ -1,2 +1,2 @@
/node_modules
.idea
.idea/

33
.travis.yml Normal file
View File

@ -0,0 +1,33 @@
sudo: false
language: node_js
branches:
only:
- master
matrix:
fast_finish: true
include:
# - os: linux
# node_js: '7'
# env: WEBPACK_VERSION="2.2.0" BITHOUND_CHECK=true JOB_PART=lint
- os: linux
node_js: '7'
env: WEBPACK_VERSION="2.2.0" JOB_PART=test
- os: linux
node_js: '4.3'
env: WEBPACK_VERSION="2.2.0" JOB_PART=test
- os: linux
node_js: '6'
env: WEBPACK_VERSION="2.2.0" JOB_PART=test
# - os: linux
# node_js: '7'
# env: WEBPACK_VERSION="2.2.0" JOB_PART=coverage
before_install:
- nvm --version
- node --version
before_script:
- if [ "$WEBPACK_VERSION" ]; then yarn add webpack@^$WEBPACK_VERSION; fi
# - if [ "$BITHOUND_CHECK" ]; then npm install -g bithound; bithound check git@github.com:$TRAVIS_REPO_SLUG.git; fi
script:
- yarn run travis:$JOB_PART
after_success:
- bash <(curl -s https://codecov.io/bash)

68
CHANGELOG.md Normal file
View File

@ -0,0 +1,68 @@
# Change Log
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
<a name="0.16.1"></a>
## [0.16.1](https://github.com/webpack/style-loader/compare/v0.16.0...v0.16.1) (2017-03-28)
### Bug Fixes
* negative refs ([#122](https://github.com/webpack/style-loader/issues/122)) ([f6f577a](https://github.com/webpack/style-loader/commit/f6f577a))
<a name="0.16.0"></a>
# [0.16.0](https://github.com/webpack/style-loader/compare/v0.15.0...v0.16.0) (2017-03-22)
### Bug Fixes
* **addStyles:** update for test for old IE versions ([#196](https://github.com/webpack/style-loader/issues/196)) ([1f68495](https://github.com/webpack/style-loader/commit/1f68495))
### Features
* Set custom attributes for tag in url mode ([#198](https://github.com/webpack/style-loader/issues/198)) ([2c4f427](https://github.com/webpack/style-loader/commit/2c4f427))
<a name="0.15.0"></a>
# [0.15.0](https://github.com/webpack/style-loader/compare/v0.14.1...v0.15.0) (2017-03-21)
### Bug Fixes
* match parens recursively on URLs to not fix embeded calls ([#192](https://github.com/webpack/style-loader/issues/192)) ([71e0908](https://github.com/webpack/style-loader/commit/71e0908))
### Features
* add insertInto option ([#135](https://github.com/webpack/style-loader/issues/135)) ([6636868](https://github.com/webpack/style-loader/commit/6636868))
<a name="0.14.1"></a>
## [0.14.1](https://github.com/webpack/style-loader/compare/v0.14.0...v0.14.1) (2017-03-15)
### Bug Fixes
* syntax error in IE10 and below because of `const` keyword ([#190](https://github.com/webpack/style-loader/issues/190)) ([01080cf](https://github.com/webpack/style-loader/commit/01080cf))
<a name="0.14.0"></a>
# [0.14.0](https://github.com/webpack/style-loader/compare/v0.13.1...v0.14.0) (2017-03-15)
### Bug Fixes
* Adds type attr. to the generated link element ([2a2f261](https://github.com/webpack/style-loader/commit/2a2f261))
* **fixUrls:** add param to fix relative urls ([#186](https://github.com/webpack/style-loader/issues/186)) ([19959ee](https://github.com/webpack/style-loader/commit/19959ee))
* **usable:** Export locals if available([#128](https://github.com/webpack/style-loader/issues/128)) ([e280cb6](https://github.com/webpack/style-loader/commit/e280cb6))
### Features
* **tag-attribute:** Add support for custom tag attribute ([995f3de](https://github.com/webpack/style-loader/commit/995f3de))

20
LICENSE Normal file
View File

@ -0,0 +1,20 @@
Copyright JS Foundation and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

124
README.md
View File

@ -1,24 +1,41 @@
# style loader for webpack
[![npm][npm]][npm-url]
[![node][node]][node-url]
[![deps][deps]][deps-url]
[![chat][chat]][chat-url]
<div align="center">
<a href="https://github.com/webpack/webpack">
<img width="200" height="200"
src="https://webpack.js.org/assets/icon-square-big.svg">
</a>
<h1>Style Loader</h1>
</div>
Adds CSS to the DOM by injecting a `<style>` tag
## Usage
<h2 align="center">Install</h2>
```
npm install style-loader --save-dev
```
<h2 align="center">Usage</h2>
[Documentation: Using loaders](http://webpack.github.io/docs/using-loaders.html)
### Simple API
``` javascript
require("simple-universal-style!raw!./file.css");
require("simple-universal-style-loader!raw-loader!./file.css");
// => add rules in file.css to document
```
It's recommended to combine it with the [`css-loader`](https://github.com/webpack/css-loader): `require("style!css!./file.css")`.
It's recommended to combine it with the [`css-loader`](https://github.com/webpack/css-loader): `require("simple-universal-style-loader!css-loader!./file.css")`.
It's also possible to add a URL instead of a CSS string:
``` javascript
require("simple-universal-style/url!file!./file.css");
require("simple-universal-style-loader/url!file-loader!./file.css");
// => add a <link rel="stylesheet"> to file.css to document
```
@ -26,17 +43,18 @@ require("simple-universal-style/url!file!./file.css");
(experimental)
When using [local scope CSS](https://github.com/webpack/css-loader#local-scope) the module exports the generated identifiers:
When using [local scope CSS](https://github.com/webpack/css-loader#css-scope) the module exports the generated identifiers:
``` javascript
var style = require("simple-universal-style!css!./file.css");
var style = require("simple-universal-style-loader!css-loader!./file.css");
style.placeholder1 === "z849f98ca812bc0d099a43e0f90184"
```
### Reference-counted API
``` javascript
var style = require("simple-universal-style/useable!css!./file.css");
var style = require("simple-universal-style-loader/useable!css-loader!./file.css");
style.use(); // = style.ref();
style.unuse(); // = style.unref();
```
@ -47,7 +65,7 @@ Note: Behavior is undefined when `unuse`/`unref` is called more often than `use`
### Server environment
On server side we can't load styles into the DOM but to collect them and use when assembling the layout.
On server side we can't load styles into the DOM but to collect them and use when assembling the layout.
Example with React:
@ -71,13 +89,36 @@ import { getStyles } from 'simple-universal-style-loader'
#### `insertAt`
By default, the style-loader appends `<style>` elements to the end of the `<head>` tag of the page. This will cause CSS created by the loader to take priority over CSS already present in the document head. To insert style elements at the beginning of the head, set this query parameter to 'top', e.g. `require('../style.css?insertAt=top')`.
By default, the style-loader appends `<style>` elements to the end of the style target, which is the `<head>` tag of the page unless specified by `insertInto`. This will cause CSS created by the loader to take priority over CSS already present in the target. To insert style elements at the beginning of the target, set this query parameter to 'top', e.g. `require('../style.css?insertAt=top')`.
#### `insertInto`
By default, the style-loader inserts the `<style>` elements into the `<head>` tag of the page. If you want the tags to be inserted somewhere else, e.g. into a [ShadowRoot](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot), you can specify a CSS selector for that element here, e.g. `require('../style.css?insertInto=#host::shadow>#root')`.
#### `singleton`
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`).
## Recommended configuration
#### `convertToAbsoluteUrls`
If convertToAbsoluteUrls 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 convertToAbsoluteUrls query parameter (`?convertToAbsoluteUrls`).
#### `attrs`
If defined, style-loader will attach given attributes with their values on `<style>` / `<link>` element.
Usage:
```javascript
require('style-loader?{attrs:{id: "style-tag-id"}}!style.css');
// will create style tag <style id="style-tag-id">
```
Usage in `url` mode:
```javascript
require('style-loader/url?{attrs:{prop: "value"}}!file-loader!style.css')
// will create link tag <link rel="stylesheet" type="text/css" href="[path]/style.css" prop="value">
```
### Recommended configuration
By convention the reference-counted API should be bound to `.useable.css` and the simple API to `.css` (similar to other file types, i.e. `.useable.less` and `.less`).
@ -86,22 +127,67 @@ So the recommended configuration for webpack is:
``` javascript
{
module: {
loaders: [
{ test: /\.css$/, exclude: /\.useable\.css$/, loader: "simple-universal-style!css" },
{ test: /\.useable\.css$/, loader: "simple-universal-style/useable!css" }
]
rules: [
{
test: /\.css$/,
use: [
{ loader: "simple-universal-style-loader" },
{ loader: "css-loader" },
],
},
{
test: /\.useable\.css$/,
use: [
{
loader: "simple-universal-style-loader/useable"
},
{ loader: "css-loader" },
],
},
],
}
}
```
**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.
**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 `convertToAbsoluteUrls` option mentioned above.
## Install
<h2 align="center">Contributing</h2>
```
npm install simple-universal-style-loader
```
## License
Don't hesitate to create a pull request. Every contribution is appreciated. In development you can start the tests by calling `npm test`.
MIT (http://www.opensource.org/licenses/mit-license.php)
<h2 align="center">Maintainers</h2>
<table>
<tbody>
<tr>
<td align="center">
<img width="150 height="150"
src="https://avatars.githubusercontent.com/sokra?v=3">
<br />
<a href="https://github.com/">Tobias Koppers</a>
</td>
<td align="center">
<img width="150 height="150"
src="https://avatars.githubusercontent.com/SpaceK33z?v=3">
<br />
<a href="https://github.com/">Kees Kluskens</a>
</td>
<tr>
<tbody>
</table>
<h2 align="center">LICENSE</h2>
MIT
[npm-url]: https://npmjs.com/package/simple-universal-style-loader
[node]: https://img.shields.io/node/v/style-loader.svg
[node-url]: https://nodejs.org
[deps]: https://img.shields.io/npm/v/style-loader.svg

View File

@ -2,14 +2,28 @@
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
module.exports = function addStyleUrl(cssUrl) {
function attachTagAttrs(element, attrs) {
Object.keys(attrs).forEach(function (key) {
element.setAttribute(key, attrs[key]);
});
}
module.exports = function addStyleUrl(cssUrl, options) {
if(typeof DEBUG !== "undefined" && DEBUG) {
if(typeof document !== "object") throw new Error("The style-loader cannot be used in a non-browser environment");
}
options = options || {};
options.attrs = typeof options.attrs === "object" ? options.attrs : {};
var styleElement = document.createElement("link");
styleElement.rel = "stylesheet";
styleElement.type = "text/css";
styleElement.href = cssUrl;
attachTagAttrs(styleElement, options.attrs);
var head = document.getElementsByTagName("head")[0];
head.appendChild(styleElement);
if(module.hot) {

View File

@ -11,14 +11,28 @@ var stylesInDom = {},
};
},
isOldIE = memoize(function() {
return /msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase());
// Test for IE <= 9 as proposed by Browserhacks
// @see http://browserhacks.com/#hack-e71d8692f65334173fee715c222cb805
// Tests for existence of standard globals is to allow style-loader
// to operate correctly into non-standard environments
// @see https://github.com/webpack-contrib/style-loader/issues/177
return window && document && document.all && !window.atob;
}),
getHeadElement = memoize(function () {
return document.head || document.getElementsByTagName("head")[0];
getElement = (function(fn) {
var memo = {};
return function(selector) {
if (typeof memo[selector] === "undefined") {
memo[selector] = fn.call(this, selector);
}
return memo[selector]
};
})(function (styleTarget) {
return document.querySelector(styleTarget)
}),
singletonElement = null,
singletonCounter = 0,
styleElementsInsertedAtTop = [];
styleElementsInsertedAtTop = [],
fixUrls = require("./fixUrls");
module.exports = function(list, options) {
if(typeof DEBUG !== "undefined" && DEBUG) {
@ -26,11 +40,16 @@ module.exports = function(list, options) {
}
options = options || {};
options.attrs = typeof options.attrs === "object" ? options.attrs : {};
// Force single-tag solution on IE6-9, which has a hard limit on the # of <style>
// tags it will allow on a page
if (typeof options.singleton === "undefined") options.singleton = isOldIE();
// By default, add <style> tags to the bottom of <head>.
// By default, add <style> tags to the <head> element
if (typeof options.insertInto === "undefined") options.insertInto = "head";
// By default, add <style> tags to the bottom of the target
if (typeof options.insertAt === "undefined") options.insertAt = "bottom";
var styles = listToStyles(list);
@ -57,7 +76,7 @@ module.exports = function(list, options) {
}
}
};
}
};
function addStylesToDom(styles, options) {
for(var i = 0; i < styles.length; i++) {
@ -100,19 +119,22 @@ function listToStyles(list) {
}
function insertStyleElement(options, styleElement) {
var head = getHeadElement();
var styleTarget = getElement(options.insertInto)
if (!styleTarget) {
throw new Error("Couldn't find a style target. This probably means that the value for the 'insertInto' parameter is invalid.");
}
var lastStyleElementInsertedAtTop = styleElementsInsertedAtTop[styleElementsInsertedAtTop.length - 1];
if (options.insertAt === "top") {
if(!lastStyleElementInsertedAtTop) {
head.insertBefore(styleElement, head.firstChild);
styleTarget.insertBefore(styleElement, styleTarget.firstChild);
} else if(lastStyleElementInsertedAtTop.nextSibling) {
head.insertBefore(styleElement, lastStyleElementInsertedAtTop.nextSibling);
styleTarget.insertBefore(styleElement, lastStyleElementInsertedAtTop.nextSibling);
} else {
head.appendChild(styleElement);
styleTarget.appendChild(styleElement);
}
styleElementsInsertedAtTop.push(styleElement);
} else if (options.insertAt === "bottom") {
head.appendChild(styleElement);
styleTarget.appendChild(styleElement);
} else {
throw new Error("Invalid value for parameter 'insertAt'. Must be 'top' or 'bottom'.");
}
@ -128,18 +150,29 @@ function removeStyleElement(styleElement) {
function createStyleElement(options) {
var styleElement = document.createElement("style");
styleElement.type = "text/css";
options.attrs.type = "text/css";
attachTagAttrs(styleElement, options.attrs);
insertStyleElement(options, styleElement);
return styleElement;
}
function createLinkElement(options) {
var linkElement = document.createElement("link");
linkElement.rel = "stylesheet";
options.attrs.type = "text/css";
options.attrs.rel = "stylesheet";
attachTagAttrs(linkElement, options.attrs);
insertStyleElement(options, linkElement);
return linkElement;
}
function attachTagAttrs(element, attrs) {
Object.keys(attrs).forEach(function (key) {
element.setAttribute(key, attrs[key]);
});
}
function addStyle(obj, options) {
var styleElement, update, remove;
@ -155,7 +188,7 @@ function addStyle(obj, options) {
typeof Blob === "function" &&
typeof btoa === "function") {
styleElement = createLinkElement(options);
update = updateLink.bind(null, styleElement);
update = updateLink.bind(null, styleElement, options);
remove = function() {
removeStyleElement(styleElement);
if(styleElement.href)
@ -226,10 +259,21 @@ function applyToTag(styleElement, obj) {
}
}
function updateLink(linkElement, obj) {
function updateLink(linkElement, options, obj) {
var css = obj.css;
var sourceMap = obj.sourceMap;
/* If convertToAbsoluteUrls isn't defined, but sourcemaps are enabled
and there is no publicPath defined then lets turn convertToAbsoluteUrls
on by default. Otherwise default to the convertToAbsoluteUrls option
directly
*/
var autoFixUrls = options.convertToAbsoluteUrls === undefined && sourceMap;
if (options.convertToAbsoluteUrls || autoFixUrls){
css = fixUrls(css);
}
if(sourceMap) {
// http://stackoverflow.com/a/26603875
css += "\n/*# sourceMappingURL=data:application/json;base64," + btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))) + " */";

89
fixUrls.js Normal file
View File

@ -0,0 +1,89 @@
/**
* When source maps are enabled, `style-loader` uses a link element with a data-uri to
* embed the css on the page. This breaks all relative urls because now they are relative to a
* bundle instead of the current page.
*
* One solution is to only use full urls, but that may be impossible.
*
* Instead, this function "fixes" the relative urls to be absolute according to the current page location.
*
* A rudimentary test suite is located at `test/fixUrls.js` and can be run via the `npm test` command.
*
*/
module.exports = function (css) {
// get current location
var location = typeof window !== "undefined" && window.location;
if (!location) {
throw new Error("fixUrls requires window.location");
}
// blank or null?
if (!css || typeof css !== "string") {
return css;
}
var baseUrl = location.protocol + "//" + location.host;
var currentDir = baseUrl + location.pathname.replace(/\/[^\/]*$/, "/");
// convert each url(...)
/*
This regular expression is just a way to recursively match brackets within
a string.
/url\s*\( = Match on the word "url" with any whitespace after it and then a parens
( = Start a capturing group
(?: = Start a non-capturing group
[^)(] = Match anything that isn't a parentheses
| = OR
\( = Match a start parentheses
(?: = Start another non-capturing groups
[^)(]+ = Match anything that isn't a parentheses
| = OR
\( = Match a start parentheses
[^)(]* = Match anything that isn't a parentheses
\) = Match a end parentheses
) = End Group
*\) = Match anything and then a close parens
) = Close non-capturing group
* = Match anything
) = Close capturing group
\) = Match a close parens
/gi = Get all matches, not the first. Be case insensitive.
*/
var fixedCss = css.replace(/url\s*\(((?:[^)(]|\((?:[^)(]+|\([^)(]*\))*\))*)\)/gi, function(fullMatch, origUrl) {
// strip quotes (if they exist)
var unquotedOrigUrl = origUrl
.trim()
.replace(/^"(.*)"$/, function(o, $1){ return $1; })
.replace(/^'(.*)'$/, function(o, $1){ return $1; });
// already a full url? no change
if (/^(#|data:|http:\/\/|https:\/\/|file:\/\/\/)/i.test(unquotedOrigUrl)) {
return fullMatch;
}
// convert the url to a full url
var newUrl;
if (unquotedOrigUrl.indexOf("//") === 0) {
//TODO: should we add protocol?
newUrl = unquotedOrigUrl;
} else if (unquotedOrigUrl.indexOf("/") === 0) {
// path should be relative to the base url
newUrl = baseUrl + unquotedOrigUrl; // already starts with '/'
} else {
// path should be relative to current directory
newUrl = currentDir + unquotedOrigUrl.replace(/^\.\//, ""); // Strip leading './'
}
// send back the fixed url(...)
return "url(" + JSON.stringify(newUrl) + ")";
});
// send back the fixed css
return fixedCss;
};

View File

@ -8,7 +8,7 @@ var loaderUtils = require("loader-utils"),
module.exports = function() {};
module.exports.pitch = function(remainingRequest) {
if(this.cacheable) this.cacheable();
var query = loaderUtils.parseQuery(this.query);
var query = loaderUtils.getOptions(this) || {};
return [
"// style-loader: Adds some css to the DOM by adding a <style> tag",
"",

View File

@ -1,10 +1,16 @@
{
"name": "simple-universal-style-loader",
"version": "0.14.4",
"version": "0.15.0",
"author": "Tobias Koppers @sokra & Istvan Jano @janoist1",
"description": "style loader module for webpack",
"devDependencies": {
"css-loader": "~0.8.0"
"css-loader": "^0.27.3",
"file-loader": "^0.10.1",
"jsdom": "^9.11.0",
"memory-fs": "^0.4.1",
"mocha": "^3.2.0",
"standard-version": "^4.0.0",
"webpack": "^2.2.1"
},
"repository": {
"type": "git",
@ -12,7 +18,7 @@
},
"license": "MIT",
"dependencies": {
"loader-utils": "^0.2.7"
"loader-utils": "^1.0.2"
},
"bugs": {
"url": "https://github.com/webpack/style-loader/issues"
@ -30,5 +36,10 @@
"style",
"loader",
"webpack"
]
],
"scripts": {
"release": "yarn run standard-version",
"test": "mocha",
"travis:test": "yarn run test"
}
}

283
test/basicTest.js Normal file
View File

@ -0,0 +1,283 @@
// Node v4 requires "use strict" to allow block scoped let & const
"use strict";
describe("basic tests", function() {
var path = require("path");
var utils = require("./utils"),
runCompilerTest = utils.runCompilerTest;
var fs;
var requiredCss = ".required { color: blue }",
requiredCssTwo = ".requiredTwo { color: cyan }",
localScopedCss = ":local(.className) { background: red; }",
requiredStyle = `<style type="text/css">${requiredCss}</style>`,
existingStyle = "<style>.existing { color: yellow }</style>",
checkValue = '<div class="check">check</div>',
rootDir = path.resolve(__dirname + "/../") + "/",
jsdomHtml = [
"<html>",
"<head>",
existingStyle,
"</head>",
"<body>",
"<div class='target'>",
checkValue,
"</div>",
"</body>",
"</html>"
].join("\n");
var styleLoaderOptions = {};
var cssRule = {};
var defaultCssRule = {
test: /\.css?$/,
use: [
{
loader: "style-loader",
options: styleLoaderOptions
},
"css-loader"
]
};
var webpackConfig = {
entry: "./main.js",
output: {
filename: "bundle.js"
},
module: {
rules: [cssRule]
}
};
beforeEach(function() {
// Reset all style-loader options
for (var member in styleLoaderOptions) {
delete styleLoaderOptions[member];
}
for (var member in defaultCssRule) {
cssRule[member] = defaultCssRule[member];
}
fs = utils.setup(webpackConfig, jsdomHtml);
// Create a tiny file system. rootDir is used because loaders are refering to absolute paths.
fs.mkdirpSync(rootDir);
fs.writeFileSync(rootDir + "main.js", "var css = require('./style.css');");
fs.writeFileSync(rootDir + "style.css", requiredCss);
fs.writeFileSync(rootDir + "styleTwo.css", requiredCssTwo);
fs.writeFileSync(rootDir + "localScoped.css", localScopedCss);
}); // before each
it("insert at bottom", function(done) {
let expected = [existingStyle, requiredStyle].join("\n");
runCompilerTest(expected, done);
}); // it insert at bottom
it("insert at top", function(done) {
styleLoaderOptions.insertAt = "top";
let expected = [requiredStyle, existingStyle].join("\n");
runCompilerTest(expected, done);
}); // it insert at top
it("insert into", function(done) {
let selector = "div.target";
styleLoaderOptions.insertInto = selector;
let expected = [checkValue, requiredStyle].join("\n");
runCompilerTest(expected, done, undefined, selector);
}); // it insert into
it("singleton", function(done) {
// Setup
styleLoaderOptions.singleton = true;
fs.writeFileSync(
rootDir + "main.js",
[
"var a = require('./style.css');",
"var b = require('./styleTwo.css');"
].join("\n")
);
// Run
let expected = [
existingStyle,
`<style type="text/css">${requiredCss}${requiredCssTwo}</style>`
].join("\n");
runCompilerTest(expected, done);
}); // it singleton
it("attrs", function(done) {
// Setup
styleLoaderOptions.attrs = {id: 'style-tag-id'};
fs.writeFileSync(
rootDir + "main.js",
[
"var a = require('./style.css');"
].join("\n")
);
// Run
let expected = [
existingStyle,
`<style id="${styleLoaderOptions.attrs.id}" type="text/css">${requiredCss}</style>`
].join("\n");
runCompilerTest(expected, done);
}); // it attrs
it("url", function(done) {
cssRule.use = [
{
loader: "style-loader/url",
options: {}
},
"file-loader"
];
// Run
let expected = [
existingStyle,
'<link rel="stylesheet" type="text/css" href="ec9d4f4f24028c3d51bf1e7728e632ff.css">'
].join("\n");
runCompilerTest(expected, done);
}); // it url
it("url with attrs", function (done) {
cssRule.use = [
{
loader: "style-loader/url",
options: {
attrs: {
'data-attr-1': 'attr-value-1',
'data-attr-2': 'attr-value-2'
}
}
},
"file-loader"
];
// Run
let expected = [
existingStyle,
'<link rel="stylesheet" type="text/css" href="ec9d4f4f24028c3d51bf1e7728e632ff.css" data-attr-1="attr-value-1" data-attr-2="attr-value-2">'
].join("\n");
runCompilerTest(expected, done);
}); // it url with attrs
it("useable", function(done) {
cssRule.use = [
{
loader: "style-loader/useable"
},
"css-loader"
];
fs.writeFileSync(
rootDir + "main.js",
[
"var css = require('./style.css');",
"var cssTwo = require('./styleTwo.css');",
"css.use();",
"cssTwo.use();",
"css.unuse();"
].join("\n")
);
// Run
let expected = [
existingStyle,
`<style type="text/css">${requiredCssTwo}</style>`
].join("\n");
runCompilerTest(expected, done);
}); // it useable
it("useable without negative refs", function(done) {
cssRule.use = [
{
loader: "style-loader/useable"
},
"css-loader"
];
fs.writeFileSync(
rootDir + "main.js",
[
"var css = require('./style.css');",
"css.unuse();", // ref still 0
"css.use();", // ref 1
].join("\n")
);
// Run
let expected = [
existingStyle,
`<style type="text/css">${requiredCss}</style>`
].join("\n");
runCompilerTest(expected, done);
}); // it useable
it("local scope", function(done) {
cssRule.use = [
{
loader: "style-loader"
},
{
loader: "css-loader",
options: {
localIdentName: '[name].[local]_[hash:base64:7]'
}
}
];
fs.writeFileSync(
rootDir + "main.js",
[
"css = require('./localScoped.css');"
].join("\n")
);
let expected = 'localScoped-className_3dIU6Uf';
runCompilerTest(expected, done, function() { return this.css.className; });
}); // it local scope
it("local scope, useable", function(done) {
cssRule.use = [
{
loader: "style-loader/useable"
},
{
loader: "css-loader",
options: {
localIdentName: '[name].[local]_[hash:base64:7]'
}
}
];
fs.writeFileSync(
rootDir + "main.js",
[
"css = require('./localScoped.css');"
].join("\n")
);
let expected = 'localScoped-className_3dIU6Uf';
runCompilerTest(expected, done, function() { return this.css.locals.className; });
}); // it local scope
}); // describe

198
test/fixUrlsTest.js Normal file
View File

@ -0,0 +1,198 @@
// Node v4 requires "use strict" to allow block scoped let & const
"use strict";
var assert = require("assert");
var url = require('url');
describe("fix urls tests", function() {
var fixUrls = require("../fixUrls");
var defaultUrl = "https://x.y.z/a/b.html";
beforeEach(function() {
global.window = {
location: url.parse(defaultUrl)
};
});
var assertUrl = function (origCss, expectedCss, specialUrl) {
if (specialUrl) {
global.window = {
location: url.parse(specialUrl)
};
}
var resultCss = fixUrls(origCss, specialUrl || defaultUrl);
expectedCss = expectedCss || origCss;
assert.equal(expectedCss, resultCss);
};
// no change
it("Null css is not modified", function() {
assertUrl(null)
});
it("Blank css is not modified", function() { assertUrl("") });
it("No url is not modified", function () { assertUrl("body { }") });
it("Full url isn't changed (no quotes)", function() {
assertUrl("body { background-image:url(http://example.com/bg.jpg); }")
});
it("Full url isn't changed (no quotes, spaces)", function() {
assertUrl("body { background-image:url ( http://example.com/bg.jpg ); }");
});
it("Full url isn't changed (double quotes)", function() {
assertUrl("body { background-image:url(\"http://example.com/bg.jpg\"); }")
});
it("Full url isn't changed (double quotes, spaces)", function() {
assertUrl("body { background-image:url ( \"http://example.com/bg.jpg\" ); }")
});
it("Full url isn't changed (single quotes)", function() {
assertUrl("body { background-image:url('http://example.com/bg.jpg'); }")
});
it("Full url isn't changed (single quotes, spaces)", function() {
assertUrl("body { background-image:url ( 'http://example.com/bg.jpg' ); }")
});
it("Multiple full urls are not changed", function() {
assertUrl(
"body { background-image:url(http://example.com/bg.jpg); }\ndiv.main { background-image:url ( 'https://www.anothersite.com/another.png' ); }"
);
});
it("Http url isn't changed", function() {
assertUrl("body { background-image:url(http://example.com/bg.jpg); }");
});
it("Https url isn't changed", function() {
assertUrl("body { background-image:url(https://example.com/bg.jpg); }");
});
it("HTTPS url isn't changed", function() {
assertUrl("body { background-image:url(HTTPS://example.com/bg.jpg); }")
});
it("File url isn't changed", function() {
assertUrl("body { background-image:url(file:///example.com/bg.jpg); }")
});
it("Double slash url isn't changed", function() {
assertUrl(
"body { background-image:url(//example.com/bg.jpg); }",
"body { background-image:url(\"//example.com/bg.jpg\"); }"
)
});
it("Image data uri url isn't changed", function() {
assertUrl("body { background-image:url(data:image/png;base64,qsrwABYuwNkimqm3gAAAABJRU5ErkJggg==); }")
});
it("Font data uri url isn't changed", function() {
assertUrl(
"body { background-image:url(data:application/x-font-woff;charset=utf-8;base64,qsrwABYuwNkimqm3gAAAABJRU5ErkJggg); }"
);
});
// relative urls
it("Relative url", function() {
assertUrl(
"body { background-image:url (bg.jpg); }",
"body { background-image:url(\"https://x.y.z/a/bg.jpg\"); }"
);
});
it("Relative url case sensitivity", function() {
assertUrl(
"body { background-image:URL (bg.jpg); }",
"body { background-image:url(\"https://x.y.z/a/bg.jpg\"); }"
);
});
it("Relative url with path", function() {
assertUrl(
"body { background-image:url(c/d/bg.jpg); }",
"body { background-image:url(\"https://x.y.z/a/c/d/bg.jpg\"); }"
);
});
it("Relative url with dot slash", function() {
assertUrl(
"body { background-image:url(./c/d/bg.jpg); }",
"body { background-image:url(\"https://x.y.z/a/c/d/bg.jpg\"); }"
);
});
it("Multiple relative urls", function() {
assertUrl(
"body { background-image:url(bg.jpg); }\ndiv.main { background-image:url(./c/d/bg.jpg); }",
"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\"); }"
);
});
it("Relative url that looks like data-uri", function() {
assertUrl(
"body { background-image:url(data/image/png.base64); }",
"body { background-image:url(\"https://x.y.z/a/data/image/png.base64\"); }"
);
});
// urls with hashes
it("Relative url with hash are not changed", function() {
assertUrl("body { background-image:url(#bg.jpg); }");
});
// rooted urls
it("Rooted url", function() {
assertUrl(
"body { background-image:url(/bg.jpg); }",
"body { background-image:url(\"https://x.y.z/bg.jpg\"); }"
);
});
it("Rooted url with path", function() {
assertUrl(
"body { background-image:url(/a/b/bg.jpg); }",
"body { background-image:url(\"https://x.y.z/a/b/bg.jpg\"); }"
);
});
//special locations
it("Location with no path, filename only", function() {
assertUrl(
"body { background-image:url(bg.jpg); }",
"body { background-image:url(\"http://x.y.z/bg.jpg\"); }",
"http://x.y.z"
);
});
it("Location with no path, path with filename", function() {
assertUrl(
"body { background-image:url(a/bg.jpg); }",
"body { background-image:url(\"http://x.y.z/a/bg.jpg\"); }",
"http://x.y.z"
);
});
it("Location with no path, rel path with filename", function() {
assertUrl(
"body { background-image:url(./a/bg.jpg); }",
"body { background-image:url(\"http://x.y.z/a/bg.jpg\"); }",
"http://x.y.z"
);
});
it("Location with no path, root filename", function() {
assertUrl(
"body { background-image:url(/a/bg.jpg); }",
"body { background-image:url(\"http://x.y.z/a/bg.jpg\"); }",
"http://x.y.z"
);
});
it("Doesn't break inline SVG", function() {
const svg = "url('data:image/svg+xml;charset=utf-8,<svg><feFlood flood-color=\"rgba(0,0,0,0.5)\" /></svg>')";
assertUrl(
"body: { background: " + svg + " }"
);
});
});

87
test/utils.js Normal file
View File

@ -0,0 +1,87 @@
// Node v4 requires "use strict" to allow block scoped let & const
"use strict";
var MemoryFS = require("memory-fs");
var realFs = require("fs");
var webpack = require("webpack");
var path = require("path");
var jsdom = require("jsdom");
var assert = require("assert");
var compiler;
var jsdomHtml;
module.exports = {
setup: function(webpackConfig, _jsdomHtml) {
let fs = new MemoryFS();
jsdomHtml = _jsdomHtml;
// Makes webpack resolve style-loader to local folder instead of node_modules
Object.assign(webpackConfig, {
resolveLoader: {
alias: {
"style-loader": path.resolve(__dirname, "../")
}
}
});
compiler = webpack(webpackConfig);
// Tell webpack to use our in-memory FS
compiler.inputFileSystem = fs;
compiler.outputFileSystem = fs;
compiler.resolvers.normal.fileSystem = fs;
compiler.resolvers.context.fileSystem = fs;
["readFileSync", "statSync"].forEach(fn => {
// Preserve the reference to original function
fs["mem" + fn] = fs[fn];
compiler.inputFileSystem[fn] = function(_path) {
// Fallback to real FS if file is not in the memoryFS
if (fs.existsSync(_path)) {
return fs["mem" + fn].apply(fs, arguments);
} else {
return realFs[fn].apply(realFs, arguments);
}
};
});
return fs;
},
/*
* @param {string} expected - Expected value.
* @param {function} done - Async callback from Mocha.
* @param {function} actual - Executed in the context of jsdom window, should return a string to compare to.
*/
runCompilerTest: function(expected, done, actual, selector) {
selector = selector || "head"
compiler.run(function(err, stats) {
if (stats.compilation.errors.length) {
throw new Error(stats.compilation.errors);
}
const bundleJs = stats.compilation.assets["bundle.js"].source();
jsdom.env({
html: jsdomHtml,
src: [bundleJs],
virtualConsole: jsdom.createVirtualConsole().sendTo(console),
done: function(err, window) {
if (typeof actual === 'function') {
assert.equal(actual.apply(window), expected);
} else {
assert.equal(window.document.querySelector(selector).innerHTML.trim(), expected);
}
// free memory associated with the window
window.close();
done();
}
});
});
}
};

3
url.js
View File

@ -7,11 +7,12 @@ var loaderUtils = require("loader-utils"),
module.exports = function() {};
module.exports.pitch = function(remainingRequest) {
this.cacheable && this.cacheable();
var query = loaderUtils.getOptions(this) || {};
return [
"// style-loader: Adds some reference to a css file to the DOM by adding a <link> tag",
"var update = require(" + JSON.stringify("!" + path.join(__dirname, "addStyleUrl.js")) + ")(",
"\trequire(" + loaderUtils.stringifyRequest(this, "!!" + remainingRequest) + ")",
");",
", " + JSON.stringify(query) + ");",
"// Hot Module Replacement",
"if(module.hot) {",
"\tmodule.hot.accept(" + loaderUtils.stringifyRequest(this, "!!" + remainingRequest) + ", function() {",

View File

@ -7,21 +7,21 @@ var loaderUtils = require("loader-utils"),
module.exports = function() {};
module.exports.pitch = function(remainingRequest) {
if(this.cacheable) this.cacheable();
var query = loaderUtils.parseQuery(this.query);
var query = loaderUtils.getOptions(this) || {};
return [
"var refs = 0;",
"var dispose;",
"var content = require(" + loaderUtils.stringifyRequest(this, "!!" + remainingRequest) + ");",
"if(typeof content === 'string') content = [[module.id, content, '']];",
"if(content.locals) exports.locals = content.locals;",
"exports.use = exports.ref = function() {",
" if(!(refs++)) {",
" exports.locals = content.locals;",
" dispose = require(" + loaderUtils.stringifyRequest(this, "!" + path.join(__dirname, "addStyles.js")) + ")(content, " + JSON.stringify(query) + ");",
" }",
" return exports;",
"};",
"exports.unuse = exports.unref = function() {",
" if(!(--refs)) {",
" if(refs > 0 && !(--refs)) {",
" dispose();",
" dispose = null;",
" }",

3130
yarn.lock Normal file

File diff suppressed because it is too large Load Diff