Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(importGlob): don't warn when CSS default import is not used #11121

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -4,15 +4,12 @@ import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { transformGlobImport } from '../../../plugins/importMetaGlob'
import { transformWithEsbuild } from '../../../plugins/esbuild'
import type { Logger } from '../../../logger'
import { createLogger } from '../../../logger'

const __dirname = resolve(fileURLToPath(import.meta.url), '..')

describe('fixture', async () => {
const resolveId = (id: string) => id
const root = resolve(__dirname, '..')
const logger = createLogger()

it('transform', async () => {
const id = resolve(__dirname, './fixture-a/index.ts')
Expand All @@ -21,22 +18,14 @@ describe('fixture', async () => {
).code

expect(
(
await transformGlobImport(code, id, root, resolveId, logger)
)?.s.toString()
(await transformGlobImport(code, id, root, resolveId, true))?.s.toString()
).toMatchSnapshot()
})

it('preserve line count', async () => {
const getTransformedLineCount = async (code: string) =>
(
await transformGlobImport(
code,
'virtual:module',
root,
resolveId,
logger
)
await transformGlobImport(code, 'virtual:module', root, resolveId, true)
)?.s
.toString()
.split('\n').length
Expand All @@ -61,13 +50,7 @@ describe('fixture', async () => {
].join('\n')
expect(
(
await transformGlobImport(
code,
'virtual:module',
root,
resolveId,
logger
)
await transformGlobImport(code, 'virtual:module', root, resolveId, true)
)?.s.toString()
).toMatchSnapshot()

Expand All @@ -77,7 +60,7 @@ describe('fixture', async () => {
'virtual:module',
root,
resolveId,
logger
true
)
expect('no error').toBe('should throw an error')
} catch (err) {
Expand All @@ -95,47 +78,8 @@ describe('fixture', async () => {

expect(
(
await transformGlobImport(code, id, root, resolveId, logger, true)
await transformGlobImport(code, id, root, resolveId, true, true)
)?.s.toString()
).toMatchSnapshot()
})

it('warn when glob css without ?inline', async () => {
sapphi-red marked this conversation as resolved.
Show resolved Hide resolved
const logs: string[] = []
const logger = {
warn(msg: string) {
logs.push(msg)
}
} as Logger

await transformGlobImport(
"import.meta.glob('./fixture-c/*.css', { query: '?inline' })",
fileURLToPath(import.meta.url),
root,
resolveId,
logger
)
expect(logs).toHaveLength(0)

await transformGlobImport(
"import.meta.glob('./fixture-c/*.module.css')",
fileURLToPath(import.meta.url),
root,
resolveId,
logger
)
expect(logs).toHaveLength(0)

await transformGlobImport(
"import.meta.glob('./fixture-c/*.css')",
fileURLToPath(import.meta.url),
root,
resolveId,
logger
)
expect(logs).toHaveLength(1)
expect(logs[0]).to.include(
'Globbing CSS files without the ?inline query is deprecated'
)
})
})
2 changes: 1 addition & 1 deletion packages/vite/src/node/optimizer/scan.ts
Expand Up @@ -223,7 +223,7 @@ function esbuildScanPlugin(
id,
config.root,
resolve,
config.logger
config.isProduction
)

return result?.s.toString() || transpiledContents
Expand Down
113 changes: 76 additions & 37 deletions packages/vite/src/node/plugins/importMetaGlob.ts
@@ -1,6 +1,5 @@
import { isAbsolute, posix } from 'node:path'
import micromatch from 'micromatch'
import colors from 'picocolors'
import { stripLiteral } from 'strip-literal'
import type {
ArrayExpression,
Expand All @@ -23,13 +22,7 @@ import type { Plugin } from '../plugin'
import type { ViteDevServer } from '../server'
import type { ModuleNode } from '../server/moduleGraph'
import type { ResolvedConfig } from '../config'
import {
generateCodeFrame,
normalizePath,
slash,
transformStableResult
} from '../utils'
import type { Logger } from '../logger'
import { normalizePath, slash, transformStableResult } from '../utils'
import { isCSSRequest, isModuleCSSRequest } from './css'

const { isMatch, scan } = micromatch
Expand Down Expand Up @@ -77,7 +70,7 @@ export function importGlobPlugin(config: ResolvedConfig): Plugin {
id,
config.root,
(im) => this.resolve(im, id).then((i) => i?.id || im),
config.logger,
config.isProduction,
config.experimental.importGlobRestoreExtension
)
if (result) {
Expand Down Expand Up @@ -308,6 +301,24 @@ const importPrefix = '__vite_glob_'

const { basename, dirname, relative, join } = posix

const warnedCSSDefaultImportVarName = '__vite_warned_css_default_import'
const jsonStringifyInOneline = (input: any) =>
JSON.stringify(input).replace(/[{,:]/g, '$& ').replace(/\}/g, ' }')
const createCssDefaultImportWarning = (
globs: string[],
options: GeneralImportGlobOptions
) =>
`if (!${warnedCSSDefaultImportVarName}) {` +
`${warnedCSSDefaultImportVarName} = true;` +
`console.warn(${JSON.stringify(
'Default import of CSS without `?inline` is deprecated. ' +
"Add the `{ query: '?inline' }` glob option to fix this.\n" +
`For example: \`import.meta.glob(${jsonStringifyInOneline(
globs.length === 1 ? globs[0] : globs
)}, ${jsonStringifyInOneline({ ...options, query: '?inline' })})\``
)});` +
`}`

export interface TransformGlobImportResult {
s: MagicString
matches: ParsedImportGlob[]
Expand All @@ -322,7 +333,7 @@ export async function transformGlobImport(
id: string,
root: string,
resolveId: IdResolver,
logger: Logger,
isProduction: boolean,
restoreQueryExtension = false
): Promise<TransformGlobImportResult | null> {
id = slash(id)
Expand Down Expand Up @@ -353,7 +364,15 @@ export async function transformGlobImport(
const staticImports = (
await Promise.all(
matches.map(
async ({ globsResolved, isRelative, options, index, start, end }) => {
async ({
globs,
globsResolved,
isRelative,
options,
index,
start,
end
}) => {
const cwd = getCommonBase(globsResolved) ?? root
const files = (
await fg(globsResolved, {
Expand All @@ -379,25 +398,6 @@ export async function transformGlobImport(

if (query && !query.startsWith('?')) query = `?${query}`

if (
!query && // ignore custom queries
files.some(
(file) => isCSSRequest(file) && !isModuleCSSRequest(file)
)
) {
logger.warn(
`\n` +
colors.cyan(id) +
`\n` +
colors.reset(generateCodeFrame(code, start)) +
`\n` +
colors.yellow(
`Globbing CSS files without the ?inline query is deprecated. ` +
`Add the \`{ query: '?inline' }\` glob option to fix this.`
)
)
}

const resolvePaths = (file: string) => {
if (!dir) {
if (isRelative)
Expand All @@ -422,6 +422,7 @@ export async function transformGlobImport(
return { filePath, importPath }
}

let includesCSS = false
files.forEach((file, i) => {
const paths = resolvePaths(file)
const filePath = paths.filePath
Expand All @@ -436,6 +437,10 @@ export async function transformGlobImport(

importPath = `${importPath}${importQuery}`

const isCSS =
!query && isCSSRequest(file) && !isModuleCSSRequest(file)
includesCSS ||= isCSS

const importKey =
options.import && options.import !== '*'
? options.import
Expand All @@ -449,14 +454,36 @@ export async function transformGlobImport(
staticImports.push(
`import ${expression} from ${JSON.stringify(importPath)}`
)
objectProps.push(`${JSON.stringify(filePath)}: ${variableName}`)
if (!isProduction && isCSS) {
objectProps.push(
`get ${JSON.stringify(
filePath
)}() { ${createCssDefaultImportWarning(
globs,
options
)} return ${variableName} }`
)
} else {
objectProps.push(`${JSON.stringify(filePath)}: ${variableName}`)
}
} else {
let importStatement = `import(${JSON.stringify(importPath)})`
if (importKey)
importStatement += `.then(m => m[${JSON.stringify(importKey)}])`
objectProps.push(
`${JSON.stringify(filePath)}: () => ${importStatement}`
)
if (!isProduction && isCSS) {
objectProps.push(
`${JSON.stringify(
filePath
)}: () => { ${createCssDefaultImportWarning(
globs,
options
)} return ${importStatement}}`
)
} else {
objectProps.push(
`${JSON.stringify(filePath)}: () => ${importStatement}`
)
}
}
})

Expand All @@ -468,9 +495,21 @@ export async function transformGlobImport(
originalLineBreakCount > 0
? '\n'.repeat(originalLineBreakCount)
: ''
const replacement = `/* #__PURE__ */ Object.assign({${objectProps.join(
','
)}${lineBreaks}})`

let replacement: string
if (!isProduction && includesCSS) {
replacement =
'/* #__PURE__ */ Object.assign(' +
'(() => {' +
`let ${warnedCSSDefaultImportVarName} = false;` +
`return {${objectProps.join(',')}${lineBreaks}};` +
'})()' +
')'
} else {
replacement = `/* #__PURE__ */ Object.assign({${objectProps.join(
','
)}${lineBreaks}})`
}
s.overwrite(start, end, replacement)

return staticImports
Expand Down
27 changes: 27 additions & 0 deletions playground/glob-import/__tests__/glob-import.spec.ts
Expand Up @@ -7,8 +7,11 @@ import {
findAssetFile,
getColor,
isBuild,
isServe,
page,
removeFile,
untilBrowserLogAfter,
viteTestUrl,
withRetry
} from '~utils'

Expand Down Expand Up @@ -190,6 +193,30 @@ test('tree-shake eager css', async () => {
}
})

test('warn CSS default import', async () => {
const logs = await untilBrowserLogAfter(
() => page.goto(viteTestUrl),
'Ran scripts'
)
const noTreeshakeCSSMessage =
'For example: `import.meta.glob("/no-tree-shake.css", { "eager": true, "query": "?inline" })`'
const treeshakeCSSMessage =
'For example: `import.meta.glob("/tree-shake.css", { "eager": true, "query": "?inline" })`'

expect(
logs.some((log) => log.includes(noTreeshakeCSSMessage)),
`expected logs to include a message including ${JSON.stringify(
noTreeshakeCSSMessage
)}`
).toBe(isServe)
expect(
logs.every((log) => !log.includes(treeshakeCSSMessage)),
`expected logs not to include a message including ${JSON.stringify(
treeshakeCSSMessage
)}`
).toBe(true)
})

test('escapes special chars in globs without mangling user supplied glob suffix', async () => {
// the escape dir contains subdirectories where each has a name that needs escaping for glob safety
// inside each of them is a glob.js that exports the result of a relative glob `./**/*.js`
Expand Down
4 changes: 4 additions & 0 deletions playground/glob-import/index.html
Expand Up @@ -140,3 +140,7 @@ <h2>Escape alias glob</h2>
.map(([glob]) => glob)
document.querySelector('.escape-alias').textContent = alias.sort().join('\n')
</script>

<script type="module">
console.log('Ran scripts')
</script>