Skip to content

Commit

Permalink
Chore: Reduce Less usage in theming (#27689)
Browse files Browse the repository at this point in the history
  • Loading branch information
tassoevan committed Jan 5, 2023
1 parent 06bf702 commit cdc2f99
Show file tree
Hide file tree
Showing 15 changed files with 220 additions and 227 deletions.
1 change: 0 additions & 1 deletion apps/meteor/.meteor/packages
Expand Up @@ -75,7 +75,6 @@ autoupdate@1.8.0
babel-compiler@7.9.0
google-oauth@1.4.2
htmljs
less
matb33:collection-hooks
meteorhacks:inject-initial
oauth@2.1.2
Expand Down
1 change: 0 additions & 1 deletion apps/meteor/.meteor/versions
Expand Up @@ -63,7 +63,6 @@ kadira:flow-router@2.12.1
konecty:multiple-instances-status@1.1.0
konecty:user-presence@2.6.3
launch-screen@1.3.0
less@3.0.2
littledata:synced-cron@1.5.1
localstorage@1.2.0
logging@1.3.1
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/settings/server/SettingsRegistry.ts
Expand Up @@ -200,7 +200,7 @@ export class SettingsRegistry {
/*
* Add a setting group
*/
async addGroup(_id: string, cb: addGroupCallback): Promise<void>;
async addGroup(_id: string, cb?: addGroupCallback): Promise<void>;

// eslint-disable-next-line no-dupe-class-members
async addGroup(_id: string, groupOptions: ISettingAddGroupOptions | addGroupCallback = {}, cb?: addGroupCallback): Promise<void> {
Expand Down
File renamed without changes.
125 changes: 125 additions & 0 deletions apps/meteor/app/theme/server/Theme.ts
@@ -0,0 +1,125 @@
import { performance } from 'perf_hooks';

import { Meteor } from 'meteor/meteor';
import less from 'less';
import AutoPrefixerLessPlugin from 'less-plugin-autoprefixer';
import { Settings } from '@rocket.chat/models';
import type { ISetting, ISettingColor } from '@rocket.chat/core-typings';

import { withDebouncing } from '../../../lib/utils/highOrderFunctions';
import { settingsRegistry, settings } from '../../settings/server';
import type { Logger } from '../../logger/server';

export class Theme {
private variables: Record<
string,
{
type: 'font' | 'color';
value: unknown;
editor?: ISettingColor['editor'];
}
> = {};

private customCSS = '';

private compileDelayed: () => void = withDebouncing({ wait: 100 })(Meteor.bindEnvironment(this.compile.bind(this)));

private logger: Logger;

public constructor({ logger }: { logger: Logger }) {
this.logger = logger;
this.watchSettings();
}

private watchSettings() {
settingsRegistry.add('css', '');
settingsRegistry.addGroup('Layout');

settings.watchByRegex(/^theme-./, (key, value) => {
if (key === 'theme-custom-css' && !!value) {
this.customCSS = String(value);
} else {
const name = key.replace(/^theme-[a-z]+-/, '');
if (this.variables[name]) {
this.variables[name].value = value;
}
}

this.compileDelayed();
});
}

private compile() {
const options: Less.Options = {
compress: true,
plugins: [new AutoPrefixerLessPlugin()],
};

const start = performance.now();

less.render(this.customCSS, options, (err, data) => {
this.logger.info({ stop_rendering: performance.now() - start });

if (err) {
this.logger.error(err);
return;
}

Settings.updateValueById('css', data?.css);

Meteor.startup(() => {
Meteor.setTimeout(() => {
process.emit('message', { refresh: 'client' });
}, 200);
});
});
}

public addVariable(type: 'font', name: string, value: ISetting['value'], section: ISetting['section'], persist?: boolean): void;

public addVariable(
type: 'color',
name: string,
value: ISettingColor['value'],
section: ISettingColor['section'],
persist: boolean,
editor: ISettingColor['editor'],
): void;

public addVariable(
type: 'font' | 'color',
name: string,
value: ISetting['value'],
section: ISetting['section'],
persist = true,
editor?: ISettingColor['editor'],
) {
this.variables[name] = {
type,
value,
editor,
};

if (!persist) {
return;
}

// TODO: this is a hack to make the type checker happy
const config: Partial<ISetting> & { allowedTypes?: ['color', 'expression'] } = {
group: 'Layout',
type,
section,
public: true,
...(type === 'color' && {
editor,
allowedTypes: ['color', 'expression'],
}),
};

settingsRegistry.add(`theme-${type}-${name}`, value, config);
}

public getCss() {
return String(settings.get('css') || '');
}
}
File renamed without changes.
138 changes: 0 additions & 138 deletions apps/meteor/app/theme/server/server.js

This file was deleted.

34 changes: 34 additions & 0 deletions apps/meteor/app/theme/server/server.ts
@@ -0,0 +1,34 @@
import crypto from 'crypto';

import { WebApp } from 'meteor/webapp';

import { settings } from '../../settings/server';
import { Logger } from '../../logger/server';
import { addStyle } from '../../ui-master/server/inject';
import { Theme } from './Theme';

const logger = new Logger('rocketchat:theme');

export const theme = new Theme({ logger });

settings.watch('css', () => {
addStyle('css-theme', theme.getCss());
process.emit('message', { refresh: 'client' });
});

WebApp.rawConnectHandlers.use((req, res, next) => {
const path = req.url?.split('?')[0];
const prefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || '';

if (path !== `${prefix}/theme.css`) {
next();
return;
}

const data = theme.getCss();

res.setHeader('Content-Type', 'text/css; charset=UTF-8');
res.setHeader('Content-Length', data.length);
res.setHeader('ETag', `"${crypto.createHash('sha1').update(data).digest('hex')}"`);
res.end(data, 'utf-8');
});
@@ -1,3 +1,5 @@
import { SettingEditor } from '@rocket.chat/core-typings';

import { theme } from './server';
import { settingsRegistry } from '../../settings/server';
// TODO: Define registers/getters/setters for packages to work with established
Expand All @@ -10,15 +12,14 @@ import { settingsRegistry } from '../../settings/server';
// Major colors form the core of the scheme
// Names changed to reflect usage, comments show pre-refactor names

const variablesContent = Assets.getText('client/imports/general/variables.css');
const variablesContent = Assets.getText('client/imports/general/variables.css') ?? '';

const regionRegex = /\/\*\s*#region\s+([^ ]*?)\s+(.*?)\s*\*\/((.|\s)*?)\/\*\s*#endregion\s*\*\//gim;
const regionRegex = /\/\*\s*#region\s+([^ ]*?)\s+(.*?)\s*\*\/([^]*?)\/\*\s*#endregion\s*\*\//gim;

for (let matches = regionRegex.exec(variablesContent); matches; matches = regionRegex.exec(variablesContent)) {
const [, type, section, content] = matches;
[...content.match(/--(.*?):\s*(.*?);/gim)].forEach((entry) => {
const matches = /--(.*?):\s*(.*?);/im.exec(entry);
const [, name, value] = matches;
[...(content.match(/--(.*?):\s*(.*?);/gim) ?? [])].forEach((entry) => {
const [, name, value] = /--(.*?):\s*(.*?);/im.exec(entry) ?? [];

if (type === 'fonts') {
theme.addVariable('font', name, value, 'Fonts', true);
Expand All @@ -27,23 +28,23 @@ for (let matches = regionRegex.exec(variablesContent); matches; matches = region

if (type === 'colors') {
if (/var/.test(value)) {
const [, variableName] = value.match(/var\(--(.*?)\)/i);
theme.addVariable('color', name, variableName, section, true, 'expression', ['color', 'expression']);
const [, variableName] = value.match(/var\(--(.*?)\)/i) ?? [];
theme.addVariable('color', name, variableName, section, true, SettingEditor.EXPRESSION);
return;
}

theme.addVariable('color', name, value, section, true, 'color', ['color', 'expression']);
theme.addVariable('color', name, value, section, true, SettingEditor.COLOR);
return;
}

if (type === 'less-colors') {
if (/var/.test(value)) {
const [, variableName] = value.match(/var\(--(.*?)\)/i);
theme.addVariable('color', name, `@${variableName}`, section, true, 'expression', ['color', 'expression']);
const [, variableName] = value.match(/var\(--(.*?)\)/i) ?? [];
theme.addVariable('color', name, `@${variableName}`, section, true, SettingEditor.EXPRESSION);
return;
}

theme.addVariable('color', name, value, section, true, 'color', ['color', 'expression']);
theme.addVariable('color', name, value, section, true, SettingEditor.COLOR);
}
});
}
Expand Down

0 comments on commit cdc2f99

Please sign in to comment.