├── .eslintignore
├── examples
├── react-todo-mvc
│ ├── .gitignore
│ ├── .babelrc
│ ├── scripts
│ │ └── get-css-string.js
│ ├── src
│ │ ├── NewTodo.css
│ │ ├── index.js
│ │ ├── TodoEdit.css
│ │ ├── Filters.css
│ │ ├── Footer.js
│ │ ├── NewTodo.js
│ │ ├── Footer.css
│ │ ├── index.css
│ │ ├── Filters.js
│ │ ├── TodoItem.js
│ │ ├── App.css
│ │ ├── TodoItem.css
│ │ └── App.js
│ ├── config
│ │ ├── webpack.config.base.js
│ │ ├── webpack.config.prod.js
│ │ └── webpack.config.dev.js
│ ├── public
│ │ └── index.html
│ └── package.json
├── webpack-react-dom
│ ├── .gitignore
│ ├── .babelrc
│ ├── src
│ │ ├── another.css
│ │ ├── index.js
│ │ ├── index.css
│ │ └── App.js
│ ├── public
│ │ └── index.html
│ ├── config
│ │ ├── webpack.config.base.js
│ │ ├── webpack.config.prod.js
│ │ └── webpack.config.dev.js
│ ├── dist
│ │ ├── bundle.css
│ │ └── bundle.js
│ └── package.json
└── webpack-vanilla-dom
│ ├── src
│ ├── another.css
│ ├── index.css
│ └── index.js
│ ├── public
│ ├── index.html
│ └── dist
│ │ ├── bundle.css
│ │ └── dist
│ │ └── bundle.css
│ ├── config
│ ├── webpack.config.base.js
│ ├── webpack.config.prod.js
│ └── webpack.config.dev.js
│ ├── dist
│ └── bundle.css
│ └── package.json
├── loader
├── index.js
├── bindings.js
├── postcss.config.js
├── deep-merge.js
├── readme.md
├── pitch-loader.js
├── normal-loader.js
└── index.old.js
├── .travis.yml
├── assets
├── dom.png
├── react.png
├── webpack.png
├── stylesheet.png
├── dom.svg
├── webpack.svg
├── react.svg
└── stylesheet.svg
├── .gitignore
├── .npmignore
├── react-dom
├── map-to-object.js
├── src
│ ├── utils.js
│ └── create-css-component.js
├── hot
│ └── index.js
├── dist
│ ├── utils.js.map
│ ├── utils.js
│ ├── create-css-component.js.map
│ └── create-css-component.js
├── index.js
├── readme.md
└── validAttributes.js
├── __tests__
├── dom
│ ├── on-dom-load.js
│ └── bind-attrs-to-cssom.js
├── react-dom
│ ├── map-to-object.js
│ ├── utils.js
│ └── index.js
├── vanilla-dom
│ ├── index.js
│ └── create-component.js
├── postcss
│ ├── attr-to-template.js
│ └── index.js
├── loader
│ └── deepMerge.js
└── core
│ ├── template.js
│ └── match-attribute.js
├── vanilla-dom
├── index.js
├── dist
│ ├── event.js
│ ├── create-component.js.map
│ └── create-component.js
├── readme.md
└── src
│ └── create-component.js
├── dom
├── src
│ ├── on-dom-load.js
│ ├── generate-class-name.js
│ └── bind-attrs-to-cssom.js
└── dist
│ ├── dom-load.js.map
│ ├── dom-load.js
│ ├── on-dom-load.js
│ ├── bind-attrs-to-cssom.js.map
│ ├── generate-class-name.js
│ └── bind-attrs-to-cssom.js
├── core
├── template.js
└── match-attribute.js
├── .eslintrc
├── postcss
├── postfix-attr-value.js
├── parse-attribute-nodes.js
├── attr-to-template.js
├── append-attr.js
└── index.js
├── .vscode
└── settings.json
├── package.json
└── readme.md
/.eslintignore:
--------------------------------------------------------------------------------
1 | */dist/*
2 |
--------------------------------------------------------------------------------
/examples/react-todo-mvc/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 |
--------------------------------------------------------------------------------
/loader/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./pitch-loader');
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js: node
3 | cache: yarn
4 |
--------------------------------------------------------------------------------
/examples/webpack-react-dom/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | */dist/
3 |
--------------------------------------------------------------------------------
/assets/dom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iddan/stylesheet/HEAD/assets/dom.png
--------------------------------------------------------------------------------
/assets/react.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iddan/stylesheet/HEAD/assets/react.png
--------------------------------------------------------------------------------
/assets/webpack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iddan/stylesheet/HEAD/assets/webpack.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/node_modules/
2 | coverage/
3 | _site/
4 | _assets/
5 | .sass-cache/
6 | search/
7 |
--------------------------------------------------------------------------------
/assets/stylesheet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iddan/stylesheet/HEAD/assets/stylesheet.png
--------------------------------------------------------------------------------
/examples/react-todo-mvc/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "stage-2",
4 | "react"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/examples/webpack-react-dom/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "stage-2",
4 | "react"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/loader/bindings.js:
--------------------------------------------------------------------------------
1 | exports['vanilla-dom'] = require('../vanilla-dom');
2 | exports['react-dom'] = require('../react-dom');
3 |
--------------------------------------------------------------------------------
/examples/webpack-vanilla-dom/src/another.css:
--------------------------------------------------------------------------------
1 | Label[name="The White Screen"] {
2 | background: white;
3 | color: red;
4 | }
5 |
--------------------------------------------------------------------------------
/loader/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = ({ options }) => ({
2 | plugins: {
3 | [require.resolve('../postcss')]: options
4 | }
5 | });
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | _site/
2 | _assets/
3 | .sass-cache
4 | search/
5 | assets/
6 | dom/src/
7 | examples/
8 | react-dom/src/
9 | vanilla-dom/src/
10 |
--------------------------------------------------------------------------------
/examples/webpack-react-dom/src/another.css:
--------------------------------------------------------------------------------
1 | Label[name="The White Screen"] {
2 | background: white;
3 | color: red;
4 | border: 1px solid gainsboro;
5 | }
6 |
--------------------------------------------------------------------------------
/react-dom/map-to-object.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash/fp');
2 |
3 | const mapToObject = _.curry((iteratee, array) => _.zipObject(array, _.map(iteratee, array)));
4 |
5 | module.exports = mapToObject;
6 |
--------------------------------------------------------------------------------
/examples/react-todo-mvc/scripts/get-css-string.js:
--------------------------------------------------------------------------------
1 | // paste in the console
2 | [...document.styleSheets]
3 | .map(stylesheet => [...stylesheet.cssRules].map(cssRule => cssRule.cssText).join('\n'))
4 | .join('\n');
5 |
--------------------------------------------------------------------------------
/examples/react-todo-mvc/src/NewTodo.css:
--------------------------------------------------------------------------------
1 | @import './TodoEdit.css';
2 |
3 | NewTodo {
4 | padding: 16px 16px 16px 60px;
5 | border: none;
6 | background: rgba(0, 0, 0, 0.003);
7 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
8 | }
9 |
--------------------------------------------------------------------------------
/examples/webpack-vanilla-dom/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/__tests__/dom/on-dom-load.js:
--------------------------------------------------------------------------------
1 | import onDOMLoad from '../../dom/dist/on-dom-load';
2 |
3 | test('Callback is triggered only after document is loaded', () => {
4 | onDOMLoad(() => {
5 | expect(document.readyState).toBe('complete');
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/react-dom/src/utils.js:
--------------------------------------------------------------------------------
1 | export const omitBy = (object, filter) => {
2 | const newObj = {};
3 | for (const key in object) {
4 | const value = object[key];
5 | if (!filter(value, key)) {
6 | newObj[key] = value;
7 | }
8 | }
9 | return newObj;
10 | };
11 |
--------------------------------------------------------------------------------
/vanilla-dom/index.js:
--------------------------------------------------------------------------------
1 | exports.createComponentPath = require.resolve('./dist/create-component');
2 |
3 | exports.preprocess = ({ selector, className, attributes = [], attrs = [], base }) => ({
4 | selector,
5 | className,
6 | attributes,
7 | attrs,
8 | base,
9 | });
10 |
--------------------------------------------------------------------------------
/__tests__/react-dom/map-to-object.js:
--------------------------------------------------------------------------------
1 | import mapToObject from '../../react-dom/map-to-object';
2 |
3 | test('Maps array items to object', () => {
4 | expect(mapToObject(item => item.toLowerCase() + '-is-cool', ['Dan', 'Ron'])).toEqual({
5 | Dan: 'dan-is-cool',
6 | Ron: 'ron-is-cool',
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/examples/webpack-vanilla-dom/config/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | module: {
3 | rules: [],
4 | },
5 | externals: {
6 | 'react': 'React',
7 | 'react-dom': 'ReactDOM',
8 | },
9 | entry: './src/index.js',
10 | output: {
11 | filename: 'dist/bundle.js',
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/examples/webpack-react-dom/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/dom/src/on-dom-load.js:
--------------------------------------------------------------------------------
1 | export default function onDOMLoad(callback) {
2 | if (document.readyState === 'complete') {
3 | callback();
4 | } else {
5 | const handleDOMLoad = () => {
6 | removeEventListener('load', handleDOMLoad);
7 | callback();
8 | };
9 | addEventListener('load', handleDOMLoad);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/webpack-react-dom/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import Stylesheet from 'stylesheet/react-dom/hot';
5 |
6 | if (module.hot) {
7 | module.hot.accept('./App', Stylesheet.handleAccept);
8 | }
9 |
10 | ReactDOM.render(React.createElement(App), document.querySelector('#root'));
11 |
--------------------------------------------------------------------------------
/core/template.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @typedef {string} Template
3 | */
4 |
5 | /**
6 | * @param {Template} template
7 | * @param {Object} values
8 | */
9 | export const format = (template, values) =>
10 | template.replace(
11 | /\{\s*(.+?)(?:\s*=\s*"(.+?)")?\s*\}/g,
12 | (match, name, defaultValue) => values[name] || defaultValue || ''
13 | );
14 |
--------------------------------------------------------------------------------
/examples/react-todo-mvc/config/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | module: {
3 | rules: [
4 | {
5 | test: /\.js/,
6 | use: 'babel-loader',
7 | },
8 | ],
9 | },
10 | externals: {
11 | 'react': 'React',
12 | 'react-dom': 'ReactDOM',
13 | },
14 | entry: './src/index.js',
15 | output: {
16 | filename: 'dist/bundle.js',
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/examples/webpack-react-dom/config/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | module: {
3 | rules: [
4 | {
5 | test: /\.js/,
6 | use: 'babel-loader',
7 | },
8 | ],
9 | },
10 | externals: {
11 | 'react': 'React',
12 | 'react-dom': 'ReactDOM',
13 | },
14 | entry: './src/index.js',
15 | output: {
16 | filename: 'dist/bundle.js',
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/examples/react-todo-mvc/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import './index.css';
5 |
6 | const initialTodos = JSON.parse(localStorage.getItem('todos'));
7 |
8 | const storeTodos = todos => localStorage.setItem('todos', JSON.stringify(todos));
9 |
10 | ReactDOM.render(
11 | ,
12 | document.querySelector('#root')
13 | );
14 |
--------------------------------------------------------------------------------
/__tests__/react-dom/utils.js:
--------------------------------------------------------------------------------
1 | import { omitBy } from '../../react-dom/dist/utils';
2 |
3 | test('Omits every name starts with D', () => {
4 | expect(
5 | omitBy(
6 | {
7 | frontEndEngineer: 'Dan',
8 | architect: 'Doron',
9 | CEO: 'Ron',
10 | CTO: 'Lior',
11 | backEndEngineer: 'Tupac',
12 | },
13 | value => value.startsWith('D')
14 | )
15 | ).toEqual({ CEO: 'Ron', CTO: 'Lior', backEndEngineer: 'Tupac' });
16 | });
17 |
--------------------------------------------------------------------------------
/dom/src/generate-class-name.js:
--------------------------------------------------------------------------------
1 | import matchAttribute from '../../core/match-attribute';
2 |
3 | const getClassName = ({ className }) => className;
4 |
5 | const generateClassName = ({ className, attributes = [], attrs = [] }) => props =>
6 | [
7 | className,
8 | ...attrs.map(getClassName),
9 | ...attributes
10 | .filter(attribute => matchAttribute(attribute, props[attribute.name]))
11 | .map(getClassName),
12 | ].join(' ');
13 |
14 | export default generateClassName;
15 |
--------------------------------------------------------------------------------
/react-dom/hot/index.js:
--------------------------------------------------------------------------------
1 | const Stylesheet = {
2 | instances: [],
3 | register(instance) {
4 | this.instances.push(instance);
5 | },
6 | unregister(instance) {
7 | this.instances.splice(this.instances.indexOf(instance), 1);
8 | },
9 | handleAccept() {
10 | setTimeout(() => {
11 | for (const instance of this.instances) {
12 | instance.init();
13 | instance.forceUpdate();
14 | }
15 | }, 1);
16 | },
17 | };
18 |
19 | export default Stylesheet;
20 |
--------------------------------------------------------------------------------
/vanilla-dom/dist/event.js:
--------------------------------------------------------------------------------
1 | Object.defineProperty(exports, '__esModule', {
2 | value: true
3 | });
4 | exports.default = StylesheetEvent;
5 | function StylesheetEvent(type, props) {
6 | this.props = props;
7 | }
8 |
9 | StylesheetEvent.prototype = Object.create(Event.prototype);
10 | // constructor(type, props) {
11 | // const event = new Event(type, {
12 | // bubbles: true,
13 | // cancelable: false,
14 | // scoped: false,
15 | // });
16 | // super(type, );
17 | // this.props = props;
18 | // }
19 | // }
20 |
--------------------------------------------------------------------------------
/examples/react-todo-mvc/src/TodoEdit.css:
--------------------------------------------------------------------------------
1 | NewTodo,
2 | TodoEdit {
3 | @apply input;
4 | position: relative;
5 | margin: 0;
6 | width: 100%;
7 | font-size: 24px;
8 | font-family: inherit;
9 | font-weight: inherit;
10 | line-height: 1.4em;
11 | border: 0;
12 | outline: none;
13 | color: inherit;
14 | padding: 6px;
15 | border: 1px solid #999;
16 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
17 | box-sizing: border-box;
18 | -webkit-font-smoothing: antialiased;
19 | -moz-font-smoothing: antialiased;
20 | font-smoothing: antialiased;
21 | }
22 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "fbjs-opensource",
4 | "plugin:lodash-fp/recommended",
5 | "plugin:promise/recommended"
6 | ],
7 | "plugins": [
8 | "lodash-fp",
9 | "promise"
10 | ],
11 | "rules": {
12 | "object-curly-spacing": ["warn", "always", {
13 | "objectsInObjects": false
14 | }],
15 | "comma-dangle": ["off"],
16 | "template-curly-spacing": ["warn", "always"],
17 | "no-unused-vars": ["warn", {
18 | "vars": "all",
19 | "args": "after-used",
20 | "ignoreRestSiblings": false
21 | }]
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/react-todo-mvc/public/index.html:
--------------------------------------------------------------------------------
1 |
2 | Stylesheet Todo MVC
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/examples/webpack-vanilla-dom/dist/bundle.css:
--------------------------------------------------------------------------------
1 | Label[name="The White Screen"] {
2 | background: white;
3 | color: red;
4 | }
5 | @media screen and (max-width: 250px) {}
6 |
7 | body {
8 | margin: 0;
9 | }
10 |
11 | .Label_Bk4BUSPrb {
12 | font-family: monospace;
13 | font-size: 14px;
14 | user-select: none;
15 | color: white;
16 | background: blue;
17 | padding: 1rem;
18 | margin: 1rem;
19 | }
20 |
21 | body .Label_Bk4BUSPrb.Label-highlighted__Bk4BUSPrb {
22 | color: white;
23 | }
24 |
25 | body .Label_Bk4BUSPrb.Label-name_TjNxB_Bk4BUSPrb {
26 | color: red;
27 | }
28 |
--------------------------------------------------------------------------------
/examples/webpack-vanilla-dom/public/dist/bundle.css:
--------------------------------------------------------------------------------
1 | Label[name="The White Screen"] {
2 | background: white;
3 | color: red;
4 | }
5 | @media screen and (max-width: 250px) {}
6 |
7 | body {
8 | margin: 0;
9 | }
10 |
11 | .Label_HkhZLBvS- {
12 | font-family: monospace;
13 | font-size: 14px;
14 | user-select: none;
15 | color: white;
16 | background: blue;
17 | padding: 1rem;
18 | margin: 1rem;
19 | }
20 |
21 | body .Label_HkhZLBvS-.Label-highlighted__HkhZLBvS- {
22 | color: white;
23 | }
24 |
25 | body .Label_HkhZLBvS-.Label-name_TjNxB_HkhZLBvS- {
26 | color: red;
27 | }
28 |
--------------------------------------------------------------------------------
/examples/webpack-vanilla-dom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stylesheet-example-webpack-vanilla-dom",
3 | "version": "0.6.0",
4 | "main": "index.js",
5 | "scripts": {
6 | "start": "webpack-dev-server --config config/webpack.config.dev.js",
7 | "build": "webpack --config config/webpack.config.prod.js && cp -r dist public/dist"
8 | },
9 | "license": "MIT",
10 | "devDependencies": {
11 | "extract-text-webpack-plugin": "^3.0.0",
12 | "style-loader": "^0.18.1",
13 | "stylesheet": "^0.9.1",
14 | "webpack": "^3.2.0",
15 | "webpack-dev-server": "^3.1.11"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/examples/webpack-vanilla-dom/public/dist/dist/bundle.css:
--------------------------------------------------------------------------------
1 | Label[name="The White Screen"] {
2 | background: white;
3 | color: red;
4 | }
5 | @media screen and (max-width: 250px) {}
6 |
7 | body {
8 | margin: 0;
9 | }
10 |
11 | .Label_Bk4BUSPrb {
12 | font-family: monospace;
13 | font-size: 14px;
14 | user-select: none;
15 | color: white;
16 | background: blue;
17 | padding: 1rem;
18 | margin: 1rem;
19 | }
20 |
21 | body .Label_Bk4BUSPrb.Label-highlighted__Bk4BUSPrb {
22 | color: white;
23 | }
24 |
25 | body .Label_Bk4BUSPrb.Label-name_TjNxB_Bk4BUSPrb {
26 | color: red;
27 | }
28 |
--------------------------------------------------------------------------------
/examples/webpack-vanilla-dom/src/index.css:
--------------------------------------------------------------------------------
1 | @import './another.css';
2 | @media screen and (max-width: 250px) {}
3 |
4 | body {
5 | margin: 0;
6 | }
7 |
8 | Label {
9 | @apply span;
10 | font-family: monospace;
11 | font-size: 14px;
12 | user-select: none;
13 | color: white;
14 | background: blue;
15 | padding: 1rem;
16 | margin: 1rem;
17 | }
18 |
19 | body Label[highlighted] {
20 | background: linear-gradient(to top, yellow, attr(color color, tomato));
21 | font-size: calc(attr(fontSize px) + 12px);
22 | color: white;
23 | }
24 |
25 | body Label[name="Ryskin"] {
26 | color: red;
27 | }
28 |
--------------------------------------------------------------------------------
/postcss/postfix-attr-value.js:
--------------------------------------------------------------------------------
1 | module.exports = function postfixAttrValue(value, type) {
2 | switch (type) {
3 | case 'em':
4 | case 'ex':
5 | case 'px':
6 | case 'rem':
7 | case 'vw':
8 | case 'vh':
9 | case 'vmin':
10 | case 'vmax':
11 | case 'mm':
12 | case 'cm':
13 | case 'in':
14 | case 'pt':
15 | case 'pc':
16 | case 'deg':
17 | case 'grad':
18 | case 'rad':
19 | case 's':
20 | case 'ms':
21 | case 'Hz':
22 | case 'kHz':
23 | case '%':
24 | return value + type;
25 | default:
26 | return value;
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/examples/react-todo-mvc/src/Filters.css:
--------------------------------------------------------------------------------
1 | Filters {
2 | @apply ul;
3 | margin: 0;
4 | padding: 0;
5 | list-style: none;
6 | position: absolute;
7 | right: 0;
8 | left: 0;
9 | }
10 |
11 | Filters li {
12 | display: inline;
13 | }
14 |
15 | Filters li Link {
16 | @apply a;
17 | color: inherit;
18 | margin: 3px;
19 | padding: 3px 7px;
20 | text-decoration: none;
21 | border: 1px solid transparent;
22 | border-radius: 3px;
23 | }
24 |
25 | Filters li Link[selected],
26 | Filters li Link:hover {
27 | border-color: rgba(175, 47, 47, 0.1);
28 | }
29 |
30 | Filters li Link[selected] {
31 | border-color: rgba(175, 47, 47, 0.2);
32 | }
33 |
--------------------------------------------------------------------------------
/react-dom/dist/utils.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["../src/utils.js"],"names":["omitBy","object","filter","newObj","key","value"],"mappings":";;;;;AAAO,IAAMA,0BAAS,SAATA,MAAS,CAACC,MAAD,EAASC,MAAT,EAAoB;AACxC,MAAMC,SAAS,EAAf;AACA,OAAK,IAAMC,GAAX,IAAkBH,MAAlB,EAA0B;AACxB,QAAMI,QAAQJ,OAAOG,GAAP,CAAd;AACA,QAAI,CAACF,OAAOG,KAAP,EAAcD,GAAd,CAAL,EAAyB;AACvBD,aAAOC,GAAP,IAAcC,KAAd;AACD;AACF;AACD,SAAOF,MAAP;AACD,CATM","file":"utils.js","sourcesContent":["export const omitBy = (object, filter) => {\n const newObj = {};\n for (const key in object) {\n const value = object[key];\n if (!filter(value, key)) {\n newObj[key] = value; \n }\n }\n return newObj;\n};"]}
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "prettier.bracketSpacing": true,
3 | "prettier.jsxBracketSameLine": true,
4 | "prettier.printWidth": 100,
5 | "prettier.semi": true,
6 | "prettier.singleQuote": true,
7 | "prettier.tabWidth": 2,
8 | "prettier.trailingComma": "es5",
9 | "prettier.useTabs": false,
10 | "prettier.eslintIntegration": true,
11 | "editor.formatOnSave": true,
12 | "cSpell.enabled": true,
13 | "files.exclude": {
14 | "**/node_modules/": "explorerExcludedFiles",
15 | "coverage/": "explorerExcludedFiles",
16 | "_site/": "explorerExcludedFiles",
17 | "_assets/": "explorerExcludedFiles",
18 | ".sass-cache/": "explorerExcludedFiles",
19 | "search/": "explorerExcludedFiles"
20 | }
21 | }
--------------------------------------------------------------------------------
/examples/webpack-react-dom/src/index.css:
--------------------------------------------------------------------------------
1 | @import './another.css';
2 | @media screen and (max-width: 250px) {}
3 |
4 | body {
5 | margin: 0;
6 | }
7 |
8 | Label {
9 | @apply span;
10 | font-family: monospace;
11 | font-size: 14px;
12 | user-select: none;
13 | color: blue;
14 | background: navajowhite;
15 | padding: 1rem;
16 | margin: 1rem;
17 | }
18 |
19 | body Label[highlighted] {
20 | background: linear-gradient(to top, yellow, attr(color color, tomato));
21 | font-size: calc(attr(fontSize px) + 12px);
22 | color: white;
23 | }
24 |
25 | body Label[name="Ryskin"] {
26 | color: red;
27 | }
28 |
29 | body Label:not([name="Ryskin"]):not([name="The White Screen"]) {
30 | color: blueviolet;
31 | }
32 |
--------------------------------------------------------------------------------
/loader/deep-merge.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prefer-object-spread/prefer-object-spread */
2 | /**
3 | * @param {Object[]} objects array of plain objects
4 | */
5 | const deepMerge = (...objects) =>
6 | objects.reduce((acc, object) => {
7 | if (!acc || typeof object !== 'object') {
8 | return object;
9 | }
10 | if (!Array.isArray(acc) && Array.isArray(object)) {
11 | return object;
12 | }
13 | if (Array.isArray(acc) && Array.isArray(object)) {
14 | return [...acc, ...object];
15 | }
16 | return Object.keys(object).reduce(
17 | (acc2, key) => Object.assign({}, acc2, { [key]: deepMerge(acc2[key], object[key]) }),
18 | acc || {}
19 | );
20 | }, {});
21 |
22 | module.exports = deepMerge;
23 |
--------------------------------------------------------------------------------
/examples/react-todo-mvc/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stylesheet-react-todo-mvc",
3 | "scripts": {
4 | "start": "webpack-dev-server --config config/webpack.config.dev.js",
5 | "build": "webpack --config config/webpack.config.prod.js && cp -r dist public/dist"
6 | },
7 | "devDependencies": {
8 | "babel-core": "^6.24.1",
9 | "babel-loader": "^7.0.0",
10 | "babel-preset-react": "^6.24.1",
11 | "babel-preset-stage-2": "^6.24.1",
12 | "extract-text-webpack-plugin": "^3.0.0",
13 | "style-loader": "^0.18.1",
14 | "stylesheet": "^0.9.1",
15 | "webpack": "^3.2.0",
16 | "webpack-dev-server": "^2.4.5"
17 | },
18 | "dependencies": {
19 | "react": "^15.4.2",
20 | "react-dom": "^15.4.2"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/webpack-react-dom/dist/bundle.css:
--------------------------------------------------------------------------------
1 | Label[name="The White Screen"] {
2 | background: white;
3 | color: red;
4 | border: 1px solid gainsboro;
5 | }
6 | @media screen and (max-width: 250px) {}
7 |
8 | body {
9 | margin: 0;
10 | }
11 |
12 | .Label_ryd7NBwBZ {
13 | font-family: monospace;
14 | font-size: 14px;
15 | user-select: none;
16 | color: blue;
17 | background: navajowhite;
18 | padding: 1rem;
19 | margin: 1rem;
20 | }
21 |
22 | body .Label_ryd7NBwBZ.Label-highlighted__ryd7NBwBZ {
23 | color: white;
24 | }
25 |
26 | body .Label_ryd7NBwBZ.Label-name_TjNxB_ryd7NBwBZ {
27 | color: red;
28 | }
29 |
30 | body .Label_ryd7NBwBZ:not([name="Ryskin"]):not([name="The White Screen"]) {
31 | color: blueviolet;
32 | }
33 |
--------------------------------------------------------------------------------
/dom/dist/dom-load.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["../src/dom-load.js"],"names":["DOMLoad","Promise","document","readyState","resolve","onDOMLoad","removeEventListener","addEventListener"],"mappings":";;;;;AAAA,IAAMA,UAAU,IAAIC,OAAJ,CAAY,mBAAW;AACrC,MAAIC,SAASC,UAAT,KAAwB,UAA5B,EAAwC;AACtCC;AACD,GAFD,MAEO;AACL,QAAMC,YAAY,SAAZA,SAAY,GAAM;AACtBC,0BAAoB,MAApB,EAA4BD,SAA5B;AACAD;AACD,KAHD;AAIAG,qBAAiB,MAAjB,EAAyBF,SAAzB;AACD;AACF,CAVe,CAAhB;;kBAYeL,O","file":"dom-load.js","sourcesContent":["const DOMLoad = new Promise(resolve => {\n if (document.readyState === 'complete') {\n resolve();\n } else {\n const onDOMLoad = () => {\n removeEventListener('load', onDOMLoad);\n resolve();\n };\n addEventListener('load', onDOMLoad);\n }\n});\n\nexport default DOMLoad;\n"]}
--------------------------------------------------------------------------------
/postcss/parse-attribute-nodes.js:
--------------------------------------------------------------------------------
1 | const parser = require('postcss-selector-parser');
2 | const { unique } = require('shorthash');
3 |
4 | const parseAttributeNodes = (id, componentName, nodes) =>
5 | nodes.map(node => {
6 | const { operator, attribute, raws: { unquoted, insensitive }} = node;
7 | const attributeId = operator ? unique(operator + unquoted) : '';
8 | const attributeClassName = `${ componentName }-${ attribute }_${ attributeId }_${ id }`;
9 | node.replaceWith(parser.className({ value: attributeClassName }));
10 | return {
11 | operator,
12 | name: attribute,
13 | value: unquoted,
14 | insensitive,
15 | className: attributeClassName,
16 | };
17 | });
18 |
19 | module.exports = parseAttributeNodes;
20 |
--------------------------------------------------------------------------------
/__tests__/vanilla-dom/index.js:
--------------------------------------------------------------------------------
1 | import { createComponentPath, preprocess } from '../../vanilla-dom';
2 |
3 | test('createComponentPath navigates to a component constructor', () => {
4 | const createComponent = require(createComponentPath);
5 | const component = createComponent(
6 | preprocess({
7 | selector: '.A',
8 | className: '.A',
9 | base: 'li',
10 | })
11 | );
12 | expect(component).toBeInstanceOf(Function);
13 | });
14 |
15 | test('Preprocess normalizes types', () => {
16 | expect(
17 | preprocess({
18 | selector: '.A',
19 | className: '.A',
20 | base: 'li',
21 | })
22 | ).toEqual({
23 | selector: '.A',
24 | className: '.A',
25 | attributes: [],
26 | attrs: [],
27 | base: 'li',
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/examples/webpack-react-dom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stylesheet-webpack-react-dom-example",
3 | "scripts": {
4 | "start": "webpack-dev-server --config config/webpack.config.dev.js",
5 | "build": "webpack --config config/webpack.config.prod.js && cp -r dist public/dist"
6 | },
7 | "devDependencies": {
8 | "babel-core": "^6.25.0",
9 | "babel-loader": "^7.1.1",
10 | "babel-preset-react": "^6.24.1",
11 | "babel-preset-stage-2": "^6.24.1",
12 | "extract-text-webpack-plugin": "^3.0.0",
13 | "style-loader": "^0.18.2",
14 | "stylesheet": "^0.9.1",
15 | "webpack": "^3.2.0",
16 | "webpack-dev-server": "^3.1.11"
17 | },
18 | "dependencies": {
19 | "react": "^15.6.1",
20 | "react-dom": "^15.6.1"
21 | },
22 | "version": "0.1.0"
23 | }
24 |
--------------------------------------------------------------------------------
/__tests__/postcss/attr-to-template.js:
--------------------------------------------------------------------------------
1 | import attrToTemplate from '../../postcss/attr-to-template';
2 |
3 | test('Transforms basic attr() declaration', () => {
4 | expect(attrToTemplate('attr(color color)')).toEqual(
5 | expect.objectContaining({
6 | template: '{ color }',
7 | attributes: ['color'],
8 | })
9 | );
10 | });
11 |
12 | test('Transforms complex attr() declaration', () => {
13 | expect(attrToTemplate('1px solid attr(color color)')).toEqual(
14 | expect.objectContaining({
15 | template: '1px solid { color }',
16 | attributes: ['color'],
17 | })
18 | );
19 | });
20 |
21 | test('Transforms attr() with unit', () => {
22 | expect(attrToTemplate('attr(height %)')).toEqual(
23 | expect.objectContaining({
24 | template: '{ height }%',
25 | attributes: ['height'],
26 | })
27 | );
28 | });
29 |
--------------------------------------------------------------------------------
/examples/react-todo-mvc/config/webpack.config.prod.js:
--------------------------------------------------------------------------------
1 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
2 | const config = require('./webpack.config.base');
3 |
4 | const { module: { rules = [] }, plugins = [] } = config;
5 |
6 | module.exports = Object.assign(config, {
7 | module: Object.assign(config.module, {
8 | rules: [
9 | ...rules,
10 | {
11 | test: /\.css$/,
12 | use: [
13 | {
14 | loader: 'stylesheet/loader',
15 | query: {
16 | bindings: 'react-dom',
17 | },
18 | },
19 | ...ExtractTextPlugin.extract({
20 | fallback: 'style-loader',
21 | use: ['css-loader'],
22 | }),
23 | ],
24 | },
25 | ],
26 | }),
27 | plugins: [...plugins, new ExtractTextPlugin('dist/bundle.css')],
28 | });
29 |
--------------------------------------------------------------------------------
/examples/webpack-react-dom/config/webpack.config.prod.js:
--------------------------------------------------------------------------------
1 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
2 | const config = require('./webpack.config.base');
3 |
4 | const { module: { rules = [] }, plugins = [] } = config;
5 |
6 | module.exports = Object.assign(config, {
7 | module: Object.assign(config.module, {
8 | rules: [
9 | ...rules,
10 | {
11 | test: /\.css$/,
12 | use: [
13 | {
14 | loader: 'stylesheet/loader',
15 | query: {
16 | bindings: 'react-dom',
17 | },
18 | },
19 | ...ExtractTextPlugin.extract({
20 | fallback: 'style-loader',
21 | use: ['css-loader'],
22 | }),
23 | ],
24 | },
25 | ],
26 | }),
27 | plugins: [...plugins, new ExtractTextPlugin('dist/bundle.css')],
28 | });
29 |
--------------------------------------------------------------------------------
/__tests__/loader/deepMerge.js:
--------------------------------------------------------------------------------
1 | import deepMerge from '../../loader/deep-merge';
2 |
3 | test('Shallowly merges two objects', () => {
4 | expect(deepMerge({ numberOfStudentsInClassroom: 1 }, { numberOfStudents: 2 })).toEqual({
5 | numberOfStudentsInClassroom: 1,
6 | numberOfStudents: 2,
7 | });
8 | });
9 |
10 | test('Shallowly merges two array', () => {
11 | expect(deepMerge(['Dan'], ['Ron'])).toEqual(['Dan', 'Ron']);
12 | });
13 |
14 | test('Deeply merges two objects', () => {
15 | expect(
16 | deepMerge({ numberOfStudents: { inClassroom: 1 }}, { numberOfStudents: { inGeneral: 2 }})
17 | ).toEqual({ numberOfStudents: { inClassroom: 1, inGeneral: 2 }});
18 | });
19 |
20 | test('Merges an array within an object', () => {
21 | expect(deepMerge({ students: ['Dan'] }, { students: ['Ron'] })).toEqual({
22 | students: ['Dan', 'Ron'],
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/examples/webpack-vanilla-dom/config/webpack.config.prod.js:
--------------------------------------------------------------------------------
1 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
2 | const config = require('./webpack.config.base');
3 |
4 | const { module: { rules = [] }, plugins = [] } = config;
5 |
6 | module.exports = Object.assign(config, {
7 | module: Object.assign(config.module, {
8 | rules: [
9 | ...rules,
10 | {
11 | test: /\.css$/,
12 | use: [
13 | {
14 | loader: 'stylesheet/loader',
15 | query: {
16 | bindings: 'vanilla-dom',
17 | },
18 | },
19 | ...ExtractTextPlugin.extract({
20 | fallback: 'style-loader',
21 | use: ['css-loader'],
22 | }),
23 | ],
24 | },
25 | ],
26 | }),
27 | plugins: [...plugins, new ExtractTextPlugin('dist/bundle.css')],
28 | });
29 |
--------------------------------------------------------------------------------
/examples/react-todo-mvc/src/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import Filters from './Filters';
4 | import { Footer as StyledFooter, TodoCount, ClearCompleted } from './Footer.css';
5 |
6 | const Footer = ({ onFilterSelect, onClearCompleted, filter, complete, incomplete }) => (
7 |
8 |
9 | {incomplete} item{incomplete !== 1 && 's'} left
10 |
11 |
12 | {Boolean(complete) && }
13 |
14 | );
15 |
16 | Footer.propTypes = {
17 | onFilterSelect: PropTypes.func.isRequired,
18 | filter: PropTypes.string.isRequired,
19 | complete: PropTypes.number.isRequired,
20 | incomplete: PropTypes.number.isRequired,
21 | onClearCompleted: PropTypes.func.isRequired,
22 | };
23 |
24 | export default Footer;
25 |
--------------------------------------------------------------------------------
/postcss/attr-to-template.js:
--------------------------------------------------------------------------------
1 | const valueParser = require('postcss-value-parser');
2 | const _ = require('lodash/fp');
3 | const postfixAttrValue = require('./postfix-attr-value');
4 |
5 | const isAttrFunction = _.matches({ type: 'function', value: 'attr' });
6 |
7 | const extractWordValues = _.flow([_.filter({ type: 'word' }), _.map('value')]);
8 |
9 | const attrToTemplate = value => {
10 | const attributes = [];
11 | const template = valueParser(value)
12 | .walk(node => {
13 | if (isAttrFunction(node)) {
14 | const [name, type, defaultValue] = extractWordValues(node.nodes);
15 | const attrValue = `{ ${ name } ${ defaultValue ? `= "${ defaultValue }"` : '' }}`;
16 | node.type = 'word';
17 | node.value = postfixAttrValue(attrValue, type);
18 | attributes.push(name);
19 | }
20 | })
21 | .toString();
22 | return { template, attributes };
23 | };
24 |
25 | module.exports = attrToTemplate;
26 |
--------------------------------------------------------------------------------
/assets/dom.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
--------------------------------------------------------------------------------
/examples/react-todo-mvc/src/NewTodo.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { NewTodo as StyledNewTodo } from './NewTodo.css';
4 |
5 | class NewTodo extends PureComponent {
6 | static propTypes = {
7 | add: PropTypes.func.isRequired,
8 | };
9 |
10 | state = {
11 | title: '',
12 | };
13 |
14 | handleKeyDown = e => {
15 | switch (e.keyCode) {
16 | case 13: {
17 | const { title } = this.state;
18 | const trimmedTitle = title.trim();
19 | if (trimmedTitle) {
20 | this.setState({ title: '' });
21 | this.props.add({ title: trimmedTitle });
22 | }
23 | }
24 | }
25 | };
26 |
27 | handleChange = e => {
28 | this.setState({ title: e.target.value });
29 | };
30 |
31 | render() {
32 | const { title } = this.state;
33 | return (
34 |
40 | );
41 | }
42 | }
43 |
44 | export default NewTodo;
45 |
--------------------------------------------------------------------------------
/examples/react-todo-mvc/config/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const config = require('./webpack.config.base');
4 |
5 | const { module: { rules = [] }, plugins = [] } = config;
6 |
7 | module.exports = Object.assign(config, {
8 | module: Object.assign(config.module, {
9 | rules: [
10 | ...rules,
11 | {
12 | test: /\.css/,
13 | use: [
14 | {
15 | loader: 'stylesheet/loader',
16 | query: {
17 | sourceMap: true,
18 | bindings: 'react-dom',
19 | },
20 | },
21 | 'style-loader',
22 | 'css-loader',
23 | ],
24 | },
25 | ],
26 | }),
27 | devtool: 'sourcemaps',
28 | devServer: {
29 | contentBase: path.resolve(__dirname, '../public'),
30 | port: 8080,
31 | hotOnly: true,
32 | historyApiFallback: true,
33 | inline: true,
34 | },
35 | plugins: [
36 | ...plugins,
37 | new webpack.HotModuleReplacementPlugin(),
38 | new webpack.NamedModulesPlugin(),
39 | new webpack.NoEmitOnErrorsPlugin(),
40 | ],
41 | });
42 |
--------------------------------------------------------------------------------
/examples/webpack-react-dom/config/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const config = require('./webpack.config.base');
4 |
5 | const { module: { rules = [] }, plugins = [] } = config;
6 |
7 | module.exports = Object.assign(config, {
8 | module: Object.assign(config.module, {
9 | rules: [
10 | ...rules,
11 | {
12 | test: /\.css/,
13 | use: [
14 | {
15 | loader: 'stylesheet/loader',
16 | query: {
17 | sourceMap: true,
18 | bindings: 'react-dom',
19 | },
20 | },
21 | 'style-loader',
22 | 'css-loader',
23 | ],
24 | },
25 | ],
26 | }),
27 | devtool: 'sourcemaps',
28 | devServer: {
29 | contentBase: path.resolve(__dirname, '../public'),
30 | port: 8080,
31 | hotOnly: true,
32 | historyApiFallback: true,
33 | inline: true,
34 | },
35 | plugins: [
36 | ...plugins,
37 | new webpack.HotModuleReplacementPlugin(),
38 | new webpack.NamedModulesPlugin(),
39 | new webpack.NoEmitOnErrorsPlugin(),
40 | ],
41 | });
42 |
--------------------------------------------------------------------------------
/examples/webpack-vanilla-dom/src/index.js:
--------------------------------------------------------------------------------
1 | import { Label } from './index.css';
2 |
3 | const randomInt = max => (Math.random() * max).toFixed(0);
4 |
5 | const randomColor = () => `rgb(${ [randomInt(255), randomInt(255), randomInt(255)].join() })`;
6 |
7 | const App = () => {
8 | const container = document.createElement('div');
9 | container.setAttribute('role', 'container');
10 |
11 | const ryskin = Label.create({
12 | fontSize: Math.random() * 100,
13 | highlighted: true,
14 | name: 'Ryskin',
15 | color: randomColor(),
16 | });
17 |
18 | ryskin.appendChild(document.createTextNode('Ryskinder, please click me!'));
19 |
20 | ryskin.addEventListener('click', () => {
21 | ryskin.fontSize = Math.random() * 100;
22 | ryskin.color = randomColor();
23 | });
24 |
25 | const theWhiteScreen = Label.create({
26 | name: 'The White Screen',
27 | });
28 |
29 | theWhiteScreen.appendChild(document.createTextNode('The White Screen'));
30 |
31 | container.appendChild(ryskin);
32 | container.appendChild(theWhiteScreen);
33 |
34 | return container;
35 | };
36 |
37 | document.querySelector('#root').appendChild(App());
38 |
--------------------------------------------------------------------------------
/examples/webpack-vanilla-dom/config/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const config = require('./webpack.config.base');
4 |
5 | const { module: { rules = [] }, plugins = [] } = config;
6 |
7 | module.exports = Object.assign(config, {
8 | module: Object.assign(config.module, {
9 | rules: [
10 | ...rules,
11 | {
12 | test: /\.css/,
13 | use: [
14 | {
15 | loader: 'stylesheet/loader',
16 | query: {
17 | sourceMap: true,
18 | bindings: 'vanilla-dom',
19 | },
20 | },
21 | 'style-loader',
22 | 'css-loader',
23 | ],
24 | },
25 | ],
26 | }),
27 | devtool: 'sourcemaps',
28 | devServer: {
29 | contentBase: path.resolve(__dirname, '../public'),
30 | port: 8080,
31 | hotOnly: true,
32 | historyApiFallback: true,
33 | inline: true,
34 | },
35 | plugins: [
36 | ...plugins,
37 | new webpack.HotModuleReplacementPlugin(),
38 | new webpack.NamedModulesPlugin(),
39 | new webpack.NoEmitOnErrorsPlugin(),
40 | ],
41 | });
42 |
--------------------------------------------------------------------------------
/examples/react-todo-mvc/src/Footer.css:
--------------------------------------------------------------------------------
1 |
2 | Footer {
3 | @apply footer;
4 | color: #777;
5 | padding: 10px 15px;
6 | height: 20px;
7 | text-align: center;
8 | border-top: 1px solid #e6e6e6;
9 | }
10 |
11 | Footer::before {
12 | content: '';
13 | position: absolute;
14 | right: 0;
15 | bottom: 0;
16 | left: 0;
17 | height: 50px;
18 | overflow: hidden;
19 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
20 | 0 8px 0 -3px #f6f6f6,
21 | 0 9px 1px -3px rgba(0, 0, 0, 0.2),
22 | 0 16px 0 -6px #f6f6f6,
23 | 0 17px 2px -6px rgba(0, 0, 0, 0.2);
24 | }
25 |
26 | TodoCount {
27 | @apply span;
28 | float: left;
29 | text-align: left;
30 | }
31 |
32 | TodoCount strong {
33 | font-weight: 300;
34 | }
35 |
36 | ClearCompleted {
37 | @apply button;
38 | }
39 |
40 | ClearCompleted,
41 | html ClearCompleted:active {
42 | float: right;
43 | position: relative;
44 | line-height: 20px;
45 | text-decoration: none;
46 | cursor: pointer;
47 | position: relative;
48 | }
49 |
50 | ClearCompleted:hover {
51 | text-decoration: underline;
52 | }
53 |
54 | ClearCompleted::after {
55 | content: 'Clear completed';
56 | }
57 |
--------------------------------------------------------------------------------
/assets/webpack.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
--------------------------------------------------------------------------------
/examples/webpack-react-dom/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { Label } from './index.css';
3 |
4 | class Counter extends PureComponent {
5 | state = { count: 0 };
6 |
7 | handleClick = () => {
8 | this.setState(state => ({ ...state, count: state.count + 1 }));
9 | };
10 |
11 | render() {
12 | return (
13 |
14 | {this.state.count}
15 |
16 | );
17 | }
18 | }
19 |
20 | class App extends PureComponent {
21 | state = {};
22 |
23 | handleClick = () => {
24 | this.setState({
25 | color: `rgb(${ (Math.random() * 255).toFixed(0) }, ${ (Math.random() * 255).toFixed(0) }, ${ (Math.random() *
26 | 255).toFixed(0) })`,
27 | fontSize: Math.random() * 100,
28 | });
29 | };
30 |
31 | render() {
32 | return (
33 |
34 |
37 |
38 |
39 |
40 |
41 | );
42 | }
43 | }
44 |
45 | export default App;
46 |
--------------------------------------------------------------------------------
/__tests__/dom/bind-attrs-to-cssom.js:
--------------------------------------------------------------------------------
1 | import onDOMLoad from '../../dom/dist/on-dom-load';
2 | import bindAttrsToCSSOM from '../../dom/dist/bind-attrs-to-cssom';
3 |
4 | const style = Object.assign(document.createElement('style'), {
5 | type: 'text/css',
6 | innerHTML: `
7 | .A .B {
8 | color: attr(color color);
9 | }
10 | `,
11 | });
12 |
13 | document.head.appendChild(style);
14 |
15 | const boundAttrs = bindAttrsToCSSOM([
16 | {
17 | prop: 'color',
18 | selector: '.A .B',
19 | template: '{ color }',
20 | attributes: ['color'],
21 | },
22 | ]);
23 |
24 | const [{ cssRule: boundCSSRule }] = boundAttrs;
25 |
26 | test('Binds attrs to CSSOM rules', () => {
27 | expect(boundAttrs).toEqual([
28 | expect.objectContaining({
29 | prop: 'color',
30 | selector: '.A .B',
31 | template: '{ color }',
32 | attributes: ['color'],
33 | className: expect.any(String),
34 | cssRule: expect.objectContaining({
35 | selectorText: expect.stringContaining('.A .B'),
36 | }),
37 | }),
38 | ]);
39 | });
40 |
41 | test('Replaces the CSSOM rules after DOM load', () => {
42 | onDOMLoad(() => {
43 | setTimeout(() => {
44 | expect(boundAttrs[0].cssRule).not.toBe(boundCSSRule);
45 | });
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/react-dom/dist/utils.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | var omitBy = exports.omitBy = function omitBy(object, filter) {
7 | var newObj = {};
8 | for (var key in object) {
9 | var value = object[key];
10 | if (!filter(value, key)) {
11 | newObj[key] = value;
12 | }
13 | }
14 | return newObj;
15 | };
16 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy91dGlscy5qcyJdLCJuYW1lcyI6WyJvbWl0QnkiLCJvYmplY3QiLCJmaWx0ZXIiLCJuZXdPYmoiLCJrZXkiLCJ2YWx1ZSJdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFBTyxJQUFNQSwwQkFBUyxTQUFUQSxNQUFTLENBQUNDLE1BQUQsRUFBU0MsTUFBVCxFQUFvQjtBQUN4QyxNQUFNQyxTQUFTLEVBQWY7QUFDQSxPQUFLLElBQU1DLEdBQVgsSUFBa0JILE1BQWxCLEVBQTBCO0FBQ3hCLFFBQU1JLFFBQVFKLE9BQU9HLEdBQVAsQ0FBZDtBQUNBLFFBQUksQ0FBQ0YsT0FBT0csS0FBUCxFQUFjRCxHQUFkLENBQUwsRUFBeUI7QUFDdkJELGFBQU9DLEdBQVAsSUFBY0MsS0FBZDtBQUNEO0FBQ0Y7QUFDRCxTQUFPRixNQUFQO0FBQ0QsQ0FUTSIsImZpbGUiOiJ1dGlscy5qcyIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBjb25zdCBvbWl0QnkgPSAob2JqZWN0LCBmaWx0ZXIpID0+IHtcbiAgY29uc3QgbmV3T2JqID0ge307XG4gIGZvciAoY29uc3Qga2V5IGluIG9iamVjdCkge1xuICAgIGNvbnN0IHZhbHVlID0gb2JqZWN0W2tleV07XG4gICAgaWYgKCFmaWx0ZXIodmFsdWUsIGtleSkpIHtcbiAgICAgIG5ld09ialtrZXldID0gdmFsdWU7XG4gICAgfVxuICB9XG4gIHJldHVybiBuZXdPYmo7XG59O1xuIl19
--------------------------------------------------------------------------------
/postcss/append-attr.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash/fp');
2 | const attrToTemplate = require('./attr-to-template');
3 |
4 | /**
5 | * @typedef {Object} Attr Components' CSS declarations which contain the attr() function representation
6 | * @property {string} prop The declaration's CSS property
7 | * @property {string} selector The CSS selector of the declaration's rule
8 | * @property {Template} template The declaration with attr() replaced to template placeholders
9 | * @property {string} attributes Attributes referenced in the declaration with the attr() function
10 | */
11 |
12 | /**
13 | * @param {Object