Skip to content

Commit f5bbfd6

Browse files
authored
feat(css): Enable individual exports (#2119)
2 parents fbbf106 + a8e8052 commit f5bbfd6

File tree

10 files changed

+560
-400
lines changed

10 files changed

+560
-400
lines changed

packages/css/src/bundle.ts

Lines changed: 57 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import browserslist from 'browserslist'
22
import { bundle as __bundle, browserslistToTargets } from 'lightningcss'
33
import { outputFileSync } from 'fs-extra'
4-
import { tokens, type TokensArgs } from './tokens'
54

65
const layerStatement = '@layer sl-reset, sl-base, sl-tokens, sl-components;'
76

@@ -12,65 +11,82 @@ export function bundle(args: BundleArgs) {
1211
const {
1312
inputFile,
1413
outdir,
15-
tokensFile,
16-
useCascadeLayers = true,
14+
outputFile,
15+
includeLayersStatement = false,
16+
layer,
1717
browserslistQuery = 'last 1 versions',
1818
} = args
1919

2020
const targets = browserslistToTargets(browserslist(browserslistQuery))
2121

22-
const { code: tokensCode } = tokens({
23-
inputFile: tokensFile,
24-
emitFile: true,
25-
outdir,
26-
useCascadeLayers,
27-
browserslistQuery,
28-
})
29-
3022
const { code: bundledCode } = __bundle({
3123
filename: inputFile,
3224
targets,
3325
minify: false,
34-
customAtRules: {
35-
theme: {
36-
prelude: '<custom-ident>',
37-
body: 'style-block',
38-
},
39-
},
40-
visitor: {
41-
Rule: {
42-
custom: {
43-
theme() {
44-
// theme is not bundled with
45-
throw new Error('Do not import tokens into your bundle')
46-
},
47-
},
48-
},
49-
},
5026
})
5127

5228
try {
53-
const outputFile = `${outdir}/styles${
54-
useCascadeLayers ? '' : '-unlayered'
29+
// Derive output filename from input file if not provided
30+
let inputBasename = outputFile
31+
? outputFile.replace(/\.css$/, '')
32+
: inputFile
33+
.split('/')
34+
.pop()
35+
?.replace(/\.css$/, '') || 'styles'
36+
37+
// Remove existing -unlayered suffix if present
38+
if (inputBasename.endsWith('-unlayered')) {
39+
inputBasename = inputBasename.replace(/-unlayered$/, '')
40+
}
41+
42+
const outputFileName = `${outdir}/${inputBasename}${
43+
layer || includeLayersStatement ? '' : '-unlayered'
5544
}.css`
5645

57-
outputFileSync(
58-
outputFile,
59-
Buffer.from(
60-
useCascadeLayers
61-
? `${layerStatement}\n\n${tokensCode.toString()}\n\n${bundledCode.toString()}`
62-
: `${tokensCode.toString()}\n\n${bundledCode.toString()}`
63-
)
64-
)
65-
console.log(`✅ Generated ${outputFile}`)
46+
let output = bundledCode.toString()
47+
48+
// Wrap in layer if layer is provided
49+
if (layer) {
50+
output = `@layer ${layer} {\n${output}\n}`
51+
}
52+
53+
if (includeLayersStatement) {
54+
output = `${layerStatement}\n\n${output}`
55+
}
56+
57+
outputFileSync(outputFileName, output)
58+
console.log(`✅ Generated ${outputFileName}`)
6659
} catch (e) {
6760
console.log('🚨 Failed to compile styles')
6861
}
6962
}
7063

71-
export interface BundleArgs extends Omit<TokensArgs, 'emitFile'> {
64+
export interface BundleArgs {
65+
/**
66+
* Input CSS file to bundle
67+
*/
68+
inputFile: string
69+
/**
70+
* Output directory
71+
*/
72+
outdir: string
73+
/**
74+
* Output filename (without extension). If not provided, derived from inputFile
75+
*/
76+
outputFile?: string
77+
/**
78+
* Whether to include the layer declaration statement
79+
* @default false
80+
*/
81+
includeLayersStatement?: boolean
82+
/**
83+
* Layer name to wrap the CSS in (e.g., 'sl-reset', 'sl-base', 'sl-components').
84+
* If provided, the stylesheet will be layered.
85+
*/
86+
layer?: string
7287
/**
73-
* file contaning the tokens
88+
* Browserslist query for CSS transformation
89+
* @default 'last 1 versions'
7490
*/
75-
tokensFile: string
91+
browserslistQuery?: string | string[]
7692
}

packages/css/src/tokens.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function tokens(args: TokensArgs) {
2727
}.css`
2828

2929
try {
30-
outputFileSync(outputFile, tokens.code)
30+
outputFileSync(outputFile, tokens.code.toString())
3131
console.log(`✅ Generated ${outputFile}`)
3232
} catch (e) {
3333
console.log('🚨 Failed to compile styles')

packages/css/src/transform-tokens.ts

Lines changed: 12 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -12,43 +12,22 @@ export function transformTokens(args: TransformTokensArgs) {
1212

1313
const result = transform({
1414
filename: 'styles.css',
15-
code: code,
15+
code: code as unknown as Uint8Array,
1616
targets,
1717
minify: false,
18-
customAtRules: {
19-
theme: {
20-
prelude: '<custom-ident>',
21-
body: 'style-block',
22-
},
23-
},
24-
visitor: {
25-
Rule: {
26-
custom: {
27-
theme(rule) {
28-
const prefixedRoot = JSON.parse(
29-
JSON.stringify(rule.body.value).replace(/--/gi, '--sl-')
30-
)
31-
32-
if (!useCascadeLayers) {
33-
return prefixedRoot
34-
}
35-
36-
return [
37-
{
38-
type: 'layer-block',
39-
value: {
40-
name: ['sl-tokens'],
41-
loc: rule.loc,
42-
rules: prefixedRoot,
43-
},
44-
},
45-
]
46-
},
47-
},
48-
},
49-
},
5018
})
5119

20+
// Wrap in layer if useCascadeLayers is true
21+
if (useCascadeLayers) {
22+
const wrappedCode = Buffer.from(
23+
`@layer sl-tokens {\n${result.code.toString()}\n}`
24+
)
25+
return {
26+
...result,
27+
code: wrappedCode,
28+
}
29+
}
30+
5231
return result
5332
}
5433

packages/docs/pages/guides/styling/applying.mdx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,66 @@ import { Tabs } from 'nextra/components'
55

66
You can refer to the [Foundations section](../../foundations) to understand how is the tokens definition. You can use them in your app as [CSS Variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) which are defined in the theme, as mentioned in the Theme guide.
77

8+
## Importing CSS
9+
10+
Shoreline provides several CSS exports that you can import into your application. The simplest way is to import the complete stylesheet:
11+
12+
```jsx
13+
import '@vtex/shoreline/css'
14+
```
15+
16+
This imports all styles including tokens, reset, base, and components styles with [cascade layers](./cascade-layers).
17+
18+
### Individual Stylesheets
19+
20+
You can also import individual stylesheets for more granular control:
21+
22+
<Tabs items={['Complete', 'Individual']}>
23+
24+
<Tabs.Tab>
25+
26+
```jsx
27+
// Import complete stylesheet (layered)
28+
import '@vtex/shoreline/css'
29+
30+
// Or unlayered version
31+
import '@vtex/shoreline/css/unlayered'
32+
```
33+
34+
</Tabs.Tab>
35+
36+
<Tabs.Tab>
37+
38+
```jsx
39+
// Import individual stylesheets (layered)
40+
import '@vtex/shoreline/css/tokens'
41+
import '@vtex/shoreline/css/reset'
42+
import '@vtex/shoreline/css/base'
43+
import '@vtex/shoreline/css/components'
44+
45+
// Or unlayered versions
46+
import '@vtex/shoreline/css/tokens/unlayered'
47+
import '@vtex/shoreline/css/reset/unlayered'
48+
import '@vtex/shoreline/css/base/unlayered'
49+
import '@vtex/shoreline/css/components/unlayered'
50+
```
51+
52+
</Tabs.Tab>
53+
54+
</Tabs>
55+
56+
### Available CSS Exports
57+
58+
| Export Path | Description | Layered | Unlayered |
59+
|------------|------------|---------|-----------|
60+
| `@vtex/shoreline/css` | Complete stylesheet (tokens + reset + base + components) || `@vtex/shoreline/css/unlayered` |
61+
| `@vtex/shoreline/css/tokens` | Design tokens (CSS variables) || `@vtex/shoreline/css/tokens/unlayered` |
62+
| `@vtex/shoreline/css/reset` | CSS reset styles || `@vtex/shoreline/css/reset/unlayered` |
63+
| `@vtex/shoreline/css/base` | Base global styles || `@vtex/shoreline/css/base/unlayered` |
64+
| `@vtex/shoreline/css/components` | Component styles || `@vtex/shoreline/css/components/unlayered` |
65+
66+
> **Note:** Layered stylesheets use [CSS cascade layers](./cascade-layers) for better style organization and control. Unlayered versions are available for environments that don't support cascade layers.
67+
868
The way you will apply the styles in your application depends on the tooling you are using. In the following example we will show how to apply styles using `CSS` and html `style` property.
969

1070

packages/docs/pages/guides/styling/cascade-layers.mdx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,15 @@ The CSS style is:
8484
```css
8585
@layer sl-reset, sl-base, sl-tokens, sl-components;
8686
```
87+
88+
## Importing Individual Layers
89+
90+
You can import individual layers for more granular control. See the [Applying Styles](./applying) guide for more information on importing CSS.
91+
92+
```jsx
93+
// Import individual layers
94+
import '@vtex/shoreline/css/tokens' // sl-tokens layer
95+
import '@vtex/shoreline/css/reset' // sl-reset layer
96+
import '@vtex/shoreline/css/base' // sl-base layer
97+
import '@vtex/shoreline/css/components' // sl-components layer
98+
```

packages/shoreline/package.json

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,24 @@
1919
},
2020
"./css": "./dist/themes/sunrise/styles.css",
2121
"./css/unlayered": "./dist/themes/sunrise/styles-unlayered.css",
22+
"./css/tokens": "./dist/themes/sunrise/tokens.css",
23+
"./css/tokens/unlayered": "./dist/themes/sunrise/tokens-unlayered.css",
24+
"./css/reset": "./dist/themes/sunrise/reset.css",
25+
"./css/reset/unlayered": "./dist/themes/sunrise/reset-unlayered.css",
26+
"./css/base": "./dist/themes/sunrise/base.css",
27+
"./css/base/unlayered": "./dist/themes/sunrise/base-unlayered.css",
28+
"./css/components": "./dist/themes/sunrise/components.css",
29+
"./css/components/unlayered": "./dist/themes/sunrise/components-unlayered.css",
2230
"./themes/sunrise": "./dist/themes/sunrise/styles.css",
23-
"./themes/sunrise/unlayered": "./dist/themes/sunrise/styles-unlayered.css"
31+
"./themes/sunrise/unlayered": "./dist/themes/sunrise/styles-unlayered.css",
32+
"./themes/sunrise/tokens": "./dist/themes/sunrise/tokens.css",
33+
"./themes/sunrise/tokens/unlayered": "./dist/themes/sunrise/tokens-unlayered.css",
34+
"./themes/sunrise/reset": "./dist/themes/sunrise/reset.css",
35+
"./themes/sunrise/reset/unlayered": "./dist/themes/sunrise/reset-unlayered.css",
36+
"./themes/sunrise/base": "./dist/themes/sunrise/base.css",
37+
"./themes/sunrise/base/unlayered": "./dist/themes/sunrise/base-unlayered.css",
38+
"./themes/sunrise/components": "./dist/themes/sunrise/components.css",
39+
"./themes/sunrise/components/unlayered": "./dist/themes/sunrise/components-unlayered.css"
2440
},
2541
"engines": {
2642
"node": ">=20"

packages/shoreline/src/scripts/build-css.ts

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,75 @@
11
import { bundle } from '@vtex/shoreline-css'
22

33
export function build() {
4+
const outdir = 'dist/themes/sunrise'
5+
const themeDir = 'src/themes/sunrise'
6+
7+
// Bundle tokens (layered and unlayered)
8+
bundle({
9+
inputFile: `${themeDir}/tokens.css`,
10+
outdir,
11+
outputFile: 'tokens',
12+
layer: 'sl-tokens',
13+
})
14+
15+
bundle({
16+
inputFile: `${themeDir}/tokens.css`,
17+
outdir,
18+
outputFile: 'tokens',
19+
})
20+
21+
// Bundle reset (layered and unlayered)
22+
bundle({
23+
inputFile: `${themeDir}/reset.css`,
24+
outdir,
25+
outputFile: 'reset',
26+
layer: 'sl-reset',
27+
})
28+
29+
bundle({
30+
inputFile: `${themeDir}/reset.css`,
31+
outdir,
32+
outputFile: 'reset',
33+
})
34+
35+
// Bundle base (layered and unlayered)
36+
bundle({
37+
inputFile: `${themeDir}/base.css`,
38+
outdir,
39+
outputFile: 'base',
40+
layer: 'sl-base',
41+
})
42+
43+
bundle({
44+
inputFile: `${themeDir}/base.css`,
45+
outdir,
46+
outputFile: 'base',
47+
})
48+
49+
// Bundle components (layered and unlayered)
50+
bundle({
51+
inputFile: `${themeDir}/components/index.css`,
52+
outdir,
53+
outputFile: 'components',
54+
layer: 'sl-components',
55+
})
56+
57+
bundle({
58+
inputFile: `${themeDir}/components/index.css`,
59+
outdir,
60+
outputFile: 'components',
61+
})
62+
63+
// Bundle styles.css for backward compatibility (layered and unlayered)
464
bundle({
5-
inputFile: 'src/themes/sunrise/styles.css',
6-
tokensFile: 'src/themes/sunrise/tokens.css',
7-
outdir: 'dist/themes/sunrise',
8-
useCascadeLayers: true,
65+
inputFile: `${themeDir}/styles.css`,
66+
outdir,
67+
includeLayersStatement: true,
968
})
1069

1170
bundle({
12-
inputFile: 'src/themes/sunrise/styles-unlayered.css',
13-
tokensFile: 'src/themes/sunrise/tokens.css',
14-
outdir: 'dist/themes/sunrise',
15-
useCascadeLayers: false,
71+
inputFile: `${themeDir}/styles-unlayered.css`,
72+
outdir,
1673
})
1774
}
1875

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
@import "tokens.css";
12
@import "reset.css";
23
@import "base.css";
34
@import "components/index.css";
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
@import "tokens.css" layer(sl-tokens);
12
@import "reset.css" layer(sl-reset);
23
@import "base.css" layer(sl-base);
34
@import "components/index.css" layer(sl-components);

0 commit comments

Comments
 (0)