├── .editorconfig ├── .gitignore ├── .npmrc ├── .nvmrc ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── package.json └── test ├── compiler.js ├── emit-declaration ├── .gitignore ├── __snapshots__ │ └── emit-declaration.test.js.snap ├── common.css ├── emit-declaration.test.js ├── index.css └── index.js ├── emit-empty-declaration ├── .gitignore ├── __snapshots__ │ └── emit-empty-declaration.test.js.snap ├── emit-empty-declaration.test.js ├── index.css └── index.js ├── getErrorMessage.js ├── verify-invalid-declaration ├── __snapshots__ │ └── verify-invalid-declaration.test.js.snap ├── index.css ├── index.css.d.ts ├── index.js └── verify-invalid-declaration.test.js ├── verify-missing-declaration ├── __snapshots__ │ └── verify-missing-declaration.test.js.snap ├── index.css ├── index.js └── verify-missing-declaration.test.js └── verify-valid-declaration ├── index.css ├── index.css.d.ts ├── index.js └── verify-valid-declaration.test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.log 3 | yarn.lock 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | directories: 4 | - node_modules 5 | notifications: 6 | email: false 7 | before_script: 8 | - npm prune 9 | script: 10 | - npm test 11 | after_success: 12 | - npm run travis-deploy-once "npm run semantic-release" 13 | branches: 14 | except: 15 | - /^v\d+\.\d+\.\d+$/ 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 SEEK 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://img.shields.io/travis/seek-oss/css-modules-typescript-loader/master.svg?style=flat-square)](http://travis-ci.org/seek-oss/css-modules-typescript-loader) [![npm](https://img.shields.io/npm/v/css-modules-typescript-loader.svg?style=flat-square)](https://www.npmjs.com/package/css-modules-typescript-loader) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=flat-square)](https://github.com/semantic-release/semantic-release) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg?style=flat-square)](http://commitizen.github.io/cz-cli/) 2 | 3 | # css-modules-typescript-loader 4 | 5 | [Webpack](https://webpack.js.org/) loader to create [TypeScript](https://www.typescriptlang.org/) declarations for [CSS Modules](https://github.com/css-modules/css-modules). 6 | 7 | Emits TypeScript declaration files matching your CSS Modules in the same location as your source files, e.g. `src/Component.css` will generate `src/Component.css.d.ts`. 8 | 9 | ## Why? 10 | 11 | There are currently a lot of [solutions to this problem](https://www.npmjs.com/search?q=css%20modules%20typescript%20loader). However, this package differs in the following ways: 12 | 13 | - Encourages generated TypeScript declarations to be checked into source control, which allows `webpack` and `tsc` commands to be run in parallel in CI. 14 | 15 | - Ensures committed TypeScript declarations are in sync with the code that generated them via the [`verify` mode](#verify-mode). 16 | 17 | ## Usage 18 | 19 | Place `css-modules-typescript-loader` directly after `css-loader` in your webpack config. 20 | 21 | ```js 22 | module.exports = { 23 | module: { 24 | rules: [ 25 | { 26 | test: /\.css$/, 27 | use: [ 28 | 'css-modules-typescript-loader', 29 | { 30 | loader: 'css-loader', 31 | options: { 32 | modules: true 33 | } 34 | } 35 | ] 36 | } 37 | ] 38 | } 39 | }; 40 | ``` 41 | 42 | ### Verify Mode 43 | 44 | Since the TypeScript declarations are generated by `webpack`, they may potentially be out of date by the time you run `tsc`. To ensure your types are up to date, you can run the loader in `verify` mode, which is particularly useful in CI. 45 | 46 | For example: 47 | 48 | ```js 49 | { 50 | loader: 'css-modules-typescript-loader', 51 | options: { 52 | mode: process.env.CI ? 'verify' : 'emit' 53 | } 54 | } 55 | ``` 56 | 57 | Instead of emitting new TypeScript declarations, this will throw an error if a generated declaration doesn't match the committed one. This allows `tsc` and `webpack` to run in parallel in CI, if desired. 58 | 59 | This workflow is similar to using the [Prettier](https://github.com/prettier/prettier) [`--list-different` option](https://prettier.io/docs/en/cli.html#list-different). 60 | 61 | ## With Thanks 62 | 63 | This package borrows heavily from [typings-for-css-modules-loader](https://github.com/Jimdo/typings-for-css-modules-loader). 64 | 65 | ## License 66 | 67 | MIT. 68 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const loaderUtils = require('loader-utils'); 4 | const LineDiff = require('line-diff'); 5 | 6 | const bannerMessage = 7 | '// This file is automatically generated.\n// Please do not change this file!'; 8 | 9 | const cssModuleExport = 'export const cssExports: CssExports;\nexport default cssExports;\n'; 10 | 11 | const getNoDeclarationFileError = ({ filename }) => 12 | new Error( 13 | `Generated type declaration does not exist. Run webpack and commit the type declaration for '${filename}'` 14 | ); 15 | 16 | const getTypeMismatchError = ({ filename, expected, actual }) => { 17 | const diff = new LineDiff(enforceLFLineSeparators(actual), expected).toString(); 18 | 19 | return new Error( 20 | `Generated type declaration file is outdated. Run webpack and commit the updated type declaration for '${filename}'\n\n${diff}` 21 | ); 22 | }; 23 | 24 | const cssModuleToInterface = (cssModuleKeys) => { 25 | const interfaceFields = cssModuleKeys 26 | .sort() 27 | .map(key => ` '${key}': string;`) 28 | .join('\n'); 29 | 30 | return `interface CssExports {\n${interfaceFields}\n}`; 31 | }; 32 | 33 | const filenameToTypingsFilename = filename => { 34 | const dirName = path.dirname(filename); 35 | const baseName = path.basename(filename); 36 | return path.join(dirName, `${baseName}.d.ts`); 37 | }; 38 | 39 | const enforceLFLineSeparators = text => { 40 | if (text) { 41 | // replace all CRLFs (Windows) by LFs (Unix) 42 | return text.replace(/\r\n/g, "\n"); 43 | } else { 44 | return text; 45 | } 46 | }; 47 | 48 | const compareText = (contentA, contentB) => { 49 | return enforceLFLineSeparators(contentA) === enforceLFLineSeparators(contentB); 50 | }; 51 | 52 | const validModes = ['emit', 'verify']; 53 | 54 | const isFileNotFound = err => err && err.code === 'ENOENT'; 55 | 56 | const makeDoneHandlers = (callback, content, rest) => ({ 57 | failed: e => callback(e), 58 | success: () => callback(null, content, ...rest) 59 | }); 60 | 61 | const makeFileHandlers = filename => ({ 62 | read: handler => fs.readFile(filename, { encoding: 'utf-8' }, handler), 63 | write: (content, handler) => 64 | fs.writeFile(filename, content, { encoding: 'utf-8' }, handler) 65 | }); 66 | 67 | const extractLocalExports = (content) => { 68 | let localExports = content.split('exports.locals')[1]; 69 | if (!localExports) { 70 | localExports = content.split('___CSS_LOADER_EXPORT___.locals')[1]; 71 | } 72 | return localExports; 73 | } 74 | 75 | module.exports = function(content, ...rest) { 76 | const { failed, success } = makeDoneHandlers(this.async(), content, rest); 77 | 78 | const filename = this.resourcePath; 79 | const { mode = 'emit' } = loaderUtils.getOptions(this) || {}; 80 | if (!validModes.includes(mode)) { 81 | return failed(new Error(`Invalid mode option: ${mode}`)); 82 | } 83 | 84 | const cssModuleInterfaceFilename = filenameToTypingsFilename(filename); 85 | const { read, write } = makeFileHandlers(cssModuleInterfaceFilename); 86 | 87 | const keyRegex = /"([^\\"]+)":/g; 88 | let match; 89 | const cssModuleKeys = []; 90 | 91 | const localExports = extractLocalExports(content); 92 | 93 | while ((match = keyRegex.exec(localExports))) { 94 | if (cssModuleKeys.indexOf(match[1]) < 0) { 95 | cssModuleKeys.push(match[1]); 96 | } 97 | } 98 | 99 | const cssModuleDefinition = `${bannerMessage}\n${cssModuleToInterface(cssModuleKeys)}\n${cssModuleExport}`; 100 | 101 | if (mode === 'verify') { 102 | read((err, fileContents) => { 103 | if (isFileNotFound(err)) { 104 | return failed( 105 | getNoDeclarationFileError({ 106 | filename: cssModuleInterfaceFilename 107 | }) 108 | ); 109 | } 110 | 111 | if (err) { 112 | return failed(err); 113 | } 114 | 115 | if (!compareText(cssModuleDefinition, fileContents)) { 116 | return failed( 117 | getTypeMismatchError({ 118 | filename: cssModuleInterfaceFilename, 119 | expected: cssModuleDefinition, 120 | actual: fileContents 121 | }) 122 | ); 123 | } 124 | 125 | return success(); 126 | }); 127 | } else { 128 | read((_, fileContents) => { 129 | if (!compareText(cssModuleDefinition, fileContents)) { 130 | write(cssModuleDefinition, err => { 131 | if (err) { 132 | failed(err); 133 | } else { 134 | success(); 135 | } 136 | }); 137 | } else { 138 | success(); 139 | } 140 | }); 141 | } 142 | }; 143 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-modules-typescript-loader", 3 | "version": "0.0.0-development", 4 | "description": "Webpack loader to create TypeScript declarations for CSS Modules", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/seek-oss/css-modules-typescript-loader.git" 9 | }, 10 | "author": "SEEK", 11 | "license": "MIT", 12 | "bugs": { 13 | "url": "https://github.com/seek-oss/css-modules-typescript-loader/issues" 14 | }, 15 | "scripts": { 16 | "commit": "git-cz", 17 | "travis-deploy-once": "travis-deploy-once", 18 | "semantic-release": "semantic-release", 19 | "test": "jest" 20 | }, 21 | "jest": { 22 | "testEnvironment": "node" 23 | }, 24 | "husky": { 25 | "hooks": { 26 | "commit-msg": "commitlint --edit --extends seek" 27 | } 28 | }, 29 | "homepage": "https://github.com/seek-oss/css-modules-typescript-loader#readme", 30 | "dependencies": { 31 | "line-diff": "^2.0.1", 32 | "loader-utils": "^1.2.3" 33 | }, 34 | "devDependencies": { 35 | "@commitlint/cli": "^7.2.1", 36 | "commitizen": "^3.0.2", 37 | "commitlint-config-seek": "^1.0.0", 38 | "css-loader": "^4.2.1", 39 | "cz-conventional-changelog": "^2.1.0", 40 | "husky": "^1.1.2", 41 | "jest": "^24.7.1", 42 | "memory-fs": "^0.4.1", 43 | "semantic-release": "^15.9.17", 44 | "travis-deploy-once": "^5.0.9", 45 | "webpack": "^4.21.0" 46 | }, 47 | "release": { 48 | "success": false 49 | }, 50 | "config": { 51 | "commitizen": { 52 | "path": "./node_modules/cz-conventional-changelog" 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/compiler.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const memoryfs = require('memory-fs'); 4 | 5 | module.exports = (entry, options = {}) => { 6 | const { sourceMap, ...loaderOptions } = options; 7 | const compiler = webpack({ 8 | context: path.dirname(entry), 9 | entry, 10 | output: { 11 | path: path.dirname(entry), 12 | filename: 'bundle.js' 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.css$/, 18 | use: [ 19 | { 20 | loader: require.resolve('../index.js'), 21 | options: loaderOptions 22 | }, 23 | { 24 | loader: 'css-loader', 25 | options: { 26 | modules: true, 27 | sourceMap: !!sourceMap 28 | } 29 | } 30 | ] 31 | } 32 | ] 33 | } 34 | }); 35 | 36 | compiler.outputFileSystem = new memoryfs(); 37 | 38 | return new Promise((resolve, reject) => { 39 | compiler.run((err, stats) => { 40 | if (err || stats.hasErrors()) { 41 | reject({ 42 | failed: true, 43 | errors: err || stats.toJson().errors 44 | }); 45 | } 46 | 47 | resolve(stats); 48 | }); 49 | }); 50 | }; 51 | -------------------------------------------------------------------------------- /test/emit-declaration/.gitignore: -------------------------------------------------------------------------------- 1 | index.css.d.ts 2 | -------------------------------------------------------------------------------- /test/emit-declaration/__snapshots__/emit-declaration.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Can emit valid declaration with sourceMap 1`] = ` 4 | "// This file is automatically generated. 5 | // Please do not change this file! 6 | interface CssExports { 7 | 'composedClass': string; 8 | 'otherClass': string; 9 | 'someClass': string; 10 | 'validClass': string; 11 | } 12 | export const cssExports: CssExports; 13 | export default cssExports; 14 | " 15 | `; 16 | 17 | exports[`Can emit valid declaration without sourceMaps 1`] = ` 18 | "// This file is automatically generated. 19 | // Please do not change this file! 20 | interface CssExports { 21 | 'composedClass': string; 22 | 'otherClass': string; 23 | 'someClass': string; 24 | 'validClass': string; 25 | } 26 | export const cssExports: CssExports; 27 | export default cssExports; 28 | " 29 | `; 30 | -------------------------------------------------------------------------------- /test/emit-declaration/common.css: -------------------------------------------------------------------------------- 1 | .baseClass { 2 | position: relative; 3 | } 4 | -------------------------------------------------------------------------------- /test/emit-declaration/emit-declaration.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const compiler = require('../compiler.js'); 3 | 4 | test('Can emit valid declaration without sourceMaps', async () => { 5 | await compiler(require.resolve('./index.js')); 6 | 7 | const declaration = fs.readFileSync( 8 | require.resolve('./index.css.d.ts'), 9 | 'utf-8' 10 | ); 11 | 12 | expect(declaration).toMatchSnapshot(); 13 | }); 14 | 15 | test('Can emit valid declaration with sourceMap', async () => { 16 | await compiler(require.resolve('./index.js'), { sourceMap: true }); 17 | 18 | const declaration = fs.readFileSync( 19 | require.resolve('./index.css.d.ts'), 20 | 'utf-8' 21 | ); 22 | 23 | expect(declaration).toMatchSnapshot(); 24 | }); 25 | 26 | -------------------------------------------------------------------------------- /test/emit-declaration/index.css: -------------------------------------------------------------------------------- 1 | .validClass { 2 | position: relative; 3 | } 4 | 5 | .someClass { 6 | position: relative; 7 | } 8 | 9 | .otherClass { 10 | display: block; 11 | } 12 | 13 | .composedClass { 14 | composes: baseClass from './common.css'; 15 | color: cornflowerblue; 16 | } 17 | -------------------------------------------------------------------------------- /test/emit-declaration/index.js: -------------------------------------------------------------------------------- 1 | import styles from './index.css'; 2 | -------------------------------------------------------------------------------- /test/emit-empty-declaration/.gitignore: -------------------------------------------------------------------------------- 1 | index.css.d.ts 2 | -------------------------------------------------------------------------------- /test/emit-empty-declaration/__snapshots__/emit-empty-declaration.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Can emit valid declaration without classes 1`] = ` 4 | "// This file is automatically generated. 5 | // Please do not change this file! 6 | interface CssExports { 7 | 8 | } 9 | export const cssExports: CssExports; 10 | export default cssExports; 11 | " 12 | `; 13 | -------------------------------------------------------------------------------- /test/emit-empty-declaration/emit-empty-declaration.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const compiler = require('../compiler.js'); 3 | 4 | test('Can emit valid declaration without classes', async () => { 5 | await compiler(require.resolve('./index.js')); 6 | 7 | const declaration = fs.readFileSync( 8 | require.resolve('./index.css.d.ts'), 9 | 'utf-8' 10 | ); 11 | 12 | expect(declaration).toMatchSnapshot(); 13 | }); 14 | -------------------------------------------------------------------------------- /test/emit-empty-declaration/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | /* No class selectors in this file ... */ 3 | } 4 | -------------------------------------------------------------------------------- /test/emit-empty-declaration/index.js: -------------------------------------------------------------------------------- 1 | import "./index.css"; 2 | -------------------------------------------------------------------------------- /test/getErrorMessage.js: -------------------------------------------------------------------------------- 1 | module.exports = error => error 2 | .split(process.cwd()) 3 | .join('') 4 | .match(/(Error: .*?)\s{4}at /s)[1]; 5 | -------------------------------------------------------------------------------- /test/verify-invalid-declaration/__snapshots__/verify-invalid-declaration.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Can error on invalid declaration 1`] = ` 4 | "Error: Generated type declaration file is outdated. Run webpack and commit the updated type declaration for '/test/verify-invalid-declaration/index.css.d.ts' 5 | 6 | // This file is automatically generated. 7 | // Please do not change this file! 8 | interface CssExports { 9 | 'classInBothFiles': string; 10 | - 'classInTypeScriptFile': string; 11 | + 'classInCssFile': string; 12 | } 13 | export const cssExports: CssExports; 14 | export default cssExports; 15 | 16 | 17 | " 18 | `; 19 | -------------------------------------------------------------------------------- /test/verify-invalid-declaration/index.css: -------------------------------------------------------------------------------- 1 | .classInBothFiles { 2 | position: relative; 3 | } 4 | 5 | .classInCssFile { 6 | display: block; 7 | } 8 | -------------------------------------------------------------------------------- /test/verify-invalid-declaration/index.css.d.ts: -------------------------------------------------------------------------------- 1 | // This file is automatically generated. 2 | // Please do not change this file! 3 | interface CssExports { 4 | 'classInBothFiles': string; 5 | 'classInTypeScriptFile': string; 6 | } 7 | export const cssExports: CssExports; 8 | export default cssExports; 9 | -------------------------------------------------------------------------------- /test/verify-invalid-declaration/index.js: -------------------------------------------------------------------------------- 1 | import styles from './index.css'; 2 | -------------------------------------------------------------------------------- /test/verify-invalid-declaration/verify-invalid-declaration.test.js: -------------------------------------------------------------------------------- 1 | const compiler = require('../compiler.js'); 2 | const getErrorMessage = require('../getErrorMessage'); 3 | 4 | test('Can error on invalid declaration', async () => { 5 | expect.assertions(1); 6 | 7 | try { 8 | await compiler(require.resolve('./index.js'), { 9 | mode: 'verify' 10 | }); 11 | } catch (err) { 12 | // make test robust for Windows by replacing backslashes in the file path with slashes 13 | let errorMessage = getErrorMessage(err.errors[0]).replace(/(?<=Error:.*)\\/g, "/"); 14 | 15 | expect(errorMessage).toMatchSnapshot(); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /test/verify-missing-declaration/__snapshots__/verify-missing-declaration.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Can error on invalid declaration 1`] = ` 4 | "Error: Generated type declaration does not exist. Run webpack and commit the type declaration for '/test/verify-missing-declaration/index.css.d.ts' 5 | " 6 | `; 7 | -------------------------------------------------------------------------------- /test/verify-missing-declaration/index.css: -------------------------------------------------------------------------------- 1 | .someClass { 2 | position: relative; 3 | } 4 | 5 | .otherClass { 6 | display: block; 7 | } 8 | -------------------------------------------------------------------------------- /test/verify-missing-declaration/index.js: -------------------------------------------------------------------------------- 1 | import styles from './index.css'; 2 | -------------------------------------------------------------------------------- /test/verify-missing-declaration/verify-missing-declaration.test.js: -------------------------------------------------------------------------------- 1 | const compiler = require('../compiler.js'); 2 | const getErrorMessage = require('../getErrorMessage'); 3 | 4 | test('Can error on invalid declaration', async () => { 5 | expect.assertions(1); 6 | 7 | try { 8 | await compiler(require.resolve('./index.js'), { 9 | mode: 'verify' 10 | }); 11 | } catch (err) { 12 | // make test robust for Windows by replacing backslashes in the file path with slashes 13 | let errorMessage = getErrorMessage(err.errors[0]).replace(/(?<=Error:.*)\\/g, "/"); 14 | 15 | expect(errorMessage).toMatchSnapshot(); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /test/verify-valid-declaration/index.css: -------------------------------------------------------------------------------- 1 | .validClass { 2 | position: relative; 3 | } 4 | 5 | .someClass { 6 | position: relative; 7 | } 8 | 9 | .otherClass { 10 | display: block; 11 | } 12 | 13 | .hyphened-classname, 14 | .underscored_classname { 15 | color: papayawhip; 16 | } 17 | -------------------------------------------------------------------------------- /test/verify-valid-declaration/index.css.d.ts: -------------------------------------------------------------------------------- 1 | // This file is automatically generated. 2 | // Please do not change this file! 3 | interface CssExports { 4 | 'hyphened-classname': string; 5 | 'otherClass': string; 6 | 'someClass': string; 7 | 'underscored_classname': string; 8 | 'validClass': string; 9 | } 10 | export const cssExports: CssExports; 11 | export default cssExports; 12 | -------------------------------------------------------------------------------- /test/verify-valid-declaration/index.js: -------------------------------------------------------------------------------- 1 | import styles from './index.css'; 2 | -------------------------------------------------------------------------------- /test/verify-valid-declaration/verify-valid-declaration.test.js: -------------------------------------------------------------------------------- 1 | const compiler = require('../compiler.js'); 2 | 3 | test('Can verify valid declaration', async () => { 4 | // just validate webpack build passes 5 | await compiler(require.resolve('./index.js'), { 6 | mode: 'verify' 7 | }); 8 | }); 9 | --------------------------------------------------------------------------------