Skip to content

Commit

Permalink
fix(css modules): apply preprocessor to composed file if needed
Browse files Browse the repository at this point in the history
Closes: vitejs#10340
  • Loading branch information
Thiry1 committed Feb 16, 2023
1 parent 0a50c59 commit f4f3199
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 4 deletions.
49 changes: 49 additions & 0 deletions packages/vite/src/node/__tests__/plugins/css.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,49 @@ position: fixed;
expect(result1.code).toBe(result2.code)
resetMock()
})

// following tests requires `sass` as devDependencies.But when we add sass to devDependencies,types is conflicts between @types/sass and sass itself types.
/*
test('Can compose files that needs to apply preprocessor', async () => {
const barScssFilePath = path.join(process.cwd(), 'bar.scss')
const bazScssFilePath = path.join(process.cwd(), 'baz.scss')
const { transform, resetMock } = await createCssPluginTransform({
[barScssFilePath]: `\
.bar {
// comment
display: block;
composes: baz from '${bazScssFilePath}';
}`,
[bazScssFilePath]: `\
.baz {
// comment
color: red;
}`,
})
const result = await transform(
`\
.foo {
position: fixed;
composes: bar from '${barScssFilePath}';
}`,
'/foo.module.scss',
)
expect(result.code).toBe(
`\
._baz_14enc_1 {
color: red;
}._bar_1vym1_1 {
display: block;
}._foo_1m9mb_1 {
position: fixed;
}`,
)
resetMock()
})
*/
})

describe('hoist @ rules', () => {
Expand Down Expand Up @@ -218,6 +261,11 @@ async function createCssPluginTransform(
.mockImplementationOnce((p, encoding, callback) => {
callback(null, Buffer.from(files?.[p] ?? ''))
})
const mockFsPromises = vi
.spyOn(fs.promises, 'readFile')
.mockImplementation((p) =>
Promise.resolve(Buffer.from(files?.[p as string] ?? '')),
)

return {
async transform(code: string, id: string) {
Expand All @@ -234,6 +282,7 @@ async function createCssPluginTransform(
},
resetMock() {
mockFs.mockReset()
mockFsPromises.mockReset()
},
}
}
55 changes: 51 additions & 4 deletions packages/vite/src/node/plugins/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ import {
renderAssetUrlInJS,
} from './asset'
import type { ESBuildOptions } from './esbuild'

// const debug = createDebugger('vite:css')

export interface CSSOptions {
Expand Down Expand Up @@ -819,8 +818,10 @@ async function compileCSS(
configToAtImportResolvers.set(config, atImportResolvers)
}

// 2. pre-processors: sass etc.
if (isPreProcessor(lang)) {
const applyPreprocessor = async (
code: string,
lang: PreprocessLang,
): Promise<string> => {
const preProcessor = preProcessors[lang]
let opts = (preprocessorOptions && preprocessorOptions[lang]) || {}
// support @import from node dependencies by default
Expand Down Expand Up @@ -857,7 +858,6 @@ async function compileCSS(
throw preprocessResult.error
}

code = preprocessResult.code
preprocessorMap = combineSourcemapsIfExists(
opts.filename,
preprocessResult.map,
Expand All @@ -872,6 +872,13 @@ async function compileCSS(
}
})
}

return preprocessResult.code
}

// 2. pre-processors: sass etc.
if (isPreProcessor(lang)) {
code = await applyPreprocessor(code, lang)
}

// 3. postcss
Expand Down Expand Up @@ -924,9 +931,49 @@ async function compileCSS(
}

if (isModule) {
const FileSystemLoader = // @ts-expect-error TODO: needs types
(await import('postcss-modules/build/FileSystemLoader')).default
postcssPlugins.unshift(
(await import('postcss-modules')).default({
...modulesOptions,
Loader: class extends FileSystemLoader {
async fetch(
_newPath: string,
relativeTo: string,
_trace: string,
): Promise<any> {
const newPath = _newPath.replace(/^["']|["']$/g, '')
const lang = newPath.match(CSS_LANGS_RE)?.[1] as CssLang | undefined
if (isPreProcessor(lang)) {
const fileResolvedPath = await this.fileResolve(
newPath,
relativeTo,
)
const code = (
await fs.promises.readFile(fileResolvedPath, {
encoding: 'utf-8',
})
).toString()
const processedCode = await applyPreprocessor(code, lang)

const trace = _trace || String.fromCharCode(this.importNr++)
const { injectableSource, exportTokens } = await this.core.load(
processedCode,
fileResolvedPath,
trace,
this.fetch.bind(this),
)
this.sources[fileResolvedPath] = injectableSource
this.traces[trace] = fileResolvedPath
this.tokensByFile[fileResolvedPath] = exportTokens

return exportTokens
} else {
// Use the default loader because there is no need to apply a preprocessor.
return super.fetch(_newPath, relativeTo, _trace)
}
}
},
localsConvention: modulesOptions?.localsConvention,
getJSON(
cssFileName: string,
Expand Down

0 comments on commit f4f3199

Please sign in to comment.