This commit is contained in:
chrosey
2017-09-13 07:52:34 +02:00
parent a1f16c37f4
commit 2340b0226b
24621 changed files with 2912161 additions and 149 deletions
+10
View File
@@ -0,0 +1,10 @@
{
"env": {
"node": true
},
"rules": {
"strict": 0,
"curly": 0,
"quotes": 0
}
}
+3
View File
@@ -0,0 +1,3 @@
node_modules
coverage
test
+15
View File
@@ -0,0 +1,15 @@
sudo: false
language: node_js
node_js:
- "0.10"
- "0.12"
- "iojs"
script: npm run travis
before_install:
- '[ "${TRAVIS_NODE_VERSION}" != "0.10" ] || npm install -g npm'
after_success:
- cat ./coverage/lcov.info | node_modules/.bin/coveralls --verbose
- cat ./coverage/coverage.json | node_modules/codecov.io/bin/codecov.io.js
- rm -rf ./coverage
+227
View File
@@ -0,0 +1,227 @@
# css loader for webpack
## installation
`npm install css-loader --save-dev`
## Usage
[Documentation: Using loaders](http://webpack.github.io/docs/using-loaders.html)
``` javascript
var css = require("css!./file.css");
// => returns css code from file.css, resolves imports and url(...)
```
`@import` and `url(...)` are interpreted like `require()` and will be resolved by the css-loader.
Good loaders for requiring your assets are the [file-loader](https://github.com/webpack/file-loader)
and the [url-loader](https://github.com/webpack/url-loader) which you should specify in your config (see below).
To be compatible with existing css files:
* `url(image.png)` => `require("./image.png")`
* `url(~module/image.png)` => `require("module/image.png")`
### Example config
This webpack config can load css files, embed small png images as Data Urls and jpg images as files.
``` javascript
module.exports = {
module: {
loaders: [
{ test: /\.css$/, loader: "style-loader!css-loader" },
{ test: /\.png$/, loader: "url-loader?limit=100000" },
{ test: /\.jpg$/, loader: "file-loader" }
]
}
};
```
### 'Root-relative' urls
For urls that start with a `/`, the default behavior is to not translate them:
* `url(/image.png)` => `url(/image.png)`
If a `root` query parameter is set, however, it will be prepended to the url
and then translated:
With a config like:
``` javascript
loaders: [
{ test: /\.css$/, loader: "style-loader!css-loader?root=." },
...
]
```
The result is:
* `url(/image.png)` => `require("./image.png")`
### Local scope
By default CSS exports all class names into a global selector scope. This is a feature which offer a local selector scope.
The syntax `:local(.className)` can be used to declare `className` in the local scope. The local identifiers are exported by the module.
With `:local` (without brackets) local mode can be switched on for this selector. `:global(.className)` can be used to declare an explicit global selector. With `:global` (without brackets) global mode can be switched on for this selector.
The loader replaces local selectors with unique identifiers. The choosen unique identifiers are exported by the module.
Example:
``` css
:local(.className) { background: red; }
:local .className { color: green; }
:local(.className .subClass) { color: green; }
:local .className .subClass :global(.global-class-name) { color: blue; }
```
is transformed to
``` css
._23_aKvs-b8bW2Vg3fwHozO { background: red; }
._23_aKvs-b8bW2Vg3fwHozO { color: green; }
._23_aKvs-b8bW2Vg3fwHozO ._13LGdX8RMStbBE9w-t0gZ1 { color: green; }
._23_aKvs-b8bW2Vg3fwHozO ._13LGdX8RMStbBE9w-t0gZ1 .global-class-name { color: blue; }
```
and the identifiers are exported:
``` js
exports.locals = {
className: "_23_aKvs-b8bW2Vg3fwHozO",
subClass: "_13LGdX8RMStbBE9w-t0gZ1"
}
```
Camelcasing is recommended for local selectors. They are easier to use in the importing javascript module.
You can use `:local(#someId)`, but this is not recommended. Use classes instead of ids.
You can configure the generated ident with the `localIdentName` query parameter (default `[hash:base64]`). Example: `css-loader?localIdentName=[path][name]---[local]---[hash:base64:5]` for easier debugging.
Note: For prerendering with extract-text-webpack-plugin you should use `css-loader/locals` instead of `style-loader!css-loader` in the prerendering bundle. It doesn't embed CSS but only exports the identifier mappings.
### Module mode
(experimental)
The query parameter `module` enables **CSS Module** mode. (`css-loader?module`)
* Local scoped by default.
* `url(...)` URLs behave like requests in modules:
* `./file.png` instead of `file.png`
* `module/file.png` instead of `~module/file.png`
Thanks to [@markdalgleish](https://github.com/markdalgleish) for prior work on this topic.
### Inheriting
When declaring a local class name you can inherit from another local class name.
``` css
:local(.className) {
background: red;
color: yellow;
}
:local(.subClass) {
extends: className;
background: blue;
}
```
This doesn't result in any change to the CSS itself but exports multiple class names:
``` js
exports.locals = {
className: "_23_aKvs-b8bW2Vg3fwHozO",
subClass: "_13LGdX8RMStbBE9w-t0gZ1 _23_aKvs-b8bW2Vg3fwHozO"
}
```
and CSS is transformed to:
``` css
._23_aKvs-b8bW2Vg3fwHozO {
background: red;
color: yellow;
}
._13LGdX8RMStbBE9w-t0gZ1 {
background: blue;
}
```
### Importing local class names
To import a local class name from another module:
``` css
:local(.continueButton) {
extends: button from "library/button.css";
background: red;
}
```
``` css
:local(.nameEdit) {
extends: edit highlight from "./edit.css";
background: red;
}
```
To import from multiple modules use multiple `extends:` rules. You can also use `url(...)` to specify the module (it behave a bit different).
``` css
:local(.className) {
extends: edit hightlight from "./edit.css";
extends: button from url("button.css");
/* equal to 'extends: button from "./button.css";' */
extends: classFromThisModule;
background: red;
}
```
### SourceMaps
To include SourceMaps set the `sourceMap` query param.
`require("css-loader?sourceMap!./file.css")`
I. e. the extract-text-webpack-plugin can handle them.
### importing and chained loaders
The query parameter `importLoaders` allow to configure which loaders should be applied to `@import`ed resources.
`importLoaders` (int): That many loaders after the css-loader are used to import resources.
Examples:
``` js
require("style-loader!css-loader?importLoaders=1!autoprefixer-loader!...")
// => imported resources are handled this way:
require("css-loader?importLoaders=1!autoprefixer-loader!...")
require("style-loader!css-loader!stylus-loader!...")
// => imported resources are handled this way:
require("css-loader!...")
```
### Minification
By default the css-loader minimizes the css if specified by the module system.
In some cases the minification is destructive to the css, so you can provide some options to it. clean-css is used for minification and you find a [list of options here](https://github.com/jakubpawlowicz/clean-css#how-to-use-clean-css-programmatically). Just provide them as query parameter: i. e. `require("css-loader?-restructuring&compatibility")` to disable restructuring and enable compatibility mode.
You can also disable or enforce minification with the `minimize` query parameter.
`require("css-loader?minimize!./file.css")` (enforced)
`require("css-loader?-minimize!./file.css")` (disabled)
## License
MIT (http://www.opensource.org/licenses/mit-license.php)
+5
View File
@@ -0,0 +1,5 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
module.exports = require("./lib/loader");
+29
View File
@@ -0,0 +1,29 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
function ReplaceMany() {
this.replacements = [];
}
module.exports = ReplaceMany;
ReplaceMany.prototype.replace = function(start, length, newString) {
this.replacements.push([start, start + length, newString]);
return this;
};
ReplaceMany.prototype.run = function(string) {
this.replacements.sort(function(a, b) {
return b[0] - a[0];
});
var result = [string];
this.replacements.forEach(function(repl) {
var str = result.pop();
var done = str.substr(repl[1]);
var leftover = str.substr(0, repl[0]);
result.push(done, repl[2], leftover);
});
result.reverse();
return result.join("");
};
+50
View File
@@ -0,0 +1,50 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
// css base code, injected by the css-loader
module.exports = function() {
var list = [];
// return the list of modules as css string
list.toString = function toString() {
var result = [];
for(var i = 0; i < this.length; i++) {
var item = this[i];
if(item[2]) {
result.push("@media " + item[2] + "{" + item[1] + "}");
} else {
result.push(item[1]);
}
}
return result.join("");
};
// import a list of modules into the list
list.i = function(modules, mediaQuery) {
if(typeof modules === "string")
modules = [[null, modules, ""]];
var alreadyImportedModules = {};
for(var i = 0; i < this.length; i++) {
var id = this[i][0];
if(typeof id === "number")
alreadyImportedModules[id] = true;
}
for(i = 0; i < modules.length; i++) {
var item = modules[i];
// skip already imported module
// this implementation is not 100% perfect for weird media query combinations
// when a module is imported multiple times with different media queries.
// I hope this will never occur (Hey this way we have smaller bundles)
if(typeof item[0] !== "number" || !alreadyImportedModules[item[0]]) {
if(mediaQuery && !item[2]) {
item[2] = mediaQuery;
} else if(mediaQuery) {
item[2] = "(" + item[2] + ") and (" + mediaQuery + ")";
}
list.push(item);
}
}
};
return list;
};
+38
View File
@@ -0,0 +1,38 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
var loaderUtils = require("loader-utils");
module.exports = function(locals, localExtends, importedUrls, importUrlPrefix, result, importAccess) {
var localKeys = Object.keys(locals);
if(localKeys.length > 0) {
var localLines = localKeys.map(function(key, idx) {
var line = " " + JSON.stringify(key) + ": ";
function addExtend(extend) {
if(extend.from) {
var importUrl = importUrlPrefix +
(extend.fromType === "url" ? loaderUtils.urlToRequest(extend.from) : extend.from);
if(importedUrls && result && importedUrls.indexOf(importUrl) < 0) {
result.push("exports.i(require(" + loaderUtils.stringifyRequest(this, importUrl) + "), \"\");");
importedUrls.push(importUrl);
}
line += " + \" \" + require(" + loaderUtils.stringifyRequest(this, importUrl) + ")" + importAccess + "[" + JSON.stringify(extend.name) + "]";
} else if(locals[extend.name]) {
line += " + \" \" + " + JSON.stringify(locals[extend.name]);
if(localExtends[extend.name]) {
localExtends[extend.name].forEach(addExtend, this);
}
} else if(this.emitError) {
this.emitError("Cannot extend from unknown class '" + extend.name + "'");
}
}
line += JSON.stringify(locals[key]);
if(localExtends[key]) {
localExtends[key].forEach(addExtend, this);
}
if(idx !== localKeys.length - 1) line += ",";
return line;
}, this);
return "{\n" + localLines.join("\n") + "\n}";
}
};
+15
View File
@@ -0,0 +1,15 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
var loaderUtils = require("loader-utils");
module.exports = function getLocalIdent(loaderContext, localIdentName, localName, options) {
var request = loaderContext.options && typeof loaderContext.options.context === "string" ?
loaderUtils.stringifyRequest({ context: loaderContext.options.context }, loaderUtils.getRemainingRequest(loaderContext)) :
loaderContext.request;
options.content = localName + " " + request;
options.context = loaderContext.options && typeof loaderContext.options.context === "string" ? loaderContext.options.context : loaderContext.context;
localIdentName = localIdentName.replace(/\[local\]/gi, localName);
var hash = loaderUtils.interpolateName(loaderContext, localIdentName, options);
return hash.replace(/[^a-zA-Z0-9\-_]/g, "-").replace(/^([^a-zA-Z_])/, "_$1");
};
+162
View File
@@ -0,0 +1,162 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
var path = require("path");
var parseSource = require("./parseSource");
var ReplaceMany = require("./ReplaceMany");
var loaderUtils = require("loader-utils");
var SourceListMap = require("source-list-map").SourceListMap;
var CleanCSS = require("clean-css");
module.exports = function(content, map) {
if(this.cacheable) this.cacheable();
var query = loaderUtils.parseQuery(this.query);
var root = query.root;
var forceMinimize = query.minimize;
var importLoaders = parseInt(query.importLoaders, 10) || 0;
var minimize = typeof forceMinimize !== "undefined" ? !!forceMinimize : (this && this.minimize);
var moduleMode = query.module;
if(typeof map !== "string") {
map = JSON.stringify(map);
}
var result = [];
// for importing CSS
var loadersRequest = this.loaders.slice(
this.loaderIndex,
this.loaderIndex + 1 + importLoaders
).map(function(x) { return x.request; }).join("!");
var importUrlPrefix = "-!" + loadersRequest + "!";
var stuff = parseSource(content);
var replacer = new ReplaceMany();
// store already imported files
var importedUrls = [];
// add @imports to result
stuff.imports.forEach(function(imp) {
replacer.replace(imp.start, imp.length, "");
if(!loaderUtils.isUrlRequest(imp.url, root)) {
result.push("exports.push([module.id, " +
JSON.stringify("@import url(" + imp.url + ");") + ", " +
JSON.stringify(imp.mediaQuery) + "]);");
} else {
var importUrl = importUrlPrefix +
(moduleMode ? imp.url : loaderUtils.urlToRequest(imp.url));
result.push("exports.i(require(" + loaderUtils.stringifyRequest(this, importUrl) + "), " + JSON.stringify(imp.mediaQuery) + ");");
if(!imp.mediaQuery)
importedUrls.push(importUrl);
}
}, this);
// replace url(...)
if(query.url !== false) {
stuff.urls.forEach(function(url, idx) {
replacer.replace(url.start, url.length, "__CSSLOADERURL_" + idx + "__");
});
}
// replace :local()
var locals = {};
var localExtends = {};
require("./processLocals").call(this, stuff.selectors, query, replacer, locals, localExtends);
// remove stuff
stuff.remove.forEach(function(rem) {
replacer.replace(rem.start, rem.length, "");
});
// pass errors from parser
if(this.emitError) {
stuff.errors.forEach(function(err) {
this.emitError(err);
}, this);
}
// generate the locals
var localsData = require("./generateLocals").call(this, locals, localExtends, importedUrls, importUrlPrefix, result, ".locals");
if(localsData) {
result.push("exports.locals = " + localsData + ";");
}
// transform the CSS
var cssContent = replacer.run(content);
// minimize CSS
if(minimize) {
var options = Object.create(query);
if(query.sourceMap && map) {
options.sourceMap = map;
}
var minimizeResult = new CleanCSS(options).minify(cssContent);
map = minimizeResult.sourceMap;
cssContent = minimizeResult.styles;
if(typeof map !== "string")
map = JSON.stringify(map);
}
function toEmbStr(str) {
return JSON.stringify(str).replace(/^"|"$/g, "");
}
// replace url(...) in the generated code
var css = JSON.stringify(cssContent);
var urlRegExp = /__CSSLOADERURL_[0-9]+__/g;
css = css.replace(urlRegExp, function(str) {
var match = /^__CSSLOADERURL_([0-9]+)__$/.exec(str);
if(!match) return str;
var idx = parseInt(match[1], 10);
if(!stuff.urls[idx]) return str;
var urlItem = stuff.urls[idx];
var url = urlItem.url;
if(!loaderUtils.isUrlRequest(url, root))
return toEmbStr(urlItem.raw);
idx = url.indexOf("?#");
if(idx < 0) idx = url.indexOf("#");
var urlRequest;
if(idx > 0) { // idx === 0 is catched by isUrlRequest
// in cases like url('webfont.eot?#iefix')
urlRequest = url.substr(0, idx);
if(!moduleMode) urlRequest = loaderUtils.urlToRequest(urlRequest, root);
return "\"+require(" + loaderUtils.stringifyRequest(this, urlRequest) + ")+\"" + url.substr(idx);
}
urlRequest = url;
if(!moduleMode) urlRequest = loaderUtils.urlToRequest(url, root);
return "\"+require(" + loaderUtils.stringifyRequest(this, urlRequest) + ")+\"";
}.bind(this));
// add a SourceMap
if(query.sourceMap && !minimize) {
var cssRequest = loaderUtils.getRemainingRequest(this);
var request = loaderUtils.getCurrentRequest(this);
if(!map) {
var sourceMap = new SourceListMap();
sourceMap.add(content, cssRequest, content);
map = sourceMap.toStringWithSourceMap({
file: request
}).map;
if(map.sources) {
map.sources = map.sources.map(function(source) {
var p = path.relative(query.context || this.options.context, source).replace(/\\/g, "/");
if(p.indexOf("../") !== 0)
p = "./" + p;
return "/" + p;
}, this);
map.sourceRoot = "webpack://";
}
map = JSON.stringify(map);
}
result.push("exports.push([module.id, " + css + ", \"\", " + map + "]);");
} else {
result.push("exports.push([module.id, " + css + ", \"\"]);");
}
// embed runtime
return "exports = module.exports = require(" + loaderUtils.stringifyRequest(this, require.resolve("./css-base.js")) + ")();\n" +
result.join("\n");
};
+32
View File
@@ -0,0 +1,32 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
var loaderUtils = require("loader-utils");
var parseSource = require("./parseSource");
module.exports = function(content) {
if(this.cacheable) this.cacheable();
var query = loaderUtils.parseQuery(this.query);
var importLoaders = parseInt(query.importLoaders, 10) || 0;
// for importing CSS
var loadersRequest = this.loaders.slice(
this.loaderIndex,
this.loaderIndex + 1 + importLoaders
).map(function(x) { return x.request; }).join("!");
var importUrlPrefix = "-!" + loadersRequest + "!";
var stuff = parseSource(content);
var locals = {};
var localExtends = {};
require("./processLocals").call(this, stuff.selectors, query, null, locals, localExtends);
// generate the locals
var localsData = require("./generateLocals").call(this, locals, localExtends, null, importUrlPrefix, null, "");
return "module.exports = " + localsData + ";";
};
+419
View File
@@ -0,0 +1,419 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
var loaderUtils = require("loader-utils");
var Parser = require("fastparse");
function errorMatch(message, newParserMode) {
return function(match) {
var index = arguments[arguments.length - 1];
var nextLine = this.source.indexOf("\n", index);
var splittedSource = this.source.substr(0, index).split("\n");
var line = splittedSource.length;
var lineBeforeError = splittedSource.pop();
var lineAfterError = this.source.substr(index, nextLine);
this.errors.push("Unexpected '" + match + "' in line " + (line + 1) + ", " + message + "\n" +
lineBeforeError + lineAfterError + "\n" + lineBeforeError.replace(/[^\\s]/g, " ") + "^");
return newParserMode;
};
}
function urlMatch(match, textBeforeUrl, replacedText, url, index) {
this.urls.push({
url: url,
raw: replacedText,
start: index + textBeforeUrl.length,
length: replacedText.length
});
}
function importMatch(match, url, mediaQuery, index) {
this.imports.push({
url: url,
mediaQuery: mediaQuery,
start: index,
length: match.length
});
}
function rulesStartMatch() {
this.blockMode = this.mode;
return "firstRule";
}
function rulesEndMatch() {
this.mode = null;
this.activeSelectors = [];
return "source";
}
function nextSelectorMatch() {
this.mode = null;
}
function enableLocal(match, whitespace, index) {
this.remove.push({
start: index + whitespace.length,
length: match.length - whitespace.length
});
this.mode = "local";
}
function enableGlobal(match, whitespace, index) {
this.remove.push({
start: index + whitespace.length,
length: match.length - whitespace.length
});
this.mode = "global";
}
function localStart(match, whitespace, index) {
this.remove.push({
start: index + whitespace.length,
length: match.length - whitespace.length
});
this.bracketStatus = 0;
return "local";
}
function globalStart(match, whitespace, index) {
this.remove.push({
start: index + whitespace.length,
length: match.length - whitespace.length
});
this.bracketStatus = 0;
return "global";
}
function withMode(mode, fn) {
return function() {
var oldMode = this.mode;
this.mode = mode;
var newParserMode = fn.apply(this, arguments);
this.mode = oldMode;
return newParserMode;
};
}
function jump(newParserMode, fn) {
return function() {
fn.apply(this, arguments);
return newParserMode;
};
}
function innerBracketIn() {
this.bracketStatus++;
}
function innerBracketOut(match, index) {
if(this.bracketStatus-- === 0) {
this.remove.push({
start: index,
length: match.length
});
return "source";
}
}
function selectorMatch(match, prefix, name, index) {
var selector = {
name: name,
prefix: prefix,
start: index,
length: match.length,
mode: this.mode
};
this.selectors.push(selector);
this.activeSelectors.push(selector);
}
function ruleScopedMatch() {
this.mode = this.blockMode;
return "ruleScoped";
}
function extendsStartMatch(match, index) {
this.remove.push({
start: index,
length: 0
});
this.activeExtends = [];
return "extends";
}
function extendsEndMatch(match, index) {
var lastRemove = this.remove[this.remove.length - 1];
lastRemove.length = index + match.length - lastRemove.start;
if(this.activeExtends.length === 0) {
errorMatch("expected class names")(match, index);
return "rule";
}
return "firstRule";
}
function extendsClassNameMatch(match, name, index) {
this.activeSelectors.forEach(function(selector) {
if(!selector.extends)
selector.extends = [];
var extend = {
name: name,
start: index,
length: match.length,
from: null,
fromType: null
};
selector.extends.push(extend);
this.activeExtends.push(extend);
}, this);
}
function extendsFromUrlMatch(match, request) {
this.activeExtends.forEach(function(extend) {
extend.from = request;
extend.fromType = "url";
});
}
function extendsFromMatch(match, request) {
this.activeExtends.forEach(function(extend) {
extend.from = loaderUtils.parseString(request);
extend.fromType = "module";
});
}
var parser = new Parser({
// shared stuff
comments: {
"/\\*[\\s\\S]*?\\*/": true
},
strings: {
'"([^\\\\"]|\\\\.)*"': true,
"'([^\\\\']|\\\\.)*'": true
},
urls: {
'(url\\s*\\()(\\s*"([^"]*)"\\s*)\\)': urlMatch,
"(url\\s*\\()(\\s*'([^']*)'\\s*)\\)": urlMatch,
"(url\\s*\\()(\\s*([^)]*)\\s*)\\)": urlMatch
},
scopedRules: {
"(?:-[a-z]+-)?animation(?:-name)?:": ruleScopedMatch
},
// states
source: [
"comments",
"strings",
"urls",
{
// imports
'@\\s*import\\s+"([^"]*)"\\s*([^;\\n]*);': importMatch,
"@\\s*import\\s+'([^'']*)'\\s*([^;\\n]*);": importMatch,
'@\\s*import\\s+url\\s*\\(\\s*"([^"]*)"\\s*\\)\\s*([^;\\n]*);': importMatch,
"@\\s*import\\s+url\\s*\\(\\s*'([^']*)'\\s*\\)\\s*([^;\\n]*);": importMatch,
"@\\s*import\\s+url\\s*\\(\\s*([^)]*)\\s*\\)\\s*([^;\\n]*);": importMatch,
// charset
"@charset": true,
// namespace
"@(?:-[a-z]+-)?namespace": true,
// atrule
"@(?:-[a-z]+-)?keyframes": "atruleScoped",
"@": "atrule"
},
{
// local
"(\\s*):local\\(": localStart,
"():local": enableLocal,
"(\\s+):local\\s+": enableLocal,
// global
"(\\s*):global\\(": globalStart,
"():global": enableGlobal,
"(\\s+):global\\s+": enableGlobal,
// class
"(\\.)(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)": selectorMatch,
// id
"(#)(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)": selectorMatch,
// inside
"\\{": rulesStartMatch,
",": nextSelectorMatch
}
],
atruleScoped: [
"comments",
"strings",
{
// identifier
":local\\(\\s*()([A-Za-z_\\-0-9]+)\\s*\\)": withMode("local", selectorMatch),
":global\\(\\s*()([A-Za-z_\\-0-9]+)\\s*\\)": withMode("global", selectorMatch),
"()([A-Za-z_\\-0-9]+)": selectorMatch,
// local
"():local": enableLocal,
"(\\s+):local\\s+": enableLocal,
// global
"():global": enableGlobal,
"(\\s+):global\\s+": enableGlobal,
// back to normal source
"\\{": "source"
}
],
atrule: [
"comments",
"strings",
{
// back to normal source
"\\{": "source"
}
],
ruleScoped: [
"comments",
{
// identifier
":local\\(\\s*()([A-Za-z_\\-0-9]+)\\s*\\)": jump("ruleScopedInactive", withMode("local", selectorMatch)),
":global\\(\\s*()([A-Za-z_\\-0-9]+)\\s*\\)": jump("ruleScopedInactive", withMode("global", selectorMatch)),
"()([A-Za-z_\\-0-9]+)": jump("ruleScopedInactive", selectorMatch),
// local
"():local": enableLocal,
"(\\s+):local\\s+": enableLocal,
// global
"():global": enableGlobal,
"(\\s+):global\\s+": enableGlobal,
// back to normal rule
";": "rule",
// back to normal source
"\\}": rulesEndMatch
}
],
ruleScopedInactive: [
"comments",
{
// reactivate
",": ruleScopedMatch,
// back to normal rule
";": "rule",
// back to normal source
"\\}": rulesEndMatch
}
],
rule: [
"comments",
"strings",
"urls",
"scopedRules",
{
// back to normal source
"\\}": rulesEndMatch
}
],
firstRule: [
"rule",
{
"extends\\s*:": extendsStartMatch,
// whitespace
"\\s+": true,
// url
".": "rule"
}
],
extends: [
"comments",
{
";\\s*": extendsEndMatch,
// whitespace
"\\s+": true,
// from
"from": "extendsFrom",
// class name
"([A-Za-z_\\-0-9]+)": extendsClassNameMatch,
".+[;}]": errorMatch("expected class names or 'from'", "rule"),
".": errorMatch("expected class names or 'from'", "rule")
}
],
extendsFrom: [
"comments",
{
";\\s*": extendsEndMatch,
// whitespace
"\\s+": true,
// module
'url\\s*\\(\\s*"([^"]*)"\\s*\\)': extendsFromUrlMatch,
"url\\s*\\(\\s*'([^']*)'\\s*\\)": extendsFromUrlMatch,
"url\\s*\\(\\s*([^)]*)\\s*\\)": extendsFromUrlMatch,
'("(?:[^\\\\"]|\\\\.)*")': extendsFromMatch,
"('(?:[^\\\\']|\\\\.)*')": extendsFromMatch,
".+[;}]": errorMatch("expected module identifier (a string or 'url(...)'')", "rule"),
".": errorMatch("expected module identifier (a string or 'url(...)'')", "rule")
}
],
local: [
"comments",
"strings",
{
// class
"(\\.)([A-Za-z_\\-0-9]+)": withMode("local", selectorMatch),
// id
"(#)([A-Za-z_\\-0-9]+)": withMode("local", selectorMatch),
// brackets
"\\(": innerBracketIn,
"\\)": innerBracketOut
}
],
global: [
"comments",
"strings",
{
// class
"(\\.)([A-Za-z_\\-0-9]+)": withMode("global", selectorMatch),
// id
"(#)([A-Za-z_\\-0-9]+)": withMode("global", selectorMatch),
// brackets
"\\(": innerBracketIn,
"\\)": innerBracketOut
}
]
});
module.exports = function parseSource(source) {
var context = {
source: source,
imports: [],
urls: [],
selectors: [],
remove: [],
errors: [],
activeSelectors: [],
bracketStatus: 0,
mode: null
};
return parser.parse("source", source, context);
};
+39
View File
@@ -0,0 +1,39 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
var getLocalIdent = require("./getLocalIdent");
module.exports = function(parsedLocals, query, replacer, locals, localExtends) {
var localIdentName = query.localIdentName || "[hash:base64]";
var localIdentRegExp = query.localIdentRegExp;
var moduleMode = query.module;
parsedLocals.forEach(function(selector) {
if(moduleMode) {
if(selector.mode === "global")
return;
} else {
if(selector.mode !== "local")
return;
}
var ident;
var name = selector.name;
if(!locals[name]) {
ident = getLocalIdent(this, localIdentName, name, {
regExp: localIdentRegExp
});
locals[name] = ident;
} else {
ident = locals[name];
}
if(selector.extends) {
selector.extends.forEach(function(extend) {
if(!localExtends[name])
localExtends[name] = [];
localExtends[name].push(extend);
});
}
if(replacer)
replacer.replace(selector.start, selector.length, selector.prefix + ident);
}, this);
};
+5
View File
@@ -0,0 +1,5 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
module.exports = require("./lib/localsLoader");
+15
View File
@@ -0,0 +1,15 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
"$basedir/node" "$basedir/../clean-css/bin/cleancss" "$@"
ret=$?
else
node "$basedir/../clean-css/bin/cleancss" "$@"
ret=$?
fi
exit $ret
+7
View File
@@ -0,0 +1,7 @@
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\..\clean-css\bin\cleancss" %*
) ELSE (
@SETLOCAL
@SET PATHEXT=%PATHEXT:;.JS;=;%
node "%~dp0\..\clean-css\bin\cleancss" %*
)
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
Copyright (C) 2015 JakubPawlowicz.com
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.
+369
View File
@@ -0,0 +1,369 @@
[![NPM version](https://img.shields.io/npm/v/clean-css.svg?style=flat)](https://www.npmjs.com/package/clean-css)
[![Linux Build Status](https://img.shields.io/travis/jakubpawlowicz/clean-css/master.svg?style=flat&label=Linux%20build)](https://travis-ci.org/jakubpawlowicz/clean-css)
[![Windows Build status](https://img.shields.io/appveyor/ci/jakubpawlowicz/clean-css/master.svg?style=flat&label=Windows%20build)](https://ci.appveyor.com/project/jakubpawlowicz/clean-css/branch/master)
[![Dependency Status](https://img.shields.io/david/jakubpawlowicz/clean-css.svg?style=flat)](https://david-dm.org/jakubpawlowicz/clean-css)
[![devDependency Status](https://img.shields.io/david/dev/jakubpawlowicz/clean-css.svg?style=flat)](https://david-dm.org/jakubpawlowicz/clean-css#info=devDependencies)
## What is clean-css?
Clean-css is a fast and efficient [Node.js](http://nodejs.org/) library for minifying CSS files.
According to [tests](http://goalsmashers.github.io/css-minification-benchmark/) it is one of the best available.
## Usage
### What are the requirements?
```
Node.js 0.10+ (tested on CentOS, Ubuntu, OS X 10.6+, and Windows 7+) or io.js 3.0+
```
### How to install clean-css?
```
npm install clean-css
```
### How to use clean-css CLI?
Clean-css accepts the following command line arguments (please make sure
you use `<source-file>` as the very last argument to avoid potential issues):
```
cleancss [options] source-file, [source-file, ...]
-h, --help output usage information
-v, --version output the version number
-b, --keep-line-breaks Keep line breaks
-c, --compatibility [ie7|ie8] Force compatibility mode (see Readme for advanced examples)
-d, --debug Shows debug information (minification time & compression efficiency)
-o, --output [output-file] Use [output-file] as output instead of STDOUT
-r, --root [root-path] Set a root path to which resolve absolute @import rules
-s, --skip-import Disable @import processing
-t, --timeout [seconds] Per connection timeout when fetching remote @imports (defaults to 5 seconds)
--rounding-precision [n] Rounds to `N` decimal places. Defaults to 2. -1 disables rounding
--s0 Remove all special comments, i.e. /*! comment */
--s1 Remove all special comments but the first one
--semantic-merging Enables unsafe mode by assuming BEM-like semantic stylesheets (warning, this may break your styling!)
--skip-advanced Disable advanced optimizations - ruleset reordering & merging
--skip-aggressive-merging Disable properties merging based on their order
--skip-import-from [rules] Disable @import processing for specified rules
--skip-media-merging Disable @media merging
--skip-rebase Disable URLs rebasing
--skip-restructuring Disable restructuring optimizations
--skip-shorthand-compacting Disable shorthand compacting
--source-map Enables building input's source map
--source-map-inline-sources Enables inlining sources inside source maps
```
#### Examples:
To minify a **public.css** file into **public-min.css** do:
```
cleancss -o public-min.css public.css
```
To minify the same **public.css** into the standard output skip the `-o` parameter:
```
cleancss public.css
```
More likely you would like to concatenate a couple of files.
If you are on a Unix-like system:
```bash
cat one.css two.css three.css | cleancss -o merged-and-minified.css
```
On Windows:
```bat
type one.css two.css three.css | cleancss -o merged-and-minified.css
```
Or even gzip the result at once:
```bash
cat one.css two.css three.css | cleancss | gzip -9 -c > merged-minified-and-gzipped.css.gz
```
### How to use clean-css API?
```js
var CleanCSS = require('clean-css');
var source = 'a{font-weight:bold;}';
var minified = new CleanCSS().minify(source).styles;
```
CleanCSS constructor accepts a hash as a parameter, i.e.,
`new CleanCSS(options)` with the following options available:
* `advanced` - set to false to disable advanced optimizations - selector & property merging, reduction, etc.
* `aggressiveMerging` - set to false to disable aggressive merging of properties.
* `benchmark` - turns on benchmarking mode measuring time spent on cleaning up (run `npm run bench` to see example)
* `compatibility` - enables compatibility mode, see [below for more examples](#how-to-set-a-compatibility-mode)
* `debug` - set to true to get minification statistics under `stats` property (see `test/custom-test.js` for examples)
* `inliner` - a hash of options for `@import` inliner, see [test/protocol-imports-test.js](https://github.com/jakubpawlowicz/clean-css/blob/master/test/protocol-imports-test.js#L372) for examples, or [this comment](https://github.com/jakubpawlowicz/clean-css/issues/612#issuecomment-119594185) for a proxy use case.
* `keepBreaks` - whether to keep line breaks (default is false)
* `keepSpecialComments` - `*` for keeping all (default), `1` for keeping first one only, `0` for removing all
* `mediaMerging` - whether to merge `@media` at-rules (default is true)
* `processImport` - whether to process `@import` rules
* `processImportFrom` - a list of `@import` rules, can be `['all']` (default), `['local']`, `['remote']`, or a blacklisted path e.g. `['!fonts.googleapis.com']`
* `rebase` - set to false to skip URL rebasing
* `relativeTo` - path to **resolve** relative `@import` rules and URLs
* `restructuring` - set to false to disable restructuring in advanced optimizations
* `root` - path to **resolve** absolute `@import` rules and **rebase** relative URLs
* `roundingPrecision` - rounding precision; defaults to `2`; `-1` disables rounding
* `semanticMerging` - set to true to enable semantic merging mode which assumes BEM-like content (default is false as it's highly likely this will break your stylesheets - **use with caution**!)
* `shorthandCompacting` - set to false to skip shorthand compacting (default is true unless sourceMap is set when it's false)
* `sourceMap` - exposes source map under `sourceMap` property, e.g. `new CleanCSS().minify(source).sourceMap` (default is false)
If input styles are a product of CSS preprocessor (Less, Sass) an input source map can be passed as a string.
* `sourceMapInlineSources` - set to true to inline sources inside a source map's `sourcesContent` field (defaults to false)
It is also required to process inlined sources from input source maps.
* `target` - path to a folder or an output file to which **rebase** all URLs
The output of `minify` method (or the 2nd argument to passed callback) is a hash containing the following fields:
* `styles` - optimized output CSS as a string
* `sourceMap` - output source map (if requested with `sourceMap` option)
* `errors` - a list of errors raised
* `warnings` - a list of warnings raised
* `stats` - a hash of statistic information (if requested with `debug` option):
* `originalSize` - original content size (after import inlining)
* `minifiedSize` - optimized content size
* `timeSpent` - time spent on optimizations
* `efficiency` - a ratio of output size to input size (e.g. 25% if content was reduced from 100 bytes to 75 bytes)
#### How to make sure remote `@import`s are processed correctly?
In order to inline remote `@import` statements you need to provide a callback to minify method, e.g.:
```js
var CleanCSS = require('clean-css');
var source = '@import url(http://path/to/remote/styles);';
new CleanCSS().minify(source, function (errors, minified) {
// minified.styles
});
```
This is due to a fact, that, while local files can be read synchronously, remote resources can only be processed asynchronously.
If you don't provide a callback, then remote `@import`s will be left intact.
### How to use clean-css with build tools?
* [Broccoli](https://github.com/broccolijs/broccoli#broccoli): [broccoli-clean-css](https://github.com/shinnn/broccoli-clean-css)
* [Brunch](http://brunch.io/): [clean-css-brunch](https://github.com/brunch/clean-css-brunch)
* [Grunt](http://gruntjs.com): [grunt-contrib-cssmin](https://github.com/gruntjs/grunt-contrib-cssmin)
* [Gulp](http://gulpjs.com/): [gulp-minify-css](https://github.com/jonathanepollack/gulp-minify-css)
* [Gulp](http://gulpjs.com/): [using vinyl-map as a wrapper - courtesy of @sogko](https://github.com/jakubpawlowicz/clean-css/issues/342)
* [component-builder2](https://github.com/component/builder2.js): [builder-clean-css](https://github.com/poying/builder-clean-css)
* [Metalsmith](http://metalsmith.io): [metalsmith-clean-css](https://github.com/aymericbeaumet/metalsmith-clean-css)
* [Lasso](https://github.com/lasso-js/lasso): [lasso-clean-css](https://github.com/yomed/lasso-clean-css)
### What are the clean-css' dev commands?
First clone the source, then run:
* `npm run bench` for clean-css benchmarks (see [test/bench.js](https://github.com/jakubpawlowicz/clean-css/blob/master/test/bench.js) for details)
* `npm run browserify` to create the browser-ready clean-css version
* `npm run check` to check JS sources with [JSHint](https://github.com/jshint/jshint/)
* `npm test` for the test suite
## How to contribute to clean-css?
See [CONTRIBUTING.md](https://github.com/jakubpawlowicz/clean-css/blob/master/CONTRIBUTING.md).
## Tips & Tricks
### How to preserve a comment block?
Use the `/*!` notation instead of the standard one `/*`:
```css
/*!
Important comments included in minified output.
*/
```
### How to rebase relative image URLs?
Clean-css will handle it automatically for you (since version 1.1) in the following cases:
* When using the CLI:
1. Use an output path via `-o`/`--output` to rebase URLs as relative to the output file.
2. Use a root path via `-r`/`--root` to rebase URLs as absolute from the given root path.
3. If you specify both then `-r`/`--root` takes precendence.
* When using clean-css as a library:
1. Use a combination of `relativeTo` and `target` options for relative rebase (same as 1 in CLI).
2. Use a combination of `relativeTo` and `root` options for absolute rebase (same as 2 in CLI).
3. `root` takes precendence over `target` as in CLI.
### How to generate source maps?
Source maps are supported since version 3.0.
Additionally to mapping original CSS files, clean-css also supports input source maps, so minified styles can be mapped into their [Less](http://lesscss.org/) or [Sass](http://sass-lang.com/) sources directly.
Source maps are generated using [source-map](https://github.com/mozilla/source-map/) module from Mozilla.
#### Using CLI
To generate a source map, use `--source-map` switch, e.g.:
```
cleancss --source-map --output styles.min.css styles.css
```
Name of the output file is required, so a map file, named by adding `.map` suffix to output file name, can be created (styles.min.css.map in this case).
#### Using API
To generate a source map, use `sourceMap: true` option, e.g.:
```js
new CleanCSS({ sourceMap: true, target: pathToOutputDirectory })
.minify(source, function (minified) {
// access minified.sourceMap for SourceMapGenerator object
// see https://github.com/mozilla/source-map/#sourcemapgenerator for more details
// see https://github.com/jakubpawlowicz/clean-css/blob/master/bin/cleancss#L114 on how it's used in clean-css' CLI
});
```
Using API you can also pass an input source map directly:
```js
new CleanCSS({ sourceMap: inputSourceMapAsString, target: pathToOutputDirectory })
.minify(source, function (minified) {
// access minified.sourceMap to access SourceMapGenerator object
// see https://github.com/mozilla/source-map/#sourcemapgenerator for more details
// see https://github.com/jakubpawlowicz/clean-css/blob/master/bin/cleancss#L114 on how it's used in clean-css' CLI
});
```
Or even multiple input source maps at once (available since version 3.1):
```js
new CleanCSS({ sourceMap: true, target: pathToOutputDirectory }).minify({
'path/to/source/1': {
styles: '...styles...',
sourceMap: '...source-map...'
},
'path/to/source/2': {
styles: '...styles...',
sourceMap: '...source-map...'
}
}, function (minified) {
// access minified.sourceMap as above
});
```
### How to minify multiple files with API?
#### Passing an array
```js
new CleanCSS().minify(['path/to/file/one', 'path/to/file/two']);
```
#### Passing a hash
```js
new CleanCSS().minify({
'path/to/file/one': {
styles: 'contents of file one'
},
'path/to/file/two': {
styles: 'contents of file two'
}
});
```
### How to set a compatibility mode?
Compatibility settings are controlled by `--compatibility` switch (CLI) and `compatibility` option (library mode).
In both modes the following values are allowed:
* `'ie7'` - Internet Explorer 7 compatibility mode
* `'ie8'` - Internet Explorer 8 compatibility mode
* `''` or `'*'` (default) - Internet Explorer 9+ compatibility mode
Since clean-css 3 a fine grained control is available over
[those settings](https://github.com/jakubpawlowicz/clean-css/blob/master/lib/utils/compatibility.js),
with the following options available:
* `'[+-]colors.opacity'` - - turn on (+) / off (-) `rgba()` / `hsla()` declarations removal
* `'[+-]properties.backgroundClipMerging'` - turn on / off background-clip merging into shorthand
* `'[+-]properties.backgroundOriginMerging'` - turn on / off background-origin merging into shorthand
* `'[+-]properties.backgroundSizeMerging'` - turn on / off background-size merging into shorthand
* `'[+-]properties.colors'` - turn on / off any color optimizations
* `'[+-]properties.ieBangHack'` - turn on / off IE bang hack removal
* `'[+-]properties.iePrefixHack'` - turn on / off IE prefix hack removal
* `'[+-]properties.ieSuffixHack'` - turn on / off IE suffix hack removal
* `'[+-]properties.merging'` - turn on / off property merging based on understandability
* `'[+-]properties.spaceAfterClosingBrace'` - turn on / off removing space after closing brace - `url() no-repeat` into `url()no-repeat`
* `'[+-]properties.urlQuotes'` - turn on / off `url()` quoting
* `'[+-]properties.zeroUnits'` - turn on / off units removal after a `0` value
* `'[+-]selectors.adjacentSpace'` - turn on / off extra space before `nav` element
* `'[+-]selectors.ie7Hack'` - turn on / off IE7 selector hack removal (`*+html...`)
* `'[+-]selectors.special'` - a regular expression with all special, unmergeable selectors (leave it empty unless you know what you are doing)
* `'[+-]units.ch'` - turn on / off treating `ch` as a proper unit
* `'[+-]units.in'` - turn on / off treating `in` as a proper unit
* `'[+-]units.pc'` - turn on / off treating `pc` as a proper unit
* `'[+-]units.pt'` - turn on / off treating `pt` as a proper unit
* `'[+-]units.rem'` - turn on / off treating `rem` as a proper unit
* `'[+-]units.vh'` - turn on / off treating `vh` as a proper unit
* `'[+-]units.vm'` - turn on / off treating `vm` as a proper unit
* `'[+-]units.vmax'` - turn on / off treating `vmax` as a proper unit
* `'[+-]units.vmin'` - turn on / off treating `vmin` as a proper unit
* `'[+-]units.vm'` - turn on / off treating `vm` as a proper unit
For example, using `--compatibility 'ie8,+units.rem'` will ensure IE8 compatibility while enabling `rem` units so the following style `margin:0px 0rem` can be shortened to `margin:0`, while in pure IE8 mode it can't be.
To pass a single off (-) switch in CLI please use the following syntax `--compatibility *,-units.rem`.
In library mode you can also pass `compatibility` as a hash of options.
### What advanced optimizations are applied?
All advanced optimizations are dispatched [here](https://github.com/jakubpawlowicz/clean-css/blob/master/lib/selectors/advanced.js#L59), and this is what they do:
* `recursivelyOptimizeBlocks` - does all the following operations on a block (think `@media` or `@keyframe` at-rules);
* `recursivelyOptimizeProperties` - optimizes properties in rulesets and "flat at-rules" (like @font-face) by splitting them into components (e.g. `margin` into `margin-(*)`), optimizing, and rebuilding them back. You may want to use `shorthandCompacting` option to control whether you want to turn multiple (long-hand) properties into a shorthand ones;
* `removeDuplicates` - gets rid of duplicate rulesets with exactly the same set of properties (think of including the same Sass / Less partial twice for no good reason);
* `mergeAdjacent` - merges adjacent rulesets with the same selector or rules;
* `reduceNonAdjacent` - identifies which properties are overridden in same-selector non-adjacent rulesets, and removes them;
* `mergeNonAdjacentBySelector` - identifies same-selector non-adjacent rulesets which can be moved (!) to be merged, requires all intermediate rulesets to not redefine the moved properties, or if redefined to be either more coarse grained (e.g. `margin` vs `margin-top`) or have the same value;
* `mergeNonAdjacentByBody` - same as the one above but for same-rules non-adjacent rulesets;
* `restructure` - tries to reorganize different-selector different-rules rulesets so they take less space, e.g. `.one{padding:0}.two{margin:0}.one{margin-bottom:3px}` into `.two{margin:0}.one{padding:0;margin-bottom:3px}`;
* `removeDuplicateMediaQueries` - removes duplicated `@media` at-rules;
* `mergeMediaQueries` - merges non-adjacent `@media` at-rules by same rules as `mergeNonAdjacentBy*` above;
## Acknowledgments (sorted alphabetically)
* Anthony Barre ([@abarre](https://github.com/abarre)) for improvements to
`@import` processing, namely introducing the `--skip-import` /
`processImport` options.
* Simon Altschuler ([@altschuler](https://github.com/altschuler)) for fixing
`@import` processing inside comments.
* Isaac ([@facelessuser](https://github.com/facelessuser)) for pointing out
a flaw in clean-css' stateless mode.
* Jan Michael Alonzo ([@jmalonzo](https://github.com/jmalonzo)) for a patch
removing node.js' old `sys` package.
* Luke Page ([@lukeapage](https://github.com/lukeapage)) for suggestions and testing the source maps feature.
Plus everyone else involved in [#125](https://github.com/jakubpawlowicz/clean-css/issues/125) for pushing it forward.
* Timur Kristóf ([@Venemo](https://github.com/Venemo)) for an outstanding
contribution of advanced property optimizer for 2.2 release.
* Vincent Voyer ([@vvo](https://github.com/vvo)) for a patch with better
empty element regex and for inspiring us to do many performance improvements
in 0.4 release.
* [@XhmikosR](https://github.com/XhmikosR) for suggesting new features
(option to remove special comments and strip out URLs quotation) and
pointing out numerous improvements (JSHint, media queries).
## License
Clean-css is released under the [MIT License](https://github.com/jakubpawlowicz/clean-css/blob/master/LICENSE).
+184
View File
@@ -0,0 +1,184 @@
#!/usr/bin/env node
var fs = require('fs');
var path = require('path');
var CleanCSS = require('../index');
var commands = require('commander');
var packageConfig = fs.readFileSync(path.join(path.dirname(fs.realpathSync(process.argv[1])), '../package.json'));
var buildVersion = JSON.parse(packageConfig).version;
var isWindows = process.platform == 'win32';
var lineBreak = require('os').EOL;
// Specify commander options to parse command line params correctly
commands
.version(buildVersion, '-v, --version')
.usage('[options] source-file, [source-file, ...]')
.option('-b, --keep-line-breaks', 'Keep line breaks')
.option('-c, --compatibility [ie7|ie8]', 'Force compatibility mode (see Readme for advanced examples)')
.option('-d, --debug', 'Shows debug information (minification time & compression efficiency)')
.option('-o, --output [output-file]', 'Use [output-file] as output instead of STDOUT')
.option('-r, --root [root-path]', 'Set a root path to which resolve absolute @import rules')
.option('-s, --skip-import', 'Disable @import processing')
.option('-t, --timeout [seconds]', 'Per connection timeout when fetching remote @imports (defaults to 5 seconds)')
.option('--rounding-precision [n]', 'Rounds to `N` decimal places. Defaults to 2. -1 disables rounding', parseInt)
.option('--s0', 'Remove all special comments, i.e. /*! comment */')
.option('--s1', 'Remove all special comments but the first one')
.option('--semantic-merging', 'Enables unsafe mode by assuming BEM-like semantic stylesheets (warning, this may break your styling!)')
.option('--skip-advanced', 'Disable advanced optimizations - ruleset reordering & merging')
.option('--skip-aggressive-merging', 'Disable properties merging based on their order')
.option('--skip-import-from [rules]', 'Disable @import processing for specified rules', function (val) { return val.split(','); }, [])
.option('--skip-media-merging', 'Disable @media merging')
.option('--skip-rebase', 'Disable URLs rebasing')
.option('--skip-restructuring', 'Disable restructuring optimizations')
.option('--skip-shorthand-compacting', 'Disable shorthand compacting')
.option('--source-map', 'Enables building input\'s source map')
.option('--source-map-inline-sources', 'Enables inlining sources inside source maps');
commands.on('--help', function () {
console.log(' Examples:\n');
console.log(' %> cleancss one.css');
console.log(' %> cleancss -o one-min.css one.css');
if (isWindows) {
console.log(' %> type one.css two.css three.css | cleancss -o merged-and-minified.css');
} else {
console.log(' %> cat one.css two.css three.css | cleancss -o merged-and-minified.css');
console.log(' %> cat one.css two.css three.css | cleancss | gzip -9 -c > merged-minified-and-gzipped.css.gz');
}
console.log('');
process.exit();
});
commands.parse(process.argv);
// If no sensible data passed in just print help and exit
var fromStdin = !process.env.__DIRECT__ && !process.stdin.isTTY;
if (!fromStdin && commands.args.length === 0) {
commands.outputHelp();
return 0;
}
// Now coerce commands into CleanCSS configuration...
var options = {
advanced: commands.skipAdvanced ? false : true,
aggressiveMerging: commands.skipAggressiveMerging ? false : true,
compatibility: commands.compatibility,
debug: commands.debug,
inliner: commands.timeout ? { timeout: parseFloat(commands.timeout) * 1000 } : undefined,
keepBreaks: !!commands.keepLineBreaks,
keepSpecialComments: commands.s0 ? 0 : (commands.s1 ? 1 : '*'),
mediaMerging: commands.skipMediaMerging ? false : true,
processImport: commands.skipImport ? false : true,
processImportFrom: processImportFrom(commands.skipImportFrom),
rebase: commands.skipRebase ? false : true,
restructuring: commands.skipRestructuring ? false : true,
root: commands.root,
roundingPrecision: commands.roundingPrecision,
semanticMerging: commands.semanticMerging ? true : false,
shorthandCompacting: commands.skipShorthandCompacting ? false : true,
sourceMap: commands.sourceMap,
sourceMapInlineSources: commands.sourceMapInlineSources,
target: commands.output
};
if (options.root || commands.args.length > 0) {
var relativeTo = options.root || commands.args[0];
if (isRemote(relativeTo)) {
options.relativeTo = relativeTo;
} else {
var resolvedRelativeTo = path.resolve(relativeTo);
options.relativeTo = fs.statSync(resolvedRelativeTo).isFile() ?
path.dirname(resolvedRelativeTo) :
resolvedRelativeTo;
}
}
if (options.sourceMap && !options.target) {
outputFeedback(['Source maps will not be built because you have not specified an output file.'], true);
options.sourceMap = false;
}
// ... and do the magic!
if (commands.args.length > 0) {
minify(commands.args);
} else {
var stdin = process.openStdin();
stdin.setEncoding('utf-8');
var data = '';
stdin.on('data', function (chunk) {
data += chunk;
});
stdin.on('end', function () {
minify(data);
});
}
function isRemote(path) {
return /^https?:\/\//.test(path) || /^\/\//.test(path);
}
function processImportFrom(rules) {
if (rules.length === 0) {
return ['all'];
} else if (rules.length == 1 && rules[0] == 'all') {
return [];
} else {
return rules.map(function (rule) {
if (rule == 'local')
return 'remote';
else if (rule == 'remote')
return 'local';
else
return '!' + rule;
});
}
}
function minify(data) {
new CleanCSS(options).minify(data, function (errors, minified) {
if (options.debug) {
console.error('Original: %d bytes', minified.stats.originalSize);
console.error('Minified: %d bytes', minified.stats.minifiedSize);
console.error('Efficiency: %d%', ~~(minified.stats.efficiency * 10000) / 100.0);
console.error('Time spent: %dms', minified.stats.timeSpent);
}
outputFeedback(minified.errors, true);
outputFeedback(minified.warnings);
if (minified.errors.length > 0)
process.exit(1);
if (minified.sourceMap) {
var mapFilename = path.basename(options.target) + '.map';
output(minified.styles + lineBreak + '/*# sourceMappingURL=' + mapFilename + ' */');
outputMap(minified.sourceMap, mapFilename);
} else {
output(minified.styles);
}
});
}
function output(minified) {
if (options.target)
fs.writeFileSync(options.target, minified, 'utf8');
else
process.stdout.write(minified);
}
function outputMap(sourceMap, mapFilename) {
var mapPath = path.join(path.dirname(options.target), mapFilename);
fs.writeFileSync(mapPath, sourceMap.toString(), 'utf-8');
}
function outputFeedback(messages, isError) {
var prefix = isError ? '\x1B[31mERROR\x1B[39m:' : 'WARNING:';
messages.forEach(function (message) {
console.error('%s %s', prefix, message);
});
}
+1
View File
@@ -0,0 +1 @@
module.exports = require('./lib/clean');
+231
View File
@@ -0,0 +1,231 @@
/**
* Clean-css - https://github.com/jakubpawlowicz/clean-css
* Released under the terms of MIT license
*
* Copyright (C) 2015 JakubPawlowicz.com
*/
var ImportInliner = require('./imports/inliner');
var rebaseUrls = require('./urls/rebase');
var tokenize = require('./tokenizer/tokenize');
var simpleOptimize = require('./selectors/simple');
var advancedOptimize = require('./selectors/advanced');
var simpleStringify = require('./stringifier/simple');
var sourceMapStringify = require('./stringifier/source-maps');
var CommentsProcessor = require('./text/comments-processor');
var ExpressionsProcessor = require('./text/expressions-processor');
var FreeTextProcessor = require('./text/free-text-processor');
var UrlsProcessor = require('./text/urls-processor');
var Compatibility = require('./utils/compatibility');
var InputSourceMapTracker = require('./utils/input-source-map-tracker');
var SourceTracker = require('./utils/source-tracker');
var SourceReader = require('./utils/source-reader');
var Validator = require('./properties/validator');
var fs = require('fs');
var path = require('path');
var url = require('url');
var override = require('./utils/object').override;
var DEFAULT_TIMEOUT = 5000;
var CleanCSS = module.exports = function CleanCSS(options) {
options = options || {};
this.options = {
advanced: undefined === options.advanced ? true : !!options.advanced,
aggressiveMerging: undefined === options.aggressiveMerging ? true : !!options.aggressiveMerging,
benchmark: options.benchmark,
compatibility: new Compatibility(options.compatibility).toOptions(),
debug: options.debug,
explicitRoot: !!options.root,
explicitTarget: !!options.target,
inliner: options.inliner || {},
keepBreaks: options.keepBreaks || false,
keepSpecialComments: 'keepSpecialComments' in options ? options.keepSpecialComments : '*',
mediaMerging: undefined === options.mediaMerging ? true : !!options.mediaMerging,
processImport: undefined === options.processImport ? true : !!options.processImport,
processImportFrom: importOptionsFrom(options.processImportFrom),
rebase: undefined === options.rebase ? true : !!options.rebase,
relativeTo: options.relativeTo,
restructuring: undefined === options.restructuring ? true : !!options.restructuring,
root: options.root || process.cwd(),
roundingPrecision: options.roundingPrecision,
semanticMerging: undefined === options.semanticMerging ? false : !!options.semanticMerging,
shorthandCompacting: undefined === options.shorthandCompacting ? true : !!options.shorthandCompacting,
sourceMap: options.sourceMap,
sourceMapInlineSources: !!options.sourceMapInlineSources,
target: !options.target || missingDirectory(options.target) || presentDirectory(options.target) ? options.target : path.dirname(options.target)
};
this.options.inliner.timeout = this.options.inliner.timeout || DEFAULT_TIMEOUT;
this.options.inliner.request = override(
/* jshint camelcase: false */
proxyOptionsFrom(process.env.HTTP_PROXY || process.env.http_proxy),
this.options.inliner.request || {}
);
};
function importOptionsFrom(rules) {
return undefined === rules ? ['all'] : rules;
}
function missingDirectory(filepath) {
return !fs.existsSync(filepath) && !/\.css$/.test(filepath);
}
function presentDirectory(filepath) {
return fs.existsSync(filepath) && fs.statSync(filepath).isDirectory();
}
function proxyOptionsFrom(httpProxy) {
return httpProxy ?
{
hostname: url.parse(httpProxy).hostname,
port: parseInt(url.parse(httpProxy).port)
} :
{};
}
CleanCSS.prototype.minify = function (data, callback) {
var context = {
stats: {},
errors: [],
warnings: [],
options: this.options,
debug: this.options.debug,
localOnly: !callback,
sourceTracker: new SourceTracker(),
validator: new Validator(this.options.compatibility)
};
if (context.options.sourceMap)
context.inputSourceMapTracker = new InputSourceMapTracker(context);
context.sourceReader = new SourceReader(context, data);
data = context.sourceReader.toString();
if (context.options.processImport || data.indexOf('@shallow') > 0) {
// inline all imports
var runner = callback ?
process.nextTick :
function (callback) { return callback(); };
return runner(function () {
return new ImportInliner(context).process(data, {
localOnly: context.localOnly,
imports: context.options.processImportFrom,
whenDone: runMinifier(callback, context)
});
});
} else {
return runMinifier(callback, context)(data);
}
};
function runMinifier(callback, context) {
function whenSourceMapReady (data) {
data = context.options.debug ?
minifyWithDebug(context, data) :
minify(context, data);
data = withMetadata(context, data);
return callback ?
callback.call(null, context.errors.length > 0 ? context.errors : null, data) :
data;
}
return function (data) {
if (context.options.sourceMap) {
return context.inputSourceMapTracker.track(data, function () {
if (context.options.sourceMapInlineSources) {
return context.inputSourceMapTracker.resolveSources(function () {
return whenSourceMapReady(data);
});
} else {
return whenSourceMapReady(data);
}
});
} else {
return whenSourceMapReady(data);
}
};
}
function withMetadata(context, data) {
data.stats = context.stats;
data.errors = context.errors;
data.warnings = context.warnings;
return data;
}
function minifyWithDebug(context, data) {
var startedAt = process.hrtime();
context.stats.originalSize = context.sourceTracker.removeAll(data).length;
data = minify(context, data);
var elapsed = process.hrtime(startedAt);
context.stats.timeSpent = ~~(elapsed[0] * 1e3 + elapsed[1] / 1e6);
context.stats.efficiency = 1 - data.styles.length / context.stats.originalSize;
context.stats.minifiedSize = data.styles.length;
return data;
}
function benchmark(runner) {
return function (processor, action) {
var name = processor.constructor.name + '#' + action;
var start = process.hrtime();
runner(processor, action);
var itTook = process.hrtime(start);
console.log('%d ms: ' + name, 1000 * itTook[0] + itTook[1] / 1000000);
};
}
function minify(context, data) {
var options = context.options;
var commentsProcessor = new CommentsProcessor(context, options.keepSpecialComments, options.keepBreaks, options.sourceMap);
var expressionsProcessor = new ExpressionsProcessor(options.sourceMap);
var freeTextProcessor = new FreeTextProcessor(options.sourceMap);
var urlsProcessor = new UrlsProcessor(context, options.sourceMap, options.compatibility.properties.urlQuotes);
var stringify = options.sourceMap ? sourceMapStringify : simpleStringify;
var run = function (processor, action) {
data = typeof processor == 'function' ?
processor(data) :
processor[action](data);
};
if (options.benchmark)
run = benchmark(run);
run(commentsProcessor, 'escape');
run(expressionsProcessor, 'escape');
run(urlsProcessor, 'escape');
run(freeTextProcessor, 'escape');
function restoreEscapes(data, prefixContent) {
data = freeTextProcessor.restore(data, prefixContent);
data = urlsProcessor.restore(data);
data = options.rebase ? rebaseUrls(data, context) : data;
data = expressionsProcessor.restore(data);
return commentsProcessor.restore(data);
}
var tokens = tokenize(data, context);
simpleOptimize(tokens, options, context);
if (options.advanced)
advancedOptimize(tokens, options, context, true);
return stringify(tokens, options, restoreEscapes, context.inputSourceMapTracker);
}
@@ -0,0 +1,186 @@
var HexNameShortener = {};
var COLORS = {
aliceblue: '#f0f8ff',
antiquewhite: '#faebd7',
aqua: '#0ff',
aquamarine: '#7fffd4',
azure: '#f0ffff',
beige: '#f5f5dc',
bisque: '#ffe4c4',
black: '#000',
blanchedalmond: '#ffebcd',
blue: '#00f',
blueviolet: '#8a2be2',
brown: '#a52a2a',
burlywood: '#deb887',
cadetblue: '#5f9ea0',
chartreuse: '#7fff00',
chocolate: '#d2691e',
coral: '#ff7f50',
cornflowerblue: '#6495ed',
cornsilk: '#fff8dc',
crimson: '#dc143c',
cyan: '#0ff',
darkblue: '#00008b',
darkcyan: '#008b8b',
darkgoldenrod: '#b8860b',
darkgray: '#a9a9a9',
darkgreen: '#006400',
darkgrey: '#a9a9a9',
darkkhaki: '#bdb76b',
darkmagenta: '#8b008b',
darkolivegreen: '#556b2f',
darkorange: '#ff8c00',
darkorchid: '#9932cc',
darkred: '#8b0000',
darksalmon: '#e9967a',
darkseagreen: '#8fbc8f',
darkslateblue: '#483d8b',
darkslategray: '#2f4f4f',
darkslategrey: '#2f4f4f',
darkturquoise: '#00ced1',
darkviolet: '#9400d3',
deeppink: '#ff1493',
deepskyblue: '#00bfff',
dimgray: '#696969',
dimgrey: '#696969',
dodgerblue: '#1e90ff',
firebrick: '#b22222',
floralwhite: '#fffaf0',
forestgreen: '#228b22',
fuchsia: '#f0f',
gainsboro: '#dcdcdc',
ghostwhite: '#f8f8ff',
gold: '#ffd700',
goldenrod: '#daa520',
gray: '#808080',
green: '#008000',
greenyellow: '#adff2f',
grey: '#808080',
honeydew: '#f0fff0',
hotpink: '#ff69b4',
indianred: '#cd5c5c',
indigo: '#4b0082',
ivory: '#fffff0',
khaki: '#f0e68c',
lavender: '#e6e6fa',
lavenderblush: '#fff0f5',
lawngreen: '#7cfc00',
lemonchiffon: '#fffacd',
lightblue: '#add8e6',
lightcoral: '#f08080',
lightcyan: '#e0ffff',
lightgoldenrodyellow: '#fafad2',
lightgray: '#d3d3d3',
lightgreen: '#90ee90',
lightgrey: '#d3d3d3',
lightpink: '#ffb6c1',
lightsalmon: '#ffa07a',
lightseagreen: '#20b2aa',
lightskyblue: '#87cefa',
lightslategray: '#778899',
lightslategrey: '#778899',
lightsteelblue: '#b0c4de',
lightyellow: '#ffffe0',
lime: '#0f0',
limegreen: '#32cd32',
linen: '#faf0e6',
magenta: '#ff00ff',
maroon: '#800000',
mediumaquamarine: '#66cdaa',
mediumblue: '#0000cd',
mediumorchid: '#ba55d3',
mediumpurple: '#9370db',
mediumseagreen: '#3cb371',
mediumslateblue: '#7b68ee',
mediumspringgreen: '#00fa9a',
mediumturquoise: '#48d1cc',
mediumvioletred: '#c71585',
midnightblue: '#191970',
mintcream: '#f5fffa',
mistyrose: '#ffe4e1',
moccasin: '#ffe4b5',
navajowhite: '#ffdead',
navy: '#000080',
oldlace: '#fdf5e6',
olive: '#808000',
olivedrab: '#6b8e23',
orange: '#ffa500',
orangered: '#ff4500',
orchid: '#da70d6',
palegoldenrod: '#eee8aa',
palegreen: '#98fb98',
paleturquoise: '#afeeee',
palevioletred: '#db7093',
papayawhip: '#ffefd5',
peachpuff: '#ffdab9',
peru: '#cd853f',
pink: '#ffc0cb',
plum: '#dda0dd',
powderblue: '#b0e0e6',
purple: '#800080',
rebeccapurple: '#663399',
red: '#f00',
rosybrown: '#bc8f8f',
royalblue: '#4169e1',
saddlebrown: '#8b4513',
salmon: '#fa8072',
sandybrown: '#f4a460',
seagreen: '#2e8b57',
seashell: '#fff5ee',
sienna: '#a0522d',
silver: '#c0c0c0',
skyblue: '#87ceeb',
slateblue: '#6a5acd',
slategray: '#708090',
slategrey: '#708090',
snow: '#fffafa',
springgreen: '#00ff7f',
steelblue: '#4682b4',
tan: '#d2b48c',
teal: '#008080',
thistle: '#d8bfd8',
tomato: '#ff6347',
turquoise: '#40e0d0',
violet: '#ee82ee',
wheat: '#f5deb3',
white: '#fff',
whitesmoke: '#f5f5f5',
yellow: '#ff0',
yellowgreen: '#9acd32'
};
var toHex = {};
var toName = {};
for (var name in COLORS) {
var hex = COLORS[name];
if (name.length < hex.length)
toName[hex] = name;
else
toHex[name] = hex;
}
var toHexPattern = new RegExp('(^| |,|\\))(' + Object.keys(toHex).join('|') + ')( |,|\\)|$)', 'ig');
var toNamePattern = new RegExp('(' + Object.keys(toName).join('|') + ')([^a-f0-9]|$)', 'ig');
function hexConverter(match, prefix, colorValue, suffix) {
return prefix + toHex[colorValue.toLowerCase()] + suffix;
}
function nameConverter(match, colorValue, suffix) {
return toName[colorValue.toLowerCase()] + suffix;
}
HexNameShortener.shorten = function (value) {
var hasHex = value.indexOf('#') > -1;
var shortened = value.replace(toHexPattern, hexConverter);
if (shortened != value)
shortened = shortened.replace(toHexPattern, hexConverter);
return hasHex ? shortened.replace(toNamePattern, nameConverter) : shortened;
};
module.exports = HexNameShortener;
+67
View File
@@ -0,0 +1,67 @@
// HSL to RGB converter. Both methods adapted from:
// http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
function HSLColor(hue, saturation, lightness) {
this.hue = hue;
this.saturation = saturation;
this.lightness = lightness;
}
function hslToRgb(h, s, l) {
var r, g, b;
// normalize hue orientation b/w 0 and 360 degrees
h = h % 360;
if (h < 0)
h += 360;
h = ~~h / 360;
if (s < 0)
s = 0;
else if (s > 100)
s = 100;
s = ~~s / 100;
if (l < 0)
l = 0;
else if (l > 100)
l = 100;
l = ~~l / 100;
if (s === 0) {
r = g = b = l; // achromatic
} else {
var q = l < 0.5 ?
l * (1 + s) :
l + s - l * s;
var p = 2 * l - q;
r = hueToRgb(p, q, h + 1/3);
g = hueToRgb(p, q, h);
b = hueToRgb(p, q, h - 1/3);
}
return [~~(r * 255), ~~(g * 255), ~~(b * 255)];
}
function hueToRgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
HSLColor.prototype.toHex = function () {
var asRgb = hslToRgb(this.hue, this.saturation, this.lightness);
var redAsHex = asRgb[0].toString(16);
var greenAsHex = asRgb[1].toString(16);
var blueAsHex = asRgb[2].toString(16);
return '#' +
((redAsHex.length == 1 ? '0' : '') + redAsHex) +
((greenAsHex.length == 1 ? '0' : '') + greenAsHex) +
((blueAsHex.length == 1 ? '0' : '') + blueAsHex);
};
module.exports = HSLColor;
+16
View File
@@ -0,0 +1,16 @@
function RGB(red, green, blue) {
this.red = red;
this.green = green;
this.blue = blue;
}
RGB.prototype.toHex = function () {
var red = Math.max(0, Math.min(~~this.red, 255));
var green = Math.max(0, Math.min(~~this.green, 255));
var blue = Math.max(0, Math.min(~~this.blue, 255));
// Credit: Asen http://jsbin.com/UPUmaGOc/2/edit?js,console
return '#' + ('00000' + (red << 16 | green << 8 | blue).toString(16)).slice(-6);
};
module.exports = RGB;
+399
View File
@@ -0,0 +1,399 @@
var fs = require('fs');
var path = require('path');
var http = require('http');
var https = require('https');
var url = require('url');
var rewriteUrls = require('../urls/rewrite');
var split = require('../utils/split');
var override = require('../utils/object.js').override;
var MAP_MARKER = /\/\*# sourceMappingURL=(\S+) \*\//;
var REMOTE_RESOURCE = /^(https?:)?\/\//;
var NO_PROTOCOL_RESOURCE = /^\/\//;
function ImportInliner (context) {
this.outerContext = context;
}
ImportInliner.prototype.process = function (data, context) {
var root = this.outerContext.options.root;
context = override(context, {
baseRelativeTo: this.outerContext.options.relativeTo || root,
debug: this.outerContext.options.debug,
done: [],
errors: this.outerContext.errors,
left: [],
inliner: this.outerContext.options.inliner,
rebase: this.outerContext.options.rebase,
relativeTo: this.outerContext.options.relativeTo || root,
root: root,
sourceReader: this.outerContext.sourceReader,
sourceTracker: this.outerContext.sourceTracker,
warnings: this.outerContext.warnings,
visited: []
});
return importFrom(data, context);
};
function importFrom(data, context) {
if (context.shallow) {
context.shallow = false;
context.done.push(data);
return processNext(context);
}
var nextStart = 0;
var nextEnd = 0;
var cursor = 0;
var isComment = commentScanner(data);
for (; nextEnd < data.length;) {
nextStart = nextImportAt(data, cursor);
if (nextStart == -1)
break;
if (isComment(nextStart)) {
cursor = nextStart + 1;
continue;
}
nextEnd = data.indexOf(';', nextStart);
if (nextEnd == -1) {
cursor = data.length;
data = '';
break;
}
var noImportPart = data.substring(0, nextStart);
context.done.push(noImportPart);
context.left.unshift([data.substring(nextEnd + 1), override(context, { shallow: false })]);
context.afterContent = hasContent(noImportPart);
return inline(data, nextStart, nextEnd, context);
}
// no @import matched in current data
context.done.push(data);
return processNext(context);
}
function rebaseMap(data, source) {
return data.replace(MAP_MARKER, function (match, sourceMapUrl) {
return REMOTE_RESOURCE.test(sourceMapUrl) ?
match :
match.replace(sourceMapUrl, url.resolve(source, sourceMapUrl));
});
}
function nextImportAt(data, cursor) {
var nextLowerCase = data.indexOf('@import', cursor);
var nextUpperCase = data.indexOf('@IMPORT', cursor);
if (nextLowerCase > -1 && nextUpperCase == -1)
return nextLowerCase;
else if (nextLowerCase == -1 && nextUpperCase > -1)
return nextUpperCase;
else
return Math.min(nextLowerCase, nextUpperCase);
}
function processNext(context) {
return context.left.length > 0 ?
importFrom.apply(null, context.left.shift()) :
context.whenDone(context.done.join(''));
}
function commentScanner(data) {
var commentRegex = /(\/\*(?!\*\/)[\s\S]*?\*\/)/;
var lastStartIndex = 0;
var lastEndIndex = 0;
var noComments = false;
// test whether an index is located within a comment
return function scanner(idx) {
var comment;
var localStartIndex = 0;
var localEndIndex = 0;
var globalStartIndex = 0;
var globalEndIndex = 0;
// return if we know there are no more comments
if (noComments)
return false;
do {
// idx can be still within last matched comment (many @import statements inside one comment)
if (idx > lastStartIndex && idx < lastEndIndex)
return true;
comment = data.match(commentRegex);
if (!comment) {
noComments = true;
return false;
}
// get the indexes relative to the current data chunk
lastStartIndex = localStartIndex = comment.index;
localEndIndex = localStartIndex + comment[0].length;
// calculate the indexes relative to the full original data
globalEndIndex = localEndIndex + lastEndIndex;
globalStartIndex = globalEndIndex - comment[0].length;
// chop off data up to and including current comment block
data = data.substring(localEndIndex);
lastEndIndex = globalEndIndex;
} while (globalEndIndex < idx);
return globalEndIndex > idx && idx > globalStartIndex;
};
}
function hasContent(data) {
var isComment = commentScanner(data);
var firstContentIdx = -1;
while (true) {
firstContentIdx = data.indexOf('{', firstContentIdx + 1);
if (firstContentIdx == -1 || !isComment(firstContentIdx))
break;
}
return firstContentIdx > -1;
}
function inline(data, nextStart, nextEnd, context) {
context.shallow = data.indexOf('@shallow') > 0;
var importDeclaration = data
.substring(nextImportAt(data, nextStart) + '@import'.length + 1, nextEnd)
.replace(/@shallow\)$/, ')')
.trim();
var viaUrl = importDeclaration.indexOf('url(') === 0;
var urlStartsAt = viaUrl ? 4 : 0;
var isQuoted = /^['"]/.exec(importDeclaration.substring(urlStartsAt, urlStartsAt + 2));
var urlEndsAt = isQuoted ?
importDeclaration.indexOf(isQuoted[0], urlStartsAt + 1) :
split(importDeclaration, ' ')[0].length - (viaUrl ? 1 : 0);
var importedFile = importDeclaration
.substring(urlStartsAt, urlEndsAt)
.replace(/['"]/g, '')
.replace(/\)$/, '')
.trim();
var mediaQuery = importDeclaration
.substring(urlEndsAt + 1)
.replace(/^\)/, '')
.trim();
var isRemote = context.isRemote || REMOTE_RESOURCE.test(importedFile);
if (isRemote && (context.localOnly || !allowedResource(importedFile, true, context.imports))) {
if (context.afterContent || hasContent(context.done.join('')))
context.warnings.push('Ignoring remote @import of "' + importedFile + '" as no callback given.');
else
restoreImport(importedFile, mediaQuery, context);
return processNext(context);
}
if (!isRemote && !allowedResource(importedFile, false, context.imports)) {
if (context.afterImport)
context.warnings.push('Ignoring local @import of "' + importedFile + '" as after other inlined content.');
else
restoreImport(importedFile, mediaQuery, context);
return processNext(context);
}
if (!isRemote && context.afterContent) {
context.warnings.push('Ignoring local @import of "' + importedFile + '" as after other CSS content.');
return processNext(context);
}
var method = isRemote ? inlineRemoteResource : inlineLocalResource;
return method(importedFile, mediaQuery, context);
}
function allowedResource(importedFile, isRemote, rules) {
if (rules.length === 0)
return false;
if (isRemote && NO_PROTOCOL_RESOURCE.test(importedFile))
importedFile = 'http:' + importedFile;
var match = isRemote ?
url.parse(importedFile).host :
importedFile;
var allowed = true;
for (var i = 0; i < rules.length; i++) {
var rule = rules[i];
if (rule == 'all')
allowed = true;
else if (isRemote && rule == 'local')
allowed = false;
else if (isRemote && rule == 'remote')
allowed = true;
else if (!isRemote && rule == 'remote')
allowed = false;
else if (!isRemote && rule == 'local')
allowed = true;
else if (rule[0] == '!' && rule.substring(1) === match)
allowed = false;
}
return allowed;
}
function inlineRemoteResource(importedFile, mediaQuery, context) {
var importedUrl = REMOTE_RESOURCE.test(importedFile) ?
importedFile :
url.resolve(context.relativeTo, importedFile);
var originalUrl = importedUrl;
if (NO_PROTOCOL_RESOURCE.test(importedUrl))
importedUrl = 'http:' + importedUrl;
if (context.visited.indexOf(importedUrl) > -1)
return processNext(context);
if (context.debug)
console.error('Inlining remote stylesheet: ' + importedUrl);
context.visited.push(importedUrl);
var proxyProtocol = context.inliner.request.protocol || context.inliner.request.hostname;
var get =
((proxyProtocol && proxyProtocol.indexOf('https://') !== 0 ) ||
importedUrl.indexOf('http://') === 0) ?
http.get :
https.get;
var errorHandled = false;
function handleError(message) {
if (errorHandled)
return;
errorHandled = true;
context.errors.push('Broken @import declaration of "' + importedUrl + '" - ' + message);
restoreImport(importedUrl, mediaQuery, context);
process.nextTick(function () {
processNext(context);
});
}
var requestOptions = override(url.parse(importedUrl), context.inliner.request);
if (context.inliner.request.hostname !== undefined) {
//overwrite as we always expect a http proxy currently
requestOptions.protocol = context.inliner.request.protocol || 'http:';
requestOptions.path = requestOptions.href;
}
get(requestOptions, function (res) {
if (res.statusCode < 200 || res.statusCode > 399) {
return handleError('error ' + res.statusCode);
} else if (res.statusCode > 299) {
var movedUrl = url.resolve(importedUrl, res.headers.location);
return inlineRemoteResource(movedUrl, mediaQuery, context);
}
var chunks = [];
var parsedUrl = url.parse(importedUrl);
res.on('data', function (chunk) {
chunks.push(chunk.toString());
});
res.on('end', function () {
var importedData = chunks.join('');
if (context.rebase)
importedData = rewriteUrls(importedData, { toBase: originalUrl }, context);
context.sourceReader.trackSource(importedUrl, importedData);
importedData = context.sourceTracker.store(importedUrl, importedData);
importedData = rebaseMap(importedData, importedUrl);
if (mediaQuery.length > 0)
importedData = '@media ' + mediaQuery + '{' + importedData + '}';
context.afterImport = true;
var newContext = override(context, {
isRemote: true,
relativeTo: parsedUrl.protocol + '//' + parsedUrl.host + parsedUrl.pathname
});
process.nextTick(function () {
importFrom(importedData, newContext);
});
});
})
.on('error', function (res) {
handleError(res.message);
})
.on('timeout', function () {
handleError('timeout');
})
.setTimeout(context.inliner.timeout);
}
function inlineLocalResource(importedFile, mediaQuery, context) {
var relativeTo = importedFile[0] == '/' ?
context.root :
context.relativeTo;
var fullPath = path.resolve(path.join(relativeTo, importedFile));
if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isFile()) {
context.errors.push('Broken @import declaration of "' + importedFile + '"');
return processNext(context);
}
if (context.visited.indexOf(fullPath) > -1)
return processNext(context);
if (context.debug)
console.error('Inlining local stylesheet: ' + fullPath);
context.visited.push(fullPath);
var importRelativeTo = path.dirname(fullPath);
var importedData = fs.readFileSync(fullPath, 'utf8');
if (context.rebase) {
var rewriteOptions = {
relative: true,
fromBase: importRelativeTo,
toBase: context.baseRelativeTo
};
importedData = rewriteUrls(importedData, rewriteOptions, context);
}
var relativePath = path.relative(context.root, fullPath);
context.sourceReader.trackSource(relativePath, importedData);
importedData = context.sourceTracker.store(relativePath, importedData);
if (mediaQuery.length > 0)
importedData = '@media ' + mediaQuery + '{' + importedData + '}';
context.afterImport = true;
var newContext = override(context, {
relativeTo: importRelativeTo
});
return importFrom(importedData, newContext);
}
function restoreImport(importedUrl, mediaQuery, context) {
var restoredImport = '@import url(' + importedUrl + ')' + (mediaQuery.length > 0 ? ' ' + mediaQuery : '') + ';';
context.done.push(restoredImport);
}
module.exports = ImportInliner;
@@ -0,0 +1,335 @@
var wrapSingle = require('./wrap-for-optimizing').single;
var InvalidPropertyError = require('./invalid-property-error');
var split = require('../utils/split');
var MULTIPLEX_SEPARATOR = ',';
function _colorFilter(validator) {
return function (value) {
return value[0] == 'invert' || validator.isValidColor(value[0]);
};
}
function _styleFilter(validator) {
return function (value) {
return value[0] != 'inherit' && validator.isValidStyle(value[0]) && !validator.isValidColorValue(value[0]);
};
}
function _wrapDefault(name, property, compactable) {
var descriptor = compactable[name];
if (descriptor.doubleValues && descriptor.defaultValue.length == 2)
return wrapSingle([[name, property.important], [descriptor.defaultValue[0]], [descriptor.defaultValue[1]]]);
else if (descriptor.doubleValues && descriptor.defaultValue.length == 1)
return wrapSingle([[name, property.important], [descriptor.defaultValue[0]]]);
else
return wrapSingle([[name, property.important], [descriptor.defaultValue]]);
}
function _widthFilter(validator) {
return function (value) {
return value[0] != 'inherit' && validator.isValidWidth(value[0]) && !validator.isValidStyleKeyword(value[0]) && !validator.isValidColorValue(value[0]);
};
}
function background(property, compactable, validator) {
var image = _wrapDefault('background-image', property, compactable);
var position = _wrapDefault('background-position', property, compactable);
var size = _wrapDefault('background-size', property, compactable);
var repeat = _wrapDefault('background-repeat', property, compactable);
var attachment = _wrapDefault('background-attachment', property, compactable);
var origin = _wrapDefault('background-origin', property, compactable);
var clip = _wrapDefault('background-clip', property, compactable);
var color = _wrapDefault('background-color', property, compactable);
var components = [image, position, size, repeat, attachment, origin, clip, color];
var values = property.value;
var positionSet = false;
var clipSet = false;
var originSet = false;
var repeatSet = false;
if (property.value.length == 1 && property.value[0][0] == 'inherit') {
// NOTE: 'inherit' is not a valid value for background-attachment
color.value = image.value = repeat.value = position.value = size.value = origin.value = clip.value = property.value;
return components;
}
for (var i = values.length - 1; i >= 0; i--) {
var value = values[i];
if (validator.isValidBackgroundAttachment(value[0])) {
attachment.value = [value];
} else if (validator.isValidBackgroundBox(value[0])) {
if (clipSet) {
origin.value = [value];
originSet = true;
} else {
clip.value = [value];
clipSet = true;
}
} else if (validator.isValidBackgroundRepeat(value[0])) {
if (repeatSet) {
repeat.value.unshift(value);
} else {
repeat.value = [value];
repeatSet = true;
}
} else if (validator.isValidBackgroundPositionPart(value[0]) || validator.isValidBackgroundSizePart(value[0])) {
if (i > 0) {
var previousValue = values[i - 1];
if (previousValue[0].indexOf('/') > 0) {
var twoParts = split(previousValue[0], '/');
// NOTE: we do this slicing as value may contain metadata too, like for source maps
size.value = [[twoParts.pop()].concat(previousValue.slice(1)), value];
values[i - 1] = [twoParts.pop()].concat(previousValue.slice(1));
} else if (i > 1 && values[i - 2][0] == '/') {
size.value = [previousValue, value];
i -= 2;
} else if (previousValue[0] == '/') {
size.value = [value];
} else {
if (!positionSet)
position.value = [];
position.value.unshift(value);
positionSet = true;
}
} else {
if (!positionSet)
position.value = [];
position.value.unshift(value);
positionSet = true;
}
} else if (validator.isValidBackgroundPositionAndSize(value[0])) {
var sizeValue = split(value[0], '/');
// NOTE: we do this slicing as value may contain metadata too, like for source maps
size.value = [[sizeValue.pop()].concat(value.slice(1))];
position.value = [[sizeValue.pop()].concat(value.slice(1))];
} else if ((color.value[0][0] == compactable[color.name].defaultValue || color.value[0][0] == 'none') && validator.isValidColor(value[0])) {
color.value = [value];
} else if (validator.isValidUrl(value[0]) || validator.isValidFunction(value[0])) {
image.value = [value];
}
}
if (clipSet && !originSet)
origin.value = clip.value.slice(0);
return components;
}
function borderRadius(property, compactable) {
var values = property.value;
var splitAt = -1;
for (var i = 0, l = values.length; i < l; i++) {
if (values[i][0] == '/') {
splitAt = i;
break;
}
}
if (splitAt === 0 || splitAt === values.length - 1) {
throw new InvalidPropertyError('Invalid border-radius value.');
}
var target = _wrapDefault(property.name, property, compactable);
target.value = splitAt > -1 ?
values.slice(0, splitAt) :
values.slice(0);
target.components = fourValues(target, compactable);
var remainder = _wrapDefault(property.name, property, compactable);
remainder.value = splitAt > -1 ?
values.slice(splitAt + 1) :
values.slice(0);
remainder.components = fourValues(remainder, compactable);
for (var j = 0; j < 4; j++) {
target.components[j].multiplex = true;
target.components[j].value = target.components[j].value.concat(remainder.components[j].value);
}
return target.components;
}
function fourValues(property, compactable) {
var componentNames = compactable[property.name].components;
var components = [];
var value = property.value;
if (value.length < 1)
return [];
if (value.length < 2)
value[1] = value[0].slice(0);
if (value.length < 3)
value[2] = value[0].slice(0);
if (value.length < 4)
value[3] = value[1].slice(0);
for (var i = componentNames.length - 1; i >= 0; i--) {
var component = wrapSingle([[componentNames[i], property.important]]);
component.value = [value[i]];
components.unshift(component);
}
return components;
}
function multiplex(splitWith) {
return function (property, compactable, validator) {
var splitsAt = [];
var values = property.value;
var i, j, l, m;
// find split commas
for (i = 0, l = values.length; i < l; i++) {
if (values[i][0] == ',')
splitsAt.push(i);
}
if (splitsAt.length === 0)
return splitWith(property, compactable, validator);
var splitComponents = [];
// split over commas, and into components
for (i = 0, l = splitsAt.length; i <= l; i++) {
var from = i === 0 ? 0 : splitsAt[i - 1] + 1;
var to = i < l ? splitsAt[i] : values.length;
var _property = _wrapDefault(property.name, property, compactable);
_property.value = values.slice(from, to);
splitComponents.push(splitWith(_property, compactable, validator));
}
var components = splitComponents[0];
// group component values from each split
for (i = 0, l = components.length; i < l; i++) {
components[i].multiplex = true;
for (j = 1, m = splitComponents.length; j < m; j++) {
components[i].value.push([MULTIPLEX_SEPARATOR]);
Array.prototype.push.apply(components[i].value, splitComponents[j][i].value);
}
}
return components;
};
}
function listStyle(property, compactable, validator) {
var type = _wrapDefault('list-style-type', property, compactable);
var position = _wrapDefault('list-style-position', property, compactable);
var image = _wrapDefault('list-style-image', property, compactable);
var components = [type, position, image];
if (property.value.length == 1 && property.value[0][0] == 'inherit') {
type.value = position.value = image.value = [property.value[0]];
return components;
}
var values = property.value.slice(0);
var total = values.length;
var index = 0;
// `image` first...
for (index = 0, total = values.length; index < total; index++) {
if (validator.isValidUrl(values[index][0]) || values[index][0] == '0') {
image.value = [values[index]];
values.splice(index, 1);
break;
}
}
// ... then `type`...
for (index = 0, total = values.length; index < total; index++) {
if (validator.isValidListStyleType(values[index][0])) {
type.value = [values[index]];
values.splice(index, 1);
break;
}
}
// ... and what's left is a `position`
if (values.length > 0 && validator.isValidListStylePosition(values[0][0]))
position.value = [values[0]];
return components;
}
function widthStyleColor(property, compactable, validator) {
var descriptor = compactable[property.name];
var components = [
_wrapDefault(descriptor.components[0], property, compactable),
_wrapDefault(descriptor.components[1], property, compactable),
_wrapDefault(descriptor.components[2], property, compactable)
];
var color, style, width;
for (var i = 0; i < 3; i++) {
var component = components[i];
if (component.name.indexOf('color') > 0)
color = component;
else if (component.name.indexOf('style') > 0)
style = component;
else
width = component;
}
if ((property.value.length == 1 && property.value[0][0] == 'inherit') ||
(property.value.length == 3 && property.value[0][0] == 'inherit' && property.value[1][0] == 'inherit' && property.value[2][0] == 'inherit')) {
color.value = style.value = width.value = [property.value[0]];
return components;
}
var values = property.value.slice(0);
var match, matches;
// NOTE: usually users don't follow the required order of parts in this shorthand,
// so we'll try to parse it caring as little about order as possible
if (values.length > 0) {
matches = values.filter(_widthFilter(validator));
match = matches.length > 1 && (matches[0][0] == 'none' || matches[0][0] == 'auto') ? matches[1] : matches[0];
if (match) {
width.value = [match];
values.splice(values.indexOf(match), 1);
}
}
if (values.length > 0) {
match = values.filter(_styleFilter(validator))[0];
if (match) {
style.value = [match];
values.splice(values.indexOf(match), 1);
}
}
if (values.length > 0) {
match = values.filter(_colorFilter(validator))[0];
if (match) {
color.value = [match];
values.splice(values.indexOf(match), 1);
}
}
return components;
}
module.exports = {
background: background,
border: widthStyleColor,
borderRadius: borderRadius,
fourValues: fourValues,
listStyle: listStyle,
multiplex: multiplex,
outline: widthStyleColor
};
@@ -0,0 +1,142 @@
// Functions that decide what value can override what.
// The main purpose is to disallow removing CSS fallbacks.
// A separate implementation is needed for every different kind of CSS property.
// -----
// The generic idea is that properties that have wider browser support are 'more understandable'
// than others and that 'less understandable' values can't override more understandable ones.
// Use when two tokens of the same property can always be merged
function always() {
return true;
}
function alwaysButIntoFunction(property1, property2, validator) {
var value1 = property1.value[0][0];
var value2 = property2.value[0][0];
var validFunction1 = validator.isValidFunction(value1);
var validFunction2 = validator.isValidFunction(value2);
if (validFunction1 && validFunction2) {
return validator.areSameFunction(value1, value2);
} else if (!validFunction1 && validFunction2) {
return false;
} else {
return true;
}
}
function backgroundImage(property1, property2, validator) {
// The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa
// Understandability: (none | url | inherit) > (same function) > (same value)
// (none | url)
var image1 = property1.value[0][0];
var image2 = property2.value[0][0];
if (image2 == 'none' || image2 == 'inherit' || validator.isValidUrl(image2))
return true;
if (image1 == 'none' || image1 == 'inherit' || validator.isValidUrl(image1))
return false;
// Functions with the same name can override each other; same values can override each other
return sameFunctionOrValue(property1, property2, validator);
}
function border(property1, property2, validator) {
return color(property1.components[2], property2.components[2], validator);
}
// Use for color properties (color, background-color, border-color, etc.)
function color(property1, property2, validator) {
// The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa
// Understandability: (hex | named) > (rgba | hsla) > (same function name) > anything else
// NOTE: at this point rgb and hsl are replaced by hex values by clean-css
var color1 = property1.value[0][0];
var color2 = property2.value[0][0];
if (!validator.colorOpacity && (validator.isValidRgbaColor(color1) || validator.isValidHslaColor(color1)))
return false;
if (!validator.colorOpacity && (validator.isValidRgbaColor(color2) || validator.isValidHslaColor(color2)))
return false;
// (hex | named)
if (validator.isValidNamedColor(color2) || validator.isValidHexColor(color2))
return true;
if (validator.isValidNamedColor(color1) || validator.isValidHexColor(color1))
return false;
// (rgba|hsla)
if (validator.isValidRgbaColor(color2) || validator.isValidHslaColor(color2))
return true;
if (validator.isValidRgbaColor(color1) || validator.isValidHslaColor(color1))
return false;
// Functions with the same name can override each other; same values can override each other
return sameFunctionOrValue(property1, property2, validator);
}
function twoOptionalFunctions(property1, property2, validator) {
var value1 = property1.value[0][0];
var value2 = property2.value[0][0];
return !(validator.isValidFunction(value1) ^ validator.isValidFunction(value2));
}
function sameValue(property1, property2) {
var value1 = property1.value[0][0];
var value2 = property2.value[0][0];
return value1 === value2;
}
function sameFunctionOrValue(property1, property2, validator) {
var value1 = property1.value[0][0];
var value2 = property2.value[0][0];
// Functions with the same name can override each other
if (validator.areSameFunction(value1, value2))
return true;
return value1 === value2;
}
// Use for properties containing CSS units (margin-top, padding-left, etc.)
function unit(property1, property2, validator) {
// The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa
// Understandability: (unit without functions) > (same functions | standard functions) > anything else
// NOTE: there is no point in having different vendor-specific functions override each other or standard functions,
// or having standard functions override vendor-specific functions, but standard functions can override each other
// NOTE: vendor-specific property values are not taken into consideration here at the moment
var value1 = property1.value[0][0];
var value2 = property2.value[0][0];
if (validator.isValidAndCompatibleUnitWithoutFunction(value1) && !validator.isValidAndCompatibleUnitWithoutFunction(value2))
return false;
if (validator.isValidUnitWithoutFunction(value2))
return true;
if (validator.isValidUnitWithoutFunction(value1))
return false;
// Standard non-vendor-prefixed functions can override each other
if (validator.isValidFunctionWithoutVendorPrefix(value2) && validator.isValidFunctionWithoutVendorPrefix(value1)) {
return true;
}
// Functions with the same name can override each other; same values can override each other
return sameFunctionOrValue(property1, property2, validator);
}
module.exports = {
always: always,
alwaysButIntoFunction: alwaysButIntoFunction,
backgroundImage: backgroundImage,
border: border,
color: color,
sameValue: sameValue,
sameFunctionOrValue: sameFunctionOrValue,
twoOptionalFunctions: twoOptionalFunctions,
unit: unit
};
+26
View File
@@ -0,0 +1,26 @@
var wrapSingle = require('./wrap-for-optimizing').single;
function deep(property) {
var cloned = shallow(property);
for (var i = property.components.length - 1; i >= 0; i--) {
var component = shallow(property.components[i]);
component.value = property.components[i].value.slice(0);
cloned.components.unshift(component);
}
cloned.dirty = true;
cloned.value = property.value.slice(0);
return cloned;
}
function shallow(property) {
var cloned = wrapSingle([[property.name, property.important, property.hack]]);
cloned.unused = false;
return cloned;
}
module.exports = {
deep: deep,
shallow: shallow
};
@@ -0,0 +1,285 @@
// Contains the interpretation of CSS properties, as used by the property optimizer
var breakUp = require('./break-up');
var canOverride = require('./can-override');
var restore = require('./restore');
// Properties to process
// Extend this object in order to add support for more properties in the optimizer.
//
// Each key in this object represents a CSS property and should be an object.
// Such an object contains properties that describe how the represented CSS property should be handled.
// Possible options:
//
// * components: array (Only specify for shorthand properties.)
// Contains the names of the granular properties this shorthand compacts.
//
// * canOverride: function (Default is canOverride.sameValue - meaning that they'll only be merged if they have the same value.)
// Returns whether two tokens of this property can be merged with each other.
// This property has no meaning for shorthands.
//
// * defaultValue: string
// Specifies the default value of the property according to the CSS standard.
// For shorthand, this is used when every component is set to its default value, therefore it should be the shortest possible default value of all the components.
//
// * shortestValue: string
// Specifies the shortest possible value the property can possibly have.
// (Falls back to defaultValue if unspecified.)
//
// * breakUp: function (Only specify for shorthand properties.)
// Breaks the shorthand up to its components.
//
// * restore: function (Only specify for shorthand properties.)
// Puts the shorthand together from its components.
//
var compactable = {
'color': {
canOverride: canOverride.color,
defaultValue: 'transparent',
shortestValue: 'red'
},
'background': {
components: [
'background-image',
'background-position',
'background-size',
'background-repeat',
'background-attachment',
'background-origin',
'background-clip',
'background-color'
],
breakUp: breakUp.multiplex(breakUp.background),
defaultValue: '0 0',
restore: restore.multiplex(restore.background),
shortestValue: '0',
shorthand: true
},
'background-clip': {
canOverride: canOverride.always,
defaultValue: 'border-box',
shortestValue: 'border-box'
},
'background-color': {
canOverride: canOverride.color,
defaultValue: 'transparent',
multiplexLastOnly: true,
nonMergeableValue: 'none',
shortestValue: 'red'
},
'background-image': {
canOverride: canOverride.backgroundImage,
defaultValue: 'none'
},
'background-origin': {
canOverride: canOverride.always,
defaultValue: 'padding-box',
shortestValue: 'border-box'
},
'background-repeat': {
canOverride: canOverride.always,
defaultValue: ['repeat'],
doubleValues: true
},
'background-position': {
canOverride: canOverride.alwaysButIntoFunction,
defaultValue: ['0', '0'],
doubleValues: true,
shortestValue: '0'
},
'background-size': {
canOverride: canOverride.alwaysButIntoFunction,
defaultValue: ['auto'],
doubleValues: true,
shortestValue: '0 0'
},
'background-attachment': {
canOverride: canOverride.always,
defaultValue: 'scroll'
},
'border': {
breakUp: breakUp.border,
canOverride: canOverride.border,
components: [
'border-width',
'border-style',
'border-color'
],
defaultValue: 'none',
restore: restore.withoutDefaults,
shorthand: true
},
'border-color': {
canOverride: canOverride.color,
defaultValue: 'none',
shorthand: true
},
'border-style': {
canOverride: canOverride.always,
defaultValue: 'none',
shorthand: true
},
'border-width': {
canOverride: canOverride.unit,
defaultValue: 'medium',
shortestValue: '0',
shorthand: true
},
'list-style': {
components: [
'list-style-type',
'list-style-position',
'list-style-image'
],
canOverride: canOverride.always,
breakUp: breakUp.listStyle,
restore: restore.withoutDefaults,
defaultValue: 'outside', // can't use 'disc' because that'd override default 'decimal' for <ol>
shortestValue: 'none',
shorthand: true
},
'list-style-type' : {
canOverride: canOverride.always,
defaultValue: '__hack',
// NOTE: we can't tell the real default value here, it's 'disc' for <ul> and 'decimal' for <ol>
// -- this is a hack, but it doesn't matter because this value will be either overridden or it will disappear at the final step anyway
shortestValue: 'none'
},
'list-style-position' : {
canOverride: canOverride.always,
defaultValue: 'outside',
shortestValue: 'inside'
},
'list-style-image' : {
canOverride: canOverride.always,
defaultValue: 'none'
},
'outline': {
components: [
'outline-color',
'outline-style',
'outline-width'
],
breakUp: breakUp.outline,
restore: restore.withoutDefaults,
defaultValue: '0',
shorthand: true
},
'outline-color': {
canOverride: canOverride.color,
defaultValue: 'invert',
shortestValue: 'red'
},
'outline-style': {
canOverride: canOverride.always,
defaultValue: 'none'
},
'outline-width': {
canOverride: canOverride.unit,
defaultValue: 'medium',
shortestValue: '0'
},
'-moz-transform': {
canOverride: canOverride.sameFunctionOrValue
},
'-ms-transform': {
canOverride: canOverride.sameFunctionOrValue
},
'-webkit-transform': {
canOverride: canOverride.sameFunctionOrValue
},
'transform': {
canOverride: canOverride.sameFunctionOrValue
}
};
var addFourValueShorthand = function (prop, components, options) {
options = options || {};
compactable[prop] = {
canOverride: options.canOverride,
components: components,
breakUp: options.breakUp || breakUp.fourValues,
defaultValue: options.defaultValue || '0',
restore: options.restore || restore.fourValues,
shortestValue: options.shortestValue,
shorthand: true
};
for (var i = 0; i < components.length; i++) {
compactable[components[i]] = {
breakUp: options.breakUp || breakUp.fourValues,
canOverride: options.canOverride || canOverride.unit,
defaultValue: options.defaultValue || '0',
shortestValue: options.shortestValue
};
}
};
['', '-moz-', '-o-'].forEach(function (prefix) {
addFourValueShorthand(prefix + 'border-radius', [
prefix + 'border-top-left-radius',
prefix + 'border-top-right-radius',
prefix + 'border-bottom-right-radius',
prefix + 'border-bottom-left-radius'
], {
breakUp: breakUp.borderRadius,
restore: restore.borderRadius
});
});
addFourValueShorthand('border-color', [
'border-top-color',
'border-right-color',
'border-bottom-color',
'border-left-color'
], {
breakUp: breakUp.fourValues,
canOverride: canOverride.color,
defaultValue: 'none',
shortestValue: 'red'
});
addFourValueShorthand('border-style', [
'border-top-style',
'border-right-style',
'border-bottom-style',
'border-left-style'
], {
breakUp: breakUp.fourValues,
canOverride: canOverride.always,
defaultValue: 'none'
});
addFourValueShorthand('border-width', [
'border-top-width',
'border-right-width',
'border-bottom-width',
'border-left-width'
], {
defaultValue: 'medium',
shortestValue: '0'
});
addFourValueShorthand('padding', [
'padding-top',
'padding-right',
'padding-bottom',
'padding-left'
]);
addFourValueShorthand('margin', [
'margin-top',
'margin-right',
'margin-bottom',
'margin-left'
]);
// Adds `componentOf` field to all longhands
for (var property in compactable) {
if (compactable[property].shorthand) {
for (var i = 0, l = compactable[property].components.length; i < l; i++) {
compactable[compactable[property].components[i]].componentOf = property;
}
}
}
module.exports = compactable;
@@ -0,0 +1,28 @@
var shallowClone = require('./clone').shallow;
var MULTIPLEX_SEPARATOR = ',';
function everyCombination(fn, left, right, validator) {
var samePositon = !left.shorthand && !right.shorthand && !left.multiplex && !right.multiplex;
var _left = shallowClone(left);
var _right = shallowClone(right);
for (var i = 0, l = left.value.length; i < l; i++) {
for (var j = 0, m = right.value.length; j < m; j++) {
if (left.value[i][0] == MULTIPLEX_SEPARATOR || right.value[j][0] == MULTIPLEX_SEPARATOR)
continue;
if (samePositon && i != j)
continue;
_left.value = [left.value[i]];
_right.value = [right.value[j]];
if (!fn(_left, _right, validator))
return false;
}
}
return true;
}
module.exports = everyCombination;
@@ -0,0 +1,10 @@
function hasInherit(property) {
for (var i = property.value.length - 1; i >= 0; i--) {
if (property.value[i][0] == 'inherit')
return true;
}
return false;
}
module.exports = hasInherit;
@@ -0,0 +1,10 @@
function InvalidPropertyError(message) {
this.name = 'InvalidPropertyError';
this.message = message;
this.stack = (new Error()).stack;
}
InvalidPropertyError.prototype = Object.create(Error.prototype);
InvalidPropertyError.prototype.constructor = InvalidPropertyError;
module.exports = InvalidPropertyError;
@@ -0,0 +1,215 @@
var compactable = require('./compactable');
var wrapForOptimizing = require('./wrap-for-optimizing').all;
var populateComponents = require('./populate-components');
var compactOverrides = require('./override-compactor');
var compactShorthands = require('./shorthand-compactor');
var removeUnused = require('./remove-unused');
var restoreFromOptimizing = require('./restore-from-optimizing');
var stringifyProperty = require('../stringifier/one-time').property;
var shorthands = {
'animation-delay': ['animation'],
'animation-direction': ['animation'],
'animation-duration': ['animation'],
'animation-fill-mode': ['animation'],
'animation-iteration-count': ['animation'],
'animation-name': ['animation'],
'animation-play-state': ['animation'],
'animation-timing-function': ['animation'],
'-moz-animation-delay': ['-moz-animation'],
'-moz-animation-direction': ['-moz-animation'],
'-moz-animation-duration': ['-moz-animation'],
'-moz-animation-fill-mode': ['-moz-animation'],
'-moz-animation-iteration-count': ['-moz-animation'],
'-moz-animation-name': ['-moz-animation'],
'-moz-animation-play-state': ['-moz-animation'],
'-moz-animation-timing-function': ['-moz-animation'],
'-o-animation-delay': ['-o-animation'],
'-o-animation-direction': ['-o-animation'],
'-o-animation-duration': ['-o-animation'],
'-o-animation-fill-mode': ['-o-animation'],
'-o-animation-iteration-count': ['-o-animation'],
'-o-animation-name': ['-o-animation'],
'-o-animation-play-state': ['-o-animation'],
'-o-animation-timing-function': ['-o-animation'],
'-webkit-animation-delay': ['-webkit-animation'],
'-webkit-animation-direction': ['-webkit-animation'],
'-webkit-animation-duration': ['-webkit-animation'],
'-webkit-animation-fill-mode': ['-webkit-animation'],
'-webkit-animation-iteration-count': ['-webkit-animation'],
'-webkit-animation-name': ['-webkit-animation'],
'-webkit-animation-play-state': ['-webkit-animation'],
'-webkit-animation-timing-function': ['-webkit-animation'],
'border-color': ['border'],
'border-style': ['border'],
'border-width': ['border'],
'border-bottom': ['border'],
'border-bottom-color': ['border-bottom', 'border-color', 'border'],
'border-bottom-style': ['border-bottom', 'border-style', 'border'],
'border-bottom-width': ['border-bottom', 'border-width', 'border'],
'border-left': ['border'],
'border-left-color': ['border-left', 'border-color', 'border'],
'border-left-style': ['border-left', 'border-style', 'border'],
'border-left-width': ['border-left', 'border-width', 'border'],
'border-right': ['border'],
'border-right-color': ['border-right', 'border-color', 'border'],
'border-right-style': ['border-right', 'border-style', 'border'],
'border-right-width': ['border-right', 'border-width', 'border'],
'border-top': ['border'],
'border-top-color': ['border-top', 'border-color', 'border'],
'border-top-style': ['border-top', 'border-style', 'border'],
'border-top-width': ['border-top', 'border-width', 'border'],
'font-family': ['font'],
'font-size': ['font'],
'font-style': ['font'],
'font-variant': ['font'],
'font-weight': ['font'],
'transition-delay': ['transition'],
'transition-duration': ['transition'],
'transition-property': ['transition'],
'transition-timing-function': ['transition'],
'-moz-transition-delay': ['-moz-transition'],
'-moz-transition-duration': ['-moz-transition'],
'-moz-transition-property': ['-moz-transition'],
'-moz-transition-timing-function': ['-moz-transition'],
'-o-transition-delay': ['-o-transition'],
'-o-transition-duration': ['-o-transition'],
'-o-transition-property': ['-o-transition'],
'-o-transition-timing-function': ['-o-transition'],
'-webkit-transition-delay': ['-webkit-transition'],
'-webkit-transition-duration': ['-webkit-transition'],
'-webkit-transition-property': ['-webkit-transition'],
'-webkit-transition-timing-function': ['-webkit-transition']
};
function _optimize(properties, mergeAdjacent, aggressiveMerging, validator) {
var overrideMapping = {};
var lastName = null;
var lastProperty;
var j;
function mergeablePosition(position) {
if (mergeAdjacent === false || mergeAdjacent === true)
return mergeAdjacent;
return mergeAdjacent.indexOf(position) > -1;
}
function sameValue(position) {
var left = properties[position - 1];
var right = properties[position];
return stringifyProperty(left.all, left.position) == stringifyProperty(right.all, right.position);
}
propertyLoop:
for (var position = 0, total = properties.length; position < total; position++) {
var property = properties[position];
var _name = (property.name == '-ms-filter' || property.name == 'filter') ?
(lastName == 'background' || lastName == 'background-image' ? lastName : property.name) :
property.name;
var isImportant = property.important;
var isHack = property.hack;
if (property.unused)
continue;
if (position > 0 && lastProperty && _name == lastName && isImportant == lastProperty.important && isHack == lastProperty.hack && sameValue(position) && !lastProperty.unused) {
property.unused = true;
continue;
}
// comment is necessary - we assume that if two properties are one after another
// then it is intentional way of redefining property which may not be widely supported
// e.g. a{display:inline-block;display:-moz-inline-box}
// however if `mergeablePosition` yields true then the rule does not apply
// (e.g merging two adjacent selectors: `a{display:block}a{display:block}`)
if (_name in overrideMapping && (aggressiveMerging && _name != lastName || mergeablePosition(position))) {
var toOverridePositions = overrideMapping[_name];
var canOverride = compactable[_name] && compactable[_name].canOverride;
var anyRemoved = false;
for (j = toOverridePositions.length - 1; j >= 0; j--) {
var toRemove = properties[toOverridePositions[j]];
var longhandToShorthand = toRemove.name != _name;
var wasImportant = toRemove.important;
var wasHack = toRemove.hack;
if (toRemove.unused)
continue;
if (longhandToShorthand && wasImportant)
continue;
if (!wasImportant && (wasHack && !isHack || !wasHack && isHack))
continue;
if (wasImportant && (isHack == 'star' || isHack == 'underscore'))
continue;
if (!wasHack && !isHack && !longhandToShorthand && canOverride && !canOverride(toRemove, property, validator))
continue;
if (wasImportant && !isImportant || wasImportant && isHack) {
property.unused = true;
lastProperty = property;
continue propertyLoop;
} else {
anyRemoved = true;
toRemove.unused = true;
}
}
if (anyRemoved) {
position = -1;
lastProperty = null;
lastName = null;
overrideMapping = {};
continue;
}
} else {
overrideMapping[_name] = overrideMapping[_name] || [];
overrideMapping[_name].push(position);
// TODO: to be removed with
// certain shorthand (see values of `shorthands`) should trigger removal of
// longhand properties (see keys of `shorthands`)
var _shorthands = shorthands[_name];
if (_shorthands) {
for (j = _shorthands.length - 1; j >= 0; j--) {
var shorthand = _shorthands[j];
overrideMapping[shorthand] = overrideMapping[shorthand] || [];
overrideMapping[shorthand].push(position);
}
}
}
lastName = _name;
lastProperty = property;
}
}
function optimize(selector, properties, mergeAdjacent, withCompacting, options, context) {
var validator = context.validator;
var warnings = context.warnings;
var _properties = wrapForOptimizing(properties);
populateComponents(_properties, validator, warnings);
_optimize(_properties, mergeAdjacent, options.aggressiveMerging, validator);
for (var i = 0, l = _properties.length; i < l; i++) {
var _property = _properties[i];
if (_property.variable && _property.block)
optimize(selector, _property.value[0], mergeAdjacent, withCompacting, options, context);
}
if (withCompacting && options.shorthandCompacting) {
compactOverrides(_properties, options.compatibility, validator);
compactShorthands(_properties, options.sourceMap, validator);
}
restoreFromOptimizing(_properties);
removeUnused(_properties);
}
module.exports = optimize;
@@ -0,0 +1,384 @@
var canOverride = require('./can-override');
var compactable = require('./compactable');
var deepClone = require('./clone').deep;
var shallowClone = require('./clone').shallow;
var hasInherit = require('./has-inherit');
var restoreFromOptimizing = require('./restore-from-optimizing');
var everyCombination = require('./every-combination');
var sameVendorPrefixesIn = require('./vendor-prefixes').same;
var stringifyProperty = require('../stringifier/one-time').property;
var MULTIPLEX_SEPARATOR = ',';
// Used when searching for a component that matches property
function nameMatchFilter(to) {
return function (property) {
return to.name === property.name;
};
}
function wouldBreakCompatibility(property, validator) {
for (var i = 0; i < property.components.length; i++) {
var component = property.components[i];
var descriptor = compactable[component.name];
var canOverride = descriptor && descriptor.canOverride || canOverride.sameValue;
var _component = shallowClone(component);
_component.value = [[descriptor.defaultValue]];
if (!canOverride(_component, component, validator))
return true;
}
return false;
}
function isComponentOf(shorthand, longhand) {
return compactable[shorthand.name].components.indexOf(longhand.name) > -1;
}
function overrideIntoMultiplex(property, by) {
by.unused = true;
turnIntoMultiplex(by, multiplexSize(property));
property.value = by.value;
}
function overrideByMultiplex(property, by) {
by.unused = true;
property.multiplex = true;
property.value = by.value;
}
function overrideSimple(property, by) {
by.unused = true;
property.value = by.value;
}
function override(property, by) {
if (by.multiplex)
overrideByMultiplex(property, by);
else if (property.multiplex)
overrideIntoMultiplex(property, by);
else
overrideSimple(property, by);
}
function overrideShorthand(property, by) {
by.unused = true;
for (var i = 0, l = property.components.length; i < l; i++) {
override(property.components[i], by.components[i], property.multiplex);
}
}
function turnIntoMultiplex(property, size) {
property.multiplex = true;
for (var i = 0, l = property.components.length; i < l; i++) {
var component = property.components[i];
if (component.multiplex)
continue;
var value = component.value.slice(0);
for (var j = 1; j < size; j++) {
component.value.push([MULTIPLEX_SEPARATOR]);
Array.prototype.push.apply(component.value, value);
}
}
}
function multiplexSize(component) {
var size = 0;
for (var i = 0, l = component.value.length; i < l; i++) {
if (component.value[i][0] == MULTIPLEX_SEPARATOR)
size++;
}
return size + 1;
}
function lengthOf(property) {
var fakeAsArray = [[property.name]].concat(property.value);
return stringifyProperty([fakeAsArray], 0).length;
}
function moreSameShorthands(properties, startAt, name) {
// Since we run the main loop in `compactOverrides` backwards, at this point some
// properties may not be marked as unused.
// We should consider reverting the order if possible
var count = 0;
for (var i = startAt; i >= 0; i--) {
if (properties[i].name == name && !properties[i].unused)
count++;
if (count > 1)
break;
}
return count > 1;
}
function overridingFunction(shorthand, validator) {
for (var i = 0, l = shorthand.components.length; i < l; i++) {
if (anyValue(validator.isValidFunction, shorthand.components[i]))
return true;
}
return false;
}
function anyValue(fn, property) {
for (var i = 0, l = property.value.length; i < l; i++) {
if (property.value[i][0] == MULTIPLEX_SEPARATOR)
continue;
if (fn(property.value[i][0]))
return true;
}
return false;
}
function wouldResultInLongerValue(left, right) {
if (!left.multiplex && !right.multiplex || left.multiplex && right.multiplex)
return false;
var multiplex = left.multiplex ? left : right;
var simple = left.multiplex ? right : left;
var component;
var multiplexClone = deepClone(multiplex);
restoreFromOptimizing([multiplexClone]);
var simpleClone = deepClone(simple);
restoreFromOptimizing([simpleClone]);
var lengthBefore = lengthOf(multiplexClone) + 1 + lengthOf(simpleClone);
if (left.multiplex) {
component = multiplexClone.components.filter(nameMatchFilter(simpleClone))[0];
overrideIntoMultiplex(component, simpleClone);
} else {
component = simpleClone.components.filter(nameMatchFilter(multiplexClone))[0];
turnIntoMultiplex(simpleClone, multiplexSize(multiplexClone));
overrideByMultiplex(component, multiplexClone);
}
restoreFromOptimizing([simpleClone]);
var lengthAfter = lengthOf(simpleClone);
return lengthBefore < lengthAfter;
}
function isCompactable(property) {
return property.name in compactable;
}
function noneOverrideHack(left, right) {
return !left.multiplex &&
(left.name == 'background' || left.name == 'background-image') &&
right.multiplex &&
(right.name == 'background' || right.name == 'background-image') &&
anyLayerIsNone(right.value);
}
function anyLayerIsNone(values) {
var layers = intoLayers(values);
for (var i = 0, l = layers.length; i < l; i++) {
if (layers[i].length == 1 && layers[i][0][0] == 'none')
return true;
}
return false;
}
function intoLayers(values) {
var layers = [];
for (var i = 0, layer = [], l = values.length; i < l; i++) {
var value = values[i];
if (value[0] == MULTIPLEX_SEPARATOR) {
layers.push(layer);
layer = [];
} else {
layer.push(value);
}
}
layers.push(layer);
return layers;
}
function compactOverrides(properties, compatibility, validator) {
var mayOverride, right, left, component;
var i, j, k;
propertyLoop:
for (i = properties.length - 1; i >= 0; i--) {
right = properties[i];
if (!isCompactable(right))
continue;
if (right.variable)
continue;
mayOverride = compactable[right.name].canOverride || canOverride.sameValue;
for (j = i - 1; j >= 0; j--) {
left = properties[j];
if (!isCompactable(left))
continue;
if (left.variable)
continue;
if (left.unused || right.unused)
continue;
if (left.hack && !right.hack || !left.hack && right.hack)
continue;
if (hasInherit(right))
continue;
if (noneOverrideHack(left, right))
continue;
if (!left.shorthand && right.shorthand && isComponentOf(right, left)) {
// maybe `left` can be overridden by `right` which is a shorthand?
if (!right.important && left.important)
continue;
if (!sameVendorPrefixesIn([left], right.components))
continue;
if (!anyValue(validator.isValidFunction, left) && overridingFunction(right, validator))
continue;
component = right.components.filter(nameMatchFilter(left))[0];
mayOverride = (compactable[left.name] && compactable[left.name].canOverride) || canOverride.sameValue;
if (everyCombination(mayOverride, left, component, validator)) {
left.unused = true;
}
} else if (left.shorthand && !right.shorthand && isComponentOf(left, right)) {
// maybe `right` can be pulled into `left` which is a shorthand?
if (right.important && !left.important)
continue;
if (!right.important && left.important) {
right.unused = true;
continue;
}
// Pending more clever algorithm in #527
if (moreSameShorthands(properties, i - 1, left.name))
continue;
if (overridingFunction(left, validator))
continue;
component = left.components.filter(nameMatchFilter(right))[0];
if (everyCombination(mayOverride, component, right, validator)) {
var disabledBackgroundMerging =
!compatibility.properties.backgroundClipMerging && component.name.indexOf('background-clip') > -1 ||
!compatibility.properties.backgroundOriginMerging && component.name.indexOf('background-origin') > -1 ||
!compatibility.properties.backgroundSizeMerging && component.name.indexOf('background-size') > -1;
var nonMergeableValue = compactable[right.name].nonMergeableValue === right.value[0][0];
if (disabledBackgroundMerging || nonMergeableValue)
continue;
if (!compatibility.properties.merging && wouldBreakCompatibility(left, validator))
continue;
if (component.value[0][0] != right.value[0][0] && (hasInherit(left) || hasInherit(right)))
continue;
if (wouldResultInLongerValue(left, right))
continue;
if (!left.multiplex && right.multiplex)
turnIntoMultiplex(left, multiplexSize(right));
override(component, right);
left.dirty = true;
}
} else if (left.shorthand && right.shorthand && left.name == right.name) {
// merge if all components can be merged
if (!left.multiplex && right.multiplex)
continue;
if (!right.important && left.important) {
right.unused = true;
continue propertyLoop;
}
if (right.important && !left.important) {
left.unused = true;
continue;
}
for (k = left.components.length - 1; k >= 0; k--) {
var leftComponent = left.components[k];
var rightComponent = right.components[k];
mayOverride = compactable[leftComponent.name].canOverride || canOverride.sameValue;
if (!everyCombination(mayOverride, leftComponent, rightComponent, validator))
continue propertyLoop;
if (!everyCombination(canOverride.twoOptionalFunctions, leftComponent, rightComponent, validator) && validator.isValidFunction(rightComponent))
continue propertyLoop;
}
overrideShorthand(left, right);
left.dirty = true;
} else if (left.shorthand && right.shorthand && isComponentOf(left, right)) {
// border is a shorthand but any of its components is a shorthand too
if (!left.important && right.important)
continue;
component = left.components.filter(nameMatchFilter(right))[0];
mayOverride = compactable[right.name].canOverride || canOverride.sameValue;
if (!everyCombination(mayOverride, component, right, validator))
continue;
if (left.important && !right.important) {
right.unused = true;
continue;
}
var rightRestored = compactable[right.name].restore(right, compactable);
if (rightRestored.length > 1)
continue;
component = left.components.filter(nameMatchFilter(right))[0];
override(component, right);
right.dirty = true;
} else if (left.name == right.name) {
// two non-shorthands should be merged based on understandability
if (left.important && !right.important) {
right.unused = true;
continue;
}
mayOverride = compactable[right.name].canOverride || canOverride.sameValue;
if (!everyCombination(mayOverride, left, right, validator))
continue;
left.unused = true;
}
}
}
}
module.exports = compactOverrides;
@@ -0,0 +1,32 @@
var compactable = require('./compactable');
var InvalidPropertyError = require('./invalid-property-error');
function populateComponents(properties, validator, warnings) {
for (var i = properties.length - 1; i >= 0; i--) {
var property = properties[i];
var descriptor = compactable[property.name];
if (descriptor && descriptor.shorthand) {
property.shorthand = true;
property.dirty = true;
try {
property.components = descriptor.breakUp(property, compactable, validator);
} catch (e) {
if (e instanceof InvalidPropertyError) {
property.components = []; // this will set property.unused to true below
warnings.push(e.message);
} else {
throw e;
}
}
if (property.components.length > 0)
property.multiplex = property.components[0].multiplex;
else
property.unused = true;
}
}
}
module.exports = populateComponents;
@@ -0,0 +1,10 @@
function removeUnused(properties) {
for (var i = properties.length - 1; i >= 0; i--) {
var property = properties[i];
if (property.unused)
property.all.splice(property.position, 1);
}
}
module.exports = removeUnused;
@@ -0,0 +1,60 @@
var compactable = require('./compactable');
var BACKSLASH_HACK = '\\9';
var IMPORTANT_TOKEN = '!important';
var STAR_HACK = '*';
var UNDERSCORE_HACK = '_';
var BANG_HACK = '!ie';
function restoreImportant(property) {
property.value[property.value.length - 1][0] += IMPORTANT_TOKEN;
}
function restoreHack(property) {
if (property.hack == 'underscore')
property.name = UNDERSCORE_HACK + property.name;
else if (property.hack == 'star')
property.name = STAR_HACK + property.name;
else if (property.hack == 'backslash')
property.value[property.value.length - 1][0] += BACKSLASH_HACK;
else if (property.hack == 'bang')
property.value[property.value.length - 1][0] += ' ' + BANG_HACK;
}
function restoreFromOptimizing(properties, simpleMode) {
for (var i = properties.length - 1; i >= 0; i--) {
var property = properties[i];
var descriptor = compactable[property.name];
var restored;
if (property.unused)
continue;
if (!property.dirty && !property.important && !property.hack)
continue;
if (!simpleMode && descriptor && descriptor.shorthand) {
restored = descriptor.restore(property, compactable);
property.value = restored;
} else {
restored = property.value;
}
if (property.important)
restoreImportant(property);
if (property.hack)
restoreHack(property);
if (!('all' in property))
continue;
var current = property.all[property.position];
current[0][0] = property.name;
current.splice(1, current.length - 1);
Array.prototype.push.apply(current, restored);
}
}
module.exports = restoreFromOptimizing;
@@ -0,0 +1,232 @@
var shallowClone = require('./clone').shallow;
var MULTIPLEX_SEPARATOR = ',';
var SIZE_POSITION_SEPARATOR = '/';
function isInheritOnly(values) {
for (var i = 0, l = values.length; i < l; i++) {
var value = values[i][0];
if (value != 'inherit' && value != MULTIPLEX_SEPARATOR && value != SIZE_POSITION_SEPARATOR)
return false;
}
return true;
}
function background(property, compactable, lastInMultiplex) {
var components = property.components;
var restored = [];
var needsOne, needsBoth;
function restoreValue(component) {
Array.prototype.unshift.apply(restored, component.value);
}
function isDefaultValue(component) {
var descriptor = compactable[component.name];
if (descriptor.doubleValues) {
if (descriptor.defaultValue.length == 1)
return component.value[0][0] == descriptor.defaultValue[0] && (component.value[1] ? component.value[1][0] == descriptor.defaultValue[0] : true);
else
return component.value[0][0] == descriptor.defaultValue[0] && (component.value[1] ? component.value[1][0] : component.value[0][0]) == descriptor.defaultValue[1];
} else {
return component.value[0][0] == descriptor.defaultValue;
}
}
for (var i = components.length - 1; i >= 0; i--) {
var component = components[i];
var isDefault = isDefaultValue(component);
if (component.name == 'background-clip') {
var originComponent = components[i - 1];
var isOriginDefault = isDefaultValue(originComponent);
needsOne = component.value[0][0] == originComponent.value[0][0];
needsBoth = !needsOne && (
(isOriginDefault && !isDefault) ||
(!isOriginDefault && !isDefault) ||
(!isOriginDefault && isDefault && component.value[0][0] != originComponent.value[0][0]));
if (needsOne) {
restoreValue(originComponent);
} else if (needsBoth) {
restoreValue(component);
restoreValue(originComponent);
}
i--;
} else if (component.name == 'background-size') {
var positionComponent = components[i - 1];
var isPositionDefault = isDefaultValue(positionComponent);
needsOne = !isPositionDefault && isDefault;
needsBoth = !needsOne &&
(isPositionDefault && !isDefault || !isPositionDefault && !isDefault);
if (needsOne) {
restoreValue(positionComponent);
} else if (needsBoth) {
restoreValue(component);
restored.unshift([SIZE_POSITION_SEPARATOR]);
restoreValue(positionComponent);
} else if (positionComponent.value.length == 1) {
restoreValue(positionComponent);
}
i--;
} else {
if (isDefault || compactable[component.name].multiplexLastOnly && !lastInMultiplex)
continue;
restoreValue(component);
}
}
if (restored.length === 0 && property.value.length == 1 && property.value[0][0] == '0')
restored.push(property.value[0]);
if (restored.length === 0)
restored.push([compactable[property.name].defaultValue]);
if (isInheritOnly(restored))
return [restored[0]];
return restored;
}
function borderRadius(property, compactable) {
if (property.multiplex) {
var horizontal = shallowClone(property);
var vertical = shallowClone(property);
for (var i = 0; i < 4; i++) {
var component = property.components[i];
var horizontalComponent = shallowClone(property);
horizontalComponent.value = [component.value[0]];
horizontal.components.push(horizontalComponent);
var verticalComponent = shallowClone(property);
// FIXME: only shorthand compactor (see breakup#borderRadius) knows that border radius
// longhands have two values, whereas tokenizer does not care about populating 2nd value
// if it's missing, hence this fallback
verticalComponent.value = [component.value[1] || component.value[0]];
vertical.components.push(verticalComponent);
}
var horizontalValues = fourValues(horizontal, compactable);
var verticalValues = fourValues(vertical, compactable);
if (horizontalValues.length == verticalValues.length &&
horizontalValues[0][0] == verticalValues[0][0] &&
(horizontalValues.length > 1 ? horizontalValues[1][0] == verticalValues[1][0] : true) &&
(horizontalValues.length > 2 ? horizontalValues[2][0] == verticalValues[2][0] : true) &&
(horizontalValues.length > 3 ? horizontalValues[3][0] == verticalValues[3][0] : true)) {
return horizontalValues;
} else {
return horizontalValues.concat([['/']]).concat(verticalValues);
}
} else {
return fourValues(property, compactable);
}
}
function fourValues(property) {
var components = property.components;
var value1 = components[0].value[0];
var value2 = components[1].value[0];
var value3 = components[2].value[0];
var value4 = components[3].value[0];
if (value1[0] == value2[0] && value1[0] == value3[0] && value1[0] == value4[0]) {
return [value1];
} else if (value1[0] == value3[0] && value2[0] == value4[0]) {
return [value1, value2];
} else if (value2[0] == value4[0]) {
return [value1, value2, value3];
} else {
return [value1, value2, value3, value4];
}
}
function multiplex(restoreWith) {
return function (property, compactable) {
if (!property.multiplex)
return restoreWith(property, compactable, true);
var multiplexSize = 0;
var restored = [];
var componentMultiplexSoFar = {};
var i, l;
// At this point we don't know what's the multiplex size, e.g. how many background layers are there
for (i = 0, l = property.components[0].value.length; i < l; i++) {
if (property.components[0].value[i][0] == MULTIPLEX_SEPARATOR)
multiplexSize++;
}
for (i = 0; i <= multiplexSize; i++) {
var _property = shallowClone(property);
// We split multiplex into parts and restore them one by one
for (var j = 0, m = property.components.length; j < m; j++) {
var componentToClone = property.components[j];
var _component = shallowClone(componentToClone);
_property.components.push(_component);
// The trick is some properties has more than one value, so we iterate over values looking for
// a multiplex separator - a comma
for (var k = componentMultiplexSoFar[_component.name] || 0, n = componentToClone.value.length; k < n; k++) {
if (componentToClone.value[k][0] == MULTIPLEX_SEPARATOR) {
componentMultiplexSoFar[_component.name] = k + 1;
break;
}
_component.value.push(componentToClone.value[k]);
}
}
// No we can restore shorthand value
var lastInMultiplex = i == multiplexSize;
var _restored = restoreWith(_property, compactable, lastInMultiplex);
Array.prototype.push.apply(restored, _restored);
if (i < multiplexSize)
restored.push([',']);
}
return restored;
};
}
function withoutDefaults(property, compactable) {
var components = property.components;
var restored = [];
for (var i = components.length - 1; i >= 0; i--) {
var component = components[i];
var descriptor = compactable[component.name];
if (component.value[0][0] != descriptor.defaultValue)
restored.unshift(component.value[0]);
}
if (restored.length === 0)
restored.push([compactable[property.name].defaultValue]);
if (isInheritOnly(restored))
return [restored[0]];
return restored;
}
module.exports = {
background: background,
borderRadius: borderRadius,
fourValues: fourValues,
multiplex: multiplex,
withoutDefaults: withoutDefaults
};
@@ -0,0 +1,134 @@
var compactable = require('./compactable');
var deepClone = require('./clone').deep;
var hasInherit = require('./has-inherit');
var populateComponents = require('./populate-components');
var wrapSingle = require('./wrap-for-optimizing').single;
var everyCombination = require('./every-combination');
function mixedImportance(components) {
var important;
for (var name in components) {
if (undefined !== important && components[name].important != important)
return true;
important = components[name].important;
}
return false;
}
function componentSourceMaps(components) {
var sourceMapping = [];
for (var name in components) {
var component = components[name];
var originalValue = component.all[component.position];
var mapping = originalValue[0][originalValue[0].length - 1];
if (Array.isArray(mapping))
Array.prototype.push.apply(sourceMapping, mapping);
}
return sourceMapping;
}
function replaceWithShorthand(properties, candidateComponents, name, sourceMaps, validator) {
var descriptor = compactable[name];
var newValuePlaceholder = [[name], [descriptor.defaultValue]];
var all;
var newProperty = wrapSingle(newValuePlaceholder);
newProperty.shorthand = true;
newProperty.dirty = true;
populateComponents([newProperty], validator, []);
for (var i = 0, l = descriptor.components.length; i < l; i++) {
var component = candidateComponents[descriptor.components[i]];
var canOverride = compactable[component.name].canOverride;
if (hasInherit(component))
return;
if (!everyCombination(canOverride, newProperty.components[i], component, validator))
return;
newProperty.components[i] = deepClone(component);
newProperty.important = component.important;
all = component.all;
}
for (var componentName in candidateComponents) {
candidateComponents[componentName].unused = true;
}
if (sourceMaps) {
var sourceMapping = componentSourceMaps(candidateComponents);
if (sourceMapping.length > 0)
newValuePlaceholder[0].push(sourceMapping);
}
newProperty.position = all.length;
newProperty.all = all;
newProperty.all.push(newValuePlaceholder);
properties.push(newProperty);
}
function invalidateOrCompact(properties, position, candidates, sourceMaps, validator) {
var property = properties[position];
for (var name in candidates) {
if (undefined !== property && name == property.name)
continue;
var descriptor = compactable[name];
var candidateComponents = candidates[name];
if (descriptor.components.length > Object.keys(candidateComponents).length) {
delete candidates[name];
continue;
}
if (mixedImportance(candidateComponents))
continue;
replaceWithShorthand(properties, candidateComponents, name, sourceMaps, validator);
}
}
function compactShortands(properties, sourceMaps, validator) {
var candidates = {};
if (properties.length < 3)
return;
for (var i = 0, l = properties.length; i < l; i++) {
var property = properties[i];
if (property.unused)
continue;
if (property.hack)
continue;
if (property.variable)
continue;
var descriptor = compactable[property.name];
if (!descriptor || !descriptor.componentOf)
continue;
if (property.shorthand) {
invalidateOrCompact(properties, i, candidates, sourceMaps, validator);
} else {
var componentOf = descriptor.componentOf;
candidates[componentOf] = candidates[componentOf] || {};
candidates[componentOf][property.name] = property;
}
}
invalidateOrCompact(properties, i, candidates, sourceMaps, validator);
}
module.exports = compactShortands;
@@ -0,0 +1,197 @@
// Validates various CSS property values
var split = require('../utils/split');
var widthKeywords = ['thin', 'thick', 'medium', 'inherit', 'initial'];
var allUnits = ['px', '%', 'em', 'in', 'cm', 'mm', 'ex', 'pt', 'pc', 'ch', 'rem', 'vh', 'vm', 'vmin', 'vmax', 'vw'];
var cssUnitRegexStr = '(\\-?\\.?\\d+\\.?\\d*(' + allUnits.join('|') + '|)|auto|inherit)';
var cssCalcRegexStr = '(\\-moz\\-|\\-webkit\\-)?calc\\([^\\)]+\\)';
var cssFunctionNoVendorRegexStr = '[A-Z]+(\\-|[A-Z]|[0-9])+\\(.*?\\)';
var cssFunctionVendorRegexStr = '\\-(\\-|[A-Z]|[0-9])+\\(.*?\\)';
var cssVariableRegexStr = 'var\\(\\-\\-[^\\)]+\\)';
var cssFunctionAnyRegexStr = '(' + cssVariableRegexStr + '|' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')';
var cssUnitOrCalcRegexStr = '(' + cssUnitRegexStr + '|' + cssCalcRegexStr + ')';
var cssUnitAnyRegexStr = '(none|' + widthKeywords.join('|') + '|' + cssUnitRegexStr + '|' + cssVariableRegexStr + '|' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')';
var cssFunctionNoVendorRegex = new RegExp('^' + cssFunctionNoVendorRegexStr + '$', 'i');
var cssFunctionVendorRegex = new RegExp('^' + cssFunctionVendorRegexStr + '$', 'i');
var cssVariableRegex = new RegExp('^' + cssVariableRegexStr + '$', 'i');
var cssFunctionAnyRegex = new RegExp('^' + cssFunctionAnyRegexStr + '$', 'i');
var cssUnitRegex = new RegExp('^' + cssUnitRegexStr + '$', 'i');
var cssUnitOrCalcRegex = new RegExp('^' + cssUnitOrCalcRegexStr + '$', 'i');
var cssUnitAnyRegex = new RegExp('^' + cssUnitAnyRegexStr + '$', 'i');
var backgroundRepeatKeywords = ['repeat', 'no-repeat', 'repeat-x', 'repeat-y', 'inherit'];
var backgroundAttachmentKeywords = ['inherit', 'scroll', 'fixed', 'local'];
var backgroundPositionKeywords = ['center', 'top', 'bottom', 'left', 'right'];
var backgroundSizeKeywords = ['contain', 'cover'];
var backgroundBoxKeywords = ['border-box', 'content-box', 'padding-box'];
var styleKeywords = ['auto', 'inherit', 'hidden', 'none', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset'];
var listStyleTypeKeywords = ['armenian', 'circle', 'cjk-ideographic', 'decimal', 'decimal-leading-zero', 'disc', 'georgian', 'hebrew', 'hiragana', 'hiragana-iroha', 'inherit', 'katakana', 'katakana-iroha', 'lower-alpha', 'lower-greek', 'lower-latin', 'lower-roman', 'none', 'square', 'upper-alpha', 'upper-latin', 'upper-roman'];
var listStylePositionKeywords = ['inside', 'outside', 'inherit'];
function Validator(compatibility) {
var validUnits = allUnits.slice(0).filter(function (value) {
return !(value in compatibility.units) || compatibility.units[value] === true;
});
var compatibleCssUnitRegexStr = '(\\-?\\.?\\d+\\.?\\d*(' + validUnits.join('|') + '|)|auto|inherit)';
this.compatibleCssUnitRegex = new RegExp('^' + compatibleCssUnitRegexStr + '$', 'i');
this.compatibleCssUnitAnyRegex = new RegExp('^(none|' + widthKeywords.join('|') + '|' + compatibleCssUnitRegexStr + '|' + cssVariableRegexStr + '|' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')$', 'i');
this.colorOpacity = compatibility.colors.opacity;
}
Validator.prototype.isValidHexColor = function (s) {
return (s.length === 4 || s.length === 7) && s[0] === '#';
};
Validator.prototype.isValidRgbaColor = function (s) {
s = s.split(' ').join('');
return s.length > 0 && s.indexOf('rgba(') === 0 && s.indexOf(')') === s.length - 1;
};
Validator.prototype.isValidHslaColor = function (s) {
s = s.split(' ').join('');
return s.length > 0 && s.indexOf('hsla(') === 0 && s.indexOf(')') === s.length - 1;
};
Validator.prototype.isValidNamedColor = function (s) {
// We don't really check if it's a valid color value, but allow any letters in it
return s !== 'auto' && (s === 'transparent' || s === 'inherit' || /^[a-zA-Z]+$/.test(s));
};
Validator.prototype.isValidVariable = function (s) {
return cssVariableRegex.test(s);
};
Validator.prototype.isValidColor = function (s) {
return this.isValidNamedColor(s) ||
this.isValidColorValue(s) ||
this.isValidVariable(s) ||
this.isValidVendorPrefixedValue(s);
};
Validator.prototype.isValidColorValue = function (s) {
return this.isValidHexColor(s) ||
this.isValidRgbaColor(s) ||
this.isValidHslaColor(s);
};
Validator.prototype.isValidUrl = function (s) {
// NOTE: at this point all URLs are replaced with placeholders by clean-css, so we check for those placeholders
return s.indexOf('__ESCAPED_URL_CLEAN_CSS') === 0;
};
Validator.prototype.isValidUnit = function (s) {
return cssUnitAnyRegex.test(s);
};
Validator.prototype.isValidUnitWithoutFunction = function (s) {
return cssUnitRegex.test(s);
};
Validator.prototype.isValidAndCompatibleUnit = function (s) {
return this.compatibleCssUnitAnyRegex.test(s);
};
Validator.prototype.isValidAndCompatibleUnitWithoutFunction = function (s) {
return this.compatibleCssUnitRegex.test(s);
};
Validator.prototype.isValidFunctionWithoutVendorPrefix = function (s) {
return cssFunctionNoVendorRegex.test(s);
};
Validator.prototype.isValidFunctionWithVendorPrefix = function (s) {
return cssFunctionVendorRegex.test(s);
};
Validator.prototype.isValidFunction = function (s) {
return cssFunctionAnyRegex.test(s);
};
Validator.prototype.isValidBackgroundRepeat = function (s) {
return backgroundRepeatKeywords.indexOf(s) >= 0 || this.isValidVariable(s);
};
Validator.prototype.isValidBackgroundAttachment = function (s) {
return backgroundAttachmentKeywords.indexOf(s) >= 0 || this.isValidVariable(s);
};
Validator.prototype.isValidBackgroundBox = function (s) {
return backgroundBoxKeywords.indexOf(s) >= 0 || this.isValidVariable(s);
};
Validator.prototype.isValidBackgroundPositionPart = function (s) {
return backgroundPositionKeywords.indexOf(s) >= 0 || cssUnitOrCalcRegex.test(s) || this.isValidVariable(s);
};
Validator.prototype.isValidBackgroundPosition = function (s) {
if (s === 'inherit')
return true;
var parts = s.split(' ');
for (var i = 0, l = parts.length; i < l; i++) {
if (parts[i] === '')
continue;
if (this.isValidBackgroundPositionPart(parts[i]) || this.isValidVariable(parts[i]))
continue;
return false;
}
return true;
};
Validator.prototype.isValidBackgroundSizePart = function (s) {
return backgroundSizeKeywords.indexOf(s) >= 0 || cssUnitRegex.test(s) || this.isValidVariable(s);
};
Validator.prototype.isValidBackgroundPositionAndSize = function (s) {
if (s.indexOf('/') < 0)
return false;
var twoParts = split(s, '/');
return this.isValidBackgroundSizePart(twoParts.pop()) && this.isValidBackgroundPositionPart(twoParts.pop());
};
Validator.prototype.isValidListStyleType = function (s) {
return listStyleTypeKeywords.indexOf(s) >= 0 || this.isValidVariable(s);
};
Validator.prototype.isValidListStylePosition = function (s) {
return listStylePositionKeywords.indexOf(s) >= 0 || this.isValidVariable(s);
};
Validator.prototype.isValidStyle = function (s) {
return this.isValidStyleKeyword(s) || this.isValidVariable(s);
};
Validator.prototype.isValidStyleKeyword = function (s) {
return styleKeywords.indexOf(s) >= 0;
};
Validator.prototype.isValidWidth = function (s) {
return this.isValidUnit(s) || this.isValidWidthKeyword(s) || this.isValidVariable(s);
};
Validator.prototype.isValidWidthKeyword = function (s) {
return widthKeywords.indexOf(s) >= 0;
};
Validator.prototype.isValidVendorPrefixedValue = function (s) {
return /^-([A-Za-z0-9]|-)*$/gi.test(s);
};
Validator.prototype.areSameFunction = function (a, b) {
if (!this.isValidFunction(a) || !this.isValidFunction(b))
return false;
var f1name = a.substring(0, a.indexOf('('));
var f2name = b.substring(0, b.indexOf('('));
return f1name === f2name;
};
module.exports = Validator;
@@ -0,0 +1,26 @@
var VENDOR_PREFIX_PATTERN = /$\-moz\-|\-ms\-|\-o\-|\-webkit\-/;
function prefixesIn(tokens) {
var prefixes = [];
for (var i = 0, l = tokens.length; i < l; i++) {
var token = tokens[i];
for (var j = 0, m = token.value.length; j < m; j++) {
var match = VENDOR_PREFIX_PATTERN.exec(token.value[j][0]);
if (match && prefixes.indexOf(match[0]) == -1)
prefixes.push(match[0]);
}
}
return prefixes;
}
function same(left, right) {
return prefixesIn(left).sort().join(',') == prefixesIn(right).sort().join(',');
}
module.exports = {
same: same
};
@@ -0,0 +1,118 @@
var BACKSLASH_HACK = '\\';
var IMPORTANT_WORD = 'important';
var IMPORTANT_TOKEN = '!'+IMPORTANT_WORD;
var IMPORTANT_WORD_MATCH = new RegExp(IMPORTANT_WORD+'$', 'i');
var IMPORTANT_TOKEN_MATCH = new RegExp(IMPORTANT_TOKEN+'$', 'i');
var STAR_HACK = '*';
var UNDERSCORE_HACK = '_';
var BANG_HACK = '!';
function wrapAll(properties) {
var wrapped = [];
for (var i = properties.length - 1; i >= 0; i--) {
if (typeof properties[i][0] == 'string')
continue;
var single = wrapSingle(properties[i]);
single.all = properties;
single.position = i;
wrapped.unshift(single);
}
return wrapped;
}
function isMultiplex(property) {
for (var i = 1, l = property.length; i < l; i++) {
if (property[i][0] == ',' || property[i][0] == '/')
return true;
}
return false;
}
function hackType(property) {
var type = false;
var name = property[0][0];
var lastValue = property[property.length - 1];
if (name[0] == UNDERSCORE_HACK) {
type = 'underscore';
} else if (name[0] == STAR_HACK) {
type = 'star';
} else if (lastValue[0][0] == BANG_HACK && !lastValue[0].match(IMPORTANT_WORD_MATCH)) {
type = 'bang';
} else if (lastValue[0].indexOf(BANG_HACK) > 0 && !lastValue[0].match(IMPORTANT_WORD_MATCH)) {
type = 'bang';
} else if (lastValue[0].indexOf(BACKSLASH_HACK) > 0 && lastValue[0].indexOf(BACKSLASH_HACK) == lastValue[0].length - BACKSLASH_HACK.length - 1) {
type = 'backslash';
} else if (lastValue[0].indexOf(BACKSLASH_HACK) === 0 && lastValue[0].length == 2) {
type = 'backslash';
}
return type;
}
function isImportant(property) {
if (property.length > 1) {
var p = property[property.length - 1][0];
if (typeof(p) === 'string') {
return IMPORTANT_TOKEN_MATCH.test(p);
}
}
return false;
}
function stripImportant(property) {
if (property.length > 0)
property[property.length - 1][0] = property[property.length - 1][0].replace(IMPORTANT_TOKEN_MATCH, '');
}
function stripPrefixHack(property) {
property[0][0] = property[0][0].substring(1);
}
function stripSuffixHack(property, hackType) {
var lastValue = property[property.length - 1];
lastValue[0] = lastValue[0]
.substring(0, lastValue[0].indexOf(hackType == 'backslash' ? BACKSLASH_HACK : BANG_HACK))
.trim();
if (lastValue[0].length === 0)
property.pop();
}
function wrapSingle(property) {
var _isImportant = isImportant(property);
if (_isImportant)
stripImportant(property);
var _hackType = hackType(property);
if (_hackType == 'star' || _hackType == 'underscore')
stripPrefixHack(property);
else if (_hackType == 'backslash' || _hackType == 'bang')
stripSuffixHack(property, _hackType);
var isVariable = property[0][0].indexOf('--') === 0;
return {
block: isVariable && property[1] && Array.isArray(property[1][0][0]),
components: [],
dirty: false,
hack: _hackType,
important: _isImportant,
name: property[0][0],
multiplex: property.length > 2 ? isMultiplex(property) : false,
position: 0,
shorthand: false,
unused: property.length < 2,
value: property.slice(1),
variable: isVariable
};
}
module.exports = {
all: wrapAll,
single: wrapSingle
};
@@ -0,0 +1,86 @@
var optimizeProperties = require('../properties/optimizer');
var removeDuplicates = require('./remove-duplicates');
var mergeAdjacent = require('./merge-adjacent');
var reduceNonAdjacent = require('./reduce-non-adjacent');
var mergeNonAdjacentBySelector = require('./merge-non-adjacent-by-selector');
var mergeNonAdjacentByBody = require('./merge-non-adjacent-by-body');
var restructure = require('./restructure');
var removeDuplicateMediaQueries = require('./remove-duplicate-media-queries');
var mergeMediaQueries = require('./merge-media-queries');
function removeEmpty(tokens) {
for (var i = 0, l = tokens.length; i < l; i++) {
var token = tokens[i];
var isEmpty = false;
switch (token[0]) {
case 'selector':
isEmpty = token[1].length === 0 || token[2].length === 0;
break;
case 'block':
removeEmpty(token[2]);
isEmpty = token[2].length === 0;
}
if (isEmpty) {
tokens.splice(i, 1);
i--;
l--;
}
}
}
function recursivelyOptimizeBlocks(tokens, options, context) {
for (var i = 0, l = tokens.length; i < l; i++) {
var token = tokens[i];
if (token[0] == 'block') {
var isKeyframes = /@(-moz-|-o-|-webkit-)?keyframes/.test(token[1][0]);
optimize(token[2], options, context, !isKeyframes);
}
}
}
function recursivelyOptimizeProperties(tokens, options, context) {
for (var i = 0, l = tokens.length; i < l; i++) {
var token = tokens[i];
switch (token[0]) {
case 'selector':
optimizeProperties(token[1], token[2], false, true, options, context);
break;
case 'block':
recursivelyOptimizeProperties(token[2], options, context);
}
}
}
function optimize(tokens, options, context, withRestructuring) {
recursivelyOptimizeBlocks(tokens, options, context);
recursivelyOptimizeProperties(tokens, options, context);
removeDuplicates(tokens);
mergeAdjacent(tokens, options, context);
reduceNonAdjacent(tokens, options, context);
mergeNonAdjacentBySelector(tokens, options, context);
mergeNonAdjacentByBody(tokens, options);
if (options.restructuring && withRestructuring) {
restructure(tokens, options);
mergeAdjacent(tokens, options, context);
}
if (options.mediaMerging) {
removeDuplicateMediaQueries(tokens);
var reduced = mergeMediaQueries(tokens);
for (var i = reduced.length - 1; i >= 0; i--) {
optimize(reduced[i][2], options, context, false);
}
}
removeEmpty(tokens);
}
module.exports = optimize;
@@ -0,0 +1,89 @@
function removeWhitespace(match, value) {
return '[' + value.replace(/ /g, '') + ']';
}
function selectorSorter(s1, s2) {
return s1[0] > s2[0] ? 1 : -1;
}
function whitespaceReplacements(_, p1, p2, p3) {
if (p1 && p2 && p3.length)
return p1 + p2 + ' ';
else if (p1 && p2)
return p1 + p2;
else
return p2;
}
var CleanUp = {
selectors: function (selectors, removeUnsupported, adjacentSpace) {
var list = [];
var repeated = [];
for (var i = 0, l = selectors.length; i < l; i++) {
var selector = selectors[i];
var reduced = selector[0]
.replace(/\s+/g, ' ')
.replace(/ ?, ?/g, ',')
.replace(/\s*(\\)?([>+~])(\s*)/g, whitespaceReplacements)
.trim();
if (adjacentSpace && reduced.indexOf('nav') > 0)
reduced = reduced.replace(/\+nav(\S|$)/, '+ nav$1');
if (removeUnsupported && (reduced.indexOf('*+html ') != -1 || reduced.indexOf('*:first-child+html ') != -1))
continue;
if (reduced.indexOf('*') > -1) {
reduced = reduced
.replace(/\*([:#\.\[])/g, '$1')
.replace(/^(\:first\-child)?\+html/, '*$1+html');
}
if (reduced.indexOf('[') > -1)
reduced = reduced.replace(/\[([^\]]+)\]/g, removeWhitespace);
if (repeated.indexOf(reduced) == -1) {
selector[0] = reduced;
repeated.push(reduced);
list.push(selector);
}
}
return list.sort(selectorSorter);
},
selectorDuplicates: function (selectors) {
var list = [];
var repeated = [];
for (var i = 0, l = selectors.length; i < l; i++) {
var selector = selectors[i];
if (repeated.indexOf(selector[0]) == -1) {
repeated.push(selector[0]);
list.push(selector);
}
}
return list.sort(selectorSorter);
},
block: function (values, spaceAfterClosingBrace) {
values[0] = values[0]
.replace(/\s+/g, ' ')
.replace(/(,|:|\() /g, '$1')
.replace(/ \)/g, ')');
if (!spaceAfterClosingBrace)
values[0] = values[0].replace(/\) /g, ')');
},
atRule: function (values) {
values[0] = values[0]
.replace(/\s+/g, ' ')
.trim();
}
};
module.exports = CleanUp;
@@ -0,0 +1,69 @@
// This extractor is used in advanced optimizations
// IMPORTANT: Mind Token class and this code is not related!
// Properties will be tokenized in one step, see #429
var stringifySelectors = require('../stringifier/one-time').selectors;
var stringifyValue = require('../stringifier/one-time').value;
var AT_RULE = 'at-rule';
function extract(token) {
var properties = [];
if (token[0] == 'selector') {
var inSpecificSelector = !/[\.\+>~]/.test(stringifySelectors(token[1]));
for (var i = 0, l = token[2].length; i < l; i++) {
var property = token[2][i];
if (property.indexOf('__ESCAPED') === 0)
continue;
if (property[0] == AT_RULE)
continue;
var name = token[2][i][0][0];
if (name.length === 0)
continue;
if (name.indexOf('--') === 0)
continue;
var value = stringifyValue(token[2], i);
properties.push([
name,
value,
findNameRoot(name),
token[2][i],
name + ':' + value,
token[1],
inSpecificSelector
]);
}
} else if (token[0] == 'block') {
for (var j = 0, k = token[2].length; j < k; j++) {
properties = properties.concat(extract(token[2][j]));
}
}
return properties;
}
function findNameRoot(name) {
if (name == 'list-style')
return name;
if (name.indexOf('-radius') > 0)
return 'border-radius';
if (name == 'border-collapse' || name == 'border-spacing' || name == 'border-image')
return name;
if (name.indexOf('border-') === 0 && /^border\-\w+\-\w+$/.test(name))
return name.match(/border\-\w+/)[0];
if (name.indexOf('border-') === 0 && /^border\-\w+$/.test(name))
return 'border';
if (name.indexOf('text-') === 0)
return name;
return name.replace(/^\-\w+\-/, '').match(/([a-zA-Z]+)/)[0].toLowerCase();
}
module.exports = extract;
@@ -0,0 +1,5 @@
function isSpecial(options, selector) {
return options.compatibility.selectors.special.test(selector);
}
module.exports = isSpecial;
@@ -0,0 +1,35 @@
var optimizeProperties = require('../properties/optimizer');
var stringifyBody = require('../stringifier/one-time').body;
var stringifySelectors = require('../stringifier/one-time').selectors;
var cleanUpSelectors = require('./clean-up').selectors;
var isSpecial = require('./is-special');
function mergeAdjacent(tokens, options, context) {
var lastToken = [null, [], []];
var adjacentSpace = options.compatibility.selectors.adjacentSpace;
for (var i = 0, l = tokens.length; i < l; i++) {
var token = tokens[i];
if (token[0] != 'selector') {
lastToken = [null, [], []];
continue;
}
if (lastToken[0] == 'selector' && stringifySelectors(token[1]) == stringifySelectors(lastToken[1])) {
var joinAt = [lastToken[2].length];
Array.prototype.push.apply(lastToken[2], token[2]);
optimizeProperties(token[1], lastToken[2], joinAt, true, options, context);
token[2] = [];
} else if (lastToken[0] == 'selector' && stringifyBody(token[2]) == stringifyBody(lastToken[2]) &&
!isSpecial(options, stringifySelectors(token[1])) && !isSpecial(options, stringifySelectors(lastToken[1]))) {
lastToken[1] = cleanUpSelectors(lastToken[1].concat(token[1]), false, adjacentSpace);
token[2] = [];
} else {
lastToken = token;
}
}
}
module.exports = mergeAdjacent;
@@ -0,0 +1,64 @@
var canReorder = require('./reorderable').canReorder;
var extractProperties = require('./extractor');
function mergeMediaQueries(tokens) {
var candidates = {};
var reduced = [];
for (var i = tokens.length - 1; i >= 0; i--) {
var token = tokens[i];
if (token[0] != 'block')
continue;
var candidate = candidates[token[1][0]];
if (!candidate) {
candidate = [];
candidates[token[1][0]] = candidate;
}
candidate.push(i);
}
for (var name in candidates) {
var positions = candidates[name];
positionLoop:
for (var j = positions.length - 1; j > 0; j--) {
var positionOne = positions[j];
var tokenOne = tokens[positionOne];
var positionTwo = positions[j - 1];
var tokenTwo = tokens[positionTwo];
directionLoop:
for (var direction = 1; direction >= -1; direction -= 2) {
var topToBottom = direction == 1;
var from = topToBottom ? positionOne + 1 : positionTwo - 1;
var to = topToBottom ? positionTwo : positionOne;
var delta = topToBottom ? 1 : -1;
var source = topToBottom ? tokenOne : tokenTwo;
var target = topToBottom ? tokenTwo : tokenOne;
var movedProperties = extractProperties(source);
while (from != to) {
var traversedProperties = extractProperties(tokens[from]);
from += delta;
if (!canReorder(movedProperties, traversedProperties))
continue directionLoop;
}
target[2] = topToBottom ?
source[2].concat(target[2]) :
target[2].concat(source[2]);
source[2] = [];
reduced.push(target);
continue positionLoop;
}
}
}
return reduced;
}
module.exports = mergeMediaQueries;
@@ -0,0 +1,61 @@
var stringifyBody = require('../stringifier/one-time').body;
var stringifySelectors = require('../stringifier/one-time').selectors;
var cleanUpSelectors = require('./clean-up').selectors;
var isSpecial = require('./is-special');
function unsafeSelector(value) {
return /\.|\*| :/.test(value);
}
function isBemElement(token) {
var asString = stringifySelectors(token[1]);
return asString.indexOf('__') > -1 || asString.indexOf('--') > -1;
}
function withoutModifier(selector) {
return selector.replace(/--[^ ,>\+~:]+/g, '');
}
function removeAnyUnsafeElements(left, candidates) {
var leftSelector = withoutModifier(stringifySelectors(left[1]));
for (var body in candidates) {
var right = candidates[body];
var rightSelector = withoutModifier(stringifySelectors(right[1]));
if (rightSelector.indexOf(leftSelector) > -1 || leftSelector.indexOf(rightSelector) > -1)
delete candidates[body];
}
}
function mergeNonAdjacentByBody(tokens, options) {
var candidates = {};
var adjacentSpace = options.compatibility.selectors.adjacentSpace;
for (var i = tokens.length - 1; i >= 0; i--) {
var token = tokens[i];
if (token[0] != 'selector')
continue;
if (token[2].length > 0 && (!options.semanticMerging && unsafeSelector(stringifySelectors(token[1]))))
candidates = {};
if (token[2].length > 0 && options.semanticMerging && isBemElement(token))
removeAnyUnsafeElements(token, candidates);
var candidateBody = stringifyBody(token[2]);
var oldToken = candidates[candidateBody];
if (oldToken && !isSpecial(options, stringifySelectors(token[1])) && !isSpecial(options, stringifySelectors(oldToken[1]))) {
token[1] = token[2].length > 0 ?
cleanUpSelectors(oldToken[1].concat(token[1]), false, adjacentSpace) :
oldToken[1].concat(token[1]);
oldToken[2] = [];
candidates[candidateBody] = null;
}
candidates[stringifyBody(token[2])] = token;
}
}
module.exports = mergeNonAdjacentByBody;
@@ -0,0 +1,76 @@
var optimizeProperties = require('../properties/optimizer');
var stringifySelectors = require('../stringifier/one-time').selectors;
var extractProperties = require('./extractor');
var canReorder = require('./reorderable').canReorder;
function mergeNonAdjacentBySelector(tokens, options, context) {
var allSelectors = {};
var repeatedSelectors = [];
var i;
for (i = tokens.length - 1; i >= 0; i--) {
if (tokens[i][0] != 'selector')
continue;
if (tokens[i][2].length === 0)
continue;
var selector = stringifySelectors(tokens[i][1]);
allSelectors[selector] = [i].concat(allSelectors[selector] || []);
if (allSelectors[selector].length == 2)
repeatedSelectors.push(selector);
}
for (i = repeatedSelectors.length - 1; i >= 0; i--) {
var positions = allSelectors[repeatedSelectors[i]];
selectorIterator:
for (var j = positions.length - 1; j > 0; j--) {
var positionOne = positions[j - 1];
var tokenOne = tokens[positionOne];
var positionTwo = positions[j];
var tokenTwo = tokens[positionTwo];
directionIterator:
for (var direction = 1; direction >= -1; direction -= 2) {
var topToBottom = direction == 1;
var from = topToBottom ? positionOne + 1 : positionTwo - 1;
var to = topToBottom ? positionTwo : positionOne;
var delta = topToBottom ? 1 : -1;
var moved = topToBottom ? tokenOne : tokenTwo;
var target = topToBottom ? tokenTwo : tokenOne;
var movedProperties = extractProperties(moved);
var joinAt;
while (from != to) {
var traversedProperties = extractProperties(tokens[from]);
from += delta;
// traversed then moved as we move selectors towards the start
var reorderable = topToBottom ?
canReorder(movedProperties, traversedProperties) :
canReorder(traversedProperties, movedProperties);
if (!reorderable && !topToBottom)
continue selectorIterator;
if (!reorderable && topToBottom)
continue directionIterator;
}
if (topToBottom) {
joinAt = [moved[2].length];
Array.prototype.push.apply(moved[2], target[2]);
target[2] = moved[2];
} else {
joinAt = [target[2].length];
Array.prototype.push.apply(target[2], moved[2]);
}
optimizeProperties(target[1], target[2], joinAt, true, options, context);
moved[2] = [];
}
}
}
}
module.exports = mergeNonAdjacentBySelector;
@@ -0,0 +1,172 @@
var optimizeProperties = require('../properties/optimizer');
var stringifyBody = require('../stringifier/one-time').body;
var stringifySelectors = require('../stringifier/one-time').selectors;
var isSpecial = require('./is-special');
var cloneArray = require('../utils/clone-array');
function reduceNonAdjacent(tokens, options, context) {
var candidates = {};
var repeated = [];
for (var i = tokens.length - 1; i >= 0; i--) {
var token = tokens[i];
if (token[0] != 'selector')
continue;
if (token[2].length === 0)
continue;
var selectorAsString = stringifySelectors(token[1]);
var isComplexAndNotSpecial = token[1].length > 1 && !isSpecial(options, selectorAsString);
var wrappedSelectors = options.sourceMap ? wrappedSelectorsFrom(token[1]) : token[1];
var selectors = isComplexAndNotSpecial ?
[selectorAsString].concat(wrappedSelectors) :
[selectorAsString];
for (var j = 0, m = selectors.length; j < m; j++) {
var selector = selectors[j];
if (!candidates[selector])
candidates[selector] = [];
else
repeated.push(selector);
candidates[selector].push({
where: i,
list: wrappedSelectors,
isPartial: isComplexAndNotSpecial && j > 0,
isComplex: isComplexAndNotSpecial && j === 0
});
}
}
reduceSimpleNonAdjacentCases(tokens, repeated, candidates, options, context);
reduceComplexNonAdjacentCases(tokens, candidates, options, context);
}
function wrappedSelectorsFrom(list) {
var wrapped = [];
for (var i = 0; i < list.length; i++) {
wrapped.push([list[i][0]]);
}
return wrapped;
}
function reduceSimpleNonAdjacentCases(tokens, repeated, candidates, options, context) {
function filterOut(idx, bodies) {
return data[idx].isPartial && bodies.length === 0;
}
function reduceBody(token, newBody, processedCount, tokenIdx) {
if (!data[processedCount - tokenIdx - 1].isPartial)
token[2] = newBody;
}
for (var i = 0, l = repeated.length; i < l; i++) {
var selector = repeated[i];
var data = candidates[selector];
reduceSelector(tokens, selector, data, {
filterOut: filterOut,
callback: reduceBody
}, options, context);
}
}
function reduceComplexNonAdjacentCases(tokens, candidates, options, context) {
var localContext = {};
function filterOut(idx) {
return localContext.data[idx].where < localContext.intoPosition;
}
function collectReducedBodies(token, newBody, processedCount, tokenIdx) {
if (tokenIdx === 0)
localContext.reducedBodies.push(newBody);
}
allSelectors:
for (var complexSelector in candidates) {
var into = candidates[complexSelector];
if (!into[0].isComplex)
continue;
var intoPosition = into[into.length - 1].where;
var intoToken = tokens[intoPosition];
var reducedBodies = [];
var selectors = isSpecial(options, complexSelector) ?
[complexSelector] :
into[0].list;
localContext.intoPosition = intoPosition;
localContext.reducedBodies = reducedBodies;
for (var j = 0, m = selectors.length; j < m; j++) {
var selector = selectors[j];
var data = candidates[selector];
if (data.length < 2)
continue allSelectors;
localContext.data = data;
reduceSelector(tokens, selector, data, {
filterOut: filterOut,
callback: collectReducedBodies
}, options, context);
if (stringifyBody(reducedBodies[reducedBodies.length - 1]) != stringifyBody(reducedBodies[0]))
continue allSelectors;
}
intoToken[2] = reducedBodies[0];
}
}
function reduceSelector(tokens, selector, data, context, options, outerContext) {
var bodies = [];
var bodiesAsList = [];
var joinsAt = [];
var processedTokens = [];
for (var j = data.length - 1, m = 0; j >= 0; j--) {
if (context.filterOut(j, bodies))
continue;
var where = data[j].where;
var token = tokens[where];
var clonedBody = cloneArray(token[2]);
bodies = bodies.concat(clonedBody);
bodiesAsList.push(clonedBody);
processedTokens.push(where);
}
for (j = 0, m = bodiesAsList.length; j < m; j++) {
if (bodiesAsList[j].length > 0)
joinsAt.push((joinsAt.length > 0 ? joinsAt[joinsAt.length - 1] : 0) + bodiesAsList[j].length);
}
optimizeProperties(selector, bodies, joinsAt, false, options, outerContext);
var processedCount = processedTokens.length;
var propertyIdx = bodies.length - 1;
var tokenIdx = processedCount - 1;
while (tokenIdx >= 0) {
if ((tokenIdx === 0 || (bodies[propertyIdx] && bodiesAsList[tokenIdx].indexOf(bodies[propertyIdx]) > -1)) && propertyIdx > -1) {
propertyIdx--;
continue;
}
var newBody = bodies.splice(propertyIdx + 1);
context.callback(tokens[processedTokens[tokenIdx]], newBody, processedCount, tokenIdx);
tokenIdx--;
}
}
module.exports = reduceNonAdjacent;
@@ -0,0 +1,21 @@
var stringifyAll = require('../stringifier/one-time').all;
function removeDuplicateMediaQueries(tokens) {
var candidates = {};
for (var i = 0, l = tokens.length; i < l; i++) {
var token = tokens[i];
if (token[0] != 'block')
continue;
var key = token[1][0] + '%' + stringifyAll(token[2]);
var candidate = candidates[key];
if (candidate)
candidate[2] = [];
candidates[key] = token;
}
}
module.exports = removeDuplicateMediaQueries;
@@ -0,0 +1,41 @@
var stringifyBody = require('../stringifier/one-time').body;
var stringifySelectors = require('../stringifier/one-time').selectors;
function removeDuplicates(tokens) {
var matched = {};
var moreThanOnce = [];
var id, token;
var body, bodies;
for (var i = 0, l = tokens.length; i < l; i++) {
token = tokens[i];
if (token[0] != 'selector')
continue;
id = stringifySelectors(token[1]);
if (matched[id] && matched[id].length == 1)
moreThanOnce.push(id);
else
matched[id] = matched[id] || [];
matched[id].push(i);
}
for (i = 0, l = moreThanOnce.length; i < l; i++) {
id = moreThanOnce[i];
bodies = [];
for (var j = matched[id].length - 1; j >= 0; j--) {
token = tokens[matched[id][j]];
body = stringifyBody(token[2]);
if (bodies.indexOf(body) > -1)
token[2] = [];
else
bodies.push(body);
}
}
}
module.exports = removeDuplicates;
@@ -0,0 +1,99 @@
// TODO: it'd be great to merge it with the other canReorder functionality
var FLEX_PROPERTIES = /align\-items|box\-align|box\-pack|flex|justify/;
var BORDER_PROPERTIES = /^border\-(top|right|bottom|left|color|style|width|radius)/;
function canReorder(left, right) {
for (var i = right.length - 1; i >= 0; i--) {
for (var j = left.length - 1; j >= 0; j--) {
if (!canReorderSingle(left[j], right[i]))
return false;
}
}
return true;
}
function canReorderSingle(left, right) {
var leftName = left[0];
var leftValue = left[1];
var leftNameRoot = left[2];
var leftSelector = left[5];
var leftInSpecificSelector = left[6];
var rightName = right[0];
var rightValue = right[1];
var rightNameRoot = right[2];
var rightSelector = right[5];
var rightInSpecificSelector = right[6];
if (leftName == 'font' && rightName == 'line-height' || rightName == 'font' && leftName == 'line-height')
return false;
if (FLEX_PROPERTIES.test(leftName) && FLEX_PROPERTIES.test(rightName))
return false;
if (leftNameRoot == rightNameRoot && unprefixed(leftName) == unprefixed(rightName) && (vendorPrefixed(leftName) ^ vendorPrefixed(rightName)))
return false;
if (leftNameRoot == 'border' && BORDER_PROPERTIES.test(rightNameRoot) && (leftName == 'border' || leftName == rightNameRoot || (leftValue != rightValue && sameBorderComponent(leftName, rightName))))
return false;
if (rightNameRoot == 'border' && BORDER_PROPERTIES.test(leftNameRoot) && (rightName == 'border' || rightName == leftNameRoot || (leftValue != rightValue && sameBorderComponent(leftName, rightName))))
return false;
if (leftNameRoot == 'border' && rightNameRoot == 'border' && leftName != rightName && (isSideBorder(leftName) && isStyleBorder(rightName) || isStyleBorder(leftName) && isSideBorder(rightName)))
return false;
if (leftNameRoot != rightNameRoot)
return true;
if (leftName == rightName && leftNameRoot == rightNameRoot && (leftValue == rightValue || withDifferentVendorPrefix(leftValue, rightValue)))
return true;
if (leftName != rightName && leftNameRoot == rightNameRoot && leftName != leftNameRoot && rightName != rightNameRoot)
return true;
if (leftName != rightName && leftNameRoot == rightNameRoot && leftValue == rightValue)
return true;
if (rightInSpecificSelector && leftInSpecificSelector && !inheritable(leftNameRoot) && !inheritable(rightNameRoot) && selectorsDoNotOverlap(rightSelector, leftSelector))
return true;
return false;
}
function vendorPrefixed(name) {
return /^\-(?:moz|webkit|ms|o)\-/.test(name);
}
function unprefixed(name) {
return name.replace(/^\-(?:moz|webkit|ms|o)\-/, '');
}
function sameBorderComponent(name1, name2) {
return name1.split('-').pop() == name2.split('-').pop();
}
function isSideBorder(name) {
return name == 'border-top' || name == 'border-right' || name == 'border-bottom' || name == 'border-left';
}
function isStyleBorder(name) {
return name == 'border-color' || name == 'border-style' || name == 'border-width';
}
function withDifferentVendorPrefix(value1, value2) {
return vendorPrefixed(value1) && vendorPrefixed(value2) && value1.split('-')[1] != value2.split('-')[2];
}
function selectorsDoNotOverlap(s1, s2) {
for (var i = 0, l = s1.length; i < l; i++) {
for (var j = 0, m = s2.length; j < m; j++) {
if (s1[i][0] == s2[j][0])
return false;
}
}
return true;
}
function inheritable(name) {
// According to http://www.w3.org/TR/CSS21/propidx.html
// Others will be catched by other, preceeding rules
return name == 'font' || name == 'line-height' || name == 'list-style';
}
module.exports = {
canReorder: canReorder,
canReorderSingle: canReorderSingle
};
@@ -0,0 +1,369 @@
var extractProperties = require('./extractor');
var canReorderSingle = require('./reorderable').canReorderSingle;
var stringifyBody = require('../stringifier/one-time').body;
var stringifySelectors = require('../stringifier/one-time').selectors;
var cleanUpSelectorDuplicates = require('./clean-up').selectorDuplicates;
var isSpecial = require('./is-special');
var cloneArray = require('../utils/clone-array');
function naturalSorter(a, b) {
return a > b;
}
function cloneAndMergeSelectors(propertyA, propertyB) {
var cloned = cloneArray(propertyA);
cloned[5] = cloned[5].concat(propertyB[5]);
return cloned;
}
function restructure(tokens, options) {
var movableTokens = {};
var movedProperties = [];
var multiPropertyMoveCache = {};
var movedToBeDropped = [];
var maxCombinationsLevel = 2;
var ID_JOIN_CHARACTER = '%';
function sendToMultiPropertyMoveCache(position, movedProperty, allFits) {
for (var i = allFits.length - 1; i >= 0; i--) {
var fit = allFits[i][0];
var id = addToCache(movedProperty, fit);
if (multiPropertyMoveCache[id].length > 1 && processMultiPropertyMove(position, multiPropertyMoveCache[id])) {
removeAllMatchingFromCache(id);
break;
}
}
}
function addToCache(movedProperty, fit) {
var id = cacheId(fit);
multiPropertyMoveCache[id] = multiPropertyMoveCache[id] || [];
multiPropertyMoveCache[id].push([movedProperty, fit]);
return id;
}
function removeAllMatchingFromCache(matchId) {
var matchSelectors = matchId.split(ID_JOIN_CHARACTER);
var forRemoval = [];
var i;
for (var id in multiPropertyMoveCache) {
var selectors = id.split(ID_JOIN_CHARACTER);
for (i = selectors.length - 1; i >= 0; i--) {
if (matchSelectors.indexOf(selectors[i]) > -1) {
forRemoval.push(id);
break;
}
}
}
for (i = forRemoval.length - 1; i >= 0; i--) {
delete multiPropertyMoveCache[forRemoval[i]];
}
}
function cacheId(cachedTokens) {
var id = [];
for (var i = 0, l = cachedTokens.length; i < l; i++) {
id.push(stringifySelectors(cachedTokens[i][1]));
}
return id.join(ID_JOIN_CHARACTER);
}
function tokensToMerge(sourceTokens) {
var uniqueTokensWithBody = [];
var mergeableTokens = [];
for (var i = sourceTokens.length - 1; i >= 0; i--) {
if (isSpecial(options, stringifySelectors(sourceTokens[i][1])))
continue;
mergeableTokens.unshift(sourceTokens[i]);
if (sourceTokens[i][2].length > 0 && uniqueTokensWithBody.indexOf(sourceTokens[i]) == -1)
uniqueTokensWithBody.push(sourceTokens[i]);
}
return uniqueTokensWithBody.length > 1 ?
mergeableTokens :
[];
}
function shortenIfPossible(position, movedProperty) {
var name = movedProperty[0];
var value = movedProperty[1];
var key = movedProperty[4];
var valueSize = name.length + value.length + 1;
var allSelectors = [];
var qualifiedTokens = [];
var mergeableTokens = tokensToMerge(movableTokens[key]);
if (mergeableTokens.length < 2)
return;
var allFits = findAllFits(mergeableTokens, valueSize, 1);
var bestFit = allFits[0];
if (bestFit[1] > 0)
return sendToMultiPropertyMoveCache(position, movedProperty, allFits);
for (var i = bestFit[0].length - 1; i >=0; i--) {
allSelectors = bestFit[0][i][1].concat(allSelectors);
qualifiedTokens.unshift(bestFit[0][i]);
}
allSelectors = cleanUpSelectorDuplicates(allSelectors);
dropAsNewTokenAt(position, [movedProperty], allSelectors, qualifiedTokens);
}
function fitSorter(fit1, fit2) {
return fit1[1] > fit2[1];
}
function findAllFits(mergeableTokens, propertySize, propertiesCount) {
var combinations = allCombinations(mergeableTokens, propertySize, propertiesCount, maxCombinationsLevel - 1);
return combinations.sort(fitSorter);
}
function allCombinations(tokensVariant, propertySize, propertiesCount, level) {
var differenceVariants = [[tokensVariant, sizeDifference(tokensVariant, propertySize, propertiesCount)]];
if (tokensVariant.length > 2 && level > 0) {
for (var i = tokensVariant.length - 1; i >= 0; i--) {
var subVariant = Array.prototype.slice.call(tokensVariant, 0);
subVariant.splice(i, 1);
differenceVariants = differenceVariants.concat(allCombinations(subVariant, propertySize, propertiesCount, level - 1));
}
}
return differenceVariants;
}
function sizeDifference(tokensVariant, propertySize, propertiesCount) {
var allSelectorsSize = 0;
for (var i = tokensVariant.length - 1; i >= 0; i--) {
allSelectorsSize += tokensVariant[i][2].length > propertiesCount ? stringifySelectors(tokensVariant[i][1]).length : -1;
}
return allSelectorsSize - (tokensVariant.length - 1) * propertySize + 1;
}
function dropAsNewTokenAt(position, properties, allSelectors, mergeableTokens) {
var i, j, k, m;
var allProperties = [];
for (i = mergeableTokens.length - 1; i >= 0; i--) {
var mergeableToken = mergeableTokens[i];
for (j = mergeableToken[2].length - 1; j >= 0; j--) {
var mergeableProperty = mergeableToken[2][j];
for (k = 0, m = properties.length; k < m; k++) {
var property = properties[k];
var mergeablePropertyName = mergeableProperty[0][0];
var propertyName = property[0];
var propertyBody = property[4];
if (mergeablePropertyName == propertyName && stringifyBody([mergeableProperty]) == propertyBody) {
mergeableToken[2].splice(j, 1);
break;
}
}
}
}
for (i = properties.length - 1; i >= 0; i--) {
allProperties.unshift(properties[i][3]);
}
var newToken = ['selector', allSelectors, allProperties];
tokens.splice(position, 0, newToken);
}
function dropPropertiesAt(position, movedProperty) {
var key = movedProperty[4];
var toMove = movableTokens[key];
if (toMove && toMove.length > 1) {
if (!shortenMultiMovesIfPossible(position, movedProperty))
shortenIfPossible(position, movedProperty);
}
}
function shortenMultiMovesIfPossible(position, movedProperty) {
var candidates = [];
var propertiesAndMergableTokens = [];
var key = movedProperty[4];
var j, k;
var mergeableTokens = tokensToMerge(movableTokens[key]);
if (mergeableTokens.length < 2)
return;
movableLoop:
for (var value in movableTokens) {
var tokensList = movableTokens[value];
for (j = mergeableTokens.length - 1; j >= 0; j--) {
if (tokensList.indexOf(mergeableTokens[j]) == -1)
continue movableLoop;
}
candidates.push(value);
}
if (candidates.length < 2)
return false;
for (j = candidates.length - 1; j >= 0; j--) {
for (k = movedProperties.length - 1; k >= 0; k--) {
if (movedProperties[k][4] == candidates[j]) {
propertiesAndMergableTokens.unshift([movedProperties[k], mergeableTokens]);
break;
}
}
}
return processMultiPropertyMove(position, propertiesAndMergableTokens);
}
function processMultiPropertyMove(position, propertiesAndMergableTokens) {
var valueSize = 0;
var properties = [];
var property;
for (var i = propertiesAndMergableTokens.length - 1; i >= 0; i--) {
property = propertiesAndMergableTokens[i][0];
var fullValue = property[4];
valueSize += fullValue.length + (i > 0 ? 1 : 0);
properties.push(property);
}
var mergeableTokens = propertiesAndMergableTokens[0][1];
var bestFit = findAllFits(mergeableTokens, valueSize, properties.length)[0];
if (bestFit[1] > 0)
return false;
var allSelectors = [];
var qualifiedTokens = [];
for (i = bestFit[0].length - 1; i >= 0; i--) {
allSelectors = bestFit[0][i][1].concat(allSelectors);
qualifiedTokens.unshift(bestFit[0][i]);
}
allSelectors = cleanUpSelectorDuplicates(allSelectors);
dropAsNewTokenAt(position, properties, allSelectors, qualifiedTokens);
for (i = properties.length - 1; i >= 0; i--) {
property = properties[i];
var index = movedProperties.indexOf(property);
delete movableTokens[property[4]];
if (index > -1 && movedToBeDropped.indexOf(index) == -1)
movedToBeDropped.push(index);
}
return true;
}
function boundToAnotherPropertyInCurrrentToken(property, movedProperty, token) {
var propertyName = property[0];
var movedPropertyName = movedProperty[0];
if (propertyName != movedPropertyName)
return false;
var key = movedProperty[4];
var toMove = movableTokens[key];
return toMove && toMove.indexOf(token) > -1;
}
for (var i = tokens.length - 1; i >= 0; i--) {
var token = tokens[i];
var isSelector;
var j, k, m;
var samePropertyAt;
if (token[0] == 'selector') {
isSelector = true;
} else if (token[0] == 'block') {
isSelector = false;
} else {
continue;
}
// We cache movedProperties.length as it may change in the loop
var movedCount = movedProperties.length;
var properties = extractProperties(token);
movedToBeDropped = [];
var unmovableInCurrentToken = [];
for (j = properties.length - 1; j >= 0; j--) {
for (k = j - 1; k >= 0; k--) {
if (!canReorderSingle(properties[j], properties[k])) {
unmovableInCurrentToken.push(j);
break;
}
}
}
for (j = properties.length - 1; j >= 0; j--) {
var property = properties[j];
var movedSameProperty = false;
for (k = 0; k < movedCount; k++) {
var movedProperty = movedProperties[k];
if (movedToBeDropped.indexOf(k) == -1 && !canReorderSingle(property, movedProperty) && !boundToAnotherPropertyInCurrrentToken(property, movedProperty, token)) {
dropPropertiesAt(i + 1, movedProperty, token);
if (movedToBeDropped.indexOf(k) == -1) {
movedToBeDropped.push(k);
delete movableTokens[movedProperty[4]];
}
}
if (!movedSameProperty) {
movedSameProperty = property[0] == movedProperty[0] && property[1] == movedProperty[1];
if (movedSameProperty) {
samePropertyAt = k;
}
}
}
if (!isSelector || unmovableInCurrentToken.indexOf(j) > -1)
continue;
var key = property[4];
movableTokens[key] = movableTokens[key] || [];
movableTokens[key].push(token);
if (movedSameProperty) {
movedProperties[samePropertyAt] = cloneAndMergeSelectors(movedProperties[samePropertyAt], property);
} else {
movedProperties.push(property);
}
}
movedToBeDropped = movedToBeDropped.sort(naturalSorter);
for (j = 0, m = movedToBeDropped.length; j < m; j++) {
var dropAt = movedToBeDropped[j] - j;
movedProperties.splice(dropAt, 1);
}
}
var position = tokens[0] && tokens[0][0] == 'at-rule' && tokens[0][1][0].indexOf('@charset') === 0 ? 1 : 0;
for (; position < tokens.length - 1; position++) {
var isImportRule = tokens[position][0] === 'at-rule' && tokens[position][1][0].indexOf('@import') === 0;
var isEscapedCommentSpecial = tokens[position][0] === 'text' && tokens[position][1][0].indexOf('__ESCAPED_COMMENT_SPECIAL') === 0;
if (!(isImportRule || isEscapedCommentSpecial))
break;
}
for (i = 0; i < movedProperties.length; i++) {
dropPropertiesAt(position, movedProperties[i]);
}
}
module.exports = restructure;
+454
View File
@@ -0,0 +1,454 @@
var cleanUpSelectors = require('./clean-up').selectors;
var cleanUpBlock = require('./clean-up').block;
var cleanUpAtRule = require('./clean-up').atRule;
var split = require('../utils/split');
var RGB = require('../colors/rgb');
var HSL = require('../colors/hsl');
var HexNameShortener = require('../colors/hex-name-shortener');
var wrapForOptimizing = require('../properties/wrap-for-optimizing').all;
var restoreFromOptimizing = require('../properties/restore-from-optimizing');
var removeUnused = require('../properties/remove-unused');
var DEFAULT_ROUNDING_PRECISION = 2;
var CHARSET_TOKEN = '@charset';
var CHARSET_REGEXP = new RegExp('^' + CHARSET_TOKEN, 'i');
var IMPORT_REGEXP = /^@import["'\s]/i;
var FONT_NUMERAL_WEIGHTS = ['100', '200', '300', '400', '500', '600', '700', '800', '900'];
var FONT_NAME_WEIGHTS = ['normal', 'bold', 'bolder', 'lighter'];
var FONT_NAME_WEIGHTS_WITHOUT_NORMAL = ['bold', 'bolder', 'lighter'];
var WHOLE_PIXEL_VALUE = /(?:^|\s|\()(-?\d+)px/;
var TIME_VALUE = /^(\-?[\d\.]+)(m?s)$/;
var valueMinifiers = {
'background': function (value, index, total) {
return index === 0 && total == 1 && (value == 'none' || value == 'transparent') ? '0 0' : value;
},
'font-weight': function (value) {
if (value == 'normal')
return '400';
else if (value == 'bold')
return '700';
else
return value;
},
'outline': function (value, index, total) {
return index === 0 && total == 1 && value == 'none' ? '0' : value;
}
};
function isNegative(property, idx) {
return property.value[idx] && property.value[idx][0][0] == '-' && parseFloat(property.value[idx][0]) < 0;
}
function zeroMinifier(name, value) {
if (value.indexOf('0') == -1)
return value;
if (value.indexOf('-') > -1) {
value = value
.replace(/([^\w\d\-]|^)\-0([^\.]|$)/g, '$10$2')
.replace(/([^\w\d\-]|^)\-0([^\.]|$)/g, '$10$2');
}
return value
.replace(/(^|\s)0+([1-9])/g, '$1$2')
.replace(/(^|\D)\.0+(\D|$)/g, '$10$2')
.replace(/(^|\D)\.0+(\D|$)/g, '$10$2')
.replace(/\.([1-9]*)0+(\D|$)/g, function (match, nonZeroPart, suffix) {
return (nonZeroPart.length > 0 ? '.' : '') + nonZeroPart + suffix;
})
.replace(/(^|\D)0\.(\d)/g, '$1.$2');
}
function zeroDegMinifier(_, value) {
if (value.indexOf('0deg') == -1)
return value;
return value.replace(/\(0deg\)/g, '(0)');
}
function whitespaceMinifier(name, value) {
if (name.indexOf('filter') > -1 || value.indexOf(' ') == -1)
return value;
value = value.replace(/\s+/g, ' ');
if (value.indexOf('calc') > -1)
value = value.replace(/\) ?\/ ?/g, ')/ ');
return value
.replace(/\( /g, '(')
.replace(/ \)/g, ')')
.replace(/, /g, ',');
}
function precisionMinifier(_, value, precisionOptions) {
if (precisionOptions.value === -1 || value.indexOf('.') === -1)
return value;
return value
.replace(precisionOptions.regexp, function (match, number) {
return Math.round(parseFloat(number) * precisionOptions.multiplier) / precisionOptions.multiplier + 'px';
})
.replace(/(\d)\.($|\D)/g, '$1$2');
}
function unitMinifier(name, value, unitsRegexp) {
if (/^(?:\-moz\-calc|\-webkit\-calc|calc)\(/.test(value))
return value;
if (name == 'flex' || name == '-ms-flex' || name == '-webkit-flex' || name == 'flex-basis' || name == '-webkit-flex-basis')
return value;
if (value.indexOf('%') > 0 && (name == 'height' || name == 'max-height'))
return value;
return value
.replace(unitsRegexp, '$1' + '0' + '$2')
.replace(unitsRegexp, '$1' + '0' + '$2');
}
function multipleZerosMinifier(property) {
var values = property.value;
var spliceAt;
if (values.length == 4 && values[0][0] === '0' && values[1][0] === '0' && values[2][0] === '0' && values[3][0] === '0') {
if (property.name.indexOf('box-shadow') > -1)
spliceAt = 2;
else
spliceAt = 1;
}
if (spliceAt) {
property.value.splice(spliceAt);
property.dirty = true;
}
}
function colorMininifier(name, value, compatibility) {
if (value.indexOf('#') === -1 && value.indexOf('rgb') == -1 && value.indexOf('hsl') == -1)
return HexNameShortener.shorten(value);
value = value
.replace(/rgb\((\-?\d+),(\-?\d+),(\-?\d+)\)/g, function (match, red, green, blue) {
return new RGB(red, green, blue).toHex();
})
.replace(/hsl\((-?\d+),(-?\d+)%?,(-?\d+)%?\)/g, function (match, hue, saturation, lightness) {
return new HSL(hue, saturation, lightness).toHex();
})
.replace(/(^|[^='"])#([0-9a-f]{6})/gi, function (match, prefix, color) {
if (color[0] == color[1] && color[2] == color[3] && color[4] == color[5])
return prefix + '#' + color[0] + color[2] + color[4];
else
return prefix + '#' + color;
})
.replace(/(rgb|rgba|hsl|hsla)\(([^\)]+)\)/g, function (match, colorFunction, colorDef) {
var tokens = colorDef.split(',');
var applies = (colorFunction == 'hsl' && tokens.length == 3) ||
(colorFunction == 'hsla' && tokens.length == 4) ||
(colorFunction == 'rgb' && tokens.length == 3 && colorDef.indexOf('%') > 0) ||
(colorFunction == 'rgba' && tokens.length == 4 && colorDef.indexOf('%') > 0);
if (!applies)
return match;
if (tokens[1].indexOf('%') == -1)
tokens[1] += '%';
if (tokens[2].indexOf('%') == -1)
tokens[2] += '%';
return colorFunction + '(' + tokens.join(',') + ')';
});
if (compatibility.colors.opacity && name.indexOf('background') == -1) {
value = value.replace(/(?:rgba|hsla)\(0,0%?,0%?,0\)/g, function (match) {
if (split(value, ',').pop().indexOf('gradient(') > -1)
return match;
return 'transparent';
});
}
return HexNameShortener.shorten(value);
}
function pixelLengthMinifier(_, value, compatibility) {
if (!WHOLE_PIXEL_VALUE.test(value))
return value;
return value.replace(WHOLE_PIXEL_VALUE, function (match, val) {
var newValue;
var intVal = parseInt(val);
if (intVal === 0)
return match;
if (compatibility.properties.shorterLengthUnits && compatibility.units.pt && intVal * 3 % 4 === 0)
newValue = intVal * 3 / 4 + 'pt';
if (compatibility.properties.shorterLengthUnits && compatibility.units.pc && intVal % 16 === 0)
newValue = intVal / 16 + 'pc';
if (compatibility.properties.shorterLengthUnits && compatibility.units.in && intVal % 96 === 0)
newValue = intVal / 96 + 'in';
if (newValue)
newValue = match.substring(0, match.indexOf(val)) + newValue;
return newValue && newValue.length < match.length ? newValue : match;
});
}
function timeUnitMinifier(_, value) {
if (!TIME_VALUE.test(value))
return value;
return value.replace(TIME_VALUE, function (match, val, unit) {
var newValue;
if (unit == 'ms') {
newValue = parseInt(val) / 1000 + 's';
} else if (unit == 's') {
newValue = parseFloat(val) * 1000 + 'ms';
}
return newValue.length < match.length ? newValue : match;
});
}
function minifyBorderRadius(property) {
var values = property.value;
var spliceAt;
if (values.length == 3 && values[1][0] == '/' && values[0][0] == values[2][0])
spliceAt = 1;
else if (values.length == 5 && values[2][0] == '/' && values[0][0] == values[3][0] && values[1][0] == values[4][0])
spliceAt = 2;
else if (values.length == 7 && values[3][0] == '/' && values[0][0] == values[4][0] && values[1][0] == values[5][0] && values[2][0] == values[6][0])
spliceAt = 3;
else if (values.length == 9 && values[4][0] == '/' && values[0][0] == values[5][0] && values[1][0] == values[6][0] && values[2][0] == values[7][0] && values[3][0] == values[8][0])
spliceAt = 4;
if (spliceAt) {
property.value.splice(spliceAt);
property.dirty = true;
}
}
function minifyFilter(property) {
if (property.value.length == 1) {
property.value[0][0] = property.value[0][0].replace(/progid:DXImageTransform\.Microsoft\.(Alpha|Chroma)(\W)/, function (match, filter, suffix) {
return filter.toLowerCase() + suffix;
});
}
property.value[0][0] = property.value[0][0]
.replace(/,(\S)/g, ', $1')
.replace(/ ?= ?/g, '=');
}
function minifyFont(property) {
var values = property.value;
var hasNumeral = FONT_NUMERAL_WEIGHTS.indexOf(values[0][0]) > -1 ||
values[1] && FONT_NUMERAL_WEIGHTS.indexOf(values[1][0]) > -1 ||
values[2] && FONT_NUMERAL_WEIGHTS.indexOf(values[2][0]) > -1;
if (hasNumeral)
return;
if (values[1] == '/')
return;
var normalCount = 0;
if (values[0][0] == 'normal')
normalCount++;
if (values[1] && values[1][0] == 'normal')
normalCount++;
if (values[2] && values[2][0] == 'normal')
normalCount++;
if (normalCount > 1)
return;
var toOptimize;
if (FONT_NAME_WEIGHTS_WITHOUT_NORMAL.indexOf(values[0][0]) > -1)
toOptimize = 0;
else if (values[1] && FONT_NAME_WEIGHTS_WITHOUT_NORMAL.indexOf(values[1][0]) > -1)
toOptimize = 1;
else if (values[2] && FONT_NAME_WEIGHTS_WITHOUT_NORMAL.indexOf(values[2][0]) > -1)
toOptimize = 2;
else if (FONT_NAME_WEIGHTS.indexOf(values[0][0]) > -1)
toOptimize = 0;
else if (values[1] && FONT_NAME_WEIGHTS.indexOf(values[1][0]) > -1)
toOptimize = 1;
else if (values[2] && FONT_NAME_WEIGHTS.indexOf(values[2][0]) > -1)
toOptimize = 2;
if (toOptimize !== undefined) {
property.value[toOptimize][0] = valueMinifiers['font-weight'](values[toOptimize][0]);
property.dirty = true;
}
}
function optimizeBody(properties, options) {
var property, name, value;
var _properties = wrapForOptimizing(properties);
for (var i = 0, l = _properties.length; i < l; i++) {
property = _properties[i];
name = property.name;
if (property.hack && (
(property.hack == 'star' || property.hack == 'underscore') && !options.compatibility.properties.iePrefixHack ||
property.hack == 'backslash' && !options.compatibility.properties.ieSuffixHack ||
property.hack == 'bang' && !options.compatibility.properties.ieBangHack))
property.unused = true;
if (name.indexOf('padding') === 0 && (isNegative(property, 0) || isNegative(property, 1) || isNegative(property, 2) || isNegative(property, 3)))
property.unused = true;
if (property.unused)
continue;
if (property.variable) {
if (property.block)
optimizeBody(property.value[0], options);
continue;
}
for (var j = 0, m = property.value.length; j < m; j++) {
value = property.value[j][0];
if (valueMinifiers[name])
value = valueMinifiers[name](value, j, m);
value = whitespaceMinifier(name, value);
value = precisionMinifier(name, value, options.precision);
value = pixelLengthMinifier(name, value, options.compatibility);
value = timeUnitMinifier(name, value);
value = zeroMinifier(name, value);
if (options.compatibility.properties.zeroUnits) {
value = zeroDegMinifier(name, value);
value = unitMinifier(name, value, options.unitsRegexp);
}
if (options.compatibility.properties.colors)
value = colorMininifier(name, value, options.compatibility);
property.value[j][0] = value;
}
multipleZerosMinifier(property);
if (name.indexOf('border') === 0 && name.indexOf('radius') > 0)
minifyBorderRadius(property);
else if (name == 'filter')
minifyFilter(property);
else if (name == 'font')
minifyFont(property);
}
restoreFromOptimizing(_properties, true);
removeUnused(_properties);
}
function cleanupCharsets(tokens) {
var hasCharset = false;
for (var i = 0, l = tokens.length; i < l; i++) {
var token = tokens[i];
if (token[0] != 'at-rule')
continue;
if (!CHARSET_REGEXP.test(token[1][0]))
continue;
if (hasCharset || token[1][0].indexOf(CHARSET_TOKEN) == -1) {
tokens.splice(i, 1);
i--;
l--;
} else {
hasCharset = true;
tokens.splice(i, 1);
tokens.unshift(['at-rule', [token[1][0].replace(CHARSET_REGEXP, CHARSET_TOKEN)]]);
}
}
}
function buildUnitRegexp(options) {
var units = ['px', 'em', 'ex', 'cm', 'mm', 'in', 'pt', 'pc', '%'];
var otherUnits = ['ch', 'rem', 'vh', 'vm', 'vmax', 'vmin', 'vw'];
otherUnits.forEach(function (unit) {
if (options.compatibility.units[unit])
units.push(unit);
});
return new RegExp('(^|\\s|\\(|,)0(?:' + units.join('|') + ')(\\W|$)', 'g');
}
function buildPrecision(options) {
var precision = {};
precision.value = options.roundingPrecision === undefined ?
DEFAULT_ROUNDING_PRECISION :
options.roundingPrecision;
precision.multiplier = Math.pow(10, precision.value);
precision.regexp = new RegExp('(\\d*\\.\\d{' + (precision.value + 1) + ',})px', 'g');
return precision;
}
function optimize(tokens, options, context) {
var ie7Hack = options.compatibility.selectors.ie7Hack;
var adjacentSpace = options.compatibility.selectors.adjacentSpace;
var spaceAfterClosingBrace = options.compatibility.properties.spaceAfterClosingBrace;
var mayHaveCharset = false;
var afterContent = false;
options.unitsRegexp = buildUnitRegexp(options);
options.precision = buildPrecision(options);
for (var i = 0, l = tokens.length; i < l; i++) {
var token = tokens[i];
switch (token[0]) {
case 'selector':
token[1] = cleanUpSelectors(token[1], !ie7Hack, adjacentSpace);
optimizeBody(token[2], options);
afterContent = true;
break;
case 'block':
cleanUpBlock(token[1], spaceAfterClosingBrace);
optimize(token[2], options, context);
afterContent = true;
break;
case 'flat-block':
cleanUpBlock(token[1], spaceAfterClosingBrace);
optimizeBody(token[2], options);
afterContent = true;
break;
case 'at-rule':
cleanUpAtRule(token[1]);
mayHaveCharset = true;
}
if (token[0] == 'at-rule' && IMPORT_REGEXP.test(token[1]) && afterContent) {
context.warnings.push('Ignoring @import rule "' + token[1] + '" as it appears after rules thus browsers will ignore them.');
token[1] = '';
}
if (token[1].length === 0 || (token[2] && token[2].length === 0)) {
tokens.splice(i, 1);
i--;
l--;
}
}
if (mayHaveCharset)
cleanupCharsets(tokens);
}
module.exports = optimize;
+119
View File
@@ -0,0 +1,119 @@
var escapePrefix = '__ESCAPED_';
function trackPrefix(value, context, interestingContent) {
if (!interestingContent && value.indexOf('\n') == -1) {
if (value.indexOf(escapePrefix) === 0) {
return value;
} else {
context.column += value.length;
return;
}
}
var withoutContent = 0;
var split = value.split('\n');
var total = split.length;
var shift = 0;
while (true) {
if (withoutContent == total - 1)
break;
var part = split[withoutContent];
if (/\S/.test(part))
break;
shift += part.length + 1;
withoutContent++;
}
context.line += withoutContent;
context.column = withoutContent > 0 ? 0 : context.column;
context.column += /^(\s)*/.exec(split[withoutContent])[0].length;
return value.substring(shift).trimLeft();
}
function sourceFor(originalMetadata, contextMetadata, context) {
var source = originalMetadata.source || contextMetadata.source;
if (source && context.resolvePath)
return context.resolvePath(contextMetadata.source, source);
return source;
}
function snapshot(data, context, fallbacks) {
var metadata = {
line: context.line,
column: context.column,
source: context.source
};
var sourceContent = null;
var sourceMetadata = context.sourceMapTracker.isTracking(metadata.source) ?
context.sourceMapTracker.originalPositionFor(metadata, data, fallbacks || 0) :
{};
metadata.line = sourceMetadata.line || metadata.line;
metadata.column = sourceMetadata.column || metadata.column;
metadata.source = sourceMetadata.sourceResolved ?
sourceMetadata.source :
sourceFor(sourceMetadata, metadata, context);
if (context.sourceMapInlineSources) {
var sourceMapSourcesContent = context.sourceMapTracker.sourcesContentFor(context.source);
sourceContent = sourceMapSourcesContent && sourceMapSourcesContent[metadata.source] ?
sourceMapSourcesContent :
context.sourceReader.sourceAt(context.source);
}
return sourceContent ?
[metadata.line, metadata.column, metadata.source, sourceContent] :
[metadata.line, metadata.column, metadata.source];
}
function trackSuffix(data, context) {
var parts = data.split('\n');
for (var i = 0, l = parts.length; i < l; i++) {
var part = parts[i];
var cursor = 0;
if (i > 0) {
context.line++;
context.column = 0;
}
while (true) {
var next = part.indexOf(escapePrefix, cursor);
if (next == -1) {
context.column += part.substring(cursor).length;
break;
}
context.column += next - cursor;
cursor += next - cursor;
var escaped = part.substring(next, part.indexOf('__', next + 1) + 2);
var encodedValues = escaped.substring(escaped.indexOf('(') + 1, escaped.indexOf(')')).split(',');
context.line += ~~encodedValues[0];
context.column = (~~encodedValues[0] === 0 ? context.column : 0) + ~~encodedValues[1];
cursor += escaped.length;
}
}
}
function track(data, context, snapshotMetadata, fallbacks) {
var untracked = trackPrefix(data, context, snapshotMetadata);
var metadata = snapshotMetadata ?
snapshot(untracked, context, fallbacks) :
[];
if (untracked)
trackSuffix(untracked, context);
return metadata;
}
module.exports = track;
@@ -0,0 +1,167 @@
var lineBreak = require('os').EOL;
var AT_RULE = 'at-rule';
var PROPERTY_SEPARATOR = ';';
function hasMoreProperties(tokens, index) {
for (var i = index, l = tokens.length; i < l; i++) {
if (typeof tokens[i] != 'string')
return true;
}
return false;
}
function supportsAfterClosingBrace(token) {
return token[0][0] == 'background' || token[0][0] == 'transform' || token[0][0] == 'src';
}
function afterClosingBrace(token, valueIndex) {
return token[valueIndex][0][token[valueIndex][0].length - 1] == ')' || token[valueIndex][0].indexOf('__ESCAPED_URL_CLEAN_CSS') === 0;
}
function afterComma(token, valueIndex) {
return token[valueIndex][0] == ',';
}
function afterSlash(token, valueIndex) {
return token[valueIndex][0] == '/';
}
function beforeComma(token, valueIndex) {
return token[valueIndex + 1] && token[valueIndex + 1][0] == ',';
}
function beforeSlash(token, valueIndex) {
return token[valueIndex + 1] && token[valueIndex + 1][0] == '/';
}
function inFilter(token) {
return token[0][0] == 'filter' || token[0][0] == '-ms-filter';
}
function inSpecialContext(token, valueIndex, context) {
return !context.spaceAfterClosingBrace && supportsAfterClosingBrace(token) && afterClosingBrace(token, valueIndex) ||
beforeSlash(token, valueIndex) ||
afterSlash(token, valueIndex) ||
beforeComma(token, valueIndex) ||
afterComma(token, valueIndex);
}
function selectors(tokens, context) {
var store = context.store;
for (var i = 0, l = tokens.length; i < l; i++) {
store(tokens[i], context);
if (i < l - 1)
store(',', context);
}
}
function body(tokens, context) {
for (var i = 0, l = tokens.length; i < l; i++) {
property(tokens, i, i == l - 1, context);
}
}
function property(tokens, position, isLast, context) {
var store = context.store;
var token = tokens[position];
if (typeof token == 'string') {
store(token, context);
} else if (token[0] == AT_RULE) {
propertyAtRule(token[1], false, context);
} else {
store(token[0], context);
store(':', context);
value(tokens, position, isLast, context);
}
}
function propertyAtRule(value, isLast, context) {
var store = context.store;
store(value, context);
if (!isLast)
store(PROPERTY_SEPARATOR, context);
}
function value(tokens, position, isLast, context) {
var store = context.store;
var token = tokens[position];
var isVariableDeclaration = token[0][0].indexOf('--') === 0;
var isBlockVariable = isVariableDeclaration && Array.isArray(token[1][0]);
if (isVariableDeclaration && isBlockVariable && atRulesOrProperties(token[1])) {
store('{', context);
body(token[1], context);
store('};', context);
return;
}
for (var j = 1, m = token.length; j < m; j++) {
store(token[j], context);
if (j < m - 1 && (inFilter(token) || !inSpecialContext(token, j, context))) {
store(' ', context);
} else if (j == m - 1 && !isLast && hasMoreProperties(tokens, position + 1)) {
store(PROPERTY_SEPARATOR, context);
}
}
}
function atRulesOrProperties(values) {
for (var i = 0, l = values.length; i < l; i++) {
if (values[i][0] == AT_RULE || Array.isArray(values[i][0]))
return true;
}
return false;
}
function all(tokens, context) {
var joinCharacter = context.keepBreaks ? lineBreak : '';
var store = context.store;
for (var i = 0, l = tokens.length; i < l; i++) {
var token = tokens[i];
switch (token[0]) {
case 'at-rule':
case 'text':
store(token[1][0], context);
store(joinCharacter, context);
break;
case 'block':
selectors([token[1]], context);
store('{', context);
all(token[2], context);
store('}', context);
store(joinCharacter, context);
break;
case 'flat-block':
selectors([token[1]], context);
store('{', context);
body(token[2], context);
store('}', context);
store(joinCharacter, context);
break;
default:
selectors(token[1], context);
store('{', context);
body(token[2], context);
store('}', context);
store(joinCharacter, context);
}
}
}
module.exports = {
all: all,
body: body,
property: property,
selectors: selectors,
value: value
};
@@ -0,0 +1,50 @@
var helpers = require('./helpers');
function store(token, context) {
context.output.push(typeof token == 'string' ? token : token[0]);
}
function context() {
return {
output: [],
store: store
};
}
function all(tokens) {
var fakeContext = context();
helpers.all(tokens, fakeContext);
return fakeContext.output.join('');
}
function body(tokens) {
var fakeContext = context();
helpers.body(tokens, fakeContext);
return fakeContext.output.join('');
}
function property(tokens, position) {
var fakeContext = context();
helpers.property(tokens, position, true, fakeContext);
return fakeContext.output.join('');
}
function selectors(tokens) {
var fakeContext = context();
helpers.selectors(tokens, fakeContext);
return fakeContext.output.join('');
}
function value(tokens, position) {
var fakeContext = context();
helpers.value(tokens, position, true, fakeContext);
return fakeContext.output.join('');
}
module.exports = {
all: all,
body: body,
property: property,
selectors: selectors,
value: value
};
@@ -0,0 +1,22 @@
var all = require('./helpers').all;
function store(token, context) {
context.output.push(typeof token == 'string' ? token : token[0]);
}
function stringify(tokens, options, restoreCallback) {
var context = {
keepBreaks: options.keepBreaks,
output: [],
spaceAfterClosingBrace: options.compatibility.properties.spaceAfterClosingBrace,
store: store
};
all(tokens, context, false);
return {
styles: restoreCallback(context.output.join('')).trim()
};
}
module.exports = stringify;
@@ -0,0 +1,96 @@
var SourceMapGenerator = require('source-map').SourceMapGenerator;
var all = require('./helpers').all;
var isWindows = process.platform == 'win32';
var unknownSource = '$stdin';
function store(element, context) {
var fromString = typeof element == 'string';
var value = fromString ? element : element[0];
if (value.indexOf('_') > -1)
value = context.restore(value, prefixContentFrom(context.output));
track(value, fromString ? null : element, context);
context.output.push(value);
}
function prefixContentFrom(values) {
var content = [];
for (var i = values.length - 1; i >= 0; i--) {
var value = values[i];
content.unshift(value);
if (value == '{' || value == ';')
break;
}
return content.join('');
}
function track(value, element, context) {
if (element)
trackAllMappings(element, context);
var parts = value.split('\n');
context.line += parts.length - 1;
context.column = parts.length > 1 ? 0 : (context.column + parts.pop().length);
}
function trackAllMappings(element, context) {
var mapping = element[element.length - 1];
if (!Array.isArray(mapping))
return;
for (var i = 0, l = mapping.length; i < l; i++) {
trackMapping(mapping[i], context);
}
}
function trackMapping(mapping, context) {
var source = mapping[2] || unknownSource;
if (isWindows)
source = source.replace(/\\/g, '/');
context.outputMap.addMapping({
generated: {
line: context.line,
column: context.column
},
source: source,
original: {
line: mapping[0],
column: mapping[1]
}
});
if (mapping[3])
context.outputMap.setSourceContent(source, mapping[3][mapping[2]]);
}
function stringify(tokens, options, restoreCallback, inputMapTracker) {
var context = {
column: 0,
inputMapTracker: inputMapTracker,
keepBreaks: options.keepBreaks,
line: 1,
output: [],
outputMap: new SourceMapGenerator(),
restore: restoreCallback,
sourceMapInlineSources: options.sourceMapInlineSources,
spaceAfterClosingBrace: options.compatibility.properties.spaceAfterClosingBrace,
store: store
};
all(tokens, context, false);
return {
sourceMap: context.outputMap,
styles: context.output.join('').trim()
};
}
module.exports = stringify;
@@ -0,0 +1,131 @@
var EscapeStore = require('./escape-store');
var QuoteScanner = require('../utils/quote-scanner');
var SPECIAL_COMMENT_PREFIX = '/*!';
var COMMENT_PREFIX = '/*';
var COMMENT_SUFFIX = '*/';
var lineBreak = require('os').EOL;
function CommentsProcessor(context, keepSpecialComments, keepBreaks, saveWaypoints) {
this.comments = new EscapeStore('COMMENT');
this.specialComments = new EscapeStore('COMMENT_SPECIAL');
this.context = context;
this.restored = 0;
this.keepAll = keepSpecialComments == '*';
this.keepOne = keepSpecialComments == '1' || keepSpecialComments === 1;
this.keepBreaks = keepBreaks;
this.saveWaypoints = saveWaypoints;
}
function quoteScannerFor(data) {
var quoteMap = [];
new QuoteScanner(data).each(function (quotedString, _, startsAt) {
quoteMap.push([startsAt, startsAt + quotedString.length]);
});
return function (position) {
for (var i = 0, l = quoteMap.length; i < l; i++) {
if (quoteMap[i][0] < position && quoteMap[i][1] > position)
return true;
}
return false;
};
}
CommentsProcessor.prototype.escape = function (data) {
var tempData = [];
var nextStart = 0;
var nextEnd = 0;
var cursor = 0;
var indent = 0;
var breaksCount;
var lastBreakAt;
var newIndent;
var isQuotedAt = quoteScannerFor(data);
var saveWaypoints = this.saveWaypoints;
for (; nextEnd < data.length;) {
nextStart = data.indexOf(COMMENT_PREFIX, cursor);
if (nextStart == -1)
break;
if (isQuotedAt(nextStart)) {
tempData.push(data.substring(cursor, nextStart + COMMENT_PREFIX.length));
cursor = nextStart + COMMENT_PREFIX.length;
continue;
}
nextEnd = data.indexOf(COMMENT_SUFFIX, nextStart + COMMENT_PREFIX.length);
if (nextEnd == -1) {
this.context.warnings.push('Broken comment: \'' + data.substring(nextStart) + '\'.');
nextEnd = data.length - 2;
}
tempData.push(data.substring(cursor, nextStart));
var comment = data.substring(nextStart, nextEnd + COMMENT_SUFFIX.length);
var isSpecialComment = comment.indexOf(SPECIAL_COMMENT_PREFIX) === 0;
if (saveWaypoints) {
breaksCount = comment.split(lineBreak).length - 1;
lastBreakAt = comment.lastIndexOf(lineBreak);
newIndent = lastBreakAt > 0 ?
comment.substring(lastBreakAt + lineBreak.length).length :
indent + comment.length;
}
if (saveWaypoints || isSpecialComment) {
var metadata = saveWaypoints ? [breaksCount, newIndent] : null;
var placeholder = isSpecialComment ?
this.specialComments.store(comment, metadata) :
this.comments.store(comment, metadata);
tempData.push(placeholder);
}
if (saveWaypoints)
indent = newIndent + 1;
cursor = nextEnd + COMMENT_SUFFIX.length;
}
return tempData.length > 0 ?
tempData.join('') + data.substring(cursor, data.length) :
data;
};
function restore(context, data, from, isSpecial) {
var tempData = [];
var cursor = 0;
for (; cursor < data.length;) {
var nextMatch = from.nextMatch(data, cursor);
if (nextMatch.start < 0)
break;
tempData.push(data.substring(cursor, nextMatch.start));
var comment = from.restore(nextMatch.match);
if (isSpecial && (context.keepAll || (context.keepOne && context.restored === 0))) {
context.restored++;
tempData.push(comment);
cursor = nextMatch.end;
} else {
cursor = nextMatch.end + (context.keepBreaks && data.substring(nextMatch.end, nextMatch.end + lineBreak.length) == lineBreak ? lineBreak.length : 0);
}
}
return tempData.length > 0 ?
tempData.join('') + data.substring(cursor, data.length) :
data;
}
CommentsProcessor.prototype.restore = function (data) {
data = restore(this, data, this.comments, false);
data = restore(this, data, this.specialComments, true);
return data;
};
module.exports = CommentsProcessor;
@@ -0,0 +1,53 @@
var placeholderBrace = '__';
function EscapeStore(placeholderRoot) {
this.placeholderRoot = 'ESCAPED_' + placeholderRoot + '_CLEAN_CSS';
this.placeholderToData = {};
this.dataToPlaceholder = {};
this.count = 0;
this.restoreMatcher = new RegExp(this.placeholderRoot + '(\\d+)');
}
EscapeStore.prototype._nextPlaceholder = function (metadata) {
return {
index: this.count,
value: placeholderBrace + this.placeholderRoot + this.count++ + metadata + placeholderBrace
};
};
EscapeStore.prototype.store = function (data, metadata) {
var encodedMetadata = metadata ?
'(' + metadata.join(',') + ')' :
'';
var placeholder = this.dataToPlaceholder[data];
if (!placeholder) {
var nextPlaceholder = this._nextPlaceholder(encodedMetadata);
placeholder = nextPlaceholder.value;
this.placeholderToData[nextPlaceholder.index] = data;
this.dataToPlaceholder[data] = nextPlaceholder.value;
}
if (metadata)
placeholder = placeholder.replace(/\([^\)]+\)/, encodedMetadata);
return placeholder;
};
EscapeStore.prototype.nextMatch = function (data, cursor) {
var next = {};
next.start = data.indexOf(this.placeholderRoot, cursor) - placeholderBrace.length;
next.end = data.indexOf(placeholderBrace, next.start + placeholderBrace.length) + placeholderBrace.length;
if (next.start > -1 && next.end > -1)
next.match = data.substring(next.start, next.end);
return next;
};
EscapeStore.prototype.restore = function (placeholder) {
var index = this.restoreMatcher.exec(placeholder)[1];
return this.placeholderToData[index];
};
module.exports = EscapeStore;
@@ -0,0 +1,117 @@
var EscapeStore = require('./escape-store');
var EXPRESSION_NAME = 'expression';
var EXPRESSION_START = '(';
var EXPRESSION_END = ')';
var EXPRESSION_PREFIX = EXPRESSION_NAME + EXPRESSION_START;
var BODY_START = '{';
var BODY_END = '}';
var lineBreak = require('os').EOL;
function findEnd(data, start) {
var end = start + EXPRESSION_NAME.length;
var level = 0;
var quoted = false;
var braced = false;
while (true) {
var current = data[end++];
if (quoted) {
quoted = current != '\'' && current != '"';
} else {
quoted = current == '\'' || current == '"';
if (current == EXPRESSION_START)
level++;
if (current == EXPRESSION_END)
level--;
if (current == BODY_START)
braced = true;
if (current == BODY_END && !braced && level == 1) {
end--;
level--;
}
}
if (level === 0 && current == EXPRESSION_END)
break;
if (!current) {
end = data.substring(0, end).lastIndexOf(BODY_END);
break;
}
}
return end;
}
function ExpressionsProcessor(saveWaypoints) {
this.expressions = new EscapeStore('EXPRESSION');
this.saveWaypoints = saveWaypoints;
}
ExpressionsProcessor.prototype.escape = function (data) {
var nextStart = 0;
var nextEnd = 0;
var cursor = 0;
var tempData = [];
var indent = 0;
var breaksCount;
var lastBreakAt;
var newIndent;
var saveWaypoints = this.saveWaypoints;
for (; nextEnd < data.length;) {
nextStart = data.indexOf(EXPRESSION_PREFIX, nextEnd);
if (nextStart == -1)
break;
nextEnd = findEnd(data, nextStart);
var expression = data.substring(nextStart, nextEnd);
if (saveWaypoints) {
breaksCount = expression.split(lineBreak).length - 1;
lastBreakAt = expression.lastIndexOf(lineBreak);
newIndent = lastBreakAt > 0 ?
expression.substring(lastBreakAt + lineBreak.length).length :
indent + expression.length;
}
var metadata = saveWaypoints ? [breaksCount, newIndent] : null;
var placeholder = this.expressions.store(expression, metadata);
tempData.push(data.substring(cursor, nextStart));
tempData.push(placeholder);
if (saveWaypoints)
indent = newIndent + 1;
cursor = nextEnd;
}
return tempData.length > 0 ?
tempData.join('') + data.substring(cursor, data.length) :
data;
};
ExpressionsProcessor.prototype.restore = function (data) {
var tempData = [];
var cursor = 0;
for (; cursor < data.length;) {
var nextMatch = this.expressions.nextMatch(data, cursor);
if (nextMatch.start < 0)
break;
tempData.push(data.substring(cursor, nextMatch.start));
var comment = this.expressions.restore(nextMatch.match);
tempData.push(comment);
cursor = nextMatch.end;
}
return tempData.length > 0 ?
tempData.join('') + data.substring(cursor, data.length) :
data;
};
module.exports = ExpressionsProcessor;
@@ -0,0 +1,98 @@
var EscapeStore = require('./escape-store');
var QuoteScanner = require('../utils/quote-scanner');
var lineBreak = require('os').EOL;
function FreeTextProcessor(saveWaypoints) {
this.matches = new EscapeStore('FREE_TEXT');
this.saveWaypoints = saveWaypoints;
}
// Strip content tags by replacing them by the a special
// marker for further restoring. It's done via string scanning
// instead of regexps to speed up the process.
FreeTextProcessor.prototype.escape = function (data) {
var self = this;
var breaksCount;
var lastBreakAt;
var indent;
var metadata;
var saveWaypoints = this.saveWaypoints;
return new QuoteScanner(data).each(function (match, store) {
if (saveWaypoints) {
breaksCount = match.split(lineBreak).length - 1;
lastBreakAt = match.lastIndexOf(lineBreak);
indent = lastBreakAt > 0 ?
match.substring(lastBreakAt + lineBreak.length).length :
match.length;
metadata = [breaksCount, indent];
}
var placeholder = self.matches.store(match, metadata);
store.push(placeholder);
});
};
function normalize(text, data, prefixContext, cursor) {
// FIXME: this is even a bigger hack now - see #407
var searchIn = data;
if (prefixContext) {
searchIn = prefixContext + data.substring(0, data.indexOf('__ESCAPED_FREE_TEXT_CLEAN_CSS'));
cursor = searchIn.length;
}
var lastSemicolon = searchIn.lastIndexOf(';', cursor);
var lastOpenBrace = searchIn.lastIndexOf('{', cursor);
var lastOne = 0;
if (lastSemicolon > -1 && lastOpenBrace > -1)
lastOne = Math.max(lastSemicolon, lastOpenBrace);
else if (lastSemicolon == -1)
lastOne = lastOpenBrace;
else
lastOne = lastSemicolon;
var context = searchIn.substring(lastOne + 1, cursor);
if (/\[[\w\d\-]+[\*\|\~\^\$]?=$/.test(context)) {
text = text
.replace(/\\\n|\\\r\n/g, '')
.replace(/\n|\r\n/g, '');
}
if (/^['"][a-zA-Z][a-zA-Z\d\-_]+['"]$/.test(text) && !/format\($/.test(context)) {
var isFont = /^(font|font\-family):/.test(context);
var isAttribute = /\[[\w\d\-]+[\*\|\~\^\$]?=$/.test(context);
var isKeyframe = /@(-moz-|-o-|-webkit-)?keyframes /.test(context);
var isAnimation = /^(-moz-|-o-|-webkit-)?animation(-name)?:/.test(context);
if (isFont || isAttribute || isKeyframe || isAnimation)
text = text.substring(1, text.length - 1);
}
return text;
}
FreeTextProcessor.prototype.restore = function (data, prefixContext) {
var tempData = [];
var cursor = 0;
for (; cursor < data.length;) {
var nextMatch = this.matches.nextMatch(data, cursor);
if (nextMatch.start < 0)
break;
tempData.push(data.substring(cursor, nextMatch.start));
var text = normalize(this.matches.restore(nextMatch.match), data, prefixContext, nextMatch.start);
tempData.push(text);
cursor = nextMatch.end;
}
return tempData.length > 0 ?
tempData.join('') + data.substring(cursor, data.length) :
data;
};
module.exports = FreeTextProcessor;
@@ -0,0 +1,75 @@
var EscapeStore = require('./escape-store');
var reduceUrls = require('../urls/reduce');
var lineBreak = require('os').EOL;
function UrlsProcessor(context, saveWaypoints, keepUrlQuotes) {
this.urls = new EscapeStore('URL');
this.context = context;
this.saveWaypoints = saveWaypoints;
this.keepUrlQuotes = keepUrlQuotes;
}
// Strip urls by replacing them by a special
// marker for further restoring. It's done via string scanning
// instead of regexps to speed up the process.
UrlsProcessor.prototype.escape = function (data) {
var breaksCount;
var lastBreakAt;
var indent;
var saveWaypoints = this.saveWaypoints;
var self = this;
return reduceUrls(data, this.context, function (url, tempData) {
if (saveWaypoints) {
breaksCount = url.split(lineBreak).length - 1;
lastBreakAt = url.lastIndexOf(lineBreak);
indent = lastBreakAt > 0 ?
url.substring(lastBreakAt + lineBreak.length).length :
url.length;
}
var placeholder = self.urls.store(url, saveWaypoints ? [breaksCount, indent] : null);
tempData.push(placeholder);
});
};
function normalize(url, keepUrlQuotes) {
url = url
.replace(/^url/gi, 'url')
.replace(/\\?\n|\\?\r\n/g, '')
.replace(/(\s{2,}|\s)/g, ' ')
.replace(/^url\((['"])? /, 'url($1')
.replace(/ (['"])?\)$/, '$1)');
if (/url\(".*'.*"\)/.test(url) || /url\('.*".*'\)/.test(url))
return url;
if (!keepUrlQuotes && !/^['"].+['"]$/.test(url) && !/url\(.*[\s\(\)].*\)/.test(url) && !/url\(['"]data:[^;]+;charset/.test(url))
url = url.replace(/["']/g, '');
return url;
}
UrlsProcessor.prototype.restore = function (data) {
var tempData = [];
var cursor = 0;
for (; cursor < data.length;) {
var nextMatch = this.urls.nextMatch(data, cursor);
if (nextMatch.start < 0)
break;
tempData.push(data.substring(cursor, nextMatch.start));
var url = normalize(this.urls.restore(nextMatch.match), this.keepUrlQuotes);
tempData.push(url);
cursor = nextMatch.end;
}
return tempData.length > 0 ?
tempData.join('') + data.substring(cursor, data.length) :
data;
};
module.exports = UrlsProcessor;
@@ -0,0 +1,193 @@
var split = require('../utils/split');
var COMMA = ',';
var FORWARD_SLASH = '/';
var AT_RULE = 'at-rule';
var IMPORTANT_WORD = 'important';
var IMPORTANT_TOKEN = '!'+IMPORTANT_WORD;
var IMPORTANT_WORD_MATCH = new RegExp('^'+IMPORTANT_WORD+'$', 'i');
var IMPORTANT_TOKEN_MATCH = new RegExp('^'+IMPORTANT_TOKEN+'$', 'i');
function selectorName(value) {
return value[0];
}
function noop() {}
function withoutComments(string, into, heading, context) {
var matcher = heading ? /^__ESCAPED_COMMENT_/ : /__ESCAPED_COMMENT_/;
var track = heading ? context.track : noop; // don't track when comment not in a heading as we do it later in `trackComments`
while (matcher.test(string)) {
var startOfComment = string.indexOf('__');
var endOfComment = string.indexOf('__', startOfComment + 1) + 2;
var comment = string.substring(startOfComment, endOfComment);
string = string.substring(0, startOfComment) + string.substring(endOfComment);
track(comment);
into.push(comment);
}
return string;
}
function withoutHeadingComments(string, into, context) {
return withoutComments(string, into, true, context);
}
function withoutInnerComments(string, into, context) {
return withoutComments(string, into, false, context);
}
function trackComments(comments, into, context) {
for (var i = 0, l = comments.length; i < l; i++) {
context.track(comments[i]);
into.push(comments[i]);
}
}
function extractProperties(string, selectors, context) {
var list = [];
var innerComments = [];
var valueSeparator = /[\s,\/]/;
if (typeof string != 'string')
return [];
if (string.indexOf(')') > -1)
string = string.replace(/\)([^\s_;:,\)])/g, context.sourceMap ? ') __ESCAPED_COMMENT_CLEAN_CSS(0,-1)__ $1' : ') $1');
if (string.indexOf('ESCAPED_URL_CLEAN_CSS') > -1)
string = string.replace(/(ESCAPED_URL_CLEAN_CSS[^_]+?__)/g, context.sourceMap ? '$1 __ESCAPED_COMMENT_CLEAN_CSS(0,-1)__ ' : '$1 ');
var candidates = split(string, ';', false, '{', '}');
for (var i = 0, l = candidates.length; i < l; i++) {
var candidate = candidates[i];
var firstColonAt = candidate.indexOf(':');
var atRule = candidate.trim()[0] == '@';
if (atRule) {
context.track(candidate);
list.push([AT_RULE, candidate.trim()]);
continue;
}
if (firstColonAt == -1) {
context.track(candidate);
if (candidate.indexOf('__ESCAPED_COMMENT_SPECIAL') > -1)
list.push(candidate.trim());
continue;
}
if (candidate.indexOf('{') > 0 && candidate.indexOf('{') < firstColonAt) {
context.track(candidate);
continue;
}
var body = [];
var name = candidate.substring(0, firstColonAt);
innerComments = [];
if (name.indexOf('__ESCAPED_COMMENT') > -1)
name = withoutHeadingComments(name, list, context);
if (name.indexOf('__ESCAPED_COMMENT') > -1)
name = withoutInnerComments(name, innerComments, context);
body.push([name.trim()].concat(context.track(name, true)));
context.track(':');
trackComments(innerComments, list, context);
var firstBraceAt = candidate.indexOf('{');
var isVariable = name.trim().indexOf('--') === 0;
if (isVariable && firstBraceAt > 0) {
var blockPrefix = candidate.substring(firstColonAt + 1, firstBraceAt + 1);
var blockSuffix = candidate.substring(candidate.indexOf('}'));
var blockContent = candidate.substring(firstBraceAt + 1, candidate.length - blockSuffix.length);
context.track(blockPrefix);
body.push(extractProperties(blockContent, selectors, context));
list.push(body);
context.track(blockSuffix);
context.track(i < l - 1 ? ';' : '');
continue;
}
var values = split(candidate.substring(firstColonAt + 1), valueSeparator, true);
if (values.length == 1 && values[0] === '') {
context.warnings.push('Empty property \'' + name + '\' inside \'' + selectors.filter(selectorName).join(',') + '\' selector. Ignoring.');
continue;
}
for (var j = 0, m = values.length; j < m; j++) {
var value = values[j];
var trimmed = value.trim();
if (trimmed.length === 0)
continue;
var lastCharacter = trimmed[trimmed.length - 1];
var endsWithNonSpaceSeparator = trimmed.length > 1 && (lastCharacter == COMMA || lastCharacter == FORWARD_SLASH);
if (endsWithNonSpaceSeparator)
trimmed = trimmed.substring(0, trimmed.length - 1);
if (trimmed.indexOf('__ESCAPED_COMMENT_CLEAN_CSS(0,-') > -1) {
context.track(trimmed);
continue;
}
innerComments = [];
if (trimmed.indexOf('__ESCAPED_COMMENT') > -1)
trimmed = withoutHeadingComments(trimmed, list, context);
if (trimmed.indexOf('__ESCAPED_COMMENT') > -1)
trimmed = withoutInnerComments(trimmed, innerComments, context);
if (trimmed.length === 0) {
trackComments(innerComments, list, context);
continue;
}
var pos = body.length - 1;
if (IMPORTANT_WORD_MATCH.test(trimmed) && body[pos][0] == '!') {
context.track(trimmed);
body[pos - 1][0] += IMPORTANT_TOKEN;
body.pop();
continue;
}
if (IMPORTANT_TOKEN_MATCH.test(trimmed) || (IMPORTANT_WORD_MATCH.test(trimmed) && body[pos][0][body[pos][0].length - 1] == '!')) {
context.track(trimmed);
body[pos][0] += trimmed;
continue;
}
body.push([trimmed].concat(context.track(value, true)));
trackComments(innerComments, list, context);
if (endsWithNonSpaceSeparator) {
body.push([lastCharacter]);
context.track(lastCharacter);
}
}
if (i < l - 1)
context.track(';');
list.push(body);
}
return list;
}
module.exports = extractProperties;
@@ -0,0 +1,17 @@
var split = require('../utils/split');
function extractSelectors(string, context) {
var list = [];
var metadata;
var selectors = split(string, ',');
for (var i = 0, l = selectors.length; i < l; i++) {
metadata = context.track(selectors[i], true, i);
context.track(',');
list.push([selectors[i].trim()].concat(metadata));
}
return list;
}
module.exports = extractSelectors;
@@ -0,0 +1,297 @@
var extractProperties = require('./extract-properties');
var extractSelectors = require('./extract-selectors');
var track = require('../source-maps/track');
var split = require('../utils/split');
var path = require('path');
var flatBlock = /(@(font\-face|page|\-ms\-viewport|\-o\-viewport|viewport|counter\-style)|\\@.+?)/;
var BACKSLASH = '\\';
function tokenize(data, outerContext) {
var chunks = split(normalize(data), '}', true, '{', '}');
if (chunks.length === 0)
return [];
var context = {
chunk: chunks.shift(),
chunks: chunks,
column: 0,
cursor: 0,
line: 1,
mode: 'top',
resolvePath: outerContext.options.explicitTarget ?
relativePathResolver(outerContext.options.root, outerContext.options.target) :
null,
source: undefined,
sourceMap: outerContext.options.sourceMap,
sourceMapInlineSources: outerContext.options.sourceMapInlineSources,
sourceMapTracker: outerContext.inputSourceMapTracker,
sourceReader: outerContext.sourceReader,
sourceTracker: outerContext.sourceTracker,
state: [],
track: outerContext.options.sourceMap ?
function (data, snapshotMetadata, fallbacks) { return [[track(data, context, snapshotMetadata, fallbacks)]]; } :
function () { return []; },
warnings: outerContext.warnings
};
return intoTokens(context);
}
function normalize(data) {
return data.replace(/\r\n/g, '\n');
}
function relativePathResolver(root, target) {
var rebaseTo = path.relative(root, target);
return function (relativeTo, sourcePath) {
return relativeTo != sourcePath ?
path.normalize(path.join(path.relative(rebaseTo, path.dirname(relativeTo)), sourcePath)) :
sourcePath;
};
}
function whatsNext(context) {
var mode = context.mode;
var chunk = context.chunk;
var closest;
if (chunk.length == context.cursor) {
if (context.chunks.length === 0)
return null;
context.chunk = chunk = context.chunks.shift();
context.cursor = 0;
}
if (mode == 'body') {
if (chunk[context.cursor] == '}')
return [context.cursor, 'bodyEnd'];
if (chunk.indexOf('}', context.cursor) == -1)
return null;
closest = context.cursor + split(chunk.substring(context.cursor - 1), '}', true, '{', '}')[0].length - 2;
return [closest, 'bodyEnd'];
}
var nextSpecial = nextAt(context, '@');
var nextEscape = chunk.indexOf('__ESCAPED_', context.cursor);
var nextBodyStart = nextAt(context, '{');
var nextBodyEnd = nextAt(context, '}');
if (nextSpecial > -1 && context.cursor > 0 && !/\s|\{|\}|\/|_|,|;/.test(chunk.substring(nextSpecial - 1, nextSpecial))) {
nextSpecial = -1;
}
if (nextEscape > -1 && /\S/.test(chunk.substring(context.cursor, nextEscape)))
nextEscape = -1;
closest = nextSpecial;
if (closest == -1 || (nextEscape > -1 && nextEscape < closest))
closest = nextEscape;
if (closest == -1 || (nextBodyStart > -1 && nextBodyStart < closest))
closest = nextBodyStart;
if (closest == -1 || (nextBodyEnd > -1 && nextBodyEnd < closest))
closest = nextBodyEnd;
if (closest == -1)
return;
if (nextEscape === closest)
return [closest, 'escape'];
if (nextBodyStart === closest)
return [closest, 'bodyStart'];
if (nextBodyEnd === closest)
return [closest, 'bodyEnd'];
if (nextSpecial === closest)
return [closest, 'special'];
}
function nextAt(context, character) {
var startAt = context.cursor;
var chunk = context.chunk;
var position;
while ((position = chunk.indexOf(character, startAt)) > -1) {
if (isEscaped(chunk, position)) {
startAt = position + 1;
} else {
return position;
}
}
return -1;
}
function isEscaped(chunk, position) {
var startAt = position;
var backslashCount = 0;
while (startAt > 0 && chunk[startAt - 1] == BACKSLASH) {
backslashCount++;
startAt--;
}
return backslashCount % 2 !== 0;
}
function intoTokens(context) {
var chunk = context.chunk;
var tokenized = [];
var newToken;
var value;
while (true) {
var next = whatsNext(context);
if (!next) {
var whatsLeft = context.chunk.substring(context.cursor);
if (whatsLeft.trim().length > 0) {
if (context.mode == 'body') {
context.warnings.push('Missing \'}\' after \'' + whatsLeft + '\'. Ignoring.');
} else {
tokenized.push(['text', [whatsLeft]]);
}
context.cursor += whatsLeft.length;
}
break;
}
var nextSpecial = next[0];
var what = next[1];
var nextEnd;
var oldMode;
chunk = context.chunk;
if (context.cursor != nextSpecial && what != 'bodyEnd') {
var spacing = chunk.substring(context.cursor, nextSpecial);
var leadingWhitespace = /^\s+/.exec(spacing);
if (leadingWhitespace) {
context.cursor += leadingWhitespace[0].length;
context.track(leadingWhitespace[0]);
}
}
if (what == 'special') {
var firstOpenBraceAt = chunk.indexOf('{', nextSpecial);
var firstSemicolonAt = chunk.indexOf(';', nextSpecial);
var isSingle = firstSemicolonAt > -1 && (firstOpenBraceAt == -1 || firstSemicolonAt < firstOpenBraceAt);
var isBroken = firstOpenBraceAt == -1 && firstSemicolonAt == -1;
if (isBroken) {
context.warnings.push('Broken declaration: \'' + chunk.substring(context.cursor) + '\'.');
context.cursor = chunk.length;
} else if (isSingle) {
nextEnd = chunk.indexOf(';', nextSpecial + 1);
value = chunk.substring(context.cursor, nextEnd + 1);
tokenized.push([
'at-rule',
[value].concat(context.track(value, true))
]);
context.track(';');
context.cursor = nextEnd + 1;
} else {
nextEnd = chunk.indexOf('{', nextSpecial + 1);
value = chunk.substring(context.cursor, nextEnd);
var trimmedValue = value.trim();
var isFlat = flatBlock.test(trimmedValue);
oldMode = context.mode;
context.cursor = nextEnd + 1;
context.mode = isFlat ? 'body' : 'block';
newToken = [
isFlat ? 'flat-block' : 'block'
];
newToken.push([trimmedValue].concat(context.track(value, true)));
context.track('{');
newToken.push(intoTokens(context));
if (typeof newToken[2] == 'string')
newToken[2] = extractProperties(newToken[2], [[trimmedValue]], context);
context.mode = oldMode;
context.track('}');
tokenized.push(newToken);
}
} else if (what == 'escape') {
nextEnd = chunk.indexOf('__', nextSpecial + 1);
var escaped = chunk.substring(context.cursor, nextEnd + 2);
var isStartSourceMarker = !!context.sourceTracker.nextStart(escaped);
var isEndSourceMarker = !!context.sourceTracker.nextEnd(escaped);
if (isStartSourceMarker) {
context.track(escaped);
context.state.push({
source: context.source,
line: context.line,
column: context.column
});
context.source = context.sourceTracker.nextStart(escaped).filename;
context.line = 1;
context.column = 0;
} else if (isEndSourceMarker) {
var oldState = context.state.pop();
context.source = oldState.source;
context.line = oldState.line;
context.column = oldState.column;
context.track(escaped);
} else {
if (escaped.indexOf('__ESCAPED_COMMENT_SPECIAL') === 0)
tokenized.push(['text', [escaped]]);
context.track(escaped);
}
context.cursor = nextEnd + 2;
} else if (what == 'bodyStart') {
var selectors = extractSelectors(chunk.substring(context.cursor, nextSpecial), context);
oldMode = context.mode;
context.cursor = nextSpecial + 1;
context.mode = 'body';
var body = extractProperties(intoTokens(context), selectors, context);
context.track('{');
context.mode = oldMode;
tokenized.push([
'selector',
selectors,
body
]);
} else if (what == 'bodyEnd') {
// extra closing brace at the top level can be safely ignored
if (context.mode == 'top') {
var at = context.cursor;
var warning = chunk[context.cursor] == '}' ?
'Unexpected \'}\' in \'' + chunk.substring(at - 20, at + 20) + '\'. Ignoring.' :
'Unexpected content: \'' + chunk.substring(at, nextSpecial + 1) + '\'. Ignoring.';
context.warnings.push(warning);
context.cursor = nextSpecial + 1;
continue;
}
if (context.mode == 'block')
context.track(chunk.substring(context.cursor, nextSpecial));
if (context.mode != 'block')
tokenized = chunk.substring(context.cursor, nextSpecial);
context.cursor = nextSpecial + 1;
break;
}
}
return tokenized;
}
module.exports = tokenize;
+30
View File
@@ -0,0 +1,30 @@
var path = require('path');
var rewriteUrls = require('./rewrite');
function rebaseUrls(data, context) {
var rebaseOpts = {
absolute: context.options.explicitRoot,
relative: !context.options.explicitRoot && context.options.explicitTarget,
fromBase: context.options.relativeTo
};
if (!rebaseOpts.absolute && !rebaseOpts.relative)
return data;
if (rebaseOpts.absolute && context.options.explicitTarget)
context.warnings.push('Both \'root\' and output file given so rebasing URLs as absolute paths');
if (rebaseOpts.absolute)
rebaseOpts.toBase = path.resolve(context.options.root);
if (rebaseOpts.relative)
rebaseOpts.toBase = path.resolve(context.options.target);
if (!rebaseOpts.fromBase || !rebaseOpts.toBase)
return data;
return rewriteUrls(data, rebaseOpts, context);
}
module.exports = rebaseUrls;
+154
View File
@@ -0,0 +1,154 @@
var split = require('../utils/split');
var URL_PREFIX = 'url(';
var UPPERCASE_URL_PREFIX = 'URL(';
var URL_SUFFIX = ')';
var SINGLE_QUOTE_URL_SUFFIX = '\')';
var DOUBLE_QUOTE_URL_SUFFIX = '")';
var DATA_URI_PREFIX_PATTERN = /^\s*['"]?\s*data:/;
var DATA_URI_TRAILER_PATTERN = /[\s\};,\/!]/;
var IMPORT_URL_PREFIX = '@import';
var UPPERCASE_IMPORT_URL_PREFIX = '@IMPORT';
var COMMENT_END_MARKER = /\*\//;
function byUrl(data, context, callback) {
var nextStart = 0;
var nextStartUpperCase = 0;
var nextEnd = 0;
var firstMatch;
var isDataURI = false;
var cursor = 0;
var tempData = [];
var hasUppercaseUrl = data.indexOf(UPPERCASE_URL_PREFIX) > -1;
for (; nextEnd < data.length;) {
nextStart = data.indexOf(URL_PREFIX, nextEnd);
nextStartUpperCase = hasUppercaseUrl ? data.indexOf(UPPERCASE_URL_PREFIX, nextEnd) : -1;
if (nextStart == -1 && nextStartUpperCase == -1)
break;
if (nextStart == -1 && nextStartUpperCase > -1)
nextStart = nextStartUpperCase;
if (data[nextStart + URL_PREFIX.length] == '"') {
nextEnd = data.indexOf(DOUBLE_QUOTE_URL_SUFFIX, nextStart);
} else if (data[nextStart + URL_PREFIX.length] == '\'') {
nextEnd = data.indexOf(SINGLE_QUOTE_URL_SUFFIX, nextStart);
} else {
isDataURI = DATA_URI_PREFIX_PATTERN.test(data.substring(nextStart + URL_PREFIX.length));
if (isDataURI) {
firstMatch = split(data.substring(nextStart), DATA_URI_TRAILER_PATTERN, false, '(', ')', true).pop();
if (firstMatch && firstMatch[firstMatch.length - 1] == URL_SUFFIX) {
nextEnd = nextStart + firstMatch.length - URL_SUFFIX.length;
} else {
nextEnd = -1;
}
} else {
nextEnd = data.indexOf(URL_SUFFIX, nextStart);
}
}
// Following lines are a safety mechanism to ensure
// incorrectly terminated urls are processed correctly.
if (nextEnd == -1) {
nextEnd = data.indexOf('}', nextStart);
if (nextEnd == -1)
nextEnd = data.length;
else
nextEnd--;
context.warnings.push('Broken URL declaration: \'' + data.substring(nextStart, nextEnd + 1) + '\'.');
} else {
if (data[nextEnd] != URL_SUFFIX)
nextEnd = data.indexOf(URL_SUFFIX, nextEnd);
}
tempData.push(data.substring(cursor, nextStart));
var url = data.substring(nextStart, nextEnd + 1);
callback(url, tempData);
cursor = nextEnd + 1;
}
return tempData.length > 0 ?
tempData.join('') + data.substring(cursor, data.length) :
data;
}
function byImport(data, context, callback) {
var nextImport = 0;
var nextImportUpperCase = 0;
var nextStart = 0;
var nextEnd = 0;
var cursor = 0;
var tempData = [];
var nextSingleQuote = 0;
var nextDoubleQuote = 0;
var untilNextQuote;
var withQuote;
var SINGLE_QUOTE = '\'';
var DOUBLE_QUOTE = '"';
for (; nextEnd < data.length;) {
nextImport = data.indexOf(IMPORT_URL_PREFIX, nextEnd);
nextImportUpperCase = data.indexOf(UPPERCASE_IMPORT_URL_PREFIX, nextEnd);
if (nextImport == -1 && nextImportUpperCase == -1)
break;
if (nextImport > -1 && nextImportUpperCase > -1 && nextImportUpperCase < nextImport)
nextImport = nextImportUpperCase;
nextSingleQuote = data.indexOf(SINGLE_QUOTE, nextImport);
nextDoubleQuote = data.indexOf(DOUBLE_QUOTE, nextImport);
if (nextSingleQuote > -1 && nextDoubleQuote > -1 && nextSingleQuote < nextDoubleQuote) {
nextStart = nextSingleQuote;
withQuote = SINGLE_QUOTE;
} else if (nextSingleQuote > -1 && nextDoubleQuote > -1 && nextSingleQuote > nextDoubleQuote) {
nextStart = nextDoubleQuote;
withQuote = DOUBLE_QUOTE;
} else if (nextSingleQuote > -1) {
nextStart = nextSingleQuote;
withQuote = SINGLE_QUOTE;
} else if (nextDoubleQuote > -1) {
nextStart = nextDoubleQuote;
withQuote = DOUBLE_QUOTE;
} else {
break;
}
tempData.push(data.substring(cursor, nextStart));
nextEnd = data.indexOf(withQuote, nextStart + 1);
untilNextQuote = data.substring(nextImport, nextEnd);
if (nextEnd == -1 || /^@import\s+(url\(|__ESCAPED)/i.test(untilNextQuote) || COMMENT_END_MARKER.test(untilNextQuote)) {
cursor = nextStart;
break;
}
var url = data.substring(nextStart, nextEnd + 1);
callback(url, tempData);
cursor = nextEnd + 1;
}
return tempData.length > 0 ?
tempData.join('') + data.substring(cursor, data.length) :
data;
}
function reduceAll(data, context, callback) {
data = byUrl(data, context, callback);
data = byImport(data, context, callback);
return data;
}
module.exports = reduceAll;
+107
View File
@@ -0,0 +1,107 @@
var path = require('path');
var url = require('url');
var reduceUrls = require('./reduce');
var isWindows = process.platform == 'win32';
function isAbsolute(uri) {
return uri[0] == '/';
}
function isSVGMarker(uri) {
return uri[0] == '#';
}
function isEscaped(uri) {
return uri.indexOf('__ESCAPED_URL_CLEAN_CSS__') === 0;
}
function isInternal(uri) {
return /^\w+:\w+/.test(uri);
}
function isRemote(uri) {
return /^[^:]+?:\/\//.test(uri) || uri.indexOf('//') === 0;
}
function isSameOrigin(uri1, uri2) {
return url.parse(uri1).protocol == url.parse(uri2).protocol &&
url.parse(uri1).host == url.parse(uri2).host;
}
function isImport(uri) {
return uri.lastIndexOf('.css') === uri.length - 4;
}
function isData(uri) {
return uri.indexOf('data:') === 0;
}
function absolute(uri, options) {
return path
.resolve(path.join(options.fromBase || '', uri))
.replace(options.toBase, '');
}
function relative(uri, options) {
return path.relative(options.toBase, path.join(options.fromBase || '', uri));
}
function normalize(uri) {
return isWindows ? uri.replace(/\\/g, '/') : uri;
}
function rebase(uri, options) {
if (isAbsolute(uri) || isSVGMarker(uri) || isEscaped(uri) || isInternal(uri))
return uri;
if (options.rebase === false && !isImport(uri))
return uri;
if (!options.imports && isImport(uri))
return uri;
if (isData(uri))
return '\'' + uri + '\'';
if (isRemote(uri) && !isRemote(options.toBase))
return uri;
if (isRemote(uri) && !isSameOrigin(uri, options.toBase))
return uri;
if (!isRemote(uri) && isRemote(options.toBase))
return url.resolve(options.toBase, uri);
return options.absolute ?
normalize(absolute(uri, options)) :
normalize(relative(uri, options));
}
function quoteFor(url) {
if (url.indexOf('\'') > -1)
return '"';
else if (url.indexOf('"') > -1)
return '\'';
else if (/\s/.test(url) || /[\(\)]/.test(url))
return '\'';
else
return '';
}
function rewriteUrls(data, options, context) {
return reduceUrls(data, context, function (originUrl, tempData) {
var url = originUrl.replace(/^(url\()?\s*['"]?|['"]?\s*\)?$/g, '');
var match = originUrl.match(/^(url\()?\s*(['"]).*?(['"])\s*\)?$/);
var quote;
if (!!options.urlQuotes && match && match[2] === match[3]) {
quote = match[2];
} else {
quote = quoteFor(url);
}
tempData.push('url(' + quote + rebase(url, options) + quote + ')');
});
}
module.exports = rewriteUrls;
@@ -0,0 +1,12 @@
function cloneArray(array) {
var cloned = array.slice(0);
for (var i = 0, l = cloned.length; i < l; i++) {
if (Array.isArray(cloned[i]))
cloned[i] = cloneArray(cloned[i]);
}
return cloned;
}
module.exports = cloneArray;
@@ -0,0 +1,162 @@
var util = require('util');
var DEFAULTS = {
'*': {
colors: {
opacity: true // rgba / hsla
},
properties: {
backgroundClipMerging: false, // background-clip to shorthand
backgroundOriginMerging: false, // background-origin to shorthand
backgroundSizeMerging: false, // background-size to shorthand
colors: true, // any kind of color transformations, like `#ff00ff` to `#f0f` or `#fff` into `red`
ieBangHack: false, // !ie suffix hacks on IE<8
iePrefixHack: false, // underscore / asterisk prefix hacks on IE
ieSuffixHack: true, // \9 suffix hacks on IE6-9
merging: true, // merging properties into one
shorterLengthUnits: false, // optimize pixel units into `pt`, `pc` or `in` units
spaceAfterClosingBrace: true, // 'url() no-repeat' to 'url()no-repeat'
urlQuotes: false, // whether to wrap content of `url()` into quotes or not
zeroUnits: true // 0[unit] -> 0
},
selectors: {
adjacentSpace: false, // div+ nav Android stock browser hack
ie7Hack: false, // *+html hack
special: /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:dir\([a-z-]*\)|:first(?![a-z-])|:fullscreen|:left|:read-only|:read-write|:right|:placeholder|:host|:content|\/deep\/|:shadow|:selection|^,)/ // special selectors which prevent merging
},
units: {
ch: true,
in: true,
pc: true,
pt: true,
rem: true,
vh: true,
vm: true, // vm is vmin on IE9+ see https://developer.mozilla.org/en-US/docs/Web/CSS/length
vmax: true,
vmin: true,
vw: true
}
},
'ie8': {
colors: {
opacity: false
},
properties: {
backgroundClipMerging: false,
backgroundOriginMerging: false,
backgroundSizeMerging: false,
colors: true,
ieBangHack: false,
iePrefixHack: true,
ieSuffixHack: true,
merging: false,
shorterLengthUnits: false,
spaceAfterClosingBrace: true,
urlQuotes: false,
zeroUnits: true
},
selectors: {
adjacentSpace: false,
ie7Hack: false,
special: /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:root|:nth|:first\-of|:last|:only|:empty|:target|:checked|::selection|:enabled|:disabled|:not|:placeholder|:host|::content|\/deep\/|::shadow|^,)/
},
units: {
ch: false,
in: true,
pc: true,
pt: true,
rem: false,
vh: false,
vm: false,
vmax: false,
vmin: false,
vw: false
}
},
'ie7': {
colors: {
opacity: false
},
properties: {
backgroundClipMerging: false,
backgroundOriginMerging: false,
backgroundSizeMerging: false,
colors: true,
ieBangHack: true,
iePrefixHack: true,
ieSuffixHack: true,
merging: false,
shorterLengthUnits: false,
spaceAfterClosingBrace: true,
urlQuotes: false,
zeroUnits: true
},
selectors: {
adjacentSpace: false,
ie7Hack: true,
special: /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:focus|:before|:after|:root|:nth|:first\-of|:last|:only|:empty|:target|:checked|::selection|:enabled|:disabled|:not|:placeholder|:host|::content|\/deep\/|::shadow|^,)/
},
units: {
ch: false,
in: true,
pc: true,
pt: true,
rem: false,
vh: false,
vm: false,
vmax: false,
vmin: false,
vw: false,
}
}
};
function Compatibility(source) {
this.source = source || {};
}
function merge(source, target) {
for (var key in source) {
var value = source[key];
if (typeof value === 'object' && !util.isRegExp(value))
target[key] = merge(value, target[key] || {});
else
target[key] = key in target ? target[key] : value;
}
return target;
}
function calculateSource(source) {
if (typeof source == 'object')
return source;
if (!/[,\+\-]/.test(source))
return DEFAULTS[source] || DEFAULTS['*'];
var parts = source.split(',');
var template = parts[0] in DEFAULTS ?
DEFAULTS[parts.shift()] :
DEFAULTS['*'];
source = {};
parts.forEach(function (part) {
var isAdd = part[0] == '+';
var key = part.substring(1).split('.');
var group = key[0];
var option = key[1];
source[group] = source[group] || {};
source[group][option] = isAdd;
});
return merge(template, source);
}
Compatibility.prototype.toOptions = function () {
return merge(DEFAULTS['*'], calculateSource(this.source));
};
module.exports = Compatibility;
@@ -0,0 +1,284 @@
var SourceMapConsumer = require('source-map').SourceMapConsumer;
var fs = require('fs');
var path = require('path');
var http = require('http');
var https = require('https');
var url = require('url');
var override = require('../utils/object.js').override;
var MAP_MARKER = /\/\*# sourceMappingURL=(\S+) \*\//;
var REMOTE_RESOURCE = /^(https?:)?\/\//;
var DATA_URI = /^data:(\S*?)?(;charset=[^;]+)?(;[^,]+?)?,(.+)/;
var unescape = global.unescape;
function InputSourceMapStore(outerContext) {
this.options = outerContext.options;
this.errors = outerContext.errors;
this.warnings = outerContext.warnings;
this.sourceTracker = outerContext.sourceTracker;
this.timeout = this.options.inliner.timeout;
this.requestOptions = this.options.inliner.request;
this.localOnly = outerContext.localOnly;
this.relativeTo = outerContext.options.target || process.cwd();
this.maps = {};
this.sourcesContent = {};
}
function fromString(self, _, whenDone) {
self.trackLoaded(undefined, undefined, self.options.sourceMap);
return whenDone();
}
function fromSource(self, data, whenDone, context) {
var nextAt = 0;
function proceedToNext() {
context.cursor += nextAt + 1;
fromSource(self, data, whenDone, context);
}
while (context.cursor < data.length) {
var fragment = data.substring(context.cursor);
var markerStartMatch = self.sourceTracker.nextStart(fragment) || { index: -1 };
var markerEndMatch = self.sourceTracker.nextEnd(fragment) || { index: -1 };
var mapMatch = MAP_MARKER.exec(fragment) || { index: -1 };
var sourceMapFile = mapMatch[1];
nextAt = data.length;
if (markerStartMatch.index > -1)
nextAt = markerStartMatch.index;
if (markerEndMatch.index > -1 && markerEndMatch.index < nextAt)
nextAt = markerEndMatch.index;
if (mapMatch.index > -1 && mapMatch.index < nextAt)
nextAt = mapMatch.index;
if (nextAt == data.length)
break;
if (nextAt == markerStartMatch.index) {
context.files.push(markerStartMatch.filename);
} else if (nextAt == markerEndMatch.index) {
context.files.pop();
} else if (nextAt == mapMatch.index) {
var isRemote = /^https?:\/\//.test(sourceMapFile) || /^\/\//.test(sourceMapFile);
var isDataUri = DATA_URI.test(sourceMapFile);
if (isRemote) {
return fetchMapFile(self, sourceMapFile, context, proceedToNext);
} else {
var sourceFile = context.files[context.files.length - 1];
var sourceMapPath, sourceMapData;
var sourceDir = sourceFile ? path.dirname(sourceFile) : self.options.relativeTo;
if (isDataUri) {
// source map's path is the same as the source file it comes from
sourceMapPath = path.resolve(self.options.root, sourceFile || '');
sourceMapData = fromDataUri(sourceMapFile);
} else {
sourceMapPath = path.resolve(self.options.root, path.join(sourceDir || '', sourceMapFile));
sourceMapData = fs.readFileSync(sourceMapPath, 'utf-8');
}
self.trackLoaded(sourceFile || undefined, sourceMapPath, sourceMapData);
}
}
context.cursor += nextAt + 1;
}
return whenDone();
}
function fromDataUri(uriString) {
var match = DATA_URI.exec(uriString);
var charset = match[2] ? match[2].split(/[=;]/)[2] : 'us-ascii';
var encoding = match[3] ? match[3].split(';')[1] : 'utf8';
var data = encoding == 'utf8' ? unescape(match[4]) : match[4];
var buffer = new Buffer(data, encoding);
buffer.charset = charset;
return buffer.toString();
}
function fetchMapFile(self, sourceUrl, context, done) {
fetch(self, sourceUrl, function (data) {
self.trackLoaded(context.files[context.files.length - 1] || undefined, sourceUrl, data);
done();
}, function (message) {
context.errors.push('Broken source map at "' + sourceUrl + '" - ' + message);
return done();
});
}
function fetch(self, path, onSuccess, onFailure) {
var protocol = path.indexOf('https') === 0 ? https : http;
var requestOptions = override(url.parse(path), self.requestOptions);
var errorHandled = false;
protocol
.get(requestOptions, function (res) {
if (res.statusCode < 200 || res.statusCode > 299)
return onFailure(res.statusCode);
var chunks = [];
res.on('data', function (chunk) {
chunks.push(chunk.toString());
});
res.on('end', function () {
onSuccess(chunks.join(''));
});
})
.on('error', function (res) {
if (errorHandled)
return;
onFailure(res.message);
errorHandled = true;
})
.on('timeout', function () {
if (errorHandled)
return;
onFailure('timeout');
errorHandled = true;
})
.setTimeout(self.timeout);
}
function originalPositionIn(trackedSource, line, column, token, allowNFallbacks) {
var originalPosition;
var maxRange = token.length;
var position = {
line: line,
column: column + maxRange
};
while (maxRange-- > 0) {
position.column--;
originalPosition = trackedSource.data.originalPositionFor(position);
if (originalPosition)
break;
}
if (originalPosition.line === null && line > 1 && allowNFallbacks > 0)
return originalPositionIn(trackedSource, line - 1, column, token, allowNFallbacks - 1);
if (trackedSource.path && originalPosition.source) {
originalPosition.source = REMOTE_RESOURCE.test(trackedSource.path) ?
url.resolve(trackedSource.path, originalPosition.source) :
path.join(trackedSource.path, originalPosition.source);
originalPosition.sourceResolved = true;
}
return originalPosition;
}
function trackContentSources(self, sourceFile) {
var consumer = self.maps[sourceFile].data;
var isRemote = REMOTE_RESOURCE.test(sourceFile);
var sourcesMapping = {};
consumer.sources.forEach(function (file, index) {
var uniquePath = isRemote ?
url.resolve(path.dirname(sourceFile), file) :
path.relative(self.relativeTo, path.resolve(path.dirname(sourceFile || '.'), file));
sourcesMapping[uniquePath] = consumer.sourcesContent && consumer.sourcesContent[index];
});
self.sourcesContent[sourceFile] = sourcesMapping;
}
function _resolveSources(self, remaining, whenDone) {
function processNext() {
return _resolveSources(self, remaining, whenDone);
}
if (remaining.length === 0)
return whenDone();
var current = remaining.shift();
var sourceFile = current[0];
var originalFile = current[1];
var isRemote = REMOTE_RESOURCE.test(sourceFile);
if (isRemote && self.localOnly) {
self.warnings.push('No callback given to `#minify` method, cannot fetch a remote file from "' + originalFile + '"');
return processNext();
}
if (isRemote) {
fetch(self, originalFile, function (data) {
self.sourcesContent[sourceFile][originalFile] = data;
processNext();
}, function (message) {
self.warnings.push('Broken original source file at "' + originalFile + '" - ' + message);
processNext();
});
} else {
var fullPath = path.join(self.options.root, originalFile);
if (fs.existsSync(fullPath))
self.sourcesContent[sourceFile][originalFile] = fs.readFileSync(fullPath, 'utf-8');
else
self.warnings.push('Missing original source file at "' + fullPath + '".');
return processNext();
}
}
InputSourceMapStore.prototype.track = function (data, whenDone) {
return typeof this.options.sourceMap == 'string' ?
fromString(this, data, whenDone) :
fromSource(this, data, whenDone, { files: [], cursor: 0, errors: this.errors });
};
InputSourceMapStore.prototype.trackLoaded = function (sourcePath, mapPath, mapData) {
var relativeTo = this.options.explicitTarget ? this.options.target : this.options.root;
var isRemote = REMOTE_RESOURCE.test(sourcePath);
if (mapPath) {
mapPath = isRemote ?
path.dirname(mapPath) :
path.dirname(path.relative(relativeTo, mapPath));
}
this.maps[sourcePath] = {
path: mapPath,
data: new SourceMapConsumer(mapData)
};
trackContentSources(this, sourcePath);
};
InputSourceMapStore.prototype.isTracking = function (source) {
return !!this.maps[source];
};
InputSourceMapStore.prototype.originalPositionFor = function (sourceInfo, token, allowNFallbacks) {
return originalPositionIn(this.maps[sourceInfo.source], sourceInfo.line, sourceInfo.column, token, allowNFallbacks);
};
InputSourceMapStore.prototype.sourcesContentFor = function (contextSource) {
return this.sourcesContent[contextSource];
};
InputSourceMapStore.prototype.resolveSources = function (whenDone) {
var toResolve = [];
for (var sourceFile in this.sourcesContent) {
var contents = this.sourcesContent[sourceFile];
for (var originalFile in contents) {
if (!contents[originalFile])
toResolve.push([sourceFile, originalFile]);
}
}
return _resolveSources(this, toResolve, whenDone);
};
module.exports = InputSourceMapStore;
+11
View File
@@ -0,0 +1,11 @@
module.exports = {
override: function (source1, source2) {
var target = {};
for (var key1 in source1)
target[key1] = source1[key1];
for (var key2 in source2)
target[key2] = source2[key2];
return target;
}
};
@@ -0,0 +1,119 @@
var COMMENT_START_MARK = '/*';
function QuoteScanner(data) {
this.data = data;
}
var findQuoteEnd = function (data, matched, cursor, oldCursor) {
var commentEndMark = '*/';
var escapeMark = '\\';
var blockEndMark = '}';
var dataPrefix = data.substring(oldCursor, cursor);
var commentEndedAt = dataPrefix.lastIndexOf(commentEndMark, cursor);
var commentStartedAt = findLastCommentStartedAt(dataPrefix, cursor);
var commentStarted = false;
if (commentEndedAt >= cursor && commentStartedAt > -1)
commentStarted = true;
if (commentStartedAt < cursor && commentStartedAt > commentEndedAt)
commentStarted = true;
if (commentStarted) {
var commentEndsAt = data.indexOf(commentEndMark, cursor);
if (commentEndsAt > -1)
return commentEndsAt;
commentEndsAt = data.indexOf(blockEndMark, cursor);
return commentEndsAt > -1 ? commentEndsAt - 1 : data.length;
}
while (true) {
if (data[cursor] === undefined)
break;
if (data[cursor] == matched && (data[cursor - 1] != escapeMark || data[cursor - 2] == escapeMark))
break;
cursor++;
}
return cursor;
};
function findLastCommentStartedAt(data, cursor) {
var position = cursor;
while (position > -1) {
position = data.lastIndexOf(COMMENT_START_MARK, position);
if (position > -1 && data[position - 1] != '*') {
break;
} else {
position--;
}
}
return position;
}
function findNext(data, mark, startAt) {
var escapeMark = '\\';
var candidate = startAt;
while (true) {
candidate = data.indexOf(mark, candidate + 1);
if (candidate == -1)
return -1;
if (data[candidate - 1] != escapeMark)
return candidate;
}
}
QuoteScanner.prototype.each = function (callback) {
var data = this.data;
var tempData = [];
var nextStart = 0;
var nextEnd = 0;
var cursor = 0;
var matchedMark = null;
var singleMark = '\'';
var doubleMark = '"';
var dataLength = data.length;
for (; nextEnd < data.length;) {
var nextStartSingle = findNext(data, singleMark, nextEnd);
var nextStartDouble = findNext(data, doubleMark, nextEnd);
if (nextStartSingle == -1)
nextStartSingle = dataLength;
if (nextStartDouble == -1)
nextStartDouble = dataLength;
if (nextStartSingle < nextStartDouble) {
nextStart = nextStartSingle;
matchedMark = singleMark;
} else {
nextStart = nextStartDouble;
matchedMark = doubleMark;
}
if (nextStart == -1)
break;
nextEnd = findQuoteEnd(data, matchedMark, nextStart + 1, cursor);
if (nextEnd == -1)
break;
var text = data.substring(nextStart, nextEnd + 1);
tempData.push(data.substring(cursor, nextStart));
if (text.length > 0)
callback(text, tempData, nextStart);
cursor = nextEnd + 1;
}
return tempData.length > 0 ?
tempData.join('') + data.substring(cursor, data.length) :
data;
};
module.exports = QuoteScanner;
@@ -0,0 +1,96 @@
var path = require('path');
var rewriteUrls = require('../urls/rewrite');
var REMOTE_RESOURCE = /^(https?:)?\/\//;
function SourceReader(context, data) {
this.outerContext = context;
this.data = data;
this.sources = {};
}
SourceReader.prototype.sourceAt = function (path) {
return this.sources[path];
};
SourceReader.prototype.trackSource = function (path, source) {
this.sources[path] = {};
this.sources[path][path] = source;
};
SourceReader.prototype.toString = function () {
if (typeof this.data == 'string')
return fromString(this);
if (Buffer.isBuffer(this.data))
return fromBuffer(this);
if (Array.isArray(this.data))
return fromArray(this);
return fromHash(this);
};
function fromString(self) {
var data = self.data;
self.trackSource(undefined, data);
return data;
}
function fromBuffer(self) {
var data = self.data.toString();
self.trackSource(undefined, data);
return data;
}
function fromArray(self) {
return self.data
.map(function (source) {
return self.outerContext.options.processImport === false ?
source + '@shallow' :
source;
})
.map(function (source) {
return !self.outerContext.options.relativeTo || /^https?:\/\//.test(source) ?
source :
path.relative(self.outerContext.options.relativeTo, source);
})
.map(function (source) { return '@import url(' + source + ');'; })
.join('');
}
function fromHash(self) {
var data = [];
var toBase = path.resolve(self.outerContext.options.target || self.outerContext.options.root);
for (var source in self.data) {
var styles = self.data[source].styles;
var inputSourceMap = self.data[source].sourceMap;
var isRemote = REMOTE_RESOURCE.test(source);
var absoluteSource = isRemote ? source : path.resolve(source);
var absoluteSourcePath = path.dirname(absoluteSource);
var rewriteOptions = {
absolute: self.outerContext.options.explicitRoot,
relative: !self.outerContext.options.explicitRoot,
imports: true,
rebase: self.outerContext.options.rebase,
fromBase: absoluteSourcePath,
toBase: isRemote ? absoluteSourcePath : toBase,
urlQuotes: self.outerContext.options.compatibility.properties.urlQuotes
};
styles = rewriteUrls(styles, rewriteOptions, self.outerContext);
self.trackSource(source, styles);
styles = self.outerContext.sourceTracker.store(source, styles);
// here we assume source map lies in the same directory as `source` does
if (self.outerContext.options.sourceMap && inputSourceMap)
self.outerContext.inputSourceMapTracker.trackLoaded(source, source, inputSourceMap);
data.push(styles);
}
return data.join('');
}
module.exports = SourceReader;
@@ -0,0 +1,31 @@
function SourceTracker() {
this.sources = [];
}
SourceTracker.prototype.store = function (filename, data) {
this.sources.push(filename);
return '__ESCAPED_SOURCE_CLEAN_CSS' + (this.sources.length - 1) + '__' +
data +
'__ESCAPED_SOURCE_END_CLEAN_CSS__';
};
SourceTracker.prototype.nextStart = function (data) {
var next = /__ESCAPED_SOURCE_CLEAN_CSS(\d+)__/.exec(data);
return next ?
{ index: next.index, filename: this.sources[~~next[1]] } :
null;
};
SourceTracker.prototype.nextEnd = function (data) {
return /__ESCAPED_SOURCE_END_CLEAN_CSS__/g.exec(data);
};
SourceTracker.prototype.removeAll = function (data) {
return data
.replace(/__ESCAPED_SOURCE_CLEAN_CSS\d+__/g, '')
.replace(/__ESCAPED_SOURCE_END_CLEAN_CSS__/g, '');
};
module.exports = SourceTracker;
+62
View File
@@ -0,0 +1,62 @@
function split(value, separator, includeSeparator, openLevel, closeLevel, firstOnly) {
var withRegex = typeof separator != 'string';
var hasSeparator = withRegex ?
separator.test(value) :
value.indexOf(separator);
if (!hasSeparator)
return [value];
openLevel = openLevel || '(';
closeLevel = closeLevel || ')';
if (value.indexOf(openLevel) == -1 && !includeSeparator && !firstOnly)
return value.split(separator);
var BACKSLASH = '\\';
var isEscaped = false;
var wasEscaped = false;
var level = 0;
var cursor = 0;
var lastStart = 0;
var len = value.length;
var tokens = [];
while (cursor < len) {
isEscaped = value[cursor] == BACKSLASH;
if (wasEscaped) {
// no-op
} else
if (value[cursor] == openLevel) {
level++;
} else if (value[cursor] == closeLevel) {
level--;
}
if (!wasEscaped && level === 0 && cursor > 0 && cursor + 1 < len && (withRegex ? separator.test(value[cursor]) : value[cursor] == separator)) {
tokens.push(value.substring(lastStart, cursor + (includeSeparator ? 1 : 0)));
lastStart = cursor + 1;
if (firstOnly && tokens.length == 1) {
break;
}
}
wasEscaped = isEscaped;
cursor++;
}
if (lastStart < cursor + 1) {
var lastValue = value.substring(lastStart);
var lastCharacter = lastValue[lastValue.length - 1];
if (!includeSeparator && (withRegex ? separator.test(lastCharacter) : lastCharacter == separator))
lastValue = lastValue.substring(0, lastValue.length - 1);
tokens.push(lastValue);
}
return tokens;
}
module.exports = split;
+121
View File
@@ -0,0 +1,121 @@
{
"_args": [
[
{
"raw": "clean-css@^3.1.9",
"scope": null,
"escapedName": "clean-css",
"name": "clean-css",
"rawSpec": "^3.1.9",
"spec": ">=3.1.9 <4.0.0",
"type": "range"
},
"c:\\xampp\\htdocs\\laravel\\node_modules\\css-loader"
]
],
"_from": "clean-css@>=3.1.9 <4.0.0",
"_id": "clean-css@3.4.27",
"_inCache": true,
"_location": "/css-loader/clean-css",
"_nodeVersion": "4.6.2",
"_npmOperationalInternal": {
"host": "s3://npm-registry-packages",
"tmp": "tmp/clean-css-3.4.27.tgz_1497007382615_0.841190344421193"
},
"_npmUser": {
"name": "jakub.pawlowicz",
"email": "contact@jakubpawlowicz.com"
},
"_npmVersion": "2.15.11",
"_phantomChildren": {},
"_requested": {
"raw": "clean-css@^3.1.9",
"scope": null,
"escapedName": "clean-css",
"name": "clean-css",
"rawSpec": "^3.1.9",
"spec": ">=3.1.9 <4.0.0",
"type": "range"
},
"_requiredBy": [
"/css-loader"
],
"_resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.27.tgz",
"_shasum": "adef75b31c160ffa5d72f4de67966e2660c1a255",
"_shrinkwrap": null,
"_spec": "clean-css@^3.1.9",
"_where": "c:\\xampp\\htdocs\\laravel\\node_modules\\css-loader",
"author": {
"name": "Jakub Pawlowicz",
"email": "contact@jakubpawlowicz.com",
"url": "http://twitter.com/jakubpawlowicz"
},
"bin": {
"cleancss": "./bin/cleancss"
},
"bugs": {
"url": "https://github.com/jakubpawlowicz/clean-css/issues"
},
"dependencies": {
"commander": "2.8.x",
"source-map": "0.4.x"
},
"description": "A well-tested CSS minifier",
"devDependencies": {
"browserify": "11.x",
"http-proxy": "1.x",
"jshint": "2.8.x",
"nock": "2.x",
"server-destroy": "1.x",
"uglify-js": "2.4.x",
"vows": "0.8.x"
},
"directories": {},
"dist": {
"shasum": "adef75b31c160ffa5d72f4de67966e2660c1a255",
"tarball": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.27.tgz"
},
"engines": {
"node": ">=0.10.0"
},
"files": [
"bin",
"lib",
"History.md",
"index.js",
"LICENSE"
],
"gitHead": "a6f97c3fb4a16b0d3e2e0021d9a1694bd430b021",
"homepage": "https://github.com/jakubpawlowicz/clean-css",
"keywords": [
"css",
"minifier"
],
"license": "MIT",
"main": "index.js",
"maintainers": [
{
"name": "goalsmashers",
"email": "jakub@goalsmashers.com"
},
{
"name": "jakub.pawlowicz",
"email": "contact@jakubpawlowicz.com"
}
],
"name": "clean-css",
"optionalDependencies": {},
"readme": "ERROR: No README data found!",
"repository": {
"type": "git",
"url": "git+https://github.com/jakubpawlowicz/clean-css.git"
},
"scripts": {
"bench": "node ./test/bench.js",
"browserify": "browserify --standalone CleanCSS index.js | uglifyjs --compress --mangle -o cleancss-browser.js",
"check": "jshint ./bin/cleancss .",
"prepublish": "npm run check",
"test": "vows"
},
"version": "3.4.27"
}
+256
View File
@@ -0,0 +1,256 @@
2.8.1 / 2015-04-22
==================
* Back out `support multiline description` Close #396 #397
2.8.0 / 2015-04-07
==================
* Add `process.execArg` support, execution args like `--harmony` will be passed to sub-commands #387 @DigitalIO @zhiyelee
* Fix bug in Git-style sub-commands #372 @zhiyelee
* Allow commands to be hidden from help #383 @tonylukasavage
* When git-style sub-commands are in use, yet none are called, display help #382 @claylo
* Add ability to specify arguments syntax for top-level command #258 @rrthomas
* Support multiline descriptions #208 @zxqfox
2.7.1 / 2015-03-11
==================
* Revert #347 (fix collisions when option and first arg have same name) which causes a bug in #367.
2.7.0 / 2015-03-09
==================
* Fix git-style bug when installed globally. Close #335 #349 @zhiyelee
* Fix collisions when option and first arg have same name. Close #346 #347 @tonylukasavage
* Add support for camelCase on `opts()`. Close #353 @nkzawa
* Add node.js 0.12 and io.js to travis.yml
* Allow RegEx options. #337 @palanik
* Fixes exit code when sub-command failing. Close #260 #332 @pirelenito
* git-style `bin` files in $PATH make sense. Close #196 #327 @zhiyelee
2.6.0 / 2014-12-30
==================
* added `Command#allowUnknownOption` method. Close #138 #318 @doozr @zhiyelee
* Add application description to the help msg. Close #112 @dalssoft
2.5.1 / 2014-12-15
==================
* fixed two bugs incurred by variadic arguments. Close #291 @Quentin01 #302 @zhiyelee
2.5.0 / 2014-10-24
==================
* add support for variadic arguments. Closes #277 @whitlockjc
2.4.0 / 2014-10-17
==================
* fixed a bug on executing the coercion function of subcommands option. Closes #270
* added `Command.prototype.name` to retrieve command name. Closes #264 #266 @tonylukasavage
* added `Command.prototype.opts` to retrieve all the options as a simple object of key-value pairs. Closes #262 @tonylukasavage
* fixed a bug on subcommand name. Closes #248 @jonathandelgado
* fixed function normalize doesnt honor option terminator. Closes #216 @abbr
2.3.0 / 2014-07-16
==================
* add command alias'. Closes PR #210
* fix: Typos. Closes #99
* fix: Unused fs module. Closes #217
2.2.0 / 2014-03-29
==================
* add passing of previous option value
* fix: support subcommands on windows. Closes #142
* Now the defaultValue passed as the second argument of the coercion function.
2.1.0 / 2013-11-21
==================
* add: allow cflag style option params, unit test, fixes #174
2.0.0 / 2013-07-18
==================
* remove input methods (.prompt, .confirm, etc)
1.3.2 / 2013-07-18
==================
* add support for sub-commands to co-exist with the original command
1.3.1 / 2013-07-18
==================
* add quick .runningCommand hack so you can opt-out of other logic when running a sub command
1.3.0 / 2013-07-09
==================
* add EACCES error handling
* fix sub-command --help
1.2.0 / 2013-06-13
==================
* allow "-" hyphen as an option argument
* support for RegExp coercion
1.1.1 / 2012-11-20
==================
* add more sub-command padding
* fix .usage() when args are present. Closes #106
1.1.0 / 2012-11-16
==================
* add git-style executable subcommand support. Closes #94
1.0.5 / 2012-10-09
==================
* fix `--name` clobbering. Closes #92
* fix examples/help. Closes #89
1.0.4 / 2012-09-03
==================
* add `outputHelp()` method.
1.0.3 / 2012-08-30
==================
* remove invalid .version() defaulting
1.0.2 / 2012-08-24
==================
* add `--foo=bar` support [arv]
* fix password on node 0.8.8. Make backward compatible with 0.6 [focusaurus]
1.0.1 / 2012-08-03
==================
* fix issue #56
* fix tty.setRawMode(mode) was moved to tty.ReadStream#setRawMode() (i.e. process.stdin.setRawMode())
1.0.0 / 2012-07-05
==================
* add support for optional option descriptions
* add defaulting of `.version()` to package.json's version
0.6.1 / 2012-06-01
==================
* Added: append (yes or no) on confirmation
* Added: allow node.js v0.7.x
0.6.0 / 2012-04-10
==================
* Added `.prompt(obj, callback)` support. Closes #49
* Added default support to .choose(). Closes #41
* Fixed the choice example
0.5.1 / 2011-12-20
==================
* Fixed `password()` for recent nodes. Closes #36
0.5.0 / 2011-12-04
==================
* Added sub-command option support [itay]
0.4.3 / 2011-12-04
==================
* Fixed custom help ordering. Closes #32
0.4.2 / 2011-11-24
==================
* Added travis support
* Fixed: line-buffered input automatically trimmed. Closes #31
0.4.1 / 2011-11-18
==================
* Removed listening for "close" on --help
0.4.0 / 2011-11-15
==================
* Added support for `--`. Closes #24
0.3.3 / 2011-11-14
==================
* Fixed: wait for close event when writing help info [Jerry Hamlet]
0.3.2 / 2011-11-01
==================
* Fixed long flag definitions with values [felixge]
0.3.1 / 2011-10-31
==================
* Changed `--version` short flag to `-V` from `-v`
* Changed `.version()` so it's configurable [felixge]
0.3.0 / 2011-10-31
==================
* Added support for long flags only. Closes #18
0.2.1 / 2011-10-24
==================
* "node": ">= 0.4.x < 0.7.0". Closes #20
0.2.0 / 2011-09-26
==================
* Allow for defaults that are not just boolean. Default peassignment only occurs for --no-*, optional, and required arguments. [Jim Isaacs]
0.1.0 / 2011-08-24
==================
* Added support for custom `--help` output
0.0.5 / 2011-08-18
==================
* Changed: when the user enters nothing prompt for password again
* Fixed issue with passwords beginning with numbers [NuckChorris]
0.0.4 / 2011-08-15
==================
* Fixed `Commander#args`
0.0.3 / 2011-08-15
==================
* Added default option value support
0.0.2 / 2011-08-15
==================
* Added mask support to `Command#password(str[, mask], fn)`
* Added `Command#password(str, fn)`
0.0.1 / 2010-01-03
==================
* Initial release
+22
View File
@@ -0,0 +1,22 @@
(The MIT License)
Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>
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.
+342
View File
@@ -0,0 +1,342 @@
# Commander.js
[![Build Status](https://api.travis-ci.org/tj/commander.js.svg)](http://travis-ci.org/tj/commander.js)
[![NPM Version](http://img.shields.io/npm/v/commander.svg?style=flat)](https://www.npmjs.org/package/commander)
[![NPM Downloads](https://img.shields.io/npm/dm/commander.svg?style=flat)](https://www.npmjs.org/package/commander)
[![Join the chat at https://gitter.im/tj/commander.js](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/tj/commander.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
The complete solution for [node.js](http://nodejs.org) command-line interfaces, inspired by Ruby's [commander](https://github.com/tj/commander).
[API documentation](http://tj.github.com/commander.js/)
## Installation
$ npm install commander
## Option parsing
Options with commander are defined with the `.option()` method, also serving as documentation for the options. The example below parses args and options from `process.argv`, leaving remaining args as the `program.args` array which were not consumed by options.
```js
#!/usr/bin/env node
/**
* Module dependencies.
*/
var program = require('commander');
program
.version('0.0.1')
.option('-p, --peppers', 'Add peppers')
.option('-P, --pineapple', 'Add pineapple')
.option('-b, --bbq-sauce', 'Add bbq sauce')
.option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble')
.parse(process.argv);
console.log('you ordered a pizza with:');
if (program.peppers) console.log(' - peppers');
if (program.pineapple) console.log(' - pineapple');
if (program.bbqSauce) console.log(' - bbq');
console.log(' - %s cheese', program.cheese);
```
Short flags may be passed as a single arg, for example `-abc` is equivalent to `-a -b -c`. Multi-word options such as "--template-engine" are camel-cased, becoming `program.templateEngine` etc.
## Coercion
```js
function range(val) {
return val.split('..').map(Number);
}
function list(val) {
return val.split(',');
}
function collect(val, memo) {
memo.push(val);
return memo;
}
function increaseVerbosity(v, total) {
return total + 1;
}
program
.version('0.0.1')
.usage('[options] <file ...>')
.option('-i, --integer <n>', 'An integer argument', parseInt)
.option('-f, --float <n>', 'A float argument', parseFloat)
.option('-r, --range <a>..<b>', 'A range', range)
.option('-l, --list <items>', 'A list', list)
.option('-o, --optional [value]', 'An optional value')
.option('-c, --collect [value]', 'A repeatable value', collect, [])
.option('-v, --verbose', 'A value that can be increased', increaseVerbosity, 0)
.parse(process.argv);
console.log(' int: %j', program.integer);
console.log(' float: %j', program.float);
console.log(' optional: %j', program.optional);
program.range = program.range || [];
console.log(' range: %j..%j', program.range[0], program.range[1]);
console.log(' list: %j', program.list);
console.log(' collect: %j', program.collect);
console.log(' verbosity: %j', program.verbose);
console.log(' args: %j', program.args);
```
## Regular Expression
```js
program
.version('0.0.1')
.option('-s --size <size>', 'Pizza size', /^(large|medium|small)$/i, 'medium')
.option('-d --drink [drink]', 'Drink', /^(coke|pepsi|izze)$/i)
.parse(process.argv);
console.log(' size: %j', program.size);
console.log(' drink: %j', program.drink);
```
## Variadic arguments
The last argument of a command can be variadic, and only the last argument. To make an argument variadic you have to
append `...` to the argument name. Here is an example:
```js
#!/usr/bin/env node
/**
* Module dependencies.
*/
var program = require('commander');
program
.version('0.0.1')
.command('rmdir <dir> [otherDirs...]')
.action(function (dir, otherDirs) {
console.log('rmdir %s', dir);
if (otherDirs) {
otherDirs.forEach(function (oDir) {
console.log('rmdir %s', oDir);
});
}
});
program.parse(process.argv);
```
An `Array` is used for the value of a variadic argument. This applies to `program.args` as well as the argument passed
to your action as demonstrated above.
## Specify the argument syntax
```js
#!/usr/bin/env node
var program = require('../');
program
.version('0.0.1')
.arguments('<cmd> [env]')
.action(function (cmd, env) {
cmdValue = cmd;
envValue = env;
});
program.parse(process.argv);
if (typeof cmdValue === 'undefined') {
console.error('no command given!');
process.exit(1);
}
console.log('command:', cmdValue);
console.log('environment:', envValue || "no environment given");
```
## Git-style sub-commands
```js
// file: ./examples/pm
var program = require('..');
program
.version('0.0.1')
.command('install [name]', 'install one or more packages')
.command('search [query]', 'search with optional query')
.command('list', 'list packages installed')
.parse(process.argv);
```
When `.command()` is invoked with a description argument, no `.action(callback)` should be called to handle sub-commands, otherwise there will be an error. This tells commander that you're going to use separate executables for sub-commands, much like `git(1)` and other popular tools.
The commander will try to search the executables in the directory of the entry script (like `./examples/pm`) with the name `program-command`, like `pm-install`, `pm-search`.
If the program is designed to be installed globally, make sure the executables have proper modes, like `755`.
### `--harmony`
You can enable `--harmony` option in two ways:
* Use `#! /usr/bin/env node --harmony` in the sub-commands scripts. Note some os version dont support this pattern.
* Use the `--harmony` option when call the command, like `node --harmony examples/pm publish`. The `--harmony` option will be preserved when spawning sub-command process.
## Automated --help
The help information is auto-generated based on the information commander already knows about your program, so the following `--help` info is for free:
```
$ ./examples/pizza --help
Usage: pizza [options]
An application for pizzas ordering
Options:
-h, --help output usage information
-V, --version output the version number
-p, --peppers Add peppers
-P, --pineapple Add pineapple
-b, --bbq Add bbq sauce
-c, --cheese <type> Add the specified type of cheese [marble]
-C, --no-cheese You do not want any cheese
```
## Custom help
You can display arbitrary `-h, --help` information
by listening for "--help". Commander will automatically
exit once you are done so that the remainder of your program
does not execute causing undesired behaviours, for example
in the following executable "stuff" will not output when
`--help` is used.
```js
#!/usr/bin/env node
/**
* Module dependencies.
*/
var program = require('commander');
program
.version('0.0.1')
.option('-f, --foo', 'enable some foo')
.option('-b, --bar', 'enable some bar')
.option('-B, --baz', 'enable some baz');
// must be before .parse() since
// node's emit() is immediate
program.on('--help', function(){
console.log(' Examples:');
console.log('');
console.log(' $ custom-help --help');
console.log(' $ custom-help -h');
console.log('');
});
program.parse(process.argv);
console.log('stuff');
```
Yields the following help output when `node script-name.js -h` or `node script-name.js --help` are run:
```
Usage: custom-help [options]
Options:
-h, --help output usage information
-V, --version output the version number
-f, --foo enable some foo
-b, --bar enable some bar
-B, --baz enable some baz
Examples:
$ custom-help --help
$ custom-help -h
```
## .outputHelp()
Output help information without exiting.
If you want to display help by default (e.g. if no command was provided), you can use something like:
```js
var program = require('commander');
program
.version('0.0.1')
.command('getstream [url]', 'get stream URL')
.parse(process.argv);
if (!process.argv.slice(2).length) {
program.outputHelp();
}
```
## .help()
Output help information and exit immediately.
## Examples
```js
var program = require('commander');
program
.version('0.0.1')
.option('-C, --chdir <path>', 'change the working directory')
.option('-c, --config <path>', 'set config path. defaults to ./deploy.conf')
.option('-T, --no-tests', 'ignore test hook')
program
.command('setup [env]')
.description('run setup commands for all envs')
.option("-s, --setup_mode [mode]", "Which setup mode to use")
.action(function(env, options){
var mode = options.setup_mode || "normal";
env = env || 'all';
console.log('setup for %s env(s) with %s mode', env, mode);
});
program
.command('exec <cmd>')
.alias('ex')
.description('execute the given remote cmd')
.option("-e, --exec_mode <mode>", "Which exec mode to use")
.action(function(cmd, options){
console.log('exec "%s" using %s mode', cmd, options.exec_mode);
}).on('--help', function() {
console.log(' Examples:');
console.log();
console.log(' $ deploy exec sequential');
console.log(' $ deploy exec async');
console.log();
});
program
.command('*')
.action(function(env){
console.log('deploying "%s"', env);
});
program.parse(process.argv);
```
More Demos can be found in the [examples](https://github.com/tj/commander.js/tree/master/examples) directory.
## License
MIT
File diff suppressed because it is too large Load Diff
+108
View File
@@ -0,0 +1,108 @@
{
"_args": [
[
{
"raw": "commander@2.8.x",
"scope": null,
"escapedName": "commander",
"name": "commander",
"rawSpec": "2.8.x",
"spec": ">=2.8.0 <2.9.0",
"type": "range"
},
"c:\\xampp\\htdocs\\laravel\\node_modules\\css-loader\\node_modules\\clean-css"
]
],
"_from": "commander@>=2.8.0 <2.9.0",
"_id": "commander@2.8.1",
"_inCache": true,
"_location": "/css-loader/commander",
"_nodeVersion": "0.12.0",
"_npmUser": {
"name": "zhiyelee",
"email": "zhiyelee@gmail.com"
},
"_npmVersion": "2.5.1",
"_phantomChildren": {},
"_requested": {
"raw": "commander@2.8.x",
"scope": null,
"escapedName": "commander",
"name": "commander",
"rawSpec": "2.8.x",
"spec": ">=2.8.0 <2.9.0",
"type": "range"
},
"_requiredBy": [
"/css-loader/clean-css"
],
"_resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
"_shasum": "06be367febfda0c330aa1e2a072d3dc9762425d4",
"_shrinkwrap": null,
"_spec": "commander@2.8.x",
"_where": "c:\\xampp\\htdocs\\laravel\\node_modules\\css-loader\\node_modules\\clean-css",
"author": {
"name": "TJ Holowaychuk",
"email": "tj@vision-media.ca"
},
"bugs": {
"url": "https://github.com/tj/commander.js/issues"
},
"dependencies": {
"graceful-readlink": ">= 1.0.0"
},
"description": "the complete solution for node.js command-line programs",
"devDependencies": {
"should": ">= 0.0.1",
"sinon": ">= 1.14.1"
},
"directories": {},
"dist": {
"shasum": "06be367febfda0c330aa1e2a072d3dc9762425d4",
"tarball": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz"
},
"engines": {
"node": ">= 0.6.x"
},
"files": [
"index.js"
],
"gitHead": "c6c84726050b19c0373de27cd359f3baddff579f",
"homepage": "https://github.com/tj/commander.js",
"keywords": [
"command",
"option",
"parser"
],
"license": "MIT",
"main": "index",
"maintainers": [
{
"name": "tjholowaychuk",
"email": "tj@vision-media.ca"
},
{
"name": "somekittens",
"email": "rkoutnik@gmail.com"
},
{
"name": "zhiyelee",
"email": "zhiyelee@gmail.com"
},
{
"name": "thethomaseffect",
"email": "thethomaseffect@gmail.com"
}
],
"name": "commander",
"optionalDependencies": {},
"readme": "ERROR: No README data found!",
"repository": {
"type": "git",
"url": "git+https://github.com/tj/commander.js.git"
},
"scripts": {
"test": "make test"
},
"version": "2.8.1"
}
+510
View File
@@ -0,0 +1,510 @@
# Source Map
This is a library to generate and consume the source map format
[described here][format].
This library is written in the Asynchronous Module Definition format, and works
in the following environments:
* Modern Browsers supporting ECMAScript 5 (either after the build, or with an
AMD loader such as RequireJS)
* Inside Firefox (as a JSM file, after the build)
* With NodeJS versions 0.8.X and higher
## Node
$ npm install source-map
## Building from Source (for everywhere else)
Install Node and then run
$ git clone https://fitzgen@github.com/mozilla/source-map.git
$ cd source-map
$ npm link .
Next, run
$ node Makefile.dryice.js
This should spew a bunch of stuff to stdout, and create the following files:
* `dist/source-map.js` - The unminified browser version.
* `dist/source-map.min.js` - The minified browser version.
* `dist/SourceMap.jsm` - The JavaScript Module for inclusion in Firefox source.
## Examples
### Consuming a source map
```js
var rawSourceMap = {
version: 3,
file: 'min.js',
names: ['bar', 'baz', 'n'],
sources: ['one.js', 'two.js'],
sourceRoot: 'http://example.com/www/js/',
mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
};
var smc = new SourceMapConsumer(rawSourceMap);
console.log(smc.sources);
// [ 'http://example.com/www/js/one.js',
// 'http://example.com/www/js/two.js' ]
console.log(smc.originalPositionFor({
line: 2,
column: 28
}));
// { source: 'http://example.com/www/js/two.js',
// line: 2,
// column: 10,
// name: 'n' }
console.log(smc.generatedPositionFor({
source: 'http://example.com/www/js/two.js',
line: 2,
column: 10
}));
// { line: 2, column: 28 }
smc.eachMapping(function (m) {
// ...
});
```
### Generating a source map
In depth guide:
[**Compiling to JavaScript, and Debugging with Source Maps**](https://hacks.mozilla.org/2013/05/compiling-to-javascript-and-debugging-with-source-maps/)
#### With SourceNode (high level API)
```js
function compile(ast) {
switch (ast.type) {
case 'BinaryExpression':
return new SourceNode(
ast.location.line,
ast.location.column,
ast.location.source,
[compile(ast.left), " + ", compile(ast.right)]
);
case 'Literal':
return new SourceNode(
ast.location.line,
ast.location.column,
ast.location.source,
String(ast.value)
);
// ...
default:
throw new Error("Bad AST");
}
}
var ast = parse("40 + 2", "add.js");
console.log(compile(ast).toStringWithSourceMap({
file: 'add.js'
}));
// { code: '40 + 2',
// map: [object SourceMapGenerator] }
```
#### With SourceMapGenerator (low level API)
```js
var map = new SourceMapGenerator({
file: "source-mapped.js"
});
map.addMapping({
generated: {
line: 10,
column: 35
},
source: "foo.js",
original: {
line: 33,
column: 2
},
name: "christopher"
});
console.log(map.toString());
// '{"version":3,"file":"source-mapped.js","sources":["foo.js"],"names":["christopher"],"mappings":";;;;;;;;;mCAgCEA"}'
```
## API
Get a reference to the module:
```js
// NodeJS
var sourceMap = require('source-map');
// Browser builds
var sourceMap = window.sourceMap;
// Inside Firefox
let sourceMap = {};
Components.utils.import('resource:///modules/devtools/SourceMap.jsm', sourceMap);
```
### SourceMapConsumer
A SourceMapConsumer instance represents a parsed source map which we can query
for information about the original file positions by giving it a file position
in the generated source.
#### new SourceMapConsumer(rawSourceMap)
The only parameter is the raw source map (either as a string which can be
`JSON.parse`'d, or an object). According to the spec, source maps have the
following attributes:
* `version`: Which version of the source map spec this map is following.
* `sources`: An array of URLs to the original source files.
* `names`: An array of identifiers which can be referrenced by individual
mappings.
* `sourceRoot`: Optional. The URL root from which all sources are relative.
* `sourcesContent`: Optional. An array of contents of the original source files.
* `mappings`: A string of base64 VLQs which contain the actual mappings.
* `file`: Optional. The generated filename this source map is associated with.
#### SourceMapConsumer.prototype.computeColumnSpans()
Compute the last column for each generated mapping. The last column is
inclusive.
#### SourceMapConsumer.prototype.originalPositionFor(generatedPosition)
Returns the original source, line, and column information for the generated
source's line and column positions provided. The only argument is an object with
the following properties:
* `line`: The line number in the generated source.
* `column`: The column number in the generated source.
* `bias`: Either `SourceMapConsumer.GREATEST_LOWER_BOUND` or
`SourceMapConsumer.LEAST_UPPER_BOUND`. Specifies whether to return the closest
element that is smaller than or greater than the one we are searching for,
respectively, if the exact element cannot be found. Defaults to
`SourceMapConsumer.GREATEST_LOWER_BOUND`.
and an object is returned with the following properties:
* `source`: The original source file, or null if this information is not
available.
* `line`: The line number in the original source, or null if this information is
not available.
* `column`: The column number in the original source, or null or null if this
information is not available.
* `name`: The original identifier, or null if this information is not available.
#### SourceMapConsumer.prototype.generatedPositionFor(originalPosition)
Returns the generated line and column information for the original source,
line, and column positions provided. The only argument is an object with
the following properties:
* `source`: The filename of the original source.
* `line`: The line number in the original source.
* `column`: The column number in the original source.
and an object is returned with the following properties:
* `line`: The line number in the generated source, or null.
* `column`: The column number in the generated source, or null.
#### SourceMapConsumer.prototype.allGeneratedPositionsFor(originalPosition)
Returns all generated line and column information for the original source, line,
and column provided. If no column is provided, returns all mappings
corresponding to a either the line we are searching for or the next closest line
that has any mappings. Otherwise, returns all mappings corresponding to the
given line and either the column we are searching for or the next closest column
that has any offsets.
The only argument is an object with the following properties:
* `source`: The filename of the original source.
* `line`: The line number in the original source.
* `column`: Optional. The column number in the original source.
and an array of objects is returned, each with the following properties:
* `line`: The line number in the generated source, or null.
* `column`: The column number in the generated source, or null.
#### SourceMapConsumer.prototype.hasContentsOfAllSources()
Return true if we have the embedded source content for every source listed in
the source map, false otherwise.
In other words, if this method returns `true`, then `smc.sourceContentFor(s)`
will succeed for every source `s` in `smc.sources`.
#### SourceMapConsumer.prototype.sourceContentFor(source[, returnNullOnMissing])
Returns the original source content for the source provided. The only
argument is the URL of the original source file.
If the source content for the given source is not found, then an error is
thrown. Optionally, pass `true` as the second param to have `null` returned
instead.
#### SourceMapConsumer.prototype.eachMapping(callback, context, order)
Iterate over each mapping between an original source/line/column and a
generated line/column in this source map.
* `callback`: The function that is called with each mapping. Mappings have the
form `{ source, generatedLine, generatedColumn, originalLine, originalColumn,
name }`
* `context`: Optional. If specified, this object will be the value of `this`
every time that `callback` is called.
* `order`: Either `SourceMapConsumer.GENERATED_ORDER` or
`SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to iterate over
the mappings sorted by the generated file's line/column order or the
original's source/line/column order, respectively. Defaults to
`SourceMapConsumer.GENERATED_ORDER`.
### SourceMapGenerator
An instance of the SourceMapGenerator represents a source map which is being
built incrementally.
#### new SourceMapGenerator([startOfSourceMap])
You may pass an object with the following properties:
* `file`: The filename of the generated source that this source map is
associated with.
* `sourceRoot`: A root for all relative URLs in this source map.
* `skipValidation`: Optional. When `true`, disables validation of mappings as
they are added. This can improve performance but should be used with
discretion, as a last resort. Even then, one should avoid using this flag when
running tests, if possible.
#### SourceMapGenerator.fromSourceMap(sourceMapConsumer)
Creates a new SourceMapGenerator based on a SourceMapConsumer
* `sourceMapConsumer` The SourceMap.
#### SourceMapGenerator.prototype.addMapping(mapping)
Add a single mapping from original source line and column to the generated
source's line and column for this source map being created. The mapping object
should have the following properties:
* `generated`: An object with the generated line and column positions.
* `original`: An object with the original line and column positions.
* `source`: The original source file (relative to the sourceRoot).
* `name`: An optional original token name for this mapping.
#### SourceMapGenerator.prototype.setSourceContent(sourceFile, sourceContent)
Set the source content for an original source file.
* `sourceFile` the URL of the original source file.
* `sourceContent` the content of the source file.
#### SourceMapGenerator.prototype.applySourceMap(sourceMapConsumer[, sourceFile[, sourceMapPath]])
Applies a SourceMap for a source file to the SourceMap.
Each mapping to the supplied source file is rewritten using the
supplied SourceMap. Note: The resolution for the resulting mappings
is the minimium of this map and the supplied map.
* `sourceMapConsumer`: The SourceMap to be applied.
* `sourceFile`: Optional. The filename of the source file.
If omitted, sourceMapConsumer.file will be used, if it exists.
Otherwise an error will be thrown.
* `sourceMapPath`: Optional. The dirname of the path to the SourceMap
to be applied. If relative, it is relative to the SourceMap.
This parameter is needed when the two SourceMaps aren't in the same
directory, and the SourceMap to be applied contains relative source
paths. If so, those relative source paths need to be rewritten
relative to the SourceMap.
If omitted, it is assumed that both SourceMaps are in the same directory,
thus not needing any rewriting. (Supplying `'.'` has the same effect.)
#### SourceMapGenerator.prototype.toString()
Renders the source map being generated to a string.
### SourceNode
SourceNodes provide a way to abstract over interpolating and/or concatenating
snippets of generated JavaScript source code, while maintaining the line and
column information associated between those snippets and the original source
code. This is useful as the final intermediate representation a compiler might
use before outputting the generated JS and source map.
#### new SourceNode([line, column, source[, chunk[, name]]])
* `line`: The original line number associated with this source node, or null if
it isn't associated with an original line.
* `column`: The original column number associated with this source node, or null
if it isn't associated with an original column.
* `source`: The original source's filename; null if no filename is provided.
* `chunk`: Optional. Is immediately passed to `SourceNode.prototype.add`, see
below.
* `name`: Optional. The original identifier.
#### SourceNode.fromStringWithSourceMap(code, sourceMapConsumer[, relativePath])
Creates a SourceNode from generated code and a SourceMapConsumer.
* `code`: The generated code
* `sourceMapConsumer` The SourceMap for the generated code
* `relativePath` The optional path that relative sources in `sourceMapConsumer`
should be relative to.
#### SourceNode.prototype.add(chunk)
Add a chunk of generated JS to this source node.
* `chunk`: A string snippet of generated JS code, another instance of
`SourceNode`, or an array where each member is one of those things.
#### SourceNode.prototype.prepend(chunk)
Prepend a chunk of generated JS to this source node.
* `chunk`: A string snippet of generated JS code, another instance of
`SourceNode`, or an array where each member is one of those things.
#### SourceNode.prototype.setSourceContent(sourceFile, sourceContent)
Set the source content for a source file. This will be added to the
`SourceMap` in the `sourcesContent` field.
* `sourceFile`: The filename of the source file
* `sourceContent`: The content of the source file
#### SourceNode.prototype.walk(fn)
Walk over the tree of JS snippets in this node and its children. The walking
function is called once for each snippet of JS and is passed that snippet and
the its original associated source's line/column location.
* `fn`: The traversal function.
#### SourceNode.prototype.walkSourceContents(fn)
Walk over the tree of SourceNodes. The walking function is called for each
source file content and is passed the filename and source content.
* `fn`: The traversal function.
#### SourceNode.prototype.join(sep)
Like `Array.prototype.join` except for SourceNodes. Inserts the separator
between each of this source node's children.
* `sep`: The separator.
#### SourceNode.prototype.replaceRight(pattern, replacement)
Call `String.prototype.replace` on the very right-most source snippet. Useful
for trimming whitespace from the end of a source node, etc.
* `pattern`: The pattern to replace.
* `replacement`: The thing to replace the pattern with.
#### SourceNode.prototype.toString()
Return the string representation of this source node. Walks over the tree and
concatenates all the various snippets together to one string.
#### SourceNode.prototype.toStringWithSourceMap([startOfSourceMap])
Returns the string representation of this tree of source nodes, plus a
SourceMapGenerator which contains all the mappings between the generated and
original sources.
The arguments are the same as those to `new SourceMapGenerator`.
## Tests
[![Build Status](https://travis-ci.org/mozilla/source-map.png?branch=master)](https://travis-ci.org/mozilla/source-map)
Install NodeJS version 0.8.0 or greater, then run `node test/run-tests.js`.
To add new tests, create a new file named `test/test-<your new test name>.js`
and export your test functions with names that start with "test", for example
```js
exports["test doing the foo bar"] = function (assert, util) {
...
};
```
The new test will be located automatically when you run the suite.
The `util` argument is the test utility module located at `test/source-map/util`.
The `assert` argument is a cut down version of node's assert module. You have
access to the following assertion functions:
* `doesNotThrow`
* `equal`
* `ok`
* `strictEqual`
* `throws`
(The reason for the restricted set of test functions is because we need the
tests to run inside Firefox's test suite as well and so the assert module is
shimmed in that environment. See `build/assert-shim.js`.)
[format]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
[feature]: https://wiki.mozilla.org/DevTools/Features/SourceMap
[Dryice]: https://github.com/mozilla/dryice
+56
View File
@@ -0,0 +1,56 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
define('test/source-map/assert', ['exports'], function (exports) {
let do_throw = function (msg) {
throw new Error(msg);
};
exports.init = function (throw_fn) {
do_throw = throw_fn;
};
exports.doesNotThrow = function (fn) {
try {
fn();
}
catch (e) {
do_throw(e.message);
}
};
exports.equal = function (actual, expected, msg) {
msg = msg || String(actual) + ' != ' + String(expected);
if (actual != expected) {
do_throw(msg);
}
};
exports.ok = function (val, msg) {
msg = msg || String(val) + ' is falsey';
if (!Boolean(val)) {
do_throw(msg);
}
};
exports.strictEqual = function (actual, expected, msg) {
msg = msg || String(actual) + ' !== ' + String(expected);
if (actual !== expected) {
do_throw(msg);
}
};
exports.throws = function (fn) {
try {
fn();
do_throw('Expected an error to be thrown, but it wasn\'t.');
}
catch (e) {
}
};
});
+152
View File
@@ -0,0 +1,152 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
/**
* Define a module along with a payload.
* @param {string} moduleName Name for the payload
* @param {ignored} deps Ignored. For compatibility with CommonJS AMD Spec
* @param {function} payload Function with (require, exports, module) params
*/
function define(moduleName, deps, payload) {
if (typeof moduleName != "string") {
throw new TypeError('Expected string, got: ' + moduleName);
}
if (arguments.length == 2) {
payload = deps;
}
if (moduleName in define.modules) {
throw new Error("Module already defined: " + moduleName);
}
define.modules[moduleName] = payload;
};
/**
* The global store of un-instantiated modules
*/
define.modules = {};
/**
* We invoke require() in the context of a Domain so we can have multiple
* sets of modules running separate from each other.
* This contrasts with JSMs which are singletons, Domains allows us to
* optionally load a CommonJS module twice with separate data each time.
* Perhaps you want 2 command lines with a different set of commands in each,
* for example.
*/
function Domain() {
this.modules = {};
this._currentModule = null;
}
(function () {
/**
* Lookup module names and resolve them by calling the definition function if
* needed.
* There are 2 ways to call this, either with an array of dependencies and a
* callback to call when the dependencies are found (which can happen
* asynchronously in an in-page context) or with a single string an no callback
* where the dependency is resolved synchronously and returned.
* The API is designed to be compatible with the CommonJS AMD spec and
* RequireJS.
* @param {string[]|string} deps A name, or names for the payload
* @param {function|undefined} callback Function to call when the dependencies
* are resolved
* @return {undefined|object} The module required or undefined for
* array/callback method
*/
Domain.prototype.require = function(deps, callback) {
if (Array.isArray(deps)) {
var params = deps.map(function(dep) {
return this.lookup(dep);
}, this);
if (callback) {
callback.apply(null, params);
}
return undefined;
}
else {
return this.lookup(deps);
}
};
function normalize(path) {
var bits = path.split('/');
var i = 1;
while (i < bits.length) {
if (bits[i] === '..') {
bits.splice(i-1, 1);
} else if (bits[i] === '.') {
bits.splice(i, 1);
} else {
i++;
}
}
return bits.join('/');
}
function join(a, b) {
a = a.trim();
b = b.trim();
if (/^\//.test(b)) {
return b;
} else {
return a.replace(/\/*$/, '/') + b;
}
}
function dirname(path) {
var bits = path.split('/');
bits.pop();
return bits.join('/');
}
/**
* Lookup module names and resolve them by calling the definition function if
* needed.
* @param {string} moduleName A name for the payload to lookup
* @return {object} The module specified by aModuleName or null if not found.
*/
Domain.prototype.lookup = function(moduleName) {
if (/^\./.test(moduleName)) {
moduleName = normalize(join(dirname(this._currentModule), moduleName));
}
if (moduleName in this.modules) {
var module = this.modules[moduleName];
return module;
}
if (!(moduleName in define.modules)) {
throw new Error("Module not defined: " + moduleName);
}
var module = define.modules[moduleName];
if (typeof module == "function") {
var exports = {};
var previousModule = this._currentModule;
this._currentModule = moduleName;
module(this.require.bind(this), exports, { id: moduleName, uri: "" });
this._currentModule = previousModule;
module = exports;
}
// cache the resulting module object for next time
this.modules[moduleName] = module;
return module;
};
}());
define.Domain = Domain;
define.globalDomain = new Domain();
var require = define.globalDomain.require.bind(define.globalDomain);
@@ -0,0 +1,21 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
/*
* WARNING!
*
* Do not edit this file directly, it is built from the sources at
* https://github.com/mozilla/source-map/
*/
///////////////////////////////////////////////////////////////////////////////
this.EXPORTED_SYMBOLS = [ "SourceMapConsumer", "SourceMapGenerator", "SourceNode" ];
Components.utils.import("resource://gre/modules/devtools/Console.jsm");
Components.utils.import('resource://gre/modules/devtools/Require.jsm');
+18
View File
@@ -0,0 +1,18 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
/*
* WARNING!
*
* Do not edit this file directly, it is built from the sources at
* https://github.com/mozilla/source-map/
*/
Components.utils.import('resource://gre/modules/devtools/Require.jsm');
Components.utils.import('resource://gre/modules/devtools/SourceMap.jsm');
this.EXPORTED_SYMBOLS = [ "define", "runSourceMapTests" ];
@@ -0,0 +1,8 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
///////////////////////////////////////////////////////////////////////////////
this.sourceMap = {
SourceMapConsumer: require('source-map/source-map-consumer').SourceMapConsumer,
SourceMapGenerator: require('source-map/source-map-generator').SourceMapGenerator,
SourceNode: require('source-map/source-node').SourceNode
};
@@ -0,0 +1,6 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
///////////////////////////////////////////////////////////////////////////////
this.SourceMapConsumer = require('source-map/source-map-consumer').SourceMapConsumer;
this.SourceMapGenerator = require('source-map/source-map-generator').SourceMapGenerator;
this.SourceNode = require('source-map/source-node').SourceNode;
+21
View File
@@ -0,0 +1,21 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
function runSourceMapTests(modName, do_throw) {
let mod = require(modName);
let assert = require('test/source-map/assert');
let util = require('test/source-map/util');
assert.init(do_throw);
for (let k in mod) {
if (/^test/.test(k)) {
mod[k](assert, util);
}
}
}
this.runSourceMapTests = runSourceMapTests;
+8
View File
@@ -0,0 +1,8 @@
/*
* WARNING!
*
* Do not edit this file directly, it is built from the sources at
* https://github.com/mozilla/source-map/
*/
Components.utils.import('resource://test/Utils.jsm');
+3
View File
@@ -0,0 +1,3 @@
function run_test() {
runSourceMapTests('{THIS_MODULE}', do_throw);
}
+8
View File
@@ -0,0 +1,8 @@
/*
* Copyright 2009-2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE.txt or:
* http://opensource.org/licenses/BSD-3-Clause
*/
exports.SourceMapGenerator = require('./source-map/source-map-generator').SourceMapGenerator;
exports.SourceMapConsumer = require('./source-map/source-map-consumer').SourceMapConsumer;
exports.SourceNode = require('./source-map/source-node').SourceNode;
@@ -0,0 +1,107 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var util = require('./util');
/**
* A data structure which is a combination of an array and a set. Adding a new
* member is O(1), testing for membership is O(1), and finding the index of an
* element is O(1). Removing elements from the set is not supported. Only
* strings are supported for membership.
*/
function ArraySet() {
this._array = [];
this._set = {};
}
/**
* Static method for creating ArraySet instances from an existing array.
*/
ArraySet.fromArray = function ArraySet_fromArray(aArray, aAllowDuplicates) {
var set = new ArraySet();
for (var i = 0, len = aArray.length; i < len; i++) {
set.add(aArray[i], aAllowDuplicates);
}
return set;
};
/**
* Return how many unique items are in this ArraySet. If duplicates have been
* added, than those do not count towards the size.
*
* @returns Number
*/
ArraySet.prototype.size = function ArraySet_size() {
return Object.getOwnPropertyNames(this._set).length;
};
/**
* Add the given string to this set.
*
* @param String aStr
*/
ArraySet.prototype.add = function ArraySet_add(aStr, aAllowDuplicates) {
var isDuplicate = this.has(aStr);
var idx = this._array.length;
if (!isDuplicate || aAllowDuplicates) {
this._array.push(aStr);
}
if (!isDuplicate) {
this._set[util.toSetString(aStr)] = idx;
}
};
/**
* Is the given string a member of this set?
*
* @param String aStr
*/
ArraySet.prototype.has = function ArraySet_has(aStr) {
return Object.prototype.hasOwnProperty.call(this._set,
util.toSetString(aStr));
};
/**
* What is the index of the given string in the array?
*
* @param String aStr
*/
ArraySet.prototype.indexOf = function ArraySet_indexOf(aStr) {
if (this.has(aStr)) {
return this._set[util.toSetString(aStr)];
}
throw new Error('"' + aStr + '" is not in the set.');
};
/**
* What is the element at the given index?
*
* @param Number aIdx
*/
ArraySet.prototype.at = function ArraySet_at(aIdx) {
if (aIdx >= 0 && aIdx < this._array.length) {
return this._array[aIdx];
}
throw new Error('No element indexed by ' + aIdx);
};
/**
* Returns the array representation of this set (which has the proper indices
* indicated by indexOf). Note that this is a copy of the internal array used
* for storing the members so that no one can mess with internal state.
*/
ArraySet.prototype.toArray = function ArraySet_toArray() {
return this._array.slice();
};
exports.ArraySet = ArraySet;
});
@@ -0,0 +1,146 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*
* Based on the Base 64 VLQ implementation in Closure Compiler:
* https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java
*
* Copyright 2011 The Closure Compiler Authors. All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var base64 = require('./base64');
// A single base 64 digit can contain 6 bits of data. For the base 64 variable
// length quantities we use in the source map spec, the first bit is the sign,
// the next four bits are the actual value, and the 6th bit is the
// continuation bit. The continuation bit tells us whether there are more
// digits in this value following this digit.
//
// Continuation
// | Sign
// | |
// V V
// 101011
var VLQ_BASE_SHIFT = 5;
// binary: 100000
var VLQ_BASE = 1 << VLQ_BASE_SHIFT;
// binary: 011111
var VLQ_BASE_MASK = VLQ_BASE - 1;
// binary: 100000
var VLQ_CONTINUATION_BIT = VLQ_BASE;
/**
* Converts from a two-complement value to a value where the sign bit is
* placed in the least significant bit. For example, as decimals:
* 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
* 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
*/
function toVLQSigned(aValue) {
return aValue < 0
? ((-aValue) << 1) + 1
: (aValue << 1) + 0;
}
/**
* Converts to a two-complement value from a value where the sign bit is
* placed in the least significant bit. For example, as decimals:
* 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
* 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
*/
function fromVLQSigned(aValue) {
var isNegative = (aValue & 1) === 1;
var shifted = aValue >> 1;
return isNegative
? -shifted
: shifted;
}
/**
* Returns the base 64 VLQ encoded value.
*/
exports.encode = function base64VLQ_encode(aValue) {
var encoded = "";
var digit;
var vlq = toVLQSigned(aValue);
do {
digit = vlq & VLQ_BASE_MASK;
vlq >>>= VLQ_BASE_SHIFT;
if (vlq > 0) {
// There are still more digits in this value, so we must make sure the
// continuation bit is marked.
digit |= VLQ_CONTINUATION_BIT;
}
encoded += base64.encode(digit);
} while (vlq > 0);
return encoded;
};
/**
* Decodes the next base 64 VLQ value from the given string and returns the
* value and the rest of the string via the out parameter.
*/
exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) {
var strLen = aStr.length;
var result = 0;
var shift = 0;
var continuation, digit;
do {
if (aIndex >= strLen) {
throw new Error("Expected more digits in base 64 VLQ value.");
}
digit = base64.decode(aStr.charCodeAt(aIndex++));
if (digit === -1) {
throw new Error("Invalid base64 digit: " + aStr.charAt(aIndex - 1));
}
continuation = !!(digit & VLQ_CONTINUATION_BIT);
digit &= VLQ_BASE_MASK;
result = result + (digit << shift);
shift += VLQ_BASE_SHIFT;
} while (continuation);
aOutParam.value = fromVLQSigned(result);
aOutParam.rest = aIndex;
};
});

Some files were not shown because too many files have changed in this diff Show More