Skip to content
This repository was archived by the owner on Mar 17, 2021. It is now read-only.

Commit 017adc7

Browse files
Pimmshellscape
authored andcommitted
feat: support fallback loader in options.fallback (#123)
Resolves #118 It is now possible to explicitly specify options for the fallback loader. The new definition (schema) for the fallback option is a lighter variant of the one for module.rules.use. See schemas/WebpackOptions.json in the webpack repository.
1 parent 9946374 commit 017adc7

File tree

7 files changed

+255
-4
lines changed

7 files changed

+255
-4
lines changed

src/index.js

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import { getOptions } from 'loader-utils';
88
import validateOptions from '@webpack-contrib/schema-utils';
99
import mime from 'mime';
10+
import normalizeFallback from './utils/normalizeFallback';
1011
import schema from './options.json';
1112

1213
// Loader Mode
@@ -39,7 +40,23 @@ export default function loader(src) {
3940
)}`;
4041
}
4142

42-
const fallback = require(options.fallback ? options.fallback : 'file-loader');
43-
44-
return fallback.call(this, src);
43+
// Normalize the fallback.
44+
const { loader: fallbackLoader, query: fallbackQuery } = normalizeFallback(
45+
options.fallback,
46+
options
47+
);
48+
49+
// Require the fallback.
50+
const fallback = require(fallbackLoader);
51+
52+
// Call the fallback, passing a copy of the loader context. The copy has the query replaced. This way, the fallback
53+
// loader receives the query which was intended for it instead of the query which was intended for url-loader.
54+
const fallbackLoaderContext = Object.assign({}, this, {
55+
query: fallbackQuery,
56+
});
57+
// Delete "options". "options" was deprecated in webpack 3, and removed in webpack 4. When support for webpack 3 is
58+
// dropped, we can safely assume the fallback loader won't look at "options" and remove this line.
59+
delete fallbackLoaderContext.options;
60+
61+
return fallback.call(fallbackLoaderContext, src);
4562
}

src/options.json

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,32 @@
88
"type": "string"
99
},
1010
"fallback": {
11-
"type": "string"
11+
"anyOf": [
12+
{
13+
"type": "string"
14+
},
15+
{
16+
"additionalProperties": false,
17+
"properties": {
18+
"loader": {
19+
"description": "Fallback loader name",
20+
"type": "string"
21+
},
22+
"options": {
23+
"description": "Fallback loader options",
24+
"anyOf": [
25+
{
26+
"type": "object"
27+
},
28+
{
29+
"type": "string"
30+
}
31+
]
32+
}
33+
},
34+
"type": "object"
35+
}
36+
]
1237
}
1338
},
1439
"additionalProperties": true

src/utils/normalizeFallback.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
function normalizeFallbackString(fallbackString, originalOptions) {
2+
const index = fallbackString.indexOf('?');
3+
if (index >= 0) {
4+
return {
5+
loader: fallbackString.substr(0, index),
6+
query: fallbackString.substr(index),
7+
};
8+
}
9+
10+
// To remain consistent with version 1.0.1, pass the options which were provided to url-loader to the fallback loader.
11+
// Perhaps it would make sense to strip out ‒ or "consume" ‒ the options we know were meant for url-loader: limit and
12+
// mimetype.
13+
return {
14+
loader: fallbackString,
15+
query: originalOptions,
16+
};
17+
}
18+
19+
function normalizeFallbackObject(fallbackObject) {
20+
return {
21+
loader: fallbackObject.loader,
22+
query: fallbackObject.options,
23+
};
24+
}
25+
26+
/**
27+
* Converts the fallback option, which can be a string or an object, to an object with a loader and a query. The result
28+
* has this form:
29+
* {
30+
* loader: 'file-loader',
31+
* query: '?name=[name].[ext]'
32+
* }
33+
* Note that the returned query can be either a string or an object.
34+
*/
35+
export default function normalizeFallback(fallback, originalOptions) {
36+
// If no fallback was provided, use file-loader.
37+
if (!fallback) {
38+
return {
39+
loader: 'file-loader',
40+
};
41+
}
42+
43+
if (typeof fallback === 'string') {
44+
return normalizeFallbackString(fallback, originalOptions);
45+
}
46+
47+
return normalizeFallbackObject(fallback);
48+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

33
exports[`Options fallback {String} 1`] = `"module.exports = __webpack_public_path__ + \\"9c87cbf3ba33126ffd25ae7f2f6bbafb.png\\";"`;
4+
5+
exports[`Options fallback {String} 2`] = `"module.exports = __webpack_public_path__ + \\"file.png\\";"`;
6+
7+
exports[`Options fallback {String} 3`] = `"module.exports = __webpack_public_path__ + \\"name-for-file-loader.png\\";"`;
8+
9+
exports[`Options fallback {String} 4`] = `"module.exports = __webpack_public_path__ + \\"name-for-file-loader.png\\";"`;

test/options/fallback.test.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,66 @@ describe('Options', () => {
2121

2222
expect(source).toMatchSnapshot();
2323
});
24+
25+
// Version 1.0.1 passes options provided to url-loader to the fallback as well, so make sure that still works.
26+
test('{String}', async () => {
27+
const config = {
28+
loader: {
29+
test: /\.png$/,
30+
options: {
31+
limit: 100,
32+
fallback: 'file-loader',
33+
name: '[name].[ext]',
34+
},
35+
},
36+
};
37+
38+
const stats = await webpack('fixture.js', config);
39+
const { source } = stats.toJson().modules[0];
40+
41+
expect(source).toMatchSnapshot();
42+
});
43+
44+
// Test passing explicitly provided options to the fallback loader.
45+
test('{String}', async () => {
46+
const config = {
47+
loader: {
48+
test: /\.png$/,
49+
options: {
50+
limit: 100,
51+
name: 'name-for-url-loader.[ext]',
52+
fallback: {
53+
loader: 'file-loader',
54+
options: {
55+
name: 'name-for-file-loader.[ext]',
56+
},
57+
},
58+
},
59+
},
60+
};
61+
62+
const stats = await webpack('fixture.js', config);
63+
const { source } = stats.toJson().modules[0];
64+
65+
expect(source).toMatchSnapshot();
66+
});
67+
68+
test('{String}', async () => {
69+
const config = {
70+
loader: {
71+
test: /\.png$/,
72+
options: {
73+
limit: 100,
74+
name: 'name-for-url-loader.[ext]',
75+
fallback: 'file-loader?name=name-for-file-loader.[ext]',
76+
},
77+
},
78+
};
79+
80+
const stats = await webpack('fixture.js', config);
81+
const { source } = stats.toJson().modules[0];
82+
83+
expect(source).toMatchSnapshot();
84+
});
2485
});
2586
});
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`normalizeFallback object 1`] = `
4+
Object {
5+
"loader": "file-loader",
6+
"query": undefined,
7+
}
8+
`;
9+
10+
exports[`normalizeFallback object-with-options 1`] = `
11+
Object {
12+
"loader": "file-loader",
13+
"query": Object {
14+
"name": "name-for-file-loader.[ext]",
15+
},
16+
}
17+
`;
18+
19+
exports[`normalizeFallback string 1`] = `
20+
Object {
21+
"loader": "file-loader",
22+
"query": Object {
23+
"limit": 8192,
24+
"name": "name-for-url-loader.[ext]",
25+
},
26+
}
27+
`;
28+
29+
exports[`normalizeFallback string-with-query 1`] = `
30+
Object {
31+
"loader": "file-loader",
32+
"query": "?name=name-for-file-loader.[ext]",
33+
}
34+
`;
35+
36+
exports[`normalizeFallback undefined 1`] = `
37+
Object {
38+
"loader": "file-loader",
39+
}
40+
`;

test/utils/normalizeFallback.test.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/* eslint-disable
2+
prefer-destructuring,
3+
*/
4+
import normalizeFallback from '../../src/utils/normalizeFallback';
5+
6+
describe('normalizeFallback', () => {
7+
test('undefined', () => {
8+
const result = normalizeFallback(undefined, {
9+
limit: 8192,
10+
name: 'name-for-url-loader.[ext]',
11+
});
12+
13+
expect(result).toMatchSnapshot();
14+
});
15+
16+
test('string', () => {
17+
const result = normalizeFallback('file-loader', {
18+
limit: 8192,
19+
name: 'name-for-url-loader.[ext]',
20+
});
21+
22+
expect(result).toMatchSnapshot();
23+
});
24+
25+
test('string-with-query', () => {
26+
const result = normalizeFallback(
27+
'file-loader?name=name-for-file-loader.[ext]',
28+
{ limit: 8192, name: 'name-for-url-loader.[ext]' }
29+
);
30+
31+
expect(result).toMatchSnapshot();
32+
});
33+
34+
test('object', () => {
35+
const result = normalizeFallback(
36+
{ loader: 'file-loader' },
37+
{ limit: 8192, name: 'name-for-url-loader.[ext]' }
38+
);
39+
40+
expect(result).toMatchSnapshot();
41+
});
42+
43+
test('object-with-options', () => {
44+
const result = normalizeFallback(
45+
{
46+
loader: 'file-loader',
47+
options: { name: 'name-for-file-loader.[ext]' },
48+
},
49+
{ limit: 8192, name: 'name-for-url-loader.[ext]' }
50+
);
51+
52+
expect(result).toMatchSnapshot();
53+
});
54+
});

0 commit comments

Comments
 (0)