Skip to content

Commit

Permalink
Universal Webpack (#3578)
Browse files Browse the repository at this point in the history
* Speed up next build

* Document webpack config

* Speed up next build

* Remove comment

* Add comment

* Clean up rules

* Add comments

* Run in parallel

* Push plugins seperately

* Create a new chunk for react

* Don’t uglify react since it’s already uglified. Move react to commons in development

* Use the minified version directly

* Re-add globpattern

* Move loaders into a separate variable

* Add comment linking to Dan’s explanation

* Remove dot

* Add universal webpack

* Initial dev support

* Fix linting

* Add changes from Arunoda's work

* Made next dev works.
But super slow and no HMR support.

* Fix client side hot reload

* Server side hmr

* Only in dev

* Add on-demand-entries client + hot-middleware

* Add .babelrc support

* Speed up on demand entries by running in parallel

* Serve static generated files

* Add missing config in dev

* Add sass support

* Add support for .map

* Add cssloader config and fix .jsx support

* Rename

* use same defaults as css-loader. Fix linting

* Add NoEmitErrorsPlugin

* Add clientBootstrap

* Use webpackhotmiddleware on the multi compiler

* alpha.3

* Use babel 16.2.x

* Fix reloading after error

* Remove comment

* Release 5.0.0-univeral-alpha.1

* Remove check for React 16

* Release 5.0.0-universal-alpha.2

* React hot loader v4

* Use our static file rendering machanism to serve pages.
This should work well since the file path for a page is predictable.

* Release 5.0.0-universal-alpha.3

* Remove optional loaders

* Release 5.0.0-universal-alpha.4

* Remove clientBootstrap

* Remove renderScript

* Make sure pages bundles are served correctly

* Remove unused import

* Revert to using the same code as canary

* Fix hot loader

* Release 5.0.0-universal-alpha.5

* Check if externals dir exist before applying config

* Add typescript support

* Add support for transpiling certain packages in node_modules

Thanks to @giuseppeg’s work in #3319

* Add BABEL_DISABLE_CACHE support

* Make sourcemaps in production opt-in

* Revert "Add support for transpiling certain packages in node_modules"

This reverts commit d4b1d9b.

In favor of a better api around this.

* Support typescript through next.config.js

* Remove comments

* Bring back commons.js calculation

* Remove unused dependencies

* Move base.config.js to webpack.js

* Make sure to only invalidate webpackDevMiddleware one after other.

* Allow babel-loder caching by default.

* Add comment about preact support

* Bring back buildir replace

* Remove obsolete plugin

* Remove build replace, speed up build

* Resolve page entries like pages/day/index.js to pages/day.js

* Add componentDidCatch back

* Compile to bundles

* Use config.distDir everywhere

* Make sure the file is an array

* Remove console.log

* Apply optimization to uglifyjs

* Add comment pointing to source

* Create entries the same way in dev and production

* Remove unused and broken pagesGlobPattern

* day/index.js is automatically turned into day.js at build time

* Remove poweredByHeader option

* Load pages with the correct path.

* Release 5.0.0-universal-alpha.6

* Make sure react-dom/server can be overwritten by module-alias

* Only add react-hot-loader babel plugin in dev

* Release 5.0.0-universal-alpha.7

* Revert tests

* Release 5.0.0-universal-alpha.10

* Make sure next/head is working properly.

* Add wepack alias for 'next' back.

* Make sure overriding className in next/head works

* Alias react too

* Add missing r

* Fragment fallback has to wrap the children

* Use min.js

* Remove css.js

* Remove wallaby.js

* Release 5.0.0-universal-alpha.11

* Resolve relative to workdir instead of next

* Make sure we touch the right file

* Resolve next modules

* Remove dotjsx removal plugins since we use webpack on the server

* Revert "Resolve relative to workdir instead of next"

This reverts commit a13f3e4.

* Externalize any locally loaded module lives outside of app dir.

* Remove server aliases

* Check node_modules reliably

* Add symlink to next for tests

* Make sure dynamic imports work locally.
This is why we need it: https://github.com/webpack/webpack/blob/b545b519b2024e3f8be3041385bd326bf5d24449/lib/MainTemplate.js#L68
We need to have the finally clause in the above in __webpack_require__.
webpack output option strictModuleExceptionHandling does that.

* dynmaic -> dynamic

* Remove webpack-node-externals

* Make sure dynamic imports support SSR.

* Remove css support in favor of next-css

* Make sure we load path from `/` since it’s included in the path matching

* Catch when ensurepage couldn’t be fulfilled for `.js.map`

* Register require cache flusher for both client and server

* Add comment explaining this is to facilitate hot reloading

* Only load module when needed

* Remove unused modules

* Release 5.0.0-universal-alpha.12

* Only log the `found babel` message once

* Make sure ondemand entries working correctly.
Now we are just using a single instance of OnDemandEntryHandler.

* Better sourcemaps

* Release 5.0.0-universal-alpha.13

* Lock uglify version to 1.1.6

* Release 5.0.0-universal-alpha.14

* Fix a typo.

* Introduce multi-zones support for mircofrontends

* Add section on css
  • Loading branch information
timneutkens committed Jan 30, 2018
1 parent 202ceca commit e093441
Show file tree
Hide file tree
Showing 60 changed files with 1,219 additions and 1,606 deletions.
1 change: 1 addition & 0 deletions asset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./dist/lib/asset')
4 changes: 1 addition & 3 deletions bin/next-dev
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
#!/usr/bin/env node
import 'source-map-support/register'
import { resolve, join } from 'path'
import parseArgs from 'minimist'
import { existsSync, readFileSync } from 'fs'
import Server from '../server'
import { printAndExit } from '../lib/utils'
import pkgUp from 'pkg-up'

const argv = parseArgs(process.argv.slice(2), {
alias: {
Expand Down Expand Up @@ -64,7 +62,7 @@ srv.start(argv.port, argv.hostname)
.catch((err) => {
if (err.code === 'EADDRINUSE') {
let errorMessage = `Port ${argv.port} is already in use.`
const pkgAppPath = pkgUp.sync('.')
const pkgAppPath = require('pkg-up').sync('.')
const appPackage = JSON.parse(readFileSync(pkgAppPath, 'utf8'))
const nextScript = Object.entries(appPackage.scripts).find(scriptLine => scriptLine[1] === 'next')
if (nextScript) errorMessage += `\nUse \`npm run ${nextScript[0]} -- -p <some other port>\`.`
Expand Down
12 changes: 7 additions & 5 deletions client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import EventEmitter from '../lib/EventEmitter'
import App from '../lib/app'
import { loadGetInitialProps, getURL } from '../lib/utils'
import PageLoader from '../lib/page-loader'
import * as asset from '../lib/asset'

// Polyfill Promise globally
// This is needed because Webpack2's dynamic loading(common chunks) code
Expand All @@ -29,6 +30,9 @@ const {
location
} = window

// With this, static assets will work across zones
asset.setAssetPrefix(assetPrefix)

const asPath = getURL()

const pageLoader = new PageLoader(buildId, assetPrefix)
Expand Down Expand Up @@ -93,10 +97,7 @@ export default async ({ ErrorDebugComponent: passedDebugComponent, stripAnsi: pa
}

export async function render (props) {
// There are some errors we should ignore.
// Next.js rendering logic knows how to handle them.
// These are specially 404 errors
if (props.err && !props.err.ignore) {
if (props.err) {
await renderError(props.err)
return
}
Expand Down Expand Up @@ -159,7 +160,8 @@ async function doRender ({ Component, props, hash, err, emitter: emitterProp = e

let isInitialRender = true
function renderReactElement (reactEl, domEl) {
if (isInitialRender) {
// The check for `.hydrate` is there to support React alternatives like preact
if (isInitialRender && typeof ReactDOM.hydrate === 'function') {
ReactDOM.hydrate(reactEl, domEl)
isInitialRender = false
} else {
Expand Down
3 changes: 2 additions & 1 deletion client/next-dev.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import 'react-hot-loader/patch'
import stripAnsi from 'strip-ansi'
import initNext, * as next from './'
import ErrorDebugComponent from '../lib/error-debug'
import initOnDemandEntries from './on-demand-entries-client'
import initWebpackHMR from './webpack-hot-middleware-client'

require('@zeit/source-map-support/browser-source-map-support')

window.next = next

initNext({ ErrorDebugComponent, stripAnsi })
Expand Down
12 changes: 9 additions & 3 deletions client/on-demand-entries-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,29 @@
import Router from '../lib/router'
import fetch from 'unfetch'

const {
__NEXT_DATA__: {
assetPrefix
}
} = window

export default () => {
Router.ready(() => {
Router.router.events.on('routeChangeComplete', ping)
})

async function ping () {
try {
const url = `/_next/on-demand-entries-ping?page=${Router.pathname}`
const url = `${assetPrefix}/_next/on-demand-entries-ping?page=${Router.pathname}`
const res = await fetch(url, {
credentials: 'same-origin'
credentials: 'omit'
})
const payload = await res.json()
if (payload.invalid) {
// Payload can be invalid even if the page is not exists.
// So, we need to make sure it's exists before reloading.
const pageRes = await fetch(location.href, {
credentials: 'same-origin'
credentials: 'omit'
})
if (pageRes.status === 200) {
location.reload()
Expand Down
14 changes: 13 additions & 1 deletion client/webpack-hot-middleware-client.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
import webpackHotMiddlewareClient from 'webpack-hot-middleware/client?overlay=false&reload=true&path=/_next/webpack-hmr'
import webpackHotMiddlewareClient from 'webpack-hot-middleware/client?autoConnect=false'
import Router from '../lib/router'

const {
__NEXT_DATA__: {
assetPrefix
}
} = window

export default () => {
webpackHotMiddlewareClient.setOptionsAndConnect({
overlay: false,
reload: true,
path: `${assetPrefix}/_next/webpack-hmr`
})

const handlers = {
reload (route) {
if (route === '/_error') {
Expand Down
1 change: 0 additions & 1 deletion css.js

This file was deleted.

15 changes: 15 additions & 0 deletions errors/powered-by-header-option-removed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# The poweredByHeader has been removed

#### Why This Error Occurred

Starting at Next.js version 5.0.0 the `poweredByHeader` option has been removed.

#### Possible Ways to Fix It

If you still want to remove `x-powered-by` you can use one of the custom-server examples.

And then manually remove the header using `res.removeHeader('x-powered-by')`

### Useful Links

- [Custom Server documentation + examples](https://github.com/zeit/next.js#custom-server-and-routing)
3 changes: 3 additions & 0 deletions examples/hello-world/pages/about2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default () => (
<div>About 2</div>
)
3 changes: 3 additions & 0 deletions examples/hello-world/pages/day/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default () => (
<div>Hello Day</div>
)
71 changes: 71 additions & 0 deletions examples/with-zones/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-zones)

# Using multiple zones

With Next.js you can use multiple apps as a single app using it's multi-zones feature.
This is an example showing how to use it.

In this example, we've two apps: 'home' and 'blog'.
We also have a set of rules defined in `rules.json` for the proxy.

Now let's start two of our app using:

```
npm run home
npm run blog
```

Then start the proxy:

```
npm run proxy
```

Now you can visit http://localhost:9000 and access and develop both apps a single app.

### Proxy Rules

This is the place we define rules for our proxy. Here are the rules(in `rules.json`) available for this app:

```json
{
"rules": [
{"pathname": "/blog", "method":["GET", "POST", "OPTIONS"], "dest": "http://localhost:5000"},
{"pathname": "/**", "dest": "http://localhost:4000"}
]
}
```

These rules are based on ZEIT now [path alias](https://zeit.co/docs/features/path-aliases) rules and use [`micro-proxy`](https://github.com/zeit/micro-proxy) as the proxy.

## Special Notes

* All pages should be unique across zones. A page with the same name should not exist in multiple zones. Otherwise, there'll be unexpected behaviour in client side navigation.
* According to the above example, a page named `blog` should not be exist in the `home` zone.

## Production Deployment

Here's how are going to deploy this application into production.

* Open the `now.json` file in both `blog` and `home` directories and change the aliases as you wish.
* Then update `rules-prod.json` accordingly.
* Now deploy both apps:

~~~sh
cd home
now && now alias
cd ../blog
now && now alias
cd ..
~~~

* Finally, set the path alias rules with

~~~sh
now alias with-zones.now.sh -r rules-prod.json
~~~

> You can use a domain name of your choice in the above command instead of `with-zones.now.sh`.
That's it.
Now you can access the final app via: <https://with-zones.now.sh>
2 changes: 2 additions & 0 deletions examples/with-zones/blog/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.next
node_modules
6 changes: 6 additions & 0 deletions examples/with-zones/blog/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const { NOW_URL } = process.env
const { alias } = require('./now.json')

module.exports = {
assetPrefix: NOW_URL ? `https://${alias}` : 'http://localhost:5000'
}
3 changes: 3 additions & 0 deletions examples/with-zones/blog/now.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"alias": "with-zones-blog.now.sh"
}
14 changes: 14 additions & 0 deletions examples/with-zones/blog/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "with-zones-blog",
"version": "1.0.0",
"scripts": {
"build": "next build",
"start": "next start -p 4000"
},
"dependencies": {
"next": "zones",
"react": "^16.0.0",
"react-dom": "^16.0.0"
},
"license": "ISC"
}
5 changes: 5 additions & 0 deletions examples/with-zones/blog/pages/blog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default () => (
<div>
This is our blog
</div>
)
2 changes: 2 additions & 0 deletions examples/with-zones/home/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.next
node_modules
5 changes: 5 additions & 0 deletions examples/with-zones/home/components/Header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default () => (
<div>
<h2>The Company</h2>
</div>
)
6 changes: 6 additions & 0 deletions examples/with-zones/home/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const { NOW_URL } = process.env
const { alias } = require('./now.json')

module.exports = {
assetPrefix: NOW_URL ? `https://${alias}` : 'http://localhost:4000'
}
3 changes: 3 additions & 0 deletions examples/with-zones/home/now.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"alias": "with-zones-home.now.sh"
}
14 changes: 14 additions & 0 deletions examples/with-zones/home/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "with-zones-home",
"version": "1.0.0",
"scripts": {
"build": "next build",
"start": "next start -p 4000"
},
"dependencies": {
"next": "zones",
"react": "^16.0.0",
"react-dom": "^16.0.0"
},
"license": "ISC"
}
10 changes: 10 additions & 0 deletions examples/with-zones/home/pages/about.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import asset from 'next/asset'
import Link from 'next/link'

export default () => (
<div>
<p>This is the about page.</p>
<div><Link href='/'><a>Go Back</a></Link></div>
<img width={200} src={asset('/zeit.png')} />
</div>
)
15 changes: 15 additions & 0 deletions examples/with-zones/home/pages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Link from 'next/link'
import asset from 'next/asset'
import dynamic from 'next/dynamic'

const Header = dynamic(import('../components/Header'))

export default () => (
<div>
<Header />
<p>This is our homepage</p>
<div><Link href='/blog'><a>Blog</a></Link></div>
<div><Link href='/about'><a>About us</a></Link></div>
<img width={200} src={asset('/nextjs.png')} />
</div>
)
Binary file added examples/with-zones/home/static/nextjs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/with-zones/home/static/zeit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions examples/with-zones/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "with-zones",
"version": "1.0.0",
"scripts": {
"home": "next home -p 4000",
"home-build": "next build home",
"home-start": "next start home -p 4000",
"blog": "next blog -p 5000",
"blog-build": "next build blog",
"blog-start": "next start blog -p 5000",
"proxy": "micro-proxy -r rules-dev.json"
},
"dependencies": {
"micro-proxy": "^1.0.0",
"next": "latest",
"react": "^16.0.0",
"react-dom": "^16.0.0"
},
"license": "ISC"
}
6 changes: 6 additions & 0 deletions examples/with-zones/rules-dev.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"rules": [
{"pathname": "/blog", "method":["GET", "POST", "OPTIONS"], "dest": "http://localhost:5000"},
{"pathname": "/**", "dest": "http://localhost:4000"}
]
}
6 changes: 6 additions & 0 deletions examples/with-zones/rules-prod.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"rules": [
{"pathname": "/blog", "method":["GET", "POST", "OPTIONS"], "dest": "https://with-zones-blog.now.sh"},
{"pathname": "/**", "dest": "https://with-zones-home.now.sh"}
]
}
10 changes: 10 additions & 0 deletions lib/asset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
let assetPrefix

export default function asset (path) {
const pathWithoutSlash = path.replace(/^\//, '')
return `${assetPrefix}/static/${pathWithoutSlash}`
}

export function setAssetPrefix (url) {
assetPrefix = url
}
1 change: 0 additions & 1 deletion lib/css.js

This file was deleted.

5 changes: 5 additions & 0 deletions lib/dynamic.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ export function flushChunks () {
}

export class SameLoopPromise {
static resolve (value) {
const promise = new SameLoopPromise((done) => done(value))
return promise
}

constructor (cb) {
this.onResultCallbacks = []
this.onErrorCallbacks = []
Expand Down
2 changes: 1 addition & 1 deletion lib/head.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function reduceComponents (components) {
.filter(unique())
.reverse()
.map((c) => {
const className = (c.className ? c.className + ' ' : '') + 'next-head'
const className = (c.props && c.props.className ? c.props.className + ' ' : '') + 'next-head'
return React.cloneElement(c, { className })
})
}
Expand Down
Loading

0 comments on commit e093441

Please sign in to comment.