diff --git a/packages/docusaurus-init/templates/bootstrap/package.json b/packages/docusaurus-init/templates/bootstrap/package.json
index 53cbf5284e0e..f6ec5419e43f 100644
--- a/packages/docusaurus-init/templates/bootstrap/package.json
+++ b/packages/docusaurus-init/templates/bootstrap/package.json
@@ -19,10 +19,8 @@
"@mdx-js/react": "^1.6.21",
"@svgr/webpack": "^5.5.0",
"clsx": "^1.1.1",
- "file-loader": "^6.2.0",
"react": "^17.0.1",
- "react-dom": "^17.0.1",
- "url-loader": "^4.1.1"
+ "react-dom": "^17.0.1"
},
"browserslist": {
"production": [
diff --git a/packages/docusaurus-init/templates/classic/package.json b/packages/docusaurus-init/templates/classic/package.json
index 684d3cc213f9..83cceccf87dd 100644
--- a/packages/docusaurus-init/templates/classic/package.json
+++ b/packages/docusaurus-init/templates/classic/package.json
@@ -19,10 +19,8 @@
"@mdx-js/react": "^1.6.21",
"@svgr/webpack": "^5.5.0",
"clsx": "^1.1.1",
- "file-loader": "^6.2.0",
"react": "^17.0.1",
- "react-dom": "^17.0.1",
- "url-loader": "^4.1.1"
+ "react-dom": "^17.0.1"
},
"browserslist": {
"production": [
diff --git a/packages/docusaurus-init/templates/facebook/package.json b/packages/docusaurus-init/templates/facebook/package.json
index 69ea8e118b49..12c97e76b45d 100644
--- a/packages/docusaurus-init/templates/facebook/package.json
+++ b/packages/docusaurus-init/templates/facebook/package.json
@@ -23,10 +23,8 @@
"@mdx-js/react": "^1.6.21",
"@svgr/webpack": "^5.5.0",
"clsx": "^1.1.1",
- "file-loader": "^6.2.0",
"react": "^17.0.1",
- "react-dom": "^17.0.1",
- "url-loader": "^4.1.1"
+ "react-dom": "^17.0.1"
},
"devDependencies": {
"@babel/eslint-parser": "^7.13.10",
diff --git a/packages/docusaurus-mdx-loader/src/remark/transformImage/index.js b/packages/docusaurus-mdx-loader/src/remark/transformImage/index.js
index 42122ab4a32b..4286a6338d7e 100644
--- a/packages/docusaurus-mdx-loader/src/remark/transformImage/index.js
+++ b/packages/docusaurus-mdx-loader/src/remark/transformImage/index.js
@@ -17,18 +17,15 @@ const {
toMessageRelativeFilePath,
} = require('@docusaurus/utils');
-const {
- loaders: {inlineMarkdownImageFileLoader},
-} = getFileLoaderUtils();
+const {assetQuery} = getFileLoaderUtils();
const createJSX = (node, pathUrl) => {
const jsxNode = node;
jsxNode.type = 'jsx';
jsxNode.value = `
`;
diff --git a/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.js b/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.js
index 50bc4d7f713d..51e3e583d048 100644
--- a/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.js
+++ b/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.js
@@ -19,9 +19,7 @@ const escapeHtml = require('escape-html');
const {toValue} = require('../utils');
const {getFileLoaderUtils} = require('@docusaurus/core/lib/webpack/utils');
-const {
- loaders: {inlineMarkdownLinkFileLoader},
-} = getFileLoaderUtils();
+const {assetQuery} = getFileLoaderUtils();
async function ensureAssetFileExist(fileSystemAssetPath, sourceFilePath) {
const assetExists = await fs.pathExists(fileSystemAssetPath);
@@ -47,9 +45,9 @@ function toAssetRequireNode({node, filePath, requireAssetPath}) {
? relativeRequireAssetPath
: `./${relativeRequireAssetPath}`;
- const href = `require('${inlineMarkdownLinkFileLoader}${escapePath(
+ const href = `new URL('${escapePath(
relativeRequireAssetPath,
- )}').default`;
+ )}?${assetQuery}', import.meta.url).toString()`;
const children = (node.children || []).map((n) => toValue(n)).join('');
const title = node.title ? `title="${escapeHtml(node.title)}"` : '';
diff --git a/packages/docusaurus-plugin-ideal-image/src/index.ts b/packages/docusaurus-plugin-ideal-image/src/index.ts
index c3ddf74b9311..d3fc2fb0362c 100644
--- a/packages/docusaurus-plugin-ideal-image/src/index.ts
+++ b/packages/docusaurus-plugin-ideal-image/src/index.ts
@@ -31,7 +31,14 @@ export default function (
module: {
rules: [
{
- test: /\.(png|jpe?g|gif)$/i,
+ test: /\.(png|jpe?g)$/i,
+ resourceQuery: {
+ not: [/asset/],
+ },
+ type: 'javascript/auto',
+ generator: {
+ emit: !isServer,
+ },
use: [
require.resolve('@docusaurus/lqip-loader'),
{
@@ -40,9 +47,8 @@ export default function (
emitFile: !isServer, // don't emit for server-side rendering
disable: !isProd,
adapter: require('@docusaurus/responsive-loader/sharp'),
- name: isProd
- ? 'assets/ideal-img/[name].[hash:hex:7].[width].[ext]'
- : 'assets/ideal-img/[name].[width].[ext]',
+ name:
+ 'assets/ideal-img/[name]-[contenthash:8].[width].[ext]',
...options,
},
},
diff --git a/packages/docusaurus/src/commands/build.ts b/packages/docusaurus/src/commands/build.ts
index 7074f706b6da..0a5c1c8fb7d4 100644
--- a/packages/docusaurus/src/commands/build.ts
+++ b/packages/docusaurus/src/commands/build.ts
@@ -24,6 +24,7 @@ import {
applyConfigurePostCss,
applyConfigureWebpack,
compile,
+ getFileLoaderUtils,
} from '../webpack/utils';
import CleanWebpackPlugin from '../webpack/plugins/CleanWebpackPlugin';
import {loadI18n} from '../server/i18n';
@@ -197,6 +198,11 @@ async function buildLocale({
}
});
+ // Add the very high-priority rules triggered by using a resourceQuery like ?asset
+ const {prependAssetQueryRules} = getFileLoaderUtils();
+ clientConfig = prependAssetQueryRules(clientConfig);
+ serverConfig = prependAssetQueryRules(serverConfig);
+
// Make sure generated client-manifest is cleaned first so we don't reuse
// the one from previous builds.
if (await fs.pathExists(clientManifestPath)) {
diff --git a/packages/docusaurus/src/commands/start.ts b/packages/docusaurus/src/commands/start.ts
index 2f045b48f101..48f71d61f137 100644
--- a/packages/docusaurus/src/commands/start.ts
+++ b/packages/docusaurus/src/commands/start.ts
@@ -29,6 +29,7 @@ import {
applyConfigureWebpack,
applyConfigurePostCss,
getHttpsConfig,
+ getFileLoaderUtils,
} from '../webpack/utils';
import {getCLIOptionHost, getCLIOptionPort} from './commandUtils';
import {getTranslationsLocaleDirPath} from '../server/translations/translations';
@@ -157,6 +158,10 @@ export default async function start(
}
});
+ // Add the very high-priority rules triggered by using a resourceQuery like ?asset
+ const {prependAssetQueryRules} = getFileLoaderUtils();
+ config = prependAssetQueryRules(config);
+
// https://webpack.js.org/configuration/dev-server
const devServerConfig: WebpackDevServer.Configuration = {
...{
diff --git a/packages/docusaurus/src/webpack/base.ts b/packages/docusaurus/src/webpack/base.ts
index a43019b69ef9..4767221b67f1 100644
--- a/packages/docusaurus/src/webpack/base.ts
+++ b/packages/docusaurus/src/webpack/base.ts
@@ -108,6 +108,7 @@ export function createBaseConfig(
chunkFilename: isProd
? 'assets/js/[name].[contenthash:8].js'
: '[name].js',
+ assetModuleFilename: 'assets/[name]-[hash][ext]',
publicPath: baseUrl,
},
// Don't throw warning when asset created is over 250kb
@@ -191,7 +192,7 @@ export function createBaseConfig(
fileLoaderUtils.rules.fonts(),
fileLoaderUtils.rules.media(),
fileLoaderUtils.rules.svg(),
- fileLoaderUtils.rules.otherAssets(),
+ fileLoaderUtils.rules.files(),
{
test: /\.(j|t)sx?$/,
exclude: excludeJS,
diff --git a/packages/docusaurus/src/webpack/utils.ts b/packages/docusaurus/src/webpack/utils.ts
index f9eda08d13d7..2e892b590a1e 100644
--- a/packages/docusaurus/src/webpack/utils.ts
+++ b/packages/docusaurus/src/webpack/utils.ts
@@ -284,137 +284,157 @@ export function compile(config: Configuration[]): Promise {
});
}
-type AssetFolder = 'images' | 'files' | 'fonts' | 'medias';
+type AssetFolder = 'images' | 'files' | 'fonts' | 'medias' | 'svgs';
type FileLoaderUtils = {
- loaders: {
- file: (options: {folder: AssetFolder}) => RuleSetRule;
- url: (options: {folder: AssetFolder}) => RuleSetRule;
- inlineMarkdownImageFileLoader: string;
- inlineMarkdownLinkFileLoader: string;
- };
+ assetQuery: string;
+ prependAssetQueryRules: (configuration: Configuration) => Configuration;
rules: {
images: () => RuleSetRule;
fonts: () => RuleSetRule;
media: () => RuleSetRule;
svg: () => RuleSetRule;
- otherAssets: () => RuleSetRule;
+ files: () => RuleSetRule;
};
};
// Inspired by https://github.com/gatsbyjs/gatsby/blob/8e6e021014da310b9cc7d02e58c9b3efe938c665/packages/gatsby/src/utils/webpack-utils.ts#L447
export function getFileLoaderUtils(): FileLoaderUtils {
- // files/images < 10kb will be inlined as base64 strings directly in the html
- const urlLoaderLimit = 10000;
+ // Asset queries are used to force the usage of the file as an asset
+ // In some case we want to opt-out o
+ // - converting an image to an ideal-image
+ // - converting an SVG to a React component
+ // - other cases
+ const assetQuery = 'asset';
+ const assetResourceQuery = /asset/;
+ // Can this be removed? see https://github.com/facebook/docusaurus/commit/2f21d306bdd4d286cc5d25c81adaea2fc77f0474#commitcomment-50223144)
+ const notAssetResourceQuery: RuleSetRule['resourceQuery'] = {not: [/asset/]};
// defines the path/pattern of the assets handled by webpack
- const fileLoaderFileName = (folder: AssetFolder) =>
- `${OUTPUT_STATIC_ASSETS_DIR_NAME}/${folder}/[name]-[hash].[ext]`;
-
- const loaders: FileLoaderUtils['loaders'] = {
- file: (options: {folder: AssetFolder}) => {
- return {
- loader: require.resolve(`file-loader`),
- options: {
- name: fileLoaderFileName(options.folder),
- },
- };
- },
- url: (options: {folder: AssetFolder}) => {
- return {
- loader: require.resolve(`url-loader`),
- options: {
- limit: urlLoaderLimit,
- name: fileLoaderFileName(options.folder),
- fallback: require.resolve(`file-loader`),
+ function fileNameGenerator(folder: AssetFolder) {
+ return {
+ filename: `${OUTPUT_STATIC_ASSETS_DIR_NAME}/${folder}/[name]-[hash][ext]`,
+ };
+ }
+
+ function baseAssetRule(folder: AssetFolder): RuleSetRule {
+ return {
+ type: 'asset',
+ parser: {
+ dataUrlCondition: {
+ // Threshold for datauri/file (previously set on url-loader)
+ // files/images < 10kb will be inlined as base64 strings directly in the JS bundle
+ // See https://webpack.js.org/guides/asset-modules/#general-asset-type
+ maxSize: 10 * 1024,
},
- };
- },
+ },
+ generator: fileNameGenerator(folder),
+ resourceQuery: notAssetResourceQuery,
+ };
+ }
- // TODO find a better solution to avoid conflicts with the ideal-image plugin
- // TODO this may require a little breaking change for ideal-image users?
- // Maybe with the ideal image plugin, all md images should be "ideal"?
- // This is used to force url-loader+file-loader on markdown images
- // https://webpack.js.org/concepts/loaders/#inline
- inlineMarkdownImageFileLoader: `!url-loader?limit=${urlLoaderLimit}&name=${fileLoaderFileName(
- 'images',
- )}&fallback=file-loader!`,
- inlineMarkdownLinkFileLoader: `!file-loader?name=${fileLoaderFileName(
- 'files',
- )}!`,
- };
+ function imageAssetRule(): RuleSetRule {
+ return {
+ ...baseAssetRule('images'),
+ test: /\.(ico|jpg|jpeg|png|gif|webp)(\?.*)?$/i,
+ };
+ }
- const rules: FileLoaderUtils['rules'] = {
- /**
- * Loads image assets, inlines images via a data URI if they are below
- * the size threshold
- */
- images: () => {
- return {
- use: [loaders.url({folder: 'images'})],
- test: /\.(ico|jpg|jpeg|png|gif|webp)(\?.*)?$/,
- };
- },
+ function fontAssetRule(): RuleSetRule {
+ return {
+ ...baseAssetRule('fonts'),
+ test: /\.(woff|woff2|eot|ttf|otf)$/i,
+ };
+ }
- fonts: () => {
- return {
- use: [loaders.url({folder: 'fonts'})],
- test: /\.(woff|woff2|eot|ttf|otf)$/,
- };
- },
+ function mediaAssetRule(): RuleSetRule {
+ return {
+ ...baseAssetRule('medias'),
+ test: /\.(mp4|webm|ogv|wav|mp3|m4a|aac|oga|flac)$/i,
+ };
+ }
- /**
- * Loads audio and video and inlines them via a data URI if they are below
- * the size threshold
- */
- media: () => {
- return {
- use: [loaders.url({folder: 'medias'})],
- test: /\.(mp4|webm|ogv|wav|mp3|m4a|aac|oga|flac)$/,
- };
- },
+ function fileAssetRule(): RuleSetRule {
+ return {
+ ...baseAssetRule('files'),
+ test: /\.(pdf|doc|docx|xls|xlsx|zip|rar)$/i,
+ type: 'asset/resource',
+ };
+ }
- svg: () => {
- return {
- test: /\.svg?$/,
- oneOf: [
- {
- use: [
- {
- loader: '@svgr/webpack',
- options: {
- prettier: false,
- svgo: true,
- svgoConfig: {
- plugins: [{removeViewBox: false}],
- },
- titleProp: true,
- ref: ![path],
+ function svgAssetRule(): RuleSetRule {
+ return {
+ ...baseAssetRule('svgs'),
+ test: /\.svg?$/i,
+ };
+ }
+
+ // We convert SVG to React component when required from code only
+ // We don't convert SVG to React components when referenced in CSS
+ function svgComponentOrAssetRule(): RuleSetRule {
+ return {
+ test: /\.svg?$/i,
+ resourceQuery: notAssetResourceQuery,
+ oneOf: [
+ {
+ // only convert for those extensions
+ issuer: /\.(ts|tsx|js|jsx|md|mdx)$/,
+ use: [
+ {
+ loader: '@svgr/webpack',
+ options: {
+ prettier: false,
+ svgo: true,
+ svgoConfig: {
+ plugins: [{removeViewBox: false}],
},
+ titleProp: true,
+ ref: ![path],
},
- ],
- // We don't want to use SVGR loader for non-React source code
- // ie we don't want to use SVGR for CSS files...
- issuer: {
- and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
},
- },
+ ],
+ },
+ svgAssetRule(),
+ ],
+ };
+ }
+
+ const rules: FileLoaderUtils['rules'] = {
+ images: imageAssetRule,
+ fonts: fontAssetRule,
+ media: mediaAssetRule,
+ svg: svgComponentOrAssetRule,
+ files: fileAssetRule,
+ };
+
+ // Those rules are triggered conditionally when using ?asset
+ // They must be added at the very beginning of the rules array
+ // Even before the rules prepended by other plugins
+ // This is a replacement for Webpack 4 file/url-loader webpack queries
+ function prependAssetQueryRules(configuration: Configuration): Configuration {
+ return mergeWithCustomize({
+ customizeArray: customizeArray({
+ 'module.rules': CustomizeRule.Prepend,
+ }),
+ })(configuration, {
+ module: {
+ rules: [
+ {...imageAssetRule(), resourceQuery: assetResourceQuery},
+ {...fontAssetRule(), resourceQuery: assetResourceQuery},
+ {...mediaAssetRule(), resourceQuery: assetResourceQuery},
+ {...svgAssetRule(), resourceQuery: assetResourceQuery},
+ // Fallback when ?asset is used but the file is unknown
{
- use: [loaders.url({folder: 'images'})],
+ type: 'asset/resource',
+ resourceQuery: assetResourceQuery,
+ generator: fileNameGenerator('files'),
},
],
- };
- },
-
- otherAssets: () => {
- return {
- use: [loaders.file({folder: 'files'})],
- test: /\.(pdf|doc|docx|xls|xlsx|zip|rar)$/,
- };
- },
- };
+ },
+ } as Configuration);
+ }
- return {loaders, rules};
+ return {rules, assetQuery, prependAssetQueryRules};
}
// Ensure the certificate and key provided are valid and if not
diff --git a/website/docs/docusaurus-core.md b/website/docs/docusaurus-core.md
index 6344bfcf9a3e..2e38ad249f25 100644
--- a/website/docs/docusaurus-core.md
+++ b/website/docs/docusaurus-core.md
@@ -303,7 +303,7 @@ In most cases, you don't need `useBaseUrl`.
Prefer a `require()` call for [assets](./guides/markdown-features/markdown-features-assets.mdx):
```jsx
-
+
```
:::
diff --git a/website/docs/guides/markdown-features/markdown-features-assets.mdx b/website/docs/guides/markdown-features/markdown-features-assets.mdx
index 83d6514c7741..b9398ca9499b 100644
--- a/website/docs/guides/markdown-features/markdown-features-assets.mdx
+++ b/website/docs/guides/markdown-features/markdown-features-assets.mdx
@@ -28,7 +28,7 @@ You can use images in Markdown, or by requiring them and using a JSX image tag:
# My Markdown page
@@ -64,9 +64,7 @@ In the same way, you can link to existing assets by requiring them and using the
```mdx
# My Markdown page
-
+
Download this PDF
@@ -77,7 +75,10 @@ or
+ href={new URL(
+ '../../assets/docusaurus-asset-example-pdf.pdf',
+ import.meta.url,
+ ).toString()}>
Download this PDF
diff --git a/website/docs/static-assets.md b/website/docs/static-assets.md
index 9224e655776c..07e8f3a2f5f9 100644
--- a/website/docs/static-assets.md
+++ b/website/docs/static-assets.md
@@ -27,7 +27,7 @@ import DocusaurusImageUrl from '@site/static/img/docusaurus.png';
```
```jsx title="MyComponent.js"
-
+
```
```jsx title="MyComponent.js"