Skip to content

Commit 7eeb6f2

Browse files
alex-kinokonsapphi-redbluwy
authored
fix: add typing to CSSOptions.preprocessorOptions (#18001)
Co-authored-by: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Co-authored-by: bluwy <bjornlu.dev@gmail.com>
1 parent 94cd1e6 commit 7eeb6f2

File tree

3 files changed

+142
-87
lines changed

3 files changed

+142
-87
lines changed

packages/vite/src/node/plugins/css.ts

+99-82
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ import { formatMessages, transform } from 'esbuild'
2727
import type { RawSourceMap } from '@ampproject/remapping'
2828
import { WorkerWithFallback } from 'artichokie'
2929
import { globSync } from 'tinyglobby'
30+
import type {
31+
LessPreprocessorBaseOptions,
32+
SassLegacyPreprocessBaseOptions,
33+
SassModernPreprocessBaseOptions,
34+
StylusPreprocessorBaseOptions,
35+
} from 'types/cssPreprocessorOptions'
3036
import { getCodeWithSourcemap, injectSourcesContent } from '../server/sourcemap'
3137
import type { EnvironmentModuleNode } from '../server/moduleGraph'
3238
import {
@@ -111,7 +117,14 @@ export interface CSSOptions {
111117
* In addition to options specific to each processors, Vite supports `additionalData` option.
112118
* The `additionalData` option can be used to inject extra code for each style content.
113119
*/
114-
preprocessorOptions?: Record<string, any>
120+
preprocessorOptions?: {
121+
scss?: SassPreprocessorOptions
122+
sass?: SassPreprocessorOptions
123+
less?: LessPreprocessorOptions
124+
styl?: StylusPreprocessorOptions
125+
stylus?: StylusPreprocessorOptions
126+
}
127+
115128
/**
116129
* If this option is set, preprocessors will run in workers when possible.
117130
* `true` means the number of CPUs minus 1.
@@ -1135,32 +1148,15 @@ async function compileCSSPreprocessors(
11351148
const atImportResolvers = getAtImportResolvers(
11361149
environment.getTopLevelConfig(),
11371150
)
1138-
1139-
const preProcessor = workerController[lang]
1140-
let opts = (preprocessorOptions && preprocessorOptions[lang]) || {}
1141-
// support @import from node dependencies by default
1142-
switch (lang) {
1143-
case PreprocessLang.scss:
1144-
case PreprocessLang.sass:
1145-
opts = {
1146-
includePaths: ['node_modules'],
1147-
alias: config.resolve.alias,
1148-
...opts,
1149-
}
1150-
break
1151-
case PreprocessLang.less:
1152-
case PreprocessLang.styl:
1153-
case PreprocessLang.stylus:
1154-
opts = {
1155-
paths: ['node_modules'],
1156-
alias: config.resolve.alias,
1157-
...opts,
1158-
}
1151+
const opts = {
1152+
...((preprocessorOptions && preprocessorOptions[lang]) || {}),
1153+
alias: config.resolve.alias,
1154+
// important: set this for relative import resolving
1155+
filename: cleanUrl(id),
1156+
enableSourcemap: devSourcemap ?? false,
11591157
}
1160-
// important: set this for relative import resolving
1161-
opts.filename = cleanUrl(id)
1162-
opts.enableSourcemap = devSourcemap ?? false
11631158

1159+
const preProcessor = workerController[lang]
11641160
const preprocessResult = await preProcessor(
11651161
environment,
11661162
code,
@@ -1977,52 +1973,43 @@ type PreprocessorAdditionalData =
19771973
| PreprocessorAdditionalDataResult
19781974
| Promise<PreprocessorAdditionalDataResult>)
19791975

1980-
type StylePreprocessorOptions = {
1981-
[key: string]: any
1976+
type SassPreprocessorOptions = {
1977+
additionalData?: PreprocessorAdditionalData
1978+
} & (
1979+
| ({ api?: 'legacy' } & SassLegacyPreprocessBaseOptions)
1980+
| ({ api: 'modern' | 'modern-compiler' } & SassModernPreprocessBaseOptions)
1981+
)
1982+
1983+
type LessPreprocessorOptions = {
19821984
additionalData?: PreprocessorAdditionalData
1985+
} & LessPreprocessorBaseOptions
1986+
1987+
type StylusPreprocessorOptions = {
1988+
additionalData?: PreprocessorAdditionalData
1989+
} & StylusPreprocessorBaseOptions
1990+
1991+
type StylePreprocessorInternalOptions = {
19831992
maxWorkers?: number | true
19841993
filename: string
19851994
alias: Alias[]
19861995
enableSourcemap: boolean
19871996
}
19881997

1989-
type SassStylePreprocessorOptions = StylePreprocessorOptions &
1990-
Omit<Sass.LegacyOptions<'async'>, 'data' | 'file' | 'outFile'> & {
1991-
api?: 'legacy' | 'modern' | 'modern-compiler'
1992-
}
1998+
type SassStylePreprocessorInternalOptions = StylePreprocessorInternalOptions &
1999+
SassPreprocessorOptions
19932000

1994-
type StylusStylePreprocessorOptions = StylePreprocessorOptions & {
1995-
define?: Record<string, any>
1996-
}
2001+
type LessStylePreprocessorInternalOptions = StylePreprocessorInternalOptions &
2002+
LessPreprocessorOptions
19972003

1998-
type StylePreprocessor = {
1999-
process: (
2000-
environment: PartialEnvironment,
2001-
source: string,
2002-
root: string,
2003-
options: StylePreprocessorOptions,
2004-
resolvers: CSSAtImportResolvers,
2005-
) => StylePreprocessorResults | Promise<StylePreprocessorResults>
2006-
close: () => void
2007-
}
2004+
type StylusStylePreprocessorInternalOptions = StylePreprocessorInternalOptions &
2005+
StylusPreprocessorOptions
20082006

2009-
type SassStylePreprocessor = {
2007+
type StylePreprocessor<Options extends StylePreprocessorInternalOptions> = {
20102008
process: (
20112009
environment: PartialEnvironment,
20122010
source: string,
20132011
root: string,
2014-
options: SassStylePreprocessorOptions,
2015-
resolvers: CSSAtImportResolvers,
2016-
) => StylePreprocessorResults | Promise<StylePreprocessorResults>
2017-
close: () => void
2018-
}
2019-
2020-
type StylusStylePreprocessor = {
2021-
process: (
2022-
environment: PartialEnvironment,
2023-
source: string,
2024-
root: string,
2025-
options: StylusStylePreprocessorOptions,
2012+
options: Options,
20262013
resolvers: CSSAtImportResolvers,
20272014
) => StylePreprocessorResults | Promise<StylePreprocessorResults>
20282015
close: () => void
@@ -2176,7 +2163,10 @@ const makeScssWorker = (
21762163
sassPath: string,
21772164
data: string,
21782165
// additionalData can a function that is not cloneable but it won't be used
2179-
options: SassStylePreprocessorOptions & { additionalData: undefined },
2166+
options: SassStylePreprocessorInternalOptions & {
2167+
api: 'legacy'
2168+
additionalData: undefined
2169+
},
21802170
) => {
21812171
// eslint-disable-next-line no-restricted-globals -- this function runs inside a cjs worker
21822172
const sass: typeof Sass = require(sassPath)
@@ -2204,6 +2194,8 @@ const makeScssWorker = (
22042194
}
22052195

22062196
const finalOptions: Sass.LegacyOptions<'async'> = {
2197+
// support @import from node dependencies by default
2198+
includePaths: ['node_modules'],
22072199
...options,
22082200
data,
22092201
file: options.filename,
@@ -2287,7 +2279,10 @@ const makeModernScssWorker = (
22872279
sassPath: string,
22882280
data: string,
22892281
// additionalData can a function that is not cloneable but it won't be used
2290-
options: SassStylePreprocessorOptions & { additionalData: undefined },
2282+
options: SassStylePreprocessorInternalOptions & {
2283+
api: 'modern'
2284+
additionalData: undefined
2285+
},
22912286
) => {
22922287
// eslint-disable-next-line no-restricted-globals -- this function runs inside a cjs worker
22932288
const sass: typeof Sass = require(sassPath)
@@ -2453,8 +2448,15 @@ type ScssWorkerResult = {
24532448

24542449
const scssProcessor = (
24552450
maxWorkers: number | undefined,
2456-
): SassStylePreprocessor => {
2457-
const workerMap = new Map<unknown, ReturnType<typeof makeScssWorker>>()
2451+
): StylePreprocessor<SassStylePreprocessorInternalOptions> => {
2452+
const workerMap = new Map<
2453+
unknown,
2454+
ReturnType<
2455+
| typeof makeScssWorker
2456+
| typeof makeModernScssWorker
2457+
| typeof makeModernCompilerScssWorker
2458+
>
2459+
>()
24582460

24592461
return {
24602462
close() {
@@ -2511,6 +2513,7 @@ const scssProcessor = (
25112513
const result = await worker.run(
25122514
sassPackage.path,
25132515
data,
2516+
// @ts-expect-error the correct worker is selected for `options.type`
25142517
optionsWithoutAdditionalData,
25152518
)
25162519
const deps = result.stats.includedFiles.map((f) => cleanScssBugUrl(f))
@@ -2706,7 +2709,9 @@ const makeLessWorker = (
27062709
lessPath: string,
27072710
content: string,
27082711
// additionalData can a function that is not cloneable but it won't be used
2709-
options: StylePreprocessorOptions & { additionalData: undefined },
2712+
options: LessStylePreprocessorInternalOptions & {
2713+
additionalData: undefined
2714+
},
27102715
) => {
27112716
// eslint-disable-next-line no-restricted-globals -- this function runs inside a cjs worker
27122717
const nodeLess: typeof Less = require(lessPath)
@@ -2715,6 +2720,8 @@ const makeLessWorker = (
27152720
options.filename,
27162721
)
27172722
const result = await nodeLess.render(content, {
2723+
// support @import from node dependencies by default
2724+
paths: ['node_modules'],
27182725
...options,
27192726
plugins: [viteResolverPlugin, ...(options.plugins || [])],
27202727
...(options.enableSourcemap
@@ -2734,15 +2741,17 @@ const makeLessWorker = (
27342741
shouldUseFake(_lessPath, _content, options) {
27352742
// plugins are a function and is not serializable
27362743
// in that case, fallback to running in main thread
2737-
return options.plugins?.length > 0
2744+
return !!options.plugins && options.plugins.length > 0
27382745
},
27392746
max: maxWorkers,
27402747
},
27412748
)
27422749
return worker
27432750
}
27442751

2745-
const lessProcessor = (maxWorkers: number | undefined): StylePreprocessor => {
2752+
const lessProcessor = (
2753+
maxWorkers: number | undefined,
2754+
): StylePreprocessor<LessStylePreprocessorInternalOptions> => {
27462755
const workerMap = new Map<unknown, ReturnType<typeof makeLessWorker>>()
27472756

27482757
return {
@@ -2820,12 +2829,18 @@ const makeStylWorker = (maxWorkers: number | undefined) => {
28202829
content: string,
28212830
root: string,
28222831
// additionalData can a function that is not cloneable but it won't be used
2823-
options: StylusStylePreprocessorOptions & { additionalData: undefined },
2832+
options: StylusStylePreprocessorInternalOptions & {
2833+
additionalData: undefined
2834+
},
28242835
) => {
28252836
// eslint-disable-next-line no-restricted-globals -- this function runs inside a cjs worker
28262837
const nodeStylus: typeof Stylus = require(stylusPath)
28272838

2828-
const ref = nodeStylus(content, options)
2839+
const ref = nodeStylus(content, {
2840+
// support @import from node dependencies by default
2841+
paths: ['node_modules'],
2842+
...options,
2843+
})
28292844
if (options.define) {
28302845
for (const key in options.define) {
28312846
ref.define(key, options.define[key])
@@ -2864,7 +2879,7 @@ const makeStylWorker = (maxWorkers: number | undefined) => {
28642879

28652880
const stylProcessor = (
28662881
maxWorkers: number | undefined,
2867-
): StylusStylePreprocessor => {
2882+
): StylePreprocessor<StylusStylePreprocessorInternalOptions> => {
28682883
const workerMap = new Map<unknown, ReturnType<typeof makeStylWorker>>()
28692884

28702885
return {
@@ -2981,21 +2996,23 @@ const createPreprocessorWorkerController = (maxWorkers: number | undefined) => {
29812996
const less = lessProcessor(maxWorkers)
29822997
const styl = stylProcessor(maxWorkers)
29832998

2984-
const sassProcess: StylePreprocessor['process'] = (
2985-
environment,
2986-
source,
2987-
root,
2988-
options,
2989-
resolvers,
2990-
) => {
2991-
return scss.process(
2992-
environment,
2993-
source,
2994-
root,
2995-
{ ...options, indentedSyntax: true, syntax: 'indented' },
2996-
resolvers,
2997-
)
2998-
}
2999+
const sassProcess: StylePreprocessor<SassStylePreprocessorInternalOptions>['process'] =
3000+
(environment, source, root, options, resolvers) => {
3001+
let opts: SassStylePreprocessorInternalOptions
3002+
if (options.api === 'modern' || options.api === 'modern-compiler') {
3003+
opts = { ...options, syntax: 'indented' as const }
3004+
} else {
3005+
const narrowedOptions =
3006+
options as SassStylePreprocessorInternalOptions & {
3007+
api?: 'legacy'
3008+
}
3009+
opts = {
3010+
...narrowedOptions,
3011+
indentedSyntax: true,
3012+
}
3013+
}
3014+
return scss.process(environment, source, root, opts, resolvers)
3015+
}
29993016

30003017
const close = () => {
30013018
less.close()

packages/vite/src/types/shims.d.ts

-5
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,6 @@ declare module 'postcss-import' {
3232
export = plugin
3333
}
3434

35-
// LESS' types somewhat references this which doesn't make sense in Node,
36-
// so we have to shim it
37-
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
38-
declare interface HTMLLinkElement {}
39-
4035
// eslint-disable-next-line no-var
4136
declare var __vite_profile_session: import('node:inspector').Session | undefined
4237
// eslint-disable-next-line no-var
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/* eslint-disable @typescript-eslint/ban-ts-comment */
2+
3+
// @ts-ignore `sass` may not be installed
4+
import type Sass from 'sass'
5+
// @ts-ignore `less` may not be installed
6+
import type Less from 'less'
7+
// @ts-ignore `less` may not be installed
8+
import type Stylus from 'stylus'
9+
10+
/* eslint-enable @typescript-eslint/ban-ts-comment */
11+
12+
export type SassLegacyPreprocessBaseOptions = Omit<
13+
Sass.LegacyStringOptions<'async'>,
14+
| 'data'
15+
| 'file'
16+
| 'outFile'
17+
| 'sourceMap'
18+
| 'omitSourceMapUrl'
19+
| 'sourceMapEmbed'
20+
| 'sourceMapRoot'
21+
>
22+
23+
export type SassModernPreprocessBaseOptions = Omit<
24+
Sass.StringOptions<'async'>,
25+
'url' | 'sourceMap'
26+
>
27+
28+
export type LessPreprocessorBaseOptions = Omit<
29+
Less.Options,
30+
'sourceMap' | 'filename'
31+
>
32+
33+
export type StylusPreprocessorBaseOptions = Omit<
34+
Stylus.RenderOptions,
35+
'filename'
36+
> & { define?: Record<string, any> }
37+
38+
declare global {
39+
// LESS' types somewhat references this which doesn't make sense in Node,
40+
// so we have to shim it
41+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
42+
interface HTMLLinkElement {}
43+
}

0 commit comments

Comments
 (0)