Permalink
Browse files

major update

  • Loading branch information...
1 parent cebdfb9 commit 0770adbc05aa73b7651fb52d2d2c413f931626be guybedford committed Oct 2, 2012
View
@@ -0,0 +1 @@
+example/www-built
View
@@ -3,53 +3,75 @@ require-css
Optimizable CSS requiring with RequireJS
+For LESS inclusion, use [require-less](https://github.com/guybedford/require-less), which behaves and builds the css exactly like this module apart from the preprocessing step.
+
Overview
--------
-Allows the construction of scripts that can inherit CSS, using the simple RequireJS syntax:
+Allows the construction of scripts that can require CSS, using the simple RequireJS syntax:
```javascript
-define(['css!styles/main'], function(css) {
+define(['css!styles/main'], function() {
//code that requires the stylesheet: styles/main.css
});
```
-1. When run on the client, the CSS is downloaded and injected into the head dynamically.
-
-2. When run on the server, the CSS is simply ammended into a buffer (`css.buffer`).
-
-3. When run as part of a build with the RequireJS Optimizer, 'css!' dependencies are inlined into the built layer JavaScript for automatic injection. The layers can be built fully compatible with layer exclusions and inclusions.
-
-4. When setting the 'separateCSS' build parameter flag to true, the RequireJS Optimizer creates a separate CSS file matching the module layer in the build.
-
-_All url path normalization within the CSS files is handled automatically_
+### CSS Requiring
+* *CSS requiring* CSS is downloaded and injected into the page. Url path normalization of assets within the CSS file is managed.
+* *Fully compatible load callback* The plugin call is loaded once the CSS is downloaded and injected. This method works across all browsers and devices. It bypasses the `onload` support issues that make this a tricky problem otherwise (http://requirejs.org/docs/faq-advanced.html#css).
+* *Cross-domain CSS* Cross-domain CSS is loaded with the use of a `<link>` tag, but build and onload support are not provided for these - the callback fires instantly.
+### CSS Building
+* *CSS builds* When run as part of a build with the RequireJS optimizer, `css!` dependencies are automatically inlined into the built layer within the JavaScript, fully compatible with layering. CSS injection is performed as soon as the layer is loaded.
+* *Option to build separate layer CSS files* A `separateCSS` build parameter allows for built layers to output their css files separately, instead of inline with the JavaScript, for manual inclusion.
+* *CSS compression* CSS redundancy compression is easily supported through the external library, [csso](https://github.com/css/csso).
Installation and Setup
----------------------
-The easiest setup is with volo (`npm install volo` / https://github.com/volojs/volo):
+Download the require-css folder manually or use [volo](https://github.com/volojs/volo)(`npm install volo -g`):
```
-volo add guybedford/css
+volo add guybedford/require-css
```
-Volo will automatically install the following plugins:
-* requirejs/text
- The standard text plugin provided by Require JS - used for loading CSS resources on the client.
-
-Volo will also create the 'css' wrapper for easy requiring.
+Volo will automatically install the text plugin, which is a needed dependency. For a manual install, [download it here](https://raw.github.com/requirejs/text/latest/text.js) and copy it into the baseUrl folder.
-If installing without Volo, ensure you have the 'text' plugin dependency in the scripts folder, and add the 'css' shortcut reference in the map config to 'css/main':
+For ease of use add the following [map configuration](http://requirejs.org/docs/api.html#config-map) in RequireJS:
```javascript
map: {
'*': {
- 'css': 'css/main'
+ 'css': 'require-css/css'
}
}
```
+Use Cases and Benefits
+----------------------
+
+### Motivation
+
+The use case for RequireCSS came out of a need to manage templates and their CSS together. When writing a large dynamic application, with templates being rendered on the client-side, it makes more sense to inject the CSS as needed as part of rendering the template than having server-side CSS management or one large file. When building, templates and CSS can be bundled together into [separate layers](http://requirejs.org/docs/1.0/docs/faq-optimization.html#priority) to allow much more fine grained control over loading than is typical.
+
+### Script-inlined CSS Benefits
+
+If the layer is included as a `<script>` tag, only one browser request is needed instead of many separate CSS requests with `<link>` tags.
+
+Even better than including a layer as a `<script>` tag is to include the layer dynamically with a non-blocking require. Then the page can be displayed while the layer is still loading asynchronously in the background. In this case, the CSS that goes with a template being dynamically rendered is loaded with that same script asynchronously. No longer does it need to sit in a `<link>` tag that blocks the page display unnecessarily.
+
+
+Injection methods
+-----------------
+
+Previous attempts have used the `onLoad` callback for a `<link>` tag to register CSS require completion. This method is limited since while it is supported in all versions of IE, it has varying support in other browsers and mobile devices. Because it can't reliably work, there are many hacks which are used to get around this.
+
+In a typical use case, one doesn't mind if the assets have completed downloading yet. Hence the main CSS load requirement is that the CSS has been downloaded and parsed.
+
+CSS parsing speeds are quick enough that a dynamic injection will in most cases be instantanous. Thus, there is no real disadvantage to the method used here.
+
+CSS content is downloaded as text with the text plugin, injected into a `<style>` tag, and the load callback is run immediately after injection. Typically this would be followed by a rendering stage, and this hasn't resulted in any content flashes in tests so far across devices.
+
Optimizer Configuration
-----------------------
@@ -111,12 +133,25 @@ require(['css!mycss!'], ...);
*The suffix `!` will ensure that the CSS is never output to a file and always inlined dynamically in the js.*
+CSS Compression
+---------------
+
+CSS compression is supported with [csso](https://github.com/css/csso).
+
+To enable the CSS compression, install csso with npm:
+
+```
+ npm install csso -g
+```
+
+The build log will display the compression results.
+
Conditional CSS
---
Some styles are conditional on the environment. For example mobile stylesheets and IE-specific stylesheets.
-To manage this, use the [Require-IS](https://github.com/guybedford/is) module.
+To manage this, use the [Require-IS](https://github.com/guybedford/require-is) module.
With Require-IS, one can do:
@@ -140,7 +175,6 @@ Separate build layers can then be made for mobile specific use. Read more at the
Roadmap
-------
-
-* Comprehensive CSS minification including style reduction
-* LESS extension
+* ~Comprehensive CSS minification including style reduction~
+* ~LESS extension~
* Sprite compilation
@@ -2,8 +2,6 @@ define(['require', './normalize'], function(req, normalize) {
var baseUrl = req.toUrl('.');
var cssAPI = {};
-
- cssAPI.buffer = {};
function compress(css) {
if (typeof process !== "undefined" && process.versions && !!process.versions.node && require.nodeRequire) {
@@ -126,69 +124,61 @@ define(['require', './normalize'], function(req, normalize) {
cssAPI.defined = {};
- //string hashing for naming css strings allowing for reinjection avoidance
- //courtesy of http://erlycoder.com/49/javascript-hash-functions-to-convert-string-into-integer-hash-
- var djb2 = function(str) {
- var hash = 5381;
- for (i = 0; i < str.length; i++)
- hash = ((hash << 5) + hash) + str.charCodeAt(i); /* hash * 33 + c */
- return hash;
- }
-
- cssAPI.inject = function(cssId, css, reinject) {
- if (css === undefined && reinject === undefined)
- cssId = djb2((css = cssId));
- else if (typeof css == 'boolean' && reinject === undefined) {
+ //nb can remove server injection API
+ cssAPI.set = function(cssId, css, reinject) {
+ //if (css === undefined && reinject === undefined)
+ // cssId = djb2((css = cssId));
+ /*else */if (typeof css == 'boolean' && reinject === undefined) {
reinject = css;
css = cssId;
}
- if (cssAPI.defined[cssId] !== undefined && !reinject)
+ if (this.defined[cssId] !== undefined && !reinject)
return;
- cssAPI.defined[cssId] = css;
+ this.defined[cssId] = css;
return cssId;
}
- cssAPI.loadFile = function(cssId, complete, reload) {
- complete = complete || function(){};
+ cssAPI.loadFile = function(cssId, parse) {
//nb despite the callback this version is synchronous to work with the write API
//dont reload
- if (cssAPI.defined[cssId] !== undefined && !reload)
- return complete(cssAPI);
+ if (this.defined[cssId] !== undefined)
+ return;
var fileUrl = cssId;
if (fileUrl.substr(fileUrl.length - 1, 1) == '!')
fileUrl = fileUrl.substr(0, fileUrl.length - 1);
- if (fileUrl.substr(fileUrl.length - 4, 4) != '.css')
+ if (fileUrl.substr(fileUrl.length - 4, 4) != '.css' && !parse)
fileUrl += '.css';
fileUrl = req.toUrl(fileUrl);
//external URLS don't get added (just like JS requires)
if (fileUrl.substr(0, 7) == 'http://' || fileUrl.substr(0, 8) == 'https://')
- return complete(cssAPI);
+ return;
//add to the buffer
var css = loadFile(fileUrl);
+ if (parse)
+ css = parse(css);
css = normalize(css, fileUrl, baseUrl);
- cssAPI.inject(cssId, css);
- complete(cssAPI);
+ this.set(cssId, css);
}
- cssAPI.clear = function(cssId) {
+ /* cssAPI.clear = function(cssId) {
if (cssId)
delete cssAPI.defined[cssId];
else
for (var o in cssAPI.defined)
delete cssAPI.defined[o];
- }
+ } */
cssAPI.load = function(name, req, load, config) {
//store config
- cssAPI.config = cssAPI.config || config;
+ this.config = this.config || config;
//just return - 'write' calls are made after exclusions so we run loading there
load();
}
@@ -200,56 +190,70 @@ define(['require', './normalize'], function(req, normalize) {
}
//list of cssIds included in this layer
- var layerBuffer = [];
+ cssAPI._layerBuffer = [];
- cssAPI.write = function(pluginName, moduleName, write) {
-
- cssAPI.pluginName = cssAPI.pluginName || pluginName;
-
+ cssAPI.write = function(pluginName, moduleName, write, parse) {
if (moduleName.substr(0, 2) != '>>') {
+ //external URLS don't get added (just like JS requires)
+ if (moduleName.substr(0, 7) == 'http://' || moduleName.substr(0, 8) == 'https://')
+ return;
+
+ if (moduleName.substr(moduleName.length - 1, 1) == '!')
+ moduleName = moduleName.substr(0, moduleName.length - 1);
+
//(sync load)
- cssAPI.loadFile(moduleName);
+ this.loadFile(moduleName, parse);
//ammend the layer buffer and write the module as a stub
- layerBuffer.push(moduleName);
+ this._layerBuffer.push(moduleName);
+
write.asModule(pluginName + '!' + moduleName, 'define(function(){})');
}
//buffer / write point
- else
- cssAPI.onLayerComplete(moduleName.substr(2), write);
+ else {
+ this.onLayerComplete(moduleName.substr(2), write);
+ }
+ }
+
+ //string hashing used to name css that doesnt have an id (which is the case for builds)
+ //courtesy of http://erlycoder.com/49/javascript-hash-functions-to-convert-string-into-integer-hash-
+ var djb2 = function(str) {
+ var hash = 5381;
+ for (i = 0; i < str.length; i++)
+ hash = ((hash << 5) + hash) + str.charCodeAt(i);
+ return hash;
}
cssAPI.onLayerComplete = function(name, write) {
-
//separateCSS parameter set either globally or as a layer setting
var separateCSS = false;
- if (cssAPI.config.separateCSS)
+ if (this.config.separateCSS)
separateCSS = true;
- if (cssAPI.config.modules)
- for (var i = 0; i < cssAPI.config.modules.length; i++)
- if (typeof cssAPI.config.modules[i].separateCSS == 'boolean')
- separateCSS = cssAPI.config.modules[i].separateCSS;
+ if (this.config.modules)
+ for (var i = 0; i < this.config.modules.length; i++)
+ if (typeof this.config.modules[i].separateCSS == 'boolean')
+ separateCSS = this.config.modules[i].separateCSS;
//calculate layer css and index injection script
var css = '';
var index = '';
- for (var i = 0; i < layerBuffer.length; i++) {
- css += cssAPI.defined[layerBuffer[i]];
- index += 'defined[\'' + layerBuffer[i] + '\'] = ';
+ for (var i = 0; i < this._layerBuffer.length; i++) {
+ css += this.defined[this._layerBuffer[i]];
+ index += 'defined[\'' + this._layerBuffer[i] + '\'] = ';
}
if (separateCSS) {
if (typeof console != 'undefined' && console.log)
console.log('Writing CSS! file: ' + name + '\n');
//calculate the css output path for this layer
- var path = cssAPI.config.dir ? cssAPI.config.dir + name + '.css' : cssAPI.config.out.replace(/\.js$/, '.css');
+ var path = this.config.dir ? this.config.dir + name + '.css' : this.config.out.replace(/\.js$/, '.css');
var output = compress(normalize(css, baseUrl, path));
saveFile(path, output);
//write the layer index into the layer
- write('require([\'' + cssAPI.pluginName + '\'], function(css) { \n'
+ write('require([\'css\'], function(css) { \n'
+ 'var defined = css.defined; \n'
+ index + 'true; \n'
+ '});');
@@ -258,19 +262,20 @@ define(['require', './normalize'], function(req, normalize) {
if (css == '')
return;
//write the injection and layer index into the layer
-
//prepare the css
css = escape(compress(css));
//derive the absolute path for the normalize helper
- var normalizeParts = cssAPI.pluginName.split('/');
+ var normalizeParts = req.toUrl('css').substr(baseUrl.length - 1).split('/');
normalizeParts[normalizeParts.length - 1] = 'normalize';
var normalizeName = normalizeParts.join('/');
- write('require([\'' + cssAPI.pluginName + '\', \'' + normalizeName + '\', \'require\'], function(css, normalize, require) { \n'
+ write('require([\'css\', \'' + normalizeName + '\', \'require\'], function(css, normalize, require) { \n'
+ 'var defined = css.defined; \n'
+ + 'var baseUrl = require.toUrl(\'.\'); \n'
+ + 'baseUrl = baseUrl.substr(0, 1) == \'.\' ? baseUrl.substr(1) : baseUrl; \n'
+ index + 'true; \n'
- + 'css.inject(normalize(\'' + css + '\', \'/\', require.toUrl(\'.\').substr(1))); \n'
+ + 'css.set(\'' + djb2(css) + '\', normalize(\'' + css + '\', \'/\', baseUrl)); \n'
+ '});');
}
Oops, something went wrong.

0 comments on commit 0770adb

Please sign in to comment.