@@ -284,9 +284,11 @@ export function compile(config: Configuration[]): Promise<void> {
284284 } ) ;
285285}
286286
287- type AssetFolder = 'images' | 'files' | 'fonts' | 'medias' ;
287+ type AssetFolder = 'images' | 'files' | 'fonts' | 'medias' | 'svgs' | 'other' ;
288288
289289type FileLoaderUtils = {
290+ assetQuery : string ;
291+ prependAssetQueryRules : ( configuration : Configuration ) => Configuration ;
290292 loaders : {
291293 file : ( options : { folder : AssetFolder } ) => RuleSetRule ;
292294 url : ( options : { folder : AssetFolder } ) => RuleSetRule ;
@@ -298,34 +300,66 @@ type FileLoaderUtils = {
298300 fonts : ( ) => RuleSetRule ;
299301 media : ( ) => RuleSetRule ;
300302 svg : ( ) => RuleSetRule ;
301- otherAssets : ( ) => RuleSetRule ;
303+ files : ( ) => RuleSetRule ;
302304 } ;
303305} ;
304306
305307// Inspired by https://github.com/gatsbyjs/gatsby/blob/8e6e021014da310b9cc7d02e58c9b3efe938c665/packages/gatsby/src/utils/webpack-utils.ts#L447
306308export function getFileLoaderUtils ( ) : FileLoaderUtils {
307- // files/images < 10kb will be inlined as base64 strings directly in the html
308- const urlLoaderLimit = 10000 ;
309+ // Asset queries are used to force the usage of the file as an asset
310+ // In some case we want to opt-out o
311+ // - converting an image to an ideal-image
312+ // - converting an SVG to a React component
313+ // - other cases
314+ const assetQuery = 'asset' ;
315+ const assetQueryRegex = / a s s e t / ;
316+
317+ // Threshold for datauri/file (previously set on url-loader)
318+ // files/images < 10kb will be inlined as base64 strings directly in the JS bundle
319+ // See https://webpack.js.org/guides/asset-modules/#general-asset-type
320+ const dataUrlMaxSize = 10000 ;
309321
310322 // defines the path/pattern of the assets handled by webpack
311- const fileLoaderFileName = ( folder : AssetFolder ) =>
323+ const generatedFileName = ( folder : AssetFolder ) =>
312324 `${ OUTPUT_STATIC_ASSETS_DIR_NAME } /${ folder } /[name]-[hash].[ext]` ;
313325
326+ function fileNameGenerator ( folder : AssetFolder ) {
327+ return {
328+ filename : generatedFileName ( folder ) ,
329+ } ;
330+ }
331+
332+ function baseAssetRule ( folder : AssetFolder ) : RuleSetRule {
333+ return {
334+ parser : {
335+ dataUrlCondition : {
336+ // Threshold for datauri/file (previously set on url-loader)
337+ // files/images < 10kb will be inlined as base64 strings directly in the JS bundle
338+ // See https://webpack.js.org/guides/asset-modules/#general-asset-type
339+ maxSize : dataUrlMaxSize ,
340+ } ,
341+ } ,
342+ generator : fileNameGenerator ( folder ) ,
343+ } ;
344+ }
345+
314346 const loaders : FileLoaderUtils [ 'loaders' ] = {
347+ // TODO deprecated
315348 file : ( options : { folder : AssetFolder } ) => {
316349 return {
317350 loader : require . resolve ( `file-loader` ) ,
318351 options : {
319- name : fileLoaderFileName ( options . folder ) ,
352+ name : generatedFileName ( options . folder ) ,
320353 } ,
321354 } ;
322355 } ,
323356 url : ( options : { folder : AssetFolder } ) => {
357+ // TODO deprecated
324358 return {
325359 loader : require . resolve ( `url-loader` ) ,
326360 options : {
327- limit : urlLoaderLimit ,
328- name : fileLoaderFileName ( options . folder ) ,
361+ limit : dataUrlMaxSize ,
362+ name : generatedFileName ( options . folder ) ,
329363 fallback : require . resolve ( `file-loader` ) ,
330364 } ,
331365 } ;
@@ -336,85 +370,115 @@ export function getFileLoaderUtils(): FileLoaderUtils {
336370 // Maybe with the ideal image plugin, all md images should be "ideal"?
337371 // This is used to force url-loader+file-loader on markdown images
338372 // https://webpack.js.org/concepts/loaders/#inline
339- inlineMarkdownImageFileLoader : `!url-loader?limit=${ urlLoaderLimit } &name=${ fileLoaderFileName (
373+ inlineMarkdownImageFileLoader : `!url-loader?limit=${ dataUrlMaxSize } &name=${ generatedFileName (
340374 'images' ,
341375 ) } &fallback=file-loader!`,
342- inlineMarkdownLinkFileLoader : `!file-loader?name=${ fileLoaderFileName (
376+ inlineMarkdownLinkFileLoader : `!file-loader?name=${ generatedFileName (
343377 'files' ,
344378 ) } !`,
345379 } ;
346380
347- const rules : FileLoaderUtils [ 'rules' ] = {
348- /**
349- * Loads image assets, inlines images via a data URI if they are below
350- * the size threshold
351- */
352- images : ( ) => {
353- return {
354- use : [ loaders . url ( { folder : 'images' } ) ] ,
355- test : / \. ( i c o | j p g | j p e g | p n g | g i f | w e b p ) ( \? .* ) ? $ / ,
356- } ;
357- } ,
381+ function imageAssetRule ( ) : RuleSetRule {
382+ return {
383+ ...baseAssetRule ( 'images' ) ,
384+ test : / \. ( i c o | j p g | j p e g | p n g | g i f | w e b p ) ( \? .* ) ? $ / ,
385+ } ;
386+ }
358387
359- fonts : ( ) => {
360- return {
361- use : [ loaders . url ( { folder : 'fonts' } ) ] ,
362- test : / \. ( w o f f | w o f f 2 | e o t | t t f | o t f ) $ / ,
363- } ;
364- } ,
388+ function fontAssetRule ( ) : RuleSetRule {
389+ return {
390+ ... baseAssetRule ( 'fonts' ) ,
391+ test : / \. ( w o f f | w o f f 2 | e o t | t t f | o t f ) $ / ,
392+ } ;
393+ }
365394
366- /**
367- * Loads audio and video and inlines them via a data URI if they are below
368- * the size threshold
369- */
370- media : ( ) => {
371- return {
372- use : [ loaders . url ( { folder : 'medias' } ) ] ,
373- test : / \. ( m p 4 | w e b m | o g v | w a v | m p 3 | m 4 a | a a c | o g a | f l a c ) $ / ,
374- } ;
375- } ,
395+ function mediaAssetRule ( ) : RuleSetRule {
396+ return {
397+ ...baseAssetRule ( 'medias' ) ,
398+ test : / \. ( m p 4 | w e b m | o g v | w a v | m p 3 | m 4 a | a a c | o g a | f l a c ) $ / ,
399+ } ;
400+ }
376401
377- svg : ( ) => {
378- return {
379- test : / \. s v g ? $ / ,
380- oneOf : [
381- {
382- use : [
383- {
384- loader : '@svgr/webpack' ,
385- options : {
386- prettier : false ,
387- svgo : true ,
388- svgoConfig : {
389- plugins : [ { removeViewBox : false } ] ,
390- } ,
391- titleProp : true ,
392- ref : ! [ path ] ,
402+ function fileAssetRule ( ) : RuleSetRule {
403+ return {
404+ ...baseAssetRule ( 'files' ) ,
405+ test : / \. ( p d f | d o c | d o c x | x l s | x l s x | z i p | r a r ) $ / ,
406+ type : 'asset/resource' ,
407+ } ;
408+ }
409+
410+ function svgAssetRule ( ) : RuleSetRule {
411+ return {
412+ ...baseAssetRule ( 'svgs' ) ,
413+ test : / \. s v g ? $ / ,
414+ } ;
415+ }
416+
417+ // We convert SVG to React component when required from code only
418+ // We don't convert SVG to React components when referenced in CSS
419+ function svgComponentOrAssetRule ( ) : RuleSetRule {
420+ return {
421+ test : / \. s v g ? $ / ,
422+ oneOf : [
423+ {
424+ // only convert for those extensions
425+ issuer : / \. ( t s | t s x | j s | j s x | m d | m d x ) $ / ,
426+ use : [
427+ {
428+ loader : '@svgr/webpack' ,
429+ options : {
430+ prettier : false ,
431+ svgo : true ,
432+ svgoConfig : {
433+ plugins : [ { removeViewBox : false } ] ,
393434 } ,
435+ titleProp : true ,
436+ ref : ! [ path ] ,
394437 } ,
395- ] ,
396- // We don't want to use SVGR loader for non-React source code
397- // ie we don't want to use SVGR for CSS files...
398- issuer : {
399- and : [ / \. ( t s | t s x | j s | j s x | m d | m d x ) $ / ] ,
400438 } ,
401- } ,
439+ ] ,
440+ } ,
441+ svgAssetRule ( ) ,
442+ ] ,
443+ } ;
444+ }
445+
446+ const rules : FileLoaderUtils [ 'rules' ] = {
447+ images : imageAssetRule ,
448+ fonts : fontAssetRule ,
449+ media : mediaAssetRule ,
450+ svg : svgComponentOrAssetRule ,
451+ files : fileAssetRule ,
452+ } ;
453+
454+ // Those rules are triggered conditionally when using ?asset
455+ // They must be added at the very beginning of the rules array
456+ // Even before the rules prepended by other plugins
457+ // This is a replacement for Webpack 4 file/url-loader webpack queries
458+ function prependAssetQueryRules ( configuration : Configuration ) : Configuration {
459+ return mergeWithCustomize ( {
460+ customizeArray : customizeArray ( {
461+ 'module.rules' : CustomizeRule . Prepend ,
462+ } ) ,
463+ } ) ( configuration , {
464+ module : {
465+ rules : [
466+ { ...imageAssetRule ( ) , resourceQuery : assetQueryRegex } ,
467+ { ...fontAssetRule ( ) , resourceQuery : assetQueryRegex } ,
468+ { ...mediaAssetRule ( ) , resourceQuery : assetQueryRegex } ,
469+ { ...svgAssetRule ( ) , resourceQuery : assetQueryRegex } ,
470+ // Fallback when ?asset is used but the file is unknown
402471 {
403- use : [ loaders . url ( { folder : 'images' } ) ] ,
472+ type : 'asset/resource' ,
473+ resourceQuery : assetQueryRegex ,
474+ generator : fileNameGenerator ( 'files' ) ,
404475 } ,
405476 ] ,
406- } ;
407- } ,
408-
409- otherAssets : ( ) => {
410- return {
411- use : [ loaders . file ( { folder : 'files' } ) ] ,
412- test : / \. ( p d f | d o c | d o c x | x l s | x l s x | z i p | r a r ) $ / ,
413- } ;
414- } ,
415- } ;
477+ } ,
478+ } as Configuration ) ;
479+ }
416480
417- return { loaders, rules} ;
481+ return { loaders, rules, assetQuery , prependAssetQueryRules } ;
418482}
419483
420484// Ensure the certificate and key provided are valid and if not
0 commit comments