From d94c0875ea048c86f94ce35c6b2929f796252fff Mon Sep 17 00:00:00 2001 From: Theeraphat-Sorasetsakul <86758473+Theeraphat-Sorasetsakul@users.noreply.github.com> Date: Mon, 7 Nov 2022 17:56:25 +0700 Subject: [PATCH] feat: remove elf global and replace with eventListener (#511) * feat: add event based theming option to theme compiler * feat(core): remove elf global and replace with eventListener * fix(core): using registration instead of styles * fix(theme-compiler): fix pass variable to generateLessOptions * chore(core): add ignore eslint * test(core): add test case for registry event * refactor(core): rearrange code * feat(create-efx): add registration to using core by listener Co-authored-by: Sarin Udompanish --- packages/core/__test__/elf.test.js | 29 +++++++++++- packages/core/src/index.ts | 17 ++++--- .../create-efx/template/package.json.template | 2 +- packages/elemental-theme/package.json | 4 +- packages/halo-theme/package.json | 4 +- packages/solar-theme/package.json | 6 +-- packages/theme-compiler/src/helpers.js | 46 ++++++++++++++++--- packages/theme-compiler/src/themeParser.js | 7 +-- scripts/release/theme-extractor.js | 2 +- 9 files changed, 91 insertions(+), 26 deletions(-) diff --git a/packages/core/__test__/elf.test.js b/packages/core/__test__/elf.test.js index 208a1b0074..c72e0bf461 100644 --- a/packages/core/__test__/elf.test.js +++ b/packages/core/__test__/elf.test.js @@ -1,7 +1,7 @@ -import { expect, fixture } from '@refinitiv-ui/test-helpers'; +import { expect, fixture, oneEvent } from '@refinitiv-ui/test-helpers'; import { customElement } from './../lib/decorators/custom-element'; import { LitElement } from '../lib/index.js'; - +import { CustomStyleRegistry } from '../lib/registries/CustomStyleRegistry.js'; class BasicElementTest extends LitElement { } @@ -36,3 +36,28 @@ describe('Test ELF', () => { expect(warnMessage).to.equal('Please use an ELF element type, instead of LitElement', 'Wrong warning message is used'); }); }); + +describe('Test ELF', () => { + it('Test registry event call: ef.customStyles.define', async () => { + const mockElementName = 'test-element'; + const mockCssString = ':host{}'; + window.dispatchEvent(new CustomEvent('ef.customStyles.define', { + detail: { + name: mockElementName, + styles: mockCssString + } + })); + expect(CustomStyleRegistry.get(mockElementName)).to.equal(mockCssString); + }); + it('Test registry event call: ef.nativeStyles.define', async () => { + const mockElementName = 'test-element'; + const mockCssString = ':host{}'; + window.dispatchEvent(new CustomEvent('ef.nativeStyles.define', { + detail: { + name: mockElementName, + styles: mockCssString + } + })); + expect(CustomStyleRegistry.get(mockElementName)).to.equal(mockCssString); + }); +}); \ No newline at end of file diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 668bc22b50..e7e60cd8e5 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ + export { html, svg, @@ -74,10 +77,12 @@ import { CustomStyleRegistry } from './registries/CustomStyleRegistry.js'; import { NativeStyleRegistry } from './registries/NativeStyleRegistry.js'; import { global } from './utils/global.js'; -global.elf = global.Elf = global.ELF = { - customStyles: CustomStyleRegistry, - nativeStyles: NativeStyleRegistry, - version: 'PUBLISH_VERSION' -}; +global.addEventListener('ef.customStyles.define', (event) => { + const { name, styles } = (event as CustomEvent).detail; + CustomStyleRegistry.define(name, styles); +}); -Object.freeze(global.elf); +global.addEventListener('ef.nativeStyles.define', (event) => { + const { name, styles } = (event as CustomEvent).detail; + NativeStyleRegistry.define(name, styles); +}); diff --git a/packages/create-efx/template/package.json.template b/packages/create-efx/template/package.json.template index ff7c87759f..2caefd7465 100644 --- a/packages/create-efx/template/package.json.template +++ b/packages/create-efx/template/package.json.template @@ -16,7 +16,7 @@ "start": "vite --open --base=/demo/", "build": "npm run build:themes && tsc --sourceMap --declarationMap", "build:themes": "npm run build:theme-halo", - "build:theme-halo": "cd themes/halo && theme-compiler light --variant light && theme-compiler dark --variant dark", + "build:theme-halo": "cd themes/halo && theme-compiler light --variant light --registration=event && theme-compiler dark --variant dark --registration=event", "prepare": "npm run build:themes", "lint": "eslint src", "lint:fix": "eslint src --fix", diff --git a/packages/elemental-theme/package.json b/packages/elemental-theme/package.json index 018ad64f90..6c6dbb8677 100644 --- a/packages/elemental-theme/package.json +++ b/packages/elemental-theme/package.json @@ -15,8 +15,8 @@ "!.*" ], "scripts": { - "build:light": "theme-compiler light --variant=light", - "build:dark": "theme-compiler dark --variant=dark", + "build:light": "theme-compiler light --variant=light --registration=event", + "build:dark": "theme-compiler dark --variant=dark --registration=event", "build": "npm run build:light && npm run build:dark", "build:prod": "npm run build", "watch": "watch \"npm run build\" src --wait=10", diff --git a/packages/halo-theme/package.json b/packages/halo-theme/package.json index 68dfb99acd..57190f97c9 100644 --- a/packages/halo-theme/package.json +++ b/packages/halo-theme/package.json @@ -15,8 +15,8 @@ "!.*" ], "scripts": { - "build:dark": "theme-compiler dark --variant=dark", - "build:light": "theme-compiler light --variant=light", + "build:dark": "theme-compiler dark --variant=dark --registration=event", + "build:light": "theme-compiler light --variant=light --registration=event", "build": "npm run build:dark && npm run build:light", "build:prod": "npm run build", "watch": "watch \"npm run build\" src --wait=10", diff --git a/packages/solar-theme/package.json b/packages/solar-theme/package.json index e6f4d54da9..822adde278 100644 --- a/packages/solar-theme/package.json +++ b/packages/solar-theme/package.json @@ -15,8 +15,8 @@ "!.*" ], "scripts": { - "build:charcoal": "theme-compiler charcoal --variant=charcoal", - "build:pearl": "theme-compiler pearl --variant=pearl", + "build:charcoal": "theme-compiler charcoal --variant=charcoal --registration=event", + "build:pearl": "theme-compiler pearl --variant=pearl --registration=event", "build": "npm run build:charcoal && npm run build:pearl", "build:prod": "npm run build", "watch": "watch \"npm run build\" src --wait=10", @@ -32,4 +32,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/packages/theme-compiler/src/helpers.js b/packages/theme-compiler/src/helpers.js index 4d2d5f819c..1c02925a2b 100644 --- a/packages/theme-compiler/src/helpers.js +++ b/packages/theme-compiler/src/helpers.js @@ -7,8 +7,20 @@ const autoprefixer = require('postcss')().use(require('autoprefixer')); const clean = new (require('clean-css'))({ returnPromise: true, level: '2' }); const path = require('path'); -const wrap = (name, style) => `elf.${name.indexOf('-') > 0 ? 'custom' -: 'native'}Styles.define('${name}', '${style.replace(/'/g, '\\\'')}');\n`; +/** + * Return injector code in form of string + * @param {string} name element's path + * @param {string} style element's path + * @param {string} isEvent condition if need to be event method + * @returns {string} injector code + */ +const wrap = (name, style, isEvent) => { + const eventName = name.indexOf('-') > 0 ? 'custom' : 'native'; + if(isEvent) { + return `dispatchEvent(new CustomEvent('ef.${eventName}Styles.define', { detail: { name: '${name}', styles: '${style.replace(/'/g, '\\\'')}' }}));\n`; + } + return `elf.${eventName}Styles.define('${name}', '${style.replace(/'/g, '\\\'')}');\n`; +} const cleanCSS = css => autoprefixer.process(css, { from: false }) .then(o => clean.minify(o.css)).then(o => o.styles); @@ -16,6 +28,13 @@ const cleanCSS = css => autoprefixer.process(css, { from: false }) const wrapHostSelectors = css => Promise .resolve(css.replace(/(:host)([.:[#][^\s,{]+)/g, '$1($2)')); +/** + * Return less option template + * @param {string} entrypoint source map output + * @param {string} filename source of the element styles + * @param {string} variables variables that override the less file + * @returns {string} injector code + */ const generateLessOptions = (entrypoint, filename, variables) => ({ filename: entrypoint, math: 2, @@ -41,13 +60,21 @@ const generateLessOptions = (entrypoint, filename, variables) => ({ ] }); -const generateJsInfo = (name, css, dependencies) => { +/** + * Return generated info for injector string + * @param {string} name element name + * @param {string} css element style + * @param {string} dependencies list of elements + * @param {string} variables option variables that include using event condition + * @returns {object} injector code + */ +const generateJsInfo = (name, css, dependencies, variables) => { let importString = 'import \'./imports/native-elements.js\';\n'; importString += dependencies.filter(name => name.indexOf('-') !== -1) .map(dep => `import './${dep}.js';`).join('\n') + '\n'; return { importString, - injectorString: wrap(name, css.replace(/([^\\])\\([^\\])/g, '$1\\\\$2')) + injectorString: wrap(name, css.replace(/([^\\])\\([^\\])/g, '$1\\\\$2'), variables.registration === 'event') }; }; @@ -59,13 +86,20 @@ const getElementNameFromLess = (filename) => { return path.basename(filename).replace(/\.less$/, ''); }; -const generateOutput = (filename, output) => { +/** + * Return object that use for parser + * @param {string} filename element source + * @param {string} output less file source + * @param {string} variables option variables that include using event condition + * @returns {object} + */ +const generateOutput = (filename, output, variables) => { return cleanCSS(output.css).then(wrapHostSelectors).then(css => { let name = path.basename(filename).replace(/\.less$/, ''); let dependencies = output.imports .filter(filename => prefix.test(filename)) .map(filename => filename.replace(dependencyPattern, '')); - let styleInfo = generateJsInfo(name, css, dependencies); + let styleInfo = generateJsInfo(name, css, dependencies, variables); return { name, dependencies, diff --git a/packages/theme-compiler/src/themeParser.js b/packages/theme-compiler/src/themeParser.js index 0e2bbaedbe..f9b4eaed3e 100644 --- a/packages/theme-compiler/src/themeParser.js +++ b/packages/theme-compiler/src/themeParser.js @@ -50,7 +50,7 @@ const parse = (entrypoint, variables) => { let options = helpers.generateLessOptions(entrypoint, entrypoint, variables); return fs.readFile(entrypoint, 'utf8').then(lessInput => { return less.render(lessInput, options).then(() => { - helpers.getElementFiles().forEach(filename => renderElement(filename, lessInput)); + helpers.getElementFiles().forEach(filename => renderElement(filename, lessInput, variables)); return Promise.all(tempCollection) .then(resolvedCollection => { let result = sortCollection(resolvedCollection); @@ -65,9 +65,10 @@ const parse = (entrypoint, variables) => { * * @param {String} filename Source of the element styles * @param {String} lessInput Less input from the entrypoint + * @param {Object} variables Variables to modify in the less input * @returns {Promise} Promise */ - const renderElement = (filename, lessInput) => { + const renderElement = (filename, lessInput, variables) => { // Obtain elementName const elemName = helpers.getElementNameFromLess(filename); @@ -76,7 +77,7 @@ const parse = (entrypoint, variables) => { let options = helpers.generateLessOptions(entrypoint, filename, variables); let promise = less.render(lessInput, options) - .then(output => helpers.generateOutput(filename, output)); + .then(output => helpers.generateOutput(filename, output, variables)); tempCollection.push(promise); return promise; }; diff --git a/scripts/release/theme-extractor.js b/scripts/release/theme-extractor.js index e1c73eab62..decea4c873 100644 --- a/scripts/release/theme-extractor.js +++ b/scripts/release/theme-extractor.js @@ -169,7 +169,7 @@ const handler = async () => { // Reads theme entrypoint content const sourceContent = fs.readFileSync(possibleThemeEntrypoint).toString(); const componentThemeDefinition = sourceContent.substring( - sourceContent.indexOf('elf.customStyles.define') + sourceContent.indexOf(`dispatchEvent(new CustomEvent('ef.customStyles.define'`) ); // Skip if the file already contain the same component definition