-
Notifications
You must be signed in to change notification settings - Fork 9
/
css.js
262 lines (239 loc) · 7.53 KB
/
css.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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
/**
* CSS loading plugin for widgets.
*
* This plugin will load and wait for a css file. This can be handy to load the css
* specific to a widget.
*
* This plugin uses the link load event and a work-around on old webkit browsers.
* The work-around watches a stylesheet until its rules are
* available (not null or undefined).
*
* This plugin will return the path of the inserted css file relative to requirejs baseUrl.
*
* @example:
* To load the css file `myproj/comp.css`:
* ```
* require(["requirejs-dplugins/css!myproj/comp.css"], function (){
* // Code placed here will wait for myproj/comp.css before running.
* });
* ```
*
* Or as a widget dependency:
* ```
* define(["requirejs-dplugins/css!myproj/comp.css"], function (){
* // My widget factory
* });
* ```
*
* @module requirejs-dplugins/css
*/
define([
"./has",
"module"
], function (has, module) {
"use strict";
has.add("event-link-onload-api", function (global) {
var wk = global.navigator.userAgent.match(/AppleWebKit\/([\d.]+)/);
return !wk || parseInt(wk[1], 10) > 535;
});
var cache = {},
lastInsertedLink;
/**
* Return a promise that resolves when the specified link has finished loading.
* @param {HTMLLinkElement} link - The link element to be notified for.
* @returns {Promise} - A promise.
* @private
*/
var listenOnLoad = function (link) {
return new Promise(function (resolve) {
if (has("event-link-onload-api")) {
// We're using "readystatechange" because IE happily support both
link.onreadystatechange = link.onload = function () {
if (!link.readyState || link.readyState === "complete") {
link.onreadystatechange = link.onload = null;
resolve();
}
};
} else {
var poll = function () {
// watches a stylesheet for loading signs.
var sheet = link.sheet || link.styleSheet,
styleSheets = document.styleSheets;
if (sheet && Array.prototype.lastIndexOf.call(styleSheets, sheet) !== -1) {
resolve();
} else {
setTimeout(poll, 25);
}
};
poll();
}
});
};
var loadCss = {
id: module.id,
/*jshint maxcomplexity: 11*/
/**
* Loads a css file.
* @param {string} path - The css file to load.
* @param {Function} require - A local require function to use to load other modules.
* @param {Function} callback - A function to call when the specified stylesheets have been loaded.
* @method
*/
load: function (path, require, callback) {
if (has("builder")) {
buildFunctions.addOnce(loadList, path);
callback();
return;
}
// Replace single css bundles by corresponding layer.
var config = module.config();
if (config.layersMap) {
path = config.layersMap[path] || path;
}
var head = document.head || document.getElementsByTagName("head")[0],
url = require.toUrl(path),
link;
// if the url has not already been injected/loaded, create a new promise.
if (!cache[url]) {
// hook up load detector(s)
link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = url;
head.insertBefore(link, lastInsertedLink ? lastInsertedLink.nextSibling : head.firstChild);
lastInsertedLink = link;
cache[url] = listenOnLoad(link);
}
cache[url].then(function () {
// The stylesheet has been loaded, so call the callback
callback(path);
});
}
};
if (has("builder")) {
// build variables
var loadList = [],
writePluginFiles;
var buildFunctions = {
/**
* Write the layersMap configuration to the corresponding modules layer.
* The configuration will look like this:
* ```js
* require.config({
* config: {
* "requirejs-dplugins/css": {
* layersMap: {
* "module1.css": "path/to/layer.css",
* "module2.css": "path/to/layer.css"
* }
* }
* }
* });
* ```
*
* @param {Function} write - This function takes a string as argument
* and writes it to the modules layer.
* @param {string} mid - Current module id.
* @param {string} dest - Current css layer path.
* @param {Array} loadList - List of css files contained in current css layer.
*/
writeConfig: function (write, mid, dest, loadList) {
var cssConf = {
config: {}
};
cssConf.config[mid] = {
layersMap: {}
};
loadList.forEach(function (path) {
cssConf.config[mid].layersMap[path] = dest;
});
write("require.config(" + JSON.stringify(cssConf) + ");");
},
/**
* Concat and optimize all css files required by a modules layer and write the result.
* The node module `clean-css` is responsible for optimizing the css and correcting
* images paths.
*
* @param {Function} writePluginFiles - The write function provided by the builder to `writeFile`.
* and writes it to the modules layer.
* @param {string} dest - Current css layer path.
* @param {Array} loadList - List of css files contained in current css layer.
* @returns {boolean} Return `true` if the function successfully writes the layer.
*/
writeLayer: function (writePluginFiles, dest, loadList) {
function tryRequire(paths) {
var module;
var path = paths.shift();
if (path) {
try {
// This is a node-require so it is synchronous.
module = require.nodeRequire(path);
} catch (e) {
return tryRequire(paths);
}
}
return module;
}
var path = require.getNodePath(require.toUrl(module.id).replace(/[^\/]*$/, "node_modules/clean-css"));
var CleanCSS = tryRequire([path, "clean-css"]);
var fs = require.nodeRequire("fs");
loadList = loadList.map(require.toUrl)
.filter(function (path) {
if (!fs.existsSync(path)) {
console.log(">> Css file '" + path + "' was not found.");
return false;
}
return true;
});
if (CleanCSS) {
var layer = "";
loadList.forEach(function (src) {
var result = new CleanCSS({
relativeTo: "./",
target: dest
}).minify("@import url(" + src + ");");
// Support clean-css version 2.x and 3.x
layer += result.styles || result;
});
writePluginFiles(dest, layer);
return true;
} else {
console.log(">> WARNING: Node module clean-css not found. Skipping CSS inlining. If you" +
" want CSS inlining run 'npm install clean-css' in your console.");
loadList.forEach(function (src) {
writePluginFiles(src, fs.readFileSync(src));
});
return false;
}
},
/**
* Add the string to `ary` if it's not already in it.
* @param {Array} ary - Destination array.
* @param {string} element - Element to add.
*/
addOnce: function (ary, element) {
if (ary.indexOf(element) === -1) {
ary.push(element);
}
}
};
loadCss.writeFile = function (pluginName, resource, require, write) {
writePluginFiles = write;
};
loadCss.onLayerEnd = function (write, data) {
if (data.name && data.path) {
var dest = data.path.replace(/\.js$/, ".css");
var destMid = data.name + ".css";
// Write layer file
var success = buildFunctions.writeLayer(writePluginFiles, dest, loadList);
// Write css config on the layer if the layer was successfully written.
success && buildFunctions.writeConfig(write, module.id, destMid, loadList);
// Reset loadList
loadList = [];
}
};
// Expose build functions to be used by delite/theme
loadCss.buildFunctions = buildFunctions;
}
return loadCss;
});