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

Commit

Permalink
feat: support fallback loader in options.fallback (#123)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Pimm authored and shellscape committed Jun 16, 2018
1 parent 9946374 commit 017adc7
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 4 deletions.
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 '@webpack-contrib/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',

This comment has been minimized.

Copy link
@Terrel007

Terrel007 Aug 16, 2018

I encoutered a bug when I bundle my application with webpack. I didn't specified the fallback option, it seems to be incompatible with previous 1.0.1 versions. The output path is not consistent with the name option specified. Shouldn't be return the below object if i didn't specified the fallback option?
{ loader:‘file-loader’, query: originalOptions }

This comment has been minimized.

Copy link
@alexander-akait

alexander-akait Aug 16, 2018

Member

@Terrel007 please create new issue with minimum reproducible test repo

};
}

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]',
},
},
};

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();
});
});

0 comments on commit 017adc7

Please sign in to comment.