乐趣区

关于前端:提升组件库通用能力-NutUI-在线主题定制功能探索

开发背景

NutUI 作为京东格调的组件库,已具备 H5 和多端小程序开发能力。随着业务的一直倒退,组件库的利用场景越来越广。在公司内外面临诸如科技、金融、物流等各多个大型团队应用时,繁多的京东 APP 视觉虽能够一键进行换肤操作,然而对于更个性化的定制需要(组件级款式、标准、尺寸等)近千行的主题款式变量对开发者来说工作量是十分大的。为晋升开发体验,进步开发者效率,增强换肤性能以及实现「组件级式定制」性能火烧眉毛。

设计指标

容许用户在开发阶段切换不同主题格调的皮肤,也容许开发者对指定的组件间接进行款式批改,以满足不同设计格调的挪动端业务场景。

效率晋升

官网会提供多套主题供开发者抉择,同时开发者也能够在多套主题根底上进行实时编辑批改,实现后下载配置变量,利用在我的项目中即可,十分易上手。实现一个全局款式配置仅需 1 分钟。
绝对这种场景下的需要开发是比拟快的,可能升高开发成本。

组件粒度

主题定制配置层分为全局根本变量、组件根本变量,开发者能够批改全局,比方组件库的全局主题色彩,字体等款式。组件层的配置能够更粗疏,比方 Button 按钮胜利类型的圆角边框尺寸

通用扩大能力

现阶段官网会提供一些优质主题集成到官网的,对于社区开发者、开发团队、如果您的团队定制的款式主题文件受众十分之广,能够分割咱们,将您的主题内置到官网 npm 包中,造福更多的开发者

开发者如何应用

视频教程

NutUI 一分钟疾速在线主题定制 https://www.bilibili.com/video/BV1fi4y1D7qb

1、关上在线配置网站,依照下方图片进行批改预览下载

2、本地我的项目配置

批改本地我的项目 webpack 或者 vite 的配置文件将下载后的 custom_theme.sass 文件,集成到我的项目中比方assets/styles/custom_theme.sass

  • vite 构建工具应用示例 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 构建工具应用示例
{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 小程序应用示例

批改 config/index.js 文件中配置 scss 文件全局笼罩如:

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";`
    },
  // ...

实现原理解析

整个组件库主题定制模块,实现能够分为两个方向,一个是外部的组件库设计(供开发者应用配置每个款式变量),另一个是在线配置官网(供开发者便捷的批改),接下来顺次依照设计图来论述。

组件库外部设计

首先源码外部 style 文件夹下,别离存在 variables.scssvariables-jdt.scss 多个文件对应的不同的官网主题,每个主题的全局的 variables.scss 文件,外部其实按规范的规定寄存寄存通用款式变量和每个组件的款式变量,像上面一样

// --------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 
// ...

这里啰嗦一句,能够看到每一行前面都有一个 !default,这个是必不可少的,如果不加,开发者本地我的项目是无奈笼罩这个变量的

https://www.sass.hk/docs/#t6-9 Tips: 能够在变量的结尾增加 !default 给一个未通过 !default 申明赋值的变量赋值,此时,如果变量曾经被赋值,不会再被从新赋值,然而如果变量还没有被赋值,则会被赋予新的值。

对于每一个组件的外部,例如 button/index.scss 下是这样援用height: $button-default-height;

.nut-button {
  position: relative;
  display: inline-block;
  flex-shrink: 0;
  height: $button-default-height;
  // ...
}

其实最终组件库构建成 npm 包时,将主题的全局的 variables.scss 等主题文件裸露给开发者,而后开发者依据需要替换其中的款式变量,至此组件库外部实现主题定制就实现了

可视化配置官网

源码领先看:https://github.com/jdf2e/nutui/tree/theme/src/sites/doc/components/ThemeSetting

整体实现流程如下,接下来顺次论述

  • variables.scss 源文件,通过组件配置数据 + 正则匹配拆分,失去这样的数据结构
// 主色调
$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) });
  • 接下来依据组件不同展现该组件下所有变量,监听组件切换切换或者编辑,进行实时编译
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}
  );
  • 下载配置变量操作

因为变量文件近千行,当前可能还会更大,间接采纳 Blob 文件流进行生成下载。

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);
}

总结

文章具体介绍了 NutUI 的「主题定制」和「组件级款式定制」性能实现机制。「主题定制」能实现简略的色彩切换,「组件级款式定制」性能更弱小,通过将组件的款式变量裸露进去开发者简直能够任意批改本人想要的设计格调(组件尺寸、字体、边距)。通过弱小的主题定制能够让组件库的应用不局限于原设计者的设计领域,可灵便扩大组件,让组件库的利用范畴更广,能满足更宽泛的业务场景。

期待您的应用与反馈 ❤️~

退出移动版