From 18477c15333f54db2a81d745d95749329e9f3541 Mon Sep 17 00:00:00 2001
From: romainmenke
Date: Mon, 14 Mar 2022 20:14:57 +0100
Subject: [PATCH 01/32] design tokens
---
package-lock.json | 18 ++
plugins/postcss-design-tokens/.gitignore | 6 +
plugins/postcss-design-tokens/.nvmrc | 1 +
plugins/postcss-design-tokens/.tape.mjs | 54 ++++++
plugins/postcss-design-tokens/CHANGELOG.md | 5 +
plugins/postcss-design-tokens/INSTALL.md | 176 ++++++++++++++++++
plugins/postcss-design-tokens/LICENSE.md | 108 +++++++++++
plugins/postcss-design-tokens/README.md | 97 ++++++++++
plugins/postcss-design-tokens/docs/README.md | 51 +++++
plugins/postcss-design-tokens/package.json | 61 ++++++
plugins/postcss-design-tokens/src/index.ts | 52 ++++++
.../postcss-design-tokens/test/_import.mjs | 6 +
.../postcss-design-tokens/test/_require.cjs | 6 +
plugins/postcss-design-tokens/test/basic.css | 20 ++
.../test/basic.expect.css | 20 ++
.../test/examples/example.css | 20 ++
.../test/examples/example.expect.css | 7 +
plugins/postcss-design-tokens/tsconfig.json | 9 +
18 files changed, 717 insertions(+)
create mode 100644 plugins/postcss-design-tokens/.gitignore
create mode 100644 plugins/postcss-design-tokens/.nvmrc
create mode 100644 plugins/postcss-design-tokens/.tape.mjs
create mode 100644 plugins/postcss-design-tokens/CHANGELOG.md
create mode 100644 plugins/postcss-design-tokens/INSTALL.md
create mode 100644 plugins/postcss-design-tokens/LICENSE.md
create mode 100644 plugins/postcss-design-tokens/README.md
create mode 100644 plugins/postcss-design-tokens/docs/README.md
create mode 100644 plugins/postcss-design-tokens/package.json
create mode 100644 plugins/postcss-design-tokens/src/index.ts
create mode 100644 plugins/postcss-design-tokens/test/_import.mjs
create mode 100644 plugins/postcss-design-tokens/test/_require.cjs
create mode 100644 plugins/postcss-design-tokens/test/basic.css
create mode 100644 plugins/postcss-design-tokens/test/basic.expect.css
create mode 100644 plugins/postcss-design-tokens/test/examples/example.css
create mode 100644 plugins/postcss-design-tokens/test/examples/example.expect.css
create mode 100644 plugins/postcss-design-tokens/tsconfig.json
diff --git a/package-lock.json b/package-lock.json
index 99c0679ce..6aa802dd0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1766,6 +1766,10 @@
"resolved": "plugins/postcss-color-function",
"link": true
},
+ "node_modules/@csstools/postcss-design-tokens": {
+ "resolved": "plugins/postcss-design-tokens",
+ "link": true
+ },
"node_modules/@csstools/postcss-font-format-keywords": {
"resolved": "plugins/postcss-font-format-keywords",
"link": true
@@ -6725,6 +6729,16 @@
"postcss": "^8.4"
}
},
+ "plugins/postcss-design-tokens": {
+ "version": "1.0.0",
+ "license": "CC0-1.0",
+ "engines": {
+ "node": "^12 || ^14 || >=16"
+ },
+ "peerDependencies": {
+ "postcss": "^8.3"
+ }
+ },
"plugins/postcss-dir-pseudo-class": {
"version": "6.0.4",
"license": "CC0-1.0",
@@ -8222,6 +8236,10 @@
"postcss-value-parser": "^4.2.0"
}
},
+ "@csstools/postcss-design-tokens": {
+ "version": "file:plugins/postcss-design-tokens",
+ "requires": {}
+ },
"@csstools/postcss-font-format-keywords": {
"version": "file:plugins/postcss-font-format-keywords",
"requires": {
diff --git a/plugins/postcss-design-tokens/.gitignore b/plugins/postcss-design-tokens/.gitignore
new file mode 100644
index 000000000..7172b04f1
--- /dev/null
+++ b/plugins/postcss-design-tokens/.gitignore
@@ -0,0 +1,6 @@
+node_modules
+package-lock.json
+yarn.lock
+*.result.css
+*.result.css.map
+dist/*
diff --git a/plugins/postcss-design-tokens/.nvmrc b/plugins/postcss-design-tokens/.nvmrc
new file mode 100644
index 000000000..f0b10f153
--- /dev/null
+++ b/plugins/postcss-design-tokens/.nvmrc
@@ -0,0 +1 @@
+v16.13.1
diff --git a/plugins/postcss-design-tokens/.tape.mjs b/plugins/postcss-design-tokens/.tape.mjs
new file mode 100644
index 000000000..4e04f1f9b
--- /dev/null
+++ b/plugins/postcss-design-tokens/.tape.mjs
@@ -0,0 +1,54 @@
+import postcssTape from '../../packages/postcss-tape/dist/index.mjs';
+import plugin from '@csstools/postcss-design-tokens';
+
+postcssTape(plugin)({
+ basic: {
+ message: "supports basic usage",
+ options: {
+ requireDesignTokens: {
+ properties: [
+ 'color'
+ ]
+ },
+ designTokens: {
+ selectors: [
+ {
+ name: 'button',
+ value: ':is(.button, button[type="submit"])',
+ required: false,
+ deprecated: false,
+ }
+ ],
+ atSupports: [
+ {
+ name: 'cover',
+ value: '(object-fit: cover)',
+ required: false,
+ deprecated: false,
+ }
+ ],
+ atMedia: [
+ {
+ name: 'medium',
+ value: '(min-width: 768px)',
+ required: false,
+ deprecated: false
+ }
+ ],
+ values: [
+ {
+ name: 'my-color',
+ value: '#f00',
+ required: false,
+ deprecated: false,
+ allowedProperties: [],
+ blockedProperties: [],
+ }
+ ]
+ }
+ }
+ },
+ 'examples/example': {
+ message: 'minimal example',
+ },
+});
diff --git a/plugins/postcss-design-tokens/CHANGELOG.md b/plugins/postcss-design-tokens/CHANGELOG.md
new file mode 100644
index 000000000..ea750358a
--- /dev/null
+++ b/plugins/postcss-design-tokens/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Changes to PostCSS Design Tokens
+
+### 1.0.0 (Unreleased)
+
+- Initial version
diff --git a/plugins/postcss-design-tokens/INSTALL.md b/plugins/postcss-design-tokens/INSTALL.md
new file mode 100644
index 000000000..5a59c270a
--- /dev/null
+++ b/plugins/postcss-design-tokens/INSTALL.md
@@ -0,0 +1,176 @@
+# Installing PostCSS Design Tokens
+
+[PostCSS Design Tokens] runs in all Node environments, with special instructions for:
+
+| [Node](#node) | [PostCSS CLI](#postcss-cli) | [Webpack](#webpack) | [Create React App](#create-react-app) | [Gulp](#gulp) | [Grunt](#grunt) |
+| --- | --- | --- | --- | --- | --- |
+
+## Node
+
+Add [PostCSS Design Tokens] to your project:
+
+```bash
+npm install postcss @csstools/postcss-design-tokens --save-dev
+```
+
+Use it as a [PostCSS] plugin:
+
+```js
+const postcss = require('postcss');
+const postcssBasePlugin = require('@csstools/postcss-design-tokens');
+
+postcss([
+ postcssBasePlugin(/* pluginOptions */)
+]).process(YOUR_CSS /*, processOptions */);
+```
+
+## PostCSS CLI
+
+Add [PostCSS CLI] to your project:
+
+```bash
+npm install postcss-cli @csstools/postcss-design-tokens --save-dev
+```
+
+Use [PostCSS Design Tokens] in your `postcss.config.js` configuration file:
+
+```js
+const postcssBasePlugin = require('@csstools/postcss-design-tokens');
+
+module.exports = {
+ plugins: [
+ postcssBasePlugin(/* pluginOptions */)
+ ]
+}
+```
+
+## Webpack
+
+_Webpack version 5_
+
+Add [PostCSS Loader] to your project:
+
+```bash
+npm install postcss-loader @csstools/postcss-design-tokens --save-dev
+```
+
+Use [PostCSS Design Tokens] in your Webpack configuration:
+
+```js
+module.exports = {
+ module: {
+ rules: [
+ {
+ test: /\.css$/i,
+ use: [
+ "style-loader",
+ {
+ loader: "css-loader",
+ options: { importLoaders: 1 },
+ },
+ {
+ loader: "postcss-loader",
+ options: {
+ postcssOptions: {
+ plugins: [
+ [
+ "@csstools/postcss-design-tokens",
+ {
+ // Options
+ },
+ ],
+ ],
+ },
+ },
+ },
+ ],
+ },
+ ],
+ },
+};
+```
+
+## Create React App
+
+Add [React App Rewired] and [React App Rewire PostCSS] to your project:
+
+```bash
+npm install react-app-rewired react-app-rewire-postcss @csstools/postcss-design-tokens --save-dev
+```
+
+Use [React App Rewire PostCSS] and [PostCSS Design Tokens] in your
+`config-overrides.js` file:
+
+```js
+const reactAppRewirePostcss = require('react-app-rewire-postcss');
+const postcssBasePlugin = require('@csstools/postcss-design-tokens');
+
+module.exports = config => reactAppRewirePostcss(config, {
+ plugins: () => [
+ postcssBasePlugin(/* pluginOptions */)
+ ]
+});
+```
+
+## Gulp
+
+Add [Gulp PostCSS] to your project:
+
+```bash
+npm install gulp-postcss @csstools/postcss-design-tokens --save-dev
+```
+
+Use [PostCSS Design Tokens] in your Gulpfile:
+
+```js
+const postcss = require('gulp-postcss');
+const postcssBasePlugin = require('@csstools/postcss-design-tokens');
+
+gulp.task('css', function () {
+ var plugins = [
+ postcssBasePlugin(/* pluginOptions */)
+ ];
+
+ return gulp.src('./src/*.css')
+ .pipe(postcss(plugins))
+ .pipe(gulp.dest('.'));
+});
+```
+
+## Grunt
+
+Add [Grunt PostCSS] to your project:
+
+```bash
+npm install grunt-postcss @csstools/postcss-design-tokens --save-dev
+```
+
+Use [PostCSS Design Tokens] in your Gruntfile:
+
+```js
+const postcssBasePlugin = require('@csstools/postcss-design-tokens');
+
+grunt.loadNpmTasks('grunt-postcss');
+
+grunt.initConfig({
+ postcss: {
+ options: {
+ processors: [
+ postcssBasePlugin(/* pluginOptions */)
+ ]
+ },
+ dist: {
+ src: '*.css'
+ }
+ }
+});
+```
+
+[Gulp PostCSS]: https://github.com/postcss/gulp-postcss
+[Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss
+[PostCSS]: https://github.com/postcss/postcss
+[PostCSS CLI]: https://github.com/postcss/postcss-cli
+[PostCSS Loader]: https://github.com/postcss/postcss-loader
+[PostCSS Design Tokens]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-design-tokens
+[React App Rewire PostCSS]: https://github.com/csstools/react-app-rewire-postcss
+[React App Rewired]: https://github.com/timarney/react-app-rewired
diff --git a/plugins/postcss-design-tokens/LICENSE.md b/plugins/postcss-design-tokens/LICENSE.md
new file mode 100644
index 000000000..0bc1fa706
--- /dev/null
+++ b/plugins/postcss-design-tokens/LICENSE.md
@@ -0,0 +1,108 @@
+# CC0 1.0 Universal
+
+## Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator and
+subsequent owner(s) (each and all, an “owner”) of an original work of
+authorship and/or a database (each, a “Work”).
+
+Certain owners wish to permanently relinquish those rights to a Work for the
+purpose of contributing to a commons of creative, cultural and scientific works
+(“Commons”) that the public can reliably and without fear of later claims of
+infringement build upon, modify, incorporate in other works, reuse and
+redistribute as freely as possible in any form whatsoever and for any purposes,
+including without limitation commercial purposes. These owners may contribute
+to the Commons to promote the ideal of a free culture and the further
+production of creative, cultural and scientific works, or to gain reputation or
+greater distribution for their Work in part through the use and efforts of
+others.
+
+For these and/or other purposes and motivations, and without any expectation of
+additional consideration or compensation, the person associating CC0 with a
+Work (the “Affirmer”), to the extent that he or she is an owner of Copyright
+and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and
+publicly distribute the Work under its terms, with knowledge of his or her
+Copyright and Related Rights in the Work and the meaning and intended legal
+effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+ protected by copyright and related or neighboring rights (“Copyright and
+ Related Rights”). Copyright and Related Rights include, but are not limited
+ to, the following:
+ 1. the right to reproduce, adapt, distribute, perform, display, communicate,
+ and translate a Work;
+ 2. moral rights retained by the original author(s) and/or performer(s);
+ 3. publicity and privacy rights pertaining to a person’s image or likeness
+ depicted in a Work;
+ 4. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(i), below;
+ 5. rights protecting the extraction, dissemination, use and reuse of data in
+ a Work;
+ 6. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation thereof,
+ including any amended or successor version of such directive); and
+ 7. other similar, equivalent or corresponding rights throughout the world
+ based on applicable law or treaty, and any national implementations
+ thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention of,
+ applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
+ unconditionally waives, abandons, and surrenders all of Affirmer’s Copyright
+ and Related Rights and associated claims and causes of action, whether now
+ known or unknown (including existing as well as future claims and causes of
+ action), in the Work (i) in all territories worldwide, (ii) for the maximum
+ duration provided by applicable law or treaty (including future time
+ extensions), (iii) in any current or future medium and for any number of
+ copies, and (iv) for any purpose whatsoever, including without limitation
+ commercial, advertising or promotional purposes (the “Waiver”). Affirmer
+ makes the Waiver for the benefit of each member of the public at large and
+ to the detriment of Affirmer’s heirs and successors, fully intending that
+ such Waiver shall not be subject to revocation, rescission, cancellation,
+ termination, or any other legal or equitable action to disrupt the quiet
+ enjoyment of the Work by the public as contemplated by Affirmer’s express
+ Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason be
+ judged legally invalid or ineffective under applicable law, then the Waiver
+ shall be preserved to the maximum extent permitted taking into account
+ Affirmer’s express Statement of Purpose. In addition, to the extent the
+ Waiver is so judged Affirmer hereby grants to each affected person a
+ royalty-free, non transferable, non sublicensable, non exclusive,
+ irrevocable and unconditional license to exercise Affirmer’s Copyright and
+ Related Rights in the Work (i) in all territories worldwide, (ii) for the
+ maximum duration provided by applicable law or treaty (including future time
+ extensions), (iii) in any current or future medium and for any number of
+ copies, and (iv) for any purpose whatsoever, including without limitation
+ commercial, advertising or promotional purposes (the “License”). The License
+ shall be deemed effective as of the date CC0 was applied by Affirmer to the
+ Work. Should any part of the License for any reason be judged legally
+ invalid or ineffective under applicable law, such partial invalidity or
+ ineffectiveness shall not invalidate the remainder of the License, and in
+ such case Affirmer hereby affirms that he or she will not (i) exercise any
+ of his or her remaining Copyright and Related Rights in the Work or (ii)
+ assert any associated claims and causes of action with respect to the Work,
+ in either case contrary to Affirmer’s express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+ 1. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ 2. Affirmer offers the Work as-is and makes no representations or warranties
+ of any kind concerning the Work, express, implied, statutory or
+ otherwise, including without limitation warranties of title,
+ merchantability, fitness for a particular purpose, non infringement, or
+ the absence of latent or other defects, accuracy, or the present or
+ absence of errors, whether or not discoverable, all to the greatest
+ extent permissible under applicable law.
+ 3. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person’s Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the Work.
+ 4. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to this
+ CC0 or use of the Work.
+
+For more information, please see
+http://creativecommons.org/publicdomain/zero/1.0/.
diff --git a/plugins/postcss-design-tokens/README.md b/plugins/postcss-design-tokens/README.md
new file mode 100644
index 000000000..92fa6dd7b
--- /dev/null
+++ b/plugins/postcss-design-tokens/README.md
@@ -0,0 +1,97 @@
+# PostCSS Design Tokens [
][postcss]
+
+[
][npm-url]
+[
][css-url]
+[
][cli-url]
+[
][discord]
+
+[PostCSS Design Tokens] lets easily create new plugins following some [CSS Specification].
+
+```pcss
+.foo {
+ color: red;
+}
+
+.baz {
+ color: green;
+}
+
+/* becomes */
+
+.foo {
+ color: blue;
+}
+
+.baz {
+ color: green;
+}
+```
+
+## Usage
+
+Add [PostCSS Design Tokens] to your project:
+
+```bash
+npm install postcss @csstools/postcss-design-tokens --save-dev
+```
+
+Use it as a [PostCSS] plugin:
+
+```js
+const postcss = require('postcss');
+const postcssBasePlugin = require('@csstools/postcss-design-tokens');
+
+postcss([
+ postcssBasePlugin(/* pluginOptions */)
+]).process(YOUR_CSS /*, processOptions */);
+```
+
+[PostCSS Design Tokens] runs in all Node environments, with special
+instructions for:
+
+| [Node](INSTALL.md#node) | [PostCSS CLI](INSTALL.md#postcss-cli) | [Webpack](INSTALL.md#webpack) | [Create React App](INSTALL.md#create-react-app) | [Gulp](INSTALL.md#gulp) | [Grunt](INSTALL.md#grunt) |
+| --- | --- | --- | --- | --- | --- |
+
+## Options
+
+### preserve
+
+The `preserve` option determines whether the original notation
+is preserved. By default, it is not preserved.
+
+```js
+postcssBasePlugin({ preserve: true })
+```
+
+```pcss
+.foo {
+ color: red;
+}
+
+.baz {
+ color: green;
+}
+
+/* becomes */
+
+.foo {
+ color: blue;
+ color: red;
+}
+
+.baz {
+ color: green;
+}
+```
+
+[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
+[css-url]: https://cssdb.org/#TODO
+[discord]: https://discord.gg/bUadyRwkJS
+[npm-url]: https://www.npmjs.com/package/@csstools/postcss-design-tokens
+
+[Gulp PostCSS]: https://github.com/postcss/gulp-postcss
+[Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss
+[PostCSS]: https://github.com/postcss/postcss
+[PostCSS Loader]: https://github.com/postcss/postcss-loader
+[PostCSS Design Tokens]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-design-tokens
+[CSS Specification]: #TODO
diff --git a/plugins/postcss-design-tokens/docs/README.md b/plugins/postcss-design-tokens/docs/README.md
new file mode 100644
index 000000000..3bc455892
--- /dev/null
+++ b/plugins/postcss-design-tokens/docs/README.md
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[] lets easily create new plugins following some [CSS Specification].
+
+```pcss
+
+
+/* becomes */
+
+
+```
+
+
+
+
+
+## Options
+
+### preserve
+
+The `preserve` option determines whether the original notation
+is preserved. By default, it is not preserved.
+
+```js
+({ preserve: true })
+```
+
+```pcss
+
+
+/* becomes */
+
+
+```
+
+
+[CSS Specification]:
diff --git a/plugins/postcss-design-tokens/package.json b/plugins/postcss-design-tokens/package.json
new file mode 100644
index 000000000..f146c70f0
--- /dev/null
+++ b/plugins/postcss-design-tokens/package.json
@@ -0,0 +1,61 @@
+{
+ "name": "@csstools/postcss-design-tokens",
+ "description": "TODO: Add description for Design Tokens",
+ "version": "1.0.0",
+ "author": "Jonathan Neal ",
+ "license": "CC0-1.0",
+ "engines": {
+ "node": "^12 || ^14 || >=16"
+ },
+ "main": "dist/index.cjs",
+ "module": "dist/index.mjs",
+ "types": "dist/index.d.ts",
+ "exports": {
+ ".": {
+ "import": "./dist/index.mjs",
+ "require": "./dist/index.cjs",
+ "default": "./dist/index.mjs"
+ }
+ },
+ "files": [
+ "CHANGELOG.md",
+ "LICENSE.md",
+ "README.md",
+ "dist"
+ ],
+ "peerDependencies": {
+ "postcss": "^8.3"
+ },
+ "scripts": {
+ "build": "rollup -c ../../rollup/default.js",
+ "clean": "node -e \"fs.rmSync('./dist', { recursive: true, force: true });\"",
+ "docs": "node ../../.github/bin/generate-docs/install.mjs && node ../../.github/bin/generate-docs/readme.mjs",
+ "lint": "npm run lint:eslint && npm run lint:package-json",
+ "lint:eslint": "eslint ./src --ext .js --ext .ts --ext .mjs --no-error-on-unmatched-pattern",
+ "lint:package-json": "node ../../.github/bin/format-package-json.mjs",
+ "prepublishOnly": "npm run clean && npm run build && npm run test",
+ "stryker": "stryker run --logLevel error",
+ "test": "node .tape.mjs && npm run test:exports",
+ "test:exports": "node ./test/_import.mjs && node ./test/_require.cjs",
+ "test:rewrite-expects": "REWRITE_EXPECTS=true node .tape.mjs"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/csstools/postcss-plugins.git",
+ "directory": "plugins/postcss-design-tokens"
+ },
+ "keywords": [
+ "postcss-plugin"
+ ],
+ "csstools": {
+ "cssdbId": "TODO",
+ "exportName": "postcssDesignTokens",
+ "humanReadableName": "PostCSS Design Tokens",
+ "specUrl": "#TODO"
+ },
+ "volta": {
+ "extends": "../../package.json"
+ },
+ "homepage": "https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-design-tokens#readme",
+ "bugs": "https://github.com/csstools/postcss-plugins/issues"
+}
\ No newline at end of file
diff --git a/plugins/postcss-design-tokens/src/index.ts b/plugins/postcss-design-tokens/src/index.ts
new file mode 100644
index 000000000..d4d6e3bbd
--- /dev/null
+++ b/plugins/postcss-design-tokens/src/index.ts
@@ -0,0 +1,52 @@
+import type { PluginCreator } from 'postcss';
+
+type pluginOptions = { color?: string, preserve?: boolean };
+
+const creator: PluginCreator = (opts?: pluginOptions) => {
+ const options = Object.assign(
+ // Default options
+ {
+ color: null,
+ preserve: false,
+ },
+ // Provided options
+ opts,
+ );
+
+ return {
+ postcssPlugin: 'postcss-design-tokens',
+ Declaration(decl) {
+ if (decl.value === 'red') {
+ // Determine the new value.
+ let newValue = 'blue';
+ if (options.color) {
+ newValue = options.color;
+ }
+
+ // Check if it is different from the current value.
+ if (newValue === decl.value) {
+ return;
+ }
+
+ // Insert the new value before the current value.
+ decl.cloneBefore({
+ prop: 'color',
+ value: newValue,
+ });
+
+ // If the current value is preserved we are done and return here.
+ if (options.preserve) {
+ return;
+ }
+
+ // If the current value is not preserved we remove it.
+ decl.remove();
+ }
+ },
+ };
+};
+
+creator.postcss = true;
+
+export default creator;
+
diff --git a/plugins/postcss-design-tokens/test/_import.mjs b/plugins/postcss-design-tokens/test/_import.mjs
new file mode 100644
index 000000000..97e23a010
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/_import.mjs
@@ -0,0 +1,6 @@
+import assert from 'assert';
+import plugin from '@csstools/postcss-design-tokens';
+plugin();
+
+assert.ok(plugin.postcss, 'should have "postcss flag"');
+assert.equal(typeof plugin, 'function', 'should return a function');
diff --git a/plugins/postcss-design-tokens/test/_require.cjs b/plugins/postcss-design-tokens/test/_require.cjs
new file mode 100644
index 000000000..e48299ce9
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/_require.cjs
@@ -0,0 +1,6 @@
+const assert = require('assert');
+const plugin = require('@csstools/postcss-design-tokens');
+plugin();
+
+assert.ok(plugin.postcss, 'should have "postcss flag"');
+assert.equal(typeof plugin, 'function', 'should return a function');
diff --git a/plugins/postcss-design-tokens/test/basic.css b/plugins/postcss-design-tokens/test/basic.css
new file mode 100644
index 000000000..e77d47cc2
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/basic.css
@@ -0,0 +1,20 @@
+.foo {
+ color: design-token(color);
+}
+
+@media (design-token: medium) {
+ .baz {
+ color: green;
+ }
+}
+
+@supports design-token(cover) {
+ .baz {
+ color: green;
+ object-fit: cover;
+ }
+}
+
+:design-token(button) {
+ color: green;
+}
diff --git a/plugins/postcss-design-tokens/test/basic.expect.css b/plugins/postcss-design-tokens/test/basic.expect.css
new file mode 100644
index 000000000..f2e42303e
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/basic.expect.css
@@ -0,0 +1,20 @@
+.foo {
+ color: #f00;
+}
+
+@media (min-width: 768px) {
+ .baz {
+ color: green;
+ }
+}
+
+@supports (object-fit: cover) {
+ .baz {
+ color: green;
+ object-fit: cover;
+ }
+}
+
+:is(.button, button[type="submit"]) {
+ color: green;
+}
diff --git a/plugins/postcss-design-tokens/test/examples/example.css b/plugins/postcss-design-tokens/test/examples/example.css
new file mode 100644
index 000000000..e77d47cc2
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/examples/example.css
@@ -0,0 +1,20 @@
+.foo {
+ color: design-token(color);
+}
+
+@media (design-token: medium) {
+ .baz {
+ color: green;
+ }
+}
+
+@supports design-token(cover) {
+ .baz {
+ color: green;
+ object-fit: cover;
+ }
+}
+
+:design-token(button) {
+ color: green;
+}
diff --git a/plugins/postcss-design-tokens/test/examples/example.expect.css b/plugins/postcss-design-tokens/test/examples/example.expect.css
new file mode 100644
index 000000000..9d738d5ac
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/examples/example.expect.css
@@ -0,0 +1,7 @@
+.foo {
+ color: blue;
+}
+
+.baz {
+ color: green;
+}
diff --git a/plugins/postcss-design-tokens/tsconfig.json b/plugins/postcss-design-tokens/tsconfig.json
new file mode 100644
index 000000000..e0d06239c
--- /dev/null
+++ b/plugins/postcss-design-tokens/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "declarationDir": "."
+ },
+ "include": ["./src/**/*"],
+ "exclude": ["dist"],
+}
From 83bf59b05048276953159a884ee950fe71a210a0 Mon Sep 17 00:00:00 2001
From: romainmenke
Date: Tue, 15 Mar 2022 14:28:27 +0100
Subject: [PATCH 02/32] wip
---
plugins/postcss-design-tokens/.tape.mjs | 61 +++++++-
plugins/postcss-design-tokens/package.json | 5 +-
plugins/postcss-design-tokens/src/index.ts | 99 +++++++-----
plugins/postcss-design-tokens/src/options.ts | 145 ++++++++++++++++++
plugins/postcss-design-tokens/src/values.ts | 123 +++++++++++++++
plugins/postcss-design-tokens/test/basic.css | 9 +-
.../test/basic.expect.css | 13 +-
.../test/examples/example.css | 9 +-
.../test/examples/example.expect.css | 20 ++-
9 files changed, 423 insertions(+), 61 deletions(-)
create mode 100644 plugins/postcss-design-tokens/src/options.ts
create mode 100644 plugins/postcss-design-tokens/src/values.ts
diff --git a/plugins/postcss-design-tokens/.tape.mjs b/plugins/postcss-design-tokens/.tape.mjs
index 4e04f1f9b..6fd977c66 100644
--- a/plugins/postcss-design-tokens/.tape.mjs
+++ b/plugins/postcss-design-tokens/.tape.mjs
@@ -5,7 +5,7 @@ postcssTape(plugin)({
basic: {
message: "supports basic usage",
options: {
- requireDesignTokens: {
+ requiresDesignTokens: {
properties: [
'color'
]
@@ -15,7 +15,6 @@ postcssTape(plugin)({
{
name: 'button',
value: ':is(.button, button[type="submit"])',
- required: false,
deprecated: false,
}
],
@@ -23,7 +22,6 @@ postcssTape(plugin)({
{
name: 'cover',
value: '(object-fit: cover)',
- required: false,
deprecated: false,
}
],
@@ -31,15 +29,20 @@ postcssTape(plugin)({
{
name: 'medium',
value: '(min-width: 768px)',
- required: false,
deprecated: false
}
],
values: [
{
- name: 'my-color',
+ name: 'color',
value: '#f00',
- required: false,
+ deprecated: false,
+ allowedProperties: [],
+ blockedProperties: [],
+ },
+ {
+ name: 'red-components',
+ value: '255, 0, 0',
deprecated: false,
allowedProperties: [],
blockedProperties: [],
@@ -50,5 +53,51 @@ postcssTape(plugin)({
},
'examples/example': {
message: 'minimal example',
+ options: {
+ requiresDesignTokens: {
+ properties: [
+ 'color'
+ ]
+ },
+ designTokens: {
+ selectors: [
+ {
+ name: 'button',
+ value: ':is(.button, button[type="submit"])',
+ deprecated: false,
+ }
+ ],
+ atSupports: [
+ {
+ name: 'cover',
+ value: '(object-fit: cover)',
+ deprecated: false,
+ }
+ ],
+ atMedia: [
+ {
+ name: 'medium',
+ value: '(min-width: 768px)',
+ deprecated: false
+ }
+ ],
+ values: [
+ {
+ name: 'color',
+ value: '#f00',
+ deprecated: false,
+ allowedProperties: [],
+ blockedProperties: [],
+ },
+ {
+ name: 'red-components',
+ value: '255, 0, 0',
+ deprecated: false,
+ allowedProperties: [],
+ blockedProperties: [],
+ }
+ ]
+ }
+ }
},
});
diff --git a/plugins/postcss-design-tokens/package.json b/plugins/postcss-design-tokens/package.json
index f146c70f0..0341324ed 100644
--- a/plugins/postcss-design-tokens/package.json
+++ b/plugins/postcss-design-tokens/package.json
@@ -23,6 +23,9 @@
"README.md",
"dist"
],
+ "dependencies": {
+ "postcss-value-parser": "^4.2.0"
+ },
"peerDependencies": {
"postcss": "^8.3"
},
@@ -58,4 +61,4 @@
},
"homepage": "https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-design-tokens#readme",
"bugs": "https://github.com/csstools/postcss-plugins/issues"
-}
\ No newline at end of file
+}
diff --git a/plugins/postcss-design-tokens/src/index.ts b/plugins/postcss-design-tokens/src/index.ts
index d4d6e3bbd..d2f6e787b 100644
--- a/plugins/postcss-design-tokens/src/index.ts
+++ b/plugins/postcss-design-tokens/src/index.ts
@@ -1,47 +1,72 @@
import type { PluginCreator } from 'postcss';
+import { AtMediaOptions, AtSupportsOptions, SelectorOptions, validateOptions, ValueOptions } from './options';
+import { onCSSValue, onCSSValueRequiredDesignToken, onCSSValueUnknownDesignToken } from './values';
-type pluginOptions = { color?: string, preserve?: boolean };
+type pluginOptions = {
+ requiresDesignTokens?: {
+ properties: Array,
+ },
+ designTokens?: {
+ atMedia?: Array,
+ atSupports?: Array,
+ selectors?: Array,
+ values?: Array
+ }
+};
const creator: PluginCreator = (opts?: pluginOptions) => {
- const options = Object.assign(
- // Default options
- {
- color: null,
- preserve: false,
- },
- // Provided options
- opts,
- );
+ const config = validateOptions(opts);
return {
postcssPlugin: 'postcss-design-tokens',
- Declaration(decl) {
- if (decl.value === 'red') {
- // Determine the new value.
- let newValue = 'blue';
- if (options.color) {
- newValue = options.color;
- }
-
- // Check if it is different from the current value.
- if (newValue === decl.value) {
- return;
- }
-
- // Insert the new value before the current value.
- decl.cloneBefore({
- prop: 'color',
- value: newValue,
- });
-
- // If the current value is preserved we are done and return here.
- if (options.preserve) {
- return;
- }
-
- // If the current value is not preserved we remove it.
- decl.remove();
- }
+ prepare() {
+ return {
+ Once(root, { result }) {
+
+ if (config.requiresDesignTokens.properties.size > 0) {
+ // Emit warnings for properties that use custom values when not allowed.
+
+ root.walkDecls(decl => {
+ if (config.requiresDesignTokens.properties.has(decl.prop)) {
+ if (decl.value.indexOf('design-token') === -1) {
+ decl.warn(result, `"${decl.prop}" must always use design tokens.`);
+ return;
+ }
+
+ const modifiedValue = onCSSValueRequiredDesignToken(config, result, decl);
+ if (modifiedValue === false || modifiedValue === decl.value) {
+ decl.warn(result, `"${decl.prop}" must always use design tokens.`);
+ return;
+ }
+
+ decl.value = modifiedValue;
+ }
+ });
+ }
+ },
+ OnceExit(root, { result }) {
+ // Emit warnings if design tokens remain after processing.
+ root.walkDecls(decl => {
+ if (decl.value.indexOf('design-token') === -1) {
+ return;
+ }
+
+ onCSSValueUnknownDesignToken(config, result, decl);
+ });
+ },
+ Declaration(decl, { result }) {
+ if (decl.value.indexOf('design-token') === -1) {
+ return;
+ }
+
+ const modifiedValue = onCSSValue(config, result, decl);
+ if (modifiedValue === decl.value) {
+ return;
+ }
+
+ decl.value = modifiedValue;
+ },
+ };
},
};
};
diff --git a/plugins/postcss-design-tokens/src/options.ts b/plugins/postcss-design-tokens/src/options.ts
new file mode 100644
index 000000000..d4a0d6388
--- /dev/null
+++ b/plugins/postcss-design-tokens/src/options.ts
@@ -0,0 +1,145 @@
+// Public API vs. Internal usage.
+// The public API has a lot of optional parameters and simpler typing like "Array".
+// This is compatible with JSON.
+//
+// The internal usage typings do not have any optional parameters and use more powerful types like "Map" and "Set".
+
+// Public API
+type TokenOptions = {
+ name: string,
+ value: string,
+ deprecated?: boolean,
+}
+
+export type AtMediaOptions = TokenOptions;
+export type AtSupportsOptions = TokenOptions;
+export type SelectorOptions = TokenOptions;
+
+export type ValueOptions = {
+ name: string,
+ value: string,
+ deprecated?: boolean,
+ allowedProperties?: Array,
+ blockedProperties?: Array,
+}
+
+// Internal usage
+type TokenConfig = {
+ name: string,
+ value: string,
+ deprecated: boolean,
+}
+
+export type AtMediaConfig = TokenConfig;
+export type AtSupportsConfig = TokenConfig;
+export type SelectorConfig = TokenConfig;
+
+export type ValueConfig = {
+ name: string,
+ value: string,
+ deprecated: boolean,
+ allowedProperties: Set,
+ blockedProperties: Set,
+}
+
+function validateSubOptions(rawSubOptions: unknown): { name: string, value: string, deprecated: boolean } {
+ const subOptions = Object(rawSubOptions);
+ return {
+ name: (typeof subOptions.name === 'string') ? subOptions.name : '',
+ value: (typeof subOptions.value === 'string') ? subOptions.value : '',
+ deprecated: (typeof subOptions.deprecated === 'boolean') ? subOptions.deprecated : false,
+ };
+}
+
+export type Config = {
+ requiresDesignTokens: {
+ properties: Set,
+ },
+ designTokens: {
+ atMedia: Map,
+ atSupports: Map,
+ selectors: Map,
+ values: Map
+ }
+};
+
+export function validateOptions(rawOptions: unknown) {
+ const config: Config = {
+ requiresDesignTokens: {
+ properties: new Set(),
+ },
+ designTokens: {
+ atMedia: new Map(),
+ atSupports: new Map(),
+ selectors: new Map(),
+ values: new Map(),
+ },
+ };
+
+ const requiresDesignTokens = Object(rawOptions).requiresDesignTokens;
+ if (requiresDesignTokens && Array.isArray(Object(requiresDesignTokens).properties)) {
+ requiresDesignTokens.properties.forEach((x) => {
+ if (typeof x === 'string') {
+ config.requiresDesignTokens.properties.add(x);
+ }
+ });
+ }
+
+ const designTokens = Object(rawOptions).designTokens;
+ if (designTokens) {
+ if (Array.isArray(Object(designTokens).atMedia)) {
+ designTokens.atMedia.map((x) => {
+ return validateSubOptions(x);
+ }).filter((x: TokenConfig) => {
+ return x.name !== '' && x.value !== '';
+ }).forEach((x: TokenConfig) => {
+ config.designTokens.atMedia.set(x.name, x);
+ });
+ }
+
+ if (Array.isArray(Object(designTokens).atSupports)) {
+ designTokens.atSupports.map((x) => {
+ return validateSubOptions(x);
+ }).filter((x: TokenConfig) => {
+ return x.name !== '' && x.value !== '';
+ }).forEach((x: TokenConfig) => {
+ config.designTokens.atSupports.set(x.name, x);
+ });
+ }
+
+ if (Array.isArray(Object(designTokens).selectors)) {
+ designTokens.selectors.map((x) => {
+ return validateSubOptions(x);
+ }).filter((x: TokenConfig) => {
+ return x.name !== '' && x.value !== '';
+ }).forEach((x: TokenConfig) => {
+ config.designTokens.selectors.set(x.name, x);
+ });
+ }
+
+ if (Array.isArray(Object(designTokens).values)) {
+ designTokens.values.map((x) => {
+ const allowedProperties : Array = (Array.isArray(Object(x).allowedProperties)) ? x.allowedProperties.filter((y) => {
+ return typeof y === 'string';
+ }) : [];
+ const blockedProperties: Array = (Array.isArray(Object(x).blockedProperties)) ? x.blockedProperties.filter((y) => {
+ return typeof y === 'string';
+ }) : [];
+
+ const xx: ValueConfig = {
+ ...validateSubOptions(x),
+ allowedProperties: new Set(allowedProperties),
+ blockedProperties: new Set(blockedProperties),
+ };
+
+ return xx;
+ }).filter((x: ValueConfig) => {
+ return x.name !== '' && x.value !== '';
+ }).forEach((x: ValueConfig) => {
+ config.designTokens.values.set(x.name, x);
+ });
+ }
+ }
+
+ return config;
+}
diff --git a/plugins/postcss-design-tokens/src/values.ts b/plugins/postcss-design-tokens/src/values.ts
new file mode 100644
index 000000000..829f5e1de
--- /dev/null
+++ b/plugins/postcss-design-tokens/src/values.ts
@@ -0,0 +1,123 @@
+import type { Declaration, Result } from 'postcss';
+import valuesParser from 'postcss-value-parser';
+import { Config } from './options';
+
+export function onCSSValue(config: Config, result: Result, decl: Declaration) {
+ const valueAST = valuesParser(decl.value);
+
+ valueAST.walk(node => {
+ if (node.type !== 'function' || node.value !== 'design-token') {
+ return;
+ }
+
+ if (!node.nodes || node.nodes.length !== 1) {
+ decl.warn(result, 'Expected a single keyword for the design-token function.');
+ return;
+ }
+
+ if (node.nodes[0].type !== 'word') {
+ decl.warn(result, 'Expected a single keyword for the design-token function.');
+ return;
+ }
+
+ const replacement = config.designTokens.values.get(node.nodes[0].value);
+ if (!replacement) {
+ return;
+ }
+
+ if (replacement.deprecated) {
+ decl.warn(result, `design-token: "${replacement.name}" is deprecated.`);
+ }
+
+ if (replacement.allowedProperties.size && !replacement.allowedProperties.has(decl.prop)) {
+ decl.warn(result, `design-token: "${replacement.name}" is not allowed for "${decl.prop}".`);
+ return;
+ }
+
+ if (replacement.blockedProperties.size && replacement.blockedProperties.has(decl.prop)) {
+ decl.warn(result, `design-token: "${replacement.name}" is not allowed for "${decl.prop}".`);
+ return;
+ }
+
+ node.value = replacement.value;
+ node.nodes = undefined;
+ });
+
+ return String(valueAST);
+}
+
+export function onCSSValueRequiredDesignToken(config: Config, result: Result, decl: Declaration) {
+ const valueAST = valuesParser(decl.value);
+
+ let hasNonDesignTokens = false;
+
+ valueAST.nodes.forEach((node) => {
+ // Allowed, even when the property requires design tokens.
+ if (node.type === 'comment' || node.type === 'space' || node.type === 'div') {
+ return;
+ }
+
+ if (node.type !== 'function' || node.value !== 'design-token') {
+ hasNonDesignTokens = true;
+ return;
+ }
+
+ if (!node.nodes || node.nodes.length !== 1) {
+ decl.warn(result, 'Expected a single keyword for the design-token function.');
+ return false;
+ }
+
+ if (node.nodes[0].type !== 'word') {
+ decl.warn(result, 'Expected a single keyword for the design-token function.');
+ return false;
+ }
+
+ const replacement = config.designTokens.values.get(node.nodes[0].value);
+ if (!replacement) {
+ return;
+ }
+
+ if (replacement.deprecated) {
+ decl.warn(result, `design-token: "${replacement.name}" is deprecated.`);
+ }
+
+ if (replacement.allowedProperties.size && !replacement.allowedProperties.has(decl.prop)) {
+ decl.warn(result, `design-token: "${replacement.name}" is not allowed for "${decl.prop}".`);
+ return false;
+ }
+
+ if (replacement.blockedProperties.size && replacement.blockedProperties.has(decl.prop)) {
+ decl.warn(result, `design-token: "${replacement.name}" is not allowed for "${decl.prop}".`);
+ return false;
+ }
+
+ node.value = replacement.value;
+ node.nodes = undefined;
+ });
+
+ if (hasNonDesignTokens) {
+ return false;
+ }
+
+ return String(valueAST);
+}
+
+export function onCSSValueUnknownDesignToken(config: Config, result: Result, decl: Declaration) {
+ const valueAST = valuesParser(decl.value);
+
+ valueAST.walk(node => {
+ if (node.type !== 'function' || node.value !== 'design-token') {
+ return;
+ }
+
+ if (!node.nodes || node.nodes.length !== 1) {
+ return;
+ }
+
+ if (node.nodes[0].type !== 'word') {
+ return;
+ }
+
+ decl.warn(result, `design-token: "${node.nodes[0].value}" is not configured.`);
+ });
+}
diff --git a/plugins/postcss-design-tokens/test/basic.css b/plugins/postcss-design-tokens/test/basic.css
index e77d47cc2..5cd4316d0 100644
--- a/plugins/postcss-design-tokens/test/basic.css
+++ b/plugins/postcss-design-tokens/test/basic.css
@@ -4,17 +4,18 @@
@media (design-token: medium) {
.baz {
- color: green;
+ color: design-token(color);
+ background-color: rgb(design-token(red-components));
}
}
-@supports design-token(cover) {
+@supports (design-token: cover) {
.baz {
- color: green;
+ color: design-token(color);
object-fit: cover;
}
}
:design-token(button) {
- color: green;
+ color: design-token(color);
}
diff --git a/plugins/postcss-design-tokens/test/basic.expect.css b/plugins/postcss-design-tokens/test/basic.expect.css
index f2e42303e..924fcf346 100644
--- a/plugins/postcss-design-tokens/test/basic.expect.css
+++ b/plugins/postcss-design-tokens/test/basic.expect.css
@@ -2,19 +2,20 @@
color: #f00;
}
-@media (min-width: 768px) {
+@media (design-token: medium) {
.baz {
- color: green;
+ color: #f00;
+ background-color: rgb(255, 0, 0);
}
}
-@supports (object-fit: cover) {
+@supports (design-token: cover) {
.baz {
- color: green;
+ color: #f00;
object-fit: cover;
}
}
-:is(.button, button[type="submit"]) {
- color: green;
+:design-token(button) {
+ color: #f00;
}
diff --git a/plugins/postcss-design-tokens/test/examples/example.css b/plugins/postcss-design-tokens/test/examples/example.css
index e77d47cc2..5cd4316d0 100644
--- a/plugins/postcss-design-tokens/test/examples/example.css
+++ b/plugins/postcss-design-tokens/test/examples/example.css
@@ -4,17 +4,18 @@
@media (design-token: medium) {
.baz {
- color: green;
+ color: design-token(color);
+ background-color: rgb(design-token(red-components));
}
}
-@supports design-token(cover) {
+@supports (design-token: cover) {
.baz {
- color: green;
+ color: design-token(color);
object-fit: cover;
}
}
:design-token(button) {
- color: green;
+ color: design-token(color);
}
diff --git a/plugins/postcss-design-tokens/test/examples/example.expect.css b/plugins/postcss-design-tokens/test/examples/example.expect.css
index 9d738d5ac..924fcf346 100644
--- a/plugins/postcss-design-tokens/test/examples/example.expect.css
+++ b/plugins/postcss-design-tokens/test/examples/example.expect.css
@@ -1,7 +1,21 @@
.foo {
- color: blue;
+ color: #f00;
}
-.baz {
- color: green;
+@media (design-token: medium) {
+ .baz {
+ color: #f00;
+ background-color: rgb(255, 0, 0);
+ }
+}
+
+@supports (design-token: cover) {
+ .baz {
+ color: #f00;
+ object-fit: cover;
+ }
+}
+
+:design-token(button) {
+ color: #f00;
}
From 522c2a639fc405afc02a00aba70d11f393dc2fff Mon Sep 17 00:00:00 2001
From: romainmenke
Date: Sun, 27 Mar 2022 11:22:50 +0200
Subject: [PATCH 03/32] wip
---
package-lock.json | 38 ++++-
plugins/postcss-design-tokens/.tape.mjs | 105 ++-----------
plugins/postcss-design-tokens/INSTALL.md | 20 +--
plugins/postcss-design-tokens/README.md | 67 ++++----
plugins/postcss-design-tokens/docs/README.md | 18 ++-
plugins/postcss-design-tokens/package.json | 4 +
.../src/data-formats/base/token.ts | 3 +
.../design-tokens-specification/type.ts | 14 ++
.../design-tokens-specification/value.ts | 86 +++++++++++
.../src/data-formats/parse-import.ts | 79 ++++++++++
.../style-dictionary/style-dictionary.ts | 13 ++
.../data-formats/style-dictionary/v3/group.ts | 37 +++++
.../data-formats/style-dictionary/v3/value.ts | 48 ++++++
.../src/data-formats/token.ts | 9 ++
plugins/postcss-design-tokens/src/index.ts | 73 ++++-----
plugins/postcss-design-tokens/src/options.ts | 145 ------------------
plugins/postcss-design-tokens/src/values.ts | 109 ++-----------
plugins/postcss-design-tokens/test/basic.css | 25 +--
.../test/basic.expect.css | 22 +--
.../test/examples/example.css | 23 +--
.../test/examples/example.dark.expect.css | 5 +
.../test/examples/example.expect.css | 22 +--
.../test/examples/tokens-dark.json | 7 +
.../test/examples/tokens-light.json | 7 +
.../postcss-design-tokens/test/imported.css | 1 +
.../postcss-design-tokens/test/tokens.json | 7 +
plugins/postcss-design-tokens/tsconfig.json | 3 +-
rollup/presets/package-typescript.js | 2 +-
28 files changed, 487 insertions(+), 505 deletions(-)
create mode 100644 plugins/postcss-design-tokens/src/data-formats/base/token.ts
create mode 100644 plugins/postcss-design-tokens/src/data-formats/design-tokens-specification/type.ts
create mode 100644 plugins/postcss-design-tokens/src/data-formats/design-tokens-specification/value.ts
create mode 100644 plugins/postcss-design-tokens/src/data-formats/parse-import.ts
create mode 100644 plugins/postcss-design-tokens/src/data-formats/style-dictionary/style-dictionary.ts
create mode 100644 plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/group.ts
create mode 100644 plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/value.ts
create mode 100644 plugins/postcss-design-tokens/src/data-formats/token.ts
delete mode 100644 plugins/postcss-design-tokens/src/options.ts
create mode 100644 plugins/postcss-design-tokens/test/examples/example.dark.expect.css
create mode 100644 plugins/postcss-design-tokens/test/examples/tokens-dark.json
create mode 100644 plugins/postcss-design-tokens/test/examples/tokens-light.json
create mode 100644 plugins/postcss-design-tokens/test/imported.css
create mode 100644 plugins/postcss-design-tokens/test/tokens.json
diff --git a/package-lock.json b/package-lock.json
index ecaa4113f..e64d6cfcf 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5255,9 +5255,9 @@
"link": true
},
"node_modules/postcss-import": {
- "version": "14.0.2",
- "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.0.2.tgz",
- "integrity": "sha512-BJ2pVK4KhUyMcqjuKs9RijV5tatNzNa73e/32aBVE/ejYPe37iH+6vAu9WvqUkB5OAYgLHzbSvzHnorybJCm9g==",
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz",
+ "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==",
"dev": true,
"dependencies": {
"postcss-value-parser": "^4.0.0",
@@ -5998,6 +5998,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/style-dictionary-design-tokens-example": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/style-dictionary-design-tokens-example/-/style-dictionary-design-tokens-example-1.0.0.tgz",
+ "integrity": "sha512-dUjgpSbx4uOAj3qZ1Tja3Vur3w+/UbVlT7JW2aAH3U7w8Hqm7nKwE0dCVU8O94sSFCs0jsdfPlvcYsEIIzLtvA==",
+ "dev": true
+ },
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -6696,8 +6702,16 @@
}
},
"plugins/postcss-design-tokens": {
+ "name": "@csstools/postcss-design-tokens",
"version": "1.0.0",
"license": "CC0-1.0",
+ "dependencies": {
+ "postcss-value-parser": "^4.2.0"
+ },
+ "devDependencies": {
+ "postcss-import": "^14.1.0",
+ "style-dictionary-design-tokens-example": "^1.0.0"
+ },
"engines": {
"node": "^12 || ^14 || >=16"
},
@@ -8292,7 +8306,11 @@
},
"@csstools/postcss-design-tokens": {
"version": "file:plugins/postcss-design-tokens",
- "requires": {}
+ "requires": {
+ "postcss-import": "^14.1.0",
+ "postcss-value-parser": "^4.2.0",
+ "style-dictionary-design-tokens-example": "^1.0.0"
+ }
},
"@csstools/postcss-font-format-keywords": {
"version": "file:plugins/postcss-font-format-keywords",
@@ -10880,9 +10898,9 @@
}
},
"postcss-import": {
- "version": "14.0.2",
- "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.0.2.tgz",
- "integrity": "sha512-BJ2pVK4KhUyMcqjuKs9RijV5tatNzNa73e/32aBVE/ejYPe37iH+6vAu9WvqUkB5OAYgLHzbSvzHnorybJCm9g==",
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz",
+ "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==",
"dev": true,
"requires": {
"postcss-value-parser": "^4.0.0",
@@ -11471,6 +11489,12 @@
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true
},
+ "style-dictionary-design-tokens-example": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/style-dictionary-design-tokens-example/-/style-dictionary-design-tokens-example-1.0.0.tgz",
+ "integrity": "sha512-dUjgpSbx4uOAj3qZ1Tja3Vur3w+/UbVlT7JW2aAH3U7w8Hqm7nKwE0dCVU8O94sSFCs0jsdfPlvcYsEIIzLtvA==",
+ "dev": true
+ },
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
diff --git a/plugins/postcss-design-tokens/.tape.mjs b/plugins/postcss-design-tokens/.tape.mjs
index 6fd977c66..e18f44cb5 100644
--- a/plugins/postcss-design-tokens/.tape.mjs
+++ b/plugins/postcss-design-tokens/.tape.mjs
@@ -1,103 +1,26 @@
import postcssTape from '../../packages/postcss-tape/dist/index.mjs';
import plugin from '@csstools/postcss-design-tokens';
+import postcssImport from 'postcss-import';
postcssTape(plugin)({
basic: {
message: "supports basic usage",
- options: {
- requiresDesignTokens: {
- properties: [
- 'color'
- ]
- },
- designTokens: {
- selectors: [
- {
- name: 'button',
- value: ':is(.button, button[type="submit"])',
- deprecated: false,
- }
- ],
- atSupports: [
- {
- name: 'cover',
- value: '(object-fit: cover)',
- deprecated: false,
- }
- ],
- atMedia: [
- {
- name: 'medium',
- value: '(min-width: 768px)',
- deprecated: false
- }
- ],
- values: [
- {
- name: 'color',
- value: '#f00',
- deprecated: false,
- allowedProperties: [],
- blockedProperties: [],
- },
- {
- name: 'red-components',
- value: '255, 0, 0',
- deprecated: false,
- allowedProperties: [],
- blockedProperties: [],
- }
- ]
- }
- }
+ options: {},
+ plugins: [
+ postcssImport(),
+ plugin()
+ ]
},
'examples/example': {
message: 'minimal example',
+ options: {},
+ },
+ 'examples/example:dark': {
+ message: 'minimal example with dark theme',
options: {
- requiresDesignTokens: {
- properties: [
- 'color'
- ]
- },
- designTokens: {
- selectors: [
- {
- name: 'button',
- value: ':is(.button, button[type="submit"])',
- deprecated: false,
- }
- ],
- atSupports: [
- {
- name: 'cover',
- value: '(object-fit: cover)',
- deprecated: false,
- }
- ],
- atMedia: [
- {
- name: 'medium',
- value: '(min-width: 768px)',
- deprecated: false
- }
- ],
- values: [
- {
- name: 'color',
- value: '#f00',
- deprecated: false,
- allowedProperties: [],
- blockedProperties: [],
- },
- {
- name: 'red-components',
- value: '255, 0, 0',
- deprecated: false,
- allowedProperties: [],
- blockedProperties: [],
- }
- ]
- }
- }
+ variants: [
+ 'dark'
+ ]
+ },
},
});
diff --git a/plugins/postcss-design-tokens/INSTALL.md b/plugins/postcss-design-tokens/INSTALL.md
index 5a59c270a..a6635e80b 100644
--- a/plugins/postcss-design-tokens/INSTALL.md
+++ b/plugins/postcss-design-tokens/INSTALL.md
@@ -17,10 +17,10 @@ Use it as a [PostCSS] plugin:
```js
const postcss = require('postcss');
-const postcssBasePlugin = require('@csstools/postcss-design-tokens');
+const postcssDesignTokens = require('@csstools/postcss-design-tokens');
postcss([
- postcssBasePlugin(/* pluginOptions */)
+ postcssDesignTokens(/* pluginOptions */)
]).process(YOUR_CSS /*, processOptions */);
```
@@ -35,11 +35,11 @@ npm install postcss-cli @csstools/postcss-design-tokens --save-dev
Use [PostCSS Design Tokens] in your `postcss.config.js` configuration file:
```js
-const postcssBasePlugin = require('@csstools/postcss-design-tokens');
+const postcssDesignTokens = require('@csstools/postcss-design-tokens');
module.exports = {
plugins: [
- postcssBasePlugin(/* pluginOptions */)
+ postcssDesignTokens(/* pluginOptions */)
]
}
```
@@ -103,11 +103,11 @@ Use [React App Rewire PostCSS] and [PostCSS Design Tokens] in your
```js
const reactAppRewirePostcss = require('react-app-rewire-postcss');
-const postcssBasePlugin = require('@csstools/postcss-design-tokens');
+const postcssDesignTokens = require('@csstools/postcss-design-tokens');
module.exports = config => reactAppRewirePostcss(config, {
plugins: () => [
- postcssBasePlugin(/* pluginOptions */)
+ postcssDesignTokens(/* pluginOptions */)
]
});
```
@@ -124,11 +124,11 @@ Use [PostCSS Design Tokens] in your Gulpfile:
```js
const postcss = require('gulp-postcss');
-const postcssBasePlugin = require('@csstools/postcss-design-tokens');
+const postcssDesignTokens = require('@csstools/postcss-design-tokens');
gulp.task('css', function () {
var plugins = [
- postcssBasePlugin(/* pluginOptions */)
+ postcssDesignTokens(/* pluginOptions */)
];
return gulp.src('./src/*.css')
@@ -148,7 +148,7 @@ npm install grunt-postcss @csstools/postcss-design-tokens --save-dev
Use [PostCSS Design Tokens] in your Gruntfile:
```js
-const postcssBasePlugin = require('@csstools/postcss-design-tokens');
+const postcssDesignTokens = require('@csstools/postcss-design-tokens');
grunt.loadNpmTasks('grunt-postcss');
@@ -156,7 +156,7 @@ grunt.initConfig({
postcss: {
options: {
processors: [
- postcssBasePlugin(/* pluginOptions */)
+ postcssDesignTokens(/* pluginOptions */)
]
},
dist: {
diff --git a/plugins/postcss-design-tokens/README.md b/plugins/postcss-design-tokens/README.md
index 92fa6dd7b..ff96f55b8 100644
--- a/plugins/postcss-design-tokens/README.md
+++ b/plugins/postcss-design-tokens/README.md
@@ -7,23 +7,30 @@
[PostCSS Design Tokens] lets easily create new plugins following some [CSS Specification].
-```pcss
-.foo {
- color: red;
+```json
+{
+ "color": {
+ "background": {
+ "primary": { "value": "#fff" }
+ }
+ }
}
+```
+
+```pcss
+@design-tokens url('./tokens-light.json') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens-dark.json') variant('dark') vendor('style-dictionary') version('3');
-.baz {
- color: green;
+.foo {
+ color: design-token('color.background.primary');
}
/* becomes */
-.foo {
- color: blue;
-}
+@design-tokens url('./tokens-dark.json') variant('dark') vendor('style-dictionary') version('3');
-.baz {
- color: green;
+.foo {
+ color: #fff;
}
```
@@ -39,10 +46,10 @@ Use it as a [PostCSS] plugin:
```js
const postcss = require('postcss');
-const postcssBasePlugin = require('@csstools/postcss-design-tokens');
+const postcssDesignTokens = require('@csstools/postcss-design-tokens');
postcss([
- postcssBasePlugin(/* pluginOptions */)
+ postcssDesignTokens(/* pluginOptions */)
]).process(YOUR_CSS /*, processOptions */);
```
@@ -54,33 +61,39 @@ instructions for:
## Options
-### preserve
+### variants
-The `preserve` option determines whether the original notation
-is preserved. By default, it is not preserved.
+The `variants` option determines which design tokens are used.
+This allows you to generate multiple themed stylesheets.
```js
-postcssBasePlugin({ preserve: true })
+postcssDesignTokens({ variants: ['dark'] })
```
-```pcss
-.foo {
- color: red;
+```json
+{
+ "color": {
+ "background": {
+ "primary": { "value": "#000" }
+ }
+ }
}
+```
+
+```pcss
+@design-tokens url('./tokens-light.json') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens-dark.json') variant('dark') vendor('style-dictionary') version('3');
-.baz {
- color: green;
+.foo {
+ color: design-token('color.background.primary');
}
/* becomes */
-.foo {
- color: blue;
- color: red;
-}
+@design-tokens url('./tokens-light.json') vendor('style-dictionary') version('3');
-.baz {
- color: green;
+.foo {
+ color: #000;
}
```
diff --git a/plugins/postcss-design-tokens/docs/README.md b/plugins/postcss-design-tokens/docs/README.md
index 3bc455892..7cbf40f26 100644
--- a/plugins/postcss-design-tokens/docs/README.md
+++ b/plugins/postcss-design-tokens/docs/README.md
@@ -16,6 +16,10 @@
[] lets easily create new plugins following some [CSS Specification].
+```json
+
+```
+
```pcss
@@ -30,13 +34,17 @@
## Options
-### preserve
+### variants
-The `preserve` option determines whether the original notation
-is preserved. By default, it is not preserved.
+The `variants` option determines which design tokens are used.
+This allows you to generate multiple themed stylesheets.
```js
-({ preserve: true })
+({ variants: ['dark'] })
+```
+
+```json
+
```
```pcss
@@ -44,7 +52,7 @@ is preserved. By default, it is not preserved.
/* becomes */
-
+
```
diff --git a/plugins/postcss-design-tokens/package.json b/plugins/postcss-design-tokens/package.json
index 0341324ed..33698da03 100644
--- a/plugins/postcss-design-tokens/package.json
+++ b/plugins/postcss-design-tokens/package.json
@@ -29,6 +29,10 @@
"peerDependencies": {
"postcss": "^8.3"
},
+ "devDependencies": {
+ "postcss-import": "^14.1.0",
+ "style-dictionary-design-tokens-example": "^1.0.0"
+ },
"scripts": {
"build": "rollup -c ../../rollup/default.js",
"clean": "node -e \"fs.rmSync('./dist', { recursive: true, force: true });\"",
diff --git a/plugins/postcss-design-tokens/src/data-formats/base/token.ts b/plugins/postcss-design-tokens/src/data-formats/base/token.ts
new file mode 100644
index 000000000..ac789984f
--- /dev/null
+++ b/plugins/postcss-design-tokens/src/data-formats/base/token.ts
@@ -0,0 +1,3 @@
+export interface Token {
+ cssValue(): string
+}
diff --git a/plugins/postcss-design-tokens/src/data-formats/design-tokens-specification/type.ts b/plugins/postcss-design-tokens/src/data-formats/design-tokens-specification/type.ts
new file mode 100644
index 000000000..7b9637ce0
--- /dev/null
+++ b/plugins/postcss-design-tokens/src/data-formats/design-tokens-specification/type.ts
@@ -0,0 +1,14 @@
+export enum TokenType {
+ String = 'string',
+ Ident = 'ident',
+ Number = 'number',
+ Boolean = 'boolean',
+ Color = 'color',
+ Dimension = 'dimension',
+ FontFamily = 'fontFamily',
+ FontWeight = 'fontWeight',
+ FontStyle = 'fontStyle',
+ Duration = 'duration',
+ CubicBezier = 'cubicBezier',
+ URI = 'uri',
+}
diff --git a/plugins/postcss-design-tokens/src/data-formats/design-tokens-specification/value.ts b/plugins/postcss-design-tokens/src/data-formats/design-tokens-specification/value.ts
new file mode 100644
index 000000000..76fa79902
--- /dev/null
+++ b/plugins/postcss-design-tokens/src/data-formats/design-tokens-specification/value.ts
@@ -0,0 +1,86 @@
+import { TokenType } from './type';
+
+export type TokenValue = TokenString | TokenIdent;
+
+export type TokenBaseValue = {
+ '$type': T;
+ '$name'?: string;
+ '$description'?: string;
+ '$deprecated'?: boolean;
+}
+
+export interface TokenString extends TokenBaseValue {
+ '$value': string;
+}
+
+export interface TokenIdent extends TokenBaseValue {
+ '$value': string;
+}
+
+export interface TokenNumber extends TokenBaseValue {
+ '$value': number;
+}
+
+export interface TokenBoolean extends TokenBaseValue {
+ '$value': boolean;
+}
+
+export interface TokenColor extends TokenBaseValue {
+ '$value': {
+ colorSpace: 'srgb' | 'srgb-linear' | 'a98-rgb' | 'prophoto-rgb' | 'display-p3' | 'rec2020' | 'xyz-d50' | 'xyz-d65' | 'xyz'
+ channels: Array
+ alpha: number
+ };
+}
+
+export interface TokenDimension extends TokenBaseValue {
+ '$value': {
+ unit: string
+ value: number
+ };
+}
+
+export interface TokenFontFamily extends TokenBaseValue {
+ '$value': Array;
+}
+
+export interface TokenFontWeight extends TokenBaseValue {
+ '$value': number |
+ 'thin' |
+ 'hairline' |
+ 'extra-light' |
+ 'ultra-light' |
+ 'light' |
+ 'normal' |
+ 'regular' |
+ 'book' |
+ 'medium' |
+ 'semi-bold' |
+ 'demi-bold' |
+ 'bold' |
+ 'extra-bold' |
+ 'ultra-bold' |
+ 'black' |
+ 'heavy' |
+ 'extra-black' |
+ 'ultra-black';
+}
+
+export interface TokenFontStyle extends TokenBaseValue {
+ '$value': [TokenIdent, TokenDimension?];
+}
+
+export interface TokenDuration extends TokenBaseValue {
+ '$value': {
+ unit: string
+ value: number
+ };
+}
+
+export interface TokenCubicBezier extends TokenBaseValue {
+ '$value': Array;
+}
+
+export interface TokenURI extends TokenBaseValue {
+ '$value': string;
+}
diff --git a/plugins/postcss-design-tokens/src/data-formats/parse-import.ts b/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
new file mode 100644
index 000000000..756453c8d
--- /dev/null
+++ b/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
@@ -0,0 +1,79 @@
+import valueParser from 'postcss-value-parser';
+import { Token } from './base/token';
+import { extractStyleDictionaryTokens, latestStyleDictionaryVersion } from './style-dictionary/style-dictionary';
+import path from 'path';
+import { promises as fsp } from 'fs';
+
+function parseImport(statement: string): { filePath: string, vendor: string, version: string, variant: string } {
+ const importAST = valueParser(statement);
+
+ const result = {
+ filePath: '',
+ vendor: 'standard',
+ version: '',
+ variant: 'default',
+ };
+
+ importAST.walk((node) => {
+ if (node.type === 'function' && node.value === 'url') {
+ result.filePath = node.nodes[0].value;
+ }
+
+ if (node.type === 'function' && node.value === 'vendor') {
+ result.vendor = node.nodes[0].value;
+ }
+
+ if (node.type === 'function' && node.value === 'version') {
+ result.version = node.nodes[0].value;
+ }
+
+ if (node.type === 'function' && node.value === 'variant') {
+ result.variant = node.nodes[0].value;
+ }
+ });
+
+ if (!result.variant) {
+ result.variant = 'default';
+ }
+
+ if (!result.version) {
+ switch (result.vendor) {
+ case 'standard':
+ result.version = '0.0.1';
+ break;
+
+ case 'style-dictionary':
+ result.version = latestStyleDictionaryVersion;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return result;
+}
+
+export async function tokensFromImport(currentVariants: Array, sourceFilePath: string, statement: string): Promise<{ filePath: string, tokens: Map }|false> {
+ const { filePath, vendor, version, variant } = parseImport(statement);
+ if (!currentVariants.includes(variant)) {
+ return false;
+ }
+
+ const resolvedPath = path.resolve(path.dirname(sourceFilePath), filePath);
+ const fileContents = await fsp.readFile(resolvedPath, 'utf8');
+ const tokenContents = JSON.parse(fileContents);
+
+ switch (vendor) {
+ case 'style-dictionary':
+ return {
+ filePath: path.resolve(filePath),
+ tokens: extractStyleDictionaryTokens(version, tokenContents, resolvedPath),
+ };
+
+ default:
+ break;
+ }
+
+ throw new Error('Unsupported vendor: ' + vendor);
+}
diff --git a/plugins/postcss-design-tokens/src/data-formats/style-dictionary/style-dictionary.ts b/plugins/postcss-design-tokens/src/data-formats/style-dictionary/style-dictionary.ts
new file mode 100644
index 000000000..a2b588294
--- /dev/null
+++ b/plugins/postcss-design-tokens/src/data-formats/style-dictionary/style-dictionary.ts
@@ -0,0 +1,13 @@
+
+import { Token } from '../base/token';
+import { extractStyleDictionaryV3Tokens, StyleDictionaryV3TokenGroup } from './v3/group';
+
+export const latestStyleDictionaryVersion = '3';
+
+export function extractStyleDictionaryTokens(version: string, node: unknown, filePath: string): Map {
+ if (version === '3') {
+ return extractStyleDictionaryV3Tokens(node as StyleDictionaryV3TokenGroup, filePath);
+ }
+
+ throw new Error('Unsupported version: ' + version);
+}
diff --git a/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/group.ts b/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/group.ts
new file mode 100644
index 000000000..2115706ae
--- /dev/null
+++ b/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/group.ts
@@ -0,0 +1,37 @@
+import { Token } from '../../base/token';
+import { extractStyleDictionaryV3Token, StyleDictionaryV3TokenValue } from './value';
+
+export type StyleDictionaryV3TokenGroup = {
+ [key: string]: StyleDictionaryV3TokenGroup | StyleDictionaryV3TokenValue;
+}
+
+function extractTokens(node: StyleDictionaryV3TokenGroup, path: Array, filePath: string): Map {
+ const result: Map = new Map();
+ for (const key in node) {
+ if (Object.hasOwnProperty.call(node, key)) {
+ const child = Object(node[key]);
+ if (!child) {
+ throw new Error('Parsing error');
+ }
+
+ if (typeof child['value'] !== 'undefined') {
+ const token = extractStyleDictionaryV3Token(child, key, path, filePath);
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ result.set(token.metadata!.path.join('.'), token );
+ continue;
+ }
+
+ for (const [tokenPath, token] of extractTokens(child, [...path, key], filePath).entries()) {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ result.set(tokenPath, token);
+ continue;
+ }
+ }
+ }
+
+ return result;
+}
+
+export function extractStyleDictionaryV3Tokens(node: StyleDictionaryV3TokenGroup, filePath: string): Map {
+ return extractTokens(node, [], filePath);
+}
diff --git a/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/value.ts b/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/value.ts
new file mode 100644
index 000000000..8b96fbefe
--- /dev/null
+++ b/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/value.ts
@@ -0,0 +1,48 @@
+export type StyleDictionaryV3TokenValue = {
+ cssValue(): string
+ // The value of the design token. This can be any type of data, a hex string, an integer, a file path to a file, even an object or array.
+ value: unknown
+ // Usually the name for a design token is generated with a name transform, but you can write your own if you choose. By default Style Dictionary will add a default name which is the key of the design token object.
+ name?: string
+ // The comment attribute will show up in a code comment in output files if the format supports it.
+ comment?: string
+ // This is used in formats that support override-able or themeable values like the !default flag in Sass.
+ themeable?: boolean
+ // Extra information about the design token you want to include. Attribute transforms will modify this object so be careful
+ attributes?: unknown
+
+ // Set by the token parser
+ metadata?: {
+ // A default name of the design token that is set to the key of the design token. This is only added if you do not provide one.
+ name?: string
+ // The object path of the design token.
+ path: Array
+ // The file path of the file the token is defined in. This file path is derived from the source or include file path arrays defined in the configuration.
+ filePath: string
+ // If the token is from a file defined in the source array as opposed to include in the configuration.
+ isSource: boolean
+ }
+}
+
+export function extractStyleDictionaryV3Token(node: unknown, key: string, path: Array, filePath: string): StyleDictionaryV3TokenValue {
+ if (typeof node['value'] === 'undefined') {
+ throw new Error('Token value is undefined : ' + path.join('.'));
+ }
+
+ const value = node['value'] ?? undefined;
+
+ return {
+ value: value,
+ cssValue: () => {
+ return value ?? '';
+ },
+ name: node['name'] ?? key,
+ comment: node['comment'] ?? undefined,
+ metadata: {
+ name: node['name'] ? key : undefined,
+ path: [...path, key],
+ filePath: filePath,
+ isSource: true,
+ },
+ };
+}
diff --git a/plugins/postcss-design-tokens/src/data-formats/token.ts b/plugins/postcss-design-tokens/src/data-formats/token.ts
new file mode 100644
index 000000000..704cb796c
--- /dev/null
+++ b/plugins/postcss-design-tokens/src/data-formats/token.ts
@@ -0,0 +1,9 @@
+import { Token } from './base/token';
+
+export function mergeTokens(a: Map, b: Map): Map {
+ const result = new Map(a);
+ for (const [key, value] of b) {
+ result.set(key, value);
+ }
+ return result;
+}
diff --git a/plugins/postcss-design-tokens/src/index.ts b/plugins/postcss-design-tokens/src/index.ts
index d2f6e787b..e2d7aabbf 100644
--- a/plugins/postcss-design-tokens/src/index.ts
+++ b/plugins/postcss-design-tokens/src/index.ts
@@ -1,65 +1,52 @@
import type { PluginCreator } from 'postcss';
-import { AtMediaOptions, AtSupportsOptions, SelectorOptions, validateOptions, ValueOptions } from './options';
-import { onCSSValue, onCSSValueRequiredDesignToken, onCSSValueUnknownDesignToken } from './values';
+import { Token } from './data-formats/base/token';
+import { tokensFromImport } from './data-formats/parse-import';
+import { mergeTokens } from './data-formats/token';
+import { onCSSValue } from './values';
type pluginOptions = {
- requiresDesignTokens?: {
- properties: Array,
- },
- designTokens?: {
- atMedia?: Array,
- atSupports?: Array,
- selectors?: Array,
- values?: Array
- }
-};
+ variants?: Array
+}
const creator: PluginCreator = (opts?: pluginOptions) => {
- const config = validateOptions(opts);
+ const variants = opts?.variants ?? [];
+ if (variants.length === 0) {
+ variants.push('default');
+ }
return {
postcssPlugin: 'postcss-design-tokens',
prepare() {
- return {
- Once(root, { result }) {
+ let tokens = new Map();
- if (config.requiresDesignTokens.properties.size > 0) {
- // Emit warnings for properties that use custom values when not allowed.
-
- root.walkDecls(decl => {
- if (config.requiresDesignTokens.properties.has(decl.prop)) {
- if (decl.value.indexOf('design-token') === -1) {
- decl.warn(result, `"${decl.prop}" must always use design tokens.`);
- return;
- }
-
- const modifiedValue = onCSSValueRequiredDesignToken(config, result, decl);
- if (modifiedValue === false || modifiedValue === decl.value) {
- decl.warn(result, `"${decl.prop}" must always use design tokens.`);
- return;
- }
-
- decl.value = modifiedValue;
- }
- });
- }
- },
- OnceExit(root, { result }) {
- // Emit warnings if design tokens remain after processing.
- root.walkDecls(decl => {
- if (decl.value.indexOf('design-token') === -1) {
+ return {
+ AtRule: {
+ 'design-tokens': async (atRule, { result }) => {
+ if (!atRule?.source?.input?.file) {
return;
}
+ const importResult = await tokensFromImport(variants, atRule.source.input.file, atRule.params);
+ if (!importResult) {
+ atRule.remove();
+ return;
+ }
+
+ result.messages.push({
+ type: 'dependency',
+ plugin: 'postcss-design-tokens',
+ file: importResult.filePath,
+ parent: atRule.source.input.file,
+ });
- onCSSValueUnknownDesignToken(config, result, decl);
- });
+ tokens = mergeTokens(tokens, importResult.tokens);
+ },
},
Declaration(decl, { result }) {
if (decl.value.indexOf('design-token') === -1) {
return;
}
- const modifiedValue = onCSSValue(config, result, decl);
+ const modifiedValue = onCSSValue(tokens, result, decl);
if (modifiedValue === decl.value) {
return;
}
diff --git a/plugins/postcss-design-tokens/src/options.ts b/plugins/postcss-design-tokens/src/options.ts
deleted file mode 100644
index d4a0d6388..000000000
--- a/plugins/postcss-design-tokens/src/options.ts
+++ /dev/null
@@ -1,145 +0,0 @@
-// Public API vs. Internal usage.
-// The public API has a lot of optional parameters and simpler typing like "Array".
-// This is compatible with JSON.
-//
-// The internal usage typings do not have any optional parameters and use more powerful types like "Map" and "Set".
-
-// Public API
-type TokenOptions = {
- name: string,
- value: string,
- deprecated?: boolean,
-}
-
-export type AtMediaOptions = TokenOptions;
-export type AtSupportsOptions = TokenOptions;
-export type SelectorOptions = TokenOptions;
-
-export type ValueOptions = {
- name: string,
- value: string,
- deprecated?: boolean,
- allowedProperties?: Array,
- blockedProperties?: Array,
-}
-
-// Internal usage
-type TokenConfig = {
- name: string,
- value: string,
- deprecated: boolean,
-}
-
-export type AtMediaConfig = TokenConfig;
-export type AtSupportsConfig = TokenConfig;
-export type SelectorConfig = TokenConfig;
-
-export type ValueConfig = {
- name: string,
- value: string,
- deprecated: boolean,
- allowedProperties: Set,
- blockedProperties: Set,
-}
-
-function validateSubOptions(rawSubOptions: unknown): { name: string, value: string, deprecated: boolean } {
- const subOptions = Object(rawSubOptions);
- return {
- name: (typeof subOptions.name === 'string') ? subOptions.name : '',
- value: (typeof subOptions.value === 'string') ? subOptions.value : '',
- deprecated: (typeof subOptions.deprecated === 'boolean') ? subOptions.deprecated : false,
- };
-}
-
-export type Config = {
- requiresDesignTokens: {
- properties: Set,
- },
- designTokens: {
- atMedia: Map,
- atSupports: Map,
- selectors: Map,
- values: Map
- }
-};
-
-export function validateOptions(rawOptions: unknown) {
- const config: Config = {
- requiresDesignTokens: {
- properties: new Set(),
- },
- designTokens: {
- atMedia: new Map(),
- atSupports: new Map(),
- selectors: new Map(),
- values: new Map(),
- },
- };
-
- const requiresDesignTokens = Object(rawOptions).requiresDesignTokens;
- if (requiresDesignTokens && Array.isArray(Object(requiresDesignTokens).properties)) {
- requiresDesignTokens.properties.forEach((x) => {
- if (typeof x === 'string') {
- config.requiresDesignTokens.properties.add(x);
- }
- });
- }
-
- const designTokens = Object(rawOptions).designTokens;
- if (designTokens) {
- if (Array.isArray(Object(designTokens).atMedia)) {
- designTokens.atMedia.map((x) => {
- return validateSubOptions(x);
- }).filter((x: TokenConfig) => {
- return x.name !== '' && x.value !== '';
- }).forEach((x: TokenConfig) => {
- config.designTokens.atMedia.set(x.name, x);
- });
- }
-
- if (Array.isArray(Object(designTokens).atSupports)) {
- designTokens.atSupports.map((x) => {
- return validateSubOptions(x);
- }).filter((x: TokenConfig) => {
- return x.name !== '' && x.value !== '';
- }).forEach((x: TokenConfig) => {
- config.designTokens.atSupports.set(x.name, x);
- });
- }
-
- if (Array.isArray(Object(designTokens).selectors)) {
- designTokens.selectors.map((x) => {
- return validateSubOptions(x);
- }).filter((x: TokenConfig) => {
- return x.name !== '' && x.value !== '';
- }).forEach((x: TokenConfig) => {
- config.designTokens.selectors.set(x.name, x);
- });
- }
-
- if (Array.isArray(Object(designTokens).values)) {
- designTokens.values.map((x) => {
- const allowedProperties : Array = (Array.isArray(Object(x).allowedProperties)) ? x.allowedProperties.filter((y) => {
- return typeof y === 'string';
- }) : [];
- const blockedProperties: Array = (Array.isArray(Object(x).blockedProperties)) ? x.blockedProperties.filter((y) => {
- return typeof y === 'string';
- }) : [];
-
- const xx: ValueConfig = {
- ...validateSubOptions(x),
- allowedProperties: new Set(allowedProperties),
- blockedProperties: new Set(blockedProperties),
- };
-
- return xx;
- }).filter((x: ValueConfig) => {
- return x.name !== '' && x.value !== '';
- }).forEach((x: ValueConfig) => {
- config.designTokens.values.set(x.name, x);
- });
- }
- }
-
- return config;
-}
diff --git a/plugins/postcss-design-tokens/src/values.ts b/plugins/postcss-design-tokens/src/values.ts
index 829f5e1de..e1db3194d 100644
--- a/plugins/postcss-design-tokens/src/values.ts
+++ b/plugins/postcss-design-tokens/src/values.ts
@@ -1,9 +1,9 @@
import type { Declaration, Result } from 'postcss';
-import valuesParser from 'postcss-value-parser';
-import { Config } from './options';
+import valueParser from 'postcss-value-parser';
+import { Token } from './data-formats/base/token';
-export function onCSSValue(config: Config, result: Result, decl: Declaration) {
- const valueAST = valuesParser(decl.value);
+export function onCSSValue(tokens: Map, result: Result, decl: Declaration) {
+ const valueAST = valueParser(decl.value);
valueAST.walk(node => {
if (node.type !== 'function' || node.value !== 'design-token') {
@@ -11,113 +11,24 @@ export function onCSSValue(config: Config, result: Result, decl: Declaration) {
}
if (!node.nodes || node.nodes.length !== 1) {
- decl.warn(result, 'Expected a single keyword for the design-token function.');
+ decl.warn(result, 'Expected a single string literal for the design-token function.');
return;
}
- if (node.nodes[0].type !== 'word') {
- decl.warn(result, 'Expected a single keyword for the design-token function.');
+ if (node.nodes[0].type !== 'string') {
+ decl.warn(result, 'Expected a single string literal for the design-token function.');
return;
}
- const replacement = config.designTokens.values.get(node.nodes[0].value);
+ const replacement = tokens.get(node.nodes[0].value);
if (!replacement) {
+ decl.warn(result, `design-token: "${node.nodes[0].value}" is not configured.`);
return;
}
- if (replacement.deprecated) {
- decl.warn(result, `design-token: "${replacement.name}" is deprecated.`);
- }
-
- if (replacement.allowedProperties.size && !replacement.allowedProperties.has(decl.prop)) {
- decl.warn(result, `design-token: "${replacement.name}" is not allowed for "${decl.prop}".`);
- return;
- }
-
- if (replacement.blockedProperties.size && replacement.blockedProperties.has(decl.prop)) {
- decl.warn(result, `design-token: "${replacement.name}" is not allowed for "${decl.prop}".`);
- return;
- }
-
- node.value = replacement.value;
+ node.value = replacement.cssValue();
node.nodes = undefined;
});
return String(valueAST);
}
-
-export function onCSSValueRequiredDesignToken(config: Config, result: Result, decl: Declaration) {
- const valueAST = valuesParser(decl.value);
-
- let hasNonDesignTokens = false;
-
- valueAST.nodes.forEach((node) => {
- // Allowed, even when the property requires design tokens.
- if (node.type === 'comment' || node.type === 'space' || node.type === 'div') {
- return;
- }
-
- if (node.type !== 'function' || node.value !== 'design-token') {
- hasNonDesignTokens = true;
- return;
- }
-
- if (!node.nodes || node.nodes.length !== 1) {
- decl.warn(result, 'Expected a single keyword for the design-token function.');
- return false;
- }
-
- if (node.nodes[0].type !== 'word') {
- decl.warn(result, 'Expected a single keyword for the design-token function.');
- return false;
- }
-
- const replacement = config.designTokens.values.get(node.nodes[0].value);
- if (!replacement) {
- return;
- }
-
- if (replacement.deprecated) {
- decl.warn(result, `design-token: "${replacement.name}" is deprecated.`);
- }
-
- if (replacement.allowedProperties.size && !replacement.allowedProperties.has(decl.prop)) {
- decl.warn(result, `design-token: "${replacement.name}" is not allowed for "${decl.prop}".`);
- return false;
- }
-
- if (replacement.blockedProperties.size && replacement.blockedProperties.has(decl.prop)) {
- decl.warn(result, `design-token: "${replacement.name}" is not allowed for "${decl.prop}".`);
- return false;
- }
-
- node.value = replacement.value;
- node.nodes = undefined;
- });
-
- if (hasNonDesignTokens) {
- return false;
- }
-
- return String(valueAST);
-}
-
-export function onCSSValueUnknownDesignToken(config: Config, result: Result, decl: Declaration) {
- const valueAST = valuesParser(decl.value);
-
- valueAST.walk(node => {
- if (node.type !== 'function' || node.value !== 'design-token') {
- return;
- }
-
- if (!node.nodes || node.nodes.length !== 1) {
- return;
- }
-
- if (node.nodes[0].type !== 'word') {
- return;
- }
-
- decl.warn(result, `design-token: "${node.nodes[0].value}" is not configured.`);
- });
-}
diff --git a/plugins/postcss-design-tokens/test/basic.css b/plugins/postcss-design-tokens/test/basic.css
index 5cd4316d0..4d7979248 100644
--- a/plugins/postcss-design-tokens/test/basic.css
+++ b/plugins/postcss-design-tokens/test/basic.css
@@ -1,21 +1,8 @@
-.foo {
- color: design-token(color);
-}
-
-@media (design-token: medium) {
- .baz {
- color: design-token(color);
- background-color: rgb(design-token(red-components));
- }
-}
+@import url('./imported.css');
+@design-tokens url('./tokens.json') vendor('style-dictionary') version('3');
-@supports (design-token: cover) {
- .baz {
- color: design-token(color);
- object-fit: cover;
- }
-}
-
-:design-token(button) {
- color: design-token(color);
+.foo {
+ font-family: design-token('font.family.serif');
+ font-size: design-token('size.font.small');
+ color: design-token('color.font.base');
}
diff --git a/plugins/postcss-design-tokens/test/basic.expect.css b/plugins/postcss-design-tokens/test/basic.expect.css
index 924fcf346..d0e8338ea 100644
--- a/plugins/postcss-design-tokens/test/basic.expect.css
+++ b/plugins/postcss-design-tokens/test/basic.expect.css
@@ -1,21 +1,5 @@
.foo {
- color: #f00;
-}
-
-@media (design-token: medium) {
- .baz {
- color: #f00;
- background-color: rgb(255, 0, 0);
- }
-}
-
-@supports (design-token: cover) {
- .baz {
- color: #f00;
- object-fit: cover;
- }
-}
-
-:design-token(button) {
- color: #f00;
+ font-family: serif;
+ font-size: 10;
+ color: #111111;
}
diff --git a/plugins/postcss-design-tokens/test/examples/example.css b/plugins/postcss-design-tokens/test/examples/example.css
index 5cd4316d0..3cc76267a 100644
--- a/plugins/postcss-design-tokens/test/examples/example.css
+++ b/plugins/postcss-design-tokens/test/examples/example.css
@@ -1,21 +1,6 @@
-.foo {
- color: design-token(color);
-}
-
-@media (design-token: medium) {
- .baz {
- color: design-token(color);
- background-color: rgb(design-token(red-components));
- }
-}
+@design-tokens url('./tokens-light.json') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens-dark.json') variant('dark') vendor('style-dictionary') version('3');
-@supports (design-token: cover) {
- .baz {
- color: design-token(color);
- object-fit: cover;
- }
-}
-
-:design-token(button) {
- color: design-token(color);
+.foo {
+ color: design-token('color.background.primary');
}
diff --git a/plugins/postcss-design-tokens/test/examples/example.dark.expect.css b/plugins/postcss-design-tokens/test/examples/example.dark.expect.css
new file mode 100644
index 000000000..a88065a4d
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/examples/example.dark.expect.css
@@ -0,0 +1,5 @@
+@design-tokens url('./tokens-light.json') vendor('style-dictionary') version('3');
+
+.foo {
+ color: #000;
+}
diff --git a/plugins/postcss-design-tokens/test/examples/example.expect.css b/plugins/postcss-design-tokens/test/examples/example.expect.css
index 924fcf346..9d816767f 100644
--- a/plugins/postcss-design-tokens/test/examples/example.expect.css
+++ b/plugins/postcss-design-tokens/test/examples/example.expect.css
@@ -1,21 +1,5 @@
-.foo {
- color: #f00;
-}
-
-@media (design-token: medium) {
- .baz {
- color: #f00;
- background-color: rgb(255, 0, 0);
- }
-}
+@design-tokens url('./tokens-dark.json') variant('dark') vendor('style-dictionary') version('3');
-@supports (design-token: cover) {
- .baz {
- color: #f00;
- object-fit: cover;
- }
-}
-
-:design-token(button) {
- color: #f00;
+.foo {
+ color: #fff;
}
diff --git a/plugins/postcss-design-tokens/test/examples/tokens-dark.json b/plugins/postcss-design-tokens/test/examples/tokens-dark.json
new file mode 100644
index 000000000..09de442b7
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/examples/tokens-dark.json
@@ -0,0 +1,7 @@
+{
+ "color": {
+ "background": {
+ "primary": { "value": "#000" }
+ }
+ }
+}
diff --git a/plugins/postcss-design-tokens/test/examples/tokens-light.json b/plugins/postcss-design-tokens/test/examples/tokens-light.json
new file mode 100644
index 000000000..9776bbaed
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/examples/tokens-light.json
@@ -0,0 +1,7 @@
+{
+ "color": {
+ "background": {
+ "primary": { "value": "#fff" }
+ }
+ }
+}
diff --git a/plugins/postcss-design-tokens/test/imported.css b/plugins/postcss-design-tokens/test/imported.css
new file mode 100644
index 000000000..833f5e11c
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/imported.css
@@ -0,0 +1 @@
+@design-tokens url('../../../node_modules/style-dictionary-design-tokens-example/style-dictionary.tokens.json') vendor('style-dictionary') version('3');
diff --git a/plugins/postcss-design-tokens/test/tokens.json b/plugins/postcss-design-tokens/test/tokens.json
new file mode 100644
index 000000000..9ee085a9c
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/tokens.json
@@ -0,0 +1,7 @@
+{
+ "font": {
+ "family": {
+ "serif": { "value": "serif" }
+ }
+ }
+}
diff --git a/plugins/postcss-design-tokens/tsconfig.json b/plugins/postcss-design-tokens/tsconfig.json
index e0d06239c..e2c1924fe 100644
--- a/plugins/postcss-design-tokens/tsconfig.json
+++ b/plugins/postcss-design-tokens/tsconfig.json
@@ -2,7 +2,8 @@
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "dist",
- "declarationDir": "."
+ "declarationDir": ".",
+ "module": "es2020",
},
"include": ["./src/**/*"],
"exclude": ["dist"],
diff --git a/rollup/presets/package-typescript.js b/rollup/presets/package-typescript.js
index d765bf703..6e739ef65 100644
--- a/rollup/presets/package-typescript.js
+++ b/rollup/presets/package-typescript.js
@@ -21,7 +21,7 @@ export function packageTypescript() {
extensions: ['.js', '.ts'],
presets: packageBabelPreset,
}),
- terser(),
+ // terser(),
],
},
];
From 8488665d370cfaf61e579c75d548d56611db2c1a Mon Sep 17 00:00:00 2001
From: romainmenke
Date: Sun, 27 Mar 2022 11:25:11 +0200
Subject: [PATCH 04/32] wip
---
plugins/postcss-design-tokens/README.md | 4 ++--
plugins/postcss-design-tokens/package.json | 7 +++----
plugins/postcss-design-tokens/test/basic.expect.css | 2 ++
.../test/examples/example.dark.expect.css | 2 +-
.../postcss-design-tokens/test/examples/example.expect.css | 2 +-
5 files changed, 9 insertions(+), 8 deletions(-)
diff --git a/plugins/postcss-design-tokens/README.md b/plugins/postcss-design-tokens/README.md
index ff96f55b8..8f6aae920 100644
--- a/plugins/postcss-design-tokens/README.md
+++ b/plugins/postcss-design-tokens/README.md
@@ -27,7 +27,7 @@
/* becomes */
-@design-tokens url('./tokens-dark.json') variant('dark') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens-light.json') vendor('style-dictionary') version('3');
.foo {
color: #fff;
@@ -90,7 +90,7 @@ postcssDesignTokens({ variants: ['dark'] })
/* becomes */
-@design-tokens url('./tokens-light.json') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens-dark.json') variant('dark') vendor('style-dictionary') version('3');
.foo {
color: #000;
diff --git a/plugins/postcss-design-tokens/package.json b/plugins/postcss-design-tokens/package.json
index 33698da03..a6b43019a 100644
--- a/plugins/postcss-design-tokens/package.json
+++ b/plugins/postcss-design-tokens/package.json
@@ -41,16 +41,17 @@
"lint:eslint": "eslint ./src --ext .js --ext .ts --ext .mjs --no-error-on-unmatched-pattern",
"lint:package-json": "node ../../.github/bin/format-package-json.mjs",
"prepublishOnly": "npm run clean && npm run build && npm run test",
- "stryker": "stryker run --logLevel error",
"test": "node .tape.mjs && npm run test:exports",
"test:exports": "node ./test/_import.mjs && node ./test/_require.cjs",
"test:rewrite-expects": "REWRITE_EXPECTS=true node .tape.mjs"
},
+ "homepage": "https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-design-tokens#readme",
"repository": {
"type": "git",
"url": "https://github.com/csstools/postcss-plugins.git",
"directory": "plugins/postcss-design-tokens"
},
+ "bugs": "https://github.com/csstools/postcss-plugins/issues",
"keywords": [
"postcss-plugin"
],
@@ -62,7 +63,5 @@
},
"volta": {
"extends": "../../package.json"
- },
- "homepage": "https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-design-tokens#readme",
- "bugs": "https://github.com/csstools/postcss-plugins/issues"
+ }
}
diff --git a/plugins/postcss-design-tokens/test/basic.expect.css b/plugins/postcss-design-tokens/test/basic.expect.css
index d0e8338ea..438e4934d 100644
--- a/plugins/postcss-design-tokens/test/basic.expect.css
+++ b/plugins/postcss-design-tokens/test/basic.expect.css
@@ -1,3 +1,5 @@
+@design-tokens url('../../../node_modules/style-dictionary-design-tokens-example/style-dictionary.tokens.json') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens.json') vendor('style-dictionary') version('3');
.foo {
font-family: serif;
font-size: 10;
diff --git a/plugins/postcss-design-tokens/test/examples/example.dark.expect.css b/plugins/postcss-design-tokens/test/examples/example.dark.expect.css
index a88065a4d..8fa22a271 100644
--- a/plugins/postcss-design-tokens/test/examples/example.dark.expect.css
+++ b/plugins/postcss-design-tokens/test/examples/example.dark.expect.css
@@ -1,4 +1,4 @@
-@design-tokens url('./tokens-light.json') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens-dark.json') variant('dark') vendor('style-dictionary') version('3');
.foo {
color: #000;
diff --git a/plugins/postcss-design-tokens/test/examples/example.expect.css b/plugins/postcss-design-tokens/test/examples/example.expect.css
index 9d816767f..5dc769526 100644
--- a/plugins/postcss-design-tokens/test/examples/example.expect.css
+++ b/plugins/postcss-design-tokens/test/examples/example.expect.css
@@ -1,4 +1,4 @@
-@design-tokens url('./tokens-dark.json') variant('dark') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens-light.json') vendor('style-dictionary') version('3');
.foo {
color: #fff;
From 7fdc4e56f625e32b7ab0353f400e0959485a81c1 Mon Sep 17 00:00:00 2001
From: romainmenke
Date: Sun, 27 Mar 2022 11:26:35 +0200
Subject: [PATCH 05/32] wip
---
plugins/postcss-design-tokens/README.md | 4 ----
plugins/postcss-design-tokens/src/index.ts | 2 +-
plugins/postcss-design-tokens/test/basic.expect.css | 2 --
.../test/examples/example.dark.expect.css | 2 --
.../postcss-design-tokens/test/examples/example.expect.css | 2 --
5 files changed, 1 insertion(+), 11 deletions(-)
diff --git a/plugins/postcss-design-tokens/README.md b/plugins/postcss-design-tokens/README.md
index 8f6aae920..19650e18f 100644
--- a/plugins/postcss-design-tokens/README.md
+++ b/plugins/postcss-design-tokens/README.md
@@ -27,8 +27,6 @@
/* becomes */
-@design-tokens url('./tokens-light.json') vendor('style-dictionary') version('3');
-
.foo {
color: #fff;
}
@@ -90,8 +88,6 @@ postcssDesignTokens({ variants: ['dark'] })
/* becomes */
-@design-tokens url('./tokens-dark.json') variant('dark') vendor('style-dictionary') version('3');
-
.foo {
color: #000;
}
diff --git a/plugins/postcss-design-tokens/src/index.ts b/plugins/postcss-design-tokens/src/index.ts
index e2d7aabbf..60eea2ce5 100644
--- a/plugins/postcss-design-tokens/src/index.ts
+++ b/plugins/postcss-design-tokens/src/index.ts
@@ -26,8 +26,8 @@ const creator: PluginCreator = (opts?: pluginOptions) => {
return;
}
const importResult = await tokensFromImport(variants, atRule.source.input.file, atRule.params);
+ atRule.remove();
if (!importResult) {
- atRule.remove();
return;
}
diff --git a/plugins/postcss-design-tokens/test/basic.expect.css b/plugins/postcss-design-tokens/test/basic.expect.css
index 438e4934d..d0e8338ea 100644
--- a/plugins/postcss-design-tokens/test/basic.expect.css
+++ b/plugins/postcss-design-tokens/test/basic.expect.css
@@ -1,5 +1,3 @@
-@design-tokens url('../../../node_modules/style-dictionary-design-tokens-example/style-dictionary.tokens.json') vendor('style-dictionary') version('3');
-@design-tokens url('./tokens.json') vendor('style-dictionary') version('3');
.foo {
font-family: serif;
font-size: 10;
diff --git a/plugins/postcss-design-tokens/test/examples/example.dark.expect.css b/plugins/postcss-design-tokens/test/examples/example.dark.expect.css
index 8fa22a271..cab059366 100644
--- a/plugins/postcss-design-tokens/test/examples/example.dark.expect.css
+++ b/plugins/postcss-design-tokens/test/examples/example.dark.expect.css
@@ -1,5 +1,3 @@
-@design-tokens url('./tokens-dark.json') variant('dark') vendor('style-dictionary') version('3');
-
.foo {
color: #000;
}
diff --git a/plugins/postcss-design-tokens/test/examples/example.expect.css b/plugins/postcss-design-tokens/test/examples/example.expect.css
index 5dc769526..33a418a59 100644
--- a/plugins/postcss-design-tokens/test/examples/example.expect.css
+++ b/plugins/postcss-design-tokens/test/examples/example.expect.css
@@ -1,5 +1,3 @@
-@design-tokens url('./tokens-light.json') vendor('style-dictionary') version('3');
-
.foo {
color: #fff;
}
From 51f193ba5162be2d7478d780d1c5322275bd220b Mon Sep 17 00:00:00 2001
From: romainmenke
Date: Sun, 27 Mar 2022 12:27:57 +0200
Subject: [PATCH 06/32] wip
---
plugins/postcss-design-tokens/README.md | 4 ++--
.../src/data-formats/parse-import.ts | 16 ++++++++--------
.../test/examples/example.css | 2 +-
3 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/plugins/postcss-design-tokens/README.md b/plugins/postcss-design-tokens/README.md
index 19650e18f..391f29bf0 100644
--- a/plugins/postcss-design-tokens/README.md
+++ b/plugins/postcss-design-tokens/README.md
@@ -19,7 +19,7 @@
```pcss
@design-tokens url('./tokens-light.json') vendor('style-dictionary') version('3');
-@design-tokens url('./tokens-dark.json') variant('dark') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens-dark.json') variants('dark') vendor('style-dictionary') version('3');
.foo {
color: design-token('color.background.primary');
@@ -80,7 +80,7 @@ postcssDesignTokens({ variants: ['dark'] })
```pcss
@design-tokens url('./tokens-light.json') vendor('style-dictionary') version('3');
-@design-tokens url('./tokens-dark.json') variant('dark') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens-dark.json') variants('dark') vendor('style-dictionary') version('3');
.foo {
color: design-token('color.background.primary');
diff --git a/plugins/postcss-design-tokens/src/data-formats/parse-import.ts b/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
index 756453c8d..507c6ec7b 100644
--- a/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
+++ b/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
@@ -4,14 +4,14 @@ import { extractStyleDictionaryTokens, latestStyleDictionaryVersion } from './st
import path from 'path';
import { promises as fsp } from 'fs';
-function parseImport(statement: string): { filePath: string, vendor: string, version: string, variant: string } {
+function parseImport(statement: string): { filePath: string, vendor: string, version: string, variants: Array } {
const importAST = valueParser(statement);
const result = {
filePath: '',
vendor: 'standard',
version: '',
- variant: 'default',
+ variants: ['default'],
};
importAST.walk((node) => {
@@ -27,13 +27,13 @@ function parseImport(statement: string): { filePath: string, vendor: string, ver
result.version = node.nodes[0].value;
}
- if (node.type === 'function' && node.value === 'variant') {
- result.variant = node.nodes[0].value;
+ if (node.type === 'function' && node.value === 'variants') {
+ result.variants = node.nodes.filter((child) => child.type === 'string').map((child) => child.value);
}
});
- if (!result.variant) {
- result.variant = 'default';
+ if (!result.variants.length) {
+ result.variants = ['default'];
}
if (!result.version) {
@@ -55,8 +55,8 @@ function parseImport(statement: string): { filePath: string, vendor: string, ver
}
export async function tokensFromImport(currentVariants: Array, sourceFilePath: string, statement: string): Promise<{ filePath: string, tokens: Map }|false> {
- const { filePath, vendor, version, variant } = parseImport(statement);
- if (!currentVariants.includes(variant)) {
+ const { filePath, vendor, version, variants } = parseImport(statement);
+ if (!currentVariants.every((variant) => variants.includes(variant))) {
return false;
}
diff --git a/plugins/postcss-design-tokens/test/examples/example.css b/plugins/postcss-design-tokens/test/examples/example.css
index 3cc76267a..28d4ecca4 100644
--- a/plugins/postcss-design-tokens/test/examples/example.css
+++ b/plugins/postcss-design-tokens/test/examples/example.css
@@ -1,5 +1,5 @@
@design-tokens url('./tokens-light.json') vendor('style-dictionary') version('3');
-@design-tokens url('./tokens-dark.json') variant('dark') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens-dark.json') variants('dark') vendor('style-dictionary') version('3');
.foo {
color: design-token('color.background.primary');
From f8bfa50b21f81f82ffabc37be4316df80aa017d6 Mon Sep 17 00:00:00 2001
From: romainmenke
Date: Sun, 27 Mar 2022 12:40:29 +0200
Subject: [PATCH 07/32] wip
---
plugins/postcss-design-tokens/.tape.mjs | 6 ++++++
.../src/data-formats/parse-import.ts | 2 +-
plugins/postcss-design-tokens/test/basic.css | 2 +-
.../test/{tokens.json => tokens/basic.json} | 0
.../test/tokens/color_dark_branded-blue.tokens.json | 5 +++++
.../test/tokens/color_dark_branded-green.tokens.json | 5 +++++
.../test/tokens/color_light_branded-blue.tokens.json | 5 +++++
.../tokens/color_light_branded-green.tokens.json | 5 +++++
.../test/tokens/size_desktop.tokens.json | 5 +++++
.../test/tokens/size_mobile.tokens.json | 5 +++++
.../test/tokens/size_tablet.tokens.json | 5 +++++
plugins/postcss-design-tokens/test/variants.css | 12 ++++++++++++
.../postcss-design-tokens/test/variants.expect.css | 4 ++++
13 files changed, 59 insertions(+), 2 deletions(-)
rename plugins/postcss-design-tokens/test/{tokens.json => tokens/basic.json} (100%)
create mode 100644 plugins/postcss-design-tokens/test/tokens/color_dark_branded-blue.tokens.json
create mode 100644 plugins/postcss-design-tokens/test/tokens/color_dark_branded-green.tokens.json
create mode 100644 plugins/postcss-design-tokens/test/tokens/color_light_branded-blue.tokens.json
create mode 100644 plugins/postcss-design-tokens/test/tokens/color_light_branded-green.tokens.json
create mode 100644 plugins/postcss-design-tokens/test/tokens/size_desktop.tokens.json
create mode 100644 plugins/postcss-design-tokens/test/tokens/size_mobile.tokens.json
create mode 100644 plugins/postcss-design-tokens/test/tokens/size_tablet.tokens.json
create mode 100644 plugins/postcss-design-tokens/test/variants.css
create mode 100644 plugins/postcss-design-tokens/test/variants.expect.css
diff --git a/plugins/postcss-design-tokens/.tape.mjs b/plugins/postcss-design-tokens/.tape.mjs
index e18f44cb5..0f15d5ffd 100644
--- a/plugins/postcss-design-tokens/.tape.mjs
+++ b/plugins/postcss-design-tokens/.tape.mjs
@@ -11,6 +11,12 @@ postcssTape(plugin)({
plugin()
]
},
+ 'variants': {
+ message: "supports basic usage",
+ options: {
+ variants: ['dark', 'tablet', 'branded-green']
+ }
+ },
'examples/example': {
message: 'minimal example',
options: {},
diff --git a/plugins/postcss-design-tokens/src/data-formats/parse-import.ts b/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
index 507c6ec7b..3eb049e1b 100644
--- a/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
+++ b/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
@@ -56,7 +56,7 @@ function parseImport(statement: string): { filePath: string, vendor: string, ver
export async function tokensFromImport(currentVariants: Array, sourceFilePath: string, statement: string): Promise<{ filePath: string, tokens: Map }|false> {
const { filePath, vendor, version, variants } = parseImport(statement);
- if (!currentVariants.every((variant) => variants.includes(variant))) {
+ if (!variants.every((variant) => currentVariants.includes(variant))) {
return false;
}
diff --git a/plugins/postcss-design-tokens/test/basic.css b/plugins/postcss-design-tokens/test/basic.css
index 4d7979248..0eb5dc7d2 100644
--- a/plugins/postcss-design-tokens/test/basic.css
+++ b/plugins/postcss-design-tokens/test/basic.css
@@ -1,5 +1,5 @@
@import url('./imported.css');
-@design-tokens url('./tokens.json') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens/basic.json') vendor('style-dictionary') version('3');
.foo {
font-family: design-token('font.family.serif');
diff --git a/plugins/postcss-design-tokens/test/tokens.json b/plugins/postcss-design-tokens/test/tokens/basic.json
similarity index 100%
rename from plugins/postcss-design-tokens/test/tokens.json
rename to plugins/postcss-design-tokens/test/tokens/basic.json
diff --git a/plugins/postcss-design-tokens/test/tokens/color_dark_branded-blue.tokens.json b/plugins/postcss-design-tokens/test/tokens/color_dark_branded-blue.tokens.json
new file mode 100644
index 000000000..8554a449d
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/tokens/color_dark_branded-blue.tokens.json
@@ -0,0 +1,5 @@
+{
+ "color": {
+ "value": "rgb(0, 0, 120)"
+ }
+}
diff --git a/plugins/postcss-design-tokens/test/tokens/color_dark_branded-green.tokens.json b/plugins/postcss-design-tokens/test/tokens/color_dark_branded-green.tokens.json
new file mode 100644
index 000000000..aac06ff82
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/tokens/color_dark_branded-green.tokens.json
@@ -0,0 +1,5 @@
+{
+ "color": {
+ "value": "rgb(0, 120, 0)"
+ }
+}
diff --git a/plugins/postcss-design-tokens/test/tokens/color_light_branded-blue.tokens.json b/plugins/postcss-design-tokens/test/tokens/color_light_branded-blue.tokens.json
new file mode 100644
index 000000000..8569a6d3a
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/tokens/color_light_branded-blue.tokens.json
@@ -0,0 +1,5 @@
+{
+ "color": {
+ "value": "rgb(50, 50, 250)"
+ }
+}
diff --git a/plugins/postcss-design-tokens/test/tokens/color_light_branded-green.tokens.json b/plugins/postcss-design-tokens/test/tokens/color_light_branded-green.tokens.json
new file mode 100644
index 000000000..3e9ac3d69
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/tokens/color_light_branded-green.tokens.json
@@ -0,0 +1,5 @@
+{
+ "color": {
+ "value": "rgb(50, 250, 50)"
+ }
+}
diff --git a/plugins/postcss-design-tokens/test/tokens/size_desktop.tokens.json b/plugins/postcss-design-tokens/test/tokens/size_desktop.tokens.json
new file mode 100644
index 000000000..0e36ff29e
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/tokens/size_desktop.tokens.json
@@ -0,0 +1,5 @@
+{
+ "size": {
+ "value": "20px"
+ }
+}
diff --git a/plugins/postcss-design-tokens/test/tokens/size_mobile.tokens.json b/plugins/postcss-design-tokens/test/tokens/size_mobile.tokens.json
new file mode 100644
index 000000000..06069df89
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/tokens/size_mobile.tokens.json
@@ -0,0 +1,5 @@
+{
+ "size": {
+ "value": "16px"
+ }
+}
diff --git a/plugins/postcss-design-tokens/test/tokens/size_tablet.tokens.json b/plugins/postcss-design-tokens/test/tokens/size_tablet.tokens.json
new file mode 100644
index 000000000..8102cac0c
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/tokens/size_tablet.tokens.json
@@ -0,0 +1,5 @@
+{
+ "size": {
+ "value": "18px"
+ }
+}
diff --git a/plugins/postcss-design-tokens/test/variants.css b/plugins/postcss-design-tokens/test/variants.css
new file mode 100644
index 000000000..6db1c1d4f
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/variants.css
@@ -0,0 +1,12 @@
+@design-tokens url('./tokens/color_dark_branded-blue.tokens.json') variants('dark', 'branded-blue') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens/color_light_branded-blue.tokens.json') variants('light', 'branded-blue') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens/color_dark_branded-green.tokens.json') variants('dark', 'branded-green') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens/color_light_branded-green.tokens.json') variants('light', 'branded-green') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens/size_mobile.tokens.json') variants('mobile') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens/size_tablet.tokens.json') variants('tablet') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens/size_desktop.tokens.json') variants('desktop') vendor('style-dictionary') version('3');
+
+.foo {
+ color: design-token('color');
+ font-size: design-token('size');
+}
diff --git a/plugins/postcss-design-tokens/test/variants.expect.css b/plugins/postcss-design-tokens/test/variants.expect.css
new file mode 100644
index 000000000..6b837bc2b
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/variants.expect.css
@@ -0,0 +1,4 @@
+.foo {
+ color: rgb(0, 120, 0);
+ font-size: 18px;
+}
From 797b01d6e1b0382a959174ca59fff54b9bf84a83 Mon Sep 17 00:00:00 2001
From: romainmenke
Date: Sun, 27 Mar 2022 12:55:02 +0200
Subject: [PATCH 08/32] wip
---
.../src/data-formats/parse-import.ts | 8 +++++++-
plugins/postcss-design-tokens/src/index.ts | 13 +++++++++++--
2 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/plugins/postcss-design-tokens/src/data-formats/parse-import.ts b/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
index 3eb049e1b..5ae1ae21c 100644
--- a/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
+++ b/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
@@ -54,13 +54,19 @@ function parseImport(statement: string): { filePath: string, vendor: string, ver
return result;
}
-export async function tokensFromImport(currentVariants: Array, sourceFilePath: string, statement: string): Promise<{ filePath: string, tokens: Map }|false> {
+export async function tokensFromImport(currentVariants: Array, sourceFilePath: string, statement: string, alreadyImported: Set): Promise<{ filePath: string, tokens: Map }|false> {
const { filePath, vendor, version, variants } = parseImport(statement);
if (!variants.every((variant) => currentVariants.includes(variant))) {
return false;
}
const resolvedPath = path.resolve(path.dirname(sourceFilePath), filePath);
+ if (alreadyImported.has(resolvedPath)) {
+ return false;
+ }
+
+ alreadyImported.add(resolvedPath);
+
const fileContents = await fsp.readFile(resolvedPath, 'utf8');
const tokenContents = JSON.parse(fileContents);
diff --git a/plugins/postcss-design-tokens/src/index.ts b/plugins/postcss-design-tokens/src/index.ts
index 60eea2ce5..f52cdce95 100644
--- a/plugins/postcss-design-tokens/src/index.ts
+++ b/plugins/postcss-design-tokens/src/index.ts
@@ -18,15 +18,24 @@ const creator: PluginCreator = (opts?: pluginOptions) => {
postcssPlugin: 'postcss-design-tokens',
prepare() {
let tokens = new Map();
+ let importedFiles = new Set();
return {
+ OnceExit() {
+ tokens = new Map();
+ importedFiles = new Set();
+ },
AtRule: {
'design-tokens': async (atRule, { result }) => {
if (!atRule?.source?.input?.file) {
return;
}
- const importResult = await tokensFromImport(variants, atRule.source.input.file, atRule.params);
+
+ const filePath = atRule.source.input.file;
+ const params = atRule.params;
atRule.remove();
+
+ const importResult = await tokensFromImport(variants, filePath, params, importedFiles);
if (!importResult) {
return;
}
@@ -35,7 +44,7 @@ const creator: PluginCreator = (opts?: pluginOptions) => {
type: 'dependency',
plugin: 'postcss-design-tokens',
file: importResult.filePath,
- parent: atRule.source.input.file,
+ parent: filePath,
});
tokens = mergeTokens(tokens, importResult.tokens);
From 81619416621efc5ea4007d2ea69049baceb2d10d Mon Sep 17 00:00:00 2001
From: romainmenke
Date: Sun, 27 Mar 2022 13:58:15 +0200
Subject: [PATCH 09/32] feedback
---
plugins/postcss-design-tokens/README.md | 8 ++--
.../src/data-formats/parse-import.ts | 38 +++++--------------
plugins/postcss-design-tokens/test/basic.css | 2 +-
.../test/examples/example.css | 4 +-
.../postcss-design-tokens/test/imported.css | 2 +-
.../postcss-design-tokens/test/variants.css | 14 +++----
6 files changed, 24 insertions(+), 44 deletions(-)
diff --git a/plugins/postcss-design-tokens/README.md b/plugins/postcss-design-tokens/README.md
index 391f29bf0..33a772a8d 100644
--- a/plugins/postcss-design-tokens/README.md
+++ b/plugins/postcss-design-tokens/README.md
@@ -18,8 +18,8 @@
```
```pcss
-@design-tokens url('./tokens-light.json') vendor('style-dictionary') version('3');
-@design-tokens url('./tokens-dark.json') variants('dark') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens-light.json') format('style-dictionary3');
+@design-tokens url('./tokens-dark.json') variants('dark') format('style-dictionary3');
.foo {
color: design-token('color.background.primary');
@@ -79,8 +79,8 @@ postcssDesignTokens({ variants: ['dark'] })
```
```pcss
-@design-tokens url('./tokens-light.json') vendor('style-dictionary') version('3');
-@design-tokens url('./tokens-dark.json') variants('dark') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens-light.json') format('style-dictionary3');
+@design-tokens url('./tokens-dark.json') variants('dark') format('style-dictionary3');
.foo {
color: design-token('color.background.primary');
diff --git a/plugins/postcss-design-tokens/src/data-formats/parse-import.ts b/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
index 5ae1ae21c..7d86e3654 100644
--- a/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
+++ b/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
@@ -4,13 +4,12 @@ import { extractStyleDictionaryTokens, latestStyleDictionaryVersion } from './st
import path from 'path';
import { promises as fsp } from 'fs';
-function parseImport(statement: string): { filePath: string, vendor: string, version: string, variants: Array } {
+function parseImport(statement: string): { filePath: string, format: string, variants: Array } {
const importAST = valueParser(statement);
const result = {
filePath: '',
- vendor: 'standard',
- version: '',
+ format: 'standard',
variants: ['default'],
};
@@ -19,12 +18,8 @@ function parseImport(statement: string): { filePath: string, vendor: string, ver
result.filePath = node.nodes[0].value;
}
- if (node.type === 'function' && node.value === 'vendor') {
- result.vendor = node.nodes[0].value;
- }
-
- if (node.type === 'function' && node.value === 'version') {
- result.version = node.nodes[0].value;
+ if (node.type === 'function' && node.value === 'format') {
+ result.format = node.nodes[0].value;
}
if (node.type === 'function' && node.value === 'variants') {
@@ -36,26 +31,11 @@ function parseImport(statement: string): { filePath: string, vendor: string, ver
result.variants = ['default'];
}
- if (!result.version) {
- switch (result.vendor) {
- case 'standard':
- result.version = '0.0.1';
- break;
-
- case 'style-dictionary':
- result.version = latestStyleDictionaryVersion;
- break;
-
- default:
- break;
- }
- }
-
return result;
}
export async function tokensFromImport(currentVariants: Array, sourceFilePath: string, statement: string, alreadyImported: Set): Promise<{ filePath: string, tokens: Map }|false> {
- const { filePath, vendor, version, variants } = parseImport(statement);
+ const { filePath, format, variants } = parseImport(statement);
if (!variants.every((variant) => currentVariants.includes(variant))) {
return false;
}
@@ -70,16 +50,16 @@ export async function tokensFromImport(currentVariants: Array, sourceFil
const fileContents = await fsp.readFile(resolvedPath, 'utf8');
const tokenContents = JSON.parse(fileContents);
- switch (vendor) {
- case 'style-dictionary':
+ switch (format) {
+ case 'style-dictionary3':
return {
filePath: path.resolve(filePath),
- tokens: extractStyleDictionaryTokens(version, tokenContents, resolvedPath),
+ tokens: extractStyleDictionaryTokens('3', tokenContents, resolvedPath),
};
default:
break;
}
- throw new Error('Unsupported vendor: ' + vendor);
+ throw new Error('Unsupported format: ' + format);
}
diff --git a/plugins/postcss-design-tokens/test/basic.css b/plugins/postcss-design-tokens/test/basic.css
index 0eb5dc7d2..5456feccc 100644
--- a/plugins/postcss-design-tokens/test/basic.css
+++ b/plugins/postcss-design-tokens/test/basic.css
@@ -1,5 +1,5 @@
@import url('./imported.css');
-@design-tokens url('./tokens/basic.json') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens/basic.json') format('style-dictionary3');
.foo {
font-family: design-token('font.family.serif');
diff --git a/plugins/postcss-design-tokens/test/examples/example.css b/plugins/postcss-design-tokens/test/examples/example.css
index 28d4ecca4..cca0fe960 100644
--- a/plugins/postcss-design-tokens/test/examples/example.css
+++ b/plugins/postcss-design-tokens/test/examples/example.css
@@ -1,5 +1,5 @@
-@design-tokens url('./tokens-light.json') vendor('style-dictionary') version('3');
-@design-tokens url('./tokens-dark.json') variants('dark') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens-light.json') format('style-dictionary3');
+@design-tokens url('./tokens-dark.json') variants('dark') format('style-dictionary3');
.foo {
color: design-token('color.background.primary');
diff --git a/plugins/postcss-design-tokens/test/imported.css b/plugins/postcss-design-tokens/test/imported.css
index 833f5e11c..6c68c5991 100644
--- a/plugins/postcss-design-tokens/test/imported.css
+++ b/plugins/postcss-design-tokens/test/imported.css
@@ -1 +1 @@
-@design-tokens url('../../../node_modules/style-dictionary-design-tokens-example/style-dictionary.tokens.json') vendor('style-dictionary') version('3');
+@design-tokens url('../../../node_modules/style-dictionary-design-tokens-example/style-dictionary.tokens.json') format('style-dictionary3');
diff --git a/plugins/postcss-design-tokens/test/variants.css b/plugins/postcss-design-tokens/test/variants.css
index 6db1c1d4f..fce21ce06 100644
--- a/plugins/postcss-design-tokens/test/variants.css
+++ b/plugins/postcss-design-tokens/test/variants.css
@@ -1,10 +1,10 @@
-@design-tokens url('./tokens/color_dark_branded-blue.tokens.json') variants('dark', 'branded-blue') vendor('style-dictionary') version('3');
-@design-tokens url('./tokens/color_light_branded-blue.tokens.json') variants('light', 'branded-blue') vendor('style-dictionary') version('3');
-@design-tokens url('./tokens/color_dark_branded-green.tokens.json') variants('dark', 'branded-green') vendor('style-dictionary') version('3');
-@design-tokens url('./tokens/color_light_branded-green.tokens.json') variants('light', 'branded-green') vendor('style-dictionary') version('3');
-@design-tokens url('./tokens/size_mobile.tokens.json') variants('mobile') vendor('style-dictionary') version('3');
-@design-tokens url('./tokens/size_tablet.tokens.json') variants('tablet') vendor('style-dictionary') version('3');
-@design-tokens url('./tokens/size_desktop.tokens.json') variants('desktop') vendor('style-dictionary') version('3');
+@design-tokens url('./tokens/color_dark_branded-blue.tokens.json') variants('dark', 'branded-blue') format('style-dictionary3');
+@design-tokens url('./tokens/color_light_branded-blue.tokens.json') variants('light', 'branded-blue') format('style-dictionary3');
+@design-tokens url('./tokens/color_dark_branded-green.tokens.json') variants('dark', 'branded-green') format('style-dictionary3');
+@design-tokens url('./tokens/color_light_branded-green.tokens.json') variants('light', 'branded-green') format('style-dictionary3');
+@design-tokens url('./tokens/size_mobile.tokens.json') variants('mobile') format('style-dictionary3');
+@design-tokens url('./tokens/size_tablet.tokens.json') variants('tablet') format('style-dictionary3');
+@design-tokens url('./tokens/size_desktop.tokens.json') variants('desktop') format('style-dictionary3');
.foo {
color: design-token('color');
From ffafaadf9677617bf02d4dec1baa40cddac2e894 Mon Sep 17 00:00:00 2001
From: romainmenke
Date: Sun, 27 Mar 2022 15:45:15 +0200
Subject: [PATCH 10/32] gather tokens first
---
.../src/data-formats/parse-import.ts | 6 ++++--
plugins/postcss-design-tokens/src/index.ts | 21 +++++++++++++------
2 files changed, 19 insertions(+), 8 deletions(-)
diff --git a/plugins/postcss-design-tokens/src/data-formats/parse-import.ts b/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
index 7d86e3654..b685ead57 100644
--- a/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
+++ b/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
@@ -1,6 +1,6 @@
import valueParser from 'postcss-value-parser';
import { Token } from './base/token';
-import { extractStyleDictionaryTokens, latestStyleDictionaryVersion } from './style-dictionary/style-dictionary';
+import { extractStyleDictionaryTokens } from './style-dictionary/style-dictionary';
import path from 'path';
import { promises as fsp } from 'fs';
@@ -23,7 +23,9 @@ function parseImport(statement: string): { filePath: string, format: string, var
}
if (node.type === 'function' && node.value === 'variants') {
- result.variants = node.nodes.filter((child) => child.type === 'string').map((child) => child.value);
+ result.variants = node.nodes.filter((child) => {
+ return child.type === 'string';
+ }).map((child) => child.value);
}
});
diff --git a/plugins/postcss-design-tokens/src/index.ts b/plugins/postcss-design-tokens/src/index.ts
index f52cdce95..19a239f5e 100644
--- a/plugins/postcss-design-tokens/src/index.ts
+++ b/plugins/postcss-design-tokens/src/index.ts
@@ -25,9 +25,11 @@ const creator: PluginCreator = (opts?: pluginOptions) => {
tokens = new Map();
importedFiles = new Set();
},
- AtRule: {
- 'design-tokens': async (atRule, { result }) => {
+ Once: async (root, { result }) => {
+ const designTokenAtRules: Array<{filePath: string, params: string}> = [];
+ root.walkAtRules('design-tokens', (atRule) => {
if (!atRule?.source?.input?.file) {
+ atRule.remove();
return;
}
@@ -35,20 +37,27 @@ const creator: PluginCreator = (opts?: pluginOptions) => {
const params = atRule.params;
atRule.remove();
- const importResult = await tokensFromImport(variants, filePath, params, importedFiles);
+ designTokenAtRules.push({
+ filePath: filePath,
+ params: params,
+ });
+ });
+
+ for (const atRule of designTokenAtRules.values()) {
+ const importResult = await tokensFromImport(variants, atRule.filePath, atRule.params, importedFiles);
if (!importResult) {
- return;
+ continue;
}
result.messages.push({
type: 'dependency',
plugin: 'postcss-design-tokens',
file: importResult.filePath,
- parent: filePath,
+ parent: atRule.filePath,
});
tokens = mergeTokens(tokens, importResult.tokens);
- },
+ }
},
Declaration(decl, { result }) {
if (decl.value.indexOf('design-token') === -1) {
From b2abffc9e69a4d079e4776956d1d5fb996907c84 Mon Sep 17 00:00:00 2001
From: romainmenke
Date: Sun, 27 Mar 2022 18:13:59 +0200
Subject: [PATCH 11/32] feedback
---
plugins/postcss-design-tokens/.tape.mjs | 8 +++-----
plugins/postcss-design-tokens/README.md | 12 +++++++-----
plugins/postcss-design-tokens/docs/README.md | 8 +++++---
.../src/data-formats/parse-import.ts | 18 +++++++++---------
plugins/postcss-design-tokens/src/index.ts | 10 +++++-----
.../test/examples/example.css | 2 +-
plugins/postcss-design-tokens/test/is.css | 12 ++++++++++++
.../{variants.expect.css => is.expect.css} | 0
.../postcss-design-tokens/test/variants.css | 12 ------------
9 files changed, 42 insertions(+), 40 deletions(-)
create mode 100644 plugins/postcss-design-tokens/test/is.css
rename plugins/postcss-design-tokens/test/{variants.expect.css => is.expect.css} (100%)
delete mode 100644 plugins/postcss-design-tokens/test/variants.css
diff --git a/plugins/postcss-design-tokens/.tape.mjs b/plugins/postcss-design-tokens/.tape.mjs
index 0f15d5ffd..60b696675 100644
--- a/plugins/postcss-design-tokens/.tape.mjs
+++ b/plugins/postcss-design-tokens/.tape.mjs
@@ -11,10 +11,10 @@ postcssTape(plugin)({
plugin()
]
},
- 'variants': {
+ 'is': {
message: "supports basic usage",
options: {
- variants: ['dark', 'tablet', 'branded-green']
+ is: ['dark', 'tablet', 'branded-green']
}
},
'examples/example': {
@@ -24,9 +24,7 @@ postcssTape(plugin)({
'examples/example:dark': {
message: 'minimal example with dark theme',
options: {
- variants: [
- 'dark'
- ]
+ is: ['dark']
},
},
});
diff --git a/plugins/postcss-design-tokens/README.md b/plugins/postcss-design-tokens/README.md
index 33a772a8d..fd4fb2860 100644
--- a/plugins/postcss-design-tokens/README.md
+++ b/plugins/postcss-design-tokens/README.md
@@ -19,7 +19,7 @@
```pcss
@design-tokens url('./tokens-light.json') format('style-dictionary3');
-@design-tokens url('./tokens-dark.json') variants('dark') format('style-dictionary3');
+@design-tokens url('./tokens-dark.json') when('dark') format('style-dictionary3');
.foo {
color: design-token('color.background.primary');
@@ -59,13 +59,15 @@ instructions for:
## Options
-### variants
+### is
-The `variants` option determines which design tokens are used.
+The `is` option determines which design tokens are used.
This allows you to generate multiple themed stylesheets.
+By default only `@design-tokens` without any `when('foo')` conditions are used.
+
```js
-postcssDesignTokens({ variants: ['dark'] })
+postcssDesignTokens({ is: ['dark'] })
```
```json
@@ -80,7 +82,7 @@ postcssDesignTokens({ variants: ['dark'] })
```pcss
@design-tokens url('./tokens-light.json') format('style-dictionary3');
-@design-tokens url('./tokens-dark.json') variants('dark') format('style-dictionary3');
+@design-tokens url('./tokens-dark.json') when('dark') format('style-dictionary3');
.foo {
color: design-token('color.background.primary');
diff --git a/plugins/postcss-design-tokens/docs/README.md b/plugins/postcss-design-tokens/docs/README.md
index 7cbf40f26..0e308a9f3 100644
--- a/plugins/postcss-design-tokens/docs/README.md
+++ b/plugins/postcss-design-tokens/docs/README.md
@@ -34,13 +34,15 @@
## Options
-### variants
+### is
-The `variants` option determines which design tokens are used.
+The `is` option determines which design tokens are used.
This allows you to generate multiple themed stylesheets.
+By default only `@design-tokens` without any `when('foo')` conditions are used.
+
```js
-({ variants: ['dark'] })
+({ is: ['dark'] })
```
```json
diff --git a/plugins/postcss-design-tokens/src/data-formats/parse-import.ts b/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
index b685ead57..39f609421 100644
--- a/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
+++ b/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
@@ -4,13 +4,13 @@ import { extractStyleDictionaryTokens } from './style-dictionary/style-dictionar
import path from 'path';
import { promises as fsp } from 'fs';
-function parseImport(statement: string): { filePath: string, format: string, variants: Array } {
+function parseImport(statement: string): { filePath: string, format: string, conditions: Array } {
const importAST = valueParser(statement);
const result = {
filePath: '',
format: 'standard',
- variants: ['default'],
+ conditions: ['default'],
};
importAST.walk((node) => {
@@ -22,23 +22,23 @@ function parseImport(statement: string): { filePath: string, format: string, var
result.format = node.nodes[0].value;
}
- if (node.type === 'function' && node.value === 'variants') {
- result.variants = node.nodes.filter((child) => {
+ if (node.type === 'function' && node.value === 'when') {
+ result.conditions = node.nodes.filter((child) => {
return child.type === 'string';
}).map((child) => child.value);
}
});
- if (!result.variants.length) {
- result.variants = ['default'];
+ if (!result.conditions.length) {
+ result.conditions = ['default'];
}
return result;
}
-export async function tokensFromImport(currentVariants: Array, sourceFilePath: string, statement: string, alreadyImported: Set): Promise<{ filePath: string, tokens: Map }|false> {
- const { filePath, format, variants } = parseImport(statement);
- if (!variants.every((variant) => currentVariants.includes(variant))) {
+export async function tokensFromImport(buildIs: Array, sourceFilePath: string, statement: string, alreadyImported: Set): Promise<{ filePath: string, tokens: Map }|false> {
+ const { filePath, format, conditions } = parseImport(statement);
+ if (!conditions.every((condition) => buildIs.includes(condition))) {
return false;
}
diff --git a/plugins/postcss-design-tokens/src/index.ts b/plugins/postcss-design-tokens/src/index.ts
index 19a239f5e..e393f14ce 100644
--- a/plugins/postcss-design-tokens/src/index.ts
+++ b/plugins/postcss-design-tokens/src/index.ts
@@ -5,13 +5,13 @@ import { mergeTokens } from './data-formats/token';
import { onCSSValue } from './values';
type pluginOptions = {
- variants?: Array
+ is?: Array
}
const creator: PluginCreator = (opts?: pluginOptions) => {
- const variants = opts?.variants ?? [];
- if (variants.length === 0) {
- variants.push('default');
+ const buildIs = opts?.is ?? [];
+ if (buildIs.length === 0) {
+ buildIs.push('default');
}
return {
@@ -44,7 +44,7 @@ const creator: PluginCreator = (opts?: pluginOptions) => {
});
for (const atRule of designTokenAtRules.values()) {
- const importResult = await tokensFromImport(variants, atRule.filePath, atRule.params, importedFiles);
+ const importResult = await tokensFromImport(buildIs, atRule.filePath, atRule.params, importedFiles);
if (!importResult) {
continue;
}
diff --git a/plugins/postcss-design-tokens/test/examples/example.css b/plugins/postcss-design-tokens/test/examples/example.css
index cca0fe960..8592a21da 100644
--- a/plugins/postcss-design-tokens/test/examples/example.css
+++ b/plugins/postcss-design-tokens/test/examples/example.css
@@ -1,5 +1,5 @@
@design-tokens url('./tokens-light.json') format('style-dictionary3');
-@design-tokens url('./tokens-dark.json') variants('dark') format('style-dictionary3');
+@design-tokens url('./tokens-dark.json') when('dark') format('style-dictionary3');
.foo {
color: design-token('color.background.primary');
diff --git a/plugins/postcss-design-tokens/test/is.css b/plugins/postcss-design-tokens/test/is.css
new file mode 100644
index 000000000..57ec7371b
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/is.css
@@ -0,0 +1,12 @@
+@design-tokens url('./tokens/color_dark_branded-blue.tokens.json') when('dark', 'branded-blue') format('style-dictionary3');
+@design-tokens url('./tokens/color_light_branded-blue.tokens.json') when('light', 'branded-blue') format('style-dictionary3');
+@design-tokens url('./tokens/color_dark_branded-green.tokens.json') when('dark', 'branded-green') format('style-dictionary3');
+@design-tokens url('./tokens/color_light_branded-green.tokens.json') when('light', 'branded-green') format('style-dictionary3');
+@design-tokens url('./tokens/size_mobile.tokens.json') when('mobile') format('style-dictionary3');
+@design-tokens url('./tokens/size_tablet.tokens.json') when('tablet') format('style-dictionary3');
+@design-tokens url('./tokens/size_desktop.tokens.json') when('desktop') format('style-dictionary3');
+
+.foo {
+ color: design-token('color');
+ font-size: design-token('size');
+}
diff --git a/plugins/postcss-design-tokens/test/variants.expect.css b/plugins/postcss-design-tokens/test/is.expect.css
similarity index 100%
rename from plugins/postcss-design-tokens/test/variants.expect.css
rename to plugins/postcss-design-tokens/test/is.expect.css
diff --git a/plugins/postcss-design-tokens/test/variants.css b/plugins/postcss-design-tokens/test/variants.css
deleted file mode 100644
index fce21ce06..000000000
--- a/plugins/postcss-design-tokens/test/variants.css
+++ /dev/null
@@ -1,12 +0,0 @@
-@design-tokens url('./tokens/color_dark_branded-blue.tokens.json') variants('dark', 'branded-blue') format('style-dictionary3');
-@design-tokens url('./tokens/color_light_branded-blue.tokens.json') variants('light', 'branded-blue') format('style-dictionary3');
-@design-tokens url('./tokens/color_dark_branded-green.tokens.json') variants('dark', 'branded-green') format('style-dictionary3');
-@design-tokens url('./tokens/color_light_branded-green.tokens.json') variants('light', 'branded-green') format('style-dictionary3');
-@design-tokens url('./tokens/size_mobile.tokens.json') variants('mobile') format('style-dictionary3');
-@design-tokens url('./tokens/size_tablet.tokens.json') variants('tablet') format('style-dictionary3');
-@design-tokens url('./tokens/size_desktop.tokens.json') variants('desktop') format('style-dictionary3');
-
-.foo {
- color: design-token('color');
- font-size: design-token('size');
-}
From c5a3f77611445f0776f19ec4bf53866e32a1a77e Mon Sep 17 00:00:00 2001
From: romainmenke
Date: Thu, 31 Mar 2022 17:33:45 +0200
Subject: [PATCH 12/32] wip
---
.../json-transformer/index.cjs | 21 +++++++++++++++++++
.../json-transformer/package.json | 15 +++++++++++++
2 files changed, 36 insertions(+)
create mode 100644 e2e/style-dictionary/json-transformer/index.cjs
create mode 100644 e2e/style-dictionary/json-transformer/package.json
diff --git a/e2e/style-dictionary/json-transformer/index.cjs b/e2e/style-dictionary/json-transformer/index.cjs
new file mode 100644
index 000000000..2b485b55e
--- /dev/null
+++ b/e2e/style-dictionary/json-transformer/index.cjs
@@ -0,0 +1,21 @@
+const StyleDictionary = require('style-dictionary').extend('config.json');
+
+StyleDictionary.registerFormat({
+ name: 'csstools/designTokens',
+ formatter: function ({ dictionary, platform, options, file }) {
+ return JSON.stringify(dictionary.tokens, null, 2);
+ },
+});
+
+StyleDictionary.registerTransform({
+ name: 'time/seconds',
+ type: 'value',
+ matcher: function (prop) {
+ return prop.attributes.category === 'time';
+ },
+ transformer: function (prop) {
+ return (parseInt(prop.original.value) / 1000).toString() + 's';
+ },
+});
+
+StyleDictionary.buildAllPlatforms();
diff --git a/e2e/style-dictionary/json-transformer/package.json b/e2e/style-dictionary/json-transformer/package.json
new file mode 100644
index 000000000..b41157566
--- /dev/null
+++ b/e2e/style-dictionary/json-transformer/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "@csstools/style-dictionary--json-transformer",
+ "private": true,
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "devDependencies": {
+ "style-dictionary": "^3.7.0"
+ },
+ "author": "",
+ "license": "MIT"
+}
From 2ff810fa4e7709815ac6262235a1019f92fc17b4 Mon Sep 17 00:00:00 2001
From: romainmenke
Date: Sun, 3 Apr 2022 12:34:48 +0200
Subject: [PATCH 13/32] implement dereferencing
---
.../json-transformer/index.cjs | 21 --
.../json-transformer/package.json | 15 --
plugins/postcss-design-tokens/.tape.mjs | 8 +-
plugins/postcss-design-tokens/README.md | 57 +++-
plugins/postcss-design-tokens/docs/README.md | 42 ++-
.../src/data-formats/parse-import.ts | 4 +-
.../style-dictionary/v3/dereference.ts | 246 ++++++++++++++++++
.../data-formats/style-dictionary/v3/group.ts | 3 +-
.../src/data-formats/toposort/toposort.ts | 104 ++++++++
plugins/postcss-design-tokens/src/index.ts | 2 +-
plugins/postcss-design-tokens/test/basic.css | 7 +-
.../test/basic.expect.css | 6 +-
.../test/examples/example-conditional.css | 6 +
...ss => example-conditional.dark.expect.css} | 0
.../examples/example-conditional.expect.css | 3 +
.../test/examples/example.css | 3 +-
.../test/examples/tokens.json | 7 +
.../test/tokens/basic.json | 17 +-
18 files changed, 495 insertions(+), 56 deletions(-)
delete mode 100644 e2e/style-dictionary/json-transformer/index.cjs
delete mode 100644 e2e/style-dictionary/json-transformer/package.json
create mode 100644 plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/dereference.ts
create mode 100644 plugins/postcss-design-tokens/src/data-formats/toposort/toposort.ts
create mode 100644 plugins/postcss-design-tokens/test/examples/example-conditional.css
rename plugins/postcss-design-tokens/test/examples/{example.dark.expect.css => example-conditional.dark.expect.css} (100%)
create mode 100644 plugins/postcss-design-tokens/test/examples/example-conditional.expect.css
create mode 100644 plugins/postcss-design-tokens/test/examples/tokens.json
diff --git a/e2e/style-dictionary/json-transformer/index.cjs b/e2e/style-dictionary/json-transformer/index.cjs
deleted file mode 100644
index 2b485b55e..000000000
--- a/e2e/style-dictionary/json-transformer/index.cjs
+++ /dev/null
@@ -1,21 +0,0 @@
-const StyleDictionary = require('style-dictionary').extend('config.json');
-
-StyleDictionary.registerFormat({
- name: 'csstools/designTokens',
- formatter: function ({ dictionary, platform, options, file }) {
- return JSON.stringify(dictionary.tokens, null, 2);
- },
-});
-
-StyleDictionary.registerTransform({
- name: 'time/seconds',
- type: 'value',
- matcher: function (prop) {
- return prop.attributes.category === 'time';
- },
- transformer: function (prop) {
- return (parseInt(prop.original.value) / 1000).toString() + 's';
- },
-});
-
-StyleDictionary.buildAllPlatforms();
diff --git a/e2e/style-dictionary/json-transformer/package.json b/e2e/style-dictionary/json-transformer/package.json
deleted file mode 100644
index b41157566..000000000
--- a/e2e/style-dictionary/json-transformer/package.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "name": "@csstools/style-dictionary--json-transformer",
- "private": true,
- "version": "1.0.0",
- "description": "",
- "main": "index.js",
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
- },
- "devDependencies": {
- "style-dictionary": "^3.7.0"
- },
- "author": "",
- "license": "MIT"
-}
diff --git a/plugins/postcss-design-tokens/.tape.mjs b/plugins/postcss-design-tokens/.tape.mjs
index 60b696675..9bc5019b7 100644
--- a/plugins/postcss-design-tokens/.tape.mjs
+++ b/plugins/postcss-design-tokens/.tape.mjs
@@ -21,8 +21,12 @@ postcssTape(plugin)({
message: 'minimal example',
options: {},
},
- 'examples/example:dark': {
- message: 'minimal example with dark theme',
+ 'examples/example-conditional': {
+ message: 'minimal example with conditional imports : default',
+ options: {},
+ },
+ 'examples/example-conditional:dark': {
+ message: 'minimal example with conditional imports : dark',
options: {
is: ['dark']
},
diff --git a/plugins/postcss-design-tokens/README.md b/plugins/postcss-design-tokens/README.md
index fd4fb2860..f99cd463d 100644
--- a/plugins/postcss-design-tokens/README.md
+++ b/plugins/postcss-design-tokens/README.md
@@ -18,8 +18,7 @@
```
```pcss
-@design-tokens url('./tokens-light.json') format('style-dictionary3');
-@design-tokens url('./tokens-dark.json') when('dark') format('style-dictionary3');
+@design-tokens url('./tokens.json') format('style-dictionary3');
.foo {
color: design-token('color.background.primary');
@@ -66,8 +65,18 @@ This allows you to generate multiple themed stylesheets.
By default only `@design-tokens` without any `when('foo')` conditions are used.
-```js
-postcssDesignTokens({ is: ['dark'] })
+#### Example usage
+
+**For these two token files :**
+
+```json
+{
+ "color": {
+ "background": {
+ "primary": { "value": "#fff" }
+ }
+ }
+}
```
```json
@@ -80,6 +89,46 @@ postcssDesignTokens({ is: ['dark'] })
}
```
+**And this CSS :**
+
+```pcss
+@design-tokens url('./tokens-light.json') format('style-dictionary3');
+@design-tokens url('./tokens-dark.json') when('dark') format('style-dictionary3');
+
+.foo {
+ color: design-token('color.background.primary');
+}
+```
+
+**You can configure :**
+
+##### No `is` option.
+
+```js
+postcssDesignTokens()
+```
+
+```pcss
+@design-tokens url('./tokens-light.json') format('style-dictionary3');
+@design-tokens url('./tokens-dark.json') when('dark') format('style-dictionary3');
+
+.foo {
+ color: design-token('color.background.primary');
+}
+
+/* becomes */
+
+.foo {
+ color: #fff;
+}
+```
+
+##### `is` option set to 'dark'.
+
+```js
+postcssDesignTokens({ is: ['dark'] })
+```
+
```pcss
@design-tokens url('./tokens-light.json') format('style-dictionary3');
@design-tokens url('./tokens-dark.json') when('dark') format('style-dictionary3');
diff --git a/plugins/postcss-design-tokens/docs/README.md b/plugins/postcss-design-tokens/docs/README.md
index 0e308a9f3..ac3c9bced 100644
--- a/plugins/postcss-design-tokens/docs/README.md
+++ b/plugins/postcss-design-tokens/docs/README.md
@@ -17,7 +17,7 @@
[] lets easily create new plugins following some [CSS Specification].
```json
-
+
```
```pcss
@@ -41,20 +41,52 @@ This allows you to generate multiple themed stylesheets.
By default only `@design-tokens` without any `when('foo')` conditions are used.
-```js
-({ is: ['dark'] })
+#### Example usage
+
+**For these two token files :**
+
+```json
+
```
```json
```
+**And this CSS :**
+
```pcss
-
+
+```
+
+**You can configure :**
+
+##### No `is` option.
+
+```js
+()
+```
+
+```pcss
+
+
+/* becomes */
+
+
+```
+
+##### `is` option set to 'dark'.
+
+```js
+({ is: ['dark'] })
+```
+
+```pcss
+
/* becomes */
-
+
```
diff --git a/plugins/postcss-design-tokens/src/data-formats/parse-import.ts b/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
index 39f609421..d978506f2 100644
--- a/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
+++ b/plugins/postcss-design-tokens/src/data-formats/parse-import.ts
@@ -10,7 +10,7 @@ function parseImport(statement: string): { filePath: string, format: string, con
const result = {
filePath: '',
format: 'standard',
- conditions: ['default'],
+ conditions: ['6b4e71e7-4787-42f7-a092-8684961895db'], // a random, but shared default condition
};
importAST.walk((node) => {
@@ -30,7 +30,7 @@ function parseImport(statement: string): { filePath: string, format: string, con
});
if (!result.conditions.length) {
- result.conditions = ['default'];
+ result.conditions = ['6b4e71e7-4787-42f7-a092-8684961895db']; // a random, but shared default condition
}
return result;
diff --git a/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/dereference.ts b/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/dereference.ts
new file mode 100644
index 000000000..2c2e12577
--- /dev/null
+++ b/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/dereference.ts
@@ -0,0 +1,246 @@
+import { toposort } from '../../toposort/toposort';
+import { StyleDictionaryV3TokenValue } from './value';
+
+export function dereferenceTokenValues(tokens: Map): Map {
+ const tainted = new Set();
+ const referenceASTs = new Map>();
+
+ // Gather all references.
+ {
+ for (const [id, token] of tokens.entries()) {
+ const referenceAST = parseReferences(token.value);
+ if (!referenceAST.length) {
+ continue;
+ }
+
+ tainted.add(id);
+ referenceASTs.set(id, referenceAST);
+ }
+ }
+
+ // Fast dereference.
+ // Only handles references for which a value can found immediately.
+ {
+ for (const [id, referenceAST] of referenceASTs.entries()) {
+ for (let i = 0; i < referenceAST.length; i++) {
+ const reference = referenceAST[i];
+ if (reference.type !== 'value-reference') {
+ continue;
+ }
+
+ if (tainted.has(reference.raw)) {
+ continue;
+ }
+
+ if (!tokens.has(reference.raw)) {
+ throw new Error('Alias "' + reference.raw + '" not found');
+ }
+
+ const sourceToken = tokens.get(reference.raw);
+ referenceAST[i] = {
+ type: 'value-resolved',
+ raw: reference.raw,
+ value: sourceToken.cssValue(),
+ };
+ }
+
+ const hasFurtherReferences = referenceAST.some(part => part.type === 'value-reference');
+ if (hasFurtherReferences) {
+ continue;
+ }
+
+ const value = (referenceAST as ValuePartsResolved).map(part => part.value).join('');
+ const currentToken = tokens.get(id);
+
+ currentToken.value = value;
+ currentToken.cssValue = () => {
+ return value ?? '';
+ };
+
+ tokens.set(id, currentToken);
+ tainted.delete(id);
+ referenceASTs.delete(id);
+ }
+
+ if (tainted.size === 0) {
+ return tokens;
+ }
+ }
+
+ // Topological dereferrence.
+ {
+ const nodes : Array = Array.from(tokens.keys());
+ const edges: Array> = [];
+
+ for (const [id, referenceAST] of referenceASTs.entries()) {
+ for (let i = 0; i < referenceAST.length; i++) {
+ const reference = referenceAST[i];
+ if (reference.type !== 'value-reference') {
+ continue;
+ }
+
+ edges.push([reference.raw, id]);
+ }
+ }
+
+ const sorted = toposort(nodes, edges);
+ if (!sorted) {
+ throw new Error('Circular reference detected');
+ }
+
+ for (const id of sorted) {
+ if (!referenceASTs.has(id)) {
+ continue;
+ }
+
+ const referenceAST = referenceASTs.get(id);
+ for (let i = 0; i < referenceAST.length; i++) {
+ const reference = referenceAST[i];
+ if (reference.type !== 'value-reference') {
+ continue;
+ }
+
+ if (tainted.has(reference.raw)) {
+ throw new Error('Alias "' + reference.raw + '" can not be resolved');
+ }
+
+ if (!tokens.has(reference.raw)) {
+ throw new Error('Alias "' + reference.raw + '" not found');
+ }
+
+ const sourceToken = tokens.get(reference.raw);
+ referenceAST[i] = {
+ type: 'value-resolved',
+ raw: reference.raw,
+ value: sourceToken.cssValue(),
+ };
+ }
+
+ const hasFurtherReferences = referenceAST.some(part => part.type === 'value-reference');
+ if (hasFurtherReferences) {
+ throw new Error('Token "' + id + '" can not be fully resolved');
+ }
+
+ const value = (referenceAST as ValuePartsResolved).map(part => part.value).join('');
+ const currentToken = tokens.get(id);
+
+ currentToken.value = value;
+ currentToken.cssValue = () => {
+ return value ?? '';
+ };
+
+ tokens.set(id, currentToken);
+ tainted.delete(id);
+ referenceASTs.delete(id);
+ }
+
+ if (tainted.size === 0) {
+ return tokens;
+ }
+ }
+
+ return tokens;
+}
+
+type ValuePartsResolved = Array;
+type ValuePart = ValueReference | ValueNonReference | ValueResolved;
+
+type ValueReference = {
+ type: 'value-reference',
+ raw: string,
+}
+
+type ValueResolved = {
+ type: 'value-resolved',
+ raw: string,
+ value: string,
+}
+
+type ValueNonReference = {
+ type: 'value-non-reference',
+ value: string,
+}
+
+function parseReferences(valueWithReferences: unknown): Array {
+ if (typeof valueWithReferences !== 'string') {
+ return [];
+ }
+
+ if (valueWithReferences.indexOf('{') === -1) {
+ return [];
+ }
+
+ const result: Array = [];
+ let hasReferences = false;
+
+ let inReference = false;
+ let buf = '';
+
+ for (let index = 0; index < valueWithReferences.length; index++) {
+ const char = valueWithReferences[index];
+
+ switch (char) {
+ case '{':
+ if (inReference) {
+ throw new Error('Unexpected "{" in "' + valueWithReferences + '" at ' + index);
+ }
+
+ if (buf.length > 0) {
+ result.push({
+ type: 'value-non-reference',
+ value: buf,
+ });
+ buf = '';
+ }
+
+ inReference = true;
+ break;
+ case '}':
+ if (!inReference) {
+ throw new Error('Unexpected "}" in "' + valueWithReferences + '" at ' + index);
+ }
+
+ if (buf.length === 0) {
+ throw new Error('Empty alias "{}" in "' + valueWithReferences + '" at ' + index);
+ }
+
+ {
+ let reference = buf.trim();
+ if (reference.slice(-6) === '.value') {
+ reference = reference.slice(0, -6);
+ }
+
+ result.push({
+ type: 'value-reference',
+ raw: reference,
+ });
+ buf = '';
+ }
+
+ hasReferences = true;
+ inReference = false;
+ break;
+
+ default:
+ buf += char;
+ break;
+ }
+ }
+
+ if (inReference) {
+ throw new Error('Unexpected end of alias in "' + valueWithReferences + '"');
+ }
+
+ if (buf.length > 0) {
+ result.push({
+ type: 'value-non-reference',
+ value: buf,
+ });
+ }
+
+ if (!hasReferences) {
+ return [];
+ }
+
+ return result;
+}
diff --git a/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/group.ts b/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/group.ts
index 2115706ae..b21c3955b 100644
--- a/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/group.ts
+++ b/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/group.ts
@@ -1,4 +1,5 @@
import { Token } from '../../base/token';
+import { dereferenceTokenValues } from './dereference';
import { extractStyleDictionaryV3Token, StyleDictionaryV3TokenValue } from './value';
export type StyleDictionaryV3TokenGroup = {
@@ -33,5 +34,5 @@ function extractTokens(node: StyleDictionaryV3TokenGroup, path: Array, f
}
export function extractStyleDictionaryV3Tokens(node: StyleDictionaryV3TokenGroup, filePath: string): Map {
- return extractTokens(node, [], filePath);
+ return dereferenceTokenValues(extractTokens(node, [], filePath));
}
diff --git a/plugins/postcss-design-tokens/src/data-formats/toposort/toposort.ts b/plugins/postcss-design-tokens/src/data-formats/toposort/toposort.ts
new file mode 100644
index 000000000..15f3c39be
--- /dev/null
+++ b/plugins/postcss-design-tokens/src/data-formats/toposort/toposort.ts
@@ -0,0 +1,104 @@
+// Toposort - Topological sorting for node.js
+// Copyright (c) 2012 by Marcel Klehr
+// MIT LICENSE
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+export function toposort(nodes: Array, edges: Array>): Array {
+ let cursor = nodes.length;
+ const sorted: Array = new Array(cursor);
+ const visited = {};
+ let i = cursor;
+ // Better data structures make algorithm much faster.
+ const outgoingEdges = makeOutgoingEdges(edges);
+ const nodesHash = makeNodesHash(nodes);
+
+ // check for unknown nodes
+ edges.forEach(function (edge) {
+ if (!nodesHash.has(edge[0]) || !nodesHash.has(edge[1])) {
+ throw new Error('Unknown node. There is an unknown node in the supplied edges.');
+ }
+ });
+
+ while (i--) {
+ if (!visited[i]) {
+ visit(nodes[i], i, new Set());
+ }
+ }
+
+ return sorted;
+
+ function visit(node, j, predecessors) {
+ if (predecessors.has(node)) {
+ let nodeRep;
+ try {
+ nodeRep = ', node was:' + JSON.stringify(node);
+ } catch (e) {
+ nodeRep = '';
+ }
+ throw new Error('Cyclic dependency' + nodeRep);
+ }
+
+ if (!nodesHash.has(node)) {
+ throw new Error('Found unknown node. Make sure to provided all involved nodes. Unknown node: ' + JSON.stringify(node));
+ }
+
+ if (visited[j]) {
+ return;
+ }
+ visited[j] = true;
+
+ let outgoing = outgoingEdges.get(node) || new Set();
+ outgoing = Array.from(outgoing);
+
+ // eslint-disable-next-line no-cond-assign
+ if (j = outgoing.length) {
+ predecessors.add(node);
+ do {
+ const child = outgoing[--j];
+ visit(child, nodesHash.get(child), predecessors);
+ } while (j);
+ predecessors.delete(node);
+ }
+
+ sorted[--cursor] = node;
+ }
+}
+
+function makeOutgoingEdges(arr) {
+ const edges = new Map();
+ for (let i = 0, len = arr.length; i < len; i++) {
+ const edge = arr[i];
+ if (!edges.has(edge[0])) {
+ edges.set(edge[0], new Set());
+ }
+ if (!edges.has(edge[1])) {
+ edges.set(edge[1], new Set());
+ }
+ edges.get(edge[0]).add(edge[1]);
+ }
+ return edges;
+}
+
+function makeNodesHash(arr) {
+ const res = new Map();
+ for (let i = 0, len = arr.length; i < len; i++) {
+ res.set(arr[i], i);
+ }
+ return res;
+}
diff --git a/plugins/postcss-design-tokens/src/index.ts b/plugins/postcss-design-tokens/src/index.ts
index e393f14ce..ccf415d0b 100644
--- a/plugins/postcss-design-tokens/src/index.ts
+++ b/plugins/postcss-design-tokens/src/index.ts
@@ -11,7 +11,7 @@ type pluginOptions = {
const creator: PluginCreator = (opts?: pluginOptions) => {
const buildIs = opts?.is ?? [];
if (buildIs.length === 0) {
- buildIs.push('default');
+ buildIs.push('6b4e71e7-4787-42f7-a092-8684961895db'); // a random, but shared default condition
}
return {
diff --git a/plugins/postcss-design-tokens/test/basic.css b/plugins/postcss-design-tokens/test/basic.css
index 5456feccc..3f9ddf7f4 100644
--- a/plugins/postcss-design-tokens/test/basic.css
+++ b/plugins/postcss-design-tokens/test/basic.css
@@ -2,7 +2,12 @@
@design-tokens url('./tokens/basic.json') format('style-dictionary3');
.foo {
- font-family: design-token('font.family.serif');
+ font-family: design-token('font.family.base');
font-size: design-token('size.font.small');
color: design-token('color.font.base');
}
+
+.card {
+ background-color: design-token('card.background');
+ color: design-token('card.foreground');
+}
diff --git a/plugins/postcss-design-tokens/test/basic.expect.css b/plugins/postcss-design-tokens/test/basic.expect.css
index d0e8338ea..459bcb6ad 100644
--- a/plugins/postcss-design-tokens/test/basic.expect.css
+++ b/plugins/postcss-design-tokens/test/basic.expect.css
@@ -1,5 +1,9 @@
.foo {
- font-family: serif;
+ font-family: Helvetica sans;
font-size: 10;
color: #111111;
}
+.card {
+ background-color: blue;
+ color: red;
+}
diff --git a/plugins/postcss-design-tokens/test/examples/example-conditional.css b/plugins/postcss-design-tokens/test/examples/example-conditional.css
new file mode 100644
index 000000000..8592a21da
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/examples/example-conditional.css
@@ -0,0 +1,6 @@
+@design-tokens url('./tokens-light.json') format('style-dictionary3');
+@design-tokens url('./tokens-dark.json') when('dark') format('style-dictionary3');
+
+.foo {
+ color: design-token('color.background.primary');
+}
diff --git a/plugins/postcss-design-tokens/test/examples/example.dark.expect.css b/plugins/postcss-design-tokens/test/examples/example-conditional.dark.expect.css
similarity index 100%
rename from plugins/postcss-design-tokens/test/examples/example.dark.expect.css
rename to plugins/postcss-design-tokens/test/examples/example-conditional.dark.expect.css
diff --git a/plugins/postcss-design-tokens/test/examples/example-conditional.expect.css b/plugins/postcss-design-tokens/test/examples/example-conditional.expect.css
new file mode 100644
index 000000000..33a418a59
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/examples/example-conditional.expect.css
@@ -0,0 +1,3 @@
+.foo {
+ color: #fff;
+}
diff --git a/plugins/postcss-design-tokens/test/examples/example.css b/plugins/postcss-design-tokens/test/examples/example.css
index 8592a21da..c5d97f83a 100644
--- a/plugins/postcss-design-tokens/test/examples/example.css
+++ b/plugins/postcss-design-tokens/test/examples/example.css
@@ -1,5 +1,4 @@
-@design-tokens url('./tokens-light.json') format('style-dictionary3');
-@design-tokens url('./tokens-dark.json') when('dark') format('style-dictionary3');
+@design-tokens url('./tokens.json') format('style-dictionary3');
.foo {
color: design-token('color.background.primary');
diff --git a/plugins/postcss-design-tokens/test/examples/tokens.json b/plugins/postcss-design-tokens/test/examples/tokens.json
new file mode 100644
index 000000000..9776bbaed
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/examples/tokens.json
@@ -0,0 +1,7 @@
+{
+ "color": {
+ "background": {
+ "primary": { "value": "#fff" }
+ }
+ }
+}
diff --git a/plugins/postcss-design-tokens/test/tokens/basic.json b/plugins/postcss-design-tokens/test/tokens/basic.json
index 9ee085a9c..925b731ff 100644
--- a/plugins/postcss-design-tokens/test/tokens/basic.json
+++ b/plugins/postcss-design-tokens/test/tokens/basic.json
@@ -1,7 +1,22 @@
{
"font": {
"family": {
- "serif": { "value": "serif" }
+ "sans": { "value": "sans" },
+ "helvetica": { "value": "Helvetica" },
+ "serif": { "value": "serif" },
+ "base": { "value": "{ font.family.helvetica.value} {font.family.sans.value}" }
}
+ },
+ "card": {
+ "foreground": { "value": "{logical-color.foreground}" },
+ "background": { "value": "{logical-color.background}" }
+ },
+ "base-color": {
+ "red": { "value": "red" },
+ "blue": { "value": "blue" }
+ },
+ "logical-color": {
+ "foreground": { "value": "{base-color.red}" },
+ "background": { "value": "{base-color.blue}" }
}
}
From e1daac40edfaceb35055ed45e46f0af5bbaa75b7 Mon Sep 17 00:00:00 2001
From: romainmenke
Date: Sun, 3 Apr 2022 13:07:36 +0200
Subject: [PATCH 14/32] error handling
---
plugins/postcss-design-tokens/.tape.mjs | 5 +++++
.../src/data-formats/toposort/toposort.ts | 6 +++---
plugins/postcss-design-tokens/src/index.ts | 16 +++++++++++-----
plugins/postcss-design-tokens/test/errors.css | 4 ++++
.../postcss-design-tokens/test/errors.expect.css | 1 +
.../test/tokens/circular.json | 6 ++++++
.../test/tokens/missing.json | 5 +++++
7 files changed, 35 insertions(+), 8 deletions(-)
create mode 100644 plugins/postcss-design-tokens/test/errors.css
create mode 100644 plugins/postcss-design-tokens/test/errors.expect.css
create mode 100644 plugins/postcss-design-tokens/test/tokens/circular.json
create mode 100644 plugins/postcss-design-tokens/test/tokens/missing.json
diff --git a/plugins/postcss-design-tokens/.tape.mjs b/plugins/postcss-design-tokens/.tape.mjs
index 9bc5019b7..ad0f3c60d 100644
--- a/plugins/postcss-design-tokens/.tape.mjs
+++ b/plugins/postcss-design-tokens/.tape.mjs
@@ -11,6 +11,11 @@ postcssTape(plugin)({
plugin()
]
},
+ 'errors': {
+ message: "handles issues correctly",
+ options: {},
+ warnings: 4
+ },
'is': {
message: "supports basic usage",
options: {
diff --git a/plugins/postcss-design-tokens/src/data-formats/toposort/toposort.ts b/plugins/postcss-design-tokens/src/data-formats/toposort/toposort.ts
index 15f3c39be..b3890c1c8 100644
--- a/plugins/postcss-design-tokens/src/data-formats/toposort/toposort.ts
+++ b/plugins/postcss-design-tokens/src/data-formats/toposort/toposort.ts
@@ -31,7 +31,7 @@ export function toposort(nodes: Array, edges: Array>): Arr
// check for unknown nodes
edges.forEach(function (edge) {
if (!nodesHash.has(edge[0]) || !nodesHash.has(edge[1])) {
- throw new Error('Unknown node. There is an unknown node in the supplied edges.');
+ throw new Error('Unknown token. Make sure to provide all tokens used in aliases.');
}
});
@@ -47,7 +47,7 @@ export function toposort(nodes: Array, edges: Array>): Arr
if (predecessors.has(node)) {
let nodeRep;
try {
- nodeRep = ', node was:' + JSON.stringify(node);
+ nodeRep = ', token was: ' + JSON.stringify(node);
} catch (e) {
nodeRep = '';
}
@@ -55,7 +55,7 @@ export function toposort(nodes: Array, edges: Array>): Arr
}
if (!nodesHash.has(node)) {
- throw new Error('Found unknown node. Make sure to provided all involved nodes. Unknown node: ' + JSON.stringify(node));
+ throw new Error('Found unknown token. Make sure to provided all involved tokens. Unknown token: ' + JSON.stringify(node));
}
if (visited[j]) {
diff --git a/plugins/postcss-design-tokens/src/index.ts b/plugins/postcss-design-tokens/src/index.ts
index ccf415d0b..a6e724c19 100644
--- a/plugins/postcss-design-tokens/src/index.ts
+++ b/plugins/postcss-design-tokens/src/index.ts
@@ -1,4 +1,4 @@
-import type { PluginCreator } from 'postcss';
+import type { Node, PluginCreator } from 'postcss';
import { Token } from './data-formats/base/token';
import { tokensFromImport } from './data-formats/parse-import';
import { mergeTokens } from './data-formats/token';
@@ -26,10 +26,9 @@ const creator: PluginCreator = (opts?: pluginOptions) => {
importedFiles = new Set();
},
Once: async (root, { result }) => {
- const designTokenAtRules: Array<{filePath: string, params: string}> = [];
+ const designTokenAtRules: Array<{filePath: string, params: string, node: Node}> = [];
root.walkAtRules('design-tokens', (atRule) => {
if (!atRule?.source?.input?.file) {
- atRule.remove();
return;
}
@@ -40,12 +39,19 @@ const creator: PluginCreator = (opts?: pluginOptions) => {
designTokenAtRules.push({
filePath: filePath,
params: params,
+ node: atRule,
});
});
for (const atRule of designTokenAtRules.values()) {
- const importResult = await tokensFromImport(buildIs, atRule.filePath, atRule.params, importedFiles);
- if (!importResult) {
+ let importResult: { filePath: string, tokens: Map }|false;
+ try {
+ importResult = await tokensFromImport(buildIs, atRule.filePath, atRule.params, importedFiles);
+ if (!importResult) {
+ continue;
+ }
+ } catch (e) {
+ atRule.node.warn(result, `Failed to import design tokens from "${atRule.params}" with error:\n\t` + e.message);
continue;
}
diff --git a/plugins/postcss-design-tokens/test/errors.css b/plugins/postcss-design-tokens/test/errors.css
new file mode 100644
index 000000000..92fb33bab
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/errors.css
@@ -0,0 +1,4 @@
+@design-tokens url('./tokens/does-not-exist.json') format('style-dictionary3');
+@design-tokens url('./tokens/basic.json') format('does not exist');
+@design-tokens url('./tokens/circular.json') format('style-dictionary3');
+@design-tokens url('./tokens/missing.json') format('style-dictionary3');
diff --git a/plugins/postcss-design-tokens/test/errors.expect.css b/plugins/postcss-design-tokens/test/errors.expect.css
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/errors.expect.css
@@ -0,0 +1 @@
+
diff --git a/plugins/postcss-design-tokens/test/tokens/circular.json b/plugins/postcss-design-tokens/test/tokens/circular.json
new file mode 100644
index 000000000..805827672
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/tokens/circular.json
@@ -0,0 +1,6 @@
+{
+ "a": {
+ "1": { "value": "{a.2}" },
+ "2": { "value": "{a.1}" }
+ }
+}
diff --git a/plugins/postcss-design-tokens/test/tokens/missing.json b/plugins/postcss-design-tokens/test/tokens/missing.json
new file mode 100644
index 000000000..e08a0b0bc
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/tokens/missing.json
@@ -0,0 +1,5 @@
+{
+ "a": {
+ "1": { "value": "{a.2}" }
+ }
+}
From cb2b08f6642bf153865b45311ddbbc3754ec5005 Mon Sep 17 00:00:00 2001
From: romainmenke
Date: Thu, 21 Apr 2022 09:32:11 +0200
Subject: [PATCH 15/32] typo
---
.../src/data-formats/style-dictionary/v3/dereference.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/dereference.ts b/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/dereference.ts
index 2c2e12577..e32b3aa1e 100644
--- a/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/dereference.ts
+++ b/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/dereference.ts
@@ -67,7 +67,7 @@ export function dereferenceTokenValues(tokens: Map = Array.from(tokens.keys());
const edges: Array> = [];
From 93e6e6a7d0e6b19c66734a821ad0cfec685eea81 Mon Sep 17 00:00:00 2001
From: Romain Menke <11521496+romainmenke@users.noreply.github.com>
Date: Sat, 21 May 2022 10:46:41 +0200
Subject: [PATCH 16/32] design tokens : unit conversion (#388)
---
plugins/postcss-design-tokens/.tape.mjs | 12 +++-
.../src/data-formats/base/token.ts | 9 ++-
.../style-dictionary/v3/dereference.ts | 11 ++--
.../data-formats/style-dictionary/v3/value.ts | 64 ++++++++++++++++++-
plugins/postcss-design-tokens/src/index.ts | 6 +-
plugins/postcss-design-tokens/src/options.ts | 6 ++
plugins/postcss-design-tokens/src/values.ts | 41 +++++++++---
plugins/postcss-design-tokens/test/basic.css | 41 ++++++++++++
.../test/basic.expect.css | 30 +++++++++
.../test/basic.rootFontSize-20.expect.css | 39 +++++++++++
.../test/tokens/basic.json | 23 +++++++
11 files changed, 260 insertions(+), 22 deletions(-)
create mode 100644 plugins/postcss-design-tokens/src/options.ts
create mode 100644 plugins/postcss-design-tokens/test/basic.rootFontSize-20.expect.css
diff --git a/plugins/postcss-design-tokens/.tape.mjs b/plugins/postcss-design-tokens/.tape.mjs
index ad0f3c60d..909449c6b 100644
--- a/plugins/postcss-design-tokens/.tape.mjs
+++ b/plugins/postcss-design-tokens/.tape.mjs
@@ -5,12 +5,22 @@ import postcssImport from 'postcss-import';
postcssTape(plugin)({
basic: {
message: "supports basic usage",
- options: {},
plugins: [
postcssImport(),
plugin()
]
},
+ 'basic:rootFontSize-20': {
+ message: "supports basic usage with { unitsAndValues { rootFontSize: 20 } }",
+ plugins: [
+ postcssImport(),
+ plugin({
+ unitsAndValues: {
+ rootFontSize: 20
+ }
+ })
+ ]
+ },
'errors': {
message: "handles issues correctly",
options: {},
diff --git a/plugins/postcss-design-tokens/src/data-formats/base/token.ts b/plugins/postcss-design-tokens/src/data-formats/base/token.ts
index ac789984f..5b1a12f1e 100644
--- a/plugins/postcss-design-tokens/src/data-formats/base/token.ts
+++ b/plugins/postcss-design-tokens/src/data-formats/base/token.ts
@@ -1,3 +1,10 @@
+export interface TokenTransformOptions {
+ pluginOptions?: {
+ rootFontSize?: number;
+ };
+ toUnit?: string;
+}
+
export interface Token {
- cssValue(): string
+ cssValue(opts?: TokenTransformOptions): string
}
diff --git a/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/dereference.ts b/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/dereference.ts
index e32b3aa1e..897bed1bf 100644
--- a/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/dereference.ts
+++ b/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/dereference.ts
@@ -1,5 +1,6 @@
+import { TokenTransformOptions } from '../../base/token';
import { toposort } from '../../toposort/toposort';
-import { StyleDictionaryV3TokenValue } from './value';
+import { applyTransformsToValue, StyleDictionaryV3TokenValue } from './value';
export function dereferenceTokenValues(tokens: Map): Map {
const tainted = new Set();
@@ -53,8 +54,8 @@ export function dereferenceTokenValues(tokens: Map {
- return value ?? '';
+ currentToken.cssValue = (transformOptions: TokenTransformOptions) => {
+ return applyTransformsToValue(value, transformOptions);
};
tokens.set(id, currentToken);
@@ -125,8 +126,8 @@ export function dereferenceTokenValues(tokens: Map {
- return value ?? '';
+ currentToken.cssValue = (transformOptions: TokenTransformOptions) => {
+ return applyTransformsToValue(value, transformOptions);
};
tokens.set(id, currentToken);
diff --git a/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/value.ts b/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/value.ts
index 8b96fbefe..9f3a5a1fe 100644
--- a/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/value.ts
+++ b/plugins/postcss-design-tokens/src/data-formats/style-dictionary/v3/value.ts
@@ -1,5 +1,8 @@
+import { TokenTransformOptions } from '../../base/token';
+import valueParser from 'postcss-value-parser';
+
export type StyleDictionaryV3TokenValue = {
- cssValue(): string
+ cssValue(transformOptions?: TokenTransformOptions): string
// The value of the design token. This can be any type of data, a hex string, an integer, a file path to a file, even an object or array.
value: unknown
// Usually the name for a design token is generated with a name transform, but you can write your own if you choose. By default Style Dictionary will add a default name which is the key of the design token object.
@@ -33,8 +36,8 @@ export function extractStyleDictionaryV3Token(node: unknown, key: string, path:
return {
value: value,
- cssValue: () => {
- return value ?? '';
+ cssValue: (transformOptions?: TokenTransformOptions) => {
+ return applyTransformsToValue(value, transformOptions);
},
name: node['name'] ?? key,
comment: node['comment'] ?? undefined,
@@ -46,3 +49,58 @@ export function extractStyleDictionaryV3Token(node: unknown, key: string, path:
},
};
}
+
+export function applyTransformsToValue(value: string|undefined|null, transformOptions?: TokenTransformOptions): string {
+ if (!value) {
+ return '';
+ }
+
+ if (!transformOptions) {
+ return value;
+ }
+
+ if (transformOptions.toUnit) {
+ const dimension = valueParser.unit(value ?? '');
+ if (!dimension || dimension.unit === transformOptions.toUnit) {
+ return `${value}`;
+ }
+
+ if (dimension.unit === 'rem' && transformOptions.toUnit === 'px') {
+ return remToPx(parseFloat(dimension.number), transformOptions.pluginOptions?.rootFontSize ?? 16);
+ }
+
+ if (dimension.unit === 'px' && transformOptions.toUnit === 'rem') {
+ return pxToRem(parseFloat(dimension.number), transformOptions.pluginOptions?.rootFontSize ?? 16);
+ }
+ }
+
+ return value;
+}
+
+function remToPx(value: number, rootFontSize: number): string {
+ return `${formatFloat(value * rootFontSize)}px`;
+}
+
+function pxToRem(value: number, rootFontSize: number): string {
+ return `${formatFloat(value / rootFontSize)}rem`;
+}
+
+function formatFloat(value: number): string {
+ if (Number.isInteger(value)) {
+ return value.toString();
+ }
+
+ let fixedPrecision = value.toFixed(5);
+ for (let i = fixedPrecision.length; i > 0; i--) {
+ if (fixedPrecision[i] === '.') {
+ break;
+ }
+
+ if (fixedPrecision[i] !== '0') {
+ fixedPrecision = fixedPrecision.slice(0, i + 1);
+ continue;
+ }
+ }
+
+ return fixedPrecision;
+}
diff --git a/plugins/postcss-design-tokens/src/index.ts b/plugins/postcss-design-tokens/src/index.ts
index a6e724c19..580defa0a 100644
--- a/plugins/postcss-design-tokens/src/index.ts
+++ b/plugins/postcss-design-tokens/src/index.ts
@@ -2,11 +2,9 @@ import type { Node, PluginCreator } from 'postcss';
import { Token } from './data-formats/base/token';
import { tokensFromImport } from './data-formats/parse-import';
import { mergeTokens } from './data-formats/token';
+import { pluginOptions } from './options';
import { onCSSValue } from './values';
-type pluginOptions = {
- is?: Array
-}
const creator: PluginCreator = (opts?: pluginOptions) => {
const buildIs = opts?.is ?? [];
@@ -70,7 +68,7 @@ const creator: PluginCreator = (opts?: pluginOptions) => {
return;
}
- const modifiedValue = onCSSValue(tokens, result, decl);
+ const modifiedValue = onCSSValue(tokens, result, decl, opts);
if (modifiedValue === decl.value) {
return;
}
diff --git a/plugins/postcss-design-tokens/src/options.ts b/plugins/postcss-design-tokens/src/options.ts
new file mode 100644
index 000000000..1d4dcb734
--- /dev/null
+++ b/plugins/postcss-design-tokens/src/options.ts
@@ -0,0 +1,6 @@
+export type pluginOptions = {
+ is?: Array
+ unitsAndValues?: {
+ rootFontSize?: number
+ }
+}
diff --git a/plugins/postcss-design-tokens/src/values.ts b/plugins/postcss-design-tokens/src/values.ts
index e1db3194d..22cc949dc 100644
--- a/plugins/postcss-design-tokens/src/values.ts
+++ b/plugins/postcss-design-tokens/src/values.ts
@@ -1,8 +1,9 @@
import type { Declaration, Result } from 'postcss';
import valueParser from 'postcss-value-parser';
-import { Token } from './data-formats/base/token';
+import { Token, TokenTransformOptions } from './data-formats/base/token';
+import { pluginOptions } from './options';
-export function onCSSValue(tokens: Map, result: Result, decl: Declaration) {
+export function onCSSValue(tokens: Map, result: Result, decl: Declaration, opts?: pluginOptions) {
const valueAST = valueParser(decl.value);
valueAST.walk(node => {
@@ -10,23 +11,47 @@ export function onCSSValue(tokens: Map, result: Result, decl: Dec
return;
}
- if (!node.nodes || node.nodes.length !== 1) {
- decl.warn(result, 'Expected a single string literal for the design-token function.');
+ if (!node.nodes || node.nodes.length === 0) {
+ decl.warn(result, 'Expected at least a single string literal for the design-token function.');
return;
}
if (node.nodes[0].type !== 'string') {
- decl.warn(result, 'Expected a single string literal for the design-token function.');
+ decl.warn(result, 'Expected at least a single string literal for the design-token function.');
return;
}
- const replacement = tokens.get(node.nodes[0].value);
+ const tokenName = node.nodes[0].value;
+ const replacement = tokens.get(tokenName);
if (!replacement) {
- decl.warn(result, `design-token: "${node.nodes[0].value}" is not configured.`);
+ decl.warn(result, `design-token: "${tokenName}" is not configured.`);
return;
}
- node.value = replacement.cssValue();
+ const remainingNodes = node.nodes.slice(1).filter(x => x.type === 'word');
+ if (!remainingNodes.length) {
+ node.value = replacement.cssValue();
+ node.nodes = undefined;
+ return;
+ }
+
+ const transformOptions: TokenTransformOptions = {
+ pluginOptions: opts?.unitsAndValues,
+ };
+ for (let i = 0; i < remainingNodes.length; i++) {
+ if (
+ remainingNodes[i].type === 'word' &&
+ remainingNodes[i].value === 'to' &&
+ remainingNodes[i + 1] &&
+ remainingNodes[i + 1].type === 'word' &&
+ ['px', 'rem'].includes(remainingNodes[i + 1].value)
+ ) {
+ transformOptions.toUnit = remainingNodes[i + 1].value;
+ i++;
+ }
+ }
+
+ node.value = replacement.cssValue(transformOptions);
node.nodes = undefined;
});
diff --git a/plugins/postcss-design-tokens/test/basic.css b/plugins/postcss-design-tokens/test/basic.css
index 3f9ddf7f4..9fc8643f7 100644
--- a/plugins/postcss-design-tokens/test/basic.css
+++ b/plugins/postcss-design-tokens/test/basic.css
@@ -10,4 +10,45 @@
.card {
background-color: design-token('card.background');
color: design-token('card.foreground');
+ color: design-token( 'card.foreground');
+ color: design-token('card.foreground' );
+ color: design-token(
+ /* a foreground color */
+ 'card.foreground'
+ );
+ color: design-token(
+ 'card.foreground'
+ /* a foreground color */
+ );
+}
+
+.px-to-px {
+ padding-bottom: design-token('space.small' to px);
+ padding-bottom: design-token('space.default' to px);
+ padding-bottom: design-token('space.large' to px);
+}
+
+.px-to-rem {
+ padding-bottom: design-token('space.small' to rem);
+ padding-bottom: design-token('space.default' to rem);
+ padding-bottom: design-token('space.large' to rem);
+}
+
+.rem-to-rem {
+ padding-bottom: design-token('space.small-b' to rem);
+ padding-bottom: design-token('space.default-b' to rem);
+ padding-bottom: design-token('space.large-b' to rem);
+}
+
+.rem-to-px {
+ padding-bottom: design-token('space.small-b' to px);
+ padding-bottom: design-token('space.default-b' to px);
+ padding-bottom: design-token('space.large-b' to px);
+}
+
+.invalid-conversion {
+ color: design-token('card.foreground' to rem);
+ color: design-token('card.foreground' to px);
+ color: design-token('space.lh' to rem);
+ color: design-token('space.lh' to px);
}
diff --git a/plugins/postcss-design-tokens/test/basic.expect.css b/plugins/postcss-design-tokens/test/basic.expect.css
index 459bcb6ad..d4b93d305 100644
--- a/plugins/postcss-design-tokens/test/basic.expect.css
+++ b/plugins/postcss-design-tokens/test/basic.expect.css
@@ -6,4 +6,34 @@
.card {
background-color: blue;
color: red;
+ color: red;
+ color: red;
+ color: red;
+ color: red;
+}
+.px-to-px {
+ padding-bottom: 8px;
+ padding-bottom: 18px;
+ padding-bottom: 32px;
+}
+.px-to-rem {
+ padding-bottom: 0.5rem;
+ padding-bottom: 1.1rem;
+ padding-bottom: 2rem;
+}
+.rem-to-rem {
+ padding-bottom: 0.5rem;
+ padding-bottom: 1.125rem;
+ padding-bottom: 2rem;
+}
+.rem-to-px {
+ padding-bottom: 8px;
+ padding-bottom: 18px;
+ padding-bottom: 32px;
+}
+.invalid-conversion {
+ color: red;
+ color: red;
+ color: 1lh;
+ color: 1lh;
}
diff --git a/plugins/postcss-design-tokens/test/basic.rootFontSize-20.expect.css b/plugins/postcss-design-tokens/test/basic.rootFontSize-20.expect.css
new file mode 100644
index 000000000..d1915ac36
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/basic.rootFontSize-20.expect.css
@@ -0,0 +1,39 @@
+.foo {
+ font-family: Helvetica sans;
+ font-size: 10;
+ color: #111111;
+}
+.card {
+ background-color: blue;
+ color: red;
+ color: red;
+ color: red;
+ color: red;
+ color: red;
+}
+.px-to-px {
+ padding-bottom: 8px;
+ padding-bottom: 18px;
+ padding-bottom: 32px;
+}
+.px-to-rem {
+ padding-bottom: 0.4rem;
+ padding-bottom: 0.9rem;
+ padding-bottom: 1.6rem;
+}
+.rem-to-rem {
+ padding-bottom: 0.5rem;
+ padding-bottom: 1.125rem;
+ padding-bottom: 2rem;
+}
+.rem-to-px {
+ padding-bottom: 10px;
+ padding-bottom: 22.5px;
+ padding-bottom: 40px;
+}
+.invalid-conversion {
+ color: red;
+ color: red;
+ color: 1lh;
+ color: 1lh;
+}
diff --git a/plugins/postcss-design-tokens/test/tokens/basic.json b/plugins/postcss-design-tokens/test/tokens/basic.json
index 925b731ff..1ec411322 100644
--- a/plugins/postcss-design-tokens/test/tokens/basic.json
+++ b/plugins/postcss-design-tokens/test/tokens/basic.json
@@ -18,5 +18,28 @@
"logical-color": {
"foreground": { "value": "{base-color.red}" },
"background": { "value": "{base-color.blue}" }
+ },
+ "space": {
+ "small": {
+ "value": "8px"
+ },
+ "default": {
+ "value": "18px"
+ },
+ "large": {
+ "value": "32px"
+ },
+ "small-b": {
+ "value": "0.5rem"
+ },
+ "default-b": {
+ "value": "1.125rem"
+ },
+ "large-b": {
+ "value": "2rem"
+ },
+ "lh": {
+ "value": "1lh"
+ }
}
}
From 42e317f32fb7176c202b7f86d077f1c29537dbee Mon Sep 17 00:00:00 2001
From: romainmenke
Date: Sat, 21 May 2022 10:57:12 +0200
Subject: [PATCH 17/32] update docs
---
plugins/postcss-design-tokens/.tape.mjs | 8 ++++
plugins/postcss-design-tokens/README.md | 44 +++++++++++++++++++
plugins/postcss-design-tokens/docs/README.md | 25 +++++++++++
.../test/examples/example.css | 2 +
.../test/examples/example.expect.css | 2 +
.../example.rootFontSize-20.expect.css | 5 +++
.../test/examples/tokens.json | 5 +++
7 files changed, 91 insertions(+)
create mode 100644 plugins/postcss-design-tokens/test/examples/example.rootFontSize-20.expect.css
diff --git a/plugins/postcss-design-tokens/.tape.mjs b/plugins/postcss-design-tokens/.tape.mjs
index 909449c6b..a54df4608 100644
--- a/plugins/postcss-design-tokens/.tape.mjs
+++ b/plugins/postcss-design-tokens/.tape.mjs
@@ -46,4 +46,12 @@ postcssTape(plugin)({
is: ['dark']
},
},
+ 'examples/example:rootFontSize-20': {
+ message: "minimal example with { unitsAndValues { rootFontSize: 20 } }",
+ options: {
+ unitsAndValues: {
+ rootFontSize: 20
+ }
+ }
+ },
});
diff --git a/plugins/postcss-design-tokens/README.md b/plugins/postcss-design-tokens/README.md
index f99cd463d..a440e0aa2 100644
--- a/plugins/postcss-design-tokens/README.md
+++ b/plugins/postcss-design-tokens/README.md
@@ -13,6 +13,11 @@
"background": {
"primary": { "value": "#fff" }
}
+ },
+ "size": {
+ "spacing": {
+ "small": { "value": "16px" }
+ }
}
}
```
@@ -22,12 +27,16 @@
.foo {
color: design-token('color.background.primary');
+ padding-left: design-token('size.spacing.small' to px);
+ padding-bottom: design-token('size.spacing.small' to rem);
}
/* becomes */
.foo {
color: #fff;
+ padding-left: 16px;
+ padding-bottom: 1rem;
}
```
@@ -144,6 +153,41 @@ postcssDesignTokens({ is: ['dark'] })
}
```
+### unitsAndValues
+
+The `unitsAndValues` option allows you to control some aspects of how design values are converted to CSS.
+`rem` <-> `px` for example can only be calculated when we know the root font size.
+
+#### rootFontSize
+
+defaults to `16`
+
+```js
+postcssDesignTokens({
+ unitsAndValues: {
+ rootFontSize: 20,
+ },
+})
+```
+
+```pcss
+@design-tokens url('./tokens.json') format('style-dictionary3');
+
+.foo {
+ color: design-token('color.background.primary');
+ padding-left: design-token('size.spacing.small' to px);
+ padding-bottom: design-token('size.spacing.small' to rem);
+}
+
+/* becomes */
+
+.foo {
+ color: #fff;
+ padding-left: 16px;
+ padding-bottom: 0.8rem;
+}
+```
+
[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
[css-url]: https://cssdb.org/#TODO
[discord]: https://discord.gg/bUadyRwkJS
diff --git a/plugins/postcss-design-tokens/docs/README.md b/plugins/postcss-design-tokens/docs/README.md
index ac3c9bced..9a2a597e1 100644
--- a/plugins/postcss-design-tokens/docs/README.md
+++ b/plugins/postcss-design-tokens/docs/README.md
@@ -89,5 +89,30 @@ By default only `@design-tokens` without any `when('foo')` conditions are used.
```
+### unitsAndValues
+
+The `unitsAndValues` option allows you to control some aspects of how design values are converted to CSS.
+`rem` <-> `px` for example can only be calculated when we know the root font size.
+
+#### rootFontSize
+
+defaults to `16`
+
+```js
+({
+ unitsAndValues: {
+ rootFontSize: 20,
+ },
+})
+```
+
+```pcss
+
+
+/* becomes */
+
+
+```
+
[CSS Specification]:
diff --git a/plugins/postcss-design-tokens/test/examples/example.css b/plugins/postcss-design-tokens/test/examples/example.css
index c5d97f83a..54326026c 100644
--- a/plugins/postcss-design-tokens/test/examples/example.css
+++ b/plugins/postcss-design-tokens/test/examples/example.css
@@ -2,4 +2,6 @@
.foo {
color: design-token('color.background.primary');
+ padding-left: design-token('size.spacing.small' to px);
+ padding-bottom: design-token('size.spacing.small' to rem);
}
diff --git a/plugins/postcss-design-tokens/test/examples/example.expect.css b/plugins/postcss-design-tokens/test/examples/example.expect.css
index 33a418a59..2581d8fa1 100644
--- a/plugins/postcss-design-tokens/test/examples/example.expect.css
+++ b/plugins/postcss-design-tokens/test/examples/example.expect.css
@@ -1,3 +1,5 @@
.foo {
color: #fff;
+ padding-left: 16px;
+ padding-bottom: 1rem;
}
diff --git a/plugins/postcss-design-tokens/test/examples/example.rootFontSize-20.expect.css b/plugins/postcss-design-tokens/test/examples/example.rootFontSize-20.expect.css
new file mode 100644
index 000000000..d6252800d
--- /dev/null
+++ b/plugins/postcss-design-tokens/test/examples/example.rootFontSize-20.expect.css
@@ -0,0 +1,5 @@
+.foo {
+ color: #fff;
+ padding-left: 16px;
+ padding-bottom: 0.8rem;
+}
diff --git a/plugins/postcss-design-tokens/test/examples/tokens.json b/plugins/postcss-design-tokens/test/examples/tokens.json
index 9776bbaed..1ff537046 100644
--- a/plugins/postcss-design-tokens/test/examples/tokens.json
+++ b/plugins/postcss-design-tokens/test/examples/tokens.json
@@ -3,5 +3,10 @@
"background": {
"primary": { "value": "#fff" }
}
+ },
+ "size": {
+ "spacing": {
+ "small": { "value": "16px" }
+ }
}
}
From 6e00a6bc9bbd16f6ca81f69c1f5ca91b4248c931 Mon Sep 17 00:00:00 2001
From: romainmenke
Date: Sat, 21 May 2022 11:32:45 +0200
Subject: [PATCH 18/32] add syntax docs
---
plugins/postcss-design-tokens/README.md | 44 ++++++++++++++++++++
plugins/postcss-design-tokens/docs/README.md | 44 ++++++++++++++++++++
2 files changed, 88 insertions(+)
diff --git a/plugins/postcss-design-tokens/README.md b/plugins/postcss-design-tokens/README.md
index a440e0aa2..9f14df1cc 100644
--- a/plugins/postcss-design-tokens/README.md
+++ b/plugins/postcss-design-tokens/README.md
@@ -188,6 +188,50 @@ postcssDesignTokens({
}
```
+## Syntax
+
+[PostCSS Design Tokens] is non-standard and is not part of any official CSS Specification.
+
+### `@design-tokens` rule
+
+The `@design-tokens` rule is used to import design tokens from a JSON file into your CSS.
+
+```pcss
+@design-tokens url('./tokens.json') format('style-dictionary3');
+```
+
+```pcss
+@design-tokens url('./tokens.json') format('style-dictionary3');
+@design-tokens url('./tokens-dark-mode.json') format('style-dictionary3') when('dark');
+```
+
+```
+@design-tokens [ | ]
+ [ when() ]?
+ format();
+
+ =
+
+ = [ 'style-dictionary3' ]
+```
+
+### `design-token()` function
+
+The `design-token()` function takes a token path and returns the token value.
+
+```pcss
+.foo {
+ color: design-token('color.background.primary');
+}
+```
+
+```
+design-token() = design-token( [ to ]? )
+
+ =
+ = [ px | rem ]
+```
+
[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
[css-url]: https://cssdb.org/#TODO
[discord]: https://discord.gg/bUadyRwkJS
diff --git a/plugins/postcss-design-tokens/docs/README.md b/plugins/postcss-design-tokens/docs/README.md
index 9a2a597e1..f9a4c2b59 100644
--- a/plugins/postcss-design-tokens/docs/README.md
+++ b/plugins/postcss-design-tokens/docs/README.md
@@ -114,5 +114,49 @@ defaults to `16`
```
+## Syntax
+
+[] is non-standard and is not part of any official CSS Specification.
+
+### `@design-tokens` rule
+
+The `@design-tokens` rule is used to import design tokens from a JSON file into your CSS.
+
+```pcss
+@design-tokens url('./tokens.json') format('style-dictionary3');
+```
+
+```pcss
+@design-tokens url('./tokens.json') format('style-dictionary3');
+@design-tokens url('./tokens-dark-mode.json') format('style-dictionary3') when('dark');
+```
+
+```
+@design-tokens [ | ]
+ [ when() ]?
+ format();
+
+ =
+
+ = [ 'style-dictionary3' ]
+```
+
+### `design-token()` function
+
+The `design-token()` function takes a token path and returns the token value.
+
+```pcss
+.foo {
+ color: design-token('color.background.primary');
+}
+```
+
+```
+design-token() = design-token( [ to ]? )
+
+ =
+ = [ px | rem ]
+```
+
[CSS Specification]:
From da16cd6ae94f3ea3065885969fe1eda9070cafb0 Mon Sep 17 00:00:00 2001
From: romainmenke
Date: Sat, 21 May 2022 12:29:48 +0200
Subject: [PATCH 19/32] update docs
---
.github/bin/generate-docs/readme.mjs | 12 ++++++++++--
plugins/postcss-base-plugin/README.md | 2 +-
plugins/postcss-base-plugin/docs/README.md | 2 +-
plugins/postcss-design-tokens/README.md | 7 +++----
plugins/postcss-design-tokens/docs/README.md | 3 +--
plugins/postcss-design-tokens/package.json | 4 +---
6 files changed, 17 insertions(+), 13 deletions(-)
diff --git a/.github/bin/generate-docs/readme.mjs b/.github/bin/generate-docs/readme.mjs
index 957e7a6b0..69379812c 100644
--- a/.github/bin/generate-docs/readme.mjs
+++ b/.github/bin/generate-docs/readme.mjs
@@ -29,7 +29,11 @@ installDoc = installDoc.replace(`
installDoc = installDoc.replace('', `# [
][postcss]
[
][npm-url]
-[
][css-url]
+${
+ packageJSONInfo.csstools?.cssdbId ?
+ `[
][css-url]` :
+ ''
+}
[
][cli-url]
[
][discord]`);
@@ -62,7 +66,11 @@ instructions for:
// Insert "Link List" section
installDoc = installDoc.replace('', `[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
-[css-url]: https://cssdb.org/#
+${
+ packageJSONInfo.csstools?.cssdbId ?
+ `[css-url]: https://cssdb.org/#` :
+ ''
+}
[discord]: https://discord.gg/bUadyRwkJS
[npm-url]: https://www.npmjs.com/package/
diff --git a/plugins/postcss-base-plugin/README.md b/plugins/postcss-base-plugin/README.md
index 0d18299e7..69f952683 100644
--- a/plugins/postcss-base-plugin/README.md
+++ b/plugins/postcss-base-plugin/README.md
@@ -5,7 +5,7 @@
[
][cli-url]
[
][discord]
-[PostCSS Base Plugin] lets easily create new plugins following some [CSS Specification].
+[PostCSS Base Plugin] lets you easily create new plugins following some [CSS Specification].
```pcss
.foo {
diff --git a/plugins/postcss-base-plugin/docs/README.md b/plugins/postcss-base-plugin/docs/README.md
index 3bc455892..8680a65c8 100644
--- a/plugins/postcss-base-plugin/docs/README.md
+++ b/plugins/postcss-base-plugin/docs/README.md
@@ -14,7 +14,7 @@
-[] lets easily create new plugins following some [CSS Specification].
+[] lets you easily create new plugins following some [CSS Specification].
```pcss