README
Code driven CMS powered by GraphQL & React.
- How to use
- Production deployment
- Browser support
- Contributing
- Authors
How to use
Setup
Install it:
npm install --save smooth-core react react-dom
and add a script to your package.json like this:
{
"scripts": {
"dev": "smooth",
"build": "smooth build",
"start": "smooth start"
}
}
After that, the file-system is the main API. Every .js
file becomes a route that gets automatically processed and rendered.
Populate ./pages/index$.js
inside your project:
export default () => <div>Welcome to smooth.js!</div>
and then just run npm run dev
and go to http://localhost:3000
. To use another port, you can run npm run dev -- -p <your port here>
.
So far, we get:
- Automatic transpilation and bundling (with webpack and babel)
- Hot code reloading
- Server rendering and indexing of
./pages
- Static file serving.
./static/
is mapped to/static/
(given you create a./static/
directory inside your project)
Content
Link to another content
To link a content, you have to know two things: the slug and the name of the content page.
You can find the content slug in metadata and the name of the content page is just the name of the page file.
The Link
component take care of the language for you, you can safely use it to create a link to another page.
import React from 'react'
import gql from 'graphql-tag'
import { Link } from 'smooth-core/router'
export const contentFragment = gql`
fragment PageProps on Page {
books {
metadata {
id
slug
}
name
}
}
`
export default function Page({ books }) {
return (
<ul>
{allBooks.map(book => (
<li key={book.metadata.id}>
<Link to={`/books/${book.metadata.slug}`}>{book.name}</Link>
</li>
))}
</ul>
)
}
Automatic code splitting
Every import
you declare gets bundled and served with each page. That means pages never load unnecessary code!
import cowsay from 'cowsay-browser'
export default () => <pre>{cowsay.say({ text: 'hi there!' })}</pre>
CSS
Built-in CSS support
Smooth.js doesn't provides a built-in CSS in JS solution. But emotion is the most easy solution, because it doesn't require any SSR configuration. You can install it and use in the project.
Examples
/** @jsx jsx */
import { jsx, css } from '@emotion/core'
export default () => (
<div
css={css`
font: 15px Helvetica, Arial, sans-serif;
background: #eee;
padding: 100px;
text-align: center;
transition: 100ms ease-in background;
&:hover {
background: #ccc;
}
`}
>
<p>Hello World</p>
</div>
)
Please see the emotion documentation for more examples.
CSS-in-JS
Examples
It's possible to use any existing CSS-in-JS solution. The simplest one is inline styles:
export default () => <p style={{ color: 'red' }}>hi there</p>
To use more sophisticated CSS-in-JS solutions, you typically have to implement style flushing for server-side rendering. We enable this by allowing you to define your own custom <Document>
component that wraps each page.
Importing CSS / Sass / Less / Stylus files
Importing .css
, .scss
, .less
or .styl
files is not yet supported. You can probably achieve it by adding some webpack configuration but it is not recommended. CSS in JS is much more powerful with SSR rendering.
Static file serving (e.g.: images)
Create a folder called static
in your project root directory. From your code you can then reference those files with /static/
URLs:
export default () => <img src="/static/my-image.png" alt="my image" />
Note: Don't name the static
directory anything else. The name is required and is the only directory that Smooth.js uses for serving static assets.
<head>
Populating Examples
We expose a built-in component for appending elements to the <head>
of the page.
import Head from 'smooth-core/head'
export default () => (
<div>
<Head>
<title>My page title</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<p>Hello world!</p>
</div>
)
Note: The contents of <head>
get cleared upon unmounting the component, so make sure each page completely defines what it needs in <head>
, without making assumptions about what other pages added
Note: <title>
and <meta>
elements need to be contained as direct children of the <Head>
element, or wrapped into maximum one level of <React.Fragment>
, otherwise the metatags won't be correctly picked up on clientside navigation.
Routing
Examples
Client-side transitions between routes can be enabled via a <Link>
component. Consider these two pages:
// pages/index$.js
import { Link } from 'smooth-core/router'
export default () => (
<div>
Click <Link to="/about">About</Link> to read more
</div>
)
// pages/about$.js
export default () => <p>Welcome to About!</p>
Note: "smooth-core/router" exposes all methods from "react-router-dom".
Dynamic Import
Examples
Smooth.js supports TC39 dynamic import proposal for JavaScript. With that, you could import JavaScript modules (inc. React Components) dynamically and work with them.
You can think dynamic imports as another way to split your code into manageable chunks. Since Smooth.js supports dynamic imports with SSR, you could do amazing things with it.
Here are a few ways to use dynamic imports.
1. Basic Usage (Also does SSR)
import loadable from 'smooth-core/loadable'
const DynamicComponent = loadable(() => import('../components/hello'))
export default () => (
<div>
<Header />
<DynamicComponent />
<p>HOME PAGE is here!</p>
</div>
)
2. With Custom Loading Component
import loadable from 'smooth-core/loadable'
const DynamicComponentWithCustomLoading = loadable(
() => import('../components/hello2'),
{ fallback: <p>...</p> },
)
export default () => (
<div>
<Header />
<DynamicComponentWithCustomLoading />
<p>HOME PAGE is here!</p>
</div>
)
Note: "smooth-core/loadable" exposes all methods from "@loadable/component".
<App>
Custom Examples
Smooth.js uses the App
component to initialize pages. You can override it and control the page initialization. Which allows you to do amazing things like:
- Persisting layout between page changes
- Keeping state when navigating pages
To override, create the ./src/_app.js
file and override the App class as shown below:
import React from 'react'
export default ({ Component, ...props }) => (
<div className="layout">
<Component {...props} />
</div>
)
<Document>
Custom Examples
- Is rendered on the server side
- Is used to change the initial server side rendered document markup
- Commonly used to implement server side rendering for css-in-js libraries like styled-components.
Pages in Smooth.js
skip the definition of the surrounding document's markup. For example, you never include <html>
, <body>
, etc. To override that default behavior, you must create a file at ./src/_document.js
, where you can extend the Document
class:
// _document is only rendered on the server side and not on the client side
// Event handlers like onClick can't be added to this file
// ./src/_document.js
import Document, { Head, Main, MainScript } from 'smooth-core/document'
export default () => (
<html>
<Head>
<style>{`body { margin: 0 } /* custom! */`}</style>
</Head>
<body className="custom_class">
<Main />
<MainScript />
</body>
</html>
)
All of <Head />
, <Main />
and <MainScript />
are required for page to be properly rendered.
Note: React-components outside of <Main />
will not be initialised by the browser. Do not add application logic here. If you need shared components in all your pages (like a menu or a toolbar), take a look at the App
component instead.
renderPage
Customizing 🚧 It should be noted that the only reason you should be customizing renderPage
is for usage with css-in-js libraries
that need to wrap the application to properly work with server-rendering. 🚧
- It takes as argument an options object for further customization
import Document from 'smooth-core/document'
Document.getInitialProps = ctx => {
const sheet = new ServerStyleSheet()
const originalRenderPage = ctx.renderPage
ctx.renderPage = () =>
originalRenderPage({
// wrap the all react tree
enhanceApp: App => App,
})
// Return additional props
return {}
}
export default Document
Custom error handling
404 or 500 errors are handled both client and server side by a default component error.js
. If you wish to override it, define a _error.js
in the src folder:
⚠️ The default error.js
component is only used in production ⚠️
import React from 'react'
export default ({ error }) => (
<p>
{error.statusCode
? `An error ${error.statusCode} occurred on server`
: 'An error occurred on client'}
</p>
)
Custom configuration
For custom advanced behavior of Smooth.js, you can create a smooth.config.js
in the root of your project directory (next to src/
and package.json
).
Note: smooth.config.js
is a regular Node.js module, not a JSON file. It gets used by the Smooth server and build phases, and not included in the browser build.
// smooth.config.js
module.exports = {
/* config options here */
}
Customizing webpack config
Examples
In order to extend our usage of webpack
, you can define a function that extends its config via smooth.config.js
.
// smooth.config.js is not transformed by Babel. So you can only use javascript features supported by your version of Node.js.
module.exports = {
webpack: (config, { dev, isServer, defaultLoaders }) => {
// Perform customizations to webpack config
// Important: return the modified config
return config
},
webpackDevMiddleware: config => {
// Perform customizations to webpack dev middleware config
// Important: return the modified config
return config
},
}
The second argument to webpack
is an object containing properties useful when customizing its configuration:
dev
-Boolean
shows if the compilation is done in development modeisServer
-Boolean
shows if the resulting configuration will be used for server side (true
), or client size compilation (false
).defaultLoaders
-Object
Holds loader objects Smooth.js uses internally, so that you can use them in custom configurationbabel
-Object
thebabel-loader
configuration for Smooth.js.
Example usage of defaultLoaders.babel
:
// Example smooth.config.js for adding a loader that depends on babel-loader
module.exports = {
webpack: (config, {}) => {
config.module.rules.push({
test: /\.mdx/,
use: [
options.defaultLoaders.babel,
{
loader: '@mdx-js/loader',
options: pluginOptions.options,
},
],
})
return config
},
}
Customizing babel config
Examples
In order to extend our usage of babel
, you can simply define a .babelrc
file at the root of your app. This file is optional.
If found, we're going to consider it the source of truth, therefore it needs to define what smooth needs as well, which is the smooth-core/babel
preset.
This is designed so that you are not surprised by modifications we could make to the babel configurations.
Here's an example .babelrc
file:
{
"presets": ["smooth-core/babel"],
"plugins": []
}
The smooth-core/babel
preset includes everything needed to transpile React applications. This includes:
- preset-env
- preset-react
- plugin-proposal-class-properties
- @loadable/babel-plugin
These presets / plugins should not be added to your custom .babelrc
. Instead, you can configure them on the smooth-core/babel
preset:
{
"presets": [
[
"smooth-core/babel",
{
"preset-env": {},
"transform-runtime": {}
}
]
],
"plugins": []
}
The modules
option on "preset-env"
should be kept to false
otherwise webpack code splitting is disabled.
Production deployment
To deploy, instead of running smooth
, you want to build for production usage ahead of time. Therefore, building and starting are separate commands:
smooth build
smooth start
For example, to deploy with now
a package.json
like follows is recommended:
{
"name": "my-app",
"dependencies": {
"smooth-core": "latest"
},
"scripts": {
"dev": "smooth dev",
"build": "smooth build",
"start": "smooth start"
}
}
Then run now
and enjoy!
Browser support
Smooth.js supports IE11 and all modern browsers out of the box using @babel/preset-env
.
Contributing
Please see our contributing.md
Authors
- Greg Bergé (@neoziro) – Smooth Code