Skip to content

Commit 49c5bd0

Browse files
authored
fix: improve Sass support for tilde imports (#106)
1 parent c6b4fd3 commit 49c5bd0

File tree

3 files changed

+64
-12
lines changed

3 files changed

+64
-12
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,11 +198,11 @@ The `classes` object represents all the classnames extracted from the CSS Module
198198

199199
#### `rendererOptions`
200200

201-
| Option | Default value | Description |
202-
| -------- | ------------- | ------------------------------------------------------------------------------------ |
203-
| `less` | `{}` | Set [renderer options for Less](http://lesscss.org/usage/#less-options). |
204-
| `sass` | `{}` | Set [renderer options for Sass](https://sass-lang.com/documentation/js-api#options). |
205-
| `stylus` | `{}` | Set [renderer options for Stylus](https://stylus.bootcss.com/docs/js.html). |
201+
| Option | Default value | Description |
202+
| -------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
203+
| `less` | `{}` | Set [renderer options for Less](http://lesscss.org/usage/#less-options). |
204+
| `sass` | `{ enableWebpackTildeImports: true }` | Set [renderer options for Sass](https://sass-lang.com/documentation/js-api#options). The `enableWebpackTildeImports` property enables support for [Webpack's tilde-prefixed imports](https://webpack.js.org/loaders/css-loader/#import). |
205+
| `stylus` | `{}` | Set [renderer options for Stylus](https://stylus.bootcss.com/docs/js.html). |
206206

207207
> For convenience, `includePaths` for Sass are extended, not replaced. The defaults are the path of the current file, and `'node_modules'`.
208208

src/helpers/getClasses.ts

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import path from 'path';
2+
import fs from 'fs';
23
import postcss from 'postcss';
34
import less from 'less';
45
import sass from 'sass';
@@ -27,6 +28,50 @@ export const getFileType = (fileName: string) => {
2728

2829
const getFilePath = (fileName: string) => path.dirname(fileName);
2930

31+
// Creates a sass importer which resolves Webpack-style tilde-imports
32+
const webpackTildeSupportingImporter: sass.Importer = (
33+
rawImportPath: string,
34+
source: string,
35+
) => {
36+
// We only care about tilde-prefixed imports that do not look like paths
37+
if (!rawImportPath.startsWith('~') || rawImportPath.startsWith('~/')) {
38+
return null;
39+
}
40+
41+
// Create subpathsWithExts such that it has entries of the form
42+
// node_modules/@foo/bar/baz.(scss|sass)
43+
// for an import of the form ~@foo/bar/baz(.(scss|sass))?
44+
const nodeModSubpath = path.join('node_modules', rawImportPath.substring(1));
45+
const subpathsWithExts: string[] = [];
46+
if (nodeModSubpath.endsWith('.scss') || nodeModSubpath.endsWith('.sass')) {
47+
subpathsWithExts.push(nodeModSubpath);
48+
} else {
49+
// Look for .scss first
50+
subpathsWithExts.push(`${nodeModSubpath}.scss`, `${nodeModSubpath}.sass`);
51+
}
52+
53+
// Climbs the filesystem tree until we get to the root, looking for the first
54+
// node_modules directory which has a matching module and filename.
55+
let prevDir = '';
56+
let dir = path.dirname(source);
57+
while (prevDir !== dir) {
58+
const searchPaths = subpathsWithExts.map((subpathWithExt) =>
59+
path.join(dir, subpathWithExt),
60+
);
61+
for (const searchPath of searchPaths) {
62+
if (fs.existsSync(searchPath)) {
63+
return { file: searchPath };
64+
}
65+
}
66+
prevDir = dir;
67+
dir = path.dirname(dir);
68+
}
69+
70+
// Returning null is not itself an error, it tells sass to instead try the
71+
// next import resolution method if one exists
72+
return null;
73+
};
74+
3075
export const getClasses = ({
3176
css,
3277
fileName,
@@ -71,7 +116,8 @@ export const getClasses = ({
71116
);
72117
} else if (fileType === FileTypes.scss || fileType === FileTypes.sass) {
73118
const filePath = getFilePath(fileName);
74-
const { includePaths, ...sassOptions } = rendererOptions.sass || {};
119+
const { includePaths, enableWebpackTildeImports, ...sassOptions } =
120+
rendererOptions.sass || {};
75121
const { baseUrl, paths } = compilerOptions;
76122
const matchPath =
77123
baseUrl && paths ? createMatchPath(path.resolve(baseUrl), paths) : null;
@@ -81,15 +127,17 @@ export const getClasses = ({
81127
return newUrl ? { file: newUrl } : null;
82128
};
83129

130+
const importers = [aliasImporter];
131+
if (enableWebpackTildeImports !== false) {
132+
importers.push(webpackTildeSupportingImporter);
133+
}
134+
84135
transformedCss = sass
85136
.renderSync({
86-
// NOTE: Solves an issue where tilde imports fail.
87-
// https://github.com/sass/dart-sass/issues/801
88-
// Will strip `~` from imports, unless followed by a slash.
89-
data: css.replace(/(@import ['"])~(?!\/)/gm, '$1'),
137+
file: fileName,
90138
indentedSyntax: fileType === FileTypes.sass,
91139
includePaths: [filePath, 'node_modules', ...(includePaths || [])],
92-
importer: [aliasImporter],
140+
importer: importers,
93141
...sassOptions,
94142
})
95143
.css.toString();

src/options.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@ export interface PostCssOptions {
1313
useConfig?: boolean;
1414
}
1515

16+
export interface ExtraSassOptions {
17+
enableWebpackTildeImports?: boolean;
18+
}
19+
1620
export interface RendererOptions {
1721
less?: Partial<Less.Options>;
18-
sass?: Partial<SassOptions>;
22+
sass?: Partial<SassOptions> & ExtraSassOptions;
1923
stylus?: Partial<StylusRenderOptions>;
2024
}
2125

0 commit comments

Comments
 (0)