Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
019a6ba
commit fd2ec18
Showing
21 changed files
with
396 additions
and
245 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,105 +1,73 @@ | ||
# sambell | ||
sambell is a minimal framework for server-rendered React applications, ideal for universal react-router projects. | ||
create performant server-rendered React applications with no build configuration; ideal for universal react-router projects. | ||
|
||
- similar to [create-react-app](https://github.com/facebookincubator/create-react-app), but server side rendering. | ||
- similar to [next.js](https://github.com/zeit/next.js), but with [react-router](https://github.com/ReactTraining/react-router). | ||
- `sambell` came first! commit history proof :stuck_out_tongue_closed_eyes: | ||
|
||
Both [create-react-app](https://github.com/facebookincubator/create-react-app) and [next.js](https://github.com/zeit/next.js) are great projects, try them! I like aspects of both. But you don't get a **universal react-router** application out of the box. | ||
|
||
###### Why not Next? | ||
|
||
I think [next.js](https://github.com/zeit/next.js) is great, with one big flaw: you don't get a layout file. The entire application re-renders on route changes. This makes things like animated transitions impossible. But even something simple like a **material design ink button** link won't work, because the re-render kills the button animation. I think [react-router](https://github.com/ReactTraining/react-router) is fundamentally more powerful, and opt for that. | ||
|
||
## What will my app look like?!? | ||
|
||
Check out [the template](template)! | ||
|
||
**3 files**. **<100 loc** total for a universal single page app. (5 files to show async loading) | ||
|
||
## Install | ||
|
||
``` | ||
yarn global add sambell | ||
sambell new my-app | ||
cd my-app | ||
sambell new app | ||
cd app | ||
yarn start | ||
``` | ||
|
||
## Features | ||
|
||
**Performant** | ||
|
||
- React **16** | ||
- Server side rendering. Universal. | ||
- Critical styles with [styled-jsx](https://github.com/zeit/styled-jsx) | ||
- Async loading of routes with `react-loadable` (forked version `@humblespark/react-loadable`) | ||
- Webpack build optimized for production. | ||
|
||
**Webpack** | ||
|
||
- Webpack 2 (code splitting, tree shaking, etc). | ||
- Webpack runs for **both** client and server code. | ||
- Minimal loaders (only a JS loader). But it is configurable if you want to add more. | ||
- **absolute path** requires from your project root. `import App from 'App'` | ||
- Sourcemaps for client & server | ||
|
||
**Babel** | ||
|
||
- Presets: es2015, stage-1, react | ||
- Plugins: [styled-jsx](https://github.com/zeit/styled-jsx) | ||
|
||
**Router** | ||
**Dev experience** | ||
|
||
- Everything you (or at least, I) want without setting anything up! | ||
- Client side SPA with [react-router](https://github.com/ReactTraining/react-router) **version 4**. | ||
- Easy to add non-v4 react-router if you prefer that. But v4 is really nice if starting a new project. | ||
|
||
**CSS** | ||
|
||
- [styled-jsx](https://github.com/zeit/styled-jsx) is a great feature of Next.js that I bring in here. I find it to be more pleasant than `css-modules`, and eaiser to work with for a universal application (critical styles, etc). | ||
|
||
**sambell/env** | ||
**Performant** | ||
|
||
- `import { WEBPACK_MANIFEST, CLIENT_ENTRY, CLIENT_CHUNKS, CLIENT_OUTPUT_DIR, WEBPACK_PUBLIC_PATH, waitForChunks } from 'sambell/env';` to make creating your html template easy in `server.js`. | ||
- React **16** | ||
- Server side rendering. Universal. | ||
- Critical styles with [styled-jsx](https://github.com/zeit/styled-jsx). | ||
- Async loading of routes with `react-loadable` (forked version `@humblespark/react-loadable`). | ||
- Async (`<script async />`) loading of all webpack scripts. | ||
- Webpack build optimized for production. | ||
|
||
**Async components** | ||
|
||
- Full client & server side support for async loading components, with `react-loadable` | ||
- Forked version (`@humblespark/react-loadable`) to work with server side webpack build & a fix for checksum mismatch. | ||
- `import ready from 'sambell/ready';` `ready(() => renderApp())` for async webpack loading of chunks. | ||
|
||
``` | ||
const MyAsyncComponent = Loadable({ | ||
loader: () => import(/* webpackChunkName: "components/MyAsyncComponent" */'components/MyAsyncComponent'), | ||
webpackRequireWeakId: () => require.resolveWeak('components/MyAsyncComponent'), | ||
chunkName: 'components/MyAsyncComponent', | ||
}); | ||
const Moon = Loadable(() => import(/* webpackChunkName: "components/Moon" */'components/Moon')); | ||
``` | ||
|
||
**Other** | ||
**Webpack / Babel** | ||
|
||
- Webpack 2 (code splitting, tree shaking, etc). | ||
- Webpack runs for **both** client and server code. | ||
- Minimal loaders (only a JS loader). But it is configurable if you want to add more. | ||
- **absolute path** requires from your project root. `import App from 'App'`. | ||
- Sourcemaps for client & server. | ||
- Babel Presets: es2015, stage-1, react | ||
- Babel Plugins: [styled-jsx](https://github.com/zeit/styled-jsx) | ||
- Polyfills: `isomorphic-fetch`, `babel-polyfill` | ||
|
||
**Configurable** | ||
|
||
- You get a `server.js` file. You have full control over client and server. | ||
- Add a `gerty.js` file to your project root. | ||
- Currently, webpack only function that it called. called with: | ||
- `config` (base config from sambell). for you to overrides | ||
- `settings` (`{ dev, node }`) to conditionally alter config | ||
- `webpack` webpack instance for you to use if needed. | ||
|
||
\**gerty.js* | ||
\**gerty.js* (basic configuration to control where stuff goes) | ||
|
||
```javascript | ||
// @NOTE optional! | ||
// @NOTE this file is not compiled, use only whats available in your node version! | ||
|
||
module.exports = { | ||
// full webpack overrides | ||
webpack: (config, { dev, node }, webpack) => { | ||
config.devtool = 'eval'; | ||
return config; | ||
}, | ||
clientEntry: 'client', | ||
serverEntry: 'server', | ||
clientOutputDirectory: '.sambell/client', | ||
serverOutputDirectory: '.sambell/server', | ||
publicPath: '/static/webpack/', | ||
webpack: config => config, | ||
}; | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import React from 'react'; | ||
|
||
// @NOTE render our script tags. | ||
// take array of async chunkNames if using @humblespark/react-loadable (recommended) | ||
|
||
export const renderScripts = (asyncChunks = []) => { | ||
if (typeof window !== 'undefined') { | ||
throw new Error('ONLY AVAILABLE ON SERVER.'); | ||
} | ||
|
||
const clientChunks = JSON.parse('{{SAMBELL_CLIENT_CHUNKS}}'); | ||
const filteredAsyncChunks = asyncChunks.reduce( | ||
(acc, asyncChunk) => | ||
acc.includes(asyncChunk) || !clientChunks[asyncChunk] | ||
? acc | ||
: [...acc, clientChunks[asyncChunk]], | ||
[], | ||
); | ||
|
||
const totalChunks = [ | ||
'{{SAMBELL_CLIENT_VENDOR_ENTRY}}', | ||
'{{SAMBELL_CLIENT_ENTRY}}', | ||
...filteredAsyncChunks, | ||
]; | ||
|
||
const MANIFEST = '{{SAMBELL_CLIENT_WEBPACK_MANIFEST}}'; | ||
const WAIT_FOR = `window.__SAMBELL_WAIT_FOR_CHUNKS__=${totalChunks.length};`; | ||
|
||
return [ | ||
<script | ||
key="__sambell_init__" | ||
type="text/javascript" | ||
dangerouslySetInnerHTML={{ __html: `${MANIFEST}${WAIT_FOR}` }} | ||
/>, | ||
totalChunks.map(chunk => ( | ||
<script | ||
key={`__sambell_chunk_${chunk}__`} | ||
type="text/javascript" | ||
src={`{{SAMBELL_WEBPACK_PUBLIC_PATH}}${chunk}`} | ||
async | ||
/> | ||
)), | ||
]; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.