Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add next export for static builds #1576

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ npm-debug.log
# coverage
.nyc_output
coverage

# osx
.DS_Store
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should remove this, add it in your ~/.gitconfig

Copy link
Contributor Author

@matthewmueller matthewmueller Mar 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i sort of feel like it's the project's job to prevent stuff like this from getting committed, but i don't care much either way 😄. let's see what others think

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed with Matthew

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree it should always be in your global gitignore

1 change: 1 addition & 0 deletions bin/next
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const commands = new Set([
'init',
'build',
'start',
'export',
defaultCommand
])

Expand Down
58 changes: 58 additions & 0 deletions bin/next-export
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env node
import { resolve, join } from 'path'
import { existsSync } from 'fs'
import parseArgs from 'minimist'
import Build from '../server/build'
import Export from '../server/export'
import { printAndExit } from '../lib/utils'

process.env.NODE_ENV = process.env.NODE_ENV || 'production'

const argv = parseArgs(process.argv.slice(2), {
alias: {
h: 'help',
o: 'out'
},
boolean: ['h']
})

if (argv.help) {
console.log(`
Description
Compiles and exports the application to a static website

Usage
$ next export <dir>
$ next export --out <out> <dir>

<dir> represents where the compiled folder should go.
If no directory is provided, <dir> will be the current directory.

<out> represents where the static directory will go.
If no directory is provided, <out> will be <dir>/site.
`)
process.exit(0)
}

const dir = resolve(argv._[0] || '.')
const out = resolve(dir, argv['out'] || 'site')

// Check if pages dir exists and warn if not
if (!existsSync(dir)) {
printAndExit(`> No such directory exists as the project root: ${dir}`)
}

if (!existsSync(join(dir, 'pages'))) {
if (existsSync(join(dir, '..', 'pages'))) {
printAndExit('> No `pages` directory found. Did you mean to run `next` in the parent (`../`) directory?')
}

printAndExit('> Couldn\'t find a `pages` directory. Please create one under the project root')
}

Build(dir)
.then(() => Export({ dir, out }))
.catch((err) => {
console.error(err)
process.exit(1)
})
9 changes: 8 additions & 1 deletion client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ if (!window.Promise) {

const {
__NEXT_DATA__: {
exported,
component,
errorComponent,
props,
Expand All @@ -35,7 +36,10 @@ let lastAppProps
export const router = createRouter(pathname, query, getURL(), {
Component,
ErrorComponent,
err
err,
formatURL: exported && function (buildId, route) {
return route ? route.replace(/\/$/, '') + '/index.json' : '/index.json'
}
})

const headManager = new HeadManager()
Expand Down Expand Up @@ -75,6 +79,9 @@ async function doRender ({ Component, props, hash, err, emitter }) {
// fetch props if ErrorComponent was replaced with a page component by HMR
const { pathname, query } = router
props = await loadGetInitialProps(Component, { err, pathname, query })
} else if (exported && !lastAppProps) {
const { pathname, query } = router
props = await loadGetInitialProps(Component, { err, pathname, query })
Copy link

@bringking bringking Apr 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be loadGetStaticInitialProps, or since this is on the client, the branch condition isn't necessary

}

if (emitter) {
Expand Down
28 changes: 28 additions & 0 deletions examples/static-export/pages/about.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Component } from 'react'
import Router from 'next/router'

export default class About extends Component {
constructor (props) {
super(props)
this.state = {}
}

static async getInitialProps (ctx) {
console.log('loading about!', ctx)
return {}
}

static async getStaticInitialProps (ctx) {
console.log('build props', ctx)
return {}
}

render () {
return (
<div>
<h2>About Me</h2>
<a onClick={() => Router.push('/')}>Go Back to Index</a>
</div>
)
}
}
38 changes: 38 additions & 0 deletions examples/static-export/pages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Component } from 'react'
import Button from '../ui/button'
import Link from 'next/link'

export default class Index extends Component {
constructor (props) {
super(props)
this.state = {
answer: ''
}
}

static async getInitialProps ({ pathname }) {
return {
title: 'Hello Static Stack!'
}
}

render () {
return (
<div>
<h2>{this.props.title}</h2>
<Link href='/about'><a>Learn more about me</a></Link>
<br />
<br />
<Button onClick={() => this.setState({ answer: 'yes.' })}>
Did Rehydration Work?
</Button>
<br />
<br />
<div>{this.state.answer}</div>
<br />
<Link href='/movies'><a>Check out the movies</a></Link>
<a onClick={() => (document.location.pathname = '/about')}>Go to my about page</a>
</div>
)
}
}
19 changes: 19 additions & 0 deletions examples/static-export/pages/movies/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

import { Component } from 'react'
import Link from 'next/link'

export default class Movies extends Component {
constructor (props) {
super(props)
this.state = {}
}

render (props) {
return (
<div>
<h2>Some of my favorite movies</h2>
<Link href='/'><a>Go Back to Index</a></Link>
</div>
)
}
}
3 changes: 3 additions & 0 deletions examples/static-export/ui/button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default ({ onClick, children }) => (
<button onClick={onClick}>{children}</button>
)
10 changes: 7 additions & 3 deletions lib/router/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { _notifyBuildIdMismatch } from './'
const webpackModule = module

export default class Router {
constructor (pathname, query, as, { Component, ErrorComponent, err } = {}) {
constructor (pathname, query, as, { Component, ErrorComponent, err, formatURL } = {}) {
// represents the current component key
this.route = toRoute(pathname)

Expand All @@ -22,6 +22,7 @@ export default class Router {

// Handling Router Events
this.events = mitt()
this.formatURL = formatURL || this.formatURL

this.prefetchQueue = new PQueue({ concurrency: 2 })
this.ErrorComponent = ErrorComponent
Expand Down Expand Up @@ -333,10 +334,13 @@ export default class Router {
return promise
}

formatURL (buildId, route) {
return `/_next/${encodeURIComponent(buildId)}/pages${route}`
}

doFetchRoute (route) {
const { buildId } = window.__NEXT_DATA__
const url = `/_next/${encodeURIComponent(buildId)}/pages${route}`

const url = this.formatURL(buildId, route)
return fetch(url, {
method: 'GET',
credentials: 'same-origin',
Expand Down
12 changes: 12 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ export async function loadGetInitialProps (Component, ctx) {
return props
}

export async function loadGetStaticInitialProps (Component, ctx) {
if (!Component.getStaticInitialProps) return {}

const props = await Component.getStaticInitialProps(ctx)
if (!props) {
const compName = Component.displayName || Component.name
const message = `"${compName}.getStaticInitialProps()" should resolve to an object. But found "${props}" instead.`
throw new Error(message)
}
return props
}

export function getLocationOrigin () {
const { protocol, hostname, port } = window.location
return `${protocol}//${hostname}${port ? ':' + port : ''}`
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"cross-spawn": "5.1.0",
"del": "2.2.2",
"friendly-errors-webpack-plugin": "1.5.0",
"fs-promise": "2.0.2",
"glob-promise": "3.1.0",
"htmlescape": "1.1.1",
"http-status": "1.0.1",
Expand Down
4 changes: 2 additions & 2 deletions server/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,13 @@ export class NextScript extends Component {

getChunkScript (filename, additionalProps = {}) {
const { __NEXT_DATA__ } = this.context._documentProps
let { buildStats } = __NEXT_DATA__
let { buildStats, exported } = __NEXT_DATA__
const hash = buildStats ? buildStats[filename].hash : '-'

return (
<script
type='text/javascript'
src={`/_next/${hash}/${filename}`}
src={exported ? `/${filename}` : `/_next/${hash}/${filename}`}
{...additionalProps}
/>
)
Expand Down
Loading