Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add theme ai #114

Merged
merged 3 commits into from
Dec 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .stylelintrc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"no-empty-source": null,
"number-leading-zero": "always",
"declaration-colon-newline-after": null,
"at-rule-no-unknown": null
"at-rule-no-unknown": null,
"import-notation": "string"
}
}
36 changes: 35 additions & 1 deletion packages/less-plugin-dls/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ With [less-loader](https://github.com/webpack-contrib/less-loader):
```less
@import "~less-plugin-dls/tokens/index.less";

a { color: @dls-link-font-color-normal; }
a {
color: @dls-link-font-color-normal;
}
```

### Use CLI argument
Expand All @@ -41,6 +43,34 @@ a { color: @dls-link-font-color-normal; }
lessc style.less --dls
```

## Using metadata

Variable values/metadata are provided in three formats for each theme.

| File | Description |
| -- | -- |
| `variables.js` | The raw variable values in JavaScript ESM format. Token names are transformed from `@dls-color-brand-7` to `dlsColorBrand7` as named exports. |
| `variables.json` | The raw variable values in JSON format. |
| `variables.less` | The variable values in Less format. |

```ts
// types for the JSON format:
interface Variables {
[tokenName: string]: {
value: string
type: 'color' | 'font' | 'numeric' | 'length' | 'complex'
global: boolean
}
}
```

We also included a set of metadata files for different themes and scopes. Currently the only supported theme is `ai` and supported scopes are `global` and `palette`. The dist file names are in the format of `variables.<scope>.<theme>.<format>`, where `<scope>` and `<theme>` are both optional. eg.

```sh
variables.global.less # global variables, w/o component variables
variables.palette.ai.less # global color palette variables in AI theme
```

## Custom functions

### `dls-contextual(@color, @type)`
Expand Down Expand Up @@ -138,6 +168,10 @@ The absolute length of the line-height.

## Options

### `theme: 'ai' | undefined`

The theme of the DLS. If not specified, the default theme will be used.

### `reduceCalc: boolean`

Indicates whether to reduce `calc` expression to the simplest form or not.
Expand Down
15 changes: 12 additions & 3 deletions packages/less-plugin-dls/lib/utils/evaluate.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ import { VariablesOutputVisitor } from './visitors'
const SELECTOR = 'DLS_VARS'

export async function getVariables(path) {
if (!fs.existsSync(path)) {
return []
}

const visitor = new VariablesOutputVisitor()

await less.render(fs.readFileSync(path, 'utf-8'), {
filename: path,
plugins: [
dls({
inject: false
Expand All @@ -25,15 +30,15 @@ export async function getVariables(path) {
return visitor.variables.map((v) => v.slice(1))
}

export async function getTuples(variables) {
export async function getTuples(variables, { theme }) {
const src = [
`${SELECTOR}{`,
variables.map((v) => `${v}: @${v}`).join(';'),
dedupe(variables).map((v) => `${v}: @${v}`).join(';'),
'}'
].join('')

const { css } = await less.render(src, {
plugins: [dls()]
plugins: [dls({ theme })]
})

return css
Expand All @@ -43,3 +48,7 @@ export async function getTuples(variables) {
.filter((v) => v)
.map((decl) => decl.split(/:\s*/))
}

function dedupe (arr) {
return Array.from(new Set(arr))
}
116 changes: 78 additions & 38 deletions packages/less-plugin-dls/scripts/vars.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,86 @@ import camelCase from 'lodash.camelcase'
import { parse } from 'postcss-values-parser'
import { getVariables, getTuples } from '../lib/utils/evaluate'

async function generate() {
const paletteRe = /^dls-color-(?:brand|info|success|warning|error|gray|translucent)(?:-\d+)?$/

function getFilePath (type, { scope, theme } = {}) {
return path.resolve(__dirname, '..', `variables${scope ? `.${scope}` : ''}${theme ? `.${theme}` : ''}.${type}`)
}

function genLess (tuples, options) {
const filePath = getFilePath('less', options)

fs.writeFileSync(
filePath,
tuples.map(([key, value]) => `@${key}: ${value};`).join('\n') + '\n',
'utf8'
)
}

function genJS (tuples, options) {
const filePath = getFilePath('js', options)

fs.writeFileSync(
filePath,
tuples
.map(([key, value]) => `export const ${camelCase(key)} = '${value}'`)
.join('\n') + '\n',
'utf8'
)
}

function genJSON (tuples, options) {
const filePath = getFilePath('json', options)
fs.writeFileSync(
filePath,
JSON.stringify(
tuples
.map(([key, value]) => ({
[key]: {
value,
type: getTypeByName(key) || getTypeByValue(value),
global: options.globals.includes(key)
}
}))
.reduce((acc, cur) => {
Object.assign(acc, cur)
return acc
}, {}),
null,
' '
),
'utf8'
)
}

async function generate({ theme } = {}) {
try {
const allVariables = await getVariables('./tokens/index.less')
const globalVariables = await getVariables('./tokens/global.less')
const paletteVariables = globalVariables.filter(v => paletteRe.test(v))

const tuples = await getTuples(allVariables)
const allTuples = await getTuples(allVariables, { theme })
const globalTuples = await getTuples(globalVariables, { theme })
const paletteTuples = await getTuples(paletteVariables, { theme })

// generate variables.less
fs.writeFileSync(
path.resolve(__dirname, '..', 'variables.less'),
tuples.map(([key, value]) => `@${key}: ${value};`).join('\n') + '\n',
'utf8'
)
genLess(allTuples, { theme })
genLess(globalTuples, { scope: 'global', theme })
genLess(paletteTuples, { scope: 'palette', theme })

fs.writeFileSync(
path.resolve(__dirname, '..', 'variables.js'),
tuples
.map(([key, value]) => `export const ${camelCase(key)} = '${value}'`)
.join('\n') + '\n',
'utf8'
)
genJS(allTuples, { theme })
genJS(globalTuples, { scope: 'global', theme })
genJS(paletteTuples, { scope: 'palette', theme })

// generate variables.json
fs.writeFileSync(
path.resolve(__dirname, '..', 'variables.json'),
JSON.stringify(
tuples
.map(([key, value]) => ({
[key]: {
value,
type: getTypeByName(key) || getTypeByValue(value),
global: globalVariables.includes(key)
}
}))
.reduce((acc, cur) => {
Object.assign(acc, cur)
return acc
}, {}),
null,
' '
),
'utf8'
)

console.log(`${tuples.length} variables generated.`)
genJSON(allTuples, { theme, globals: globalVariables })
genJSON(globalTuples, { scope: 'global', theme, globals: globalVariables })
genJSON(paletteTuples, { scope: 'palette', theme, globals: paletteVariables })

console.log(
`${allTuples.length} variables generated for theme [${
theme || 'default'
}].`
)
} catch (e) {
console.error(e)
}
Expand Down Expand Up @@ -129,4 +164,9 @@ function getTypeByValue(value) {
return 'unknown'
}

generate()
async function run() {
await generate()
await generate({ theme: 'ai' })
}

run()
25 changes: 22 additions & 3 deletions packages/less-plugin-dls/src/inject.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import path from 'path'
import fs from 'fs'

const SELF_MODULE_PATH = path.resolve(__dirname, '..')
const ENTRY_LESS = path.resolve(__dirname, '../tokens/index.less')
const THEME_DIR = path.resolve(__dirname, '../tokens/themes')

class Injector {
constructor ({ theme }) {
this.theme = theme
}

process(src, extra) {
// Don't inject self
if (
Expand All @@ -19,11 +25,24 @@ class Injector {
path.dirname(extra.fileInfo.filename),
ENTRY_LESS
)

const themeLess = path.resolve(THEME_DIR, `${this.theme}.less`)
let themeRelative = fs.existsSync(themeLess) ? path.relative(
path.dirname(extra.fileInfo.filename),
themeLess
) : null

// less requires relative path to starts with ./
if (relative.charAt(0) !== '.') {
relative = `./${relative}`
}
const injected = `@import "${relative}";\n`
if (themeRelative && themeRelative.charAt(0) !== '.') {
themeRelative = `./${themeRelative}`
}

let injected = `@import "${relative}";\n`
injected += themeRelative ? `@import "${themeRelative}";\n` : ''

const ignored = extra.imports.contentsIgnoredChars
const fileInfo = extra.fileInfo
ignored[fileInfo.filename] = ignored[fileInfo.filename] || 0
Expand All @@ -33,9 +52,9 @@ class Injector {
}

export default function inject(_, pluginManager) {
const { inject = true } = this.options || {}
const { inject = true, theme } = this.options || {}

if (inject) {
pluginManager.addPreProcessor(new Injector())
pluginManager.addPreProcessor(new Injector({ theme }))
}
}
18 changes: 12 additions & 6 deletions packages/less-plugin-dls/test/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const INCLUDE_PATH = path.resolve(__dirname, '../src')
const SPEC_DIR = path.resolve(__dirname, 'specs')
const SRC_DIR = path.resolve(__dirname, '../tokens')
const SRC_COMPONENTS_DIR = path.resolve(SRC_DIR, 'components')
const MANUAL_SPEC_MODULES = ['functions', 'global']
const MANUAL_SPEC_MODULES = ['functions', 'global', 'ai']
const VAR_DEF_RE = /@([a-z]+(?:-[a-z0-9]+)*)\s*:/g

function extractVarDefs (file) {
Expand Down Expand Up @@ -114,11 +114,16 @@ function getTests () {
}

const specFile = path.resolve(moduleDir, partFile)
if (args['--update-snapshots']) {
const srcFile = !MANUAL_SPEC_MODULES.includes(module)
? path.resolve(SRC_COMPONENTS_DIR, module + '.less')
: module === 'global' ? path.resolve(SRC_DIR, 'global.less') : null

const srcFile = !MANUAL_SPEC_MODULES.includes(module)
? path.resolve(SRC_COMPONENTS_DIR, module + '.less')
: module === 'global'
? path.resolve(SRC_DIR, 'global.less')
: module === 'ai'
? path.resolve(SRC_DIR, 'themes/ai.less')
: null

if (args['--update-snapshots']) {
if (srcFile) {
const vars = extractVarDefs(srcFile)
if (vars.length) {
Expand All @@ -138,6 +143,7 @@ function getTests () {
module,
part,
src,
theme: module === 'ai' ? 'ai' : null,
expected
})
}
Expand Down Expand Up @@ -165,7 +171,7 @@ function getSuite (name, tests, { less }) {
.render(test.src, {
paths: [INCLUDE_PATH],
javascriptEnabled: true,
plugins: [dls()]
plugins: [dls({ theme: test.theme })]
})
.then(
result => {
Expand Down
46 changes: 46 additions & 0 deletions packages/less-plugin-dls/test/specs/ai/ai.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
div {
-dls-color-brand: #4d79ff;
-dls-color-brand-0: #fff;
-dls-color-brand-1: #f2f7ff;
-dls-color-brand-2: #e3edff;
-dls-color-brand-3: #c4daff;
-dls-color-brand-4: #99beff;
-dls-color-brand-5: #729cfe;
-dls-color-brand-6: #4d79ff;
-dls-color-brand-7: #3a5bfd;
-dls-color-brand-8: #333fe6;
-dls-color-brand-9: #0f2dbd;
-dls-color-brand-10: #1c244a;
-dls-color-brand-11: #000;
-dls-color-info: #4d79ff;
-dls-color-info-0: #fff;
-dls-color-info-1: #f2f7ff;
-dls-color-info-2: #e3edff;
-dls-color-info-3: #c4daff;
-dls-color-info-4: #99beff;
-dls-color-info-5: #729cfe;
-dls-color-info-6: #4d79ff;
-dls-color-info-7: #3a5bfd;
-dls-color-info-8: #333fe6;
-dls-color-info-9: #0f2dbd;
-dls-color-info-10: #1c244a;
-dls-color-info-11: #000;
-dls-color-success: #3ac222;
-dls-color-warning: #fea800;
-dls-color-error: #f53f3f;
-dls-height-xs: 28px;
-dls-height-s: 32px;
-dls-height-m: 36px;
-dls-height-l: 40px;
-dls-height-xl: 48px;
-dls-border-radius-0: 4px;
-dls-border-radius-1: 6px;
-dls-border-radius-2: 10px;
-dls-border-radius-3: 16px;
-dls-border-radius-4: 26px;
-dls-shadow-color: #0047c2;
-dls-checkbox-border-radius: 4px;
-dls-tag-color-turquoise: #00bbd1;
-dls-tag-color-violet: #824fe7;
-dls-tag-color-green: #3ac222;
}
Loading