From 4d1342ebe0969cbcfc9c6d7fc5347f85df07df7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Barr=C3=A9?= Date: Fri, 12 Jan 2024 09:56:23 +0100 Subject: [PATCH] feat: `build.assetsInlineLimit` callback (#15366) --- docs/config/build-options.md | 4 +- packages/vite/src/node/build.ts | 12 ++++-- packages/vite/src/node/constants.ts | 2 + packages/vite/src/node/plugins/asset.ts | 43 ++++++++++++------- playground/worker/vite.config-es.js | 3 +- playground/worker/vite.config-iife.js | 3 +- .../worker/vite.config-relative-base-iife.js | 3 +- .../worker/vite.config-relative-base.js | 3 +- playground/worker/worker-sourcemap-config.js | 3 +- 9 files changed, 52 insertions(+), 24 deletions(-) diff --git a/docs/config/build-options.md b/docs/config/build-options.md index 757847c41e3d74..e070d4da68cabd 100644 --- a/docs/config/build-options.md +++ b/docs/config/build-options.md @@ -82,11 +82,13 @@ Specify the directory to nest generated assets under (relative to `build.outDir` ## build.assetsInlineLimit -- **Type:** `number` +- **Type:** `number` | `((filePath: string, content: Buffer) => boolean | undefined)` - **Default:** `4096` (4 KiB) Imported or referenced assets that are smaller than this threshold will be inlined as base64 URLs to avoid extra http requests. Set to `0` to disable inlining altogether. +If a callback is passed, a boolean can be returned to opt-in or opt-out. If nothing is returned the default logic applies. + Git LFS placeholders are automatically excluded from inlining because they do not contain the content of the file they represent. ::: tip Note diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index aa71f7b4bc1312..69d5ee136645b7 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -45,7 +45,11 @@ import { initDepsOptimizer } from './optimizer' import { loadFallbackPlugin } from './plugins/loadFallback' import { findNearestPackageData } from './packages' import type { PackageCache } from './packages' -import { ESBUILD_MODULES_TARGET, VERSION } from './constants' +import { + DEFAULT_ASSETS_INLINE_LIMIT, + ESBUILD_MODULES_TARGET, + VERSION, +} from './constants' import { resolveChokidarOptions } from './watch' import { completeSystemWrapPlugin } from './plugins/completeSystemWrap' import { mergeConfig } from './publicUtils' @@ -101,7 +105,9 @@ export interface BuildOptions { * base64 strings. Default limit is `4096` (4 KiB). Set to `0` to disable. * @default 4096 */ - assetsInlineLimit?: number + assetsInlineLimit?: + | number + | ((filePath: string, content: Buffer) => boolean | undefined) /** * Whether to code-split CSS. When enabled, CSS in async chunks will be * inlined as strings in the chunk and inserted via dynamically created @@ -325,7 +331,7 @@ export function resolveBuildOptions( const defaultBuildOptions: BuildOptions = { outDir: 'dist', assetsDir: 'assets', - assetsInlineLimit: 4096, + assetsInlineLimit: DEFAULT_ASSETS_INLINE_LIMIT, cssCodeSplit: !raw?.lib, sourcemap: false, rollupOptions: {}, diff --git a/packages/vite/src/node/constants.ts b/packages/vite/src/node/constants.ts index 41b99fa52a6489..384b6694cd9cf5 100644 --- a/packages/vite/src/node/constants.ts +++ b/packages/vite/src/node/constants.ts @@ -156,4 +156,6 @@ export const DEFAULT_DEV_PORT = 5173 export const DEFAULT_PREVIEW_PORT = 4173 +export const DEFAULT_ASSETS_INLINE_LIMIT = 4096 + export const METADATA_FILENAME = '_metadata.json' diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index 9295bb9a4407dc..df5fdc1eb1aa21 100644 --- a/packages/vite/src/node/plugins/asset.ts +++ b/packages/vite/src/node/plugins/asset.ts @@ -26,7 +26,7 @@ import { removeLeadingSlash, withTrailingSlash, } from '../utils' -import { FS_PREFIX } from '../constants' +import { DEFAULT_ASSETS_INLINE_LIMIT, FS_PREFIX } from '../constants' import type { ModuleGraph } from '../server/moduleGraph' // referenceId is base64url but replaces - with $ @@ -325,7 +325,7 @@ async function fileToBuiltUrl( config: ResolvedConfig, pluginContext: PluginContext, skipPublicCheck = false, - shouldInline?: boolean, + forceInline?: boolean, ): Promise { if (!skipPublicCheck && checkPublicFile(id, config)) { return publicFileToBuiltUrl(id, config) @@ -340,18 +340,8 @@ async function fileToBuiltUrl( const file = cleanUrl(id) const content = await fsp.readFile(file) - if (shouldInline == null) { - shouldInline = - !!config.build.lib || - // Don't inline SVG with fragments, as they are meant to be reused - (!(file.endsWith('.svg') && id.includes('#')) && - !file.endsWith('.html') && - content.length < Number(config.build.assetsInlineLimit) && - !isGitLfsPlaceholder(content)) - } - let url: string - if (shouldInline) { + if (shouldInline(config, file, id, content, forceInline)) { if (config.build.lib && isGitLfsPlaceholder(content)) { config.logger.warn( colors.yellow(`Inlined file ${id} was not downloaded via Git LFS`), @@ -392,7 +382,7 @@ export async function urlToBuiltUrl( importer: string, config: ResolvedConfig, pluginContext: PluginContext, - shouldInline?: boolean, + forceInline?: boolean, ): Promise { if (checkPublicFile(url, config)) { return publicFileToBuiltUrl(url, config) @@ -407,10 +397,33 @@ export async function urlToBuiltUrl( pluginContext, // skip public check since we just did it above true, - shouldInline, + forceInline, ) } +const shouldInline = ( + config: ResolvedConfig, + file: string, + id: string, + content: Buffer, + forceInline: boolean | undefined, +): boolean => { + if (config.build.lib) return true + if (forceInline !== undefined) return forceInline + let limit: number + if (typeof config.build.assetsInlineLimit === 'function') { + const userShouldInline = config.build.assetsInlineLimit(file, content) + if (userShouldInline != null) return userShouldInline + limit = DEFAULT_ASSETS_INLINE_LIMIT + } else { + limit = Number(config.build.assetsInlineLimit) + } + if (file.endsWith('.html')) return false + // Don't inline SVG with fragments, as they are meant to be reused + if (file.endsWith('.svg') && id.includes('#')) return false + return content.length < limit && !isGitLfsPlaceholder(content) +} + const nestedQuotesRE = /"[^"']*'[^"]*"|'[^'"]*"[^']*'/ // Inspired by https://github.com/iconify/iconify/blob/main/packages/utils/src/svg/url.ts diff --git a/playground/worker/vite.config-es.js b/playground/worker/vite.config-es.js index 995902373d72a5..eba1f7e2f1bd76 100644 --- a/playground/worker/vite.config-es.js +++ b/playground/worker/vite.config-es.js @@ -21,7 +21,8 @@ export default defineConfig({ }, build: { outDir: 'dist/es', - assetsInlineLimit: 100, // keep SVG as assets URL + assetsInlineLimit: (filePath) => + filePath.endsWith('.svg') ? false : undefined, rollupOptions: { output: { assetFileNames: 'assets/[name].[ext]', diff --git a/playground/worker/vite.config-iife.js b/playground/worker/vite.config-iife.js index 7ac8220e25a7e4..3d6d0de8a170e5 100644 --- a/playground/worker/vite.config-iife.js +++ b/playground/worker/vite.config-iife.js @@ -22,7 +22,8 @@ export default defineConfig({ }, build: { outDir: 'dist/iife', - assetsInlineLimit: 100, // keep SVG as assets URL + assetsInlineLimit: (filePath) => + filePath.endsWith('.svg') ? false : undefined, manifest: true, rollupOptions: { output: { diff --git a/playground/worker/vite.config-relative-base-iife.js b/playground/worker/vite.config-relative-base-iife.js index 0ea1a872d59e82..657d7b3094acf6 100644 --- a/playground/worker/vite.config-relative-base-iife.js +++ b/playground/worker/vite.config-relative-base-iife.js @@ -21,7 +21,8 @@ export default defineConfig(({ isPreview }) => ({ }, build: { outDir: 'dist/relative-base-iife', - assetsInlineLimit: 100, // keep SVG as assets URL + assetsInlineLimit: (filePath) => + filePath.endsWith('.svg') ? false : undefined, rollupOptions: { output: { assetFileNames: 'other-assets/[name]-[hash].[ext]', diff --git a/playground/worker/vite.config-relative-base.js b/playground/worker/vite.config-relative-base.js index d5935bb3ef9132..f4f22cc12e0cd9 100644 --- a/playground/worker/vite.config-relative-base.js +++ b/playground/worker/vite.config-relative-base.js @@ -21,7 +21,8 @@ export default defineConfig(({ isPreview }) => ({ }, build: { outDir: 'dist/relative-base', - assetsInlineLimit: 100, // keep SVG as assets URL + assetsInlineLimit: (filePath) => + filePath.endsWith('.svg') ? false : undefined, rollupOptions: { output: { assetFileNames: 'other-assets/[name]-[hash].[ext]', diff --git a/playground/worker/worker-sourcemap-config.js b/playground/worker/worker-sourcemap-config.js index 25dd8aa83b70a0..2c4e40e78d2a5a 100644 --- a/playground/worker/worker-sourcemap-config.js +++ b/playground/worker/worker-sourcemap-config.js @@ -35,7 +35,8 @@ export default (sourcemap) => { }, build: { outDir: `dist/iife-${typeName}/`, - assetsInlineLimit: 100, // keep SVG as assets URL + assetsInlineLimit: (filePath) => + filePath.endsWith('.svg') ? false : undefined, sourcemap: sourcemap, rollupOptions: { output: {