Skip to content

Commit

Permalink
chore: support TS config file, extract loadFile util, add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jorenbroekema committed Nov 25, 2024
1 parent daa93b3 commit f29c831
Show file tree
Hide file tree
Showing 15 changed files with 362 additions and 105 deletions.
2 changes: 1 addition & 1 deletion .changeset/gentle-rocks-compete.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
'style-dictionary': patch
---

init native .ts file processing
Add support for native .TS token & config file processing.
16 changes: 16 additions & 0 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,19 @@ jobs:

- name: Performance tests
run: npm run test:perf
verify-strip-types:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Node 22.6.0
uses: actions/setup-node@v4
with:
node-version: 22.6.0
cache: 'npm'

- name: Install Dependencies
run: npm ci

- name: Node Strip types tests
run: npm run test:strip-types
3 changes: 2 additions & 1 deletion __tests__/StyleDictionary.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,9 @@ describe('StyleDictionary class', () => {
});

describe('method signature', () => {
it('should accept a string as a path to a JSON file', () => {
it('should accept a string as a path to a JSON file', async () => {
const StyleDictionaryExtended = new StyleDictionary('__tests__/__configs/test.json');
await StyleDictionaryExtended.hasInitialized;
expect(StyleDictionaryExtended).to.have.nested.property('platforms.web');
});

Expand Down
101 changes: 101 additions & 0 deletions __tests__/__configs/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import type { Config } from 'style-dictionary/types';

const cfg: Config = {
source: ['__tests__/__json_files/*.ts'],
platforms: {
web: {
transformGroup: 'web',
prefix: 'smop',
buildPath: '__tests__/__output/web/',
files: [
{
destination: '_icons.css',
format: 'scss/icons',
},
{
destination: '_variables.css',
format: 'scss/variables',
},
{
destination: '_styles.js',
format: 'javascript/module',
},
],
},
scss: {
transformGroup: 'scss',
prefix: 'smop',
buildPath: '__tests__/__output/scss/',
files: [
{
destination: '_icons.scss',
format: 'scss/icons',
},
{
destination: '_variables.scss',
format: 'scss/variables',
},
],
},
less: {
transformGroup: 'less',
prefix: 'smop',
buildPath: '__tests__/__output/less/',
files: [
{
destination: '_icons.less',
format: 'less/icons',
},
{
destination: '_variables.less',
format: 'less/variables',
},
],
},
android: {
transformGroup: 'android',
buildPath: '__tests__/__output/',
files: [
{
destination: 'android/colors.xml',
format: 'android/colors',
},
{
destination: 'android/font_dimen.xml',
format: 'android/fontDimens',
},
{
destination: 'android/dimens.xml',
format: 'android/dimens',
},
],
actions: ['android/copyImages'],
},
ios: {
transformGroup: 'ios',
buildPath: '__tests__/__output/ios/',
files: [
{
destination: 'style_dictionary.plist',
format: 'ios/plist',
},
{
destination: 'style_dictionary.h',
format: 'ios/macros',
},
],
},
'react-native': {
transformGroup: 'react-native',
buildPath: '__tests__/__output/react-native/',
files: [
{
destination: 'style_dictionary.js',
format: 'javascript/es6',
},
],
},
},
}

export default cfg;
7 changes: 7 additions & 0 deletions __tests__/__json_files/shallow/5.topojson
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"jsonCA": 5,
// some comment
"d": {
"jsonCe": 1
}
}
10 changes: 10 additions & 0 deletions __tests__/__json_files/tokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default {
colors: {
$type: "color",
red: {
500: {
$value: '#ff0000'
}
}
}
}
21 changes: 21 additions & 0 deletions __tests__/strip-types-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import assert from 'node:assert';
import StyleDictionary from 'style-dictionary';

// Just a quick and dirty smoke test to check that the experimental strip type flag allows using TS tokens files

// this config also uses ".ts" tokens paths
const sd = new StyleDictionary('__tests__/__configs/test.ts');
await sd.hasInitialized;

assert.deepEqual(sd.tokens, {
colors: {
red: {
500: {
$type: 'color',
$value: '#ff0000',
filePath: '__tests__/__json_files/tokens.ts',
isSource: true,
},
},
},
});
19 changes: 19 additions & 0 deletions __tests__/utils/__snapshots__/combineJSON.test.snap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* @web/test-runner snapshot v1 */
export const snapshots = {};

snapshots["utils combineJSON should support custom json extensions by warning about unrecognized file extension, using JSON5 parser as fallback"] =
`Unrecognized file extension: .topojson. Using JSON5 parser as a default. Alternatively, create a custom parser to handle this filetype https://styledictionary.com/reference/hooks/parsers/`;
/* end snapshot utils combineJSON should support custom json extensions by warning about unrecognized file extension, using JSON5 parser as fallback */
snapshots["utils combineJSON should throw error if it tries to import TS files with unsupported Node env"] =
`Failed to load or parse JSON or JS Object: Could not import TypeScript file: /Users/joren/code/style-dictionary/__tests__/__json_files/tokens.ts
Executing typescript files during runtime is only possible via
- 'node >= 22.6.0' with '--experimental-strip-types'
Alternatively by using 'deno' or 'bun'
If you are not able to satisfy the above requirements, consider transpiling the TypeScript file to plain JavaScript before running the Style Dictionary build process.
Unknown file extension ".ts" for /Users/joren/code/style-dictionary/__tests__/__json_files/tokens.ts`;
/* end snapshot utils combineJSON should throw error if it tries to import TS files with unsupported Node env */

22 changes: 22 additions & 0 deletions __tests__/utils/__snapshots__/loadFile.test.snap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* @web/test-runner snapshot v1 */
export const snapshots = {};

snapshots["utils loadFile should support custom json extensions by warning about unrecognized file extension, using JSON5 parser as fallback"] =
`Unrecognized file extension: .topojson. Using JSON5 parser as a default. Alternatively, create a custom parser to handle this filetype https://styledictionary.com/reference/hooks/parsers/`;
/* end snapshot utils loadFile should support custom json extensions by warning about unrecognized file extension, using JSON5 parser as fallback */

snapshots["utils loadFile should throw error if it tries to import TS files with unsupported Node env"] =
`Failed to load or parse JSON or JS Object:
Could not import TypeScript file: /Users/joren/code/style-dictionary/__tests__/__json_files/tokens.ts
Executing typescript files during runtime is only possible via
- 'node >= 22.6.0' with '--experimental-strip-types'
Alternatively by using 'deno' or 'bun'
If you are not able to satisfy the above requirements, consider transpiling the TypeScript file to plain JavaScript before running the Style Dictionary build process.
Unknown file extension ".ts" for /Users/joren/code/style-dictionary/__tests__/__json_files/tokens.ts`;
/* end snapshot utils loadFile should throw error if it tries to import TS files with unsupported Node env */

21 changes: 2 additions & 19 deletions __tests__/utils/combineJSON.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { join } from 'path-unified';
import yaml from 'yaml';
import { expectThrowsAsync } from '../__helpers.js';
import combineJSON from '../../lib/utils/combineJSON.js';
import { stubMethod, restore } from 'hanbi';
import { isNode } from '../../lib/utils/isNode.js';

describe('utils', () => {
describe('combineJSON', () => {
Expand Down Expand Up @@ -75,13 +77,6 @@ describe('utils', () => {
});
});

it('should fail on invalid JSON', async () => {
await expectThrowsAsync(
() => combineJSON(['__tests__/__json_files/broken/*.json']),
"Failed to load or parse JSON or JS Object: JSON5: invalid character '!' at 2:18",
);
});

it('should fail if there is a collision and it is passed a collision function', async () => {
await expectThrowsAsync(
() =>
Expand All @@ -95,18 +90,6 @@ describe('utils', () => {
);
});

it('should support json5', async () => {
const { tokens } = await combineJSON(['__tests__/__json_files/shallow/*.json5']);
expect(tokens).to.have.property('json5A', 5);
expect(tokens.d).to.have.property('json5e', 1);
});

it('should support jsonc', async () => {
const { tokens } = await combineJSON(['__tests__/__json_files/shallow/*.jsonc']);
expect(tokens).to.have.property('jsonCA', 5);
expect(tokens.d).to.have.property('jsonCe', 1);
});

describe('custom parsers', () => {
it('should support yaml.parse', async () => {
const parsers = {
Expand Down
61 changes: 61 additions & 0 deletions __tests__/utils/loadFile.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/
import { expect } from 'chai';
import { expectThrowsAsync } from '../__helpers.js';
import { loadFile } from '../../lib/utils/loadFile.js';
import { stubMethod, restore } from 'hanbi';
import { isNode } from '../../lib/utils/isNode.js';

describe('utils', () => {
describe('loadFile', () => {
it('should fail on invalid JSON', async () => {
await expectThrowsAsync(
() => loadFile('__tests__/__json_files/broken/broken.json'),
"Failed to load or parse JSON or JS Object:\n\nJSON5: invalid character '!' at 2:18",
);
});

it('should support json5', async () => {
const tokens = await loadFile('__tests__/__json_files/shallow/3.json5');
expect(tokens).to.have.property('json5A', 5);
expect(tokens.d).to.have.property('json5e', 1);
});

it('should support jsonc', async () => {
const tokens = await loadFile('__tests__/__json_files/shallow/4.jsonc');
expect(tokens).to.have.property('jsonCA', 5);
expect(tokens.d).to.have.property('jsonCe', 1);
});

it('should throw error if it tries to import TS files with unsupported Node env', async () => {
if (isNode) {
let err;
try {
await loadFile('__tests__/__json_files/tokens.ts');
} catch (e) {
err = e;
}
await expect(err.message).to.matchSnapshot();
}
});

it('should support custom json extensions by warning about unrecognized file extension, using JSON5 parser as fallback', async () => {
const stub = stubMethod(console, 'warn');
const tokens = await loadFile('__tests__/__json_files/shallow/5.topojson');
expect(tokens).to.have.property('jsonCA', 5);
expect(tokens.d).to.have.property('jsonCe', 1);
await expect([...stub.calls][0].args[0]).to.matchSnapshot();
restore();
});
});
});
18 changes: 2 additions & 16 deletions lib/StyleDictionary.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import cleanFiles from './cleanFiles.js';
import cleanDirs from './cleanDirs.js';
import cleanActions from './cleanActions.js';
import { isNode } from './utils/isNode.js';
import { loadFile } from './utils/loadFile.js';

/**
* @typedef {import('../types/Volume.d.ts').Volume} Volume
Expand Down Expand Up @@ -217,22 +218,7 @@ export default class StyleDictionary extends Register {
// Overloaded method, can accept a string as a path that points to a JS or
// JSON file or a plain object. Potentially refactor.
if (typeof config === 'string') {
// get ext name without leading .
const ext = extname(config).replace(/^\./, '');
// import path in Node has to be relative to cwd, in browser to root
const cfgFilePath = resolve(config, this.volume.__custom_fs__);
if (['json', 'json5', 'jsonc'].includes(ext)) {
options = JSON5.parse(
/** @type {string} */ (this.volume.readFileSync(cfgFilePath, 'utf-8')),
);
} else {
let _filePath = cfgFilePath;
if (isNode && process?.platform === 'win32') {
// Windows FS compatibility. If in browser, we use an FS shim which doesn't require this Windows workaround
_filePath = new URL(`file:///${_filePath}`).href;
}
options = (await import(/* @vite-ignore */ /* webpackIgnore: true */ _filePath)).default;
}
options = /** @type {Config} */ (await loadFile(config, this.volume));
} else {
options = config;
}
Expand Down
Loading

0 comments on commit f29c831

Please sign in to comment.