├── .gitattributes ├── .gitignore ├── .prettierrc ├── lib ├── sheet │ ├── browser.d.ts │ ├── browser.js │ ├── server.d.ts │ └── server.js ├── parse.d.ts ├── index.d.ts ├── index.js └── parse.js ├── src ├── sheet │ ├── browser.ts │ └── server.ts ├── parse.ts └── index.ts ├── .editorconfig ├── jest.config.js ├── test ├── __snapshots__ │ └── index.test.ts.snap └── index.test.ts ├── circle.yml ├── rollup.config.js ├── LICENSE ├── dist ├── style-module.min.js └── style-module.js ├── package.json ├── README.md └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "bracketSpacing": true 5 | } 6 | -------------------------------------------------------------------------------- /lib/sheet/browser.d.ts: -------------------------------------------------------------------------------- 1 | declare const _default: () => StyleSheet | null; 2 | export default _default; 3 | -------------------------------------------------------------------------------- /src/sheet/browser.ts: -------------------------------------------------------------------------------- 1 | export default () => 2 | typeof document === 'undefined' 3 | ? null 4 | : document.head.appendChild(document.createElement('style')).sheet 5 | -------------------------------------------------------------------------------- /lib/sheet/browser.js: -------------------------------------------------------------------------------- 1 | export default (function () { 2 | return typeof document === 'undefined' 3 | ? null 4 | : document.head.appendChild(document.createElement('style')).sheet; 5 | }); 6 | -------------------------------------------------------------------------------- /lib/sheet/server.d.ts: -------------------------------------------------------------------------------- 1 | declare const _default: () => StyleSheet | { 2 | insertRule(rule: string, position: number): void; 3 | cssRules: { 4 | [k: string]: any; 5 | }; 6 | } | null; 7 | export default _default; 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /lib/parse.d.ts: -------------------------------------------------------------------------------- 1 | interface RuleObj { 2 | [k: string]: string | RuleObj; 3 | } 4 | declare function wrap(stringToWrap: string, wrapper: string): string; 5 | declare function parse(obj: RuleObj, className: string, isInsideObj?: boolean): string[]; 6 | export { RuleObj, parse, wrap }; 7 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'jsdom', 3 | transform: { 4 | '^.+\\.tsx?$': 'ts-jest' 5 | }, 6 | testRegex: '(/__test__/.*|(\\.|/)(test|spec))\\.tsx?$', 7 | testPathIgnorePatterns: ['/node_modules/', '/dist/', '/types/'], 8 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'] 9 | } 10 | -------------------------------------------------------------------------------- /src/sheet/server.ts: -------------------------------------------------------------------------------- 1 | import createSheet from './browser' 2 | 3 | export default () => { 4 | if (typeof document !== 'undefined') return createSheet() 5 | 6 | const rules: { [k: string]: any } = {} 7 | return { 8 | insertRule(rule: string, position: number) { 9 | rules[position] = { 10 | cssText: rule 11 | } 12 | }, 13 | cssRules: rules 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/__snapshots__/index.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`css composes 1`] = `".sm_0 {color: black;}.sm_1 {color: red;}"`; 4 | 5 | exports[`css simple 1`] = `".sm_0 {color: red; font-size: 16px;}.sm_0:hover {color: blue;}.sm_0 .foo {color: yellow;}"`; 6 | 7 | exports[`styleModule 1`] = `".sm_0 {color: red; font-size: 16px;}.sm_0:hover {color: blue;}.sm_0 .foo {color: yellow;}"`; 8 | -------------------------------------------------------------------------------- /lib/sheet/server.js: -------------------------------------------------------------------------------- 1 | import createSheet from './browser'; 2 | export default (function () { 3 | if (typeof document !== 'undefined') 4 | return createSheet(); 5 | var rules = {}; 6 | return { 7 | insertRule: function (rule, position) { 8 | rules[position] = { 9 | cssText: rule 10 | }; 11 | }, 12 | cssRules: rules 13 | }; 14 | }); 15 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | import { RuleObj } from './parse'; 2 | declare let sheet: StyleSheet | { 3 | insertRule(rule: string, position: number): void; 4 | cssRules: { 5 | [k: string]: any; 6 | }; 7 | } | null; 8 | declare function reset(): void; 9 | declare function css(obj: RuleObj): string; 10 | interface StyleModuleInput { 11 | [k: string]: RuleObj; 12 | } 13 | interface StyleModuleOutput { 14 | [k: string]: string; 15 | } 16 | declare function styleModule(obj: StyleModuleInput | (() => StyleModuleInput)): StyleModuleOutput; 17 | declare function keyframes(obj: { 18 | [k: string]: { 19 | [k: string]: any; 20 | }; 21 | }): string; 22 | export { css, styleModule, sheet, reset, keyframes }; 23 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:latest 6 | branches: 7 | ignore: 8 | - gh-pages # list of branches to ignore 9 | - /release\/.*/ # or ignore regexes 10 | steps: 11 | - checkout 12 | - restore_cache: 13 | key: dependency-cache-{{ checksum "yarn.lock" }} 14 | - run: 15 | name: install dependences 16 | command: yarn 17 | - save_cache: 18 | key: dependency-cache-{{ checksum "yarn.lock" }} 19 | paths: 20 | - ./node_modules 21 | - run: 22 | name: test 23 | command: yarn test 24 | - run: 25 | name: Release 26 | command: yarn semantic-release 27 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | const createConfig = ({ format, minify }) => { 2 | let ext = format === 'cjs' ? '.cjs.js' : format === 'es' ? '.mjs' : '.js' 3 | if (minify) { 4 | ext = ext.replace(/\.([a-z]+)$/, '.min.$1') 5 | } 6 | return { 7 | input: 'src/index.ts', 8 | output: { 9 | format: format, 10 | file: `dist/style-module${ext}`, 11 | name: 'styleModule' 12 | }, 13 | plugins: [ 14 | require('rollup-plugin-alias')({ 15 | resolve: ['.ts', '.js'], 16 | './sheet/server': './sheet/browser' 17 | }), 18 | require('rollup-plugin-typescript')({ 19 | declaration: false 20 | }), 21 | minify && require('rollup-plugin-terser').terser() 22 | ].filter(Boolean) 23 | } 24 | } 25 | 26 | export default [ 27 | createConfig({ format: 'umd' }), 28 | createConfig({ format: 'umd', minify: true }) 29 | ] 30 | -------------------------------------------------------------------------------- /src/parse.ts: -------------------------------------------------------------------------------- 1 | interface RuleObj { 2 | [k: string]: string | RuleObj 3 | } 4 | 5 | function kebabcase(str: string) { 6 | return str.replace(/[A-Z]/g, '-$&').toLowerCase() 7 | } 8 | 9 | function wrap(stringToWrap: string, wrapper: string) { 10 | return wrapper + '{' + stringToWrap + '}' 11 | } 12 | 13 | function parse(obj: RuleObj, className: string, isInsideObj = false) { 14 | const rules = [''] 15 | 16 | for (let prop of Object.keys(obj)) { 17 | let value = obj[prop] 18 | 19 | if (prop === 'composes') { 20 | continue 21 | } 22 | 23 | prop = kebabcase(prop) 24 | if (typeof value === 'object') { 25 | if (/^(:|>|\.|\*)/.test(prop)) { 26 | prop = className + prop 27 | } 28 | // replace & in "&:hover", "p>&" 29 | prop = prop.replace(/&/g, className) 30 | const res = parse(value, className, !/^@/.test(prop)) 31 | rules.push(wrap(res.join(''), prop)) 32 | } else { 33 | rules[0] += `${prop}:${value};` 34 | } 35 | } 36 | if (!isInsideObj) { 37 | rules[0] = wrap(rules[0], className) 38 | } 39 | return rules 40 | } 41 | 42 | export { RuleObj, parse, wrap } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) EGOIST <0x142857@gmail.com> (https://egoist.sh) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | import createSheet from './sheet/server'; 2 | import { parse, wrap } from './parse'; 3 | var _id = 0; 4 | var sheet = createSheet(); 5 | function reset() { 6 | _id = 0; 7 | sheet = createSheet(); 8 | } 9 | reset(); 10 | function insert(rule) { 11 | if (sheet instanceof CSSStyleSheet) { 12 | sheet.insertRule(rule, sheet.cssRules.length); 13 | } 14 | } 15 | function css(obj) { 16 | var id = "sm_" + _id++; 17 | var rules = parse(obj, "." + id); 18 | for (var _i = 0, rules_1 = rules; _i < rules_1.length; _i++) { 19 | var rule = rules_1[_i]; 20 | insert(rule); 21 | } 22 | if (obj.composes) { 23 | return obj.composes + " " + id; 24 | } 25 | return id; 26 | } 27 | function styleModule(obj) { 28 | var decl = typeof obj === 'function' ? obj() : obj; 29 | return Object.keys(decl).reduce(function (res, className) { 30 | res[className] = css(decl[className]); 31 | return res; 32 | }, {}); 33 | } 34 | function keyframes(obj) { 35 | var id = "sm_" + _id++; 36 | insert(wrap(parse(obj, id, true).join(''), '@keyframes ' + id)); 37 | return id; 38 | } 39 | export { css, styleModule, sheet, reset, keyframes }; 40 | -------------------------------------------------------------------------------- /lib/parse.js: -------------------------------------------------------------------------------- 1 | function kebabcase(str) { 2 | return str.replace(/[A-Z]/g, '-$&').toLowerCase(); 3 | } 4 | function wrap(stringToWrap, wrapper) { 5 | return wrapper + '{' + stringToWrap + '}'; 6 | } 7 | function parse(obj, className, isInsideObj) { 8 | if (isInsideObj === void 0) { isInsideObj = false; } 9 | var rules = ['']; 10 | for (var _i = 0, _a = Object.keys(obj); _i < _a.length; _i++) { 11 | var prop = _a[_i]; 12 | var value = obj[prop]; 13 | if (prop === 'composes') { 14 | continue; 15 | } 16 | prop = kebabcase(prop); 17 | if (typeof value === 'object') { 18 | if (/^(:|>|\.|\*)/.test(prop)) { 19 | prop = className + prop; 20 | } 21 | // replace & in "&:hover", "p>&" 22 | prop = prop.replace(/&/g, className); 23 | var res = parse(value, className, !/^@/.test(prop)); 24 | rules.push(wrap(res.join(''), prop)); 25 | } 26 | else { 27 | rules[0] += prop + ":" + value + ";"; 28 | } 29 | } 30 | if (!isInsideObj) { 31 | rules[0] = wrap(rules[0], className); 32 | } 33 | return rules; 34 | } 35 | export { parse, wrap }; 36 | -------------------------------------------------------------------------------- /dist/style-module.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e=e||self).styleModule={})}(this,function(e){"use strict";var t=function(){return"undefined"==typeof document?null:document.head.appendChild(document.createElement("style")).sheet};function n(e,t){return t+"{"+e+"}"}function o(e,t,s){void 0===s&&(s=!1);for(var r=[""],u=0,f=Object.keys(e);u|\.|\*)/.test(c)&&(c=t+c),c=c.replace(/&/g,t);var l=o(i,t,!/^@/.test(c));r.push(n(l.join(""),c))}else r[0]+=c+":"+i+";"}return s||(r[0]=n(r[0],t)),r}var s=0;function r(){s=0,e.sheet=t()}function u(t){e.sheet instanceof CSSStyleSheet&&e.sheet.insertRule(t,e.sheet.cssRules.length)}function f(e){for(var t="sm_"+s++,n=0,r=o(e,"."+t);n StyleModuleInput) 42 | ): StyleModuleOutput { 43 | const decl = typeof obj === 'function' ? obj() : obj 44 | return Object.keys(decl).reduce((res: StyleModuleOutput, className) => { 45 | res[className] = css(decl[className]) 46 | return res 47 | }, {}) 48 | } 49 | 50 | function keyframes(obj: { [k: string]: { [k: string]: any } }) { 51 | const id = `sm_${_id++}` 52 | insert(wrap(parse(obj, id, true).join(''), '@keyframes ' + id)) 53 | return id 54 | } 55 | 56 | export { css, styleModule, sheet, reset, keyframes } 57 | -------------------------------------------------------------------------------- /test/index.test.ts: -------------------------------------------------------------------------------- 1 | import { css, sheet, styleModule, reset } from '../src' 2 | 3 | const getStyle = () => { 4 | let res = '' 5 | if (sheet instanceof CSSStyleSheet) { 6 | for (let i = 0; i < sheet.cssRules.length; i++) { 7 | res += sheet.cssRules[i].cssText 8 | } 9 | } 10 | return res 11 | } 12 | 13 | beforeEach(() => { 14 | reset() 15 | }) 16 | 17 | describe('css', () => { 18 | test('simple', () => { 19 | expect( 20 | css({ 21 | color: 'red', 22 | fontSize: '16px', 23 | ':hover': { 24 | color: 'blue' 25 | }, 26 | '& .foo': { 27 | color: 'yellow' 28 | } 29 | }) 30 | ).toBe('sm_0') 31 | expect(getStyle()).toMatchSnapshot() 32 | }) 33 | 34 | test('composes', () => { 35 | const black = css({ 36 | color: 'black' 37 | }) 38 | const className = css({ 39 | composes: black, 40 | color: 'red' 41 | }) 42 | expect(className).toBe('sm_0 sm_1') 43 | expect(getStyle()).toMatchSnapshot() 44 | }) 45 | 46 | // test('nesting', () => { 47 | // const className = css({ 48 | // '& .button': { 49 | // color: 'pink', 50 | // ':hover': { 51 | // color: 'red' 52 | // } 53 | // } 54 | // }) 55 | // expect(className).toBe('sm_0') 56 | // expect(getStyle()).toMatchSnapshot() 57 | // }) 58 | }) 59 | 60 | test('styleModule', () => { 61 | const styles = styleModule({ 62 | button: { 63 | color: 'red', 64 | fontSize: '16px', 65 | ':hover': { 66 | color: 'blue' 67 | }, 68 | '& .foo': { 69 | color: 'yellow' 70 | } 71 | } 72 | }) 73 | expect(styles.button).toBe('sm_0') 74 | expect(getStyle()).toMatchSnapshot() 75 | }) 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "style-module", 3 | "version": "0.0.0-managed-by-semantic-release", 4 | "description": "CSS modules in JS.", 5 | "main": "dist/style-module.js", 6 | "module": "lib/index.js", 7 | "types": "lib/index.d.ts", 8 | "files": [ 9 | "dist", 10 | "lib" 11 | ], 12 | "browser": { 13 | "./sheet/server": "./sheet/browser" 14 | }, 15 | "scripts": { 16 | "test": "npm run test:unit", 17 | "test:unit": "jest -i", 18 | "build": "npm run build:umd && npm run build:es", 19 | "build:umd": "rollup -c", 20 | "build:es": "tsc", 21 | "prepublishOnly": "npm run build", 22 | "commit": "git-cz" 23 | }, 24 | "repository": { 25 | "url": "egoist/style-module", 26 | "type": "git" 27 | }, 28 | "author": "egoist<0x142857@gmail.com>", 29 | "license": "MIT", 30 | "dependencies": {}, 31 | "devDependencies": { 32 | "@types/jest": "^23.3.11", 33 | "commitizen": "^3.0.5", 34 | "cz-conventional-changelog": "^2.1.0", 35 | "husky": "^1.0.0-rc.13", 36 | "jest": "^23.6.0", 37 | "lint-staged": "^7.2.0", 38 | "prettier": "^1.15.2", 39 | "rollup": "^1.0.1", 40 | "rollup-plugin-alias": "^1.5.1", 41 | "rollup-plugin-terser": "^4.0.1", 42 | "rollup-plugin-typescript": "^1.0.0", 43 | "semantic-release": "^15.13.2", 44 | "ts-jest": "^23.10.5", 45 | "typescript": "^3.2.2" 46 | }, 47 | "husky": { 48 | "hooks": { 49 | "pre-commit": "lint-staged" 50 | } 51 | }, 52 | "lint-staged": { 53 | "{src,test}/**/*.{ts,js}": [ 54 | "prettier --write", 55 | "git add" 56 | ], 57 | "*.{json,md}": [ 58 | "prettier --write", 59 | "git add" 60 | ], 61 | "src/**/*.ts": [ 62 | "npm run build", 63 | "git add" 64 | ] 65 | }, 66 | "config": { 67 | "commitizen": { 68 | "path": "cz-conventional-changelog" 69 | } 70 | }, 71 | "release": { 72 | "branch": "master" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /dist/style-module.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : 3 | typeof define === 'function' && define.amd ? define(['exports'], factory) : 4 | (global = global || self, factory(global.styleModule = {})); 5 | }(this, function (exports) { 'use strict'; 6 | 7 | var createSheet = (function () { 8 | return typeof document === 'undefined' 9 | ? null 10 | : document.head.appendChild(document.createElement('style')).sheet; 11 | }); 12 | 13 | function kebabcase(str) { 14 | return str.replace(/[A-Z]/g, '-$&').toLowerCase(); 15 | } 16 | function wrap(stringToWrap, wrapper) { 17 | return wrapper + '{' + stringToWrap + '}'; 18 | } 19 | function parse(obj, className, isInsideObj) { 20 | if (isInsideObj === void 0) { isInsideObj = false; } 21 | var rules = ['']; 22 | for (var _i = 0, _a = Object.keys(obj); _i < _a.length; _i++) { 23 | var prop = _a[_i]; 24 | var value = obj[prop]; 25 | if (prop === 'composes') { 26 | continue; 27 | } 28 | prop = kebabcase(prop); 29 | if (typeof value === 'object') { 30 | if (/^(:|>|\.|\*)/.test(prop)) { 31 | prop = className + prop; 32 | } 33 | // replace & in "&:hover", "p>&" 34 | prop = prop.replace(/&/g, className); 35 | var res = parse(value, className, !/^@/.test(prop)); 36 | rules.push(wrap(res.join(''), prop)); 37 | } 38 | else { 39 | rules[0] += prop + ":" + value + ";"; 40 | } 41 | } 42 | if (!isInsideObj) { 43 | rules[0] = wrap(rules[0], className); 44 | } 45 | return rules; 46 | } 47 | 48 | var _id = 0; 49 | exports.sheet = createSheet(); 50 | function reset() { 51 | _id = 0; 52 | exports.sheet = createSheet(); 53 | } 54 | reset(); 55 | function insert(rule) { 56 | if (exports.sheet instanceof CSSStyleSheet) { 57 | exports.sheet.insertRule(rule, exports.sheet.cssRules.length); 58 | } 59 | } 60 | function css(obj) { 61 | var id = "sm_" + _id++; 62 | var rules = parse(obj, "." + id); 63 | for (var _i = 0, rules_1 = rules; _i < rules_1.length; _i++) { 64 | var rule = rules_1[_i]; 65 | insert(rule); 66 | } 67 | if (obj.composes) { 68 | return obj.composes + " " + id; 69 | } 70 | return id; 71 | } 72 | function styleModule(obj) { 73 | var decl = typeof obj === 'function' ? obj() : obj; 74 | return Object.keys(decl).reduce(function (res, className) { 75 | res[className] = css(decl[className]); 76 | return res; 77 | }, {}); 78 | } 79 | function keyframes(obj) { 80 | var id = "sm_" + _id++; 81 | insert(wrap(parse(obj, id, true).join(''), '@keyframes ' + id)); 82 | return id; 83 | } 84 | 85 | exports.css = css; 86 | exports.styleModule = styleModule; 87 | exports.reset = reset; 88 | exports.keyframes = keyframes; 89 | 90 | Object.defineProperty(exports, '__esModule', { value: true }); 91 | 92 | })); 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # style-module 2 | 3 | [![NPM version](https://badgen.net/npm/v/style-module)](https://npmjs.com/package/style-module) [![NPM downloads](https://badgen.net/npm/dm/style-module)](https://npmjs.com/package/style-module) [![CircleCI](https://badgen.net/circleci/github/egoist/style-module/master)](https://circleci.com/gh/egoist/style-module/tree/master) [![donate](https://badgen.net/badge/support%20me/donate/ff69b4)](https://patreon.com/egoist) [![chat](https://badgen.net/badge/chat%20on/discord/7289DA)](https://chat.egoist.moe) 4 | 5 | **Please consider [donating](https://www.patreon.com/egoist) to this project's author, [EGOIST](https://egoist.sh), to show your ❤️ and support.** 6 | 7 | ## Install 8 | 9 | ```bash 10 | npm i style-module 11 | ``` 12 | 13 |
Use UMD bundle from CDN 14 | 15 | ```html 16 | 17 | 26 | ``` 27 | 28 |
29 | 30 |
Use ESM bundle from CDN 31 | 32 | ```html 33 | 42 | ``` 43 | 44 |
45 | 46 | ## Usage 47 | 48 | Check out the live demo on [CodePan](https://codepan.net/gist/bfcdf733f6099fff24080d039569c9aa). 49 | 50 | ```js 51 | import { styleModule } from 'style-module' 52 | 53 | const buttonStyles = styleModule({ 54 | button: { 55 | color: 'red', 56 | ':hover': { 57 | color: 'blue' 58 | } 59 | } 60 | }) 61 | 62 | const styles = styleModule({ 63 | main: { 64 | font: '14px/1.4 helvetica', 65 | backgroundColor: 'red' 66 | }, 67 | button: { 68 | // Composes (i.e. inherits) all the styles from buttonStyles.button 69 | composes: buttonStyles.button, 70 | color: 'pink' 71 | } 72 | }) 73 | 74 | // Generated class names 75 | console.log(styles) 76 | //=> 77 | { 78 | main: 'sm_2', 79 | button: 'sm_0 sm_3' 80 | } 81 | ``` 82 | 83 | ### Composes 84 | 85 | Composes (i.e. inherits) all styles from an existing class name which is usually generated by `css` or `styleModule` function: 86 | 87 | ```js 88 | import { css } from 'styleModule' 89 | 90 | const defaultButton = css({ 91 | border: '1px solid #ccc' 92 | }) 93 | 94 | const primaryButton = css({ 95 | composes: defaultButton, 96 | color: 'red', 97 | ':hover': { 98 | color: 'pink' 99 | } 100 | }) 101 | ``` 102 | 103 | Notes: 104 | 105 | - `composes` currently only works at the top level. 106 | - It's possible to compose multiple classes with composes: `${classNameA} ${classNameB}` 107 | 108 | ### Keyframes 109 | 110 | Check out the live demo on [CodePan](https://codepan.net/gist/3d965f020974fdbbc3aaaacf8c7cefda). 111 | 112 | ```js 113 | import { css, keyframes } from 'style-module' 114 | 115 | const zoom = keyframes({ 116 | from: { 117 | transform: 'scale(0.5)' 118 | }, 119 | to: { 120 | transform: 'scale(2)' 121 | } 122 | }) 123 | 124 | const className = css({ 125 | animation: `${zoom} 300ms infinite` 126 | }) 127 | ``` 128 | 129 | ## API 130 | 131 | ### css 132 | 133 | Generate class name for a style record: 134 | 135 | ```js 136 | import { css } from 'styleModule' 137 | 138 | const className = css({ 139 | color: 'red', 140 | ':hover': { 141 | color: 'black' 142 | } 143 | }) 144 | 145 | className //=> sm_0 146 | ``` 147 | 148 | ### styleModule 149 | 150 | Generate class names for _multiple style records (a module)_: 151 | 152 | ```js 153 | import { styleModule } from 'styleModule' 154 | 155 | const styles = styleModule({ 156 | button: { 157 | color: 'red', 158 | ':hover': { 159 | color: 'black' 160 | } 161 | } 162 | }) 163 | 164 | styles.button //=> .sm_0 165 | ``` 166 | 167 | `styleModule` is just a short-hand for generating multiple class names at once, internally it calls `css` function too. 168 | 169 | ### keyframes 170 | 171 | Create a `@keyframes` rule, you should pass in the definition of the rule and it returns the name. 172 | 173 | ## TODO 174 | 175 | - [ ] Atomic CSS 176 | 177 | ## Contributing 178 | 179 | 1. Fork it! 180 | 2. Create your feature branch: `git checkout -b my-new-feature` 181 | 3. Commit your changes: `git commit -am 'Add some feature'` 182 | 4. Push to the branch: `git push origin my-new-feature` 183 | 5. Submit a pull request :D 184 | 185 | ## Author 186 | 187 | **style-module** © EGOIST, Released under the [MIT](./LICENSE) License.
188 | Authored and maintained by EGOIST with help from contributors ([list](https://github.com/egoist/style-module/contributors)). 189 | 190 | > [Website](https://egoist.sh) · GitHub [@EGOIST](https://github.com/egoist) · Twitter [@\_egoistlily](https://twitter.com/_egoistlily) 191 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */, 5 | "module": "esnext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 6 | "lib": [ 7 | "dom", 8 | "es2015" 9 | ] /* Specify library files to be included in the compilation. */, 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "lib" /* Redirect output structure to the directory. */, 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "removeComments": true, /* Do not emit comments to output. */ 21 | // "noEmit": true, /* Do not emit outputs. */ 22 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 23 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 24 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 25 | 26 | /* Strict Type-Checking Options */ 27 | "strict": true /* Enable all strict type-checking options. */, 28 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 29 | // "strictNullChecks": true, /* Enable strict null checks. */ 30 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 31 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 32 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 33 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 34 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 35 | 36 | /* Additional Checks */ 37 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 38 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 39 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 40 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 41 | 42 | /* Module Resolution Options */ 43 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 44 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 45 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 46 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 47 | // "typeRoots": [], /* List of folders to include type definitions from. */ 48 | // "types": [], /* Type declaration files to be included in compilation. */ 49 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 50 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 51 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | }, 63 | "include": ["src"] 64 | } 65 | --------------------------------------------------------------------------------