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;