Development background
As a JD-style component library, NutUI already has H5 and multi-terminal applet development capabilities. With the continuous development of business, the application scenarios of component libraries are becoming wider and wider. When faced with the use of multiple large teams such as technology, finance, logistics, etc. inside and outside the company, although a single JD.com APP vision can be skinned with one click, but for more personalized customization requirements (component-level styles, specifications, dimensions, etc. ) The theme style variable with nearly a thousand lines is a lot of work for developers. In order to improve the development experience and improve the efficiency of developers, it is urgent to strengthen the skinning function and realize the "component-level customization" function.
Design goals
It allows users to switch skins with different theme styles during the development stage, and also allows developers to directly modify the style of specified components to meet mobile business scenarios with different design styles.
Efficiency improvement
The official website will provide multiple sets of themes for developers to choose from. At the same time, developers can also edit and modify in real time on the basis of multiple sets of themes. After completion, download the configuration variables and apply them in the project, which is very easy to use. It only takes 1 minute to complete a global style configuration.
Compared with this scenario, the development of requirements is relatively fast, which can reduce the development cost.
component granularity
The theme customization configuration layer is divided into global basic variables and component basic variables. Developers can modify the global, such as the global theme color, font and other styles of the component library. The configuration of the component layer can be more detailed, such as the rounded border size of the Button button success type
Universal Expansion Capability
At this stage, the official will provide some high-quality themes to integrate into the official website. For community developers, development teams, and if the style theme files customized by your team have a very wide audience, you can contact us to build your theme into the official npm package. Benefit more developers
How developers use
video tutorial
NutUI One-minute fast online theme customization https://www.bilibili.com/video/BV1fi4y1D7qb
1. Open the online configuration website, modify the preview and download according to the picture below
2. Local project configuration
Modify the configuration file of the local project webpack or vite to integrate the downloaded custom_theme.sass
file into the project, such as assets/styles/custom_theme.sass
- The vite build tool uses a sample vite.config
// https://vitejs.dev/config/
export default defineConfig({
//...
css: {
preprocessorOptions: {
scss: {
// 默认京东 APP 10.0主题 > @import "@nutui/nutui/dist/styles/variables.scss";
// 京东科技主题 > @import "@nutui/nutui/dist/styles/variables-jdt.scss";
additionalData: `@import "./assets/styles/custom_theme.scss";@import "@nutui/nutui/dist/styles/variables.scss";`
}
}
}
})
- Webpack build tool usage example
{
test: /\.(sa|sc)ss$/,
use: [
{
loader: 'sass-loader',
options: {
// 默认京东 APP 10.0主题 > @import "@nutui/nutui/dist/styles/variables.scss";
// 京东科技主题 > @import "@nutui/nutui/dist/styles/variables-jdt.scss";
data: `@import "./assets/styles/custom_theme.scss";@import "@nutui/nutui/dist/styles/variables.scss";`,
}
}
]
}
- taro applet usage example
Modify the global coverage of the config/index.js
file in the scss
file, such as:
const path = require('path');
const config = {
deviceRatio: {
640: 2.34 / 2,
750: 1,
828: 1.81 / 2,
375: 2 / 1
},
sass: {
resource: [
path.resolve(__dirname, '..', 'src/assets/styles/custom_theme.scss')
],
// 默认京东 APP 10.0主题 > @import "@nutui/nutui-taro/dist/styles/variables.scss";
// 京东科技主题 > @import "@nutui/nutui-taro/dist/styles/variables-jdt.scss";
data: `@import "@nutui/nutui-taro/dist/styles/variables.scss";`
},
// ...
Implementation principle analysis
The theme customization module of the entire component library can be implemented in two directions, one is the internal component library design (for developers to use and configure each style variable), the other is the online configuration of the official website (for developers to modify easily), connect Next, it is explained in accordance with the design diagram.
Component library internal design
First of all, under the style
folder inside the source code, there are different official themes corresponding to variables.scss
and variables-jdt.scss
files. The global variables.scss
file for each theme actually stores general style variables and style variables for each component according to standard rules. , like below
// --------base begin-------
// 主色调
$primary-color: #fa2c19 !default;
$primary-color-end: #fa6419 !default;
// 辅助色
$help-color: #f5f5f5 !default;
// 标题常规文字
$title-color: #1a1a1a !default;
// 副标题
$title-color2: #666666 !default;
// 次内容
$text-color: #808080 !default;
//...
// Font
$font-size-0: 10px !default;
$font-size-1: 12px !default;
$font-size-2: 14px !default;
$font-size-3: 16px !default;
$font-size-4: 18px !default;
$font-weight-bold: 400 !default;
$font-size-small: $font-size-1 !default;
$font-size-base: $font-size-2 !default;
$font-size-large: $font-size-3 !default;
$line-height-base: 1.5 !default;
// --------base end-------
// button
$button-border-radius: 25px !default;
$button-border-width: 1px !default;
$button-default-bg-color: $white !default;
$button-default-border-color: rgba(204, 204, 204, 1) !default;
$button-default-color: rgba(102, 102, 102, 1) !default;
//...
// icon
// ...
Here is a long-winded sentence. You can see that there is a !default
after each line. This is essential. If it is not added, the developer's local project cannot override this variable.
https://www.sass.hk/docs/#t6-9 Tips: You can add !default at the end of the variable to assign a value to a variable that has not passed the !default declaration. will be reassigned, but if the variable has not been assigned, it will be assigned the new value.
For each component inside, for example button/index.scss
is referenced like this height: $button-default-height;
.nut-button {
position: relative;
display: inline-block;
flex-shrink: 0;
height: $button-default-height;
// ...
}
In fact, when the final component library is built into an npm package, the global theme files such as variables.scss
of the theme are exposed to the developer, and then the developer replaces the style variables according to their needs. So far, the theme customization is realized within the component library.
Visual configuration official website
Source code first look: https://github.com/jdf2e/nutui/tree/theme/src/sites/doc/components/ThemeSetting
The overall implementation process is as follows, which will be explained in turn
- Split the
variables.scss
source file through component configuration data + regular matching to obtain such a data structure
// 主色调
$primary-color: #fa2c19 !default;
$primary-color-end: #fa6419 !default;
//...
// button
$button-border-radius: 25px !default;
$button-border-width: 1px !default;
//...
// icon
// ...
[
{name: 'Base', lowerCaseName: 'base', key: '$primary-color', rawValue: '#fa2c19', computedRawValue: ''}
{name: 'Base', lowerCaseName: 'base', key: '$primary-color-end', rawValue: '#fa6419', computedRawValue: ''}
// ...
{name: 'Button', lowerCaseName: 'button', key: '$button-border-width', rawValue: '1px', computedRawValue: ''}
{name: 'Button', lowerCaseName: 'button', key: '$button-border-radius', rawValue: '25px', computedRawValue: ''}
//{name: 'components1', lowerCaseName: 'components1', key: '$components1-border-radius', rawValue: 'xx', computedRawValue: ''}
//...
]
const findStyle = (componentName: string) => {
// https://raw.githubusercontent.com/jdf2e/nutui/next/src/packages/styles/variables.scss
// var pattern = /\$button.*;/g;
var p = new RegExp(`\\$${componentName}.*;`, 'g');
let parray: any[] = varcss.match(p) || [];
// 需要包含换行
let commponetns = parray.map((item) => {
let cArray = item.split(':');
let name = cArray[0],
value: string = cArray[1].replace(' !default;', '').trim();
return {
name: componentName,
key: name,
rawValue:value,
computedRawValue: ''
}
});
}
components.map(item=>{ findStyle(item.name) });
- Next, display all variables under the component according to the component, monitor component switching or editing, and perform real-time compilation
const cssText = computed(() => {
const variablesText = store.variables.map(({ key, value }) => `${key}:${value}`).join(';');
cachedStyles = cachedStyles || extractStyle(store.rawStyles);
return `${variablesText};${cachedStyles}`;
});
const formItems = computed(() => {
const name = route.path.substring(1);
return store.variables.filter(({ lowerCaseName }) => lowerCaseName === name);
});
watch(
() => cssText.value,
(css) => {
clearTimeout(timer);
timer = setTimeout(() => {
const Sass = (window as any).Sass;
let beginTime = new Date().getTime();
console.log('sass编译开始', beginTime);
Sass &&
Sass.compile(css, async (res: Obj) => {
await awaitIframe();
const iframe = window.frames[0] as any;
if (res.text && iframe) {
console.log('sass编译成功', new Date().getTime() - beginTime);
if (!iframe.__styleEl) {
const style = iframe.document.createElement('style');
style.id = 'theme';
iframe.__styleEl = style;
}
iframe.__styleEl.innerHTML = res.text;
iframe.document.head.appendChild(iframe.__styleEl);
} else {
console.log('sass编译失败', new Date().getTime() - beginTime);
console.error(res);
}
if (res.status !== 0 && res.message) {
console.log(res.message);
}
});
}, 300);
},
{ immediate: true }
);
- Download Configuration Variable Action
Since the variable file has nearly a thousand lines, it may be larger in the future, so the Blob
file stream is directly used to generate and download.
downloadScssVariables() {
if (!store.variables.length) {
return;
}
let temp = '';
const variablesText = store.variables
.map(({ name, key, value }) => {
let comment = '';
if (temp !== name) {
temp = name;
comment = `\n// ${name}\n`;
}
return comment + `${key}: ${value};`;
})
.join('\n');
download(`// NutUI主题定制\n${variablesText}`, 'custom_theme.scss');
}
function download(content: string, filename: string) {
const eleLink = document.createElement('a');
eleLink.download = filename;
eleLink.style.display = 'none';
const blob = new Blob([content]);
eleLink.href = URL.createObjectURL(blob);
document.body.appendChild(eleLink);
eleLink.click();
document.body.removeChild(eleLink);
}
Summarize
The article introduces the implementation mechanism of NutUI's "theme customization" and "component-level style customization" functions in detail. "Theme customization" can achieve simple color switching, and "component-level style customization" is more powerful. By exposing the style variables of components, developers can almost arbitrarily modify the design style they want (component size, font, margin) . Through powerful theme customization, the use of the component library is not limited to the design scope of the original designer, and the components can be flexibly expanded, so that the application scope of the component library is wider and can meet a wider range of business scenarios.
Looking forward to your use and feedback ~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。