diff --git a/.babelrc b/.babelrc index 6f19a631..900da0c0 100644 --- a/.babelrc +++ b/.babelrc @@ -1,6 +1,6 @@ { "presets": [ - "latest", + "env", "react" ], "plugins": [ diff --git a/bin/next b/bin/next index 8654d788..79f635b7 100755 --- a/bin/next +++ b/bin/next @@ -1,9 +1,10 @@ #!/usr/bin/env node -import { join } from 'path' +import { join, resolve } from 'path' import { spawn } from 'cross-spawn' import { watchFile } from 'fs' import pkg from '../../package.json' +import getConfig from '../server/config' if (pkg.peerDependencies) { Object.keys(pkg.peerDependencies).forEach(dependency => { @@ -78,9 +79,10 @@ const startProcess = () => { } let proc = startProcess() +const { pagesDirectory = resolve(process.cwd(), 'pages') } = getConfig(process.cwd()) if (cmd === 'dev') { - watchFile(join(process.cwd(), 'next.config.js'), (cur, prev) => { + watchFile(`${resolve(pagesDirectory, '..')}/next.config.js`, (cur, prev) => { if (cur.size > 0 || prev.size > 0) { console.log('\n> Found a change in next.config.js, restarting the server...') // Don't listen to 'close' now since otherwise parent gets killed by listener diff --git a/client/index.js b/client/index.js index 48bffab9..cbf237c4 100644 --- a/client/index.js +++ b/client/index.js @@ -24,12 +24,13 @@ const { pathname, query, buildId, - chunks + chunks, + assetPrefix }, location } = window -const pageLoader = new PageLoader(buildId) +const pageLoader = new PageLoader(buildId, assetPrefix) window.__NEXT_LOADED_PAGES__.forEach(({ route, fn }) => { pageLoader.registerPage(route, fn) }) diff --git a/client/webpack-hot-middleware-client.js b/client/webpack-hot-middleware-client.js index 38b55df6..f97c5853 100644 --- a/client/webpack-hot-middleware-client.js +++ b/client/webpack-hot-middleware-client.js @@ -1,4 +1,4 @@ -import webpackHotMiddlewareClient from 'webpack-hot-middleware/client?overlay=false&reload=true' +import webpackHotMiddlewareClient from 'webpack-hot-middleware/client?overlay=false&reload=true&path=/_next/webpack-hmr' import Router from '../lib/router' export default () => { diff --git a/examples/parameterized-routing/server.js b/examples/parameterized-routing/server.js index 35a42482..1089d526 100644 --- a/examples/parameterized-routing/server.js +++ b/examples/parameterized-routing/server.js @@ -12,14 +12,16 @@ const match = route('/blog/:id') app.prepare() .then(() => { createServer((req, res) => { - const { pathname } = parse(req.url) + const { pathname, query } = parse(req.url, true) const params = match(pathname) if (params === false) { handle(req, res) return } - - app.render(req, res, '/blog', params) + // assigning `query` into the params means that we still + // get the query string passed to our application + // i.e. /blog/foo?show-comments=true + app.render(req, res, '/blog', Object.assign(params, query)) }) .listen(3000, (err) => { if (err) throw err diff --git a/examples/with-global-stylesheet/README.md b/examples/with-global-stylesheet/README.md index 2576c371..53cd30b8 100644 --- a/examples/with-global-stylesheet/README.md +++ b/examples/with-global-stylesheet/README.md @@ -37,7 +37,7 @@ Another babel plugin [module-resolver](https://github.com/tleunen/babel-plugin-m The `sass-loader` is configured with `includePaths: ['styles', 'node_modules']` so that your scss can `@import` from those places, again without relative paths, for maximum convenience and ability to use npm-published libraries. Furthermore, `glob` paths are also supported, so one could for example add `'node_modules/@material/*'` to the `includePaths`, which would make [material-components-web](https://github.com/material-components/material-components-web) (if you'd like) even easier to work with. -Furthermore, PostCSS is used to [pre-process](https://blog.madewithenvy.com/webpack-2-postcss-cssnext-fdcd2fd7d0bd#.r6t2d0smy) both `css` and `scss` stylesheets, the latter after Sass pre-processing. This is to illustrate `@import 'normalize.css';` from `node_modules` thanks to `postcss-easy-import`. [Autoprefixer](https://github.com/postcss/autoprefixer) is also added as a "best practice". Consider [cssnext](http://cssnext.io) instead, which includes `autoprefixer` as well as many other CSS spec features. +Furthermore, PostCSS is used to [pre-process](https://medium.com/@ddprrt/deconfusing-pre-and-post-processing-d68e3bd078a3) both `css` and `scss` stylesheets, the latter after Sass pre-processing. This is to illustrate `@import 'normalize.css';` from `node_modules` thanks to `postcss-easy-import`. [Autoprefixer](https://github.com/postcss/autoprefixer) is also added as a "best practice". Consider [cssnext](http://cssnext.io) instead, which includes `autoprefixer` as well as many other CSS spec features. This project shows how you can set it up. Have a look at: - .babelrc diff --git a/examples/with-pretty-url-routing/package.json b/examples/with-pretty-url-routing/package.json index 6ecb3fb5..3080fbb7 100644 --- a/examples/with-pretty-url-routing/package.json +++ b/examples/with-pretty-url-routing/package.json @@ -8,6 +8,7 @@ "express": "^4.15.2", "next": "^2.0.0", "next-url-prettifier": "^1.0.2", + "prop-types": "^15.5.6", "react": "^15.4.2", "react-dom": "^15.4.2" } diff --git a/examples/with-pretty-url-routing/pages/greeting.js b/examples/with-pretty-url-routing/pages/greeting.js index e4fe354f..03b9d4fb 100644 --- a/examples/with-pretty-url-routing/pages/greeting.js +++ b/examples/with-pretty-url-routing/pages/greeting.js @@ -1,4 +1,5 @@ import React from 'react' +import PropTypes from 'prop-types' import {Link} from 'next-url-prettifier' import {Router} from '../routes' @@ -29,6 +30,6 @@ export default class GreetingPage extends React.Component { } GreetingPage.propTypes = { - lang: React.PropTypes.string, - name: React.PropTypes.string + lang: PropTypes.string, + name: PropTypes.string } diff --git a/lib/app.js b/lib/app.js index 2cc4d575..f8d33cee 100644 --- a/lib/app.js +++ b/lib/app.js @@ -1,4 +1,5 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import { AppContainer } from 'react-hot-loader' import shallowEquals from './shallow-equals' import { warn } from './utils' diff --git a/lib/head.js b/lib/head.js index ea580942..6200efe4 100644 --- a/lib/head.js +++ b/lib/head.js @@ -1,9 +1,10 @@ import React from 'react' +import PropTypes from 'prop-types' import sideEffect from './side-effect' class Head extends React.Component { static contextTypes = { - headManager: React.PropTypes.object + headManager: PropTypes.object } render () { diff --git a/lib/link.js b/lib/link.js index 87c4c32f..547161e7 100644 --- a/lib/link.js +++ b/lib/link.js @@ -1,5 +1,6 @@ import { resolve, format, parse } from 'url' -import React, { Component, Children, PropTypes } from 'react' +import React, { Component, Children } from 'react' +import PropTypes from 'prop-types' import Router from './router' import { warn, execOnce, getLocationOrigin } from './utils' diff --git a/lib/page-loader.js b/lib/page-loader.js index 9b4459b9..100eef60 100644 --- a/lib/page-loader.js +++ b/lib/page-loader.js @@ -4,8 +4,10 @@ import mitt from 'mitt' const webpackModule = module export default class PageLoader { - constructor (buildId) { + constructor (buildId, assetPrefix) { this.buildId = buildId + this.assetPrefix = assetPrefix + this.pageCache = {} this.pageLoadedHandlers = {} this.pageRegisterEvents = mitt() @@ -59,7 +61,7 @@ export default class PageLoader { route = this.normalizeRoute(route) const script = document.createElement('script') - const url = `/_next/${encodeURIComponent(this.buildId)}/page${route}` + const url = `${this.assetPrefix}/_next/${encodeURIComponent(this.buildId)}/page${route}` script.src = url script.type = 'text/javascript' script.onerror = () => { diff --git a/lib/router/router.js b/lib/router/router.js index 5272030e..8b1a4263 100644 --- a/lib/router/router.js +++ b/lib/router/router.js @@ -81,8 +81,8 @@ export default class Router { if (route !== this.route) return + const { pathname, query } = this const url = window.location.href - const { pathname, query } = parse(url, true) this.events.emit('routeChangeStart', url) const routeInfo = await this.getRouteInfo(route, pathname, query, url) diff --git a/package.json b/package.json index 013a3918..2de899ff 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,10 @@ { "name": "next", - "version": "2.0.1", + "version": "2.1.1", "description": "Minimalistic framework for server-rendered React applications", "main": "./dist/server/next.js", "license": "MIT", "repository": "zeit/next.js", - "publishConfig": { - "tag": "beta" - }, "files": [ "dist", "babel.js", @@ -57,17 +54,19 @@ "babel-plugin-transform-es2015-modules-commonjs": "6.24.0", "babel-plugin-transform-object-rest-spread": "6.22.0", "babel-plugin-transform-react-jsx-source": "6.22.0", - "babel-plugin-transform-react-remove-prop-types": "0.3.2", + "babel-plugin-transform-react-remove-prop-types": "0.4.0", "babel-plugin-transform-runtime": "6.22.0", - "babel-preset-latest": "6.24.0", + "babel-preset-env": "1.3.3", "babel-preset-react": "6.23.0", "babel-runtime": "6.23.0", "babel-template": "6.24.1", "case-sensitive-paths-webpack-plugin": "2.0.0", "cross-spawn": "5.1.0", "del": "2.2.2", + "etag": "1.8.0", + "fresh": "0.5.0", "friendly-errors-webpack-plugin": "1.5.0", - "glob": "^7.1.1", + "glob": "7.1.1", "glob-promise": "3.1.0", "htmlescape": "1.1.1", "http-status": "1.0.1", @@ -82,6 +81,7 @@ "mz": "2.6.0", "path-match": "1.2.4", "pkg-up": "1.0.0", + "prop-types": "15.5.7", "react-hot-loader": "3.0.0-beta.6", "send": "0.15.1", "source-map-support": "0.4.14", @@ -97,14 +97,14 @@ "write-file-webpack-plugin": "4.0.0" }, "devDependencies": { - "babel-eslint": "7.2.0", + "babel-eslint": "7.2.2", "babel-jest": "18.0.0", "babel-plugin-istanbul": "4.1.1", "babel-plugin-transform-remove-strict-mode": "0.0.2", "babel-preset-es2015": "6.24.0", "benchmark": "2.1.4", "cheerio": "0.22.0", - "chromedriver": "2.28.0", + "chromedriver": "2.29.0", "coveralls": "2.13.0", "cross-env": "4.0.0", "fly": "2.0.5", @@ -118,8 +118,8 @@ "node-fetch": "1.6.3", "node-notifier": "5.1.2", "nyc": "10.2.0", - "react": "15.4.2", - "react-dom": "15.4.2", + "react": "15.5.3", + "react-dom": "15.5.3", "standard": "9.0.2", "wd": "1.2.0" }, diff --git a/readme.md b/readme.md index cadc8e78..bc2e0439 100644 --- a/readme.md +++ b/readme.md @@ -11,6 +11,7 @@ Next.js is a minimalistic framework for server-rendered React applications. - [How to use](#how-to-use) + - [Getting Started](#getting-started) - [Setup](#setup) - [Automatic code splitting](#automatic-code-splitting) - [CSS](#css) @@ -33,9 +34,9 @@ Next.js is a minimalistic framework for server-rendered React applications. - [Custom configuration](#custom-configuration) - [Customizing webpack config](#customizing-webpack-config) - [Customizing babel config](#customizing-babel-config) + - [CDN support with Asset Prefix](#cdn-support-with-asset-prefix) - [Production deployment](#production-deployment) - [FAQ](#faq) -- [Roadmap](#roadmap) - [Contributing](#contributing) - [Authors](#authors) @@ -43,6 +44,10 @@ Next.js is a minimalistic framework for server-rendered React applications. ## How to use +### Getting Started + +A step by step interactive guide of next features is available at [learnnextjs.com](https://learnnextjs.com/) + ### Setup Install it: @@ -298,7 +303,7 @@ The component `` can also receive an URL object and it will automatically // pages/index.js import Link from 'next/link' export default () => ( -
Click here to read more
+
Click here to read more
) ``` @@ -700,6 +705,20 @@ Here's an example `.babelrc` file: } ``` +### CDN support with Asset Prefix + +To set up a CDN, you can set up the `assetPrefix` setting and configure your CDN's origin to resolve to the domain that Next.js is hosted on. + +```js +const isProd = process.NODE_ENV === 'production' +module.exports = { + // You may only need to add assetPrefix in the production. + assetPrefix: isProd ? 'https://cdn.mydomain.com' : '' +} +``` + +Note: Next.js will automatically use that prefix the scripts it loads, but this has no effect whatsoever on `/static`. If you want to serve those assets over the CDN, you'll have to introduce the prefix yourself. One way of introducing a prefix that works inside your components and varies by environment is documented [in this example](https://github.com/zeit/next.js/tree/master/examples/with-universal-configuration). + ## Production deployment To deploy, instead of running `next`, you want to build for production usage ahead of time. Therefore, building and starting are separate commands: @@ -840,10 +859,6 @@ As we were researching options for server-rendering React that didn’t involve -## Roadmap - -Our Roadmap towards 2.0.0 [is public](https://github.com/zeit/next.js/wiki/Roadmap#nextjs-200). - ## Contributing Please see our [contributing.md](./contributing.md) diff --git a/server/build/babel/preset.js b/server/build/babel/preset.js index 2590c2cc..61e3e371 100644 --- a/server/build/babel/preset.js +++ b/server/build/babel/preset.js @@ -13,8 +13,8 @@ const plugins = envPlugins[process.env.NODE_ENV] || [] module.exports = { presets: [ - [require.resolve('babel-preset-latest'), { - 'es2015': { modules: false } + [require.resolve('babel-preset-env'), { + modules: false }], require.resolve('babel-preset-react') ], diff --git a/server/build/index.js b/server/build/index.js index 95195460..e13d36ff 100644 --- a/server/build/index.js +++ b/server/build/index.js @@ -1,6 +1,5 @@ import { tmpdir } from 'os' import { join } from 'path' -import getConfig from '../config' import fs from 'mz/fs' import uuid from 'uuid' import del from 'del' @@ -14,10 +13,8 @@ export default async function build (dir) { try { await runCompiler(compiler) - - // Pass in both the buildDir and the dir to retrieve config - await writeBuildStats(buildDir, dir) - await writeBuildId(buildDir, dir) + await writeBuildStats(buildDir) + await writeBuildId(buildDir) } catch (err) { console.error(`> Failed to build on ${buildDir}`) throw err @@ -48,24 +45,22 @@ function runCompiler (compiler) { }) } -async function writeBuildStats (buildDir, dir) { - const dist = getConfig(dir).distDir +async function writeBuildStats (dir) { // Here we can't use hashes in webpack chunks. // That's because the "app.js" is not tied to a chunk. // It's created by merging a few assets. (commons.js and main.js) // So, we need to generate the hash ourself. const assetHashMap = { 'app.js': { - hash: await md5File(join(buildDir, dist, 'app.js')) + hash: await md5File(join(dir, '.next', 'app.js')) } } - const buildStatsPath = join(buildDir, dist, 'build-stats.json') + const buildStatsPath = join(dir, '.next', 'build-stats.json') await fs.writeFile(buildStatsPath, JSON.stringify(assetHashMap), 'utf8') } -async function writeBuildId (buildDir, dir) { - const dist = getConfig(dir).distDir - const buildIdPath = join(buildDir, dist, 'BUILD_ID') +async function writeBuildId (dir) { + const buildIdPath = join(dir, '.next', 'BUILD_ID') const buildId = uuid.v4() await fs.writeFile(buildIdPath, buildId, 'utf8') } diff --git a/server/build/replace.js b/server/build/replace.js index 1679ba25..22ff9e4a 100644 --- a/server/build/replace.js +++ b/server/build/replace.js @@ -4,10 +4,9 @@ import getConfig from '../config' export default async function replaceCurrentBuild (dir, buildDir) { const dist = getConfig(dir).distDir - const buildDist = getConfig(buildDir).distDir const _dir = join(dir, dist) - const _buildDir = join(buildDir, dist) - const oldDir = join(buildDir, `${buildDist}.old`) + const _buildDir = join(buildDir, '.next') + const oldDir = join(buildDir, '.next.old') try { await move(_dir, oldDir) diff --git a/server/build/webpack.js b/server/build/webpack.js index 2c38cc4a..bb70f4d1 100644 --- a/server/build/webpack.js +++ b/server/build/webpack.js @@ -37,7 +37,7 @@ export default async function createCompiler (dir, { dev = false, quiet = false, const mainJS = dev ? require.resolve('../../client/next-dev') : require.resolve('../../client/next') - let minChunks + let totalPages const entry = async () => { const entries = { @@ -69,8 +69,7 @@ export default async function createCompiler (dir, { dev = false, quiet = false, } } - // calculate minChunks of CommonsChunkPlugin for later use - minChunks = Math.max(2, pages.filter((p) => p !== documentPage).length) + totalPages = pages.filter((p) => p !== documentPage).length return entries } @@ -102,9 +101,8 @@ export default async function createCompiler (dir, { dev = false, quiet = false, return module.context && module.context.indexOf('node_modules') >= 0 } - // NOTE: it depends on the fact that the entry funtion is always called - // before applying CommonsChunkPlugin - return count >= minChunks + // Move modules used in at-least 1/2 of the total pages into commons. + return count >= totalPages * 0.5 } }), // This chunk contains all the webpack related code. So, all the changes @@ -268,10 +266,10 @@ export default async function createCompiler (dir, { dev = false, quiet = false, context: dir, entry, output: { - path: join(buildDir || dir, config.distDir), + path: buildDir ? join(buildDir, '.next') : join(dir, config.distDir), filename: '[name]', libraryTarget: 'commonjs2', - publicPath: '/_webpack/', + publicPath: '/_next/webpack/', strictModuleExceptionHandling: true, devtoolModuleFilenameTemplate ({ resourcePath }) { const hash = createHash('sha1') diff --git a/server/config.js b/server/config.js index 5a70252c..bc5059f5 100644 --- a/server/config.js +++ b/server/config.js @@ -6,7 +6,8 @@ const cache = new Map() const defaultConfig = { webpack: null, poweredByHeader: true, - distDir: '.next' + distDir: '.next', + assetPrefix: '' } export default function getConfig (dir) { diff --git a/server/document.js b/server/document.js index a9d81830..ef21c7b8 100644 --- a/server/document.js +++ b/server/document.js @@ -1,4 +1,5 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import htmlescape from 'htmlescape' import flush from 'styled-jsx/server' @@ -35,14 +36,14 @@ export class Head extends Component { getChunkPreloadLink (filename) { const { __NEXT_DATA__ } = this.context._documentProps - let { buildStats } = __NEXT_DATA__ + let { buildStats, assetPrefix } = __NEXT_DATA__ const hash = buildStats ? buildStats[filename].hash : '-' return ( ) @@ -65,12 +66,13 @@ export class Head extends Component { } getPreloadDynamicChunks () { - const { chunks } = this.context._documentProps + const { chunks, __NEXT_DATA__ } = this.context._documentProps + let { assetPrefix } = __NEXT_DATA__ return chunks.map((chunk) => ( )) @@ -78,11 +80,11 @@ export class Head extends Component { render () { const { head, styles, __NEXT_DATA__ } = this.context._documentProps - const { pathname, buildId } = __NEXT_DATA__ + const { pathname, buildId, assetPrefix } = __NEXT_DATA__ return - - + + {this.getPreloadDynamicChunks()} {this.getPreloadMainLinks()} {(head || []).map((h, i) => React.cloneElement(h, { key: i }))} @@ -115,13 +117,13 @@ export class NextScript extends Component { getChunkScript (filename, additionalProps = {}) { const { __NEXT_DATA__ } = this.context._documentProps - let { buildStats } = __NEXT_DATA__ + let { buildStats, assetPrefix } = __NEXT_DATA__ const hash = buildStats ? buildStats[filename].hash : '-' return (