Skip to content

Commit 8df67c9

Browse files
feat(#13): add custom lookup path option (#126)
* feat(#13): add custom lookup path option * feat: add typing of opt.resolve Co-authored-by: kamilic <kamilic.work@outlook.com>
1 parent ebd30f7 commit 8df67c9

File tree

6 files changed

+99
-30
lines changed

6 files changed

+99
-30
lines changed

README.md

+18
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,24 @@ Style of exported classnames, the keys in your json.
211211

212212
In lieu of a string, a custom function can generate the exported class names.
213213

214+
### Resolve path alias
215+
216+
You can rewrite paths for `composes/from` by using `resolve` options.
217+
It's useful when you need to resolve custom path alias.
218+
219+
```js
220+
postcss([
221+
require("postcss-modules")({
222+
resolve: function (file) {
223+
return file.replace(/^@/, process.cwd());
224+
},
225+
}),
226+
]);
227+
```
228+
229+
`resolve` may also return a `Promise<string>`.
230+
231+
214232
## Integration with templates
215233

216234
The plugin only transforms CSS classes to CSS modules.

index.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ declare interface Options {
4848
root?: string;
4949

5050
Loader?: typeof Loader;
51+
52+
resolve?: (file: string) => string | Promise<string>;
5153
}
5254

5355
declare interface PostcssModulesPlugin {

src/index.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,15 @@ module.exports = (opts = {}) => {
8585
const earlierPlugins = result.processor.plugins.slice(0, resultPluginIndex);
8686
const loaderPlugins = [...earlierPlugins, ...pluginList];
8787
const loader = getLoader(opts, loaderPlugins);
88-
const parser = new Parser(loader.fetch.bind(loader));
88+
const fetcher = ((file, relativeTo, depTrace) => {
89+
const resolvedResult = (typeof opts.resolve === 'function' && opts.resolve(file));
90+
const resolvedFile = resolvedResult instanceof Promise ? resolvedResult : Promise.resolve(resolvedResult);
91+
92+
return resolvedFile.then((f = file) => {
93+
return loader.fetch.call(loader, f || file, relativeTo, depTrace);
94+
});
95+
});
96+
const parser = new Parser(fetcher);
8997

9098
await postcss([...pluginList, parser.plugin()]).process(css, {
9199
from: inputFile,

test/__snapshots__/test.js.snap

+13
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,19 @@ exports[`processes globalModulePaths option: processes globalModulePaths option
303303

304304
exports[`processes hashPrefix option: processes hashPrefix option 1`] = `"._8xPWf {}"`;
305305

306+
exports[`processes resolve option: processes resolve option 1`] = `
307+
"._composes_a_another-mixin {
308+
display: flex;
309+
height: 100px;
310+
width: 200px;
311+
}._composes_a_hello {
312+
foo: bar;
313+
}._compose_resolve_figure {
314+
display: flex;
315+
}
316+
"
317+
`;
318+
306319
exports[`processes values: processes values - CSS 1`] = `
307320
"
308321

test/fixtures/in/compose.resolve.css

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.figure {
2+
composes: hello from "test-fixture-in/composes.a.css";
3+
display: flex;
4+
}

test/test.js

+53-29
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ const fixturesPath = path.resolve(__dirname, "./fixtures");
1010
function createPlugin(name, processor) {
1111
const plugin = () => ({
1212
postcssPlugin: name,
13-
Once: processor
14-
})
15-
plugin.postcss = true
16-
return plugin
13+
Once: processor,
14+
});
15+
plugin.postcss = true;
16+
return plugin;
1717
}
1818

1919
const cases = {
@@ -74,33 +74,35 @@ Object.keys(cases).forEach((name) => {
7474

7575
const plugins = [
7676
autoprefixer,
77-
createPlugin(
78-
'validator-1',
79-
(root) => {
80-
if (rootsSeenBeforePlugin.has(root)) {
81-
throw new Error('Plugin before ours was called multiple times.')
82-
}
83-
rootsSeenBeforePlugin.add(root);
84-
root.prepend(`/* validator-1-start (${path.basename(root.source.input.file)}) */`);
85-
root.append(`/* validator-1-end (${path.basename(root.source.input.file)}) */`);
77+
createPlugin("validator-1", (root) => {
78+
if (rootsSeenBeforePlugin.has(root)) {
79+
throw new Error("Plugin before ours was called multiple times.");
8680
}
87-
),
81+
rootsSeenBeforePlugin.add(root);
82+
root.prepend(
83+
`/* validator-1-start (${path.basename(root.source.input.file)}) */`
84+
);
85+
root.append(
86+
`/* validator-1-end (${path.basename(root.source.input.file)}) */`
87+
);
88+
}),
8889
plugin({
8990
scopeBehaviour,
9091
generateScopedName: scopedNameGenerator,
9192
getJSON: () => {},
9293
}),
93-
createPlugin(
94-
'validator-2',
95-
(root) => {
96-
if (rootsSeenAfterPlugin.has(root)) {
97-
throw new Error('Plugin after ours was called multiple times.')
98-
}
99-
rootsSeenAfterPlugin.add(root);
100-
root.prepend(`/* validator-2-start (${path.basename(root.source.input.file)}) */`);
101-
root.append(`/* validator-2-end (${path.basename(root.source.input.file)}) */`);
94+
createPlugin("validator-2", (root) => {
95+
if (rootsSeenAfterPlugin.has(root)) {
96+
throw new Error("Plugin after ours was called multiple times.");
10297
}
103-
),
98+
rootsSeenAfterPlugin.add(root);
99+
root.prepend(
100+
`/* validator-2-start (${path.basename(root.source.input.file)}) */`
101+
);
102+
root.append(
103+
`/* validator-2-end (${path.basename(root.source.input.file)}) */`
104+
);
105+
}),
104106
];
105107

106108
const result = await postcss(plugins).process(source, { from: sourceFile });
@@ -252,7 +254,6 @@ it("processes localsConvention with dashes option", async () => {
252254
});
253255
});
254256

255-
256257
it("processes localsConvention with function option", async () => {
257258
const sourceFile = path.join(fixturesPath, "in", "camelCase.css");
258259
const source = fs.readFileSync(sourceFile).toString();
@@ -261,17 +262,20 @@ it("processes localsConvention with function option", async () => {
261262
if (fs.existsSync(jsonFile)) fs.unlinkSync(jsonFile);
262263

263264
await postcss([
264-
plugin({ generateScopedName, localsConvention: (className) => {
265-
return className.replace('camel-case', 'cc');
266-
} }),
265+
plugin({
266+
generateScopedName,
267+
localsConvention: (className) => {
268+
return className.replace("camel-case", "cc");
269+
},
270+
}),
267271
]).process(source, { from: sourceFile });
268272

269273
const json = fs.readFileSync(jsonFile).toString();
270274
fs.unlinkSync(jsonFile);
271275

272276
expect(JSON.parse(json)).toMatchObject({
273277
cc: "_camelCase_camel-case",
274-
'cc-extra': "_camelCase_camel-case-extra",
278+
"cc-extra": "_camelCase_camel-case-extra",
275279
FooBar: "_camelCase_FooBar",
276280
});
277281
});
@@ -383,3 +387,23 @@ it("processes exportGlobals option", async () => {
383387
article: "_classes_article",
384388
});
385389
});
390+
391+
it("processes resolve option", async () => {
392+
const sourceFile = path.join(fixturesPath, "in", "compose.resolve.css");
393+
const source = fs.readFileSync(sourceFile).toString();
394+
let json;
395+
const result = await postcss([
396+
plugin({
397+
generateScopedName,
398+
resolve: async (file) => {
399+
return file.replace(/test-fixture-in/, path.dirname(sourceFile));
400+
},
401+
getJSON: (_, result) => {
402+
json = result;
403+
},
404+
}),
405+
]).process(source, { from: sourceFile });
406+
407+
expect(result.css).toMatchSnapshot("processes resolve option");
408+
expect(json).toMatchObject({"figure": "_compose_resolve_figure _composes_a_hello"});
409+
});

0 commit comments

Comments
 (0)