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

feat(index): support fallback loader options (options.fallback) #123

Merged
merged 1 commit into from
Jun 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { getOptions } from 'loader-utils';
import validateOptions from 'schema-utils';
import mime from 'mime';
import normalizeFallback from './utils/normalizeFallback';
import schema from './options.json';

// Loader Mode
Expand Down Expand Up @@ -39,7 +40,23 @@ export default function loader(src) {
)}`;
}

const fallback = require(options.fallback ? options.fallback : 'file-loader');

return fallback.call(this, src);
// Normalize the fallback.
const { loader: fallbackLoader, query: fallbackQuery } = normalizeFallback(
options.fallback,
options
);

// Require the fallback.
const fallback = require(fallbackLoader);

// Call the fallback, passing a copy of the loader context. The copy has the query replaced. This way, the fallback
// loader receives the query which was intended for it instead of the query which was intended for url-loader.
const fallbackLoaderContext = Object.assign({}, this, {
query: fallbackQuery,
});
// Delete "options". "options" was deprecated in webpack 3, and removed in webpack 4. When support for webpack 3 is
// dropped, we can safely assume the fallback loader won't look at "options" and remove this line.
delete fallbackLoaderContext.options;

return fallback.call(fallbackLoaderContext, src);
}
27 changes: 26 additions & 1 deletion src/options.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,32 @@
"type": "string"
},
"fallback": {
"type": "string"
"anyOf": [
{
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"loader": {
"description": "Fallback loader name",
"type": "string"
},
"options": {
"description": "Fallback loader options",
"anyOf": [
{
"type": "object"
},
{
"type": "string"
}
]
}
},
"type": "object"
}
]
}
},
"additionalProperties": true
Expand Down
48 changes: 48 additions & 0 deletions src/utils/normalizeFallback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
function normalizeFallbackString(fallbackString, originalOptions) {
const index = fallbackString.indexOf('?');
if (index >= 0) {
return {
loader: fallbackString.substr(0, index),
query: fallbackString.substr(index),
};
}

// To remain consistent with version 1.0.1, pass the options which were provided to url-loader to the fallback loader.
// Perhaps it would make sense to strip out ‒ or "consume" ‒ the options we know were meant for url-loader: limit and
// mimetype.
return {
loader: fallbackString,
query: originalOptions,
};
}

function normalizeFallbackObject(fallbackObject) {
return {
loader: fallbackObject.loader,
query: fallbackObject.options,
};
}

/**
* Converts the fallback option, which can be a string or an object, to an object with a loader and a query. The result
* has this form:
* {
* loader: 'file-loader',
* query: '?name=[name].[ext]'
* }
* Note that the returned query can be either a string or an object.
*/
export default function normalizeFallback(fallback, originalOptions) {
// If no fallback was provided, use file-loader.
if (!fallback) {
return {
loader: 'file-loader',
};
}

if (typeof fallback === 'string') {
return normalizeFallbackString(fallback, originalOptions);
}

return normalizeFallbackObject(fallback);
}
6 changes: 6 additions & 0 deletions test/options/__snapshots__/fallback.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Options fallback {String} 1`] = `"module.exports = __webpack_public_path__ + \\"9c87cbf3ba33126ffd25ae7f2f6bbafb.png\\";"`;

exports[`Options fallback {String} 2`] = `"module.exports = __webpack_public_path__ + \\"file.png\\";"`;

exports[`Options fallback {String} 3`] = `"module.exports = __webpack_public_path__ + \\"name-for-file-loader.png\\";"`;

exports[`Options fallback {String} 4`] = `"module.exports = __webpack_public_path__ + \\"name-for-file-loader.png\\";"`;
61 changes: 61 additions & 0 deletions test/options/fallback.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,66 @@ describe('Options', () => {

expect(source).toMatchSnapshot();
});

// Version 1.0.1 passes options provided to url-loader to the fallback as well, so make sure that still works.
test('{String}', async () => {
const config = {
loader: {
test: /\.png$/,
options: {
limit: 100,
fallback: 'file-loader',
name: '[name].[ext]',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add tests where name is [hash].[ext] to ensure options will send in fallback.

},
},
};

const stats = await webpack('fixture.js', config);
const { source } = stats.toJson().modules[0];

expect(source).toMatchSnapshot();
});

// Test passing explicitly provided options to the fallback loader.
test('{String}', async () => {
const config = {
loader: {
test: /\.png$/,
options: {
limit: 100,
name: 'name-for-url-loader.[ext]',
fallback: {
loader: 'file-loader',
options: {
name: 'name-for-file-loader.[ext]',
},
},
},
},
};

const stats = await webpack('fixture.js', config);
const { source } = stats.toJson().modules[0];

expect(source).toMatchSnapshot();
});

test('{String}', async () => {
const config = {
loader: {
test: /\.png$/,
options: {
limit: 100,
name: 'name-for-url-loader.[ext]',
fallback: 'file-loader?name=name-for-file-loader.[ext]',
},
},
};

const stats = await webpack('fixture.js', config);
const { source } = stats.toJson().modules[0];

expect(source).toMatchSnapshot();
});
});
});
40 changes: 40 additions & 0 deletions test/utils/__snapshots__/normalizeFallback.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`normalizeFallback object 1`] = `
Object {
"loader": "file-loader",
"query": undefined,
}
`;

exports[`normalizeFallback object-with-options 1`] = `
Object {
"loader": "file-loader",
"query": Object {
"name": "name-for-file-loader.[ext]",
},
}
`;

exports[`normalizeFallback string 1`] = `
Object {
"loader": "file-loader",
"query": Object {
"limit": 8192,
"name": "name-for-url-loader.[ext]",
},
}
`;

exports[`normalizeFallback string-with-query 1`] = `
Object {
"loader": "file-loader",
"query": "?name=name-for-file-loader.[ext]",
}
`;

exports[`normalizeFallback undefined 1`] = `
Object {
"loader": "file-loader",
}
`;
54 changes: 54 additions & 0 deletions test/utils/normalizeFallback.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/* eslint-disable
prefer-destructuring,
*/
import normalizeFallback from '../../src/utils/normalizeFallback';

describe('normalizeFallback', () => {
test('undefined', () => {
const result = normalizeFallback(undefined, {
limit: 8192,
name: 'name-for-url-loader.[ext]',
});

expect(result).toMatchSnapshot();
});

test('string', () => {
const result = normalizeFallback('file-loader', {
limit: 8192,
name: 'name-for-url-loader.[ext]',
});

expect(result).toMatchSnapshot();
});

test('string-with-query', () => {
const result = normalizeFallback(
'file-loader?name=name-for-file-loader.[ext]',
{ limit: 8192, name: 'name-for-url-loader.[ext]' }
);

expect(result).toMatchSnapshot();
});

test('object', () => {
const result = normalizeFallback(
{ loader: 'file-loader' },
{ limit: 8192, name: 'name-for-url-loader.[ext]' }
);

expect(result).toMatchSnapshot();
});

test('object-with-options', () => {
const result = normalizeFallback(
{
loader: 'file-loader',
options: { name: 'name-for-file-loader.[ext]' },
},
{ limit: 8192, name: 'name-for-url-loader.[ext]' }
);

expect(result).toMatchSnapshot();
});
});