Skip to content

Add extended Sass support #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ Once installed, add this plugin to your `tsconfig.json`:

### Options

| Option | Default value | Description |
| --------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `customMatcher` | `"\\.module\\.(sa\|sc\|c)ss$"` | Change the file extensions that this plugin works with. |
| `camelCase` | `false` | Implements the behaviour of the [`camelCase` CSS Loader option](https://github.com/webpack-contrib/css-loader#camelcase) (accepting the same values). |
| Option | Default value | Description |
| --------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `customMatcher` | `"\\.module\\.(c\|le\|sa\|sc)ss$"` | Change the file extensions that this plugin works with. |
| `camelCase` | `false` | Implements the behaviour of the [`camelCase` CSS Loader option](https://github.com/webpack-contrib/css-loader#camelcase) (accepting the same values). |

The below is an example that only matches "\*.m.css" files, and [camel-cases dashes](https://github.com/webpack-contrib/css-loader#camelcase).

Expand All @@ -65,17 +65,16 @@ The below is an example that only matches "\*.m.css" files, and [camel-cases das

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

1. Add this plugin to `"typescript.tsserver.pluginPaths"` in settings. Note that this method doesn't currently support
plugin options. This is planned for the [November update](https://github.com/Microsoft/vscode/issues/62876).
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).

2. Add this plugin to `"typescript.tsserver.pluginPaths"` in settings. Note that this method doesn't currently support plugin options.

```json
{
"typescript.tsserver.pluginPaths": ["typescript-plugin-css-modules"]
}
```

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

### Custom definitions

_Note: Create React App users can skip this section if you're using `[email protected]` or higher._
Expand All @@ -84,7 +83,7 @@ If your project doesn't already have global declarations for CSS Modules, you wi

Where you store global declarations is up to you. An example might look like: `src/custom.d.ts`.

The below is an example that you can copy, or modify if you use a `customMatcher`.
The below is an example that you can copy or modify. If you use a `customMatcher`, you'll need to modify it.

```ts
declare module '*.module.css' {
Expand All @@ -101,4 +100,9 @@ declare module '*.module.sass' {
const classes: { [key: string]: string };
export default classes;
}

declare module '*.module.less' {
const classes: { [key: string]: string };
export default classes;
}
```
28 changes: 14 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
],
"scripts": {
"build": "rm -rf ./lib && tsc",
"prepare": "yarn build",
"prepublishOnly": "yarn build",
"test": "jest ./src"
},
"husky": {
Expand All @@ -42,24 +42,24 @@
"trailingComma": "all"
},
"dependencies": {
"icss-utils": "^4.0.0",
"icss-utils": "^4.1.0",
"lodash": "^4.17.11",
"postcss": "^7.0.7",
"postcss": "^7.0.16",
"postcss-icss-selectors": "^2.0.3",
"postcss-nested": "^4.1.1",
"strip-css-singleline-comments": "^1.1.0"
"sass": "^1.20.1"
},
"devDependencies": {
"@types/jest": "^23.3.12",
"@types/lodash": "^4.14.119",
"@types/jest": "^24.0.13",
"@types/lodash": "^4.14.132",
"@types/node": "^10.12.18",
"husky": "^1.3.1",
"jest": "^23.6.0",
"prettier": "^1.15.3",
"pretty-quick": "^1.8.0",
"ts-jest": "^23.10.5",
"tslint": "^5.12.0",
"typescript": "^3.2.2"
"@types/sass": "^1.16.0",
"husky": "^2.3.0",
"jest": "^24.8.0",
"prettier": "^1.17.1",
"pretty-quick": "^1.11.0",
"ts-jest": "^24.0.2",
"tslint": "^5.16.0",
"typescript": "^3.4.5"
},
"peerDependencies": {
"typescript": "^3.0.0"
Expand Down
18 changes: 18 additions & 0 deletions src/helpers/__tests__/__snapshots__/cssSnapshots.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ exports[`utils / cssSnapshots with file 'test.module.scss' createExports should
'nested-class-parent': string;
'child-class': string;
'nested-class-parent--extended': string;
'section-1': string;
'section-2': string;
'section-3': string;
'section-4': string;
'section-5': string;
'section-6': string;
'section-7': string;
'section-8': string;
'section-9': string;
};
export default classes;
"
Expand All @@ -59,5 +68,14 @@ Object {
"local-class-inside-local": "file__local-class-inside-local---QdL6b",
"nested-class-parent": "file__nested-class-parent---_ft7G",
"nested-class-parent--extended": "file__nested-class-parent--extended---1642l",
"section-1": "file__section-1---2EiKX",
"section-2": "file__section-2---2f4aZ",
"section-3": "file__section-3---R_Ilj",
"section-4": "file__section-4---3EjYO",
"section-5": "file__section-5---1DSe8",
"section-6": "file__section-6---1RoVP",
"section-7": "file__section-7---l5yMj",
"section-8": "file__section-8---3FEWv",
"section-9": "file__section-9---1TFYE",
}
`;
1 change: 1 addition & 0 deletions src/helpers/__tests__/classTransforms.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { transformClasses } from '../classTransforms';
import { CamelCaseOptions } from '../../options';

describe('utils / classTransforms', () => {
const classNames = [
Expand Down
5 changes: 3 additions & 2 deletions src/helpers/__tests__/createMatchers.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { createMatchers } from '../createMatchers';
import { Options } from '../../options';

describe('utils / createMatchers', () => {
it('should match `customMatcher` regexp', () => {
const options: IOptions = { customMatcher: '\\.css$' };
const options: Options = { customMatcher: '\\.css$' };
const { isCSS, isRelativeCSS } = createMatchers(options);

expect(isCSS('./myfile.css')).toBe(true);
Expand All @@ -14,7 +15,7 @@ describe('utils / createMatchers', () => {
});

it('should handle bad `customMatcher` regexp', () => {
const options: IOptions = { customMatcher: '$([a' };
const options: Options = { customMatcher: '$([a' };
const { isCSS, isRelativeCSS } = createMatchers(options);

expect(isCSS('./myfile.module.css')).toBe(true);
Expand Down
5 changes: 5 additions & 0 deletions src/helpers/__tests__/fixtures/test.module.css
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
.classA {
color: rebeccapurple;
}

.ClassB {
color: rebeccapurple;
}

.class-c {
color: rebeccapurple;
}

.parent {
.childA {
color: rebeccapurple;
}
.childB {
.nestedChild {
color: rebeccapurple;
}
}
}
20 changes: 20 additions & 0 deletions src/helpers/__tests__/fixtures/test.module.scss
Original file line number Diff line number Diff line change
@@ -1,26 +1,46 @@
:global .global-class {
color: rebeccapurple;
}

:global(.global-class-2) {
.local-class-inside-global {
color: rebeccapurple;
}
}

:local .local-class {
color: rebeccapurple;
}

:local(.local-class-2) {
.local-class-inside-local {
color: rebeccapurple;
}
}

.nested-class-parent {
.child-class {
color: rebeccapurple;
}
&--extended {
color: rebeccapurple;
}
}

$color: rebeccapurple !default;

@for $section from 1 to 10 {
.section-#{$section} {
color: $color;
}
}

// .commented-parent-class {
// .commented-child-class
// }

/*
.commented-parent-class {
.commented-child-class
}
*/
3 changes: 2 additions & 1 deletion src/helpers/createMatchers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createIsCSS, createIsRelativeCSS } from './cssExtensions';
import { Options } from '../options';

export const createMatchers = (options: IOptions = {}) => {
export const createMatchers = (options: Options = {}) => {
// Allow custom matchers to be used, and handle bad matcher patterns.
let isCSS = createIsCSS();
try {
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/cssExtensions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export type isCSSFn = (fileName: string) => boolean;
const DEFAULT_REGEXP = /\.module\.(sa|sc|c)ss$/;
const DEFAULT_REGEXP = /\.module\.(c|le|sa|sc)ss$/;

const isRelative = (fileName: string) => /^\.\.?($|[\\/])/.test(fileName);

Expand Down
36 changes: 20 additions & 16 deletions src/helpers/cssSnapshots.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
import { extractICSS, IICSSExports } from 'icss-utils';
import * as postcss from 'postcss';
import * as postcssIcssSelectors from 'postcss-icss-selectors';
import * as postcssNested from 'postcss-nested';
import * as strip from 'strip-css-singleline-comments/sync';
import * as ts_module from 'typescript/lib/tsserverlibrary';
import * as sass from 'sass';
import { transformClasses } from './classTransforms';
import { Options } from '../options';

const processor = postcss(
postcssNested,
postcssIcssSelectors({ mode: 'local' }),
);
const processor = postcss(postcssIcssSelectors({ mode: 'local' }));

export const getClasses = (css: string) => {
const classNameToProperty = (className: string) => `'${className}': string;`;

const flattenClassNames = (
previousValue: string[] = [],
currentValue: string[],
) => previousValue.concat(currentValue);

export const getClasses = (css: string, isLess: boolean = false) => {
try {
const cleanCss = strip(css);
const processedCss = processor.process(cleanCss);
let transformedCss: string;
if (isLess) {
transformedCss = '';
} else {
transformedCss = sass.renderSync({ data: css }).css.toString();
}
const processedCss = processor.process(transformedCss);
return extractICSS(processedCss.root).icssExports;
} catch (e) {
return {};
}
};
const classNameToProperty = (className: string) => `'${className}': string;`;
const flattenClassNames = (
previousValue: string[] = [],
currentValue: string[],
) => previousValue.concat(currentValue);

export const createExports = (classes: IICSSExports, options: IOptions) => `\
export const createExports = (classes: IICSSExports, options: Options) => `\
declare const classes: {
${Object.keys(classes)
.map(transformClasses(options.camelCase))
Expand All @@ -40,7 +44,7 @@ export default classes;
export const getDtsSnapshot = (
ts: typeof ts_module,
scriptSnapshot: ts.IScriptSnapshot,
options: IOptions,
options: Options,
) => {
const css = scriptSnapshot.getText(0, scriptSnapshot.getLength());
const classes = getClasses(css);
Expand Down
5 changes: 4 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ import * as ts_module from 'typescript/lib/tsserverlibrary';
import { createMatchers } from './helpers/createMatchers';
import { isCSSFn } from './helpers/cssExtensions';
import { getDtsSnapshot } from './helpers/cssSnapshots';
import { Options } from './options';

function init({ typescript: ts }: { typescript: typeof ts_module }) {
let _isCSS: isCSSFn;
function create(info: ts.server.PluginCreateInfo) {
// User options for plugin.
const options: IOptions = info.config.options || {};
const options: Options = info.config.options || {};

// Create matchers using options object.
const { isCSS, isRelativeCSS } = createMatchers(options);
_isCSS = isCSS;

info.project.projectService.logger.info(`******** isCSS: ${isCSS}`);

// Creates new virtual source files for the CSS modules.
const _createLanguageServiceSourceFile = ts.createLanguageServiceSourceFile;
ts.createLanguageServiceSourceFile = (
Expand Down
4 changes: 2 additions & 2 deletions src/@types/global.d.ts → src/options.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
declare interface IOptions {
export interface Options {
camelCase?: CamelCaseOptions;
customMatcher?: string;
}

declare type CamelCaseOptions =
export type CamelCaseOptions =
| true
| 'dashes'
| 'dashesOnly'
Expand Down
Loading