diff --git a/packages/vite/src/node/constants.ts b/packages/vite/src/node/constants.ts index 12bc4f22c10038..5a108652ef7ce6 100644 --- a/packages/vite/src/node/constants.ts +++ b/packages/vite/src/node/constants.ts @@ -41,7 +41,7 @@ export const DEFAULT_CONFIG_FILES = [ export const JS_TYPES_RE = /\.(?:j|t)sx?$|\.mjs$/ -export const OPTIMIZABLE_ENTRY_RE = /\.(?:m?js|ts)$/ +export const OPTIMIZABLE_ENTRY_RE = /\.(?:(m|c)?js|ts)$/ export const SPECIAL_QUERY_RE = /[\?&](?:worker|sharedworker|raw|url)\b/ diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 82d8c8aececcb7..9024aca62ac1bd 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -14,6 +14,7 @@ import { emptyDir, flattenId, getHash, + isOptimizable, lookupFile, normalizeId, normalizePath, @@ -644,20 +645,26 @@ export async function findKnownImports( async function addManuallyIncludedOptimizeDeps( deps: Record, config: ResolvedConfig, - extra?: string[], + extra: string[] = [], filter?: (id: string) => boolean ): Promise { - const include = [...(config.optimizeDeps?.include ?? []), ...(extra ?? [])] - if (include) { + const optimizeDepsInclude = config.optimizeDeps?.include ?? [] + if (optimizeDepsInclude.length || extra.length) { const resolve = config.createResolver({ asSrc: false, scan: true }) - for (const id of include) { + for (const id of [...optimizeDepsInclude, ...extra]) { // normalize 'foo >bar` as 'foo > bar' to prevent same id being added // and for pretty printing const normalizedId = normalizeId(id) if (!deps[normalizedId] && filter?.(normalizedId) !== false) { const entry = await resolve(id) if (entry) { - deps[normalizedId] = entry + if (isOptimizable(entry, config.optimizeDeps)) { + deps[normalizedId] = entry + } else if (optimizeDepsInclude.includes(id)) { + config.logger.warn( + `Cannot optimize included dependency: ${colors.cyan(id)}` + ) + } } else { throw new Error( `Failed to resolve force included dependency: ${colors.cyan(id)}` diff --git a/packages/vite/src/node/optimizer/scan.ts b/packages/vite/src/node/optimizer/scan.ts index 36eb0229c6fcc0..5608472b4efe91 100644 --- a/packages/vite/src/node/optimizer/scan.ts +++ b/packages/vite/src/node/optimizer/scan.ts @@ -6,18 +6,14 @@ import type { Loader, OnLoadResult, Plugin } from 'esbuild' import { build, transform } from 'esbuild' import colors from 'picocolors' import type { ResolvedConfig } from '..' -import { - JS_TYPES_RE, - KNOWN_ASSET_TYPES, - OPTIMIZABLE_ENTRY_RE, - SPECIAL_QUERY_RE -} from '../constants' +import { JS_TYPES_RE, KNOWN_ASSET_TYPES, SPECIAL_QUERY_RE } from '../constants' import { cleanUrl, createDebugger, dataUrlRE, externalRE, isObject, + isOptimizable, moduleListContains, multilineCommentsRE, normalizePath, @@ -189,10 +185,6 @@ function esbuildScanPlugin( '@vite/env' ] - const isOptimizable = (id: string) => - OPTIMIZABLE_ENTRY_RE.test(id) || - !!config.optimizeDeps.extensions?.some((ext) => id.endsWith(ext)) - const externalUnlessEntry = ({ path }: { path: string }) => ({ path, external: !entries.includes(path) @@ -235,7 +227,11 @@ function esbuildScanPlugin( // It is possible for the scanner to scan html types in node_modules. // If we can optimize this html type, skip it so it's handled by the // bare import resolve, and recorded as optimization dep. - if (resolved.includes('node_modules') && isOptimizable(resolved)) return + if ( + resolved.includes('node_modules') && + isOptimizable(resolved, config.optimizeDeps) + ) + return return { path: resolved, namespace: 'html' @@ -382,7 +378,7 @@ function esbuildScanPlugin( } if (resolved.includes('node_modules') || include?.includes(id)) { // dependency or forced included, externalize and stop crawling - if (isOptimizable(resolved)) { + if (isOptimizable(resolved, config.optimizeDeps)) { depImports[id] = resolved } return externalUnlessEntry({ path: id }) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 796f95a35f6667..345ce0a8627365 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -24,6 +24,7 @@ import { DEFAULT_EXTENSIONS, ENV_PUBLIC_PATH, FS_PREFIX, + OPTIMIZABLE_ENTRY_RE, VALID_ID_PREFIX, wildcardHosts } from './constants' @@ -91,6 +92,16 @@ export function moduleListContains( return moduleList?.some((m) => m === id || id.startsWith(m + '/')) } +export function isOptimizable( + id: string, + optimizeDepsConfig: ResolvedConfig['optimizeDeps'] +): boolean { + return ( + OPTIMIZABLE_ENTRY_RE.test(id) || + (optimizeDepsConfig.extensions?.some((ext) => id.endsWith(ext)) ?? false) + ) +} + export const bareImportRE = /^[\w@](?!.*:\/\/)/ export const deepImportRE = /^([^@][^/]*)\/|^(@[^/]+\/[^/]+)\// diff --git a/playground/optimize-deps/non-optimizable-include/index.css b/playground/optimize-deps/non-optimizable-include/index.css new file mode 100644 index 00000000000000..161363ab3641a8 --- /dev/null +++ b/playground/optimize-deps/non-optimizable-include/index.css @@ -0,0 +1,4 @@ +@font-face { + font-family: 'Not Real Sans'; + src: url('./i-throw-if-you-optimize-this-file.woff') format('woff'); +} diff --git a/playground/optimize-deps/non-optimizable-include/package.json b/playground/optimize-deps/non-optimizable-include/package.json new file mode 100644 index 00000000000000..b49a3f7a4c004d --- /dev/null +++ b/playground/optimize-deps/non-optimizable-include/package.json @@ -0,0 +1,9 @@ +{ + "name": "non-optimizable-include", + "private": true, + "type": "module", + "version": "0.0.0", + "exports": { + ".": "./index.css" + } +} diff --git a/playground/optimize-deps/vite.config.js b/playground/optimize-deps/vite.config.js index 133d0da1e32b74..0b857173fe1e83 100644 --- a/playground/optimize-deps/vite.config.js +++ b/playground/optimize-deps/vite.config.js @@ -16,7 +16,12 @@ module.exports = { }, optimizeDeps: { - include: ['dep-linked-include', 'nested-exclude > nested-include'], + include: [ + 'dep-linked-include', + 'nested-exclude > nested-include', + // will throw if optimized (should log warning instead) + 'non-optimizable-include' + ], exclude: ['nested-exclude'], esbuildOptions: { plugins: [ diff --git a/playground/ssr-deps/no-external-css/index.css b/playground/ssr-deps/no-external-css/index.css new file mode 100644 index 00000000000000..161363ab3641a8 --- /dev/null +++ b/playground/ssr-deps/no-external-css/index.css @@ -0,0 +1,4 @@ +@font-face { + font-family: 'Not Real Sans'; + src: url('./i-throw-if-you-optimize-this-file.woff') format('woff'); +} diff --git a/playground/ssr-deps/no-external-css/package.json b/playground/ssr-deps/no-external-css/package.json new file mode 100644 index 00000000000000..c86fdf92229bc5 --- /dev/null +++ b/playground/ssr-deps/no-external-css/package.json @@ -0,0 +1,9 @@ +{ + "name": "no-external-css", + "private": true, + "type": "module", + "version": "0.0.0", + "exports": { + ".": "./index.css" + } +} diff --git a/playground/ssr-deps/package.json b/playground/ssr-deps/package.json index 4dd1ee49aea717..5f9493a7dbf6be 100644 --- a/playground/ssr-deps/package.json +++ b/playground/ssr-deps/package.json @@ -19,7 +19,8 @@ "read-file-content": "file:./read-file-content", "require-absolute": "file:./require-absolute", "ts-transpiled-exports": "file:./ts-transpiled-exports", - "no-external-cjs": "file:./no-external-cjs" + "no-external-cjs": "file:./no-external-cjs", + "no-external-css": "file:./no-external-css" }, "devDependencies": { "cross-env": "^7.0.3", diff --git a/playground/ssr-deps/server.js b/playground/ssr-deps/server.js index 52a214f7668e39..0e1c28ce31dbf0 100644 --- a/playground/ssr-deps/server.js +++ b/playground/ssr-deps/server.js @@ -35,7 +35,7 @@ export async function createServer(root = process.cwd(), hmrPort) { }, appType: 'custom', ssr: { - noExternal: ['no-external-cjs'] + noExternal: ['no-external-cjs', 'no-external-css'] } }) // use vite's connect instance as middleware diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 404ceea6b8f837..8935eb766366cf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -857,6 +857,7 @@ importers: express: ^4.18.1 forwarded-export: file:./forwarded-export no-external-cjs: file:./no-external-cjs + no-external-css: file:./no-external-css object-assigned-exports: file:./object-assigned-exports only-object-assigned-exports: file:./only-object-assigned-exports primitive-export: file:./primitive-export @@ -869,6 +870,7 @@ importers: define-property-exports: file:playground/ssr-deps/define-property-exports forwarded-export: file:playground/ssr-deps/forwarded-export no-external-cjs: file:playground/ssr-deps/no-external-cjs + no-external-css: file:playground/ssr-deps/no-external-css object-assigned-exports: file:playground/ssr-deps/object-assigned-exports only-object-assigned-exports: file:playground/ssr-deps/only-object-assigned-exports primitive-export: file:playground/ssr-deps/primitive-export @@ -894,6 +896,9 @@ importers: playground/ssr-deps/no-external-cjs: specifiers: {} + playground/ssr-deps/no-external-css: + specifiers: {} + playground/ssr-deps/object-assigned-exports: specifiers: {} @@ -8859,6 +8864,12 @@ packages: version: 0.0.0 dev: false + file:playground/ssr-deps/no-external-css: + resolution: {directory: playground/ssr-deps/no-external-css, type: directory} + name: primitive-export-css + version: 0.0.0 + dev: false + file:playground/ssr-deps/object-assigned-exports: resolution: {directory: playground/ssr-deps/object-assigned-exports, type: directory} name: object-assigned-exports