Skip to content

Commit 75f3054

Browse files
authored
fix: improve logic in Sass tilde importer (#193)
1 parent e570031 commit 75f3054

File tree

2 files changed

+52
-54
lines changed

2 files changed

+52
-54
lines changed

src/importers/__tests__/sassTildeImporter.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { join } from 'path';
2-
import { sassTildeImporter } from '../sassTildeImporter';
2+
import { sassTildeImporter, resolveUrls } from '../sassTildeImporter';
33

44
const getAbsoluteFileUrl = (expected: string) =>
55
`file://${join(process.cwd(), expected)}`;
@@ -59,6 +59,12 @@ describe('importers / sassTildeImporter', () => {
5959
})
6060
?.toString(),
6161
).toBe(getAbsoluteFileUrl('node_modules/bootstrap/scss/_grid.scss'));
62+
expect(resolveUrls('~sass-mq/mq.scss')).toContain(
63+
'node_modules/sass-mq/_mq.scss',
64+
);
65+
expect(resolveUrls('~sass-mq/mq')).toContain(
66+
'node_modules/sass-mq/_mq.scss',
67+
);
6268
});
6369

6470
it('should resolve index files', () => {

src/importers/sassTildeImporter.ts

Lines changed: 45 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,66 +2,58 @@ import path from 'path';
22
import fs from 'fs';
33
import sass from 'sass';
44

5+
const DEFAULT_EXTS = ['scss', 'sass', 'css'];
6+
7+
export function resolveUrls(url: string, extensions: string[] = DEFAULT_EXTS) {
8+
// We only care about tilde-prefixed imports that do not look like paths.
9+
if (!url.startsWith('~') || url.startsWith('~/')) {
10+
return [];
11+
}
12+
13+
const module_path = path.join('node_modules', url.substring(1));
14+
let variants = [module_path];
15+
16+
const parts = path.parse(module_path);
17+
18+
// Support sass partials by including paths where the file is prefixed by an underscore.
19+
if (!parts.base.startsWith('_')) {
20+
const underscore_name = '_'.concat(parts.name);
21+
const replacement = {
22+
root: parts.root,
23+
dir: parts.dir,
24+
ext: parts.ext,
25+
base: `${underscore_name}${parts.ext}`,
26+
name: underscore_name,
27+
};
28+
variants.push(path.format(replacement));
29+
}
30+
31+
// Support index files.
32+
variants.push(path.join(module_path, '_index'));
33+
34+
// Create variants such that it has entries of the form
35+
// node_modules/@foo/bar/baz.(scss|sass)
36+
// for an import of the form ~@foo/bar/baz(.(scss|sass))?
37+
if (!extensions.some((ext) => parts.ext == `.${ext}`)) {
38+
variants = extensions.flatMap((ext) =>
39+
variants.map((variant) => `${variant}.${ext}`),
40+
);
41+
}
42+
43+
return variants;
44+
}
45+
546
/**
647
* Creates a sass importer which resolves Webpack-style tilde-imports.
748
*/
849
export const sassTildeImporter: sass.FileImporter<'sync'> = {
950
findFileUrl(url) {
10-
// We only care about tilde-prefixed imports that do not look like paths.
11-
if (!url.startsWith('~') || url.startsWith('~/')) {
12-
return null;
13-
}
14-
15-
// Create subpathsWithExts such that it has entries of the form
16-
// node_modules/@foo/bar/baz.(scss|sass)
17-
// for an import of the form ~@foo/bar/baz(.(scss|sass))?
18-
const nodeModSubpath = path.join('node_modules', url.substring(1));
19-
const subpathsWithExts: string[] = [];
20-
if (
21-
nodeModSubpath.endsWith('.scss') ||
22-
nodeModSubpath.endsWith('.sass') ||
23-
nodeModSubpath.endsWith('.css')
24-
) {
25-
subpathsWithExts.push(nodeModSubpath);
26-
} else {
27-
// Look for .scss first.
28-
subpathsWithExts.push(
29-
`${nodeModSubpath}.scss`,
30-
`${nodeModSubpath}.sass`,
31-
`${nodeModSubpath}.css`,
32-
);
33-
}
34-
35-
// Support index files.
36-
subpathsWithExts.push(
37-
`${nodeModSubpath}/_index.scss`,
38-
`${nodeModSubpath}/_index.sass`,
39-
);
40-
41-
// Support sass partials by including paths where the file is prefixed by an underscore.
42-
const basename = path.basename(nodeModSubpath);
43-
if (!basename.startsWith('_')) {
44-
const partials = subpathsWithExts.map((file) =>
45-
file.replace(basename, `_${basename}`),
46-
);
47-
subpathsWithExts.push(...partials);
48-
}
51+
const searchPaths = resolveUrls(url);
4952

50-
// Climbs the filesystem tree until we get to the root, looking for the first
51-
// node_modules directory which has a matching module and filename.
52-
let prevDir = '';
53-
let dir = path.dirname(url);
54-
while (prevDir !== dir) {
55-
const searchPaths = subpathsWithExts.map((subpathWithExt) =>
56-
path.join(dir, subpathWithExt),
57-
);
58-
for (const searchPath of searchPaths) {
59-
if (fs.existsSync(searchPath)) {
60-
return new URL(`file://${path.resolve(searchPath)}`);
61-
}
53+
for (const searchPath of searchPaths) {
54+
if (fs.existsSync(searchPath)) {
55+
return new URL(`file://${path.resolve(searchPath)}`);
6256
}
63-
prevDir = dir;
64-
dir = path.dirname(dir);
6557
}
6658

6759
// Returning null is not itself an error, it tells sass to instead try the

0 commit comments

Comments
 (0)