Skip to content

Commit

Permalink
fix: add typing to CSSOptions.preprocessorOptions (#18001)
Browse files Browse the repository at this point in the history
Co-authored-by: sapphi-red <[email protected]>
Co-authored-by: bluwy <[email protected]>
  • Loading branch information
3 people authored Oct 24, 2024
1 parent 94cd1e6 commit 7eeb6f2
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 87 deletions.
181 changes: 99 additions & 82 deletions packages/vite/src/node/plugins/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ import { formatMessages, transform } from 'esbuild'
import type { RawSourceMap } from '@ampproject/remapping'
import { WorkerWithFallback } from 'artichokie'
import { globSync } from 'tinyglobby'
import type {
LessPreprocessorBaseOptions,
SassLegacyPreprocessBaseOptions,
SassModernPreprocessBaseOptions,
StylusPreprocessorBaseOptions,
} from 'types/cssPreprocessorOptions'
import { getCodeWithSourcemap, injectSourcesContent } from '../server/sourcemap'
import type { EnvironmentModuleNode } from '../server/moduleGraph'
import {
Expand Down Expand Up @@ -111,7 +117,14 @@ export interface CSSOptions {
* In addition to options specific to each processors, Vite supports `additionalData` option.
* The `additionalData` option can be used to inject extra code for each style content.
*/
preprocessorOptions?: Record<string, any>
preprocessorOptions?: {
scss?: SassPreprocessorOptions
sass?: SassPreprocessorOptions
less?: LessPreprocessorOptions
styl?: StylusPreprocessorOptions
stylus?: StylusPreprocessorOptions
}

/**
* If this option is set, preprocessors will run in workers when possible.
* `true` means the number of CPUs minus 1.
Expand Down Expand Up @@ -1135,32 +1148,15 @@ async function compileCSSPreprocessors(
const atImportResolvers = getAtImportResolvers(
environment.getTopLevelConfig(),
)

const preProcessor = workerController[lang]
let opts = (preprocessorOptions && preprocessorOptions[lang]) || {}
// support @import from node dependencies by default
switch (lang) {
case PreprocessLang.scss:
case PreprocessLang.sass:
opts = {
includePaths: ['node_modules'],
alias: config.resolve.alias,
...opts,
}
break
case PreprocessLang.less:
case PreprocessLang.styl:
case PreprocessLang.stylus:
opts = {
paths: ['node_modules'],
alias: config.resolve.alias,
...opts,
}
const opts = {
...((preprocessorOptions && preprocessorOptions[lang]) || {}),
alias: config.resolve.alias,
// important: set this for relative import resolving
filename: cleanUrl(id),
enableSourcemap: devSourcemap ?? false,
}
// important: set this for relative import resolving
opts.filename = cleanUrl(id)
opts.enableSourcemap = devSourcemap ?? false

const preProcessor = workerController[lang]
const preprocessResult = await preProcessor(
environment,
code,
Expand Down Expand Up @@ -1977,52 +1973,43 @@ type PreprocessorAdditionalData =
| PreprocessorAdditionalDataResult
| Promise<PreprocessorAdditionalDataResult>)

type StylePreprocessorOptions = {
[key: string]: any
type SassPreprocessorOptions = {
additionalData?: PreprocessorAdditionalData
} & (
| ({ api?: 'legacy' } & SassLegacyPreprocessBaseOptions)
| ({ api: 'modern' | 'modern-compiler' } & SassModernPreprocessBaseOptions)
)

type LessPreprocessorOptions = {
additionalData?: PreprocessorAdditionalData
} & LessPreprocessorBaseOptions

type StylusPreprocessorOptions = {
additionalData?: PreprocessorAdditionalData
} & StylusPreprocessorBaseOptions

type StylePreprocessorInternalOptions = {
maxWorkers?: number | true
filename: string
alias: Alias[]
enableSourcemap: boolean
}

type SassStylePreprocessorOptions = StylePreprocessorOptions &
Omit<Sass.LegacyOptions<'async'>, 'data' | 'file' | 'outFile'> & {
api?: 'legacy' | 'modern' | 'modern-compiler'
}
type SassStylePreprocessorInternalOptions = StylePreprocessorInternalOptions &
SassPreprocessorOptions

type StylusStylePreprocessorOptions = StylePreprocessorOptions & {
define?: Record<string, any>
}
type LessStylePreprocessorInternalOptions = StylePreprocessorInternalOptions &
LessPreprocessorOptions

type StylePreprocessor = {
process: (
environment: PartialEnvironment,
source: string,
root: string,
options: StylePreprocessorOptions,
resolvers: CSSAtImportResolvers,
) => StylePreprocessorResults | Promise<StylePreprocessorResults>
close: () => void
}
type StylusStylePreprocessorInternalOptions = StylePreprocessorInternalOptions &
StylusPreprocessorOptions

type SassStylePreprocessor = {
type StylePreprocessor<Options extends StylePreprocessorInternalOptions> = {
process: (
environment: PartialEnvironment,
source: string,
root: string,
options: SassStylePreprocessorOptions,
resolvers: CSSAtImportResolvers,
) => StylePreprocessorResults | Promise<StylePreprocessorResults>
close: () => void
}

type StylusStylePreprocessor = {
process: (
environment: PartialEnvironment,
source: string,
root: string,
options: StylusStylePreprocessorOptions,
options: Options,
resolvers: CSSAtImportResolvers,
) => StylePreprocessorResults | Promise<StylePreprocessorResults>
close: () => void
Expand Down Expand Up @@ -2176,7 +2163,10 @@ const makeScssWorker = (
sassPath: string,
data: string,
// additionalData can a function that is not cloneable but it won't be used
options: SassStylePreprocessorOptions & { additionalData: undefined },
options: SassStylePreprocessorInternalOptions & {
api: 'legacy'
additionalData: undefined
},
) => {
// eslint-disable-next-line no-restricted-globals -- this function runs inside a cjs worker
const sass: typeof Sass = require(sassPath)
Expand Down Expand Up @@ -2204,6 +2194,8 @@ const makeScssWorker = (
}

const finalOptions: Sass.LegacyOptions<'async'> = {
// support @import from node dependencies by default
includePaths: ['node_modules'],
...options,
data,
file: options.filename,
Expand Down Expand Up @@ -2287,7 +2279,10 @@ const makeModernScssWorker = (
sassPath: string,
data: string,
// additionalData can a function that is not cloneable but it won't be used
options: SassStylePreprocessorOptions & { additionalData: undefined },
options: SassStylePreprocessorInternalOptions & {
api: 'modern'
additionalData: undefined
},
) => {
// eslint-disable-next-line no-restricted-globals -- this function runs inside a cjs worker
const sass: typeof Sass = require(sassPath)
Expand Down Expand Up @@ -2453,8 +2448,15 @@ type ScssWorkerResult = {

const scssProcessor = (
maxWorkers: number | undefined,
): SassStylePreprocessor => {
const workerMap = new Map<unknown, ReturnType<typeof makeScssWorker>>()
): StylePreprocessor<SassStylePreprocessorInternalOptions> => {
const workerMap = new Map<
unknown,
ReturnType<
| typeof makeScssWorker
| typeof makeModernScssWorker
| typeof makeModernCompilerScssWorker
>
>()

return {
close() {
Expand Down Expand Up @@ -2511,6 +2513,7 @@ const scssProcessor = (
const result = await worker.run(
sassPackage.path,
data,
// @ts-expect-error the correct worker is selected for `options.type`
optionsWithoutAdditionalData,
)
const deps = result.stats.includedFiles.map((f) => cleanScssBugUrl(f))
Expand Down Expand Up @@ -2706,7 +2709,9 @@ const makeLessWorker = (
lessPath: string,
content: string,
// additionalData can a function that is not cloneable but it won't be used
options: StylePreprocessorOptions & { additionalData: undefined },
options: LessStylePreprocessorInternalOptions & {
additionalData: undefined
},
) => {
// eslint-disable-next-line no-restricted-globals -- this function runs inside a cjs worker
const nodeLess: typeof Less = require(lessPath)
Expand All @@ -2715,6 +2720,8 @@ const makeLessWorker = (
options.filename,
)
const result = await nodeLess.render(content, {
// support @import from node dependencies by default
paths: ['node_modules'],
...options,
plugins: [viteResolverPlugin, ...(options.plugins || [])],
...(options.enableSourcemap
Expand All @@ -2734,15 +2741,17 @@ const makeLessWorker = (
shouldUseFake(_lessPath, _content, options) {
// plugins are a function and is not serializable
// in that case, fallback to running in main thread
return options.plugins?.length > 0
return !!options.plugins && options.plugins.length > 0
},
max: maxWorkers,
},
)
return worker
}

const lessProcessor = (maxWorkers: number | undefined): StylePreprocessor => {
const lessProcessor = (
maxWorkers: number | undefined,
): StylePreprocessor<LessStylePreprocessorInternalOptions> => {
const workerMap = new Map<unknown, ReturnType<typeof makeLessWorker>>()

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

const ref = nodeStylus(content, options)
const ref = nodeStylus(content, {
// support @import from node dependencies by default
paths: ['node_modules'],
...options,
})
if (options.define) {
for (const key in options.define) {
ref.define(key, options.define[key])
Expand Down Expand Up @@ -2864,7 +2879,7 @@ const makeStylWorker = (maxWorkers: number | undefined) => {

const stylProcessor = (
maxWorkers: number | undefined,
): StylusStylePreprocessor => {
): StylePreprocessor<StylusStylePreprocessorInternalOptions> => {
const workerMap = new Map<unknown, ReturnType<typeof makeStylWorker>>()

return {
Expand Down Expand Up @@ -2981,21 +2996,23 @@ const createPreprocessorWorkerController = (maxWorkers: number | undefined) => {
const less = lessProcessor(maxWorkers)
const styl = stylProcessor(maxWorkers)

const sassProcess: StylePreprocessor['process'] = (
environment,
source,
root,
options,
resolvers,
) => {
return scss.process(
environment,
source,
root,
{ ...options, indentedSyntax: true, syntax: 'indented' },
resolvers,
)
}
const sassProcess: StylePreprocessor<SassStylePreprocessorInternalOptions>['process'] =
(environment, source, root, options, resolvers) => {
let opts: SassStylePreprocessorInternalOptions
if (options.api === 'modern' || options.api === 'modern-compiler') {
opts = { ...options, syntax: 'indented' as const }
} else {
const narrowedOptions =
options as SassStylePreprocessorInternalOptions & {
api?: 'legacy'
}
opts = {
...narrowedOptions,
indentedSyntax: true,
}
}
return scss.process(environment, source, root, opts, resolvers)
}

const close = () => {
less.close()
Expand Down
5 changes: 0 additions & 5 deletions packages/vite/src/types/shims.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,6 @@ declare module 'postcss-import' {
export = plugin
}

// LESS' types somewhat references this which doesn't make sense in Node,
// so we have to shim it
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
declare interface HTMLLinkElement {}

// eslint-disable-next-line no-var
declare var __vite_profile_session: import('node:inspector').Session | undefined
// eslint-disable-next-line no-var
Expand Down
43 changes: 43 additions & 0 deletions packages/vite/types/cssPreprocessorOptions.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */

// @ts-ignore `sass` may not be installed
import type Sass from 'sass'
// @ts-ignore `less` may not be installed
import type Less from 'less'
// @ts-ignore `less` may not be installed
import type Stylus from 'stylus'

/* eslint-enable @typescript-eslint/ban-ts-comment */

export type SassLegacyPreprocessBaseOptions = Omit<
Sass.LegacyStringOptions<'async'>,
| 'data'
| 'file'
| 'outFile'
| 'sourceMap'
| 'omitSourceMapUrl'
| 'sourceMapEmbed'
| 'sourceMapRoot'
>

export type SassModernPreprocessBaseOptions = Omit<
Sass.StringOptions<'async'>,
'url' | 'sourceMap'
>

export type LessPreprocessorBaseOptions = Omit<
Less.Options,
'sourceMap' | 'filename'
>

export type StylusPreprocessorBaseOptions = Omit<
Stylus.RenderOptions,
'filename'
> & { define?: Record<string, any> }

declare global {
// LESS' types somewhat references this which doesn't make sense in Node,
// so we have to shim it
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface HTMLLinkElement {}
}

0 comments on commit 7eeb6f2

Please sign in to comment.