Skip to content

Commit e4cbeca

Browse files
authored
feat(css): extract CSS pipeline into @tsdown/css package (#790)
1 parent eb2ece0 commit e4cbeca

40 files changed

+1354
-323
lines changed

dts.snapshot.json

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"config-!~{00b}~.d.mts": {
2+
"config-!~{00e}~.d.mts": {
33
"defineConfig": "declare function defineConfig(_: UserConfigExport): UserConfigExport",
44
"mergeConfig": "declare function mergeConfig(_: InlineConfig, _: InlineConfig): InlineConfig",
55
"resolveUserConfig": "declare function resolveUserConfig(_: UserConfig, _: InlineConfig): Promise<ResolvedConfig[]>"
@@ -13,6 +13,32 @@
1313
"mergeConfig"
1414
]
1515
},
16+
"css.d.mts": {
17+
"createCssPostHooks": "declare function createCssPostHooks(_: { css: { splitting: boolean; fileName: string } }, _: CssStyles): Pick<Required<Plugin>, 'renderChunk' | 'generateBundle'>",
18+
"CssStyles": "type CssStyles = Map<string, string>",
19+
"getEmptyChunkReplacer": "declare function getEmptyChunkReplacer(_: string[]): (_: string) => string",
20+
"RE_CSS": "RegExp",
21+
"removePureCssChunks": "declare function removePureCssChunks(_: Record<string, OutputChunk | OutputAsset>, _: CssStyles): void",
22+
"#exports": [
23+
"CssOptions",
24+
"CssStyles",
25+
"LessPreprocessorOptions",
26+
"LightningCSSOptions",
27+
"PreprocessorAdditionalData",
28+
"PreprocessorAdditionalDataResult",
29+
"PreprocessorOptions",
30+
"RE_CSS",
31+
"ResolvedCssOptions",
32+
"SassPreprocessorOptions",
33+
"Sourcemap",
34+
"StylusPreprocessorOptions",
35+
"createCssPostHooks",
36+
"defaultCssBundleName",
37+
"getEmptyChunkReplacer",
38+
"removePureCssChunks",
39+
"resolveCssOptions"
40+
]
41+
},
1642
"index.d.mts": {
1743
"build": "declare function build(_: InlineConfig): Promise<TsdownBundle[]>",
1844
"buildWithConfigs": "declare function buildWithConfigs(_: ResolvedConfig[], _: string[], _: () => void): Promise<TsdownBundle[]>",
@@ -92,7 +118,7 @@
92118
"run.d.mts": {
93119
"#exports": []
94120
},
95-
"types-!~{00a}~.d.mts": {
121+
"types-!~{00d}~.d.mts": {
96122
"Arrayable": "type Arrayable<T> = T | T[]",
97123
"AttwOptions": "interface AttwOptions extends CheckPackageOptions {\n profile?: 'strict' | 'node16' | 'esm-only'\n level?: 'error' | 'warn'\n ignoreRules?: string[]\n}",
98124
"Awaitable": "type Awaitable<T> = T | Promise<T>",
@@ -105,7 +131,8 @@
105131
"CopyEntry": "interface CopyEntry {\n from: string | string[]\n to?: string\n flatten?: boolean\n verbose?: boolean\n rename?: string | ((_: string, _: string, _: string) => string)\n}",
106132
"CopyOptions": "type CopyOptions = Arrayable<string | CopyEntry>",
107133
"CopyOptionsFn": "type CopyOptionsFn = (_: ResolvedConfig) => Awaitable<CopyOptions>",
108-
"CssOptions": "interface CssOptions {\n splitting?: boolean\n fileName?: string\n preprocessorOptions?: PreprocessorOptions\n lightningcss?: LightningCSSOptions\n}",
134+
"CssOptions": "interface CssOptions {\n splitting?: boolean\n fileName?: string\n target?: string | string[] | false\n preprocessorOptions?: PreprocessorOptions\n minify?: boolean\n lightningcss?: LightningCSSOptions\n}",
135+
"defaultCssBundleName": "'style.css'",
109136
"DepPlugin": "declare function DepPlugin(_: ResolvedConfig, _: TsdownBundle): Plugin",
110137
"DepsConfig": "interface DepsConfig {\n neverBundle?: ExternalOption\n alwaysBundle?: Arrayable<string | RegExp> | NoExternalFn\n onlyAllowBundle?: Arrayable<string | RegExp> | false\n skipNodeModulesBundle?: boolean\n}",
111138
"DevtoolsOptions": "interface DevtoolsOptions extends NonNullable<InputOptions['devtools']> {\n ui?: boolean | Partial<StartOptions>\n clean?: boolean\n}",
@@ -115,7 +142,7 @@
115142
"globalLogger": "Logger",
116143
"InlineConfig": "interface InlineConfig extends UserConfig {\n config?: boolean | string\n configLoader?: 'auto' | 'native' | 'unrun'\n filter?: RegExp | Arrayable<string>\n}",
117144
"LessPreprocessorOptions": "interface LessPreprocessorOptions {\n additionalData?: PreprocessorAdditionalData\n math?: any\n paths?: string[]\n plugins?: any[]\n [key: string]: any\n}",
118-
"LightningCSSOptions": "type LightningCSSOptions = Omit<TransformOptions<any>, 'filename' | 'code' | 'minify' | 'sourceMap' | 'inputSourceMap'>",
145+
"LightningCSSOptions": "type LightningCSSOptions = Record<string, any>",
119146
"Logger": "interface Logger {\n level: LogLevel\n options?: LoggerOptions\n info: (...args: any[]) => void\n warn: (...args: any[]) => void\n warnOnce: (...args: any[]) => void\n error: (...args: any[]) => void\n success: (...args: any[]) => void\n clearScreen: (_: LogType) => void\n}",
120147
"LoggerOptions": "interface LoggerOptions {\n allowClearScreen?: boolean\n customLogger?: Logger\n console?: Console\n failOnWarn?: boolean\n}",
121148
"LogLevel": "type LogLevel = LogType | 'silent'",
@@ -147,8 +174,9 @@
147174
"PublintOptions": "interface PublintOptions extends Omit<Options, 'pack' | 'pkgDir'> {}",
148175
"ReportOptions": "interface ReportOptions {\n gzip?: boolean\n brotli?: boolean\n maxCompressSize?: number\n}",
149176
"ReportPlugin": "declare function ReportPlugin(_: ResolvedConfig, _: boolean, _: boolean): Plugin",
177+
"resolveCssOptions": "declare function resolveCssOptions(_: CssOptions, _: string[]): ResolvedCssOptions",
150178
"ResolvedConfig": "type ResolvedConfig = Overwrite<MarkPartial<Omit<UserConfig, 'workspace' | 'fromVite' | 'publicDir' | 'bundle' | 'removeNodeProtocol' | 'external' | 'noExternal' | 'inlineOnly' | 'skipNodeModulesBundle' | 'logLevel' | 'failOnWarn' | 'customLogger' | 'envFile' | 'envPrefix'>, 'globalName' | 'inputOptions' | 'outputOptions' | 'minify' | 'define' | 'alias' | 'onSuccess' | 'outExtensions' | 'hooks' | 'copy' | 'loader' | 'name' | 'banner' | 'footer' | 'checks'>, { entry: Record<string, string>; rawEntry?: TsdownInputOption; nameLabel: string | undefined; format: NormalizedFormat; target?: string[]; clean: string[]; pkg?: PackageJsonWithPath; nodeProtocol: 'strip' | boolean; logger: Logger; ignoreWatch: Array<string | RegExp>; deps: ResolvedDepsConfig; css: ResolvedCssOptions; dts: false | DtsOptions; report: false | ReportOptions; tsconfig: false | string; exports: false | ExportsOptions; devtools: false | DevtoolsOptions; publint: false | PublintOptions; attw: false | AttwOptions; unused: false | UnusedOptions; exe: false | ExeOptions }>",
151-
"ResolvedCssOptions": "interface ResolvedCssOptions {\n splitting: boolean\n fileName: string\n preprocessorOptions?: PreprocessorOptions\n lightningcss?: LightningCSSOptions\n}",
179+
"ResolvedCssOptions": "interface ResolvedCssOptions {\n splitting: boolean\n fileName: string\n minify: boolean\n target?: string[]\n preprocessorOptions?: PreprocessorOptions\n lightningcss?: LightningCSSOptions\n}",
152180
"ResolvedDepsConfig": "interface ResolvedDepsConfig {\n neverBundle?: ExternalOption\n alwaysBundle?: NoExternalFn\n onlyAllowBundle?: Array<string | RegExp> | false\n skipNodeModulesBundle: boolean\n}",
153181
"RolldownChunk": "type RolldownChunk = (OutputChunk | OutputAsset) & { outDir: string }",
154182
"RolldownContext": "interface RolldownContext {\n buildOptions: BuildOptions\n}",

package.json

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,26 @@
1616
"url": "https://github.com/rolldown/tsdown/issues"
1717
},
1818
"exports": {
19-
".": "./dist/index.mjs",
20-
"./config": "./dist/config.mjs",
21-
"./plugins": "./dist/plugins.mjs",
22-
"./run": "./dist/run.mjs",
19+
".": {
20+
"dev": "./src/index.ts",
21+
"default": "./dist/index.mjs"
22+
},
23+
"./config": {
24+
"dev": "./src/config.ts",
25+
"default": "./dist/config.mjs"
26+
},
27+
"./css": {
28+
"dev": "./src/css.ts",
29+
"default": "./dist/css.mjs"
30+
},
31+
"./plugins": {
32+
"dev": "./src/plugins.ts",
33+
"default": "./dist/plugins.mjs"
34+
},
35+
"./run": {
36+
"dev": "./src/run.ts",
37+
"default": "./dist/run.mjs"
38+
},
2339
"./package.json": "./package.json",
2440
"./client": "./client.d.ts"
2541
},
@@ -41,7 +57,16 @@
4157
"esm-shims.js"
4258
],
4359
"publishConfig": {
44-
"access": "public"
60+
"access": "public",
61+
"exports": {
62+
".": "./dist/index.mjs",
63+
"./config": "./dist/config.mjs",
64+
"./css": "./dist/css.mjs",
65+
"./plugins": "./dist/plugins.mjs",
66+
"./run": "./dist/run.mjs",
67+
"./package.json": "./package.json",
68+
"./client": "./client.d.ts"
69+
}
4570
},
4671
"engines": {
4772
"node": ">=20.19.0"
@@ -62,9 +87,9 @@
6287
},
6388
"peerDependencies": {
6489
"@arethetypeswrong/core": "^0.18.1",
90+
"@tsdown/css": "workspace:*",
6591
"@tsdown/exe": "workspace:*",
6692
"@vitejs/devtools": "*",
67-
"lightningcss": "^1.29.0",
6893
"publint": "^0.3.0",
6994
"typescript": "^5.0.0",
7095
"unplugin-unused": "^0.5.0"
@@ -73,13 +98,13 @@
7398
"@arethetypeswrong/core": {
7499
"optional": true
75100
},
76-
"@tsdown/exe": {
101+
"@tsdown/css": {
77102
"optional": true
78103
},
79-
"@vitejs/devtools": {
104+
"@tsdown/exe": {
80105
"optional": true
81106
},
82-
"lightningcss": {
107+
"@vitejs/devtools": {
83108
"optional": true
84109
},
85110
"publint": {
@@ -116,6 +141,7 @@
116141
"@sxzz/eslint-config": "catalog:dev",
117142
"@sxzz/prettier-config": "catalog:dev",
118143
"@sxzz/test-utils": "catalog:dev",
144+
"@tsdown/css": "workspace:*",
119145
"@tsdown/exe": "workspace:*",
120146
"@types/node": "catalog:dev",
121147
"@types/picomatch": "catalog:dev",
@@ -130,7 +156,6 @@
130156
"dedent": "catalog:dev",
131157
"eslint": "catalog:dev",
132158
"is-in-ci": "catalog:prod",
133-
"lightningcss": "catalog:dev",
134159
"memfs": "catalog:dev",
135160
"package-manager-detector": "catalog:prod",
136161
"pkg-types": "catalog:dev",

packages/css/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# @tsdown/css
2+
3+
[![npm version][npmx-version-src]][npmx-href]
4+
[![npm downloads][npmx-downloads-src]][npmx-href]
5+
6+
Advanced CSS pipeline for [tsdown](https://tsdown.dev), powered by [Lightning CSS](https://lightningcss.dev/).
7+
8+
## Features
9+
10+
- CSS `@import` inlining via Lightning CSS `bundleAsync`
11+
- CSS syntax lowering and autoprefixing
12+
- CSS minification
13+
- Source map support
14+
- Preprocessor support (Sass, Less, Stylus)
15+
16+
## Documentation
17+
18+
See the [CSS documentation](https://tsdown.dev/options/css) for usage and configuration details.
19+
20+
## License
21+
22+
[MIT](../../LICENSE)
23+
24+
<!-- Badges -->
25+
26+
[npmx-version-src]: https://npmx.dev/api/registry/badge/version/@tsdown/css
27+
[npmx-downloads-src]: https://npmx.dev/api/registry/badge/downloads-month/@tsdown/css
28+
[npmx-href]: https://npmx.dev/@tsdown/css

packages/css/package.json

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"name": "@tsdown/css",
3+
"type": "module",
4+
"version": "0.21.0-beta.3",
5+
"description": "Advanced CSS pipeline for tsdown powered by Lightning CSS",
6+
"author": "Kevin Deng <sxzz@sxzz.moe>",
7+
"license": "MIT",
8+
"funding": "https://github.com/sponsors/sxzz",
9+
"homepage": "http://tsdown.dev/",
10+
"repository": {
11+
"type": "git",
12+
"url": "git+https://github.com/rolldown/tsdown.git",
13+
"directory": "packages/css"
14+
},
15+
"bugs": {
16+
"url": "https://github.com/rolldown/tsdown/issues"
17+
},
18+
"exports": {
19+
".": {
20+
"dev": "./src/index.ts",
21+
"default": "./dist/index.mjs"
22+
},
23+
"./package.json": "./package.json"
24+
},
25+
"types": "./dist/index.d.mts",
26+
"typesVersions": {
27+
"*": {
28+
"*": [
29+
"./dist/*.d.mts",
30+
"./*"
31+
]
32+
}
33+
},
34+
"files": [
35+
"dist"
36+
],
37+
"publishConfig": {
38+
"access": "public",
39+
"exports": {
40+
".": "./dist/index.mjs",
41+
"./package.json": "./package.json"
42+
}
43+
},
44+
"engines": {
45+
"node": ">=20.19.0"
46+
},
47+
"scripts": {
48+
"build": "unrun ../../src/run.ts -c ../../tsdown.config.ts -F ."
49+
},
50+
"peerDependencies": {
51+
"tsdown": "workspace:*"
52+
},
53+
"dependencies": {
54+
"lightningcss": "catalog:prod"
55+
}
56+
}

packages/css/src/index.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { readFile } from 'node:fs/promises'
2+
import { createCssPostHooks, RE_CSS, type CssStyles } from 'tsdown/css'
3+
import {
4+
bundleWithLightningCSS,
5+
transformWithLightningCSS,
6+
} from './lightningcss.ts'
7+
import {
8+
compilePreprocessor,
9+
getPreprocessorLang,
10+
isCssOrPreprocessor,
11+
} from './preprocessors.ts'
12+
import type { ResolvedConfig, Rolldown } from 'tsdown'
13+
14+
export function CssPlugin(config: ResolvedConfig): Rolldown.Plugin {
15+
const styles: CssStyles = new Map()
16+
const postHooks = createCssPostHooks(config, styles)
17+
const shouldMinify = config.css.minify
18+
const cssTarget = config.css.target
19+
20+
return {
21+
name: '@tsdown/css',
22+
23+
buildStart() {
24+
styles.clear()
25+
},
26+
27+
async load(id) {
28+
if (!isCssOrPreprocessor(id)) return
29+
30+
let code: string
31+
const deps: string[] = []
32+
33+
const lang = getPreprocessorLang(id)
34+
if (lang) {
35+
const rawCode = await readFile(id, 'utf8')
36+
const preResult = await compilePreprocessor(
37+
lang,
38+
rawCode,
39+
id,
40+
config.css.preprocessorOptions,
41+
)
42+
code = preResult.code
43+
deps.push(...preResult.deps)
44+
45+
code = await transformWithLightningCSS(code, id, {
46+
target: cssTarget,
47+
lightningcss: config.css.lightningcss,
48+
minify: shouldMinify,
49+
})
50+
} else if (RE_CSS.test(id)) {
51+
const bundleResult = await bundleWithLightningCSS(id, {
52+
target: cssTarget,
53+
lightningcss: config.css.lightningcss,
54+
minify: shouldMinify,
55+
preprocessorOptions: config.css.preprocessorOptions,
56+
})
57+
code = bundleResult.code
58+
deps.push(...bundleResult.deps)
59+
} else {
60+
return
61+
}
62+
63+
for (const dep of deps) {
64+
this.addWatchFile(dep)
65+
}
66+
67+
if (code.length && !code.endsWith('\n')) {
68+
code += '\n'
69+
}
70+
71+
styles.set(id, code)
72+
return {
73+
code: '',
74+
moduleSideEffects: 'no-treeshake',
75+
moduleType: 'js',
76+
}
77+
},
78+
79+
...postHooks,
80+
}
81+
}

0 commit comments

Comments
 (0)