diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index 2bfec01237b119..d47da5489e95c5 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -94,6 +94,20 @@ export interface CSSModulesOptions { | null } +interface CompileCSSResult { + code: string + map?: SourceMapInput + ast?: PostCSS.Result + modules?: Record + deps?: Set +} + +type CSSCompiler = ( + id: string, + raw: string, + ssr?: boolean +) => Promise + const cssLangs = `\\.(css|less|sass|scss|styl|stylus|pcss|postcss)($|\\?)` const cssLangRE = new RegExp(cssLangs) const cssModuleRE = new RegExp(`\\.module${cssLangs}`) @@ -146,13 +160,7 @@ const postcssConfigCache = new WeakMap< export function cssPlugin(config: ResolvedConfig): Plugin { let server: ViteDevServer let moduleCache: Map> - - const resolveUrl = config.createResolver({ - preferRelative: true, - tryIndex: false, - extensions: [] - }) - const atImportResolvers = createCSSResolvers(config) + let cssCompiler: CSSCompiler return { name: 'vite:css', @@ -177,87 +185,9 @@ export function cssPlugin(config: ResolvedConfig): Plugin { ) { return } + cssCompiler ||= createCSSCompiler(config, this, server) const ssr = options?.ssr === true - - const urlReplacer: CssUrlReplacer = async (url, importer) => { - if (checkPublicFile(url, config)) { - return config.base + url.slice(1) - } - const resolved = await resolveUrl(url, importer) - if (resolved) { - return fileToUrl(resolved, config, this) - } - return url - } - - const { - code: css, - modules, - deps, - map - } = await compileCSS( - id, - raw, - config, - urlReplacer, - atImportResolvers, - server - ) - if (modules) { - moduleCache.set(id, modules) - } - - // track deps for build watch mode - if (config.command === 'build' && config.build.watch && deps) { - for (const file of deps) { - this.addWatchFile(file) - } - } - - // dev - if (server) { - // server only logic for handling CSS @import dependency hmr - const { moduleGraph } = server - const thisModule = moduleGraph.getModuleById(id) - if (thisModule) { - // CSS modules cannot self-accept since it exports values - const isSelfAccepting = !modules && !inlineRE.test(id) - if (deps) { - // record deps in the module graph so edits to @import css can trigger - // main import to hot update - const depModules = new Set() - for (const file of deps) { - depModules.add( - isCSSRequest(file) - ? moduleGraph.createFileOnlyEntry(file) - : await moduleGraph.ensureEntryFromUrl( - ( - await fileToUrl(file, config, this) - ).replace( - (config.server?.origin ?? '') + config.base, - '/' - ), - ssr - ) - ) - } - moduleGraph.updateModuleInfo( - thisModule, - depModules, - // The root CSS proxy module is self-accepting and should not - // have an explicit accept list - new Set(), - isSelfAccepting, - ssr - ) - for (const file of deps) { - this.addWatchFile(file) - } - } else { - thisModule.isSelfAccepting = isSelfAccepting - } - } - } + const { code: css, map } = await cssCompiler(id, raw, ssr) return { code: css, @@ -615,7 +545,11 @@ function getCssResolversKeys( return Object.keys(resolvers) as unknown as Array } -export function createCSSCompiler(config: ResolvedConfig, ctx: PluginContext) { +export function createCSSCompiler( + config: ResolvedConfig, + ctx: PluginContext, + server?: ViteDevServer +): CSSCompiler { const resolveUrl = config.createResolver({ preferRelative: true, tryIndex: false, @@ -634,8 +568,71 @@ export function createCSSCompiler(config: ResolvedConfig, ctx: PluginContext) { return url } - return (id: string, code: string) => - compileCSS(id, code, config, urlReplacer, atImportResolvers) + return async (id: string, raw: string, ssr?: boolean) => { + const compiledResult = await compileCSS( + id, + raw, + config, + urlReplacer, + atImportResolvers, + server + ) + const { modules, deps } = compiledResult + if (modules) { + cssModulesCache.get(config)?.set(id, modules) + } + + // track deps for build watch mode + if (config.command === 'build' && config.build.watch && deps) { + for (const file of deps) { + ctx.addWatchFile(file) + } + } + + // dev + if (server) { + // server only logic for handling CSS @import dependency hmr + const { moduleGraph } = server + const thisModule = moduleGraph.getModuleById(id) + if (thisModule) { + // CSS modules cannot self-accept since it exports values + const isSelfAccepting = !modules && !inlineRE.test(id) + if (deps) { + // record deps in the module graph so edits to @import css can trigger + // main import to hot update + const depModules = new Set() + for (const file of deps) { + depModules.add( + isCSSRequest(file) + ? moduleGraph.createFileOnlyEntry(file) + : await moduleGraph.ensureEntryFromUrl( + ( + await fileToUrl(file, config, ctx) + ).replace((config.server?.origin ?? '') + config.base, '/'), + ssr + ) + ) + } + moduleGraph.updateModuleInfo( + thisModule, + depModules, + // The root CSS proxy module is self-accepting and should not + // have an explicit accept list + new Set(), + isSelfAccepting, + ssr + ) + for (const file of deps) { + ctx.addWatchFile(file) + } + } else { + thisModule.isSelfAccepting = isSelfAccepting + } + } + } + + return compiledResult + } } async function compileCSS( @@ -645,13 +642,7 @@ async function compileCSS( urlReplacer: CssUrlReplacer, atImportResolvers: CSSAtImportResolvers, server?: ViteDevServer -): Promise<{ - code: string - map?: SourceMapInput - ast?: PostCSS.Result - modules?: Record - deps?: Set -}> { +): Promise { const { modules: modulesOptions, preprocessorOptions, diff --git a/packages/vite/src/node/server/middlewares/indexHtml.ts b/packages/vite/src/node/server/middlewares/indexHtml.ts index f4c81ed6f88db6..689b5d0cb046b4 100644 --- a/packages/vite/src/node/server/middlewares/indexHtml.ts +++ b/packages/vite/src/node/server/middlewares/indexHtml.ts @@ -101,7 +101,7 @@ const devHtmlHook: IndexHtmlTransformHook = async ( { path: htmlPath, filename, server, originalUrl } ) => { // css compile need to emitFile in build mode, and serve mode is not need. - const compileCSS = createCSSCompiler(server!.config, null as any) + const compileCSS = createCSSCompiler(server!.config, null as any, server) const { config, moduleGraph } = server! const base = config.base || '/' @@ -110,7 +110,7 @@ const devHtmlHook: IndexHtmlTransformHook = async ( const filePath = cleanUrl(htmlPath) const styleUrl: AssetNode[] = [] - const addInlineModule = (node: ElementNode, ext: 'js' | 'css') => { + const addInlineModule = (node: ElementNode, ext: 'js') => { inlineModuleIndex++ const url = filePath.replace(normalizePath(config.root), '') @@ -137,17 +137,12 @@ const devHtmlHook: IndexHtmlTransformHook = async ( if (module) { server?.moduleGraph.invalidateModule(module) } - if (ext === 'js') { - s.overwrite( - node.loc.start.offset, - node.loc.end.offset, - ``, - { contentOnly: true } - ) - } else if (ext === 'css') { - // just use the style update hmr don't render css - s.append(``) - } + s.overwrite( + node.loc.start.offset, + node.loc.end.offset, + ``, + { contentOnly: true } + ) } await traverseHtml(html, htmlPath, (node) => { @@ -173,7 +168,6 @@ const devHtmlHook: IndexHtmlTransformHook = async ( end: children.loc.end.offset, code: children.content }) - addInlineModule(node, 'css') } // elements with [href/src] attrs @@ -192,8 +186,8 @@ const devHtmlHook: IndexHtmlTransformHook = async ( }) for (const { start, end, code } of styleUrl) { - const compliedCode = await compileCSS(filename + '.css', code) - s.overwrite(start, end, compliedCode.code) + const complied = await compileCSS(filename + '.css', code) + s.overwrite(start, end, complied.code) } html = s.toString()