forked from meteor/meteor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathminifiers.js
135 lines (115 loc) · 5.22 KB
/
minifiers.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
var cssParse = Npm.require('css-parse');
var cssStringify = Npm.require('css-stringify');
var path = Npm.require('path');
var url = Npm.require('url');
UglifyJS = Npm.require('uglify-js');
UglifyJSMinify = UglifyJS.minify;
CssTools = {
parseCss: cssParse,
stringifyCss: cssStringify,
minifyCss: function (cssText) {
return CssTools.minifyCssAst(cssParse(cssText));
},
minifyCssAst: function (cssAst) {
return MinifyAst(cssAst);
},
mergeCssAsts: function (cssAsts, warnCb) {
var rulesPredicate = function (rules) {
if (! _.isArray(rules))
rules = [rules];
return function (node) {
return _.contains(rules, node.type);
}
};
// Simple concatenation of CSS files would break @import rules
// located in the beginning of a file. Before concatenation, pull them to
// the beginning of a new syntax tree so they always precede other rules.
var newAst = {
type: 'stylesheet',
stylesheet: { rules: [] }
};
_.each(cssAsts, function (ast) {
// Pick only the imports from the beginning of file ignoring @charset
// rules as every file is assumed to be in UTF-8.
var charsetRules = _.filter(ast.stylesheet.rules,
rulesPredicate("charset"));
if (_.any(charsetRules, function (rule) {
// According to MDN, only 'UTF-8' and "UTF-8" are the correct encoding
// directives representing UTF-8.
return ! /^(['"])UTF-8\1$/.test(rule.charset);
})) {
warnCb(ast.filename, "@charset rules in this file will be ignored as UTF-8 is the only encoding supported");
}
ast.stylesheet.rules = _.reject(ast.stylesheet.rules,
rulesPredicate("charset"));
var importCount = 0;
for (var i = 0; i < ast.stylesheet.rules.length; i++)
if (! rulesPredicate(["import", "comment"])(ast.stylesheet.rules[i])) {
importCount = i;
break;
}
CssTools.rewriteCssUrls(ast);
var imports = ast.stylesheet.rules.splice(0, importCount);
newAst.stylesheet.rules = newAst.stylesheet.rules.concat(imports);
// if there are imports left in the middle of file, warn user as it might
// be a potential bug (imports are valid only in the beginning of file).
if (_.any(ast.stylesheet.rules, rulesPredicate("import"))) {
// XXX make this an error?
warnCb(ast.filename, "there are some @import rules those are not taking effect as they are required to be in the beginning of the file");
}
});
// Now we can put the rest of CSS rules into new AST
_.each(cssAsts, function (ast) {
newAst.stylesheet.rules =
newAst.stylesheet.rules.concat(ast.stylesheet.rules);
});
return newAst;
},
// We are looking for all relative urls defined with the `url()` functional
// notation and rewriting them to the equivalent absolute url using the
// `position.source` path provided by css-parse
// For performance reasons this function acts by side effect by modifying the
// given AST without doing a deep copy.
rewriteCssUrls: function (ast) {
var isRelative = function(path) {
return path && path.charAt(0) !== '/';
};
_.each(ast.stylesheet.rules, function(rule, ruleIndex) {
var basePath = path.dirname(rule.position.source);
// Set the correct basePath based on how the linked asset will be served.
// XXX This is wrong. We are coupling the information about how files will
// be served by the web server to the information how they were stored
// originally on the filesystem in the project structure. Ideally, there
// should be some module that tells us precisely how each asset will be
// served but for now we are just assuming that everything that comes from
// a folder starting with "/packages/" is served on the same path as
// it was on the filesystem and everything else is served on root "/".
if (! basePath.match(/^\/?packages\//i))
basePath = "/";
_.each(rule.declarations, function(declaration, declarationIndex) {
var parts, resource, absolutePath, quotes, oldCssUrl, newCssUrl;
var value = declaration.value;
// Match css values containing some functional calls to `url(URI)` where
// URI is optionally quoted.
// Note that a css value can contains other elements, for instance:
// background: top center url("background.png") black;
// or even multiple url(), for instance for multiple backgrounds.
var cssUrlRegex = /url\s*\(\s*(['"]?)(.+?)\1\s*\)/gi;
while (parts = cssUrlRegex.exec(value)) {
oldCssUrl = parts[0];
quotes = parts[1];
resource = url.parse(parts[2]);
// Rewrite relative paths to absolute paths.
// We don't rewrite urls starting with a protocol definition such as
// http, https, or data.
if (isRelative(resource.path) && resource.protocol === null) {
absolutePath = path.join(basePath, resource.path);
newCssUrl = "url(" + quotes + absolutePath + quotes + ")";
value = value.replace(oldCssUrl, newCssUrl);
}
}
declaration.value = value;
});
});
}
};