UNPKG

antd-theme

Version:

Provide runtime dynamic theme for ant design.

286 lines (260 loc) 6.47 kB
const less = require('less'); const fs = require('fs'); const pLimit = require('p-limit'); const LessThemePlugin = require('./lessThemePlugin'); const ColorPalettePlugin = require('./colorPalettePlugin'); const extractVariables = require('./extractVariables'); const RuntimeVariableVisitor = require('./visitors/runtimeVariableVisitor'); const ResolveVisitor = require('./visitors/resolveVisitor'); const serialize = require('./serialize'); const sha1 = require('./sha1'); const limit = pLimit(1); const defaultThemeName = 'default'; const initialize = async (options) => { const mergedVariableGroups = {}; const changedVariableNames = new Map(); const runtimeVariableNames = options.variables || []; const runtimeVariableVisitors = []; const resolveVisitors = []; const isRuntimeVariable = (i, node, variableName) => { if (runtimeVariableNames.length === 0) { return false; } return runtimeVariableVisitors[i].include(node, variableName); }; const lessOptions = { javascriptEnabled: true, plugins: [ new ColorPalettePlugin(), ], }; const filenames = [ require.resolve('antd/lib/style/themes/default.less'), ...options.themes.map(({ filename }) => filename), ]; const [defaultVariables, ...themeVariableGroups] = await Promise.all( filenames.map( (filename) => extractVariables(`@import "${filename}";`, lessOptions) ) ).then( (variableGroups) => variableGroups.map( (variables) => variables.reduce( (acc, variable) => ({ ...acc, [variable.name]: variable, }), {} ) ) ); mergedVariableGroups[defaultThemeName] = defaultVariables; options.themes.forEach( (theme, i) => { mergedVariableGroups[theme.name] = { ...defaultVariables, ...themeVariableGroups[i], }; } ); Object.keys(mergedVariableGroups).forEach( (name, i) => { runtimeVariableVisitors[i] = new RuntimeVariableVisitor( runtimeVariableNames, mergedVariableGroups[name] ); resolveVisitors[i] = new ResolveVisitor( runtimeVariableNames, mergedVariableGroups[name], runtimeVariableVisitors[i] ); } ); Object.keys(mergedVariableGroups).forEach( (themeName, themeIndex) => { const themeVariables = mergedVariableGroups[themeName]; Object.keys(themeVariables).forEach( (name) => { const themeVariable = themeVariables[name]; themeVariables[name] = { ...themeVariable, runtime: isRuntimeVariable(themeIndex, themeVariable.expr, themeVariable.name), }; } ); } ); Object.values(mergedVariableGroups).forEach( (themeVariables, i) => { if (i === 0) { return; } Object.values(themeVariables).forEach( (themeVariable) => { const { name } = themeVariable; const defaultVariable = defaultVariables[name]; if (!defaultVariable) { return; } if (themeVariable.runtime || defaultVariable.value !== themeVariable.value) { changedVariableNames.set(name, defaultVariable.node); } } ); } ); return { mergedVariableGroups, changedVariableNames, resolveVisitors, }; }; const options = { variables: ['primary-color'], themes: [ { name: 'dark', filename: require.resolve('antd/lib/style/themes/dark.less'), }, { name: 'compact', filename: require.resolve('antd/lib/style/themes/compact.less'), }, { name: 'aliyun', filename: require.resolve('@ant-design/aliyun-theme/index.less'), }, ], }; const compileThemes = async (options, input) => { const { mergedVariableGroups, changedVariableNames, resolveVisitors, } = await initialize(options); const extractedExprs = new Map(); const { css } = await less.render(input, { javascriptEnabled: true, plugins: [ new LessThemePlugin( (expr) => { extractedExprs[expr.name] = expr; }, { has: (key) => changedVariableNames.has(key), get: (key) => changedVariableNames.get(key), } ), ], }); const resolvedExprGroups = {}; const defineRuntimeExprs = new Map(); Object.keys(mergedVariableGroups).forEach((themeName, themeIndex) => { const resolvedExprs = {}; Object.keys(extractedExprs).forEach( (name) => { const expr = extractedExprs[name]; const resolved = resolveVisitors[themeIndex].run(expr.node); if (resolved.runtime) { const code = JSON.stringify(serialize(resolved.expr)); const hash = sha1(code); resolved.hash = hash; defineRuntimeExprs.set(hash, code); } resolvedExprs[name] = resolved; } ); resolvedExprGroups[themeName] = resolvedExprs; }); }; const components = [ 'affix', 'alert', 'anchor', 'auto-complete', 'avatar', 'back-top', 'badge', 'breadcrumb', 'button', 'calendar', 'carousel', 'card', 'cascader', 'checkbox', 'col', 'collapse', 'comment', 'config-provider', 'date-picker', 'descriptions', 'divider', 'drawer', 'dropdown', 'empty', 'form', 'grid', 'icon', 'input', 'input-number', 'layout', 'list', 'locale-provider', 'mentions', 'menu', 'message', 'modal', 'notification', 'page-header', 'pagination', 'popconfirm', 'popover', 'progress', 'radio', 'rate', 'result', 'row', 'select', 'skeleton', 'slider', 'space', 'spin', 'statistic', 'steps', 'switch', 'table', 'tabs', 'tag', 'timeline', 'time-picker', 'tooltip', 'transfer', 'tree', 'tree-select', 'typography', 'upload', 'version', ]; describe('compile themes', () => { const tests = components.map( (name) => ({ name, filename: require.resolve(`antd/lib/${name}/style/index`).replace(/js$/, 'less'), }) ).filter( ({ filename }) => fs.existsSync(filename) ).map( ({ name, filename }) => ({ name, compiled: limit( () => { const input = (`@import "${filename}";`); return compileThemes(options, input); } ), }) ); tests.forEach( ({ name, compiled }) => { // eslint-disable-next-line jest/expect-expect test(name, () => compiled); } ); });