Skip to content

Commit

Permalink
feat: support snapshoting
Browse files Browse the repository at this point in the history
Fix #20
  • Loading branch information
gregberge committed Nov 27, 2017
1 parent b0ddcf0 commit 55ecf90
Show file tree
Hide file tree
Showing 14 changed files with 362 additions and 197 deletions.
2 changes: 1 addition & 1 deletion .mversionrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"scripts": {
"precommit": "yarn build && git add -A",
"postcommit": "git push && git push --tags",
"postupdate": "npm publish lib && conventional-github-releaser --preset angular"
"postupdate": "conventional-github-releaser --preset angular && echo "Please publish using: npm publish lib""
}
}
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/*
!/lib/*.js
*.test.js
setupTests.js
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ I tried several solutions, [react-async-components](https://github.com/ctrlplusb

```js
// Routes.js
import loadable from 'loadable-components'

export const Home = loadable(() => import('./Home'))
export const About = loadable(() => import('./About'))
export const Contact = loadable(() => import('./Contact'))
Expand Down Expand Up @@ -223,6 +225,23 @@ Dynamic `import` syntax is natively supported by Webpack but not by node. That's

To have a different configuration for client and server, you can use [Babel env option](https://babeljs.io/docs/usage/babelrc/#env-option).

### Snapshoting

An alternative to server-side rendering is [snapshoting](https://medium.com/superhighfives/an-almost-static-stack-6df0a2791319). Basically, you crawl your React website locally and you generate HTML pages.

You need to instruct your snapshot solution to save state of `loadable-components` to the `window` in the end.

`getState()` will return `{__LOADABLE_COMPONENT_IDS__: [...]}`, and this should be converted to `<script>window.__LOADABLE_COMPONENT_IDS__ = [...]</script>` in the resulting html.

For example, to do this with [`react-snap`](https://github.com/stereobooster/react-snap) you can use following code:

```js
import { getState } from 'loadable-components/snap'

// Set up for react-snap.
window.snapSaveState = () => getState()
```

## API Reference

### loadable
Expand Down
40 changes: 18 additions & 22 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,7 @@
"license": "MIT",
"jest": {
"rootDir": "src",
"coverageDirectory": "./coverage/",
"setupFiles": [
"raf/polyfill",
"<rootDir>/../testSetup.js"
]
"setupFiles": ["<rootDir>/setupTests.js"]
},
"scripts": {
"build": "rm -rf lib/ && NODE_ENV=production babel src -d lib && cp package.json lib/ && cp README.md lib/",
Expand All @@ -36,30 +32,30 @@
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-eslint": "^8.0.1",
"babel-plugin-dynamic-import-node": "^1.1.0",
"babel-eslint": "^8.0.2",
"babel-plugin-dynamic-import-node": "^1.2.0",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-env": "^1.6.0",
"babel-preset-env": "^1.6.1",
"babel-preset-react": "^6.24.1",
"codecov": "^2.3.0",
"conventional-github-releaser": "^1.1.12",
"conventional-recommended-bump": "^1.0.2",
"enzyme": "^3.1.0",
"enzyme-adapter-react-16": "^1.0.1",
"eslint": "^4.8.0",
"eslint-config-airbnb": "^16.0.0",
"eslint-config-prettier": "^2.6.0",
"eslint-plugin-import": "^2.7.0",
"codecov": "^3.0.0",
"conventional-github-releaser": "^2.0.0",
"conventional-recommended-bump": "^1.0.3",
"enzyme": "^3.2.0",
"enzyme-adapter-react-16": "^1.1.0",
"eslint": "^4.11.0",
"eslint-config-airbnb": "^16.1.0",
"eslint-config-prettier": "^2.8.0",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-jsx-a11y": "^6.0.2",
"eslint-plugin-react": "^7.4.0",
"eslint-plugin-react": "^7.5.1",
"jest": "^21.2.1",
"mversion": "^1.10.1",
"prettier": "^1.7.4",
"prettier": "^1.8.2",
"raf": "^3.4.0",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"react": "^16.1.1",
"react-dom": "^16.1.1",
"react-router": "^4.2.0",
"react-test-renderer": "^16.0.0"
"react-test-renderer": "^16.1.1"
}
}
3 changes: 2 additions & 1 deletion src/__fixtures__/Books.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import React from 'react'
import { Route } from 'react-router'
import { Book } from './Routes'

const Books = ({ match }) =>
const Books = ({ match }) => (
<div>
<h1>Books</h1>
<Route path={`${match.url}/:bookId`} component={Book} />
</div>
)

export default Books
1 change: 1 addition & 0 deletions src/componentTracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export const track = component => {
}

export const get = id => components[id]
export const getAll = () => ({ ...components })
5 changes: 5 additions & 0 deletions src/componentTracker.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,10 @@ describe('componentTracker', () => {

expect(componentTracker.get(id1)).toBe(Component1)
expect(componentTracker.get(id2)).toBe(Component2)

expect(componentTracker.getAll()).toEqual({
[id1]: Component1,
[id2]: Component2,
})
})
})
9 changes: 8 additions & 1 deletion src/loadComponents.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ function loadComponents() {
}

const ids = window[COMPONENT_IDS] || []
return Promise.all(ids.map(id => componentTracker.get(id)[LOADABLE]().load()))
return Promise.all(
ids.map(id =>
componentTracker
.get(id)
[LOADABLE]()
.load(),
),
)
}

export default loadComponents
4 changes: 3 additions & 1 deletion src/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,9 @@ export function getLoadableState(
errors.length === 1
? errors[0]
: new Error(
`${errors.length} errors were thrown when importing your modules.`,
`${
errors.length
} errors were thrown when importing your modules.`,
)
error.queryErrors = errors
throw error
Expand Down
3 changes: 2 additions & 1 deletion src/server/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ describe('server side rendering', () => {
let app

beforeEach(() => {
const App = () =>
const App = () => (
<div>
<Route path="/books" component={Books} />
</div>
)

const context = {}

Expand Down
1 change: 1 addition & 0 deletions testSetup.js → src/setupTests.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint-disable import/no-extraneous-dependencies */
import 'raf/polyfill'
import Enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'

Expand Down
14 changes: 14 additions & 0 deletions src/snap/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* eslint-env browser */
/* eslint-disable import/prefer-default-export */
import { getAll as getAllComponents } from '../componentTracker'
import { COMPONENT_IDS } from '../constants'

export function getState() {
const componentByIds = getAllComponents()
const componentIds = Object.keys(componentByIds).reduce((ids, id) => {
const component = componentByIds[id]
if (component.loadingPromise) return [...ids, component.componentId]
return ids
}, [])
return { [COMPONENT_IDS]: componentIds }
}
14 changes: 14 additions & 0 deletions src/snap/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { getState } from './'
import loadable from '../loadable'

describe('snap', () => {
describe('#getState', () => {
it('should return only loaded components', () => {
const getComponent = jest.fn(() => import('../__fixtures__/Dummy'))
const Loadable = loadable(getComponent)
expect(getState()).toEqual({ __LOADABLE_COMPONENT_IDS__: [] })
Loadable.load()
expect(getState()).toEqual({ __LOADABLE_COMPONENT_IDS__: [0] })
})
})
})
Loading

0 comments on commit 55ecf90

Please sign in to comment.