npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

skuy

v0.1.3

Published

React SSR Power Pack

Downloads

9

Readme

Getting started with Skuy

Skuy has same API as After.js.

If you have familiarize yourself with After, then you are not finding it difficult to migrate to Skuy.

Also : You can build it on your SSR boilerplate (either it webpack, parcel, etc).

Skuy is just another component wrapper to ease React SSR.

Quickstart with Razzle

curl https://codeload.github.com/rayandrews/skuy/tar.gz/master | tar -xz --strip=2 skuy-master/examples/basic
cd basic

Background

After is awesome library but it has some drawbacks that I found it difficult to modify it in my other projects such as,

  • Not able to modify routes tree
  • Not able to modify static method for get initial props (getInitialProps is good, but you should be able to modify the name based on your content)
  • Not able to add loading or error component while transitioning and getting initial props for other route
  • Code splitting and load inital props based on routes config

How about Rogue?

Brilliant Idea to walk into react tree component recursively like Apollo did in their library

But.. something is bothering me..

  • Document feature in Next and After is REALLY syntactic sugar
  • GetInitialProps method only called on top highest level component (for performance, I know)
  • And of course, not be able to modify static method too

Table of Contents


How Skuy Works

Skuy will walk through your React Tree to find static method that you've already specified.

If Skuy not found any static method, Skuy will gracefully return your rendered component.

Data Fetching

For now, in all components that you want to fetch, you can add a static async getInitialProps or another function's name that exactly does the same. This will be called on both initial server render, and then client mounts.

// Home.js
import React from 'react';
import { NavLink } from 'react-router-dom';

class Home extends React.Component {
  static async getInitialProps({ req, res, match }) {
    const stuff = await CallMyApi();
    return { stuff }; // returned value from static method not passed on props by default
  }

  render() {
    return (
      <div>
        <NavLink to="/about">About</NavLink>
        <h1>Home</h1>
      </div>
    );
  }
}

export default Home;

getInitialProps | { name } : (ctx) => object

Within getInitialProps or another function name, you will get access to all you need to fetch data on both the client and the server (same like After)

  • req?: Request: (server-only) A Express.js request object
  • res?: Request: (server-only) An Express.js response object
  • match: React Router 4's match object.
  • history: React Router 4's history object.
  • location: (client-only) React Router 4's location object.
  • isServer: Check whether code is running on server or client

You can also add another variable to be passed into static method like Redux Store, etc.

If you are using some server only modules inside getInitialProps or anoher function name, make sure to import them properly. Otherwise, it'll slow down your app.

Taken from Next

Injected Context Data

  • data: any - Whatever you have returned in getInitialProps or another function name
  • loading: boolean - Loading state while fetching data in client
  • error: boolean - Error state (but not throwing error) while fetching data in client
  • prefetch: (pathname: string) => void - Imperatively prefetch and cache data for a path.
// Home.js
import React from 'react';
import { NavLink } from 'react-router-dom';
import { SkuyConsumer } from 'skuy/core';

class Home extends React.Component {
  static async getInitialProps({ req, res, match }) {
    const stuff = await CallMyApi();
    return { stuff };
  }

  render() {
    return (
      <div>
        <NavLink to="/about">About</NavLink>
        <h1>Home</h1>
        <SkuyConsumer>
          {({ data, loading }) => (
            if(loading) return <div>'Loading...'</div>;

            return data.stuff;
          )}
        </SkuyConsumer>
      </div>
    );
  }
}

export default Home;

Using skuyHoc to wrap your context into props, just like Redux connect.

You can also pass options like LoadingComponent and ErrorComponent to reduce boilerplate in your render function.

skuyHoc : ({ LoadingComponent?: null, ErrorComponent?: null }) => (Component) => WrappedComponent

TL;DR All static methods will be hoisted

// Home.js
import React from 'react';
import { NavLink } from 'react-router-dom';
import { skuyHoc } from 'skuy/core';

class Home extends React.Component {
  static async getInitialProps({ req, res, match }) {
    const stuff = await CallMyApi();
    return { stuff };
  }

  render() {
    return (
      <div>
        <NavLink to="/about">About</NavLink>
        <h1>Home</h1>
        <div>{this.props.data.stuff}</div>
      </div>
    );
  }
}

export default skuyHoc({
  LoadingComponent: () => <div>Loading...</div>,
  ErrorComponent: () => <div>Error!</div>
})(Home);

Routing

React Router 4 is used in all over Skuy API.

Custom Options

Skuy does not need any router config, so just passing React component with React Router 4 in it, and you're done!

Skuy has default options as follows

{
  document = DefaultSkuyDocument,
  staticMethod = 'getInitialProps',
  rootId = 'root',
  dataId = 'server-app-state',
}

Example

Skuy : ({ component, data, options }) => RenderedComponent

// client.js

import React from 'react';
import { hydrate } from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import './client.css';

import { Skuy } from 'skuy/core';
import { getSsrData } from 'skuy/client';
import App from './App';

const data = getSsrData();
const options = {
  staticMethod: 'fetchData', // * change the method to make client can preload data

  // Anything else you add here will be made available
  // within static method in client
  // e.g a redux store, etc.
}

hydrate(
  <BrowserRouter>
    <Skuy component={App} data={data} options={options} />
  </BrowserRouter>,
  document.getElementById('root')
);

if (module.hot) {
  module.hot.accept();
}

render : (component = App, routerContext, options) => html : string

// server.js

import express from 'express';
import { render } from 'skuy/server';

import App from './App';

const assets = require(process.env.RAZZLE_ASSETS_MANIFEST);

const server = express();
server
  .disable('x-powered-by')
  .use(express.static(process.env.RAZZLE_PUBLIC_DIR))
  .get('/*', async (req, res) => {
    const routerContext = {};

    if (req.url.match(/.map$/)) return;

    try {
      const html = await render(App, routerContext, {
        req,
        res,
        assets,
        staticMethod: 'fetchData',
        customThing: 'thing',

        // Anything else you add here will be made available
        // within static method in server
        // e.g a redux store, etc.
      });
      res.send(html);
    } catch (error) {
      res.json(error);
    }
  });

export default server;

Code Splitting

Skuy does not defining any code splitting method like After, Next, or Rogue (with loadable-components) did.

But Skuy does enforce you to implement code splitting with other libraries

With the right custom render function, you can implement it with another React code splitting library out there such as

Custom Document

Skuy works like After and Next, you can override any html structure that suitable for your needs.

Why we need it?

It really helps if you want to add CSS or other component with side-effects (React Helmet, etc) that needs custom document structure.

Example with React Native Web

import React, { Component } from 'react';
import { AppRegistry } from 'react-native';
import { renderToStaticMarkup } from 'react-dom/server';

import { SkuyRoot, SkuyData } from 'skuy/document';

/* eslint-disable */

export default class CustomDocument extends Component {
  static async getInitialProps({ assets, data, renderPage }) {
    const page = await renderPage();

    AppRegistry.registerComponent('SkuyRoot', () => SkuyRoot);
    const { getStyleElement } = AppRegistry.getApplication('SkuyRoot', {});

    const rnwCss = renderToStaticMarkup(getStyleElement());

    return { assets, data, rnwCss, ...page };
  }

  render() {
    const {
      rootId,
      dataId,
      helmet,
      assets,
      data,
      rnwCss,
    } = this.props;

    const htmlAttrs = helmet.htmlAttributes.toComponent();
    const bodyAttrs = helmet.bodyAttributes.toComponent();

    return (
      <html lang="en" {...htmlAttrs}>
        <head>
          <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
          <meta charSet="utf-8" />
          <meta name="viewport" content="width=device-width, initial-scale=1" />
          {helmet.title.toComponent()}
          {helmet.meta.toComponent()}
          {helmet.link.toComponent()}
          {assets.client.css && (
            <link rel="stylesheet" href={assets.client.css} />
          )}
          <style
            id="react-native-stylesheet"
            dangerouslySetInnerHTML={{
              __html: rnwCss
                .replace(/<\/style>/g, '')
                .replace(/<style id="react-native-stylesheet">/g, ''),
            }}
          />
        </head>
        <body {...bodyAttrs}>
          <SkuyRoot id={rootId} />
          <SkuyData id={dataId} data={data} />
          <script
            type="text/javascript"
            src={assets.client.js}
            crossOrigin="anonymous"
          />
        </body>
      </html>
    );
  }
}

If you were using something like styled-components, and you need to wrap you entire app with some sort of additional provider or function, you can do this with renderPage().

Taken from After

// Document.js
import React, { Component } from 'react';
import { ServerStyleSheet } from 'styled-components'
import { renderToStaticMarkup } from 'react-dom/server';

import { SkuyRoot, SkuyData } from 'skuy/document';

export default class CustomDocument extends Component {
  static async getInitialProps({ assets, data, renderPage }) {
    const sheet = new ServerStyleSheet();
    const page = await renderPage(App => props => sheet.collectStyles(<App {...props} />));
    const styleTags = sheet.getStyleElement();
    return { assets, data, ...page, styleTags };
  }

  render() {
    const {
      rootId,
      dataId,
      helmet,
      assets,
      data,
      styleTags,
    } = this.props;
    const htmlAttrs = helmet.htmlAttributes.toComponent();
    const bodyAttrs = helmet.bodyAttributes.toComponent();

    return (
      <html lang="en" {...htmlAttrs}>
        <head>
          <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
          <meta charSet="utf-8" />
          <meta name="viewport" content="width=device-width, initial-scale=1" />
          {helmet.title.toComponent()}
          {helmet.meta.toComponent()}
          {helmet.link.toComponent()}
          {styleTags}
        </head>
        <body {...bodyAttrs}>
          <SkuyRoot id={rootId} />
          <SkuyData id={dataId} data={data} />
          <script
            type="text/javascript"
            src={assets.client.js}
            defer
            crossOrigin="anonymous"
          />
        </body>
      </html>
    );
  }

To use custom document, you need to pass it on server file

// server.js

import express from 'express';
import { render } from 'skuy/server';

import App from './App';
import Doc from './Document';

const assets = require(process.env.RAZZLE_ASSETS_MANIFEST);

const server = express();
server
  .disable('x-powered-by')
  .use(express.static(process.env.RAZZLE_PUBLIC_DIR))
  .get('/*', async (req, res) => {
    const routerContext = {};

    if (req.url.match(/.map$/)) return;

    try {
      const html = await render(App, routerContext, {
        req,
        res,
        assets,
        staticMethod: 'fetchData',
        customThing: 'thing',
        document: Doc,
        // Anything else you add here will be made available
        // within static method in server
        // e.g a redux store, etc.
      });
      res.send(html);
    } catch (error) {
      res.json(error);
    }
  });

export default server;

Custom/Async Rendering

You can provide a custom (potentially async) rendering function as an option to Skuy render function, just like After.js.

If it presents, it will be used instead of the default ReactDOMServer renderToString function.

It has to return an object of shape { html : string!, ...otherProps }, in which html will be used as the rendered string.

otherProps will be passed as props to the rendered Document.

defaultRenderer = (node) => ({ html: ReactDOMServer.renderToString(node) })

Example


// server.js

import express from 'express';
import { render } from 'skuy/server';

import { Capture } from 'react-loadable';
import { getBundles } from 'react-loadable/webpack';

import stats from 'build/react-loadable.json';

import configureStore from 'store/configureStore';

import App from './App';
import Doc from './Document';

const assets = require(process.env.RAZZLE_ASSETS_MANIFEST);

const server = express();
server
  .disable('x-powered-by')
  .use(express.static(process.env.RAZZLE_PUBLIC_DIR))
  .get('/*', async (req, res) => {
    const routerContext = {};

    if (req.url.match(/.map$/)) return;

    try {
      const preloadedState = {};
      const store = configureStore(preloadedState);
      const modules = [];

      const customRenderer = (node) => {
        const CustomApp = (
          <Capture report={(moduleName) => modules.push(moduleName)}>
            <Provider store={store}>{node}</Provider>
          </Capture>
        );

        const bundles = getBundles(stats, modules);
        const chunks = bundles.filter((bundle) => bundle.file.endsWith('.js'));

        return {
          chunks,
          store, // notice that this will passed into document
          html: renderToString(CustomApp),
        };
      };

      const html = await render(App, routerContext, {
        req,
        res,
        assets,
        staticMethod: 'fetchData',
        customThing: 'thing',
        document: Doc,
        store, // this will be passed in static method in server

        // Anything else you add here will be made available
        // within static method in server
        // e.g a redux store, etc.
      });
      res.send(html);
    } catch (error) {
      res.json(error);
    }
  });

export default server;

Authors


Special Thanks


Inspirations


License

This project is licensed under the MIT License - see the LICENSE.md file for details