JSX 内连样式px转换 rem

197 阅读2分钟

github npm

场景

对于大屏(大于 1920 以上的屏幕)的适配,我们通常可以将项目中的px单位转换成rem 如:

.container {
  width: 100px;
}

// transform to rem html font-size: 16px;
.container {
  width: 6.25rem;
}

对于html字体的设置,可以如下(less 语言):

html {
  font-size: 16px;
  @media (min-width: 1920px) {
    font-size: (16 / 1920 * 100)px;
  }
}

我们知道对于样式文件(css/less/scss/stylus),可以使用 postcss-pxtorem postcss插件自动转换,如:

// postcss.config.js
module.exports = {
  plugins: {
    autoprefixer: {},
    'postcss-pxtorem': {
      rootValue: 16,
      unitPrecision: 5,
      replace: true,
      mediaQuery: false,
      minPixelValue: 1,
      propList: ['*'],
    },
  },
};

但对于 JSX(React) 中的内连样式 postcss-pxtorem 无法自动转换,需要开发人员手动转换,比较繁琐,且容易遗漏,下面介绍一个 JSX 内连样式自动转换的方案

编写babel插件自动将内连样式的px转换成rem

在介绍转换方案之前我们来下认识下,JSX 中的静态样式和动态样式

静态样式 & 动态样式

静态样式

const a = <div style={{width: 100, height: '100px'}}></div>

例子中 width 和 height 属性都是静态样式,所谓静态,是指不变的,确定的

动态样式

动态样式又可分为动态属性样式和动态style样式

动态属性样式
const width = 100;
const a = <div style={{width: width, height: width + 100}}></div>

例子中 width 和 height 属性都是动态属性样式

动态style
// case 1
const style = {width: 100, height: 200}
const a = <div style={style}></div> 
// case 2

const spreadProps = {style: style}
const a = <div {...spreadProps}></div>

JSX Style 转rem 方案

静态样式转换

const a = <div style={{width: 100, height: '100px'}}></div>

// compiled to  (automatic 模式)
import { jsx as _jsx } from '.xxxxx/jsx-runtime'
_jsx('div', {
    style: {
        width: '6.25rem',
        height: '6.25rem'
    }
})
// or compiled to (classic 模式)
import React from 'react'
React.createElement('div', {
    style: {
        width: '6.25rem',
        height: '6.25rem'
    }
})

动态属性转换

const width = 100;
const a = <div style={{width: width, height: width + 100}}></div>

// compiled to
import { jsx as _jsx } from 'react/jsx-runtime'
import { covertStylePropToRem } from 'jsx-style-px-to-rem-babel-plugin'
_jsx('div', {
    style: {
        width: covertStylePropToRem(width, 'width'),
        height: covertStylePropToRem(width + 100, 'height')
    }
})

covertStylePropToRem 为辅助函数,其作用是在运行时将 px 转换成 rem,下面的例子中出现了多个辅助函数,作用一致。

动态Style转换

// case 1
const style = {width: 100, height: 200}
const a = <div style={style}></div> 
// compiled to 
import { jsx as _jsx } from 'react/jsx-runtime'
import { covertJsxStyleToRem, covertJsxPropsToRem } from 'jsx-style-px-to-rem-babel-plugin'
_jsx('div', {
    style: covertJsxStyleToRem(style)
})


// case 2
const spreadProps = {style: style}
const a = <div {...spreadProps}></div>

// compiled to 
import { jsx as _jsx } from 'react/jsx-runtime'
import { covertJsxPropsToRem } from 'jsx-style-px-to-rem-babel-plugin'
_jsx('div', covertJsxPropsToRem(Object.assign({}, spreadProps)));

转换辅助函数

export function covertStylePropertyToRem(
  value: string | number,
  key?: string
): string | number {
  if (key) {
    const covertFn = SPECIAL_PROPERTYS_COVERTER[key];
    if (covertFn) {
      return covertFn(value) as string;
    }
  }
  if (typeof value === "number") {
    return pxtorem(value);
  }
  return covertStringPropertyToRem(value);
}

export function covertJsxStyleToRem(
  style: Record<string, any>
): Record<string, string> {
  if (!style) return style;
  // const newStyle: Record<string, any> = {};
  Object.keys(style).forEach((key) => {
    if (isIgnoreUnitProperty(key)) {
      return;
    }
    const value = style[key];
    if (!value) {
      return;
    }
    style[key] = covertStylePropertyToRem(value, key);
  });
  return style;
}

export function covertJsxPropsToRem(props: Record<string, any>) {
  if (!props) return props;
  if (!("style" in props)) return props;
  props.style = covertJsxStyleToRem(props.style);
  return props;
}

export function pxtorem(value: number): string {
  if (value === 0) return "0";
  if (getOptionsConfig().minPixelValue && value < getOptionsConfig().minPixelValue) {
    return `${value}px`;
  }
  return `${(value / getBaseFontSize()).toFixed(
    getOptionsConfig().unitPrecision
  )}rem`;
}
export function covertStringPropertyToRem(value: string): string {
  return value?.replace(PX_VALUE_REG, (_, p1) => {
    return pxtorem(parseFloat(p1));
  }) || '';
}

插件源码

github.com/chaomingd/f…

插件npm

www.npmjs.com/package/bab…