Skip to content

shortlist-digital/tapestry-lite

Repository files navigation

This repository is no longer maintained (as of Sept 2020).



npm circleci

Tapestry Lite

An opinionated React application service. Create React components and let Tapestry handle the data loading, server rendering, JavaScript bundling and more.

Features

  • Data handling
  • Server rendered React
  • Small, secure Node server through Hapi
  • CSS-in-JS out of the box
  • Hot reloading
  • Production ready

Installation

yarn add tapestry-lite react react-dom

Usage

Tapestry has a couple of commands to handle building and running the project, you can pop these into your NPM scripts.

tapestry will create the client/server bundles and run the server in development mode, tapestry build will create the client and server bundles for production and tapestry start will run the server in production mode.

Often we'll set up our projects like so:

{
  "scripts": {
    "start": "tapestry",
    "build": "tapestry build",
    "start:prod": "tapestry start"
  }
}

Create a tapestry.config.js in the root of your project and export an object with your site URL and routes or components to render.

import Post from './components/post'
import Page from './components/page'

export default {
  siteUrl: 'http://your-api.url',
  components: { Post, Page }
}

These components will match the default permalink routes for each page type. e.g. /2017/12/08/a-post-slug.

These default components are a simple way to connect a instance to your React application, to control the routing schema completely you can add a routes array to your config. Each route requires a path and a component for a static page, to access data pass in an endpoint or nonCacheableEndpoint

import Post from './components/post'
import Page from './components/page'

export default {
  siteUrl: 'http://your-api.url',
  routes: [
    {
      path: '/:slug/:id',
      endpoint: id => `posts/${id}`,
      component: Post
    },
    {
      path: '/about/:slug',
      endpoint: slug => `pages?filter=${slug}`,
      component: Page
    },
    {
      path: '/path/:slug',
      nonCacheableEndpoint: slug => `get/{slug}`,
      component: Post
    }
  ]
}

Options

tapestry.config.js has a number of options to modify the Tapestry bundling and server.

{
  // [string] URL for your api
  siteUrl: '',
  // [string] URL for your non cacheable api
  nonCacheableSiteUrl: '',
  // [object] Container for React components
  components: {
    // [function] React components for rendering a post, page, category
    Category,
    CustomError,
    FrontPage,
    Page,
    Post
  },
  // [array] Container for route objects
  routes: [
    {
      // [string] Path to match component
      path: '',
      path: '/path/:dynamic-path/:optional-path?'

      // [function] React component to render
      component: () => {},
      // [any] Source for API data, can be one of array, object or string, can also be a function that returns any of those data-types. When used as a function it has access to the dynamic parameters from the path
      endpoint: 'posts',
      endpoint: ['posts', 'pages'],
      endpoint: { posts: 'posts', pages: 'pages' },
      endpoint: (id) => `posts/${id}`,
      endpoint: (id) => [`posts/${id}`, `pages/${id}`],
      endpoint: (id) => { posts: `posts/${id}`, pages: `pages/${id}` }
      // [object] Container for route specific options
      options: {
        // [function] A React component to handle the surrounding document
        customDocument: ({ html, css, ids, asyncProps, assets }) => {},
        // [boolean] Return the doctype with or without the HTML string
        disableDoctype: false,
        // [string] Custom route specific URL for your api instance
        baseUrl: '',
        // [string] Custom route specific api URL
        apiBaseUrl: '',
        // [string] Custom route specific api base path, utilising the siteUrl as the base
        apiBasePath: '',
      }
    }
  ],
  // [array] Paths to proxy through to the api URL
  proxyPaths: [],
  // [object] Redirects from key to value e.g. { 'from': 'to' }
  redirectPaths: {},
  // [string] [uri] URL for JSON redirects file, will get picked up on server boot
  redirectsEndpoint: '',
  // [object] Container for site options
  options: {
    // [string] 'localhost', '0.0.0.0'
    host: '',
    // [number] 3030
    port: 3030,
    // [boolean] Registers https Hapi plugin
    forceHttps: false,
    // [boolean] Wordpress.com hosting configuration
    wordpressDotComHosting: false
  },
  // function(req, key) to modify the cache key. Uses browser request and initial cache key as arguments and must return a string which replaces the initial cache key.
  cacheKeyHandler: (request, cacheKey) => {
    let newKey = cacheKey
    if (request.headers['region'] === 'en-us') {
      newKey = cacheKey + ':region-us'
    }
    return newKey
  },
  // function(key) to declare all keys per cache entry. Uses initial cache key as arguments and must return a string or array representing all possible keys that could be used for the cache entry.
  cachePurgeHandler: (key) => {
    return [key, key + ':region-us']
  },
  // [array] Request headers you wish to access from the front-end
  headers: ['region'],
}

Commands

Tapestry comes with a series of commands to control compiling and running the server.

  • tapestry - Hot compiles the server/client JavaScript and boots the server in development mode
  • tapestry build - Compiles the server/client JavaScript
  • tapestry start - Runs any server/client bundles

Custom compilation

Babel

If you need to modify the default Tapestry babel configuration, you can create a .babelrc file in the root of your project and Tapestry will use it to override any default options. You will need to import tapestry-lite/babelrc to enable the required plugins.

Webpack

To modify the Webpack config you can create a webpack.config.js in the root of your project that exports a modified config.

An example config that adds an alias for components:

const path = require('path')
const merge = require('webpack-merge')

module.exports = (default, options, webpack) => {
  const custom = {
    resolve: {
      alias: {
        components: path.resolve(
          __dirname, 'src', 'components'
        )
      }
    }
  }
  merge(default, custom)
}

Environment variables

To configure the application per environment we have a series of environment variables available to use.

Server

TAPESTRY_HOST Define the host for the server

TAPESTRY_PORT Define the port for the server

ENABLE_CONCURRENCY Boolean to toggle multiple server instances

WEB_CONCURRENCY Number of server instances to boot, if not configured this will default to the number of cores available

SERVER_SOURCE_MAPS Boolean to toggle source maps on server JS bundles

Cache

CACHE_CONTROL_MAX_AGE Cache age for all server responses

STATIC_CACHE_CONTROL_MAX_AGE Cache age for static assets bundled from Webpack

CACHE_MAX_ITEM_COUNT Maximum number of items to store in memory or Redis

CACHE_MAX_AGE Maximum age of item in memory or Redis

REDIS_URL URL for Redis DB

SECRET_PURGE_PATH Path to clear the in memory cache or Redis, defaults to purge. Purge will use the path from /purge as the key when removing from the cache, e.g. /purge/hello/world will remove hello/world from the cache

Logging

LOG_LEVEL Tapestry has a series of logs from the server using Winston, LOG_LEVEL corresponds to the log level of Winston github.com/winstonjs/winston#logging-levels

TAPESTRY_LOGS_FOLDER Directory to store logs, defaults to writing to STDOUT

Application

CSS_PLUGIN One of emotion or glamor to switch between CSS-in-JS libraries, defaults to glamor

NODE_ENV Can be used to toggle production mode, will affect Webpack, Babel and other vendor services