Skip to content
This repository has been archived by the owner on Aug 4, 2019. It is now read-only.
/ ts-sfc-plugin Public archive

A plugin for optimizing stateless component of React (tsx)

License

Notifications You must be signed in to change notification settings

Saviio/ts-sfc-plugin

Repository files navigation

Codacy grade npm Coveralls github branch CircleCI branch GitHub license

ts-sfc-plugin

A plugin for optimizing stateless component of React (tsx)

Important

After react 16.8, react team announced new feature [Hooks], this plugin cannot work with it as its special runtime mechanism.

Why

React functional component(SFC) is easy to use and help to reduce code size significantly, but sometimes people might have been some misunderstanding about its perfomance. Usually, we think functional components would avoid some overheads like mounting / unmounting / lifecycle checking and memory allocations, but in fact, there're no special optimizations currently (but after react 16 was released, sfc is indeed faster than before).

Fortunately SFC just function in react world, if we do care about performance in production there're still a way to improve and this plugin here come to simplify these situation.

const code1 = (
  <div>
    <Avatar />
  </div>
)

const code2 = (
  <div>
    { Avatar() }
  </div>
)

As we cannot recognize if the component is functional, we have to use an anotation to tag the expression:

<Avatar sfc />
// Plugin use `sfc` as identifier by default, but you can pass an option to override it.

How to use

webpack

  module: {
    rules: [
      {
        test: /\.(jsx|tsx|js|ts)$/,
        loader: 'ts-loader',
        options: {
          transpileOnly: true,
          getCustomTransformers: () => ({
            before: [sfcPlugin()],
          }),
          compilerOptions: {
            module: 'esnext',
          },
        },
        exclude: /node_modules/,
      }
    ],
  }

code

import React from 'react'

export const Avatar = ({ name }) => {
  return (
    <div>
      <img src=... />
      <span>{ name }</span>
    </div>
  )
}
import React from 'react'
import { Avatar } from './avatar.component'

export class App extends React.PureComponent {
  render() {
    return (
      <div>
        <Avatar name={ 'hello world' } sfc />
      </div>
    )
  }
}

option

sfcPlugin(option?: Option)

interface Option {
  pragma?: string
  mode?: 1 | 2
}

Deopt

Reason Deopt (mode 1) Deopt (mode 2)
spread operator true false
prop: key true false

Considering we transform the code from tsx to native function-call which means the additional vd-layer will be eliminatd, and the effects of key will be removed as well.

// before

const Message = () => <div>bravo</div>

export class App extends React.PureComponent {
  render() {
    return <Message key={ 1 } />
  }
}
// after

const Message = () => <div>bravo</div>

export class App extends React.PureComponent {
  render() {
    // won't get benefit from prop: `key`
    return Message()
  }
}

Notice

Unlike @babel/plugin-transform-react-inline-elements, we won't take ref into account because of this plugin will be applied to typescript only.

const Message = () => <div>bravo</div>

export class App extends React.PureComponent {
  render() {
    // ERROR: this is not type-safe
    // { ref: any } is not assignable to IntrinsicAttributes
    return <Message ref={ ... } />
  }
}

Defect

The following code is recommanded:

<Avatar sfc>
// enable rule: `jsx-boolean-value` in tslint.json

using declaration merging in global .d.ts

import React from 'react'

declare module 'react' {
  namespace JSX {
    interface IntrinsicAttributes extends React.Attributes {
      sfc?: boolean
    }
  }
}

Exception

code like the usage will not work, because the plugin does not include any runtime type checking.

const component = <Avatar sfc={ this.props.flag } />

Benchmark

React 16.4, <Dot />, 50 times, MacBook Pro (Retina, 13-inch, Early 2013)

Classical Functional Direct-call Auto-transform
660ms 408ms 226ms 229ms

Refs

scu vs sfc

45% faster react functional components now