Skip to content

Commit 26e6193

Browse files
committed
hot-reload: initial
1 parent facd19b commit 26e6193

16 files changed

+285
-30
lines changed

bin/next

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ const bin = resolve(__dirname, 'next-' + cmd)
2626
const proc = spawn(bin, args, { stdio: 'inherit', customFds: [0, 1, 2] })
2727
proc.on('close', (code) => process.exit(code))
2828
proc.on('error', (err) => {
29-
console.log(err)
29+
console.error(err)
3030
process.exit(1)
3131
})

bin/next-dev

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { resolve } from 'path'
44
import parseArgs from 'minimist'
55
import Server from '../server'
66
import build from '../server/build'
7+
import HotReloader from '../server/hot-reloader'
8+
import webpack from '../server/build/webpack'
79

810
const argv = parseArgs(process.argv.slice(2), {
911
alias: {
@@ -20,7 +22,9 @@ const dir = resolve(argv._[0] || '.')
2022

2123
build(dir)
2224
.then(async () => {
23-
const srv = new Server({ dir, dev: true })
25+
const compiler = await webpack(dir, { hotReload: true })
26+
const hotReloader = new HotReloader(compiler)
27+
const srv = new Server({ dir, dev: true, hotReloader })
2428
await srv.start(argv.port)
2529
console.log('> Ready on http://localhost:%d', argv.port);
2630
})

client/next-dev.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1-
import './next'
1+
import 'react-hot-loader/patch'
2+
import 'webpack-dev-server/client?http://localhost:3030'
3+
import * as next from './next'
4+
5+
module.exports = next
6+
7+
window.next = next

client/next.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ const {
1313
const App = app ? evalScript(app).default : DefaultApp
1414
const Component = evalScript(component).default
1515

16-
const router = new Router(location.href, { Component })
16+
export const router = new Router(location.href, { Component })
17+
1718
const headManager = new HeadManager()
1819
const container = document.getElementById('__next')
1920
const appProps = { Component, props, router, headManager }
2021

2122
StyleSheet.rehydrate(classNames)
22-
render(createElement(App, { ...appProps }), container)
23+
render(createElement(App, appProps), container)

gulpfile.js

+42-2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,16 @@ gulp.task('compile-test', () => {
6565
.pipe(notify('Compiled test files'))
6666
})
6767

68+
gulp.task('copy', [
69+
'copy-pages',
70+
'copy-test-fixtures'
71+
]);
72+
73+
gulp.task('copy-pages', () => {
74+
return gulp.src('pages/**/*.js')
75+
.pipe(gulp.dest('dist/pages'))
76+
})
77+
6878
gulp.task('copy-test-fixtures', () => {
6979
return gulp.src('test/fixtures/**/*')
7080
.pipe(gulp.dest('dist/test/fixtures'))
@@ -83,7 +93,21 @@ gulp.task('build-dev-client', ['compile-lib', 'compile-client'], () => {
8393
.src('dist/client/next-dev.js')
8494
.pipe(webpack({
8595
quiet: true,
86-
output: { filename: 'next-dev.bundle.js' }
96+
output: { filename: 'next-dev.bundle.js' },
97+
module: {
98+
loaders: [
99+
{
100+
test: /eval-script\.js$/,
101+
exclude: /node_modules/,
102+
loader: 'babel',
103+
query: {
104+
plugins: [
105+
'babel-plugin-transform-remove-strict-mode'
106+
]
107+
}
108+
}
109+
]
110+
},
87111
}))
88112
.pipe(gulp.dest('dist/client'))
89113
.pipe(notify('Built dev client'))
@@ -102,7 +126,21 @@ gulp.task('build-release-client', ['compile-lib', 'compile-client'], () => {
102126
}
103127
}),
104128
new webpack.webpack.optimize.UglifyJsPlugin()
105-
]
129+
],
130+
module: {
131+
loaders: [
132+
{
133+
test: /eval-script\.js$/,
134+
exclude: /node_modules/,
135+
loader: 'babel',
136+
query: {
137+
plugins: [
138+
'babel-plugin-transform-remove-strict-mode'
139+
]
140+
}
141+
}
142+
]
143+
}
106144
}))
107145
.pipe(gulp.dest('dist/client'))
108146
.pipe(notify('Built release client'))
@@ -157,6 +195,7 @@ gulp.task('clean-test', () => {
157195
gulp.task('default', [
158196
'compile',
159197
'build',
198+
'copy',
160199
'test',
161200
'watch'
162201
])
@@ -165,6 +204,7 @@ gulp.task('release', (cb) => {
165204
sequence('clean', [
166205
'compile',
167206
'build-release',
207+
'copy',
168208
'test'
169209
], 'clean-test', cb)
170210
})

lib/app.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { Component, PropTypes } from 'react'
2+
import { AppContainer } from 'react-hot-loader'
23

34
export default class App extends Component {
45
static childContextTypes = {
@@ -28,6 +29,7 @@ export default class App extends Component {
2829
const props = data.props || this.state.props
2930
const state = propsToState({
3031
...data,
32+
props,
3133
router
3234
})
3335

@@ -50,7 +52,15 @@ export default class App extends Component {
5052

5153
render () {
5254
const { Component, props } = this.state
53-
return React.createElement(Component, { ...props })
55+
56+
if ('undefined' === typeof window) {
57+
// workaround for https://github.com/gaearon/react-hot-loader/issues/283
58+
return <Component { ...props }/>
59+
}
60+
61+
return <AppContainer>
62+
<Component { ...props }/>
63+
</AppContainer>
5464
}
5565
}
5666

package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,16 @@
3636
"path-match": "1.2.4",
3737
"react": "15.3.2",
3838
"react-dom": "15.3.2",
39+
"react-hot-loader": "3.0.0-beta.6",
3940
"resolve": "1.1.7",
4041
"run-sequence": "1.2.2",
4142
"send": "0.14.1",
4243
"url": "0.11.0",
43-
"webpack": "1.13.2"
44+
"webpack": "1.13.2",
45+
"webpack-dev-server": "1.16.2"
4446
},
4547
"devDependencies": {
48+
"babel-plugin-transform-remove-strict-mode": "0.0.2",
4649
"del": "2.2.2",
4750
"gulp": "3.9.1",
4851
"gulp-ava": "0.14.1",
File renamed without changes.

server/build/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import bundle from './bundle'
55

66
export default async function build (dir) {
77
const dstDir = resolve(dir, '.next')
8-
const templateDir = resolve(__dirname, '..', '..', 'lib', 'pages')
8+
const templateDir = resolve(__dirname, '..', '..', 'pages')
99

1010
// create `.next/pages/_error.js`
1111
// which may be overwriten by the user sciprt, `pages/_error.js`

server/build/webpack.js

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { resolve } from 'path'
2+
import webpack from 'webpack'
3+
import glob from 'glob-promise'
4+
5+
export default async function createCompiler(dir, { hotReload = false } = {}) {
6+
const pages = await glob('**/*.js', { cwd: resolve(dir, 'pages') })
7+
8+
const entry = {}
9+
const defaultEntries = hotReload ? ['webpack/hot/only-dev-server'] : []
10+
for (const p of pages) {
11+
entry[p] = defaultEntries.concat(['./pages/' + p])
12+
}
13+
14+
if (!entry['_error.js']) {
15+
entry._error = resolve(__dirname, '..', '..', 'pages', '_error.js')
16+
}
17+
18+
const nodeModulesDir = resolve(__dirname, '..', '..', '..', 'node_modules')
19+
20+
const plugins = hotReload
21+
? [new webpack.HotModuleReplacementPlugin()]
22+
: [
23+
new webpack.optimize.UglifyJsPlugin({
24+
compress: { warnings: false },
25+
sourceMap: false
26+
})
27+
]
28+
29+
const babelRuntimePath = require.resolve('babel-runtime/package')
30+
.replace(/[\\\/]package\.json$/, '');
31+
32+
const loaders = [{
33+
test: /\.js$/,
34+
loader: 'babel',
35+
include: [
36+
resolve(dir),
37+
resolve(__dirname, '..', '..', 'pages')
38+
],
39+
exclude: /node_modules/,
40+
query: {
41+
presets: ['es2015', 'react'],
42+
plugins: [
43+
'transform-async-to-generator',
44+
'transform-object-rest-spread',
45+
'transform-class-properties',
46+
'transform-runtime',
47+
[
48+
'module-alias',
49+
[
50+
{ src: `npm:${babelRuntimePath}`, expose: 'babel-runtime' },
51+
{ src: `npm:${require.resolve('react')}`, expose: 'react' },
52+
{ src: `npm:${require.resolve('../../lib/link')}`, expose: 'next/link' },
53+
{ src: `npm:${require.resolve('../../lib/css')}`, expose: 'next/css' },
54+
{ src: `npm:${require.resolve('../../lib/head')}`, expose: 'next/head' }
55+
]
56+
]
57+
]
58+
}
59+
}]
60+
.concat(hotReload ? [{
61+
test: /\.js$/,
62+
loader: 'hot-self-accept-loader',
63+
include: resolve(dir, 'pages')
64+
}] : [])
65+
66+
return webpack({
67+
context: dir,
68+
entry,
69+
output: {
70+
path: resolve(dir, '.next', '_bundles', 'pages'),
71+
filename: '[name]',
72+
libraryTarget: 'commonjs2',
73+
publicPath: hotReload ? 'http://localhost:3030/' : null
74+
},
75+
externals: [
76+
'react',
77+
'react-dom',
78+
{
79+
[require.resolve('react')]: 'react',
80+
[require.resolve('../../lib/link')]: 'next/link',
81+
[require.resolve('../../lib/css')]: 'next/css',
82+
[require.resolve('../../lib/head')]: 'next/head'
83+
}
84+
],
85+
resolve: {
86+
root: [
87+
nodeModulesDir,
88+
resolve(dir, 'node_modules')
89+
]
90+
},
91+
resolveLoader: {
92+
root: [
93+
nodeModulesDir,
94+
resolve(__dirname, '..', 'loaders')
95+
]
96+
},
97+
plugins,
98+
module: {
99+
preLoaders: [
100+
{ test: /\.json$/, loader: 'json-loader' }
101+
],
102+
loaders
103+
}
104+
})
105+
}

server/hot-reloader.js

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import WebpackDevServer from 'webpack-dev-server'
2+
3+
export default class HotReloader {
4+
constructor (compiler) {
5+
this.server = new WebpackDevServer(compiler, {
6+
publicPath: '/',
7+
hot: true,
8+
noInfo: true,
9+
clientLogLevel: 'warning'
10+
})
11+
}
12+
13+
async start () {
14+
await this.waitBuild()
15+
await this.listen()
16+
}
17+
18+
async waitBuild () {
19+
const stats = await new Promise((resolve) => {
20+
this.server.middleware.waitUntilValid(resolve)
21+
})
22+
23+
const jsonStats = stats.toJson()
24+
if (jsonStats.errors.length > 0) {
25+
const err = new Error(jsonStats.errors[0])
26+
err.errors = jsonStats.errors
27+
err.warnings = jsonStats.warnings
28+
throw err
29+
}
30+
}
31+
32+
listen () {
33+
return new Promise((resolve, reject) => {
34+
this.server.listen(3030, (err) => {
35+
if (err) return reject(err)
36+
resolve()
37+
})
38+
})
39+
}
40+
41+
get fileSystem () {
42+
return this.server.middleware.fileSystem
43+
}
44+
}

0 commit comments

Comments
 (0)