Giter Site home page Giter Site logo

ggd543 / rax Goto Github PK

View Code? Open in Web Editor NEW

This project forked from alibaba/rax

0.0 1.0 0.0 18.08 MB

[:tada: v1.0 released] The fastest way to build universal application.

License: Other

JavaScript 96.84% HTML 2.75% CSS 0.12% Shell 0.07% Vue 0.22%

rax's Introduction

Rax

The fastest way to build universal application.

gzip size


๐ŸŽ„ Familiar: React compatible API with Class Component and Hooks.

๐Ÿฌ Tiny: ~7 KB minified + gzipped.

๐ŸŒ Universal: works with DOM, Weex, Node.js, Mini-program, WebGL and could work more container that implements driver specification.

๐ŸŒ Easy: using via rax-cli with zero configuration, one codebase with universal UI toolkit & APIs.

๐Ÿญ Friendly: developer tools for Rax development.



Quick Start

Install via NPM

Quickly add rax to your project:

$ npm install rax
$ npm install driver-dom

Starter template

// Hello.jsx
import {createElement, useState} from 'rax';

export default (props) => {
  const [name, setName] = useState(props.name);
  const handleClick = () => {
    setName('rax');
  };
  return (
    <h1 onClick={handleClick}> Hello {name}</h1>
  );
}
// app.jsx
import {render} from 'rax';
import DriverDOM from 'driver-dom';
import Hello from './Hello';

render(<Hello name="world" />, document.body, { driver: DriverDOM });

Using via CLI

Install the Rax CLI tools to init project:

$ npm install rax-cli -g
$ rax init <YourProjectName>

Start local server to launch project:

$ cd YourProjectName
$ npm run start

Using via CDN

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello World</title>
    <script src="https://unpkg.com/[email protected]/dist/rax.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/driver-dom.js"></script>
    
    <!-- Don't use this in production: -->
    <script src="https://unpkg.com/[email protected]/babel.min.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel">
      // @jsx Rax.createElement
      Rax.render(
        <h1>Hello, world!</h1>,
        document.getElementById('root'),
        { driver: DriverDOM }
      );
    </script>
  </body>
</html>

Guides

Server-side rendering and hydration

Use renderToString() to generate HTML on the server and send the markup down on the initial request for faster page loads and to allow search engines to crawl your pages for SEO purposes.

// MyComponent.jsx
function MyComponent(props) {
  return <h1>Hello world</h1>;
}
import express from 'express';
import renderer from 'rax-server-renderer';
import {createElement} from 'rax';
import MyComponent from './MyComponent';

const app = express();
app.listen(8080);

app.get('/hello', (req, res) => {
  let html = renderer.renderToString(createElement(MyComponent));
  res.send(`<!DOCTYPE html><html><body>${html}</body></html>`);
});

If you call hydrate() on a node that already has this server-rendered markup, Rax will preserve it and only attach event handlers, allowing you to have a very performant first-load experience.

import hydrate from 'rax-hydrate';
import MyComponent from './MyComponent';

hydrate(<MyComponent />, document.body);

App Router

Use useRouter to config routing rules, each route map to a component.

import { createElement, Fragment } from 'rax';
import { useRouter, push } from 'rax-use-router';
import { createHashHistory } from 'history';
import Foo from './Foo';
import NotFound from './NotFound';

const config = () => {
  return {
    history: createHashHistory(),
    routes: [
      // Static Component
      {
        path: '',
        component: <>
          <button onClick={() => push('/home')}>go home</button>
          <button onClick={() => push('/404')}>go 404</button>
        </>,
      },
      {
        path: '/home',
        routes: [
          // Dynamic Component 
          {
            path: '',                  // www.example.com/home
            component: () => <>
              <button onClick={() => push('/foo')}>go foo</button>
              <button onClick={() => push('/bar')}>go bar</button>
              <button onClick={() => push('/home/jack')}>go jack</button>
            </>,
          },
          // URL Parameters
          {
            path: '/:username',        // www.example.com/home/xxx
            component: (params) => <>
              <p>{params.username}</p>
              <button onClick={ () => push('/home') }>Go home</button>
            </>
          }
        ]
      },
      // Code Splitting
      {
        path: '/bar',
        routes: [
          {
            path: '',                 // www.example.com/bar
            component: () => import(/* webpackChunkName: "bar" */ './Bar').then((Bar) => {
              // interop-require see: https://www.npmjs.com/package/interop-require
              Bar = Bar.__esModule ? Bar.default : Bar;
              // return a created element
              return <Bar />;
            }),
          },
        ],
      },
      {
        path: '/foo',                 // www.example.com/foo
        component: () => <Foo />,  
      },
      // No match (404)
      {
        component: () => <NotFound />,
      }
    ]
  }
};

export default function Example() {
  const { component } = useRouter(config);
  return component;
}
// Foo.jsx
import { createElement } from 'rax';
import { push } from 'rax-use-router';

export default function Foo() {
  return <button onClick={ () => push('/home') }>Go home</button>
}
// Bar.jsx
import { createElement } from 'rax';
import { push } from 'rax-use-router';

export default function Bar() {
  return <button onClick={ () => push('/home') }>Go home</button>
}
// NotFound.jsx
import { createElement } from 'rax';
import { replace } from 'rax-use-router';

export default function NotFound() {
  return <button onClick={ () => replace('/home') }>Go home</button>
}

Asynchronous Operation

import { createElement, useMemo } from 'rax';
import usePromise from 'rax-use-promise';

const fetchData = () => fetch('https://httpbin.org/get').then(res => res.json());

function Example() {
  const [data, error] = usePromise(useMemo(fetchData));
  if (error) {
    return <p>error</p>
  } else if (data) {
    return <p>{data.foo}</p>
  }
}

Fetch Data

import { createElement } from 'rax';
import useFetch from 'rax-use-fetch';

function Example() {
  const [data, error] = useFetch('https://httpbin.org/get');
  if (error) {
    return <p>error</p>;
  } else if (data) {
    return <p>{data.foo}</p>;
  } else {
    return <p>loading</p>;
  }
}

Code Splitting

Code-Splitting allows you to split your code into various bundles which can then be loaded on demand or in parallel. It can be used to achieve smaller bundles and control resource load prioritization which, if used correctly, can have a major impact on load time.

Code-Splitting is supported by Webpack which can create multiple bundles that can be dynamically loaded at runtime.

import { createElement } from 'rax';
import useImport from 'rax-use-import';

export default function App() {
  const [Bar, error] = useImport(() => import(/* webpackChunkName: "bar" */ './Bar'));
  if (error) {
    return <p>error</p>;
  } else if (Bar) {
    return <Bar />
  } else {
    return <p>loading</p>;
  }
}

Testing

rax-test-renderer provides an renderer that can be used to render Rax components to pure JavaScript objects, without depending on the DOM or a native mobile environment:

import {createElement} from 'rax';
import renderer from 'rax-test-renderer';

const tree = renderer.create(
  <Link page="https://example.com/">Example</Link>
);

console.log(tree.toJSON());
// { tagName: 'A',
//   attributes: { href: 'https://example.com/' },
//   children: [ 'Example' ] }

You can also use Jest's snapshot testing feature to automatically save a copy of the JSON tree to a file and check in your tests that it hasn't changed: http://facebook.github.io/jest/blog/2016/07/27/jest-14.html.

import {createElement} from 'rax';
import renderer from 'rax-test-renderer';

test('Link renders correctly', () => {
  const tree = renderer.create(
    <Link page="https://example.com">Example</Link>
  ).toJSON();
  expect(tree).toMatchSnapshot();
});

Developer Tools

You can inspect and modify the state of your Rax components at runtime using the React Developer Tools browser extension.

  1. Install the React Developer Tools extension
  2. Import the "rax/lib/devtools" module in your app
import 'rax/lib/devtools';
  1. Set process.env.NODE_ENV to 'development'
  2. Reload and go to the 'React' tab in the browser's development tools

React compatibility

Add an alias for react and react-dom in webpack config that makes React-based modules work with Rax, without any code changes:

{
  // ...
  resolve: {
    alias: {
      'react': 'rax/lib/compat',
      'react-dom': 'rax-dom'
    }
  }
  // ...
}

Use TypeScript

Install TypeScript:

$ npm install typescript --save-dev

Install TypeScript loader for webpack:

$ npm install ts-loader --save-dev

Create or update webpack.config.js:

module.exports = {
  mode: "development",
  devtool: "inline-source-map",
  entry: "./app.tsx",
  output: {
    filename: "bundle.js"
  },
  resolve: {
    // Add `.ts` and `.tsx` as a resolvable extension.
    extensions: [".ts", ".tsx", ".js"]
  },
  module: {
    rules: [
      // all files with a `.ts` or `.tsx` extension will be handled by `ts-loader`
      { test: /\.tsx?$/, loader: "ts-loader" }
    ]
  }
};

Add a tsconfig.json file:

{
  "compilerOptions": {
    "sourceMap": true,
    "jsx": "react",
    "jsxFactory": "createElement"
  }
}

Write your first TypeScript file using Rax:

// app.tsx
import { createElement, render } from 'rax';
import * as DriverDOM from 'driver-dom';

interface HelloProps { compiler: string; framework: string; }

const Hello = (props: HelloProps) => <h1>Hello from {props.compiler} and {props.framework}!</h1>;

render(<Hello compiler="TypeScript" framework="React" />, document.body, { driver: DriverDOM});

API Reference

Creating Elements

  • createElement(type, [props], [...children])
createElement('div', { id: 'foo' }, createElement('p', null, 'hello world'));

Fragments

  • Fragment
    <Fragment>
      <header>A heading</header>
      <footer>A footer</footer>
    </Fragment>

Refs

  • createRef()
    const inputRef = createRef();
    function MyComponent() {
      return <input type="text" ref={inputRef} />;
    }
  • forwardRef()
    const MyButton = forwardRef((props, ref) => (
      <button ref={ref}>
        {props.children}
      </button>
    ));
    
    // You can get a ref directly to the DOM button:
    const ref = createRef();
    <MyButton ref={ref}>Click me!</MyButton>;

Hooks

  • useState()
    function Example() {
      // Declare a new state variable, which we'll call "count"
      const [count, setCount] = useState(0);
    
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>
            Click me
          </button>
        </div>
      );
    }
  • useEffect()
    function Example() {
      const [count, setCount] = useState(0);
    
      // Similar to componentDidMount and componentDidUpdate:
      useEffect(() => {
        document.title = `You clicked ${count} times`;
      });
    
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>
            Click me
          </button>
        </div>
      );
    }
  • useLayoutEffect()
    function Example() {
      const [count, setCount] = useState(0);
    
      useLayoutEffect(() => {
        // Fires in the same phase as componentDidMount and componentDidUpdate
      });
    
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={() => setCount(count + 1)}>
            Click me
          </button>
        </div>
      );
    }
  • useContext()
    // Create a Context
    const NumberContext = createContext();
    
    function Example() {
      const value = useContext(NumberContext);
      return <div>The answer is {value}.</div>;
    }
  • useRef()
    function TextInputWithFocusButton() {
      const inputEl = useRef(null);
      const onButtonClick = () => {
        // `current` points to the mounted text input element
        inputEl.current.focus();
      };
      return (
        <>
          <input ref={inputEl} type="text" />
          <button onClick={onButtonClick}>Focus the input</button>
        </>
      );
    }
  • useCallback()
    const memoizedCallback = useCallback(
      () => {
        doSomething(a, b);
      },
      [a, b],
    );
  • useMemo()
    const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  • useReducer()
    const initialState = {count: 0};
    
    function reducer(state, action) {
      switch (action.type) {
        case 'reset':
          return initialState;
        case 'increment':
          return {count: state.count + 1};
        case 'decrement':
          return {count: state.count - 1};
        default:
          // A reducer must always return a valid state.
          // Alternatively you can throw an error if an invalid action is dispatched.
          return state;
      }
    }
    
    function Counter({initialCount}) {
      const [state, dispatch] = useReducer(reducer, {count: initialCount});
      return (
        <>
          Count: {state.count}
          <button onClick={() => dispatch({type: 'reset'})}>
            Reset
          </button>
          <button onClick={() => dispatch({type: 'increment'})}>+</button>
          <button onClick={() => dispatch({type: 'decrement'})}>-</button>
        </>
      );
    }
  • useImperativeHandle()
    function FancyInput(props, ref) {
      const inputRef = useRef();
      useImperativeHandle(ref, () => ({
        focus: () => {
          inputRef.current.focus();
        }
      }));
      return <input ref={inputRef} />;
    }
    FancyInput = forwardRef(FancyInput);

Performance

  • memo()
    function MyComponent(props) {
      /* render using props */
    }
    function areEqual(prevProps, nextProps) {
      /* 
        return true if passing nextProps to render would return
        the same result as passing prevProps to render,
        otherwise return false
      */
    }
    export default memo(MyComponent, areEqual);

Rendering Elements

  • render(element [, container] [, options] [, callback])
    render(<HelloMessage name="world" />, document.body, { driver: DomDriver })

Class Component

  • Component
  • PureComponent

Version

  • version

rax-children

  • Children
    • Children.map(children, function[(thisArg)])
    • Children.forEach(children, function[(thisArg)])
    • Children.count(children)
    • Children.only(children)
    • Children.toArray(children)

rax-proptypes

  • PropTypes
    • PropTypes.array
    • PropTypes.bool
    • PropTypes.func
    • PropTypes.number
    • PropTypes.object
    • PropTypes.string
    • PropTypes.symbol
    • PropTypes.element
    • PropTypes.node
    • PropTypes.any
    • PropTypes.arrayOf
    • PropTypes.instanceOf
    • PropTypes.objectOf
    • PropTypes.oneOf
    • PropTypes.oneOfType
    • PropTypes.shape

rax-is-valid-element

  • isValidElement(object)

rax-clone-element

  • cloneElement(element, [props], [...children])

rax-create-factory

  • createFactory(type)

rax-create-portal

  • createPortal(child, container)

rax-hydrate

  • hydrate(element, container[, callback])

rax-find-dom-node

  • findDOMNode(component)

rax-unmount-component-at-node

  • unmountComponentAtNode(container)

Legacy API

rax-create-class

  • createClass()

Contributing

Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on our guidelines for contributing.

Core Team


@yuanyan

Core


@imsobear

Development


@yacheng

Universals & Components


@boiawang

Loaders & Plugins


@wssgcg1213

DSL Runtimes & Loaders

Users


โฌ† back to top

rax's People

Contributors

alvinhui avatar appli456 avatar azu avatar battle-ooze avatar boiawang avatar buaaljy avatar chenjun1011 avatar clarence-pan avatar crazybear avatar fengwuxp avatar franklife avatar gaohaoyang avatar gengjiawen avatar hijiangtao avatar huxiaoqi567 avatar imsobear avatar jasminecjc avatar leanhunter avatar noyobo avatar playing avatar solojiang avatar tinple avatar weekeight avatar wssgcg1213 avatar xcodebuild avatar yacheng avatar yongningfu avatar yuanyan avatar yujiangshui avatar zhangmengxue avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.