2
头图

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 ~


京东设计中心JDC
696 声望1k 粉丝

致力为京东零售消费者提供完美的购物体验。以京东零售体验设计为核心,为京东集团各业务条线提供设计支持, 包括线上基础产品体验设计、营销活动体验设计、品牌创意设计、新媒体传播设计、内外部系统产品设计、企...