0.3.0 • Published 4 years ago

@airy/maleo v0.3.0

Weekly downloads
1
License
MIT
Repository
github
Last release
4 years ago

Build Status Coverage Status MIT license Discord Chat

Welcome to Maleo.js

Maleo.js is an un-opinionated framework to enable Universal Rendering in JavaScript using React with no hassle.

We are here to solve the time consuming setups Universal Rendering Required.


Readme below is the documentation for the canary (prerelease) branch. To view the documentation for the latest stable Maleo.js version change branch to master


Table of Contents


Features

  • Universal Rendering
  • Plugin based framework
  • Customizable

Setup

Install Maleo.js

NPM

$ npm install --save @airy/maleo react react-router-dom

Yarn

$ yarn add @airy/maleo react react-router-dom

Add this script to your package.json

{
  "scripts": {
    "dev": "maleo",
    "build": "export NODE_ENV=production && maleo build",
    "start": "export NODE_ENV=production && node .maleo/server.js"
  }
}

Create a routing file called routes.js

import React from 'react';

const App = () => (<h1>Hello World</h1>);

export default [
  {
    path: '/',
    component: App,
    key: 'root',
  },
];

After that you can now run $ npm run dev and go to http://localhost:3000.

You should now see your app running on your browser.

By now you should see

  • Automatic transpilation and bundling (with webpack and babel)
  • Hot code reloading #17
  • Server rendering

To see how simple this is, check out the sample app!

Component Lifecycle

Maleo.js added a new component lifecycle hook called getInitialProps, this function is called during Server Side Rendering (SSR).

This is useful especially for SEO purposes.

Example for stateful component:

import React from 'react';

export default class extends React.Component {
  static getInitialProps = async (ctx) => {
    const { req } = ctx;

    const userAgent = req ? req.headers['user-agent'] : navigator.userAgent;

    // the return value will be passed as props for this component
    return { userAgent };
  }

  render() {
    return (
      <div>
        Hello World {this.props.userAgent}
      </div>
    );
  }
}

Example for stateless component:

const Component = (props) => (
  <div>
    Hello World {props.userAgent}
  </div>
);

Component.getInitialprops = async (ctx) => {
  const { req } = ctx;

  const userAgent = req ? req.headers['user-agent'] : navigator.userAgent;

  return { userAgent };
};

export default Component;

getInitialProps receives a context object with the following properties:

  • req - HTTP request object (server only)
  • res - HTTP response object (server only)
  • ...wrapProps - Spreaded properties from custom Wrap
  • ...appProps - Spreaded properties from custom App

Routing

Routing is declared in a centralized route config. Register all the route config in maleo-routes.json file.

If you put the maleo-routes.json files on root directory, Maleo will automatically register your route. Otherwise put path to your routes on Maleo config.

Routes file has to export default the route configuration. The route object expected to have distinct key to indicate the route.

For example:

[
  {
    "page": "./src/MainApp",
    "routes": [
      {
        "path": "/",
        "page": "./src/Search",
        "exact": true
      },
      {
        "path": "/search",
        "page": "./src/Search",
        "routes": [
          {
            "path": "/search/hello",
            "page": "./src/Detail",
            "exact": true
          }
        ]
      },
      {
        "path": "/detail",
        "page": "./src/Detail",
        "exact": true
      }
    ]
  }
]

Dynamic Import Component

Maleo.js supports TC39 dynamic import proposal for JavaScript.

You can think dynamic import as another way to split your code into manageable chunks. You can use our Dynamic function which utilizes react loadable

For Example

// DynamicComponent.js
import Dynamic from '@airy/maleo/dynamic';

export default Dynamic({
  loader: () => import( /* webpackChunkName:"DynamicComponent" */ './component/DynamicLoad'),
})

Preloading

For optimization purposes, you can also preload a component even before the component got rendered.

For example, if you want to load component when a button get pressed, you can start preloading the component when the user hovers the mouse over the button.

The component created by Dynamic exposes a static preload method.

import React from 'react';
import Dynamic from '@airy/maleo/dynamic';

const DynamicBar = Dynamic({
  loader: () => import('./Bar'),
  loading: LoadingCompoinent
});

class MyComponent extends React.Component {
  state = { showBar: false };

  onClick = () => {
    this.setState({ showBar: true });
  };

  onMouseOver = () => DynamicBar.preload();

  render() {
    return (
      <div>
        <button
          onClick={this.onClick}
          onMouseOver={this.onMouseOver}>
          Show Bar
        </button>
        { this.state.showBar && <DynamicBar /> }
      </div>
    )
  }
}

Customizable Component

Custom Document

Highly inspired by what Next.js has done on their awesome template customization.

Maleo.js also enable customization on Document as document's markup. So you don't need to include tags like <html>, <head>, <body>, etc.

To override the default behavior, you'll need to create a component that extends the Document React class provided by Maleo.

// document.jsx
import React from 'react';
import { Document, Header, Main, Scripts } from '@airy/maleo/document';

export default class extends Document {
  render() {
    return (
      <html>
        <Header>
          <title>Maleo JS</title>
          <meta charset="utf-8" />
          <meta name="description" content="Maleo.js is awesome!" />

          <style>
            {` body { background-color: #fff } `}
          </style>
        </Header>

        <body>
          <Main />

          <Scripts />

          <ReduxScript />
        </body>
      </html>
    );
  }
}

Custom Wrap

Maleo.js uses the Wrap component to initialize pages. Wrap contains React Router's Component. You can add HoC here to wrap the application and control the page initialization. Which allows you to do amazing things like:

  • Persisting layour between page changes
  • Keeping state when navigating pages
  • Custom error handling using componentDidCatch
  • Inject additional data into pages (like Redux provider, etc)

To override the default behavior, you'll need to create a component that extends the Wrap React class provided by Maleo.

// wrap.jsx
import React from 'react';
import { Wrap } from '@airy/maleo/wrap';

// Redux plugin for Maleo.js
// Hoc that creates store and add Redux Provider
import { withRedux } from '@airy/maleo-redux-plugin';

// Custom Wrapper that will be rendered for the whole Application
import CustomWrapper from './component/CustomWrapper';

import { createStore } from './store';

@withRedux(createStore)
export default class extends Wrap {
  static getInitialProps = (ctx) => {
    const { store } = ctx
    // you receive store from context
    // you can access or do something with the store here
    console.log('Initialized Redux Store', store);
    return {}
  }

  render() {
    return (
      <CustomWrapper>
        {super.render()}
      </CustomWrapper>
    )
  }
}

If you put document.jsx and wrap.jsx on root directory (the same level with package.json), then Maleo will automatically register your custom Document and Wrap. Otherwise, you can add the path to your custom Document and Wrap on Maleo config


We are also working on adding default and customizable Error component page


Custom Configuration

For more advanced configuration of Maleo.js, like webpack config, registering plugins, path to your routes, custom Document and Wrap, and adding path alias, you can create a maleo.config.js in the root of your project directory. (same directory with package.json)

// maleo.config.js

module.exports = {
  /* config options here */
}

Here are the API's for the configuration:

Customize Server

Create a server.js file on root directory where your package.json lives. Here you can customize Maleo's server.

import { Server } from '@airy/maleo/server';
import path from 'path';

import routeConfig from './routes';

const PORT = process.env.PORT || 8080;

const maleoServer = Server.init({
  port: PORT,
});

maleoServer.run(() => {
  console.log('Server running on port :', PORT);
});

Here are the API's for the configuration:

Customize Webpack

You are able to extend Maleo.js' default webpack configuration by defining a function on maleo.config.js

// maleo.config.js

module.exports = {
  webpack(config, context, next) {
    // Perform customizations to webpack config
    // Important: This call is required in order to let Maleo pass the config to webpack
    return next(); 
  },
};

Webpack function will receive three arguments:

Example of adding ts-loader through maleo.config.js:

// maleo.config.js

// Partly taken and modified from @airy/maleo-ts-plugin source
// for simplicity purposes

module.exports = {
  webpack(config, context, next) {
    const { isDev } = context

    config.module.rules.push({
      test: /\.tsx?/,
      exclude: /node_modules/,
      use: [
        require.resolve('@airy/maleo/lib/build/loaders/maleo-babel-loader'),
        {
          loader: 'ts-loader',
          options: {
            transpileOnly: true,
          },
        },
      ],

      if (isDev) {
        config.plugins.push(new ForkTSCheckerWebpackPlugin());
      }

      return next();
    })
  },
};

Customize Babel Config

Maleo.js also let you have your own babel config. Just simply add .babelrc file at the root directory of your app.

You can include Maleo.js' babel preset in order to have latest JavaScript preset.

Here's an example of .babelrc file:

{
  "presets": ["@airy/maleo/babel"],
  "plugins": []
}

The @airy/maleo/babel preset includes everything you need to get your development started. The preset includes:

  • @babel/preset-env
  • @babel/preset-react
  • @babel/plugin-proposal-class-properties
  • @babel/plugin-proposal-decorators
  • @babel/plugin-proposal-object-rest-spread
  • @babel/plugin-transform-runtime
  • react-loadable/babel

CDN Support

If you are using a CDN, you can set up the publicPath setting and configure your CDN's origin to resolve to the domain that Maleo.js is hosted on.

// maleo.config.js

const isProd = process.env.NODE_ENV === 'production';

module.exports = {
  assetPrefix: isProd && 'https://cdn.example.com';
}

FAQ

== TO BE DETERMINED ==

Plugins

Contributing

Please follow these steps to contribute to Maleo.js

Please follow these steps to contribute to Maleo.js' plugins

Contributors

This project exists thanks to all the people who contribute. Contribute. contributors

License

MIT

0.3.1-canary.36

4 years ago

0.3.1-canary.35

4 years ago

0.3.1-canary.34

4 years ago

0.3.1-canary.33

4 years ago

0.3.1-canary.32

5 years ago

0.3.1-canary.31

5 years ago

0.3.1-canary.30

5 years ago

0.3.1-canary.29

5 years ago

0.3.1-canary.28

5 years ago

0.3.1-canary.27

5 years ago

0.3.1-canary.26

5 years ago

0.3.1-canary.25

5 years ago

0.3.1-canary.24

5 years ago

0.3.1-canary.23

5 years ago

0.3.1-canary.22

5 years ago

0.3.1-canary.21

5 years ago

0.3.1-canary.20

5 years ago

0.3.1-canary.19

5 years ago

0.3.1-canary.18

5 years ago

0.3.1-canary.17

5 years ago

0.3.1-canary.16

5 years ago

0.3.1-canary.15

5 years ago

0.3.1-canary.14

5 years ago

0.3.1-canary.13

5 years ago

0.3.1-canary.12

5 years ago

0.3.1-canary.11

5 years ago

0.3.1-canary.10

5 years ago

0.3.1-canary.9

5 years ago

0.3.1-canary.8

5 years ago

0.3.1-canary.7

5 years ago

0.3.1-canary.6

5 years ago

0.3.1-canary.5

5 years ago

0.3.1-canary.4

5 years ago

0.3.1-canary.3

5 years ago

0.3.1-canary.2

5 years ago

0.3.1-canary.1

5 years ago

0.3.1-canary.0

5 years ago

0.3.0

5 years ago

0.2.1-canary.2

5 years ago

0.2.1

5 years ago

0.2.1-canary.1

5 years ago

0.2.1-canary.0

5 years ago

0.2.0

5 years ago

0.1.2-canary.0

5 years ago

0.1.1

5 years ago

0.1.1-canary.3

5 years ago

0.1.1-canary.2

5 years ago

0.1.1-canary.1

5 years ago

0.1.1-canary.0

5 years ago

0.1.0

5 years ago

0.0.15

5 years ago

0.0.15-canary.0

5 years ago

0.0.14

5 years ago

0.0.13

5 years ago

0.0.13-canary.0

5 years ago

0.0.12

5 years ago

0.0.12-canary.1

5 years ago

0.0.12-canary.0

5 years ago

0.0.11

5 years ago

0.0.10

5 years ago

0.0.10-canary.0

5 years ago

0.0.9

5 years ago

0.0.9-canary.1

5 years ago

0.0.9-canary.0

5 years ago

0.0.8

5 years ago

1.0.0

5 years ago

0.0.8-canary.3

5 years ago

0.0.8-canary.2

5 years ago

0.0.8-canary.0

5 years ago

0.0.6-canary.0

5 years ago

0.0.6-canary.49

5 years ago

0.0.5-canary.49

5 years ago

0.0.4-canary.49

5 years ago

0.0.3-canary.49

5 years ago

0.0.1-canary.49

5 years ago

0.0.1-canary.51

5 years ago