Skip to content

Commit c804817

Browse files
committed
Merge branch 'release/1.3.0'
2 parents 4641eb6 + 8698439 commit c804817

16 files changed

+1049
-964
lines changed

.circleci/config.yml

-44
This file was deleted.

.github/workflows/ci.yml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: CI
2+
3+
on: [pull_request]
4+
5+
jobs:
6+
build-and-test:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v1
10+
- uses: actions/setup-node@v1
11+
with:
12+
node-version: 10.x
13+
- name: Yarn install, build, and test
14+
run: |
15+
yarn install
16+
yarn build
17+
yarn test

README.md

+12-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# typescript-plugin-css-modules
22

3-
[![CircleCI branch](https://img.shields.io/circleci/project/github/mrmckeb/typescript-plugin-css-modules/master.svg)](https://circleci.com/gh/mrmckeb/typescript-plugin-css-modules)
4-
[![npm](https://img.shields.io/npm/v/typescript-plugin-css-modules.svg)](https://www.npmjs.com/package/typescript-plugin-css-modules)
5-
[![license](https://img.shields.io/npm/l/typescript-plugin-css-modules.svg)](https://github.com/mrmckeb/typescript-plugin-css-modules/blob/develop/LICENSE)
3+
[![npm](https://img.shields.io/npm/v/typescript-plugin-css-modules)](https://www.npmjs.com/package/typescript-plugin-css-modules)
4+
[![npm](https://img.shields.io/npm/dw/typescript-plugin-css-modules)](https://www.npmjs.com/package/typescript-plugin-css-modules)
5+
[![license](https://img.shields.io/npm/l/typescript-plugin-css-modules)](https://github.com/mrmckeb/typescript-plugin-css-modules/blob/develop/LICENSE)
66

77
A [TypeScript language service plugin](https://github.com/Microsoft/TypeScript/wiki/Writing-a-Language-Service-Plugin)
88
for [CSS Modules](https://github.com/css-modules/css-modules).
@@ -83,7 +83,7 @@ The below is an example that only matches "\*.m.css" files, and [camel-cases das
8383

8484
### Visual Studio Code
8585

86-
By default, VSCode will use it's own version of TypeScript. To make it work with this plugin, you have two options:
86+
By default, VSCode will use its own version of TypeScript. To make it work with this plugin, you have two options:
8787

8888
1. Use your workspace's version of TypeScript, which will load plugins from your `tsconfig.json` file. This is the recommended approach. For instructions, see: [Using the workspace version of TypeScript](https://code.visualstudio.com/docs/languages/typescript#_using-the-workspace-version-of-typescript).
8989

@@ -126,3 +126,11 @@ declare module '*.module.less' {
126126
export default classes;
127127
}
128128
```
129+
130+
## Troubleshooting
131+
132+
If you're having issues with this extension, you can view the TypeScript Server Log in VSCode by entering `Typescript: Open TS Server log` in the [command palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette).
133+
134+
If that doesn't work, or you're not using VSCode, you can set the [TSS_LOG environment variable](https://github.com/Microsoft/TypeScript/wiki/Standalone-Server-%28tsserver%29#logging).
135+
136+
You can also include this with any issues you file on this project.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "typescript-plugin-css-modules",
3-
"version": "1.2.1",
3+
"version": "1.3.0",
44
"main": "lib/index.js",
55
"author": "Brody McKee <[email protected]>",
66
"license": "MIT",
@@ -52,6 +52,7 @@
5252
"lodash": "^4.17.14",
5353
"postcss": "^7.0.17",
5454
"postcss-icss-selectors": "^2.0.3",
55+
"postcss-load-config": "^2.1.0",
5556
"reserved-words": "^0.1.2",
5657
"sass": "^1.22.4"
5758
},

src/@types/postcss-load.config.d.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
declare module 'postcss-load-config' {
2+
interface PostCSSConfig {
3+
plugins: any[];
4+
options?: any;
5+
}
6+
7+
interface Load {
8+
(context: object, path?: string, options?: object): Promise<PostCSSConfig>;
9+
sync(context: object, path?: string, options?: object): PostCSSConfig;
10+
}
11+
12+
const load: Load;
13+
14+
export = load;
15+
}

src/config.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const packageName = 'typescript-plugin-css-modules';

src/helpers/DtsSnapshotCreator.ts

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { extractICSS, IICSSExports } from 'icss-utils';
2+
import * as postcss from 'postcss';
3+
import * as ts_module from 'typescript/lib/tsserverlibrary';
4+
import * as less from 'less';
5+
import * as sass from 'sass';
6+
import * as reserved from 'reserved-words';
7+
import { transformClasses } from './classTransforms';
8+
import { Options } from '../options';
9+
import { Logger } from './logger';
10+
11+
const NOT_CAMELCASE_REGEXP = /[\-_]/;
12+
13+
const classNameToProperty = (className: string) => `'${className}': string;`;
14+
const classNameToNamedExport = (className: string) =>
15+
`export const ${className}: string;`;
16+
17+
const flattenClassNames = (
18+
previousValue: string[] = [],
19+
currentValue: string[],
20+
) => previousValue.concat(currentValue);
21+
22+
export const enum FileTypes {
23+
css = 'css',
24+
less = 'less',
25+
scss = 'scss',
26+
}
27+
28+
export const getFileType = (fileName: string) => {
29+
if (fileName.endsWith('.css')) return FileTypes.css;
30+
if (fileName.endsWith('.less')) return FileTypes.less;
31+
return FileTypes.scss;
32+
};
33+
34+
const getFilePath = (fileName: string) =>
35+
fileName.substring(0, fileName.lastIndexOf('/'));
36+
37+
export class DtsSnapshotCreator {
38+
constructor(private readonly logger: Logger) {}
39+
40+
getClasses(processor: postcss.Processor, css: string, fileName: string) {
41+
try {
42+
const fileType = getFileType(fileName);
43+
let transformedCss = '';
44+
45+
if (fileType === FileTypes.less) {
46+
less.render(css, { asyncImport: true } as any, (err, output) => {
47+
transformedCss = output.css.toString();
48+
});
49+
} else if (fileType === FileTypes.scss) {
50+
const filePath = getFilePath(fileName);
51+
transformedCss = sass
52+
.renderSync({
53+
data: css,
54+
includePaths: [filePath],
55+
})
56+
.css.toString();
57+
} else {
58+
transformedCss = css;
59+
}
60+
61+
const processedCss = processor.process(transformedCss);
62+
63+
return processedCss.root
64+
? extractICSS(processedCss.root).icssExports
65+
: {};
66+
} catch (e) {
67+
this.logger.error(e);
68+
return {};
69+
}
70+
}
71+
72+
createExports(classes: IICSSExports, options: Options) {
73+
const isCamelCase = (className: string) =>
74+
!NOT_CAMELCASE_REGEXP.test(className);
75+
const isReservedWord = (className: string) => !reserved.check(className);
76+
77+
const processedClasses = Object.keys(classes)
78+
.map(transformClasses(options.camelCase))
79+
.reduce(flattenClassNames, []);
80+
const camelCasedKeys = processedClasses
81+
.filter(isCamelCase)
82+
.filter(isReservedWord)
83+
.map(classNameToNamedExport);
84+
85+
const defaultExport = `\
86+
declare const classes: {
87+
${processedClasses.map(classNameToProperty).join('\n ')}
88+
};
89+
export default classes;
90+
`;
91+
92+
if (camelCasedKeys.length) {
93+
return defaultExport + camelCasedKeys.join('\n') + '\n';
94+
}
95+
return defaultExport;
96+
}
97+
98+
getDtsSnapshot(
99+
ts: typeof ts_module,
100+
processor: postcss.Processor,
101+
fileName: string,
102+
scriptSnapshot: ts.IScriptSnapshot,
103+
options: Options,
104+
) {
105+
const css = scriptSnapshot.getText(0, scriptSnapshot.getLength());
106+
107+
// FIXME: Temporary workaround for https://github.com/mrmckeb/typescript-plugin-css-modules/issues/41
108+
// Needs investigation for a more elegant solution.
109+
if (/export default classes/.test(css)) {
110+
return scriptSnapshot;
111+
}
112+
113+
const classes = this.getClasses(processor, css, fileName);
114+
const dts = this.createExports(classes, options);
115+
return ts.ScriptSnapshot.fromString(dts);
116+
}
117+
}

src/helpers/__tests__/cssSnapshots.test.ts renamed to src/helpers/__tests__/DtsSnapshotCreator.test.ts

+16-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { readFileSync } from 'fs';
22
import { IICSSExports } from 'icss-utils';
33
import { join } from 'path';
4-
import { createExports, getClasses, getFileType } from '../cssSnapshots';
4+
import * as postcss from 'postcss';
5+
import * as postcssIcssSelectors from 'postcss-icss-selectors';
6+
import { DtsSnapshotCreator } from '../DtsSnapshotCreator';
57

68
const testFileNames = [
79
'test.module.css',
@@ -11,14 +13,25 @@ const testFileNames = [
1113
'empty.module.scss',
1214
];
1315

16+
const processor = postcss([postcssIcssSelectors({ mode: 'local' })]);
17+
1418
describe('utils / cssSnapshots', () => {
1519
testFileNames.forEach((fileName) => {
1620
let classes: IICSSExports;
21+
let dtsSnapshotCreator: DtsSnapshotCreator;
1722
const fullFileName = join(__dirname, 'fixtures', fileName);
1823
const testFile = readFileSync(fullFileName, 'utf8');
1924

2025
beforeAll(() => {
21-
classes = getClasses(testFile, fullFileName);
26+
dtsSnapshotCreator = new DtsSnapshotCreator({
27+
log: jest.fn(),
28+
error: jest.fn(),
29+
});
30+
classes = dtsSnapshotCreator.getClasses(
31+
processor,
32+
testFile,
33+
fullFileName,
34+
);
2235
});
2336

2437
describe(`with file '${fileName}'`, () => {
@@ -30,7 +43,7 @@ describe('utils / cssSnapshots', () => {
3043

3144
describe('createExports', () => {
3245
it('should create an exports file', () => {
33-
const exports = createExports(classes, {});
46+
const exports = dtsSnapshotCreator.createExports(classes, {});
3447
expect(exports).toMatchSnapshot();
3548
});
3649
});

src/helpers/__tests__/createMatchers.test.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { createMatchers } from '../createMatchers';
22
import { Options } from '../../options';
3+
import { Logger } from '../logger';
34

45
describe('utils / createMatchers', () => {
6+
const logger: Logger = { log: jest.fn(), error: jest.fn() };
57
it('should match `customMatcher` regexp', () => {
68
const options: Options = { customMatcher: '\\.css$' };
7-
const { isCSS, isRelativeCSS } = createMatchers(options);
9+
const { isCSS, isRelativeCSS } = createMatchers(logger, options);
810

911
expect(isCSS('./myfile.css')).toBe(true);
1012
expect(isCSS('./myfile.m.css')).toBe(true);
@@ -16,7 +18,7 @@ describe('utils / createMatchers', () => {
1618

1719
it('should handle bad `customMatcher` regexp', () => {
1820
const options: Options = { customMatcher: '$([a' };
19-
const { isCSS, isRelativeCSS } = createMatchers(options);
21+
const { isCSS, isRelativeCSS } = createMatchers(logger, options);
2022

2123
expect(isCSS('./myfile.module.css')).toBe(true);
2224
expect(isRelativeCSS('../folders/myfile.module.scss')).toBe(true);

src/helpers/createMatchers.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { createIsCSS, createIsRelativeCSS } from './cssExtensions';
22
import { Options } from '../options';
3+
import { Logger } from './logger';
34

4-
export const createMatchers = (options: Options = {}) => {
5+
export const createMatchers = (logger: Logger, options: Options = {}) => {
56
// Allow custom matchers to be used, and handle bad matcher patterns.
67
let isCSS = createIsCSS();
78
try {
@@ -11,6 +12,7 @@ export const createMatchers = (options: Options = {}) => {
1112
isCSS = createIsCSS(customMatcherRegExp);
1213
}
1314
} catch (e) {
15+
logger.error(e);
1416
// TODO: Provide error/warning to user.
1517
}
1618

0 commit comments

Comments
 (0)