Skip to content

Commit f5e97ce

Browse files
nicksrandallsxzz
andauthored
feat: implement CSS Modules (#28)
Co-authored-by: 三咲智子 Kevin Deng <[email protected]>
1 parent f582721 commit f5e97ce

File tree

6 files changed

+105
-2
lines changed

6 files changed

+105
-2
lines changed

src/core/transform.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Buffer } from 'node:buffer'
2+
import { readFile } from 'node:fs/promises'
23
import { transform } from 'lightningcss'
34
import type { Options } from './options'
45

@@ -23,3 +24,34 @@ export function transformCss(
2324
map: 'map' in res ? res.map?.toString() : undefined,
2425
}
2526
}
27+
28+
export async function transformCssModule(
29+
id: string,
30+
options: Options['options'],
31+
): Promise<{ code: string; map?: string; exports: string; id: string }> {
32+
const actualId = id.replace(/\?css_module$/, '')
33+
const code = await readFile(actualId, 'utf-8')
34+
const filename = cleanUrl(actualId)
35+
const res = transform({
36+
cssModules: true,
37+
...options,
38+
filename,
39+
code: Buffer.from(code),
40+
})
41+
const compiledId = actualId
42+
.replaceAll('\\', '/')
43+
.replace(/\.module\.css$/, '.module_built.css')
44+
return {
45+
code: res.code.toString(),
46+
map: 'map' in res ? res.map?.toString() : undefined,
47+
id: compiledId,
48+
exports: res.exports
49+
? Object.entries(res.exports)
50+
.map(
51+
([name, { name: className }]) =>
52+
`export const ${name} = "${className}";`,
53+
)
54+
.join('\n')
55+
: '',
56+
}
57+
}

src/index.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1+
import path from 'node:path'
12
import { createFilter } from '@rollup/pluginutils'
23
import { createUnplugin, type UnpluginInstance } from 'unplugin'
34
import { resolveOption, type Options } from './core/options'
4-
import { transformCss } from './core/transform'
5+
import { transformCss, transformCssModule } from './core/transform'
56

67
const plugin: UnpluginInstance<Options | undefined, false> = createUnplugin(
78
(rawOptions = {}) => {
89
const options = resolveOption(rawOptions)
910
const filter = createFilter(options.include, options.exclude)
1011

12+
const transformedFiles = new Map<string, string>()
13+
1114
const name = 'unplugin-lightningcss'
1215
return {
1316
name,
@@ -17,9 +20,36 @@ const plugin: UnpluginInstance<Options | undefined, false> = createUnplugin(
1720
return filter(id)
1821
},
1922

23+
resolveId(id, importer) {
24+
if (id.endsWith('.module_built.css')) return id
25+
if (id.endsWith('.module.css')) {
26+
return `${path.resolve(path.dirname(importer || ''), id)}?css_module`
27+
}
28+
},
29+
2030
transform(code, id) {
2131
return transformCss(id, code, options.options)
2232
},
33+
34+
async load(id) {
35+
if (id.endsWith('.module_built.css')) {
36+
const code = transformedFiles.get(id)!
37+
return { id, code }
38+
}
39+
if (id.endsWith('?css_module')) {
40+
const {
41+
code,
42+
map,
43+
exports,
44+
id: compiledId,
45+
} = await transformCssModule(id, options.options)
46+
transformedFiles.set(compiledId, code)
47+
return {
48+
code: `import "${compiledId}";\n${exports}`,
49+
map,
50+
}
51+
}
52+
},
2353
}
2454
},
2555
)

tests/__snapshots__/transform.test.ts.snap

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3+
exports[`CSS Modules > should transform CSS Modules 1`] = `
4+
"// assets/asset-B_IWpIjy
5+
.dummy_dummy_button{color:red;bakcground-color:blue}
6+
7+
// index.js
8+
const button = "dummy_button";
9+
10+
const btn = document.createElement('div').classList.add(button);
11+
document.body.append(btn);
12+
"
13+
`;
14+
315
exports[`transform > tests/fixtures/basic.css 1`] = `
416
"// assets/asset-CuZ0KhGL
517
.red{color:red}

tests/css-module-fixture/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import * as styles from './styles.module.css'
2+
3+
const btn = document.createElement('div').classList.add(styles.button)
4+
document.body.append(btn)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.button {
2+
color: red;
3+
bakcground-color: blue;
4+
}

tests/transform.test.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { resolve } from 'node:path'
22
import { rollupBuild, testFixtures } from '@sxzz/test-utils'
33
import css from 'rollup-plugin-css-only'
4-
import { describe } from 'vitest'
4+
import { describe, expect, it } from 'vitest'
55
import LightningCSS from '../src/rollup'
66

77
describe('transform', async () => {
@@ -24,3 +24,24 @@ describe('transform', async () => {
2424
{ cwd: resolve(__dirname, '..'), promise: true },
2525
)
2626
})
27+
28+
describe('CSS Modules', () => {
29+
it('should transform CSS Modules', async () => {
30+
const entry = resolve(__dirname, './css-module-fixture/index.js')
31+
const { snapshot } = await rollupBuild(entry, [
32+
LightningCSS({
33+
options: {
34+
minify: true,
35+
targets: {
36+
ie: 11,
37+
},
38+
cssModules: {
39+
pattern: 'dummy_[local]',
40+
},
41+
},
42+
}),
43+
css(),
44+
])
45+
expect(snapshot).toMatchSnapshot()
46+
})
47+
})

0 commit comments

Comments
 (0)