Skip to content

Commit 2f21d30

Browse files
committed
WIP attempt to use webpack 5 asset modules
1 parent 46b1b82 commit 2f21d30

7 files changed

Lines changed: 157 additions & 83 deletions

File tree

packages/docusaurus-mdx-loader/src/remark/transformImage/index.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,13 @@ const escapeHtml = require('escape-html');
1313
const {getFileLoaderUtils} = require('@docusaurus/core/lib/webpack/utils');
1414
const {posixPath, toMessageRelativeFilePath} = require('@docusaurus/utils');
1515

16-
const {
17-
loaders: {inlineMarkdownImageFileLoader},
18-
} = getFileLoaderUtils();
16+
const {assetQuery} = getFileLoaderUtils();
1917

2018
const createJSX = (node, pathUrl) => {
2119
const jsxNode = node;
2220
jsxNode.type = 'jsx';
2321
jsxNode.value = `<img ${node.alt ? `alt={"${escapeHtml(node.alt)}"} ` : ''}${
24-
node.url
25-
? `src={require("${inlineMarkdownImageFileLoader}${pathUrl}").default}`
26-
: ''
22+
node.url ? `src={require("${pathUrl}?${assetQuery}").default}` : ''
2723
}${node.title ? ` title="${escapeHtml(node.title)}"` : ''} />`;
2824

2925
if (jsxNode.url) {

packages/docusaurus-mdx-loader/src/remark/transformLinks/index.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@ const escapeHtml = require('escape-html');
1515
const {toValue} = require('../utils');
1616
const {getFileLoaderUtils} = require('@docusaurus/core/lib/webpack/utils');
1717

18-
const {
19-
loaders: {inlineMarkdownLinkFileLoader},
20-
} = getFileLoaderUtils();
18+
const {assetQuery} = getFileLoaderUtils();
2119

2220
async function ensureAssetFileExist(fileSystemAssetPath, sourceFilePath) {
2321
const assetExists = await fs.pathExists(fileSystemAssetPath);
@@ -43,7 +41,7 @@ function toAssetRequireNode({node, filePath, requireAssetPath}) {
4341
? relativeRequireAssetPath
4442
: `./${relativeRequireAssetPath}`;
4543

46-
const href = `require('${inlineMarkdownLinkFileLoader}${relativeRequireAssetPath}').default`;
44+
const href = `require('${relativeRequireAssetPath}?${assetQuery}').default`;
4745
const children = (node.children || []).map((n) => toValue(n)).join('');
4846
const title = node.title ? `title="${escapeHtml(node.title)}"` : '';
4947

packages/docusaurus-plugin-ideal-image/src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ export default function (
3131
module: {
3232
rules: [
3333
{
34-
test: /\.(png|jpe?g|gif)$/i,
34+
test: /\.(png|jpe?g)$/i,
35+
type: 'javascript/auto',
36+
generator: {
37+
emit: !isServer,
38+
},
3539
use: [
3640
require.resolve('@docusaurus/lqip-loader'),
3741
{

packages/docusaurus/src/commands/build.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
applyConfigurePostCss,
2525
applyConfigureWebpack,
2626
compile,
27+
getFileLoaderUtils,
2728
} from '../webpack/utils';
2829
import CleanWebpackPlugin from '../webpack/plugins/CleanWebpackPlugin';
2930
import {loadI18n} from '../server/i18n';
@@ -197,6 +198,11 @@ async function buildLocale({
197198
}
198199
});
199200

201+
// Add the very high-priority rules triggered by using a resourceQuery like ?asset
202+
const {prependAssetQueryRules} = getFileLoaderUtils();
203+
clientConfig = prependAssetQueryRules(clientConfig);
204+
serverConfig = prependAssetQueryRules(serverConfig);
205+
200206
// Make sure generated client-manifest is cleaned first so we don't reuse
201207
// the one from previous builds.
202208
if (await fs.pathExists(clientManifestPath)) {

packages/docusaurus/src/commands/start.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
applyConfigureWebpack,
3030
applyConfigurePostCss,
3131
getHttpsConfig,
32+
getFileLoaderUtils,
3233
} from '../webpack/utils';
3334
import {getCLIOptionHost, getCLIOptionPort} from './commandUtils';
3435
import {getTranslationsLocaleDirPath} from '../server/translations/translations';
@@ -157,6 +158,10 @@ export default async function start(
157158
}
158159
});
159160

161+
// Add the very high-priority rules triggered by using a resourceQuery like ?asset
162+
const {prependAssetQueryRules} = getFileLoaderUtils();
163+
config = prependAssetQueryRules(config);
164+
160165
// https://webpack.js.org/configuration/dev-server
161166
const devServerConfig: WebpackDevServer.Configuration = {
162167
...{

packages/docusaurus/src/webpack/base.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ export function createBaseConfig(
104104
chunkFilename: isProd
105105
? 'assets/js/[name].[contenthash:8].js'
106106
: '[name].js',
107+
assetModuleFilename: 'assets/[hash][ext][query]',
107108
publicPath: baseUrl,
108109
},
109110
// Don't throw warning when asset created is over 250kb
@@ -187,7 +188,7 @@ export function createBaseConfig(
187188
fileLoaderUtils.rules.fonts(),
188189
fileLoaderUtils.rules.media(),
189190
fileLoaderUtils.rules.svg(),
190-
fileLoaderUtils.rules.otherAssets(),
191+
fileLoaderUtils.rules.files(),
191192
{
192193
test: /\.(j|t)sx?$/,
193194
exclude: excludeJS,

packages/docusaurus/src/webpack/utils.ts

Lines changed: 135 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -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

289289
type 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
306308
export 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 = /asset/;
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: /\.(ico|jpg|jpeg|png|gif|webp)(\?.*)?$/,
356-
};
357-
},
381+
function imageAssetRule(): RuleSetRule {
382+
return {
383+
...baseAssetRule('images'),
384+
test: /\.(ico|jpg|jpeg|png|gif|webp)(\?.*)?$/,
385+
};
386+
}
358387

359-
fonts: () => {
360-
return {
361-
use: [loaders.url({folder: 'fonts'})],
362-
test: /\.(woff|woff2|eot|ttf|otf)$/,
363-
};
364-
},
388+
function fontAssetRule(): RuleSetRule {
389+
return {
390+
...baseAssetRule('fonts'),
391+
test: /\.(woff|woff2|eot|ttf|otf)$/,
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: /\.(mp4|webm|ogv|wav|mp3|m4a|aac|oga|flac)$/,
374-
};
375-
},
395+
function mediaAssetRule(): RuleSetRule {
396+
return {
397+
...baseAssetRule('medias'),
398+
test: /\.(mp4|webm|ogv|wav|mp3|m4a|aac|oga|flac)$/,
399+
};
400+
}
376401

377-
svg: () => {
378-
return {
379-
test: /\.svg?$/,
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: /\.(pdf|doc|docx|xls|xlsx|zip|rar)$/,
406+
type: 'asset/resource',
407+
};
408+
}
409+
410+
function svgAssetRule(): RuleSetRule {
411+
return {
412+
...baseAssetRule('svgs'),
413+
test: /\.svg?$/,
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: /\.svg?$/,
422+
oneOf: [
423+
{
424+
// only convert for those extensions
425+
issuer: /\.(ts|tsx|js|jsx|md|mdx)$/,
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: [/\.(ts|tsx|js|jsx|md|mdx)$/],
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: /\.(pdf|doc|docx|xls|xlsx|zip|rar)$/,
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

Comments
 (0)