Repository: giuseppeg/dss Branch: master Commit: f3c53eaba534 Files: 117 Total size: 113.5 KB Directory structure: gitextract_s4oatymr/ ├── .editorconfig ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── acss-stats-tool/ │ ├── LICENSE │ ├── README.md │ ├── cli.js │ ├── index.js │ └── package.json ├── classnames/ │ ├── LICENSE │ ├── README.md │ ├── index.js │ └── package.json ├── compiler/ │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── bin/ │ │ └── dss │ ├── index.js │ ├── package.json │ ├── processor.js │ ├── src/ │ │ ├── compile.js │ │ ├── index.js │ │ ├── plugins/ │ │ │ ├── nest-pseudo.js │ │ │ ├── sort-at-rules.js │ │ │ ├── split-grouped-selectors.js │ │ │ └── validator.js │ │ ├── processor.js │ │ └── vendor/ │ │ └── hash.js │ └── test/ │ ├── __snapshots__/ │ │ ├── index.test.js.snap │ │ ├── objectify.test.js.snap │ │ └── processor.test.js.snap │ ├── browser/ │ │ ├── index.js │ │ ├── server.js │ │ └── utils.js │ ├── index.test.js │ ├── objectify.test.js │ └── processor.test.js ├── examples/ │ ├── cli/ │ │ ├── a.css │ │ ├── b.css │ │ ├── d.css │ │ ├── index.html │ │ └── package.json │ ├── webpack3/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── a.css │ │ │ ├── b.css │ │ │ ├── d.css │ │ │ ├── index.html │ │ │ └── index.js │ │ └── webpack.config.js │ └── webpack4/ │ ├── package.json │ ├── src/ │ │ ├── a.css │ │ ├── b.css │ │ ├── d.css │ │ ├── index.html │ │ └── index.js │ └── webpack.config.js ├── lerna.json ├── next-dss/ │ ├── LICENSE │ ├── README.md │ ├── index.js │ └── package.json ├── package.json ├── release-website ├── webpack/ │ ├── LICENSE │ ├── README.md │ ├── index.js │ ├── loader.js │ └── package.json └── website/ ├── .babelrc ├── LICENSE ├── components/ │ ├── analytics/ │ │ └── index.js │ ├── body/ │ │ ├── index.css │ │ └── index.js │ ├── heading/ │ │ ├── index.css │ │ └── index.js │ ├── layout/ │ │ ├── index.css │ │ └── index.js │ ├── link/ │ │ ├── index.css │ │ └── index.js │ ├── logo/ │ │ ├── index.css │ │ └── index.js │ ├── markdown/ │ │ ├── index.css │ │ └── index.js │ ├── navigation/ │ │ ├── CurrentPath.js │ │ ├── index.css │ │ └── index.js │ └── playground/ │ ├── index.css │ └── index.js ├── md/ │ ├── atomic-css.md │ ├── classnames-helper.md │ ├── examples.md │ ├── how-it-works.md │ ├── index.md │ ├── sass-preprocessors.md │ ├── supported-css-features.md │ ├── usage.md │ └── webpack.md ├── next.config.js ├── package.json ├── pages/ │ ├── _app.js │ ├── _document.js │ ├── atomic-css.js │ ├── classnames-helper.js │ ├── examples.js │ ├── how-it-works.js │ ├── index.js │ ├── sass-preprocessors.js │ ├── supported-css-features.js │ ├── usage.js │ └── webpack.js ├── postcss.config.js ├── static/ │ └── playground/ │ └── index.html └── theme/ ├── index.js ├── utils.js └── variables.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # EditorConfig helps developers define and maintain consistent # coding styles between different editors and IDEs # http://editorconfig.org root = true [*] end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 2 max_line_length = 100 ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: giuseppeg ================================================ FILE: .gitignore ================================================ dist node_modules coverage .next website/out ================================================ FILE: .travis.yml ================================================ language: node_js sudo: false node_js: - "8" addons: apt: packages: - xvfb before_install: - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.6.0 - export PATH="$HOME/.yarn/bin:$PATH" install: - export DISPLAY=':99.0' - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & - yarn ================================================ FILE: LICENSE ================================================ Copyright 2018-present Giuseppe Gurgone. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ screen shot 2018-07-08 at 5 45 52 pm # Deterministic Style Sheets ✨ [![Build Status](https://travis-ci.org/giuseppeg/dss.svg?branch=master)](https://travis-ci.org/giuseppeg/dss) [![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo) [![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) DSS (Deterministic StyleSheets) is a component-oriented CSS authoring system that compiles to high-performance atomic CSS classes-based stylesheets. DSS works like CSS Modules except that styles resolution is deterministic, CSS is compiled to atomic classes and the final bundle is very small. Read more about how it works on the [website](https://giuseppeg.github.io/dss/). [**warning, this is an experimental project and might not be production ready**] The repo comes with `examples`: ```shell cd examples/cli # or cd examples/webpack npm install npm start ``` ## Features * ⚡️ Automatic compilation to Atomic CSS classes and high-performance stylesheets * 🆎 Deterministic styles resolution: styles are always resolved in application order * 📦 Scoped Styles * 🌎 Framework and language agnostic * 🤝 Preprocessors friendly * 💻 Standalone CLI and support for Webpack 3 and 4 with automatic vendor prefixing * ✂️ CSS the Best Parts ## Contributing DSS is developed as a monorepo thanks to lerna and yarn workspaces. Everything you need to know is in this repository. Since this is a side project and I don't want to burn out, I decided to disable the GitHub issues. ### Bugs If you find a bug please submit a pull request with a failing test or a fix, and good description for the issue. ### Features request Please submit a pull request with an RFC where you explain the why and the how you think this feature is useful. I'd be glad to start a conversation from there before moving on to implementation. Also please let me know if you would be up to implement the feature you are suggesting. ### My code is crap I know, it is a side project and I didn't sweat the details. I am more than happy to discuss about a complete rewrite if the project becomes popular. ## LICENSE MIT ================================================ FILE: acss-stats-tool/LICENSE ================================================ Copyright 2018-present Giuseppe Gurgone. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: acss-stats-tool/README.md ================================================ # atomic-css-stats Provides stats on `.css` files size (gzipped and brotli). Also compiles the styles to atomic CSS classes for compariaon. ``` npm i -g atomic-css-stats ``` Accepts a space separated list of paths or URL to css files. ``` acss-stats ./file.css https://example.com/bundle.css [...] ``` example: ``` $ acss-stats https://abs.twimg.com/a/1532484778/css/t1/twitter_core.bundle.css ============== | https://abs.twimg.com/a/1532484778/css/t1/twitter_core.bundle.css ============== Original file: Size: 182.72 KB Gzipped size: 34.08 KB Brotli size: 28.95 KB --- Compiled to atomic CSS classes: Size: 60.05 KB Gzipped size: 15.48 KB Brotli size: 12.83 KB ``` ## Contributing This package is part of the [DSS monorepo](https://github.com/giuseppeg/dss#contributing). ## License MIT ================================================ FILE: acss-stats-tool/cli.js ================================================ #!/usr/bin/env node const fs = require('fs') const path = require('path') const { promisify } = require('util') const fetch = require('isomorphic-fetch') const getStats = require('./') function read(resource) { if (/^https?:\/\//.test(resource)) { return fetch(resource).then(r => r.text()) } return promisify(fs.readFile)(path.resolve(resource)) } const resources = [...process.argv.slice(2)] ;(async function () { const stats = [] for (const resource of resources) { if (!resources) { console.error('Provide a valid path to file or url for ' + resource) process.exit(1) } const content = await read(resource) const s = await getStats(content) stats.push({ resource, stats: s, }) } stats.forEach(({ resource, stats }) => { console.log( ` ============== | ${resource} ============== Original file: Size: ${stats.original.size} Gzipped size: ${stats.original.gzipSize} Brotli size: ${stats.original.brotliSize} --- Compiled to atomic CSS classes: Size: ${stats.atomic.size} Gzipped size: ${stats.atomic.gzipSize} Brotli size: ${stats.atomic.brotliSize} `) }) }()); ================================================ FILE: acss-stats-tool/index.js ================================================ const filesize = require('filesize') const gzipSize = require('gzip-size') const brotliSize = require('brotli-size') const mrmuh = require('murmurhash') const hash = str => mrmuh(str, str.length).toString(36) const postcss = require('postcss') exports = module.exports = async function getStats(content) { const originalSize = filesize(content.length) const atomizedContent = await atomizer(content) return { original: { size: filesize(content.length), gzipSize: filesize(gzipSize.sync(content)), brotliSize:filesize(brotliSize.sync(content)) }, atomic: { size: filesize(atomizedContent.length), gzipSize: filesize(gzipSize.sync(atomizedContent)), brotliSize: filesize(brotliSize.sync(atomizedContent)), } } } async function atomizer(src) { const processed = {} let css = '' function plugin() { return root => { root.walkDecls(decl => { if (processed[decl.prop + decl.value]) return processed[decl.prop + decl.value] = true if (decl.prop.startsWith('-') && !decl.prop.startsWith('--') && css.endsWith('}')) { css += `${css.substring(0, -1)}; ${decl.prop}: ${decl.value} }` } else { css += `.dss_${hash(decl.prop)}-${hash(decl.value)} { ${decl.prop}: ${decl.value} }` } }) } } return await postcss(plugin()).process(src, { from: undefined }).then(() => css) } ================================================ FILE: acss-stats-tool/package.json ================================================ { "name": "atomic-css-stats", "version": "0.1.0-beta.4", "description": "Provides information about regular and compiled to atomic CSS classes files size (gzipped)", "main": "index.js", "bin": { "acss-stats": "cli.js" }, "keywords": [ "dss", "atomic css", "css in js", "css", "classes", "css modules", "sass", "postcss", "classnames" ], "author": "Giuseppe Gurgone", "license": "MIT", "dependencies": { "brotli-size": "^0.0.2", "filesize": "^3.6.1", "gzip-size": "^5.0.0", "isomorphic-fetch": "^2.2.1", "murmurhash": "^0.0.2", "postcss": "^7.0.1" } } ================================================ FILE: classnames/LICENSE ================================================ Copyright 2018-present Giuseppe Gurgone. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: classnames/README.md ================================================ # dss-classnames Deterministic Style Sheets - classNames helper. Read [more about this package](https://dss-lang.com/usage/#dss-classnames). ## Contributing This package is part of the [DSS monorepo](https://github.com/giuseppeg/dss#contributing). ## License MIT ================================================ FILE: classnames/index.js ================================================ /* eslint-disable no-var, prefer-arrow-callback */ ;(function(root) { if (typeof module === 'object' && module.exports) { module.exports = classnames } else { root.classnames = classnames } function setVal(processed, out, propValue) { var prop if (propValue.substr(0, 4) !== 'dss_') { return propValue + ' ' + out } prop = propValue.substr(0, propValue.indexOf('-')) if (!processed[prop]) { processed[prop] = true return out + ' ' + propValue } return out } function classnames() { var groups = arguments var processed = {} var out = '' for (var i = groups.length - 1; i >= 0; i--) { var group = groups[i] if (!group) { continue } if (typeof group === 'string') { out = setVal(processed, out, group) continue } group.forEach(function(item) { out = setVal(processed, out, item) }) } return out } })(typeof self === 'undefined' ? this : self) ================================================ FILE: classnames/package.json ================================================ { "name": "dss-classnames", "version": "0.1.0-beta.0", "description": "Deterministic Style Sheets - classNames helper", "main": "index.js", "keywords": [ "dss", "atomic css", "css in js", "css", "classes", "css modules", "sass", "postcss", "classnames" ], "author": "Giuseppe Gurgone", "license": "MIT" } ================================================ FILE: compiler/.gitignore ================================================ dist node_modules coverage ================================================ FILE: compiler/LICENSE ================================================ Copyright 2018-present Giuseppe Gurgone. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: compiler/README.md ================================================ # dss-compiler Deterministic Style Sheets - compiler. Read [more about this package](https://dss-lang.com/usage/#dss-compiler). ## Contributing This package is part of the [DSS monorepo](https://github.com/giuseppeg/dss#contributing). ## License MIT ================================================ FILE: compiler/bin/dss ================================================ #!/usr/bin/env node const fs = require('fs') const path = require('path') const getopts = require("getopts") const glob = require('glob') const dss = require('..') const optimizer = require('../processor').optimizer const read = filePath => fs.readFileSync(filePath, 'utf-8') const args = getopts(process.argv.slice(2), { alias: { t: 'outType', n: 'bundleName', h: 'help' }, default: { outType: 'json', bundleName: 'index.css', help: false, } }) if (args._.length != 2 && !args.help) { console.error('You must specifiy a glob pattern to find .css files and a target directory\n') args.help = true } if (args.help) { const programName = process.argv[1].split('/').slice(-1).toString() console.log([ 'Usage:', programName + ' [--bundleName] [--outType]', 'Example:', programName + ' ./src/**/*.css ./build --bundleName bundle.css --outType js', '' ].join('\n\n')) process.exit(args._.length != 2 ? 1 : 0) } const [globPattern, dist] = args._ const jsSheets = glob.sync(globPattern) if (jsSheets.length === 0) { console.error('DSS: the glob ' + globPattern + ' did not match any files.') process.exit(1) } const compilePromises = jsSheets.map(filePath => { const css = read(filePath) return dss.singleton(css, { filePath, readableClass: process.env.NODE_ENV !== 'production' }) }) Promise.all(compilePromises).then(results => { let flush results.forEach((result, i) => { const stringified = JSON.stringify(result.locals, null, 2) const out = args.outType === 'js' ? `exports = module.exports = ${stringified}` : stringified fs.writeFileSync( path.resolve(path.join(dist, path.basename(jsSheets[i])+'.'+args.outType)), out ) flush = result.flush }) const css = flush() const destPath = path.resolve(path.join(dist, args.bundleName)) optimizer(css, { from: 'dss files', to: destPath }) .then(result => { fs.writeFileSync(destPath, result.css) }) .catch(e => { throw e }) }).catch(e => { console.error(e.reason ? `Error in: ${e.file}\n\n${e.reason}` : e) process.exit(1) }) ================================================ FILE: compiler/index.js ================================================ module.exports = require('./src') ================================================ FILE: compiler/package.json ================================================ { "name": "dss-compiler", "version": "0.1.0-beta.0", "description": "Deterministic Style Sheets - compiler", "main": "index.js", "bin": { "dss": "bin/dss" }, "files": [ "bin", "src", "index.js", "processor.js", "LICENSE", "README.md" ], "keywords": [ "dss", "atomic css", "css in js", "css", "classes", "css modules", "sass", "postcss", "classnames" ], "author": "Giuseppe Gurgone", "license": "MIT", "scripts": { "test": "jest --coverage && npm run test:browser", "test:browser": "run-p --race test:browser:server test:browser:run", "test:browser:run": "browserify test/browser/index.js | tape-run", "test:browser:server": "node test/browser/server.js", "jest": "jest" }, "dependencies": { "autoprefixer": "8.6.4", "getopts": "2.0.6", "glob": "7.1.2", "just-flatten-it": "2.0.0", "postcss": "6.0.17", "postcss-discard-duplicates": "2.1.0", "postcss-js": "1.0.1", "postcss-nest-atrules": "0.1.3" }, "devDependencies": { "browserify": "^16.1.1", "dss-classnames": "0.1.0-beta.0", "jest": "^22.4.3", "npm-run-all": "^4.1.2", "tape": "^4.9.0", "tape-css": "^1.0.2-beta", "tape-run": "^4.0.0" }, "jest": { "collectCoverageFrom": [ "src/**/*.{js}", "!src/vendor/*" ] } } ================================================ FILE: compiler/processor.js ================================================ module.exports = require('./src/processor') ================================================ FILE: compiler/src/compile.js ================================================ const flatten = require('just-flatten-it') const hash = require('./vendor/hash') /* Fork of cxs with fundamendal changes */ const DEFAULT_SHEET_ID = '__defaultSheetId' const cache = {} const rules = {} const insertedRules = {} const insert = (sheetId, rule) => { rules[sheetId].push(rule) } const hyph = s => s.replace(/[A-Z]|^ms/g, '-$&').toLowerCase() const mx = (rule, media) => (media ? `${media}{${rule}}` : rule) const propVal = (prop, val) => (Array.isArray(val) ? val : [val]).map(val => `${hyph(prop)}:${val}`).join(';') const rx = (cn, prop, val) => `${cn.startsWith(':') ? '' : '.'}${cn}{${propVal(prop, val)}}` const rxArr = (cn, prop, vals) => `.${cn}{${propVal(prop, vals)}}` const noAnd = s => s.replace(/&/g, '') const combinatorOrPure = (className, child) => { if (child.endsWith('&')) { return `${noAnd(child)}.${className}` } return className + noAnd(child) } const className = (sheetId, key, val, child, media) => { const _key = key + val + child + media const cached = cache[_key] if (cached) { const [className, rule] = cached if (!insertedRules[sheetId][className]) { insert(sheetId, rule) insertedRules[sheetId][className] = true } return className } const className = `dss_${hash(key + media + child.replace(/[^\w]+$/i, ''))}-${hash( val.toString() )}` const rxFn = Array.isArray(val) ? rxArr : rx const rule = mx(rxFn(combinatorOrPure(className, child), key, val), media) cache[_key] = [className, rule] insert(sheetId, rule) insertedRules[sheetId][className] = true return className } const parse = (sheetId, obj, child = '', media, callback) => Object.keys(obj).map(key => { const val = obj[key] if (val === null) return '' if (Object.prototype.toString.call(val) === '[object Object]') { const m2 = key.charAt(0) === '@' ? key : null const c2 = m2 ? child : child + key return parse(sheetId, val, c2, m2 || media, callback) } return callback(sheetId, key, val, child, media) }) module.exports = (styles, opts = {}) => { const sheetId = opts.sheetId || DEFAULT_SHEET_ID if (!rules[sheetId]) { rules[sheetId] = [] insertedRules[sheetId] = {} } return Object.keys(styles).reduce((acc, key) => { // Insert non nested at rules like @keyframes as-is // since they don't have selectors associated to them. if (key.charAt(0) === '@') { ;(Array.isArray(styles[key]) ? styles[key] : [styles[key]]).forEach(styles => { const steps = {} parse(sheetId, styles, '', null, (sheetId, prop, val, child) => { const props = propVal(prop, val) if (steps[child]) { steps[child].push(props) } else { steps[child] = [props] } }) const css = Object.keys(steps).reduce((css, step) => { css += mx(steps[step].join(';'), step) return css }, '') insert(sheetId, mx(css, key)) }) return acc } const jsKey = key.replace(/^\./, '') acc[jsKey] = flatten(parse(sheetId, styles[key], undefined, undefined, className)) if (typeof opts.makeReadableClass === 'function') { const readableClass = opts.makeReadableClass(jsKey) acc[jsKey].unshift(readableClass) } return acc }, {}) } module.exports.css = (sheetId = DEFAULT_SHEET_ID) => (rules[sheetId] || []).sort().join('') module.exports.reset = (sheetId = DEFAULT_SHEET_ID) => { if (rules[sheetId]) { delete rules[sheetId] delete insertedRules[sheetId] } } ================================================ FILE: compiler/src/index.js ================================================ const path = require('path') const { objectify } = require('postcss-js') const hash = require('./vendor/hash') const processor = require('./processor') const compile = require('./compile') const defaultOptions = { filePath: undefined, readableClass: false, } let uuid = 1 const createDss = (singleton = false) => async (css, options = {}) => { const opts = Object.assign({}, defaultOptions, options) let makeReadableClass if (opts.readableClass) { if (typeof opts.readableClass === 'function') { makeReadableClass = localName => opts.readableClass(localName, hash(css)) } else { let prefix if (typeof opts.filePath === 'string') { const filename = path.basename(opts.filePath) const p = filename .split('.') .slice(0, -1) .join('-') || 'DssSource' prefix = p.charAt(0).toUpperCase() + p.substr(1) } else { prefix = 'DssSource' } makeReadableClass = localName => `${prefix}-${localName}-${hash(css)}` } } const sheetId = String(singleton ? 0 : uuid++) const result = await processor(css, { from: opts.filePath }) const locals = compile(objectify(result.root), { makeReadableClass, sheetId, }) return { locals, css: () => compile.css(sheetId), reset: () => compile.reset(sheetId), flush: () => { const css = compile.css(sheetId) compile.reset(sheetId) return css }, } } module.exports = createDss() module.exports.singleton = createDss(true) ================================================ FILE: compiler/src/plugins/nest-pseudo.js ================================================ const postcss = require('postcss') module.exports = postcss.plugin('postcss-dss-nest-pseudo', () => { return root => { root.walkRules(rule => { let parts = rule.selector.split(/:/) if (parts.length < 2 || parts[0] === '&' || rule.selector.slice(-1) === '&') { return } let parentSelector = parts[0] let selector = ':' + parts.slice(1).join(':') // :hover > .foo if (parts[0] === '') { const delimiter = selector.match(/[>~+]/) if (delimiter) { parts = selector.split(delimiter[0]) selector = `${parts[0].trim()} ${delimiter[0]} &` parentSelector = parts[1].trim() } else { return } } const clone = rule.clone() clone.selector = selector rule.nodes = [clone] rule.selector = parentSelector }) } }) ================================================ FILE: compiler/src/plugins/sort-at-rules.js ================================================ const postcss = require('postcss') // Moves at rules at the bottom of the file. module.exports = postcss.plugin('postcss-dss-sort-at-rules', () => { return root => { const atRules = [] root.walkAtRules(atRule => { atRules.push(atRule.clone()) atRule.remove() }) root.nodes = root.nodes.concat(atRules) } }) ================================================ FILE: compiler/src/plugins/split-grouped-selectors.js ================================================ const postcss = require('postcss') module.exports = postcss.plugin('postcss-dss-split-grouped-selectors', () => { return root => { root.walkRules(rule => { const selector = rule.selector.split(',').map(s => s.trim()) if (selector.length < 2) { return } rule.selector = selector[0] for (let i = 1; i < selector.length; i++) { const clone = rule.clone() clone.selector = selector[i] rule.parent.insertAfter(rule, clone) } }) } }) ================================================ FILE: compiler/src/plugins/validator.js ================================================ const postcss = require('postcss') const shortHandProperties = [ 'animation', 'background', 'border', 'border-bottom', 'border-left', 'border-radius', 'border-right', 'border-top', 'column-rule', 'columns', 'flex', 'flex-flow', 'font', 'grid', 'grid-area', 'grid-column', 'grid-row', 'grid-template', 'list-style', 'margin', 'offset', 'outline', 'overflow', 'padding', 'place-content', 'place-items', 'place-self', 'text-decoration', 'transition', ] function error(node, message) { throw node.error( `DSS Error ${message} For a comprehensive list of supported features refer to http://giuseppeg.github.io/dss/supported-css-features/ ` ) } module.exports = postcss.plugin('postcss-dss-validator', () => { return root => { const processed = {} root.walkRules(rule => { const { selector, parent } = rule if (parent && parent.type === 'atrule') { return } const params = parent && parent.params ? parent.params : '' if (processed[params + selector]) { error(rule, `Detected duplicated selector: '${selector}'. Please merge it with the previous one.` ) } if (selector.split(',').length > 1) { error(rule, `Invalid selector: ${selector}. Selectors cannot be grouped.` ) } if (/::?(after|before|first-letter|first-line)/.test(selector)) { error(rule, `Detected pseudo-element: '${selector}'. Pseudo-elements are not supported. Please use regular elements.` ) } if (/:(matches|has|not|lang|any|current)/.test(selector)) { error(rule, `Detected unsupported pseudo-class: '${selector}'.` ) } const split = selector.split(/\s*[+>~\s]\s*/g) switch (split.length) { case 2: if (split[0].charAt(0) !== ':' || split[1].charAt(0) !== '.') { error(rule, `Invalid selector: ${selector}.` ) } break case 1: if (split[0].charAt(0) !== '.') { error(rule, `Invalid selector: ${selector}. Only class selectors are allowed.` ) } break default: error(rule, `Invalid selector: ${selector}.` ) } if (/\[/.test(selector)) { error(rule, `Invalid selector: ${selector}. Cannot use complex selectors, please use only class selectors.` ) } processed[params + selector] = true }) root.walkDecls(decl => { if (shortHandProperties.includes(decl.prop)) { error(decl, '`' + decl.prop + '`: DSS does\'t support shorthand properties at the moment. This CSS feature will likely be supported in the future. Please expand your shorthand properties for now.' + `\n Can't remember what is the long form for \`${decl.prop}\`? Ask Google 👉 https://google.com/search?q=${encodeURIComponent(`css ${decl.prop} properties`)}` ) } if (decl.important) { error(decl, '!important is not allowed' ) } }) } }) ================================================ FILE: compiler/src/processor.js ================================================ const postcss = require('postcss') const nestAtRulesPlugin = require('postcss-nest-atrules') const nestPseudoPlugin = require('./plugins/nest-pseudo') const splitGroupedSelectorsPlugin = require('./plugins/split-grouped-selectors') const validatorPlugin = require('./plugins/validator') const processor = postcss([ splitGroupedSelectorsPlugin, validatorPlugin, nestAtRulesPlugin, nestPseudoPlugin, ]) module.exports = (css, opts = { from: undefined }) => processor.process(css, opts) const optimzr = postcss([ /* eslint-disable import/order */ require('postcss-discard-duplicates'), require('autoprefixer'), require('./plugins/sort-at-rules'), /* eslint-enable import/order */ ]) module.exports.optimizer = (css, opts = { from: undefined, to: undefined }) => optimzr.process(css, opts) ================================================ FILE: compiler/src/vendor/hash.js ================================================ // @flow // murmurhash2 via https://gist.github.com/raycmorgan/588423 module.exports = function hashString(str) { return hash(str, str.length).toString(36) } function hash(str, seed) { let m = 0x5bd1e995 let r = 24 let h = seed ^ str.length let length = str.length let currentIndex = 0 while (length >= 4) { let k = UInt32(str, currentIndex) k = Umul32(k, m) k ^= k >>> r k = Umul32(k, m) h = Umul32(h, m) h ^= k currentIndex += 4 length -= 4 } switch (length) { case 3: h ^= UInt16(str, currentIndex) h ^= str.charCodeAt(currentIndex + 2) << 16 h = Umul32(h, m) break case 2: h ^= UInt16(str, currentIndex) h = Umul32(h, m) break case 1: h ^= str.charCodeAt(currentIndex) h = Umul32(h, m) break } h ^= h >>> 13 h = Umul32(h, m) h ^= h >>> 15 return h >>> 0 } function UInt32(str, pos) { return ( str.charCodeAt(pos++) + (str.charCodeAt(pos++) << 8) + (str.charCodeAt(pos++) << 16) + (str.charCodeAt(pos) << 24) ) } function UInt16(str, pos) { return str.charCodeAt(pos++) + (str.charCodeAt(pos++) << 8) } function Umul32(n, m) { n = n | 0 m = m | 0 let nlo = n & 0xffff let nhi = n >>> 16 let res = (nlo * m + (((nhi * m) & 0xffff) << 16)) | 0 return res } ================================================ FILE: compiler/test/__snapshots__/index.test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`dss compiles 1`] = ` Object { "a": Array [ "dss_rfc3hq-169mlyl", "dss_16bwgo5-11z5xnj", "dss_1u3exn4-nfznl2", "dss_1u3exn4-1ysx8fe", "dss_1qphpw1-hlr2nt", ], } `; exports[`dss compiles 2`] = ` " .a { color: red; } .a:hover { color: blue; } @media screen and (min-width: 30px) { .a { color: hotpink } } :hover > .a { color: orange; } :hover + .a { display: block; } ⬇⬇⬇⬇ .dss_1u3exn4-nfznl2:hover{color:blue}.dss_rfc3hq-169mlyl{color:red}:hover + .dss_1qphpw1-hlr2nt{display:block}:hover > .dss_1u3exn4-1ysx8fe{color:orange}@media screen and (min-width: 30px){.dss_16bwgo5-11z5xnj{color:hotpink}}" `; exports[`dss compiles fallbacks 1`] = ` Object { "a": Array [ "dss_rfc3hq-1t0ure0", ], } `; exports[`dss compiles fallbacks 2`] = ` " .a { color: red; color: green; } ⬇⬇⬇⬇ .dss_rfc3hq-1t0ure0{color:red;color:green}" `; exports[`dss compiles keyframes 1`] = ` Object { "a": Array [ "dss_1q9w8i1-4hi1ll", "dss_1ot5qnt-ykz8s4", "dss_rwa454-yobcwj", ], } `; exports[`dss compiles keyframes 2`] = ` " @font-face { font-family: 'foo'; src: url(http://b.ar) } .a { transition-property: fade; transition-timing-function: ease-out; transition-duration: 0.5s; } @keyframes some {0% { opacity:0 } 100% { opacity:1}} @keyframes fade {0% { opacity:0 } 100% { opacity:1}} @keyframes fade { 0% { opacity:0; margin-left: 0; } 100% { opacity:1; margin-left: 100; } } ⬇⬇⬇⬇ .dss_1ot5qnt-ykz8s4{transition-timing-function:ease-out}.dss_1q9w8i1-4hi1ll{transition-property:fade}.dss_rwa454-yobcwj{transition-duration:0.5s}@font-face{font-family:'foo';src:url(http://b.ar)}@keyframes fade{0%{opacity:0;margin-left:0}100%{opacity:1;margin-left:100}}@keyframes fade{0%{opacity:0}100%{opacity:1}}@keyframes some{0%{opacity:0}100%{opacity:1}}" `; exports[`dss does not have duplicates 1`] = ` Object { "bar": Array [ "dss_14e3233-hlr2nt", ], "foo": Array [ "dss_14e3233-hlr2nt", ], } `; exports[`dss does not have duplicates 2`] = `".dss_14e3233-hlr2nt{display:block}"`; ================================================ FILE: compiler/test/__snapshots__/objectify.test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`objetify css media and pseudo 1`] = ` Object { ".a": Object { "@media (min-width: 30px)": Object { ":hover": Object { "color": "red", }, }, }, } `; exports[`objetify css multiple 1`] = ` Object { ".a": Object { ":hover": Object { "color": "blue", }, "@media screen and (min-width: 30px)": Object { "color": "hotpink", }, "color": "red", }, } `; exports[`objetify css pseudo class 1`] = ` Object { ".a": Object { ":hover": Object { "color": "red", }, }, } `; exports[`objetify css simple and pseudo class 1`] = ` Object { ".a": Object { ":hover": Object { "color": "red", }, "color": "pink", }, } `; exports[`objetify css simple rule 1`] = ` Object { ".a": Object { "color": "red", }, } `; exports[`objetify css state-combinator-class 1`] = ` Object { ".a": Object { ":hover > &": Object { "color": "blue", }, }, } `; ================================================ FILE: compiler/test/__snapshots__/processor.test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`processor applies the nest-atrules plugin 1`] = ` " .root { color: red; @media (min-width: 10px) { display: block } } " `; exports[`processor applies the nest-pseudo plugin 1`] = ` " .a { :hover { color: red; } } .b { :hover { color: hotpink } } .b { color: hotpink } .c { color: red; } .c { :hover > & { display: block; } } " `; exports[`processor applies the split-grouped-selectors plugin 1`] = ` " .a { color: red; } .b { color: red; } " `; exports[`processor merges rules 1`] = ` " .root { :hover { color: yellow; } } .block { display: block; margin-top: 10px; } .root { color: red; font-family: Verdana; display: block; @media (min-width: 600px) { color: green } } .test { color: red; color: pink; @media (min-width: 600px) { color: green; color: yellow } } " `; exports[`processor mixed nest-atrules and nest-pseudo 1`] = ` " .a { :hover { @media (min-width: 10px) { color: red } } } " `; exports[`processor moves at rules at the end of the file 1`] = ` " div { color: red } :hover > .foo { color: red } @media (min-width: 1px) { body { color: red } } @media (min-width: 10px) { body { color: gree } } " `; ================================================ FILE: compiler/test/browser/index.js ================================================ const test = require('tape-css')(require('tape')) const classNames = require('dss-classnames') const { compile, makeDom, run } = require('./utils') run(async () => { const {classes, styles} = await compile(` .a { background-color: red } .a:focus { background-color: yellow } .b { background-color: green } .c { background-color: red; display: block } @media (min-width: 0px) { .c { background-color: green; display: inline-block; } .d:focus { background-color: yellow } } .d { background-color: orange; font-weight: bold } .f { background-color: green; background-color: invalid; } `) const dom1 = makeDom(`
`) test( 'resolves deterministically', { dom: dom1, styles }, t => { t.equal( getComputedStyle(dom1.children[0]).getPropertyValue('background-color'), 'rgb(255, 0, 0)', 'the first child should be rgb(255, 0, 0) i.e. red' ) t.equal( getComputedStyle(dom1.children[1]).getPropertyValue('background-color'), 'rgb(0, 128, 0)', 'the second child should be rgb(0, 128, 0) i.e. green' ) t.end() } ) const dom2 = makeDom(` `) test( 'works with pseudo selectors', { dom: dom2, styles }, t => { t.equal( getComputedStyle(dom2).getPropertyValue('background-color'), 'rgb(255, 0, 0)', 'initially it should be rgb(255, 0, 0) i.e. red' ) dom2.focus() t.equal( getComputedStyle(dom2).getPropertyValue('background-color'), 'rgb(255, 255, 0)', 'on focus it should be rgb(255, 255, 0) i.e. yellow' ) t.end() } ) const dom3 = makeDom(` `) test( 'works with media queries', { dom: dom3, styles }, t => { const s = getComputedStyle(dom3) t.equal( s.getPropertyValue('background-color'), 'rgb(0, 128, 0)', 'initially `background-color` should be rgb(0, 128, 0) i.e. green' ) t.equal( s.getPropertyValue('display'), 'inline-block', 'initially `display` should be `inline-block`' ) t.equal( s.getPropertyValue('font-weight'), 'bold', 'initially `font-weight` should be `bold`' ) dom3.focus() t.equal( getComputedStyle(dom3).getPropertyValue('background-color'), 'rgb(255, 255, 0)', 'on focus it should be rgb(255, 255, 0) i.e. yellow' ) t.end() } ) const dom4 = makeDom(` `) test( 'works with merged rules', { dom: dom4, styles }, t => { t.equal( getComputedStyle(dom4).getPropertyValue('background-color'), 'rgb(0, 128, 0)', 'initially `background-color` should be rgb(0, 128, 0) i.e. green' ) dom4.focus() t.equal( getComputedStyle(dom4).getPropertyValue('background-color'), 'rgb(255, 255, 0)', 'on focus it should be rgb(255, 255, 0) i.e. yellow' ) t.end() } ) const dom5 = makeDom(` `) test( 'works with fallbacks', { dom: dom5, styles }, t => { t.equal( getComputedStyle(dom5).getPropertyValue('background-color'), 'rgb(0, 128, 0)', 'initially `background-color` should be rgb(0, 128, 0) i.e. green' ) t.end() } ) const dom6 = makeDom(` `) test( 'has readable class names', { dom: dom6, styles }, t => { const matches = dom6.className.match(/(Test-[a|f]-)/g) t.equal( matches ? matches.length : 0, 2, 'should have Test-a-hash and Test-f-hash class names' ) t.end() } ) }) ================================================ FILE: compiler/test/browser/server.js ================================================ const http = require('http') const dss = require('../../').singleton const port = process.env.PORT || 3000 const requestHandler = (request, response) => { let css = '' response.setHeader('Content-Type', 'application/json') response.setHeader('Access-Control-Allow-Origin', '*') response.setHeader('Access-Control-Request-Method', '*') response.setHeader('Access-Control-Allow-Methods', 'POST') response.setHeader('Access-Control-Allow-Headers', '*') request.on('data', data => { css += data }) request.on('end', async () => { const result = await dss(css, { readableClass: (localName, hash) => `Test-${localName}-${hash}` }) response.write(JSON.stringify({ classes: result.locals, styles: result.css() })) response.end() }) } const server = http.createServer(requestHandler) server.listen(port, (err) => { if (err) { return console.log('something bad happened', err) } console.log(`server is listening on ${port}`) }) ================================================ FILE: compiler/test/browser/utils.js ================================================ const tape = require('tape') module.exports.compile = function (css) { return fetch('http://localhost:3000', { body: css, cache: 'no-cache', method: 'POST', redirect: 'follow', referrer: 'no-referrer' }) .then(response => response.json()) .catch(err => { throw err }) } module.exports.makeDom = function (html) { const fragment = document.createElement('div') fragment.innerHTML = html return fragment.children[0] } let runCount = 1 module.exports.run = function (fn) { tape('suite ' + runCount++, t => { const result = fn() if (result instanceof Promise) { result.then(() => { t.end() }) } else { t.end() } }) } ================================================ FILE: compiler/test/index.test.js ================================================ const dss = require('../') describe('dss', () => { it('compiles', async () => { const src = ` .a { color: red; } .a:hover { color: blue; } @media screen and (min-width: 30px) { .a { color: hotpink } } :hover > .a { color: orange; } :hover + .a { display: block; } ` const { locals, flush } = await dss(src) expect(locals).toMatchSnapshot() expect(src + '\n\n⬇⬇⬇⬇\n\n' + flush()).toMatchSnapshot() }) it('compiles fallbacks', async () => { const src = ` .a { color: red; color: green; } ` const { locals, flush } = await dss(src) expect(locals).toMatchSnapshot() expect(src + '\n\n⬇⬇⬇⬇\n\n' + flush()).toMatchSnapshot() }) it('works in async mode', async () => { const styles1Promise = dss('.foo { color: red }') const r1 = await dss('.bar { display: block }') const css2 = r1.flush() const r2 = await styles1Promise const css1 = r2.flush() expect(css2).toBeTruthy() expect(css1).toBeTruthy() }) it('does not have duplicates', async () => { const { locals, css } = await dss('.foo { display: block } .bar { display: block }') expect(locals).toMatchSnapshot() expect(css()).toMatchSnapshot() }) it('works as a singleton', async () => { function rulesLength(css) { return (css.match(/\.dss_/g) || []).length } const call1 = await dss.singleton('.foo { display: block }') expect(call1.locals.foo.length).toBe(1) expect(rulesLength(call1.css())).toBe(1) const call2 = await dss.singleton('.bar { display: block }') expect(call2.locals.bar.length).toBe(1) expect(rulesLength(call2.css())).toBe(1) const call3 = await dss.singleton('.foo { color: red } .baz { color: orange }') expect(call3.locals.foo.length).toBe(1) expect(call3.locals.baz.length).toBe(1) expect(rulesLength(call3.css())).toBe(3) const call4 = await dss.singleton('.bar { vertical-align: middle }') const css = call1.flush() expect(rulesLength(call4.css())).toBe(0) expect(rulesLength(css)).toBe(4) }) it('compiles keyframes', async () => { // @keyframes fade {0% { opacity:0 } 100% { opacity:1}} const src = ` @font-face { font-family: 'foo'; src: url(http://b.ar) } .a { transition-property: fade; transition-timing-function: ease-out; transition-duration: 0.5s; } @keyframes some {0% { opacity:0 } 100% { opacity:1}} @keyframes fade {0% { opacity:0 } 100% { opacity:1}} @keyframes fade { 0% { opacity:0; margin-left: 0; } 100% { opacity:1; margin-left: 100; } } ` const { locals, flush } = await dss(src) expect(locals).toMatchSnapshot() expect(src + '\n\n⬇⬇⬇⬇\n\n' + flush()).toMatchSnapshot() }) }) ================================================ FILE: compiler/test/objectify.test.js ================================================ const { parse } = require('postcss') const { objectify } = require('postcss-js') const compile = css => objectify(parse(css)) describe('objetify css', () => { it('simple rule', () => { expect( compile(` .a { color: red } `) ).toMatchSnapshot() }) it('pseudo class', () => { expect( compile(` .a { :hover { color: red } } `) ).toMatchSnapshot() }) it('simple and pseudo class', () => { expect( compile(` .a { color: pink; :hover { color: red } } `) ).toMatchSnapshot() }) it('media and pseudo', () => { expect( compile(` .a { @media (min-width: 30px) { :hover { color: red } } } `) ).toMatchSnapshot() }) it('multiple', () => { expect( compile(` .a { color: red; } .a { :hover { color: blue; } } .a { @media screen and (min-width: 30px) { color: hotpink } } `) ).toMatchSnapshot() }) it('state-combinator-class', () => { expect( compile(` .a { :hover > & { color: blue; } } `) ).toMatchSnapshot() }) }) ================================================ FILE: compiler/test/processor.test.js ================================================ const postcss = require('postcss') const validatorPlugin = require('../src/plugins/validator') const sortAtRulesPlugin = require('../src/plugins/sort-at-rules') const processor = require('../src/processor') describe('processor', () => { it('processes css', async () => { const { css } = await processor(` .root { color: red; } `) expect(css).not.toBe('') }) it('applies the split-grouped-selectors plugin', async () => { const { css } = await processor(` .a, .b { color: red; } `) expect(css).toMatchSnapshot() }) it('applies the nest-atrules plugin', async () => { const { css } = await processor(` .root { color: red; } @media (min-width: 10px) { .root { display: block; } } `) expect(css).toMatchSnapshot() }) it('applies the nest-pseudo plugin', async () => { const { css } = await processor(` .a:hover { color: red; } .b:hover, .b { color: hotpink } .c { color: red; } :hover > .c { display: block; } `) expect(css).toMatchSnapshot() }) it('mixed nest-atrules and nest-pseudo', async () => { const { css } = await processor(` @media (min-width: 10px) { .a:hover { color: red; } } `) expect(css).toMatchSnapshot() }) it('merges rules', async () => { const { css } = await processor(` .root:hover { color: yellow; } .block { display: block; margin-top: 10px; } @media (min-width: 600px) { .root { color: green; } .test { color: green; color: yellow; } } .root { color: red; font-family: Verdana; display: block; } .test { color: red; color: pink; } `) expect(css).toMatchSnapshot() }) describe('validation', () => { const selectorsProcessor = css => postcss([validatorPlugin]).process(css, { from: undefined }) describe('throws when it detecs duplicated selectors', async () => { it('simple', () => expect( processor(` .a { color: red } .a { color: blue } `) ).rejects.toThrow(/Detected duplicated selector/)) it('combined', () => expect( processor(` .a, .a { color: red } `) ).rejects.toThrow(/Detected duplicated selector/)) it('except when it is a state declaration', () => expect( processor(` .a { color: red } .a:hover { color: blue } `) ).resolves.toBeTruthy()) }) it('throws when selectors are grouped', () => expect(selectorsProcessor(`.a, .b { color: red }`)).rejects.toThrow( /Selectors cannot be grouped/ )) it('throws when using pseudo-elements', () => { expect.assertions(8) return Promise.all( [ '.a:before', '.a:after', '.a:first-line', '.a:first-letter', '.a::before', '.a::after', '.a::first-line', '.a:first-letter', ].map(selector => expect(selectorsProcessor(`${selector} { color: red }`)).rejects.toThrow( /Detected pseudo-element/ ) ) ) }) it('throws when using unsupported pseudo-classes', () => { expect.assertions(6) return Promise.all( ['.a:matches(b)', '.a:has(b)', '.a:not(b)', '.a:lang(en)', '.a:any(b)', '.a:current'].map( selector => expect(selectorsProcessor(`${selector} { color: red }`)).rejects.toThrow( /Detected unsupported pseudo-class/ ) ) ) }) it('throws when using complex or non class selectors', async () => { expect.assertions(8) await Promise.all( ['div', '[class]', '*'].map(selector => expect(selectorsProcessor(`${selector} { color: red }`)).rejects.toThrow( /Invalid selector/ ) ) ) await Promise.all( ['.a[href]', '.a .b', '.a *', '.a > .b', '.a + .b'].map(selector => expect(selectorsProcessor(`${selector} { color: red }`)).rejects.toThrow( /Invalid selector/ ) ) ) }) it('does not throw when using state-combinator-class selectors', async () => { expect.assertions(2) await Promise.all( [':hover > .foo', ':focus + .foo'].map(selector => expect(selectorsProcessor(`${selector} { color: red }`)).resolves.toEqual( expect.objectContaining({ css: `${selector} { color: red }` }) ) ) ) }) it('does not throw when using keyframes', async () => { expect.assertions(1) expect(processor('@keyframes fade {0% { opacity:0 } 100% { opacity:1}}')).resolves.toEqual( expect.objectContaining({ css: '@keyframes fade {0% { opacity:0 } 100% { opacity:1}}' }) ) }) it('throws an error when using a shorthand property', async () => { expect(selectorsProcessor(`.a { background: red }`)).rejects.toThrow( /support shorthand properties/ ) }) it('throws an error when using !important', async () => { expect(selectorsProcessor(`.a { color: red ! important }`)).rejects.toThrow( /!important is not allowed/ ) }) }) it('moves at rules at the end of the file', async () => { const src = ` @media (min-width: 1px) { body { color: red } } div { color: red } @media (min-width: 10px) { body { color: gree } } :hover > .foo { color: red } ` const { css } = await postcss([sortAtRulesPlugin]).process(src, { from: undefined }) expect(css).toMatchSnapshot() }) }) ================================================ FILE: examples/cli/a.css ================================================ .root:hover { color: yellow; } .block { display: block; margin-top: 10px; } @media (min-width: 600px) { .root { color: green; } .test { color: green; color: yellow; } } .root { color: red; font-family: Verdana; display: block; } .test { color: red; color: pink; } ================================================ FILE: examples/cli/b.css ================================================ .root { color: blue; font-family: monospace; font-size: 2em; } @media (max-width: 400px) { .root { color: hotpink; } } @media (min-width: 600px) { .root { color: orange; } } ================================================ FILE: examples/cli/d.css ================================================ @supports (color: yellow) { .root { color: yellow; } } ================================================ FILE: examples/cli/index.html ================================================

Deterministic Style Sheets POF

================================================ FILE: examples/cli/package.json ================================================ { "name": "dss-example-vanilla", "version": "0.1.0-beta.0", "description": "", "main": "index.js", "scripts": { "start": "rm -rf dist && mkdir dist && cp -R ./index.html node_modules/dss-classnames dist && dss '*.css' dist && serve dist" }, "keywords": [], "author": "", "license": "MIT", "dependencies": { "dss-classnames": "0.1.0-beta.0" }, "devDependencies": { "dss-compiler": "0.1.0-beta.0", "serve": "^7.0.0" } } ================================================ FILE: examples/webpack3/package.json ================================================ { "name": "dss-example-webpack3", "version": "0.1.0-beta.0", "description": "", "main": "index.js", "scripts": { "start": "webpack-dev-server", "prod": "NODE_ENV=production webpack --config webpack.config.js" }, "keywords": [], "author": "", "license": "MIT", "dependencies": { "dss-classnames": "0.1.0-beta.0" }, "devDependencies": { "dss-webpack": "0.1.0-beta.0", "extract-text-webpack-plugin": "^3.0.2", "html-webpack-plugin": "^2.0.0", "webpack": "^3.0.0", "webpack-dev-server": "^2.0.0", "last-call-webpack-plugin": "^2.1.2" } } ================================================ FILE: examples/webpack3/src/a.css ================================================ .root:hover { color: yellow; } .block { display: block; margin-top: 10px; filter: blur(20px); border-top-left-radius: 5px; } @media (min-width: 600px) { .root { color: green; } .test { color: green; color: yellow; } } .root { color: red; font-family: Verdana; display: block; } .test { color: red; color: pink; } ================================================ FILE: examples/webpack3/src/b.css ================================================ .root { color: blue; font-family: monospace; font-size: 2em; } @media (max-width: 400px) { .root { color: hotpink; } } @media (min-width: 600px) { .root { color: orange; } } ================================================ FILE: examples/webpack3/src/d.css ================================================ @supports (color: yellow) { .root { color: yellow; } } ================================================ FILE: examples/webpack3/src/index.html ================================================

Deterministic Style Sheets POF

================================================ FILE: examples/webpack3/src/index.js ================================================ const classNames = require('dss-classnames') const a = require('./a.css') const b = require('./b.css') const root = document.querySelector('main') root.innerHTML = `

hello

hello

hello

` ================================================ FILE: examples/webpack3/webpack.config.js ================================================ const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') const ExtractTextPlugin = require('extract-text-webpack-plugin') const DSSWebpackPlugin = require('dss-webpack') const localIdentName = process.env.NODE_ENV === 'production' ? 'DSS-[hash:base32]' : '[name]-[local]--[hash:base32:5]' const config = { entry: path.resolve('./src/index.js'), output: { path: path.resolve('./dist'), filename: '[name].js', }, module: { rules: [ { test: /\.css$/, use: ExtractTextPlugin.extract({ use: [ { loader: DSSWebpackPlugin.loader, query: { localIdentName, }, }, ], }), }, ], }, plugins: [ new HtmlWebpackPlugin({ template: path.resolve('./src/index.html'), }), new ExtractTextPlugin('index.css'), new DSSWebpackPlugin({ test: /index\.css$/, }), ], } module.exports = config ================================================ FILE: examples/webpack4/package.json ================================================ { "name": "dss-example-webpack4", "version": "0.1.0-beta.0", "description": "", "main": "index.js", "scripts": { "start": "webpack-dev-server", "prod": "NODE_ENV=production webpack --config webpack.config.js" }, "keywords": [], "author": "", "license": "MIT", "dependencies": { "dss-classnames": "0.1.0-beta.0" }, "devDependencies": { "dss-webpack": "0.1.0-beta.0", "html-webpack-plugin": "^3.0.0", "mini-css-extract-plugin": "^0.4.0", "webpack": "^4.0.0", "webpack-cli": "^2.0.15", "webpack-dev-server": "^3.0.0", "last-call-webpack-plugin": "^3.0.0" } } ================================================ FILE: examples/webpack4/src/a.css ================================================ .root:hover { color: yellow; } .block { display: block; margin-top: 10px; filter: blur(20px); border-top-left-radius: 5px; } @media (min-width: 600px) { .root { color: green; } .test { color: green; color: yellow; } } .root { color: red; font-family: Verdana; display: block; } .test { color: red; color: pink; } ================================================ FILE: examples/webpack4/src/b.css ================================================ .root { color: blue; font-family: monospace; font-size: 2em; } @media (max-width: 400px) { .root { color: hotpink; } } @media (min-width: 600px) { .root { color: orange; } } ================================================ FILE: examples/webpack4/src/d.css ================================================ @supports (color: yellow) { .root { color: yellow; } } ================================================ FILE: examples/webpack4/src/index.html ================================================

Deterministic Style Sheets POF

================================================ FILE: examples/webpack4/src/index.js ================================================ const classNames = require('dss-classnames') const a = require('./a.css') const b = require('./b.css') const root = document.querySelector('main') root.innerHTML = `

hello

hello

hello

` ================================================ FILE: examples/webpack4/webpack.config.js ================================================ const path = require('path') const MiniCssExtractPlugin = require('mini-css-extract-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') const DSSWebpackPlugin = require('dss-webpack') const localIdentName = process.env.NODE_ENV === 'production' ? 'DSS-[hash:base32]' : '[name]-[local]--[hash:base32:5]' const mode = process.env.NODE_ENV || 'development' const config = { mode, entry: path.resolve('./src/index.js'), output: { path: path.resolve('./dist'), filename: '[name].js', }, module: { rules: [ { test: /\.css$/, use: [ MiniCssExtractPlugin.loader, { loader: DSSWebpackPlugin.loader, query: { localIdentName, }, }, ], }, ], }, plugins: [ new HtmlWebpackPlugin({ template: path.resolve('./src/index.html'), }), new MiniCssExtractPlugin({ // Options similar to the same options in webpackOptions.output // both options are optional filename: 'index.css', }), new DSSWebpackPlugin({ test: /index\.css$/, }), ], } module.exports = config ================================================ FILE: lerna.json ================================================ { "lerna": "2.11.0", "packages": [ "classnames", "compiler", "next-dss", "webpack" ], "version": "independent", "command": { "init": { "exact": true } }, "npmClient": "yarn", "useWorkspaces": true } ================================================ FILE: next-dss/LICENSE ================================================ Copyright 2018-present Giuseppe Gurgone. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: next-dss/README.md ================================================ # next-dss Deterministic Style Sheets - Next.js plugin. Read [more about this package](https://dss-lang.com/usage/#next-dss). ## Contributing This package is part of the [DSS monorepo](https://github.com/giuseppeg/dss#contributing). ## License MIT ================================================ FILE: next-dss/index.js ================================================ const DSSWebpackPlugin = require('dss-webpack') const ExtractTextPlugin = require('extract-text-webpack-plugin') const cssLoaderConfig = require('@zeit/next-css/css-loader-config') const commonsChunkConfig = require('@zeit/next-css/commons-chunk-config') const escapeStringRegexp = require('escape-string-regexp') module.exports = (nextConfig = {}) => { return Object.assign({}, nextConfig, { webpack(config, options) { if (!options.defaultLoaders) { throw new Error( 'This plugin is not compatible with Next.js versions below 5.0.0 https://err.sh/next-plugins/upgrade' ) } const { dev, isServer } = options const { dssLoaderOptions } = nextConfig // Support the user providing their own instance of ExtractTextPlugin. // If extractCSSPlugin is not defined we pass the same instance of ExtractTextPlugin to all css related modules // So that they compile to the same file in production let extractCSSPlugin = nextConfig.extractCSSPlugin || options.extractCSSPlugin const bundleName = dssLoaderOptions.filename || 'static/style.css' if (!extractCSSPlugin) { extractCSSPlugin = new ExtractTextPlugin({ filename: bundleName }) config.plugins.push(extractCSSPlugin) options.extractCSSPlugin = extractCSSPlugin if (!isServer) { config = commonsChunkConfig(config, /\.css$/) } } options.defaultLoaders.css = cssLoaderConfig(config, extractCSSPlugin, { cssModules: true, cssLoaderOptions: {}, dev, isServer: false }).map(loader => { // Replace css-loader with the dss-loader if (typeof loader.loader !== 'string' || !loader.loader.startsWith('css-loader')) { return loader } return { loader: DSSWebpackPlugin.loader, query: { localIdentName: dssLoaderOptions.localIdentName } } }) config.module.rules.push({ test: /\.css$/, use: options.defaultLoaders.css }) config.plugins.push( new DSSWebpackPlugin({ test: new RegExp(escapeStringRegexp(bundleName)) }) ) if (typeof nextConfig.webpack === 'function') { return nextConfig.webpack(config, options) } return config } }) } ================================================ FILE: next-dss/package.json ================================================ { "name": "next-dss", "version": "0.1.0-beta.0", "main": "index.js", "keywords": [ "dss", "atomic css", "css in js", "css", "classes", "css modules", "sass", "postcss", "classnames", "react", "next plugin", "next.js" ], "license": "MIT", "dependencies": { "dss-webpack": "0.1.0-beta.0", "@zeit/next-css": "0.2.0", "escape-string-regexp": "1.0.5", "extract-text-webpack-plugin": "3.0.2", "last-call-webpack-plugin": "2.1.2" } } ================================================ FILE: package.json ================================================ { "private": true, "version": "0.1.0-beta.0", "description": "Deterministic Style Sheets", "keywords": [], "author": "Giuseppe Gurgone", "license": "MIT", "workspaces": [ "classnames", "compiler", "examples/cli", "examples/webpack*", "webpack", "website" ], "scripts": { "test": "xo && cd compiler && npm test", "lint": "xo", "format": "prettier --single-quote --trailing-comma=es5 --no-semi --write all {src,test,*}/**/*.js", "clean": "rm -rf node_modules **/node_modules **/**/node_modules **/dist **/**/dist website/.next website/out" }, "devDependencies": { "lerna": "2.11.0", "prettier": "^1.11.1", "push-dir": "^0.4.1", "xo": "^0.20.3" }, "xo": { "envs": [ "node", "browser" ], "extends": [ "prettier" ], "ignores": [ "compiler/src/vendor", "examples", "website" ], "rules": { "capitalized-comments": 0, "unicorn/import-index": 0 }, "globals": [ "describe", "it", "expect" ] } } ================================================ FILE: release-website ================================================ #!/usr/bin/env bash rm -rf out cd website && yarn export && touch out/.nojekyll && touch out/CNAME && echo "dss-lang.com" >> out/CNAME && ../node_modules/.bin/push-dir --dir=out --branch=gh-pages ================================================ FILE: webpack/LICENSE ================================================ Copyright 2018-present Giuseppe Gurgone. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: webpack/README.md ================================================ # dss-webpack Deterministic Style Sheets - webpack loader and plugin for webpack 3 and 4. Read [more about this package](https://dss-lang.com/usage/#dss-webpack). ## Contributing This package is part of the [DSS monorepo](https://github.com/giuseppeg/dss#contributing). ## License MIT ================================================ FILE: webpack/index.js ================================================ const optimizer = require('dss-compiler/processor').optimizer let LastCallWebpackPlugin try { LastCallWebpackPlugin = require('last-call-webpack-plugin') } catch (error) { if (/cannot find module/i.test(error.message)) { throw new Error(`DSSWebpackPlugin depends on last-call-webpack-plugin. Are you Webpack 3 user? Please install last-call-webpack-plugin@^2.0.0 as devDependency. Are you Webpack 4 user? Please install last-call-webpack-plugin@^3.0.0 as devDependency. `) } throw error } const PHASES = "PHASE" in LastCallWebpackPlugin ? "PHASE" : "PHASES"; function processor(assetName, asset) { const css = asset.source() return optimizer(css, { from: assetName, to: assetName }).then(result => result.css) } class DSSPlugin extends LastCallWebpackPlugin { constructor(options = { canPrint: false }) { super({ assetProcessors: [ { phase: LastCallWebpackPlugin[PHASES].OPTIMIZE_ASSETS, regExp: options.test || /\.css$/g, processor }, { phase: LastCallWebpackPlugin[PHASES].OPTIMIZE_CHUNK_ASSETS, regExp: options.test || /\.css$/g, processor } ], canPrint: options.canPrint }) } buildPluginDescriptor() { return { name: 'DSSWebpackPlugin' } } } DSSPlugin.loader = require.resolve('./loader') module.exports = DSSPlugin ================================================ FILE: webpack/loader.js ================================================ const loaderUtils = require('loader-utils') const dss = require('dss-compiler') const BANNER = '/* DSS file */' module.exports = function(content) { if (this.cacheable) this.cacheable() this.addDependency(this.resourcePath) const callback = this.async() const options = loaderUtils.getOptions(this) || {} let readableClass if (typeof options.localIdentName === 'string') { const identName = loaderUtils.interpolateName(this, options.localIdentName, { content }) readableClass = localName => identName.replace(/\[local]/g, localName) } dss(content, { readableClass }) .then(({ locals, flush }) => { const moduleExports = [ BANNER, `exports = module.exports = [[module.id, "${flush()}", ""]];`, `exports.locals = ${JSON.stringify(locals)}` ].join('\n') callback(null, moduleExports) }) .catch(callback) } ================================================ FILE: webpack/package.json ================================================ { "name": "dss-webpack", "version": "0.1.0-beta.0", "description": "Deterministic Style Sheets - webpack plugin and loader", "main": "index.js", "keywords": [ "dss", "atomic css", "css in js", "css", "classes", "css modules", "sass", "postcss", "classnames", "webpack", "react" ], "author": "Giuseppe Gurgone", "license": "MIT", "dependencies": { "dss-compiler": "0.1.0-beta.0", "loader-utils": "1.1.0" }, "peerDependencies": { "last-call-webpack-plugin": "^2.0.0 || ^3.0.0" } } ================================================ FILE: website/.babelrc ================================================ { "presets": ["next/babel"], "plugins": [["babel-plugin-classnames", { "packageName": "dss-classnames"}]] } ================================================ FILE: website/LICENSE ================================================ Copyright 2018-present Giuseppe Gurgone. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: website/components/analytics/index.js ================================================ import React from 'react' import ReactGA from 'react-ga' export default class Analytics extends React.Component { track = () => { ReactGA.set({ page: this.props.route + window.location.hash }) ReactGA.pageview(this.props.route + window.location.hash) } componentDidMount() { ReactGA.initialize(this.props.id) this.track() window.addEventListener('hashchange', this.track) } componentDidUpdate(prevProps) { if (prevProps.route !== this.props.route) { this.track() } } componentWillUnmount() { window.removeEventListener('hashchange', this.track) } render() { return this.props.children } } ================================================ FILE: website/components/body/index.css ================================================ .root { margin-top: 0; margin-bottom: 0; margin-left: 0; margin-right: 0; padding-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; display: flex; flex-direction: column; } ================================================ FILE: website/components/body/index.js ================================================ import styles from './index.css' export default ({children}) => {children} ================================================ FILE: website/components/heading/index.css ================================================ .tag { display: block; margin-top: 0; margin-bottom: 0; margin-left: 0; margin-right: 0; padding-top: 0; padding-bottom: 0; padding-left: 0; padding-right: 0; font-family: $HeadingFontFamily; font-size: $HeadingFontSize; font-weight: $HeadingFontWeight; color: $HeadingColor; } .content { display: block; margin-top: 2em; } .link { color: inherit; } .size1 { font-size: $u-fontSize1; margin-top: 0; } .size2 { font-size: $u-fontSize2; } .size3 { font-size: $u-fontSize3; } .size4 { font-size: $u-fontSize4; } .size5 { font-size: $u-fontSize5; } .size6 { font-size: $u-fontSize6; } ================================================ FILE: website/components/heading/index.js ================================================ import Link from '../link' import styles from './index.css' const defaultClassName = { link: [], tag: [], content: [], } const Wrap = ({children, className, href, target}) => { if (!href) { return children } return {children} } export default ({children, className = defaultClassName, level = 1, size = 1, href = null, target = '_self', autolink = true}) => { const Tag = `h${level}` if (!href && autolink && level > 1) { href = '#' + children.toLowerCase().replace(/[^a-z]/g, '-') } return ( {children} ) } ================================================ FILE: website/components/layout/index.css ================================================ .container { display: flex; flex-wrap: wrap; border-top-width: $SideBarSpacing; border-top-style: solid; border-top-color: $SideBarBackgroundColor; min-height: 100vh; } .side { display: flex; flex-direction: row-reverse; justify-content: space-between; width: 100%; box-sizing: border-box; padding-top: $SideBarSpacing; padding-bottom: $SideBarSpacing; padding-left: $SideBarSpacing; padding-right: $SideBarSpacing; background-color: $SideBarBackgroundColor; color: $SideBarColor; max-width: 100%; } .sideOpen { background-color: $SideBarStateBackgroundColor; } .logo { font-size: $LogoSize; } .spacer { height: $SideBarSpacing; } .navigationButton { margin-top: $SideBarNavigationSpacing; margin-bottom: $SideBarNavigationSpacing; margin-left: 0; margin-right: 0; } .playground { margin-top: $u-spacing1; } .main { flex-basis: 0%; flex-grow: 1; flex-shrink: 1; background-color: $MainBackgroundColor; max-width: 100%; } .mainContent { background-color: $MainContentBackgroundColor; color: $MainColor; max-width: 960px; min-height: 100%; padding-top: $SideBarSpacing; padding-bottom: $SideBarSpacing; padding-left: $MainSpacing; padding-right: $MainSpacing; } .starOnGithub { width: 140px; height: 20px; } @media (min-width: 680px) { .container { flex-wrap: nowrap; } .side { width: auto; flex-direction: column; align-items: center; justify-content: flex-start; } } @media (min-width: 1025px) { .mainContent { position: relative; padding-top: calc($LogoSize + $SideBarSpacing * 2); padding-bottom: calc($LogoSize + $SideBarSpacing * 2); padding-left: calc(($LogoSize + $SideBarSpacing * 2) * 0.9); padding-right: calc(($LogoSize + $SideBarSpacing * 2) * 0.9); } .starOnGithub { position: absolute; top: $SideBarSpacing; left: 50%; } } ================================================ FILE: website/components/layout/index.js ================================================ import styles from './index.css' import Link from '../link' import Head from 'next/head' import Logo from '../logo' import Heading from '../heading' import Navigation from '../navigation' import Playground from '../playground' import { LogoColor, LogoBackground } from '../../theme' export default class Layout extends React.Component { media = typeof window !== 'undefined' ? window.matchMedia('(min-width: 1150px)') : null onMedia = ({matches}) => { if (!this.state.isNavigationOpen && matches) { this.setState({isNavigationOpen: true}) } if (this.state.isNavigationOpen && !matches) { this.setState({isNavigationOpen: false}) } } state = { isNavigationOpen: false } toggle = e => { e.preventDefault() this.setState(({isNavigationOpen}) => ({ isNavigationOpen: !isNavigationOpen })) } componentDidMount() { this.media = window.matchMedia('(min-width: 1150px)') this.media.addListener(this.onMedia) this.onMedia({ matches: this.media.matches }) } componentWillUnmount() { this.media.removeListener(this.onMedia) } render() { const { children, title = 'Deterministic StyleSheets' } = this.props const { isNavigationOpen } = this.state return ( { `DSS | ${title}` }