├── .demos └── autocomplete.gif ├── .github └── workflows │ ├── prerelease.yml │ ├── release-ts-classnames.yml │ └── release.yml ├── .gitignore ├── .prettierrc ├── .vscode ├── launch.json └── settings.json ├── README.md ├── __tests__ └── postcss-plugin.test.ts ├── index.js ├── jest.config.js ├── package.json ├── postcss.js ├── src ├── class-name-collector.ts └── plugin.ts ├── ts-classnames ├── README.md ├── index.d.ts ├── index.js ├── package-lock.json └── package.json ├── tsconfig.build.json ├── tsconfig.dtslint.json └── tsconfig.json /.demos/autocomplete.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esamattis/postcss-ts-classnames/aed937a57123971da6b4992f34d86ef7d54a505a/.demos/autocomplete.gif -------------------------------------------------------------------------------- /.github/workflows/prerelease.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | paths: 6 | - src/* 7 | - package.json 8 | 9 | 10 | jobs: 11 | prerelease: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Make prerelease to npm 16 | uses: epeli/npm-release@v1 17 | with: 18 | type: prerelease 19 | token: ${{ secrets.NPM_TOKEN }} 20 | -------------------------------------------------------------------------------- /.github/workflows/release-ts-classnames.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | paths: 6 | - ts-classnames/package.json 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v1 13 | - name: Make stable release to npm 14 | uses: epeli/npm-release@v1 15 | with: 16 | type: stable 17 | dir: ts-classnames 18 | token: ${{ secrets.NPM_TOKEN }} 19 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | release: 3 | types: [published] 4 | 5 | jobs: 6 | prerelease: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | - name: Make stable release to npm 11 | uses: epeli/npm-release@v1 12 | with: 13 | type: stable 14 | token: ${{ secrets.NPM_TOKEN }} 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /dist 3 | /package-lock.json -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "tabWidth": 4 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Jest All", 8 | "program": "${workspaceFolder}/node_modules/.bin/jest", 9 | "windows": { 10 | "program": "${workspaceFolder}/node_modules/jest/bin/jest" 11 | }, 12 | "args": ["--runInBand"], 13 | "console": "internalConsole", 14 | "internalConsoleOptions": "openOnSessionStart" 15 | }, 16 | { 17 | "type": "node", 18 | "request": "launch", 19 | "name": "Jest Current File", 20 | "program": "${workspaceFolder}/node_modules/.bin/jest", 21 | "windows": { 22 | "program": "${workspaceFolder}/node_modules/jest/bin/jest", 23 | "args": ["--testMatch", "**/__tests__/**/${fileBasename}"] 24 | }, 25 | "args": ["${relativeFile}"], 26 | "console": "internalConsole", 27 | "internalConsoleOptions": "openOnSessionStart" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "dist": true, 4 | "node_modules": true 5 | } 6 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # postcss-ts-classnames 2 | 3 | [PostCSS][] plugin to generate TypeScript types from **your** CSS class names. 4 | 5 | [postcss]: https://postcss.org/ 6 | 7 | It generates a global `ClassNames` type which is a union of all classes 8 | used in your project whether written by you or from a framework such as 9 | Bootstrap or Tailwind (which can get [bit too slow](https://github.com/esamattis/postcss-ts-classnames/issues/5)). 10 | 11 | Ex. for css 12 | 13 | ```css 14 | .button { 15 | background: green; 16 | } 17 | 18 | .button-danger { 19 | background: red; 20 | } 21 | ``` 22 | 23 | you'll get 24 | 25 | ```ts 26 | type ClassNames = "button" | "button-danger"; 27 | ``` 28 | 29 | With it you can create a helper function like 30 | 31 | ```ts 32 | function cn(...args: ClassNames[]) { 33 | return args.join(" "); 34 | } 35 | ``` 36 | 37 | and have your editor autocomplete and validate the class names: 38 | 39 | ![vscode demo](.demos/autocomplete.gif?raw=true "VSCode demo") 40 | 41 | ## Setup 42 | 43 | Install the plugin 44 | 45 | npm install postcss-ts-classnames 46 | 47 | In your PostCSS config add it close to the end before optimizing plugins such 48 | as cssnano or purgecss: 49 | 50 | ```js 51 | module.exports = { 52 | plugins: [ 53 | require("postcss-import"), 54 | require("tailwindcss"), 55 | 56 | require("postcss-ts-classnames")({ 57 | dest: "src/classnames.d.ts", 58 | // Set isModule if you want to import ClassNames from another file 59 | isModule: true, 60 | exportAsDefault: true, // to use in combination with isModule 61 | }), 62 | 63 | require("@fullhuman/postcss-purgecss")({ 64 | content: ["./src/**/*.html"], 65 | }), 66 | ], 67 | }; 68 | ``` 69 | 70 | ## ts-classnames 71 | 72 | There's also a `ts-classnames` module which is re-exported version of the 73 | original [classnames][] which uses the generated `ClassNames` type to 74 | validate the class names 75 | 76 | [classnames]: https://www.npmjs.com/package/classnames 77 | 78 | Install 79 | 80 | npm install ts-classnames 81 | 82 | Import 83 | 84 | ```ts 85 | import { cn } from "ts-classnames"; 86 | ``` 87 | 88 | ## Vanilla JavaScript 89 | 90 | If you don't use TypeScript you can still leverage this as VSCode can pick up 91 | TypeScript types from JSDoc comments so you can do 92 | 93 | ```js 94 | /** 95 | * @param {ClassNames[]} args 96 | */ 97 | function cn(...args) { 98 | return args.join(" "); 99 | } 100 | ``` 101 | 102 | This will give the autocomplete but if you want the class names checking you 103 | can add [`// @ts-check`][js] to the top of the file. 104 | 105 | The `ts-classnames` will work with Vanilla JS too. 106 | 107 | [js]: https://github.com/microsoft/TypeScript/wiki/Type-Checking-JavaScript-Files 108 | -------------------------------------------------------------------------------- /__tests__/postcss-plugin.test.ts: -------------------------------------------------------------------------------- 1 | import postcss from "postcss"; 2 | import PathUtils from "path"; 3 | import { sh } from "sh-thunk"; 4 | import { promises as fs } from "fs"; 5 | import { createPlugin } from "../src/plugin"; 6 | import { ClassNameCollector } from "../src/class-name-collector"; 7 | 8 | function css(literals: TemplateStringsArray, ...placeholders: string[]) { 9 | let result = ""; 10 | 11 | // interleave the literals with the placeholders 12 | for (let i = 0; i < placeholders.length; i++) { 13 | result += literals[i]; 14 | result += placeholders[i]; 15 | } 16 | 17 | // add the last literal 18 | result += literals[literals.length - 1]; 19 | return result; 20 | } 21 | 22 | async function run(collector: ClassNameCollector, cssString: string) { 23 | await postcss([createPlugin(collector)]).process(cssString, { 24 | from: "test.css", 25 | }); 26 | } 27 | 28 | test("single class", async () => { 29 | const collector = new ClassNameCollector({}); 30 | 31 | await run( 32 | collector, 33 | css` 34 | .foo { 35 | color: red; 36 | } 37 | `, 38 | ); 39 | 40 | expect(collector.getClassNames()).toEqual(["foo"]); 41 | }); 42 | 43 | test(":hover", async () => { 44 | const collector = new ClassNameCollector({}); 45 | 46 | await run( 47 | collector, 48 | css` 49 | .foo:hover { 50 | color: red; 51 | } 52 | `, 53 | ); 54 | 55 | expect(collector.getClassNames()).toEqual(["foo"]); 56 | }); 57 | 58 | test("media query nesting", async () => { 59 | const collector = new ClassNameCollector({}); 60 | 61 | await run( 62 | collector, 63 | css` 64 | @media (min-width: 640px) { 65 | .foo { 66 | color: red; 67 | } 68 | } 69 | `, 70 | ); 71 | 72 | expect(collector.getClassNames()).toEqual(["foo"]); 73 | }); 74 | 75 | test("single two classes", async () => { 76 | const collector = new ClassNameCollector({}); 77 | 78 | await run( 79 | collector, 80 | css` 81 | .foo { 82 | color: red; 83 | } 84 | .bar { 85 | color: green; 86 | } 87 | `, 88 | ); 89 | 90 | expect(collector.getClassNames()).toEqual(["bar", "foo"]); 91 | }); 92 | 93 | test("nested classes", async () => { 94 | const collector = new ClassNameCollector({}); 95 | 96 | await run( 97 | collector, 98 | css` 99 | .foo .bar .baz { 100 | color: red; 101 | } 102 | `, 103 | ); 104 | 105 | expect(collector.getClassNames()).toEqual(["bar", "baz", "foo"]); 106 | }); 107 | 108 | test("nested classes under other selectors", async () => { 109 | const collector = new ClassNameCollector({}); 110 | 111 | await run( 112 | collector, 113 | css` 114 | p .foo { 115 | color: red; 116 | } 117 | `, 118 | ); 119 | 120 | expect(collector.getClassNames()).toEqual(["foo"]); 121 | }); 122 | 123 | test("comma separated classes", async () => { 124 | const collector = new ClassNameCollector({}); 125 | 126 | await run( 127 | collector, 128 | css` 129 | .foo, 130 | .bar, 131 | .baz { 132 | color: red; 133 | } 134 | `, 135 | ); 136 | 137 | expect(collector.getClassNames()).toEqual(["bar", "baz", "foo"]); 138 | }); 139 | 140 | test("multiple files", async () => { 141 | const collector = new ClassNameCollector({}); 142 | 143 | const post = postcss([createPlugin(collector)]); 144 | 145 | await post.process( 146 | css` 147 | .foo { 148 | color: red; 149 | } 150 | `, 151 | { 152 | from: "foo-file.css", 153 | }, 154 | ); 155 | 156 | await post.process( 157 | css` 158 | .bar { 159 | color: red; 160 | } 161 | `, 162 | { 163 | from: "bar-file.css", 164 | }, 165 | ); 166 | 167 | expect(collector.getClassNames()).toEqual(["bar", "foo"]); 168 | }); 169 | 170 | test("can remove classes", async () => { 171 | const collector = new ClassNameCollector({}); 172 | 173 | const post = postcss([createPlugin(collector)]); 174 | 175 | await post.process( 176 | css` 177 | .foo { 178 | color: red; 179 | } 180 | .bar { 181 | color: green; 182 | } 183 | `, 184 | { 185 | from: "foo-file.css", 186 | }, 187 | ); 188 | 189 | expect(collector.getClassNames()).toEqual(["bar", "foo"]); 190 | 191 | await collector.waitForWrite(); 192 | 193 | await post.process( 194 | css` 195 | .foo { 196 | color: red; 197 | } 198 | `, 199 | { 200 | from: "foo-file.css", 201 | }, 202 | ); 203 | 204 | expect(collector.getClassNames()).toEqual(["foo"]); 205 | }); 206 | 207 | describe("files", () => { 208 | let dir = "___noope"; 209 | 210 | beforeEach(async () => { 211 | dir = await fs.mkdtemp(".ts-classnames-test"); 212 | }); 213 | 214 | afterEach(async () => { 215 | await sh`rm -r ${dir}`(); 216 | }); 217 | 218 | test("can write the type file", async () => { 219 | const dest = PathUtils.join(dir, "types.d.ts"); 220 | 221 | const collector = new ClassNameCollector({ 222 | dest, 223 | }); 224 | 225 | await run( 226 | collector, 227 | css` 228 | .foo, 229 | .bar, 230 | .baz { 231 | color: red; 232 | } 233 | `, 234 | ); 235 | 236 | await collector.waitForWrite(); 237 | 238 | const content = await fs.readFile(dest); 239 | 240 | expect(content.toString()).toMatchInlineSnapshot(` 241 | "// This file is auto-generated with postcss-ts-classnames. 242 | 243 | type ClassNames = 244 | | \\"bar\\" 245 | | \\"baz\\" 246 | | \\"foo\\";" 247 | `); 248 | }); 249 | 250 | test("can export the type file as a module", async () => { 251 | const dest = PathUtils.join(dir, "types.ts"); 252 | const collector = new ClassNameCollector({ 253 | dest, 254 | isModule: true, 255 | }); 256 | 257 | await run( 258 | collector, 259 | css` 260 | .foo { 261 | color: blue; 262 | } 263 | `, 264 | ); 265 | await collector.waitForWrite(); 266 | 267 | const content = await fs.readFile(dest); 268 | 269 | expect(content.toString()).toMatchInlineSnapshot(` 270 | "// This file is auto-generated with postcss-ts-classnames. 271 | 272 | export type ClassNames = 273 | | \\"foo\\";" 274 | `); 275 | }); 276 | test("can export the type file as a module and as default", async () => { 277 | const dest = PathUtils.join(dir, "types.ts"); 278 | const collector = new ClassNameCollector({ 279 | dest, 280 | isModule: true, 281 | exportAsDefault: true, 282 | }); 283 | 284 | await run( 285 | collector, 286 | css` 287 | .foo { 288 | color: blue; 289 | } 290 | `, 291 | ); 292 | await collector.waitForWrite(); 293 | 294 | const content = await fs.readFile(dest); 295 | 296 | expect(content.toString()).toMatchInlineSnapshot(` 297 | "// This file is auto-generated with postcss-ts-classnames. 298 | 299 | export type ClassNames = 300 | | \\"foo\\"; 301 | 302 | export default ClassNames;" 303 | `); 304 | }); 305 | 306 | }); 307 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./dist/plugin").default; 2 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ["ts", "tsx", "js"], 3 | transform: { 4 | "^.+\\.(ts|tsx)$": "ts-jest", 5 | }, 6 | globals: { 7 | "ts-jest": { 8 | tsConfig: "tsconfig.json", 9 | }, 10 | }, 11 | testMatch: ["**/?(*.)+(spec|test).ts?(x)"], 12 | }; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-ts-classnames", 3 | "version": "0.3.0", 4 | "description": "PostCSS plugin to generate TypeScript types from _your_ CSS class names", 5 | "main": "index.js", 6 | "homepage": "https://github.com/epeli/postcss-ts-classnames", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/epeli/postcss-ts-classnames.git" 10 | }, 11 | "scripts": { 12 | "jest": "jest", 13 | "tsc": "tsc", 14 | "build": "rm -rf dist && tsc --project tsconfig.build.json", 15 | "watch": "tsc --project tsconfig.build.json --watch", 16 | "test": "npm run tsc && npm run jest", 17 | "release": "np --no-publish", 18 | "prepublishOnly": "npm test && npm run build" 19 | }, 20 | "keywords": [ 21 | "postcss", 22 | "postcss-plugin" 23 | ], 24 | "files": [ 25 | "index.js", 26 | "dist" 27 | ], 28 | "author": "Esa-Matti Suuronen", 29 | "license": "ISC", 30 | "devDependencies": { 31 | "@types/jest": "^24.0.21", 32 | "@types/lodash.debounce": "^4.0.6", 33 | "@types/node": "^12.12.5", 34 | "jest": "^24.9.0", 35 | "prettier": "^1.18.2", 36 | "sh-thunk": "^0.3.0", 37 | "ts-jest": "^24.1.0", 38 | "typescript": "^3.6.4" 39 | }, 40 | "dependencies": { 41 | "lodash.debounce": "^4.0.8", 42 | "postcss": "^7.0.21", 43 | "postcss-selector-parser": "^6.0.2" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /postcss.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./dist/postcss/plugin").default; 2 | -------------------------------------------------------------------------------- /src/class-name-collector.ts: -------------------------------------------------------------------------------- 1 | import createSelectorParser from "postcss-selector-parser"; 2 | import { promises as fs } from "fs"; 3 | import { Rule, Root } from "postcss"; 4 | import debounce from "lodash.debounce"; 5 | 6 | export interface ClassNameCollectorOptions { 7 | dest?: string; 8 | isModule?: boolean; 9 | exportAsDefault?: boolean; 10 | } 11 | 12 | export class ClassNameCollector { 13 | classNames: Map>; 14 | dest?: string; 15 | isModule?: boolean; 16 | exportAsDefault?: boolean; 17 | 18 | waiters = [] as VoidFunction[]; 19 | 20 | constructor(options: ClassNameCollectorOptions) { 21 | this.dest = options.dest; 22 | this.isModule = options.isModule; 23 | this.exportAsDefault = options.exportAsDefault; 24 | this.classNames = new Map(); 25 | } 26 | 27 | debouncedWrite = debounce(async () => { 28 | if (this.dest) { 29 | await fs.writeFile(this.dest, this.getTypeScriptFileContent()); 30 | } 31 | 32 | this.waiters.forEach(resolve => resolve()); 33 | this.waiters = []; 34 | }, 100); 35 | 36 | async waitForWrite() { 37 | return new Promise(resolve => { 38 | this.waiters.push(resolve); 39 | }); 40 | } 41 | 42 | addClassName(file: string, className: string) { 43 | let classNames = this.classNames.get(file); 44 | 45 | if (!classNames) { 46 | classNames = new Set(); 47 | this.classNames.set(file, classNames); 48 | } 49 | 50 | classNames.add(className); 51 | this.debouncedWrite(); 52 | } 53 | 54 | getClassNames() { 55 | const allUniq = new Set(); 56 | 57 | for (const names of Array.from(this.classNames.values())) { 58 | if (names) { 59 | names.forEach(n => allUniq.add(n)); 60 | } 61 | } 62 | 63 | return Array.from(allUniq).sort(); 64 | } 65 | 66 | getTypeScriptFileContent() { 67 | const comment = '// This file is auto-generated with postcss-ts-classnames.'; 68 | const prefix = ' | '; 69 | const names = this.getClassNames() 70 | .map(n => `"${n}"`) 71 | .join(`\n${prefix}`); 72 | 73 | if (this.isModule) { 74 | const result = `${comment}\n\nexport type ClassNames =\n${prefix}${names};`; 75 | return (this.exportAsDefault) 76 | ? result + '\n\nexport default ClassNames;' 77 | : result; 78 | } 79 | return `${comment}\n\ntype ClassNames =\n${prefix}${names};`; 80 | } 81 | 82 | process(root: Root) { 83 | if (!root.source) { 84 | return; 85 | } 86 | 87 | const file = root.source.input.file; 88 | 89 | if (!file) { 90 | return; 91 | } 92 | 93 | // clear classes from previous file version 94 | this.classNames.delete(file); 95 | 96 | const parser = createSelectorParser(selectors => { 97 | selectors.each(selector => { 98 | if (selector.type !== "selector") { 99 | return; 100 | } 101 | 102 | for (const node of selector.nodes) { 103 | if (node.type === "class") { 104 | this.addClassName(file, node.toString().slice(1)); 105 | } 106 | } 107 | }); 108 | }); 109 | 110 | root.walkRules(rule => { 111 | parser.process(rule, { lossless: false }); 112 | }); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/plugin.ts: -------------------------------------------------------------------------------- 1 | import postcss from "postcss"; 2 | import { 3 | ClassNameCollector, 4 | ClassNameCollectorOptions, 5 | } from "./class-name-collector"; 6 | 7 | export function getSingleton(): ClassNameCollector { 8 | const key = "ts-classname-collector"; 9 | const anyGlobal = global as any; 10 | 11 | let instance: ClassNameCollector = anyGlobal[key]; 12 | 13 | if (!instance) { 14 | instance = anyGlobal[key] = new ClassNameCollector({ 15 | dest: "src/classnames.d.ts", 16 | }); 17 | } 18 | 19 | return instance; 20 | } 21 | 22 | export function createPlugin(collector: ClassNameCollector) { 23 | return postcss.plugin("postcss-ts-classnames", _userOptions => { 24 | const userOptions = _userOptions as 25 | | Partial 26 | | undefined; 27 | 28 | if (userOptions && userOptions.dest) { 29 | collector.dest = userOptions.dest; 30 | } 31 | if (userOptions && userOptions.isModule) { 32 | collector.isModule = userOptions.isModule; 33 | } 34 | if (userOptions && userOptions.exportAsDefault) { 35 | collector.exportAsDefault = userOptions.exportAsDefault; 36 | } 37 | 38 | return root => { 39 | collector.process(root); 40 | }; 41 | }); 42 | } 43 | 44 | export default createPlugin(getSingleton()); 45 | -------------------------------------------------------------------------------- /ts-classnames/README.md: -------------------------------------------------------------------------------- 1 | # ts-classnames 2 | 3 | Re-exported version of the `classnames` module for the 4 | `postcss-ts-classnames` PostCSS plugin. [See it's readme][readme]. 5 | 6 | [readme]: https://github.com/epeli/postcss-ts-classnames#ts-classnames 7 | -------------------------------------------------------------------------------- /ts-classnames/index.d.ts: -------------------------------------------------------------------------------- 1 | type ClassNameRecord = { 2 | [P in K]?: boolean | null | undefined; 3 | }; 4 | 5 | type Variants = 6 | | T 7 | | ClassNameRecord 8 | | null 9 | | undefined 10 | | boolean; 11 | 12 | export interface ClassNamesFunction { 13 | (...args: Variants[]): string; 14 | } 15 | 16 | /** 17 | * Using user defined ClassNames type 18 | */ 19 | export const cn: ClassNamesFunction; 20 | export const classNames: ClassNamesFunction; 21 | export default cn; 22 | -------------------------------------------------------------------------------- /ts-classnames/index.js: -------------------------------------------------------------------------------- 1 | var cn = require("classnames"); 2 | 3 | cn.classNames = cn; 4 | cn.default = cn; 5 | cn.cn = cn; 6 | 7 | module.exports = cn; 8 | -------------------------------------------------------------------------------- /ts-classnames/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-classnames", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "classnames": { 8 | "version": "2.2.6", 9 | "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", 10 | "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ts-classnames/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-classnames", 3 | "version": "0.1.1", 4 | "description": "Typed classnames module", 5 | "main": "index.js", 6 | "homepage": "https://github.com/epeli/postcss-ts-classnames", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/epeli/postcss-ts-classnames.git" 10 | }, 11 | "files": [ 12 | "index.js", 13 | "index.d.ts" 14 | ], 15 | "scripts": { 16 | "test": "echo \"Error: no test specified\" && exit 1" 17 | }, 18 | "author": "", 19 | "license": "ISC", 20 | "dependencies": { 21 | "classnames": "^2.2.6" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"], 4 | "compilerOptions": { 5 | "target": "es6", 6 | "sourceMap": true, 7 | "noEmit": false, 8 | "declaration": true, 9 | "outDir": "./dist", 10 | "rootDir": "src" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.dtslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["docs"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "__tests__"], 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "noEmit": true, 6 | "lib": ["esnext", "dom"], 7 | "moduleResolution": "node", 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "esModuleInterop": true 11 | } 12 | } 13 | --------------------------------------------------------------------------------