Skip to content

Commit 4c48ce4

Browse files
committed
feature #296 Add Encore.configureUrlLoader() method to the public API (Lyrkan)
This PR was squashed before being merged into the master branch (closes #296). Discussion ---------- Add Encore.configureUrlLoader() method to the public API This PR adds a `configureUrlLoader()` method to the public API in order to allow the use of the `url-loader` instead of the `file-loader` for images and fonts (closes #295). Usage: ```js // Enable the url-loader for both images and fonts Encore.configureUrlLoader({ images: { limit: 8192, mimetype: 'image/png' }, fonts: { limit: 4096 } }); // Enable the url-loader for images and implicitly // disable it for fonts (by omitting the "fonts" key) Encore.configureUrlLoader({ images: { limit: 8192, mimetype: 'image/png' } }); // Enable the url-loader for fonts and explicitly // disable it for images (by using a falsy value // for the "images" key). Encore.configureUrlLoader({ images: false, fonts: { limit: 4096 } }); ``` Commits ------- 92ea9c4 Encore.configureUrlLoader() - Replace some 'let' by 'const' 9ca96a5 Encore.configureUrlLoader() - Move url-loader to dev dependencies 69e2cfe Encore.configureUrlLoader() - Merge options instead of replacing them 0ba2677 Add Encore.configureUrlLoader() method to the public API
2 parents 9ac995e + 92ea9c4 commit 4c48ce4

File tree

11 files changed

+245
-10
lines changed

11 files changed

+245
-10
lines changed

fixtures/css/url-loader.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@font-face {
2+
font-family: 'Roboto';
3+
src: url('./../fonts/Roboto.woff2') format('woff2');
4+
}
5+
6+
.foo {
7+
background: url('./../images/symfony_logo.png');
8+
}

index.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,33 @@ class Encore {
771771
return this;
772772
}
773773

774+
/**
775+
* Allows to configure the URL loader.
776+
*
777+
* https://github.com/webpack-contrib/url-loader
778+
*
779+
* Encore.configureUrlLoader({
780+
* images: {
781+
* limit: 8192,
782+
* mimetype: 'image/png'
783+
* },
784+
* fonts: {
785+
* limit: 4096
786+
* }
787+
* });
788+
*
789+
* If a key (e.g. fonts) doesn't exists or contains a
790+
* falsy value the file-loader will be used instead.
791+
*
792+
* @param {object} urlLoaderOptions
793+
* @return {Encore}
794+
*/
795+
configureUrlLoader(urlLoaderOptions = {}) {
796+
webpackConfig.configureUrlLoader(urlLoaderOptions);
797+
798+
return this;
799+
}
800+
774801
/**
775802
* If enabled, the output directory is emptied between each build (to remove old files).
776803
*

lib/WebpackConfig.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ class WebpackConfig {
7373
this.preactOptions = {
7474
preactCompat: false
7575
};
76+
this.urlLoaderOptions = {
77+
images: false,
78+
fonts: false
79+
};
7680

7781
// Features/Loaders options callbacks
7882
this.postCssLoaderOptionsCallback = () => {};
@@ -466,6 +470,22 @@ class WebpackConfig {
466470
this.configuredFilenames = configuredFilenames;
467471
}
468472

473+
configureUrlLoader(urlLoaderOptions = {}) {
474+
if (typeof urlLoaderOptions !== 'object') {
475+
throw new Error('Argument 1 to configureUrlLoader() must be an object.');
476+
}
477+
478+
// Check allowed keys
479+
const validKeys = ['images', 'fonts'];
480+
for (const key of Object.keys(urlLoaderOptions)) {
481+
if (validKeys.indexOf(key) === -1) {
482+
throw new Error(`"${key}" is not a valid key for configureUrlLoader(). Valid keys: ${validKeys.join(', ')}.`);
483+
}
484+
}
485+
486+
this.urlLoaderOptions = urlLoaderOptions;
487+
}
488+
469489
cleanupOutputBeforeBuild(paths = ['**/*'], cleanWebpackPluginOptionsCallback = () => {}) {
470490
if (!Array.isArray(paths)) {
471491
throw new Error('Argument 1 to cleanupOutputBeforeBuild() must be an Array of paths - e.g. [\'**/*\']');

lib/config-generator.js

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
const extractText = require('./loaders/extract-text');
1313
const pathUtil = require('./config/path-util');
14+
const loaderFeatures = require('./features');
1415
// loaders utils
1516
const cssLoaderUtil = require('./loaders/css');
1617
const sassLoaderUtil = require('./loaders/sass');
@@ -149,13 +150,24 @@ class ConfigGenerator {
149150
filename = this.webpackConfig.configuredFilenames.images;
150151
}
151152

153+
// The url-loader can be used instead of the default file-loader by
154+
// calling Encore.configureUrlLoader({ images: {/* ... */}})
155+
let loaderName = 'file-loader';
156+
const loaderOptions = {
157+
name: filename,
158+
publicPath: this.webpackConfig.getRealPublicPath()
159+
};
160+
161+
if (this.webpackConfig.urlLoaderOptions.images) {
162+
loaderFeatures.ensurePackagesExist('urlloader');
163+
loaderName = 'url-loader';
164+
Object.assign(loaderOptions, this.webpackConfig.urlLoaderOptions.images);
165+
}
166+
152167
rules.push({
153168
test: /\.(png|jpg|jpeg|gif|ico|svg|webp)$/,
154-
loader: 'file-loader',
155-
options: {
156-
name: filename,
157-
publicPath: this.webpackConfig.getRealPublicPath()
158-
}
169+
loader: loaderName,
170+
options: loaderOptions
159171
});
160172
}
161173

@@ -166,13 +178,24 @@ class ConfigGenerator {
166178
filename = this.webpackConfig.configuredFilenames.fonts;
167179
}
168180

181+
// The url-loader can be used instead of the default file-loader by
182+
// calling Encore.configureUrlLoader({ fonts: {/* ... */}})
183+
let loaderName = 'file-loader';
184+
const loaderOptions = {
185+
name: filename,
186+
publicPath: this.webpackConfig.getRealPublicPath()
187+
};
188+
189+
if (this.webpackConfig.urlLoaderOptions.fonts) {
190+
loaderFeatures.ensurePackagesExist('urlloader');
191+
loaderName = 'url-loader';
192+
Object.assign(loaderOptions, this.webpackConfig.urlLoaderOptions.fonts);
193+
}
194+
169195
rules.push({
170196
test: /\.(woff|woff2|ttf|eot|otf)$/,
171-
loader: 'file-loader',
172-
options: {
173-
name: filename,
174-
publicPath: this.webpackConfig.getRealPublicPath()
175-
}
197+
loader: loaderName,
198+
options: loaderOptions
176199
});
177200
}
178201

lib/features.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ const features = {
7272
method: 'enableBuildNotifications()',
7373
packages: ['webpack-notifier'],
7474
description: 'display build notifications'
75+
},
76+
urlloader: {
77+
method: 'configureUrlLoader()',
78+
packages: ['url-loader'],
79+
description: 'use the url-loader'
7580
}
7681
};
7782

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"stylus-loader": "^3.0.1",
7777
"ts-loader": "^2.1.0",
7878
"typescript": "^2.3.4",
79+
"url-loader": "^1.0.1",
7980
"vue": "^2.3.4",
8081
"vue-loader": "^12.2.1",
8182
"vue-template-compiler": "^2.3.4",

test/WebpackConfig.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,4 +838,37 @@ describe('WebpackConfig object', () => {
838838
}).to.throw('"foo" is not a valid key');
839839
});
840840
});
841+
842+
describe('configureUrlLoader', () => {
843+
it('Calling method sets it', () => {
844+
const config = createConfig();
845+
config.configureUrlLoader({
846+
images: { limit: 8192 },
847+
fonts: { limit: 4096 }
848+
});
849+
850+
expect(config.urlLoaderOptions).to.deep.equals({
851+
images: { limit: 8192 },
852+
fonts: { limit: 4096 }
853+
});
854+
});
855+
856+
it('Calling with non-object throws an error', () => {
857+
const config = createConfig();
858+
859+
expect(() => {
860+
config.configureUrlLoader('FOO');
861+
}).to.throw('must be an object');
862+
});
863+
864+
it('Calling with an unknown key throws an error', () => {
865+
const config = createConfig();
866+
867+
expect(() => {
868+
config.configureUrlLoader({
869+
foo: 'bar'
870+
});
871+
}).to.throw('"foo" is not a valid key');
872+
});
873+
});
841874
});

test/config-generator.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,66 @@ describe('The config-generator function', () => {
674674
});
675675
});
676676

677+
describe('configureUrlLoader() allows to use the URL loader for fonts/images', () => {
678+
it('without configureUrlLoader()', () => {
679+
const config = createConfig();
680+
config.outputPath = '/tmp/public-path';
681+
config.publicPath = '/public-path';
682+
config.addEntry('main', './main');
683+
684+
const actualConfig = configGenerator(config);
685+
686+
const imagesRule = findRule(/\.(png|jpg|jpeg|gif|ico|svg|webp)$/, actualConfig.module.rules);
687+
expect(imagesRule.loader).to.equal('file-loader');
688+
689+
const fontsRule = findRule(/\.(woff|woff2|ttf|eot|otf)$/, actualConfig.module.rules);
690+
expect(fontsRule.loader).to.equal('file-loader');
691+
});
692+
693+
it('with configureUrlLoader() and missing keys', () => {
694+
const config = createConfig();
695+
config.outputPath = '/tmp/public-path';
696+
config.publicPath = '/public-path';
697+
config.addEntry('main', './main');
698+
config.configureUrlLoader({});
699+
700+
const actualConfig = configGenerator(config);
701+
702+
const imagesRule = findRule(/\.(png|jpg|jpeg|gif|ico|svg|webp)$/, actualConfig.module.rules);
703+
expect(imagesRule.loader).to.equal('file-loader');
704+
705+
const fontsRule = findRule(/\.(woff|woff2|ttf|eot|otf)$/, actualConfig.module.rules);
706+
expect(fontsRule.loader).to.equal('file-loader');
707+
});
708+
709+
it('with configureUrlLoader()', () => {
710+
const config = createConfig();
711+
config.outputPath = '/tmp/public-path';
712+
config.publicPath = '/public-path';
713+
config.addEntry('main', './main');
714+
config.configureFilenames({
715+
images: '[name].foo.[ext]',
716+
fonts: '[name].bar.[ext]'
717+
});
718+
config.configureUrlLoader({
719+
images: { limit: 8192 },
720+
fonts: { limit: 4096 }
721+
});
722+
723+
const actualConfig = configGenerator(config);
724+
725+
const imagesRule = findRule(/\.(png|jpg|jpeg|gif|ico|svg|webp)$/, actualConfig.module.rules);
726+
expect(imagesRule.loader).to.equal('url-loader');
727+
expect(imagesRule.options.name).to.equal('[name].foo.[ext]');
728+
expect(imagesRule.options.limit).to.equal(8192);
729+
730+
const fontsRule = findRule(/\.(woff|woff2|ttf|eot|otf)$/, actualConfig.module.rules);
731+
expect(fontsRule.loader).to.equal('url-loader');
732+
expect(fontsRule.options.limit).to.equal(4096);
733+
expect(fontsRule.options.name).to.equal('[name].bar.[ext]');
734+
});
735+
});
736+
677737
describe('Test preact preset', () => {
678738
describe('Without preact-compat', () => {
679739
it('enablePreactPreset() does not add aliases to use preact-compat', () => {

test/functional.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,5 +977,35 @@ module.exports = {
977977
done();
978978
}, true);
979979
});
980+
981+
it('configureUrlLoader() allows to use the URL loader for images/fonts', (done) => {
982+
const config = createWebpackConfig('web/build', 'dev');
983+
config.setPublicPath('/build');
984+
config.addStyleEntry('url-loader', './css/url-loader.css');
985+
config.configureUrlLoader({
986+
images: { limit: 102400 },
987+
fonts: { limit: 102400 }
988+
});
989+
990+
testSetup.runWebpack(config, (webpackAssert) => {
991+
expect(config.outputPath).to.be.a.directory()
992+
.with.files([
993+
'url-loader.css',
994+
'manifest.json'
995+
]);
996+
997+
webpackAssert.assertOutputFileContains(
998+
'url-loader.css',
999+
'url(data:font/woff2;base64,'
1000+
);
1001+
1002+
webpackAssert.assertOutputFileContains(
1003+
'url-loader.css',
1004+
'url(data:image/png;base64,'
1005+
);
1006+
1007+
done();
1008+
});
1009+
});
9801010
});
9811011
});

test/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,15 @@ describe('Public API', () => {
287287

288288
});
289289

290+
describe('configureUrlLoader', () => {
291+
292+
it('must return the API object', () => {
293+
const returnedValue = api.configureUrlLoader({});
294+
expect(returnedValue).to.equal(api);
295+
});
296+
297+
});
298+
290299
describe('cleanupOutputBeforeBuild', () => {
291300

292301
it('must return the API object', () => {

0 commit comments

Comments
 (0)