Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

ThemeBuilder: Rework use customer variables #25383

Merged
merged 12 commits into from
Sep 8, 2023
184 changes: 110 additions & 74 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/devextreme-themebuilder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"clean-css": "^5.3.0",
"less": "^3.13.1",
"postcss": "^8.2.6",
"sass-embedded": "1.62.0"
"sass-embedded": "1.66.0"
},
"scripts": {
"test": "jest --coverage --verbose --runInBand",
Expand Down
26 changes: 7 additions & 19 deletions packages/devextreme-themebuilder/src/metadata/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ export default class MetadataGenerator {
return metaItems;
}

static getOverrideVariables(metaItems: MetaItem[]): string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consolidate:

  • get Override; Overridden?

const result = metaItems.map((item) => `${item.Key}: getCustomVar(("${item.Key}")) !default;`).join('\n');
return result;
}

static getMapFromMeta(metaItems: MetaItem[]): string {
const result = metaItems.map((item) => `"${item.Key}": ${item.Key},\n`).join('');
return `(\n${result})`;
Expand All @@ -64,22 +69,10 @@ export default class MetadataGenerator {
return fileName.includes('bundles');
}

static getMainColorsFileTheme(fileName: string): string {
const match = /widgets[/\\](material|generic)[/\\]_colors.scss/.exec(fileName);

if (match === null) return null;

return match[1];
}

static getBundleContent(content: string): string {
return content.replace(/(..\/widgets\/(material|generic))"/, '$1/tb_index"');
}

static getMainColorsFileContent(content: string, theme: string): string {
return content.replace(/\.\/variables/g, `tb_${theme}`);
}

clean(): void {
this.metadata = {
generic: [],
Expand All @@ -105,14 +98,8 @@ export default class MetadataGenerator {
}

let modifiedContent = content;
const mainFileTheme = MetadataGenerator.getMainColorsFileTheme(filePath);

if (mainFileTheme) {
modifiedContent = MetadataGenerator.getMainColorsFileContent(content, mainFileTheme);
}

const metaItems = MetadataGenerator.getMetaItems(content);

if (!metaItems.length) {
return modifiedContent;
}
Expand All @@ -121,8 +108,9 @@ export default class MetadataGenerator {
this.fillMetaData(item, filePath);
});

const overeideVariables = MetadataGenerator.getOverrideVariables(metaItems);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consolidate:

  • overeide Variables; spelling
    overridden?

const collector = `$never-used: collector(${MetadataGenerator.getMapFromMeta(metaItems)});\n`;
modifiedContent += collector;
modifiedContent = overeideVariables + '\n\n' + modifiedContent + collector;

return modifiedContent;
}
Expand Down
63 changes: 24 additions & 39 deletions packages/devextreme-themebuilder/src/modules/compiler.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,20 @@
import * as sass from 'sass-embedded';
// eslint-disable-next-line import/extensions
import { metadata } from '../data/metadata/dx-theme-builder-metadata';
import { parse } from './parse-value';
import { parse, parseString } from './parse-value';
import { optimizeCss } from './post-compiler';

export enum ImportType {
Index,
Color,
Unknown,
}

export default class Compiler {
changedVariables: { [key: string]: string } = {};

importerCache: Record<string, string> = {};

meta: ThemesMetadata = metadata;

userItems: ConfigMetaItem[] = [];
userItems: { [key: string]: ConfigMetaItem } = {};

indexFileContent: string;

static getImportType = (url: string): ImportType => {
if (url.endsWith('tb_index')) return ImportType.Index;
if (url.startsWith('tb_')) return ImportType.Color;
return ImportType.Unknown;
};

compile = async (
file: string,
items: ConfigMetaItem[],
Expand All @@ -46,7 +34,13 @@ export default class Compiler {
compile: (source: string, options?: sass.Options<'async'>) => Promise<sass.CompileResult>,
): Promise<CompilerResult> => {
this.changedVariables = {};
this.userItems = items || [];

if (items) {
this.userItems = items.reduce((acc: { [key: string]: ConfigMetaItem }, item) => {
acc[item.key] = item;
return acc;
}, {});
}

let compilerOptions: sass.Options<'async'> = {
importers: [{
Expand All @@ -56,6 +50,7 @@ export default class Compiler {
}],
functions: {
'collector($map)': this.collector,
'getCustomVar($value)': this.getCustomVar,
},
};

Expand All @@ -76,36 +71,26 @@ export default class Compiler {
});
};

getMatchingUserItemsAsString(theme: string): string {
const meta = theme === 'generic' ? this.meta.generic : this.meta.material;
const themeKeys: string[] = meta.map((item) => item.Key);

return this.userItems
.filter((item) => themeKeys.includes(item.key))
.map((item) => `${item.key}: ${item.value};`)
.join('');
}

// eslint-disable-next-line spellcheck/spell-checker
canonicalize = (url: string): URL => (url.includes('tb_') ? new URL(`db:${url}`) : null);
canonicalize = (url: string): URL => (url.includes('tb_index') ? new URL(`db:${url}`) : null);

load = (): sass.ImporterResult => ({
contents: this.indexFileContent,
syntax: 'scss',
} as sass.ImporterResult);

load = (url: URL): sass.ImporterResult => {
const { pathname: path } = url;
const importType = Compiler.getImportType(path);
getCustomVar = (values: sass.Value[]): sass.Value => {
const customVariable = values[0].get(0);
const nameVariable = customVariable.get(0) as sass.SassString;

let content = this.importerCache[path];
if (!content) {
content = importType === ImportType.Index
? this.indexFileContent
: this.getMatchingUserItemsAsString(path.replace('tb_', ''));
let result = sass.sassNull;

this.importerCache[path] = content;
const customerVariable = this.userItems[nameVariable.text];
if (customerVariable) {
result = parseString(customerVariable.value);
}

return {
contents: content,
syntax: 'scss',
} as sass.ImporterResult;
return result;
};

collector = (maps: sass.SassMap[]): sass.Value => {
Expand Down
29 changes: 29 additions & 0 deletions packages/devextreme-themebuilder/src/modules/parse-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,32 @@ export const parse = (value: sass.Value): string => {

return result;
};

/**
* Parse a string as a Sass object
* cribbed from davidkpiano/sassport
*
* @param {string} str
* @return {Value}
*/
export const parseString = (str: string): sass.Value => {
// eslint-disable-next-line @typescript-eslint/init-declarations
let result: sass.Value;

try {
sass.compileString(`$_: ___(${str});`, {
functions: {
'___($value)': (args) => {
// eslint-disable-next-line prefer-destructuring
result = args[0];
return result;
},
},
});
} catch (e) {
// debugger;
return new sass.SassString(str);
}

return result;
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@forward "tb_generic";
$base-font-family: getCustomVar(("$base-font-family")) !default;
$base-accent: getCustomVar(("$base-accent")) !default;

@use "tb_generic" as *;
@use "sass:color";
$color: null !default;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
$accordion-title-color: getCustomVar(("$accordion-title-color")) !default;
$accordion-item-title-opened-bg: getCustomVar(("$accordion-item-title-opened-bg")) !default;

@use "sass:color";
@use "../sizes" as *;
@use "../colors" as *;
Expand Down
46 changes: 3 additions & 43 deletions packages/devextreme-themebuilder/tests/metadata/generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,16 +182,16 @@ describe('Metadata generator - collectMetadata', () => {

test('collectMetadata for file with comments modify file content and add data to metadata', () => {
const path = '/scss/widgets/generic/toolbar/_colors.scss';
const content = `
@use "colors";
const content = `@use "colors";
/**
* $name Slide out background
* $type color
*/
$slideout-background: #000 !default;
`;

const expected = `
const expected = `$slideout-background: getCustomVar(("$slideout-background")) !default;

@use "colors";
/**
* $name Slide out background
Expand Down Expand Up @@ -253,22 +253,6 @@ $slideout-background: #000 !default;
});
});

test('collectMetadata - "./variables" imports change in main index file', () => {
const path = '/scss/widgets/material/_colors.scss';
const content = `
@forward "./variables";
@use "./variables" as *;
`;

const expected = `
@forward "tb_material";
@use "tb_material" as *;
`;
const result = generator.collectMetadata(path, content);

expect(result).toBe(expected);
});

test('collectMetadata - right content for bundle', () => {
const path = '/scss/bundles/dx.light.scss';
const content = `
Expand All @@ -294,16 +278,6 @@ describe('Metadata generator - generate files content', () => {
expect(MetadataGenerator.isBundleFile('base/accordion/_index.scss')).toBe(false);
});

test('getMainColorsFileTheme', () => {
expect(MetadataGenerator.getMainColorsFileTheme('/widgets/generic/_colors.scss')).toBe('generic');
expect(MetadataGenerator.getMainColorsFileTheme('widgets\\generic\\_colors.scss')).toBe('generic');
expect(MetadataGenerator.getMainColorsFileTheme('widgets/material/_colors.scss')).toBe('material');
expect(MetadataGenerator.getMainColorsFileTheme('/scss/widgets/material/_colors.scss')).toBe('material');
expect(MetadataGenerator.getMainColorsFileTheme('bundles/dx.light.scss')).toBe(null);
expect(MetadataGenerator.getMainColorsFileTheme('path/widgets/generic/accordion/_index.scss')).toBe(null);
expect(MetadataGenerator.getMainColorsFileTheme('base/accordion/_index.scss')).toBe(null);
});

test('getBundleContent', () => {
const genericBundleContent = `
@use "../widgets/generic/colors" with ($color: "carmine");
Expand Down Expand Up @@ -339,18 +313,4 @@ describe('Metadata generator - generate files content', () => {
expect(MetadataGenerator.getBundleContent(commonBundleContent))
.toBe(commonBundleContent);
});

test('getMainColorsFileContent', () => {
const content = `
@forward "./variables";
@use "./variables" as *;
`;

const expected = `
@forward "tb_material";
@use "tb_material" as *;
`;

expect(MetadataGenerator.getMainColorsFileContent(content, 'material')).toBe(expected);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -94,54 +94,4 @@ describe('Sass features', () => {
$var7: '200px',
});
});

test('getMatchingUserItemsAsString - return right string for the url', () => {
const compiler = new Compiler();

compiler.userItems = [{
key: '$var2',
value: 'rgba(0,0,0,0)',
}, {
key: '$var0',
value: '10px',
}];
const expected = '$var2: rgba(0,0,0,0);$var0: 10px;';

const content = compiler.getMatchingUserItemsAsString('generic');

expect(content).toEqual(expected);
});

test('setter function (custom importer)', () => {
const compiler = new Compiler();
compiler.userItems = [{
key: '$var2',
value: 'rgba(0,0,0,0)',
}, {
key: '$var0',
value: '10px',
}];

const url = new URL('db:tb_generic');
const expectedContent = '$var2: rgba(0,0,0,0);$var0: 10px;';

const setterResult = compiler.load(url);
expect(setterResult).toEqual({
contents: expectedContent,
syntax: 'scss',
});
expect(compiler.importerCache[url.pathname]).toBe(expectedContent);
});

test('setter call getMatchingUserItemsAsString once for every url', () => {
const url = new URL('db:tb_generic');
const compiler = new Compiler();
compiler.getMatchingUserItemsAsString = jest.fn().mockImplementation(() => 'content');
compiler.load(url);
compiler.load(url);
compiler.load(url);

expect(compiler.getMatchingUserItemsAsString).toHaveBeenCalledTimes(1);
expect(compiler.importerCache[url.pathname]).toBe('content');
});
});
11 changes: 2 additions & 9 deletions packages/devextreme-themebuilder/tests/modules/compiler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import fs from 'fs';
import { metadata } from '../data/metadata';
import noModificationsMeta from '../data/compilation-results/no-changes-meta';

import Compiler, { ImportType } from '../../src/modules/compiler';
import Compiler from '../../src/modules/compiler';

jest.mock('../../src/data/metadata/dx-theme-builder-metadata', () => ({
__esModule: true,
Expand Down Expand Up @@ -104,19 +104,12 @@ describe('compile', () => {
});

describe('compile with widgets', () => {
test('getImportType', () => {
expect(Compiler.getImportType('tb_generic')).toBe(ImportType.Color);
expect(Compiler.getImportType('../widgets/generic/tb_index')).toBe(ImportType.Index);
expect(Compiler.getImportType('colors')).toBe(ImportType.Unknown);
});

test('setter return indexFileContent for index file', () => {
const compiler = new Compiler();
const contentOfIndexFile = 'some content';
const indexFileUrl = new URL('db:../widgets/generic/tb_index');
compiler.indexFileContent = contentOfIndexFile;

expect(compiler.load(indexFileUrl)).toEqual({
expect(compiler.load()).toEqual({
contents: contentOfIndexFile,
syntax: 'scss',
});
Expand Down