Permalink
Browse files

(feat) Incremental builds are now public and more stable/robust

  • Loading branch information...
tannerlinsley committed Jan 8, 2019
1 parent 1cce60b commit 21bb89a15092e93798e573fbe2f75c4e433e52fe
Showing with 280 additions and 172 deletions.
  1. +2 βˆ’1 .gitignore
  2. +11 βˆ’0 CHANGELOG.md
  3. +81 βˆ’0 docs/guides/incremental-builds.md
  4. +2 βˆ’2 package.json
  5. +3 βˆ’0 packages/react-static/bin/react-static
  6. +0 βˆ’1 packages/react-static/package.json
  7. +6 βˆ’3 packages/react-static/src/bootstrapTemplates.js
  8. +3 βˆ’8 packages/react-static/src/browser/components/Root.js
  9. +16 βˆ’18 packages/react-static/src/browser/index.js
  10. +11 βˆ’7 packages/react-static/src/commands/bundle.js
  11. +13 βˆ’21 packages/react-static/src/commands/export.js
  12. +7 βˆ’5 packages/react-static/src/commands/start.js
  13. +3 βˆ’0 packages/react-static/src/static/buildHTML.js
  14. +15 βˆ’0 packages/react-static/src/static/buildInfo.js
  15. +19 βˆ’0 packages/react-static/src/static/clientStats.js
  16. +16 βˆ’2 packages/react-static/src/static/exportRoute.js
  17. +6 βˆ’1 packages/react-static/src/static/exportRoutes.js
  18. +8 βˆ’1 packages/react-static/src/static/exporter.js
  19. +2 βˆ’1 packages/react-static/src/static/exporter.threaded.js
  20. +14 βˆ’21 packages/react-static/src/static/extractTemplates.js
  21. +2 βˆ’4 packages/react-static/src/static/generateBrowserPlugins.js
  22. +9 βˆ’23 packages/react-static/src/static/generateTemplates.js
  23. +0 βˆ’4 packages/react-static/src/static/getRoutes/getRoutesFromPages.js
  24. +1 βˆ’1 packages/react-static/src/static/getRoutes/index.js
  25. +10 βˆ’5 packages/react-static/src/static/getRoutes/normalizeAllRoutes.js
  26. +1 βˆ’0 packages/react-static/src/static/getRoutes/normalizeRoute.js
  27. +3 βˆ’1 packages/react-static/src/static/index.js
  28. +1 βˆ’4 packages/react-static/src/static/{preparePlugins.js β†’ prepareBrowserPlugins.js}
  29. +4 βˆ’7 packages/react-static/src/static/prepareRoutes.js
  30. +10 βˆ’24 packages/react-static/src/static/webpack/index.js
  31. +1 βˆ’1 packages/react-static/src/static/webpack/rules/jsLoader.js
  32. +0 βˆ’6 packages/react-static/src/utils/binHelper.js
@@ -34,4 +34,5 @@ templates/tmp
templates/yarn.lock
tmp
yarn-debug.log*
yarn-error.log*
yarn-error.log*
packages/react-static/templates/basic/yarn.lock
@@ -1,3 +1,14 @@
# 6.2.0

#### New Features

- Added support for incremental builds

#### Fixes & Optimizations

- Fixed a security issue where `process.env` variables could be exported and distributed by accident.
- Remove update-notifier. It was never that reliable and was presenting problems with multi-threading.

# 6.1.0

#### New Features
@@ -0,0 +1,81 @@
# Incremental Builds Experimental API

Incremental builds for extremely large sites cant be very valuable. They allow you to update only a portion of your site that has changed, remove content, and even switch route templates around (as long as the templates were previously bundled in the app)

Incremental builds allow you to:

- Remove Routes
- Add New Routes (only existing templates can be used)
- Update Existing Routes (only existing templates can be used)

### Requirements

If you are using either **source control** or ** a hosting service that idempotently or deterministically updates your site** (eg. Netlify, or any other non-imperative file-upload service):

- You must commit your projects `dist` directory. Not only do you need to store your site as a cache, but these directories also contain build artifacts that are necessary for incremental builds to function correctly (eg. app bundles, environments, templates, plugins, etc)

To perform an incremental build:

- Ensure your `dist` directory and any other build artifacts are up to date with the latest version of your site
- Detect the incremental build in your config and only return the routes you woud like to add, update, or remove.
- Peform an export with the `incremental` flag:

```sh
react-static export --incremental
```

### Example

Below is a basic example that:

- Updates the `blog` route to not have any `posts` data (and updates the exported html)
- Removes the `blog/post/3` route

```javascript
import axios from 'axios'
export default {
getSiteData: () => ({
title: 'React Static',
}),
getRoutes: async ({ incremental }) => {
// Detect incremental mode
if (incremental) {
return [
{
path: '/blog',
getData: () => ({
posts: [], // Update the posts data
}),
children: [
{
path: '/post/3',
remove: true, // Flag for removal
},
],
},
]
}
const { data: posts } = await axios.get(
'https://jsonplaceholder.typicode.com/posts'
)
return [
{
path: '/blog',
getData: () => ({
posts,
}),
children: posts.map(post => ({
path: `/post/${post.id}`,
component: 'src/containers/Post',
getData: () => ({
post,
}),
})),
},
]
},
}
```
@@ -4,8 +4,8 @@
"releaseNext": "lerna publish prerelease --preid beta --npm-tag next",
"release": "lerna publish",
"releaseForce": "lerna publish --force-publish react-static",
"build": "lerna run 'build' --ignore react-static-example-*",
"watch": "lerna run 'watch' --ignore react-static-example-* --parallel",
"build": "lerna run 'build'",
"watch": "lerna run 'watch' --parallel",
"start": "yarn watch",
"startDocs": "cd www && yarn && yarn start",
"buildDocs": "cd www && yarn && yarn build",
@@ -125,13 +125,16 @@ Description: Exports a pre-bundled React Static project to a static site.
Options:
-c, --config <path> The path to a custom static.config.js
-s, --staging Turn on staging mode
-i, --incremental Turn on incremental build mode
-d, --debug Turn on debug mode
Examples:
react-static export
react-static export --config=path/to/my/static.config.js
react-static export -c path/to/my/static.config.js
react-static export --incremental
react-static export -i
react-static export --debug
react-static export -d
`)
return
@@ -94,7 +94,6 @@
"thread-loader": "^1.2.0",
"uglifyjs-webpack-plugin": "^2.0.1",
"upath": "^1.1.0",
"update-notifier": "^2.5.0",
"url-loader": "^1.1.2",
"webpack": "^4.25.1",
"webpack-bundle-analyzer": "^3.0.3",
@@ -1,12 +1,15 @@
/* eslint-disable import/no-dynamic-require */

const templates = require(process.env.REACT_STATIC_TEMPLATES_PATH).default
const { default: templates, notFoundTemplate } = require(process.env
.REACT_STATIC_TEMPLATES_PATH)
const { registerTemplates } = require('./browser')

registerTemplates(templates)
registerTemplates(templates, notFoundTemplate)

if (typeof document !== 'undefined' && module && module.hot) {
module.hot.accept(process.env.REACT_STATIC_TEMPLATES_PATH, () => {
registerTemplates(require(process.env.REACT_STATIC_TEMPLATES_PATH).default)
const { default: templates, notFoundTemplate } = require(process.env
.REACT_STATIC_TEMPLATES_PATH)
registerTemplates(templates, notFoundTemplate)
})
}
@@ -38,12 +38,7 @@ const Root = withStaticInfo(
super()
const { staticInfo } = props
if (process.env.REACT_STATIC_ENV === 'production' && staticInfo) {
const {
path,
sharedData,
sharedHashesByProp,
templateIndex,
} = staticInfo
const { path, sharedData, sharedHashesByProp, template } = staticInfo

// Hydrate routeInfoByPath with the embedded routeInfo
routeInfoByPath[path] = staticInfo
@@ -53,9 +48,9 @@ const Root = withStaticInfo(
sharedDataByHash[sharedHashesByProp[propKey]] = sharedData[propKey]
})

// In SRR and production, synchronously register the templateIndex for the
// In SRR and production, synchronously register the template for the
// initial path
registerTemplateForPath(path, templateIndex)
registerTemplateForPath(path, template)
}
}
render() {
@@ -26,20 +26,24 @@ export const registerPlugins = newPlugins => {
}

// Templates
export const templates = []
export const templateUpdated = { cb: () => {} }
const templateIndexByPath = {
'404': 0,
}
export const templatesByPath = {
'404': templates[0],
}
export const templates = {}
export const templatesByPath = {}
export const templateErrorByPath = {}
export const registerTemplates = tmps => {
templates.splice(0, Infinity, ...tmps)
templatesByPath['404'] = templates[0]
export const templateUpdated = { cb: () => {} }
export const registerTemplates = (tmps, notFoundKey) => {
Object.keys(templates).forEach(key => {
delete templates[key]
})
Object.keys(tmps).forEach(key => {
templates[key] = tmps[key]
})
templatesByPath['404'] = templates[notFoundKey]
templateUpdated.cb()
}
export const registerTemplateForPath = (path, template) => {
path = getRoutePath(path)
templatesByPath[path] = templates[template]
}

init()

@@ -96,12 +100,6 @@ function startPreloader() {
}
}

export function registerTemplateForPath(path, index) {
path = getRoutePath(path)
templateIndexByPath[path] = index
templatesByPath[path] = templates[index]
}

export function reloadRouteData() {
// Delete all cached data
;[
@@ -277,7 +275,7 @@ export async function prefetchTemplate(path, { priority } = {}) {
const routeInfo = await getRouteInfo(path, { priority })

if (routeInfo) {
registerTemplateForPath(path, routeInfo.templateIndex)
registerTemplateForPath(path, routeInfo.template)
}

// Preload the template if available
@@ -3,13 +3,14 @@ import chalk from 'chalk'
//
import {
prepareRoutes,
preparePlugins,
prepareBrowserPlugins,
buildProductionBundles,
getConfig,
extractTemplates,
generateTemplates,
outputBuildInfo,
} from '../static'

import extractTemplates from '../static/extractTemplates'

import { copyPublicFolder, time, timeEnd } from '../utils'

export default (async function bundle({
@@ -43,7 +44,7 @@ export default (async function bundle({

if (!config.siteRoot) {
console.log(
"=> Info: No 'siteRoot' is defined in 'static.config.js'. This is suggested for absolute urls and a sitemap.xml to be automatically generated."
"=> Info: No 'siteRoot' is defined in 'static.config.js'. This is suggested for absolute urls and also required to automatically generate a sitemap.xml."
)
console.log('')
}
@@ -62,9 +63,10 @@ export default (async function bundle({
timeEnd(chalk.green('=> [\u2713] Assets cleaned'))
}

config = await preparePlugins({ config })
config = await prepareRoutes({ config, opts: { dev: false } })
extractTemplates(config)
config = await prepareBrowserPlugins(config)
config = await prepareRoutes(config)
await extractTemplates(config)
await generateTemplates(config)

console.log('=> Copying public directory...')
time(chalk.green('=> [\u2713] Public directory copied'))
@@ -77,6 +79,8 @@ export default (async function bundle({
await buildProductionBundles({ config })
timeEnd(chalk.green('=> [\u2713] App Bundled'))

await outputBuildInfo(config)

if (config.bundleAnalyzer) {
await new Promise(() => {})
}
@@ -1,6 +1,11 @@
import fs from 'fs-extra'
//
import { exportRoutes, buildXML, prepareRoutes, getConfig } from '../static'
import {
exportRoutes,
buildXML,
prepareRoutes,
getConfig,
importClientStats,
extractTemplates,
} from '../static'

export default async ({
config: originalConfig,
@@ -35,41 +40,28 @@ export default async ({
if (!isBuild) {
config = await getConfig(originalConfig)
config.originalConfig = originalConfig
// Restore the process environment variables that were present during the build
const bundledEnv = await fs.readJson(
`${config.paths.TEMP}/bundle-environment.json`
)
Object.keys(bundledEnv).forEach(key => {
if (typeof process.env[key] === 'undefined') {
process.env[key] = bundledEnv[key]
}
})
config = await prepareRoutes({ config, opts: { dev: false, incremental } })
config = await prepareRoutes(config, { incremental })
await extractTemplates(config, { incremental })
} else {
config = originalConfig
}

if (!config.routes) {
await prepareRoutes(config, { dev: false })
await prepareRoutes(config)
}

if (debug) {
console.log('DEBUG - Resolved static.config.js:')
console.log(config)
}

const clientStats = await fs.readJson(
`${config.paths.TEMP}/client-stats.json`
)

if (!clientStats) {
throw new Error('No Client Stats Found')
}
const clientStats = await importClientStats(config)

try {
await exportRoutes({
config,
clientStats,
incremental,
})
} catch (e) {
const PrettyError = require('pretty-error')
@@ -2,12 +2,13 @@ import fs from 'fs-extra'
//
import {
prepareRoutes,
preparePlugins,
prepareBrowserPlugins,
startDevServer,
reloadRoutes,
getConfig,
extractTemplates,
generateTemplates,
} from '../static'
import extractTemplates from '../static/extractTemplates'
import { createIndexFilePlaceholder } from '../utils'
//

@@ -49,10 +50,11 @@ export default (async function start({ config: configPath, debug } = {}) {
})
}

config = await preparePlugins({ config })
config = await prepareBrowserPlugins(config)

await prepareRoutes({ config, opts: { dev: true } }, async config => {
await extractTemplates(config)
await prepareRoutes(config, { dev: true }, async config => {
await extractTemplates(config, { dev: true })
await generateTemplates(config)
reloadRoutes()

// Build the JS bundle
Oops, something went wrong.

0 comments on commit 21bb89a

Please sign in to comment.