Skip to content

xuyuanxiang/typescript-styled-components-px2rem

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

typescript-styled-components-px2rem MIT

npm version NPM Downloads Build Status codecov

TypeScript transformer for convert px to rem units of styled-components

  1. Use postcss-plugin-px2rem to process all css text in template strings.

  2. Add a runtime px2rem function polyfill to process expression embedded in template strings when enable transformRuntime option.

Babel plugin with similar functionality:babel-plugin-styled-components-px2rem.

Table of Contents

Requirement

You need to install the following peerDependencies of typescript-styled-components-px2rem into your project at the same time:

{ "peerDependencies": { "typescript": "^3.0.0", "postcss": "^7.0.0" } }

Usage

ttypescript compiler

see example

The use of React and styled-components test cases.

Integration with rollup-plugin-typescript2 and ttypescript:

import typescript2 from 'rollup-plugin-typescript2'; import tts from 'ttypescript'; export default { // ... other rollup configs plugins: [ // ...other rollup plugins typescript2({ typescript: tts, objectHashIgnoreUnknownHack: true, tsconfigOverride: { compilerOptions: { module: 'ES2015', plugins: [ { transform: 'typescript-styled-components-px2rem', rootValue: 100, unitPrecision: 5, minPixelValue: 0, multiplier: 1, tags: ['styled', 'css', 'createGlobalStyle', 'keyframes'], transformRuntime: false, }, ], }, }, }), // ...other rollup plugins ], };

Integration with awesome-typescript-loader or ts-loader:

const createCustomTransformer = require('typescript-styled-components-px2rem').default; const customTransformer = createCustomTransformer({ rootValue: 100, unitPrecision: 5, minPixelValue: 0, multiplier: 1, tags: ['styled', 'css', 'createGlobalStyle', 'keyframes'], transformRuntime: false, }); module.exports = { // ... other webpack configs module: { rules: [ { test: /\.tsx?$/, // loader: 'awesome-typescript-loader', loader: 'ts-loader', // ts-loader or awesome-typescript-loader options: { // ... other loader options getCustomTransformers: () => ({ before: [customTransformer] }), }, }, ], }, };

Integration with ts-jest and ttypescript:

jest.config.js:

module.exports = { // other jest configs globals: { 'ts-jest': { compiler: 'ttypescript', }, }, };

tsconfig.json:

{ "compilerOptions": { "plugins": [ { "transform": "typescript-styled-components-px2rem", "type": "config", "rootValue": 100, "unitPrecision": 5, "minPixelValue": 0, "multiplier": 1, "tags": ["styled", "css", "createGlobalStyle", "keyframes"], "transformRuntime": false } ] } }

Composition

It should be put before typescript-plugin-styled-components

tsconfig.json:

{ "compilerOptions": { "plugins": [ { "transform": "typescript-styled-components-px2rem", "type": "config" }, { "transform": "typescript-plugin-styled-components", "type": "config" } ] } }

Options

name type required default description
rootValue number false 100 The root element font size
unitPrecision number false 5 The decimal numbers to allow the REM units to grow to
minPixelValue number false 0 Set the minimum pixel value to replace
multiplier number false 1 The multiplier of input value
tags string[] false ["styled", "css", "createGlobalStyle", "keyframes"] styled-components template literal tagged
transformRuntime boolean false false since 1.1.0,enable transformation of all expressions that embedded in template strings

Simple version of the formula:

const input = '32px'; // the value in css text const pixels = parseFloat(input); if (pixels < minPixelValue) { return input; } const fixedVal = toFixed((pixels * multiplier) / rootValue, unitPrecision); return `${fixedVal}rem`;

Remaining options are consistent with postcss-plugin-px2rem.

Transform Runtime

If enabled transformRuntime option, all supported expressions embedded in template strings are processed as follows:

Note: Only expression that end with px will be processed.

FunctionExpression

source code:

import styled from 'styled-components'; export const FunctionExpression = styled.button<{ width?: number | string }>`  width: ${function(props) {  return props.width;  }}px; /* Block Body */  ${props => (props.disabled ? 'height: 400px' : 'height: 200px')}; `;

compiled:

import styled from 'styled-components'; export const FunctionExpression = styled.button`  width: ${(...args) =>  px2rem_1(function(props) {  return props.width;  }, ...args)}; /* Block Body */  ${props => (props.disabled ? 'height: 4rem' : 'height: 2rem')}; `; function px2rem_1(input, ...args) { if (typeof input === 'function') return px2rem_1(input(...args), ...args); var value = parseFloat(input); var pixels = Number.isNaN(value) ? 0 : value; if (Math.abs(pixels) < 0) return `${pixels}px`; var multiplier = Math.pow(10, 5 + 1); var wholeNumber = Math.floor(((pixels * 1) / 100) * multiplier); return `${(Math.round(wholeNumber / 10) * 10) / multiplier}rem`; }

ArrowFunctionExpression

source code:

import styled from 'styled-components'; const height = '44'; export const ArrowFunction = styled.input.attrs(props => ({ type: 'password', size: props.size || '16px', width: props.width || 100, }))`  color: palevioletred;  font-size: 14px;  border: 1px solid palevioletred;  border-radius: 8px;  width: ${props => props.width}px; /* PropertyAccess Body */  height: ${() => height}px; /* Identifier Body */  line-height: ${() => '44'}px; /* StringLiteral Body */  margin: ${() => 32}px; /* NumericLiteral Body */  padding: ${props => props.size}; `; export const ArrowFunctionWithBlockBody = styled.button<{ width?: number | string }>`  width: ${props => {  if (props.width) {  return props.width;  } else {  return 0;  }  }}px; /* Block Body */  ${props => (props.disabled ? 'height: 400px' : 'height: 200px')}; `; export const ArrowFunctionWithBinaryBody = styled.button<{ height?: number }>`  ${props =>  props.disabled &&  `  width: 200px;  font-size: 14px;  `};  height: ${props => !props.disabled && props.height}px; /* Binary Body */ `; export const ArrowFunctionWithConditionalBody = styled.button<{ height?: number }>`  height: ${props => (props.height ? height : 100)}px; /* Conditional Body */ `;

compiled:

import styled from 'styled-components'; const height = '44'; export const ArrowFunction = styled.input.attrs(props => ({ type: 'password', size: props.size || '0.16rem', width: props.width || 100, }))`  color: palevioletred;  font-size: 0.14rem;  border: 1px solid palevioletred;  border-radius: 0.08rem;  width: ${props => px2rem_1(props.width)}; /* PropertyAccess Body */  height: ${() => px2rem_1(height)}; /* Identifier Body */  line-height: ${() => px2rem_1('44')}; /* StringLiteral Body */  margin: ${() => px2rem_1(32)}; /* NumericLiteral Body */  padding: ${props => props.size}; `; export const ArrowFunctionWithBlockBody = styled.button`  width: ${props =>  px2rem_1(() => {  if (props.width) {  return props.width;  } else {  return 0;  }  })}; /* Block Body */  ${props => (props.disabled ? 'height: 4rem' : 'height: 2rem')}; `; export const ArrowFunctionWithBinaryBody = styled.button`  ${props =>  props.disabled &&  `  width: 2rem;  font-size: 0.14rem;  `};  height: ${props => px2rem_1(!props.disabled && props.height)}; /* Binary Body */ `; export const ArrowFunctionWithConditionalBody = styled.button`  height: ${props => (props.height ? px2rem_1(height) : px2rem_1(100))}; /* Conditional Body */ `; function px2rem_1(input, ...args) { if (typeof input === 'function') return px2rem_1(input(...args), ...args); var value = parseFloat(input); var pixels = Number.isNaN(value) ? 0 : value; if (Math.abs(pixels) < 0) return `${pixels}px`; var multiplier = Math.pow(10, 5 + 1); var wholeNumber = Math.floor(((pixels * 1) / 100) * multiplier); return `${(Math.round(wholeNumber / 10) * 10) / multiplier}rem`; }

PropertyAccessExpression

source code:

import styled from 'styled-components'; export const PropertyAccessExpression = styled.button<{ width: number; height: string }>( props => `  width: ${props.width}px;  height: ${props.height}; /* Note: Only expression end with 'px' will be processed. */ `, );

compiled:

import styled from 'styled-components'; export const PropertyAccessExpression = styled.button( props => `  width: ${px2rem_1(props.width)};  height: ${props.height}; /* Note: Only expression end with 'px' will be processed. */ `, ); function px2rem_1(input, ...args) { if (typeof input === 'function') return px2rem_1(input(...args), ...args); var value = parseFloat(input); var pixels = Number.isNaN(value) ? 0 : value; if (Math.abs(pixels) < 0) return `${pixels}px`; var multiplier = Math.pow(10, 5 + 1); var wholeNumber = Math.floor(((pixels * 1) / 100) * multiplier); return `${(Math.round(wholeNumber / 10) * 10) / multiplier}rem`; }

ConditionalExpression

source code:

import React from 'react'; import styled from 'styled-components'; export const ConditionalExpression = function({ fontSize }: { fontSize?: unknown }) { const StyledButton = styled.button`  font-size: ${typeof fontSize === 'number' ? fontSize : props => props?.theme.fontSize}px;  `; return <StyledButton />; }; export const ConditionalExpressionWhenTrue = function({ fontSize }: { fontSize?: unknown }) { const StyledButton = styled.button`  font-size: ${typeof fontSize !== 'number' ? props => props?.theme.fontSize : fontSize}px;  `; return <StyledButton />; };

compiled:

import React from 'react'; import styled from 'styled-components'; export const ConditionalExpression = function({ fontSize }) { const StyledButton = styled.button`  font-size: ${typeof fontSize === 'number' ? px2rem_1(fontSize) : props => px2rem_1(props?.theme.fontSize)};  `; return React.createElement(StyledButton, null); }; export const ConditionalExpressionWhenTrue = function({ fontSize }) { const StyledButton = styled.button`  font-size: ${typeof fontSize !== 'number' ? props => px2rem_1(props?.theme.fontSize) : px2rem_1(fontSize)};  `; return React.createElement(StyledButton, null); }; function px2rem_1(input, ...args) { if (typeof input === 'function') return px2rem_1(input(...args), ...args); var value = parseFloat(input); var pixels = Number.isNaN(value) ? 0 : value; if (Math.abs(pixels) < 0) return `${pixels}px`; var multiplier = Math.pow(10, 5 + 1); var wholeNumber = Math.floor(((pixels * 1) / 100) * multiplier); return `${(Math.round(wholeNumber / 10) * 10) / multiplier}rem`; }

Other Expressions

Identifier, CallExpression, BinaryExpression, ...

source code:

import styled, { css, createGlobalStyle } from 'styled-components'; const fontSize = 18; export const GlobalStyle = createGlobalStyle`  html body {  font-size: ${fontSize}px;  } `; function getHeight() { const height = 100; return height / 2; } const mixins = css`  padding: 0 16px; `; export const MixinsButton = styled.button`  ${mixins};  height: ${getHeight()}px; `; const condition = false; function calc() { return 20; } export const BinaryExpression = styled.button`  ${condition ||  `  width: 200px;  `};  height: ${condition || 100}px;  padding: ${40 + 50}px 8px ${4}px 16px;  line-height: ${calc() - 2}px; `;

compiled:

import styled, { css, createGlobalStyle } from 'styled-components'; const fontSize = 18; export const GlobalStyle = createGlobalStyle`  html body {  font-size: ${px2rem_1(fontSize)};  } `; function getHeight() { const height = 100; return height / 2; } const mixins = css`  padding: 0 0.16rem; `; export const MixinsButton = styled.button`  ${mixins};  height: ${px2rem_1(getHeight())}; `; const condition = false; function calc() { return 20; } export const BinaryExpression = styled.button`  ${condition ||  `  width: 2rem;  `};  height: ${px2rem_1(condition || 100)};  padding: ${px2rem_1(40 + 50)} 0.08rem ${px2rem_1(4)} 0.16rem;  line-height: ${px2rem_1(calc() - 2)}; `; function px2rem_1(input, ...args) { if (typeof input === 'function') return px2rem_1(input(...args), ...args); var value = parseFloat(input); var pixels = Number.isNaN(value) ? 0 : value; if (Math.abs(pixels) < 0) return `${pixels}px`; var multiplier = Math.pow(10, 5 + 1); var wholeNumber = Math.floor(((pixels * 1) / 100) * multiplier); return `${(Math.round(wholeNumber / 10) * 10) / multiplier}rem`; }

Polyfill

Maybe you need import some polyfills from core-js only once in your entry file to support outdated user agent like: iOS 7.x, iOS 8.x and android 4.x.

import 'core-js/es/number/is-nan'; import 'core-js/es/parse-float';

About

TypeScript transformer for convert px to rem units of styled-components

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published