Skip to content

Commit d49629b

Browse files
committed
refactor importer and add tests
1 parent 0482bd5 commit d49629b

File tree

2 files changed

+51
-58
lines changed

2 files changed

+51
-58
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: 44 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2,70 +2,57 @@ 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+
variants.push(path.join(module_path, '_index'));
32+
33+
// Create variants such that it has entries of the form
34+
// node_modules/@foo/bar/baz.(scss|sass)
35+
// for an import of the form ~@foo/bar/baz(.(scss|sass))?
36+
if (!extensions.some((ext) => parts.ext == `.${ext}`)) {
37+
variants = extensions.flatMap((ext) =>
38+
variants.map((variant) => `${variant}.${ext}`),
39+
);
40+
}
41+
42+
return variants;
43+
}
44+
545
/**
646
* Creates a sass importer which resolves Webpack-style tilde-imports.
747
*/
848
export const sassTildeImporter: sass.FileImporter<'sync'> = {
949
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-
const parts = path.parse(file);
46-
const replacement = '_'.concat(parts.name);
47-
parts.base = parts.base.replace(parts.name, replacement);
48-
parts.name = replacement;
49-
return path.format(parts);
50-
});
51-
subpathsWithExts.push(...partials);
52-
}
50+
const searchPaths = resolveUrls(url);
5351

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

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

0 commit comments

Comments
 (0)