Repository: andreypopp/styling
Branch: master
Commit: 1caf6ade2f79
Files: 25
Total size: 15.7 KB
Directory structure:
gitextract_7jxyp0wc/
├── .gitignore
├── Makefile
├── README.md
├── Styling.js
├── browser.js
├── examples/
│ ├── extract/
│ │ ├── .gitignore
│ │ ├── Button.js
│ │ ├── Button.style.js
│ │ ├── README.md
│ │ ├── Theme.js
│ │ ├── package.json
│ │ └── webpack.config.js
│ └── simple/
│ ├── .gitignore
│ ├── Button.js
│ ├── Button.style.js
│ ├── README.md
│ ├── Theme.js
│ ├── package.json
│ └── webpack.config.js
├── index.js
├── loader.js
├── omit.js
├── package.json
├── renderStyling.js
└── renderStylingSheet.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
node_modules/
================================================
FILE: Makefile
================================================
.DELETE_ON_ERROR:
version-major version-minor version-patch:
@npm version $(@:version-%=%)
publish:
@git push --tags origin HEAD:master
@npm publish
================================================
FILE: README.md
================================================
Styling
=======
Styling is the [Webpack][] based tool to write component styles with the full
power of JavaScript:
```js
import styling from 'styling'
import {baseColor} from './theme'
export let button = styling({
backgroundColor: baseColor
})
```
Why
---
* Modules, variables, functions, all of these works out of the box because you
use JavaScript.
* Rich ecosystem of ready to use [npm][] packages: for example you can use
[color][] for color manipulation.
* Compatability with the existent CSS tools such as [autoprefixer][] and a ton
of other [PostCSS][] transforms.
* Compatability with the existent JS tools such as compile-to-js languages
(CoffeeScript, TypeScript), type checkers (FlowType), linters (ESLint) and
others.
How
---
Styling is implemented as a [Webpack][] loader which executes JavaScript code to
produce *styling* objects.
Each styling object is then converted to a [CSS module][] and passed further to
Webpack CSS processing pipeline (usually css-loader and style-loader).
Consuming styling styles is no different than consuming a CSS module: you get a
mapping of CSS class names which can be used to style your components.
Limitations
-----------
You should still keep your UI code and your stylesheet code separate as
stylesheet code executes during bundling and doesn't have any runtime
representation.
Installation
------------
Install from [npm][]:
```bash
% npm install styling
```
Usage
-----
Add the following configuration to `webpack.config.js`:
```js
var styling = require('styling')
module.exports = {
module: {
loaders: [
{
test: /\.style\.js/,
loader: styling(
['style', 'css'], // loaders to execute after styling
['babel'] // loaders to execute before styling
)
}
]
}
}
```
Function `styling` configures loader and accepts two arguments, one for
*postloaders* and one for *preloaders*.
Now you can write styles with the full power of JavaScript, `Button.style.js`:
```js
import styling from 'styling'
export let self = styling({
backgroundColor: 'red',
borderWidth: 1 + 10,
hover: {
borderWidth: 100
}
})
```
And consume them, `Button.js`:
```js
import ButtonStyle from './Button.style'
export function render() {
return ``
}
```
Usage with Extract Text Webpack plugin
--------------------------------------
Styling is compatible with [extract-text-webpack-plugin][] so you can have your
styles extracted into a separate CSS bundle by Webpack. This is how you
configure it to do so:
```js
var styling = require('styling')
var ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')
module.exports = {
module: {
loaders: [
{
test: /\.style\.js/,
loader: styling(ExtractTextWebpackPlugin.extract('style', 'css'), 'babel')
}
]
},
plugins: [
new ExtractTextWebpackPlugin('bundle.css')
]
}
```
[npm]: http://npmjs.org
[Webpack]: http://webpack.github.io/
[extract-text-webpack-plugin]: https://github.com/webpack/extract-text-webpack-plugin
[color]: https://www.npmjs.com/package/color
[CSS module]: https://github.com/css-modules/css-modules
[autoprefixer]: https://github.com/postcss/autoprefixer
[PostCSS]: http://postcss.parts/
================================================
FILE: Styling.js
================================================
/**
* @copyright 2015, Andrey Popp
*/
var KEY = '@@styling';
function Styling(spec) {
this[KEY] = spec;
this.rules = spec;
}
Styling.prototype.getSpec = function Styling_getStyle() {
return this[KEY];
}
Styling.is = function Styling_is(obj) {
return obj && obj[KEY] !== undefined;
}
module.exports = Styling;
================================================
FILE: browser.js
================================================
/**
* @copyright 2015, Andrey Popp
*/
var Styling = require('./Styling');
function styling(spec) {
return new Styling(spec);
}
module.exports = styling;
================================================
FILE: examples/extract/.gitignore
================================================
build
================================================
FILE: examples/extract/Button.js
================================================
import Style from './Button.style'
export default class Button extends React.Component {
render() {
return (
)
}
}
================================================
FILE: examples/extract/Button.style.js
================================================
import styling from 'styling'
import {bgColor} from './Theme'
export let self = styling({
background: bgColor
})
export let icon = styling({
padding: 10 + 5
})
export let caption = styling({
fontWeight: 'bold'
})
================================================
FILE: examples/extract/README.md
================================================
styling-example-extract
=======================
This is an example of using styling to write component styles which are then
extracted into a separate CSS chunk.
Build:
% npm install .
% npm run webpack
Project description:
├── webpack.config.js Webpack config
├── Button.js Component
├── Button.style.js Component styles
└── Theme.js Theme constants (used by styles)
Build output desciption:
build/
├── bundle.css Built styles
└── bundle.js Built code
================================================
FILE: examples/extract/Theme.js
================================================
export let bgColor = 'red'
================================================
FILE: examples/extract/package.json
================================================
{
"name": "styling-example-simple",
"version": "1.0.0",
"scripts": {
"webpack": "webpack"
},
"dependencies": {
"styling": "../../",
"babel-loader": "^5.3.2",
"css-loader": "^0.16.0",
"extract-text-webpack-plugin": "^0.8.2",
"style-loader": "^0.12.3",
"webpack": "^1.11.0"
}
}
================================================
FILE: examples/extract/webpack.config.js
================================================
var styling = require('styling');
var ExtractTextWebpackPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: './Button',
output: {
path: './build',
filename: 'bundle.js'
},
module: {
loaders: [
{
include: /\.style\.js$/,
loader: styling(ExtractTextWebpackPlugin.extract('style', 'css?modules'), 'babel')
},
{
include: /\.js$/,
loader: 'babel'
}
]
},
plugins: [
new ExtractTextWebpackPlugin('bundle.css')
]
};
================================================
FILE: examples/simple/.gitignore
================================================
build
================================================
FILE: examples/simple/Button.js
================================================
import Style from './Button.style'
export default class Button {
render() {
return (
)
}
}
================================================
FILE: examples/simple/Button.style.js
================================================
import styling from 'styling'
import {bgColor} from './Theme'
export let self = styling({
background: bgColor
})
export let icon = styling({
padding: 10 + 5
})
export let caption = styling({
fontWeight: 'bold'
})
================================================
FILE: examples/simple/README.md
================================================
styling-example-simple
======================
This is an example of using styling to write component styles.
Build:
% npm install .
% npm run webpack
Project description:
├── webpack.config.js Webpack config
├── Button.js Component
├── Button.style.js Component styles
└── Theme.js Theme constants (used by styles)
Build output desciption:
build/
└── bundle.js Built bundle (including code and styles)
================================================
FILE: examples/simple/Theme.js
================================================
export let bgColor = 'red'
================================================
FILE: examples/simple/package.json
================================================
{
"name": "styling-example-simple",
"version": "1.0.0",
"scripts": {
"webpack": "webpack"
},
"dependencies": {
"styling": "../../",
"babel-loader": "^5.3.2",
"css-loader": "^0.16.0",
"style-loader": "^0.12.3",
"webpack": "^1.11.0"
}
}
================================================
FILE: examples/simple/webpack.config.js
================================================
var styling = require('styling');
module.exports = {
entry: './Button',
output: {
path: './build',
filename: 'bundle.js'
},
module: {
loaders: [
{
include: /\.style\.js$/,
loader: styling(['style', 'css'], ['babel'])
},
{
include: /\.js$/,
loader: 'babel'
}
]
}
};
================================================
FILE: index.js
================================================
/**
* @copyright 2015, Andrey Popp
*/
var omitRequest = require.resolve('./omit');
var loaderRequest = require.resolve('./loader');
module.exports = function styling(post, pre) {
if (Array.isArray(post)) {
post = post.join('!');
}
if (Array.isArray(pre)) {
pre = pre.join('!');
}
return [omitRequest, post, loaderRequest, pre || ''].join('!');
}
================================================
FILE: loader.js
================================================
/**
* @copyright 2015, Andrey Popp
*/
var path = require('path');
var Module = require('module');
var Styling = require('./Styling');
var renderStylingSheet = require('./renderStylingSheet');
var NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin');
var NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin');
var LibraryTemplatePlugin = require('webpack/lib/LibraryTemplatePlugin');
var SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');
var LimitChunkCountPlugin = require('webpack/lib/optimize/LimitChunkCountPlugin');
var renderStylingSheetMod = require.resolve('./renderStylingSheet');
var extractTextWebpackPluginKey;
try {
extractTextWebpackPluginKey = path.dirname(require.resolve('extract-text-webpack-plugin'));
} catch (error) {}
module.exports = function styling(content) {
this.cacheable();
if (this[__dirname] === false) {
return '';
} else if (typeof this[extractTextWebpackPluginKey] === 'function') {
return '';
} else if (this[extractTextWebpackPluginKey] === false) {
var request = this.request.split('!').slice(this.loaderIndex + 1).join('!');
produce(this, request, this.async());
} else {
return '';
}
};
module.exports.pitch = function stylingPitch(request, precedingRequest, data) {
this.cacheable();
if (this[__dirname] === false) {
// if we already inside the loader
return;
} else if (extractTextWebpackPluginKey in this) {
// if extract-text-webpack-plugin is active we do all work in a loader phase
return;
} else {
produce(this, request, this.async());
}
};
function produce(loader, request, callback) {
var outputFilename = "styling-output-filename";
var outputOptions = {filename: outputFilename};
var childCompiler = getRootCompilation(loader).createChildCompiler("styling-compiler", outputOptions);
childCompiler.apply(new NodeTemplatePlugin(outputOptions));
childCompiler.apply(new LibraryTemplatePlugin(null, "commonjs2"));
childCompiler.apply(new NodeTargetPlugin());
childCompiler.apply(new SingleEntryPlugin(loader.context, "!!" + request));
childCompiler.apply(new LimitChunkCountPlugin({ maxChunks: 1 }));
var subCache = "subcache " + __dirname + " " + request;
childCompiler.plugin("compilation", function(compilation) {
if (compilation.cache) {
if(!compilation.cache[subCache]) {
compilation.cache[subCache] = {};
}
compilation.cache = compilation.cache[subCache];
}
});
// We set loaderContext[__dirname] = false to indicate we already in
// a child compiler so we don't spawn another child compilers from there.
childCompiler.plugin("this-compilation", function(compilation) {
compilation.plugin("normal-module-loader", function(loaderContext) {
loaderContext[__dirname] = false;
if (extractTextWebpackPluginKey in loader) {
loaderContext[extractTextWebpackPluginKey] = loader[extractTextWebpackPluginKey];
}
});
});
var source;
childCompiler.plugin("after-compile", function(compilation, callback) {
source = compilation.assets[outputFilename] && compilation.assets[outputFilename].source();
// Remove all chunk assets
compilation.chunks.forEach(function(chunk) {
chunk.files.forEach(function(file) {
delete compilation.assets[file];
});
});
callback();
});
childCompiler.runAsChild(function(error, entries, compilation) {
if (error) {
return callback(error);
}
if (compilation.errors.length > 0) {
return callback(compilation.errors[0]);
}
if (!source) {
return callback(new Error("Didn't get a result from child compiler"));
}
compilation.fileDependencies.forEach(function(dep) {
loader.addDependency(dep);
});
compilation.contextDependencies.forEach(function(dep) {
loader.addContextDependency(dep);
});
try {
var exports = loader.exec(source, request);
var text = renderStylingSheet(exports);
} catch (e) {
return callback(e);
}
if (text) {
callback(null, text);
} else {
callback();
}
});
}
function getRootCompilation(loader) {
var compiler = loader._compiler;
var compilation = loader._compilation;
while (compiler.parentCompilation) {
compilation = compiler.parentCompilation;
compiler = compilation.compiler;
}
return compilation;
}
================================================
FILE: omit.js
================================================
var loaderPath = require.resolve('./loader');
module.exports = function(content) {
this.cacheable();
return content;
}
module.exports.pitch = function(request) {
if (this[__dirname] === false) {
request = request.split('!');
while (request.length > 1) {
var req = request.shift();
if (req === loaderPath) {
break;
}
}
request = request.join('!');
return 'module.exports = require(' + JSON.stringify('!!' + request) + ');';
}
}
================================================
FILE: package.json
================================================
{
"name": "styling",
"version": "0.4.1",
"description": "Style components with JavaScript",
"main": "index.js",
"browser": "browser.js",
"webpackLoader": "loader.js",
"author": "Andrey Popp <8mayday@gmail.com>",
"license": "MIT",
"repository": "andreypopp/styling",
"keywords": [
"webpack",
"webpack-loader",
"css-modules",
"css",
"style"
],
"dependencies": {}
}
================================================
FILE: renderStyling.js
================================================
/**
* @copyright 2013-2015, Facebook, Inc.
* @copyright 2015, Andrey Popp
*/
function renderStyling(key, styling) {
var stylesheet = [];
var spec = styling.getSpec();
if (typeof spec === 'string') {
stylesheet.push(spec);
} else {
var self = {};
for (var prop in spec) {
if (spec.hasOwnProperty(prop)) {
var value = spec[prop];
value = valueOf(value);
if (value && typeof value === 'object' && !Array.isArray(value)) {
stylesheet.push(renderStyle(key, prop, value));
} else {
self[prop] = value;
}
}
}
stylesheet.unshift(renderStyle(key, null, self));
}
return stylesheet;
}
function renderStyle(name, state, style) {
var css = '';
css += renderSelector(name, state) + ' {\n';
for (var prop in style) {
if (style.hasOwnProperty(prop)) {
var value = valueOf(style[prop]);
if (Array.isArray(value)) {
for (var i = 0; i < value.length; i++) {
css += ' ' + renderProp(prop, value[i]) + '\n';
}
} else {
css += ' ' + renderProp(prop, value) + '\n';
}
}
}
css += '}';
return css;
}
function renderProp(key, value) {
value = valueOf(value);
var isNonNumeric = isNaN(value);
if (isNonNumeric || value === 0 ||
IS_UNITLESS_NUMBER.hasOwnProperty(key) && IS_UNITLESS_NUMBER[key]) {
value = '' + value;
} else {
value = value + 'px';
}
key = key.replace(CAMEL_CASE_TO_DASH_CASE, '$1-$2').toLowerCase();
return key + ': ' + value + ';';
}
function renderSelector(name, state) {
return ':local(.' + name + (state ? ':' + state : '') + ')';
}
function valueOf(value) {
if (value != null) {
return value.valueOf();
} else {
return value;
}
}
var CAMEL_CASE_TO_DASH_CASE = /([a-z]|^)([A-Z])/g;
var IS_UNITLESS_NUMBER = {
boxFlex: true,
boxFlexGroup: true,
columnCount: true,
flex: true,
flexGrow: true,
flexPositive: true,
flexShrink: true,
flexNegative: true,
fontWeight: true,
lineClamp: true,
lineHeight: true,
opacity: true,
order: true,
orphans: true,
tabSize: true,
widows: true,
zIndex: true,
zoom: true,
// SVG-related properties
fillOpacity: true,
strokeDashoffset: true,
strokeOpacity: true,
strokeWidth: true,
};
module.exports = renderStyling;
================================================
FILE: renderStylingSheet.js
================================================
/**
* @copyright 2015, Andrey Popp
*/
var Styling = require('./Styling');
var renderStyling = require('./renderStyling');
function renderStylingSheet(sheet) {
var stylesheet = [];
for (var key in sheet) {
if (sheet.hasOwnProperty(key)) {
var styling = sheet[key];
if (Styling.is(styling)) {
stylesheet = stylesheet.concat(renderStyling(key, styling));
}
}
}
return stylesheet.join('\n\n');
}
module.exports = renderStylingSheet;