1
0
Fork 0
mirror of https://github.com/terribleplan/next.js.git synced 2024-01-19 02:48:18 +00:00

Merge branch 'canary' of github.com:zeit/next.js into canary

This commit is contained in:
Tim Neutkens 2018-03-30 16:09:40 +02:00
commit b6b88e5f81
101 changed files with 2125 additions and 1013 deletions

View file

@ -1,7 +1,8 @@
{
"presets": [
"env",
"react"
"react",
"flow"
],
"plugins": [
"transform-object-rest-spread",

2
.flowconfig Normal file
View file

@ -0,0 +1,2 @@
[ignore]
<PROJECT_ROOT>/examples/.*

View file

@ -7,14 +7,14 @@
"dependencies": {
"next": "latest",
"react": "^16.2.0",
"react-dom": "^16.2.0"
"react-dom": "^16.2.0",
"@zeit/next-typescript": "0.0.11",
"typescript": "^2.7.1"
},
"devDependencies": {
"@types/next": "^2.4.7",
"@types/react": "^16.0.36",
"@zeit/next-typescript": "^0.0.8",
"nodemon": "^1.12.1",
"ts-node": "^4.1.0",
"typescript": "^2.7.1"
"ts-node": "^4.1.0"
}
}

View file

@ -1,3 +1,4 @@
/* eslint-disable */
const withCss = require('@zeit/next-css')
// fix: prevents error when .css files are required by node

View file

@ -49,3 +49,6 @@ export default withReduxSaga(connect(mapStateToProps, null)(Index));
```
`connect` must go inside `withReduxSaga` otherwise `connect` will not be able to find the store.
### Note:
In these *with-apollo* examples, the ```withData()``` HOC must wrap a top-level component from within the ```pages``` directory. Wrapping a child component with the HOC will result in a ```Warning: Failed prop type: The prop 'serverState' is marked as required in 'WithData(Apollo(Component))', but its value is 'undefined'``` error. Down-tree child components will have access to Apollo, and can be wrapped with any other sort of ```graphql()```, ```compose()```, etc HOC's.

View file

@ -49,3 +49,6 @@ const mapStateToProps = state => ({
export default withRedux(connect(mapStateToProps, null)(Index));
```
### Note:
In these *with-apollo* examples, the ```withData()``` HOC must wrap a top-level component from within the ```pages``` directory. Wrapping a child component with the HOC will result in a ```Warning: Failed prop type: The prop 'serverState' is marked as required in 'WithData(Apollo(Component))', but its value is 'undefined'``` error. Down-tree child components will have access to Apollo, and can be wrapped with any other sort of ```graphql()```, ```compose()```, etc HOC's.

View file

@ -66,3 +66,7 @@ It is important to note the use of Apollo's `resetStore()` method after signing
To get this example running locally, you will need to create a graph.cool
account, and provide [the `project.graphcool` schema](https://github.com/zeit/next.js/blob/master/examples/with-apollo-auth/project.graphcool).
### Note:
In these *with-apollo* examples, the ```withData()``` HOC must wrap a top-level component from within the ```pages``` directory. Wrapping a child component with the HOC will result in a ```Warning: Failed prop type: The prop 'serverState' is marked as required in 'WithData(Apollo(Component))', but its value is 'undefined'``` error. Down-tree child components will have access to Apollo, and can be wrapped with any other sort of ```graphql()```, ```compose()```, etc HOC's.

View file

@ -48,3 +48,6 @@ In this simple example, we integrate Apollo seamlessly with Next by wrapping our
On initial page load, while on the server and inside `getInitialProps`, we invoke the Apollo method, [`getDataFromTree`](http://dev.apollodata.com/react/server-side-rendering.html#getDataFromTree). This method returns a promise; at the point in which the promise resolves, our Apollo Client store is completely initialized.
This example relies on [graph.cool](https://www.graph.cool) for its GraphQL backend.
### Note:
In these *with-apollo* examples, the ```withData()``` HOC must wrap a top-level component from within the ```pages``` directory. Wrapping a child component with the HOC will result in a ```Warning: Failed prop type: The prop 'serverState' is marked as required in 'WithData(Apollo(Component))', but its value is 'undefined'``` error. Down-tree child components will have access to Apollo, and can be wrapped with any other sort of ```graphql()```, ```compose()```, etc HOC's.

View file

@ -32,9 +32,16 @@ export default ComposedComponent => {
// and extract the resulting data
const apollo = initApollo()
try {
// create the url prop which is passed to every page
const url = {
query: ctx.query,
asPath: ctx.asPath,
pathname: ctx.pathname,
};
// Run all GraphQL queries
await getDataFromTree(
<ComposedComponent ctx={ctx} {...composedInitialProps} />,
<ComposedComponent ctx={ctx} url={url} {...composedInitialProps} />,
{
router: {
asPath: ctx.asPath,

View file

@ -0,0 +1,41 @@
[![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-google-analytics)
# Example app with analytics
## How to use
### Using `create-next-app`
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
```bash
npx create-next-app --example with-google-analytics with-google-analytics-app
# or
yarn create next-app --example with-google-analytics with-google-analytics-app
```
### Download manually
Download the example [or clone the repo](https://github.com/zeit/next.js):
```bash
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-google-analytics
cd with-google-analytics
```
Install it and run:
```bash
yarn
yarn dev
```
Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download))
```bash
now
```
## The idea behind the example
This example shows how to use [Next.js](https://github.com/zeit/next.js) along with [Google Analytics](https://developers.google.com/analytics/devguides/collection/gtagjs/). A custom [\_document](https://github.com/zeit/next.js/#custom-document) is used to inject [tracking snippet](https://developers.google.com/analytics/devguides/collection/gtagjs/) and track [pageviews](https://developers.google.com/analytics/devguides/collection/gtagjs/pages) and [event](https://developers.google.com/analytics/devguides/collection/gtagjs/events).

View file

@ -0,0 +1,26 @@
import React from 'react'
import Link from 'next/link'
export default () => (
<header>
<nav>
<ul>
<li>
<Link href='/'>
<a>Home</a>
</Link>
</li>
<li>
<Link href='/about'>
<a>About</a>
</Link>
</li>
<li>
<Link href='/contact'>
<a>Contact</a>
</Link>
</li>
</ul>
</nav>
</header>
)

View file

@ -0,0 +1,16 @@
import React from 'react'
import Router from 'next/router'
import Header from './Header'
import * as gtag from '../lib/gtag'
Router.onRouteChangeComplete = url => {
gtag.pageview(url)
}
export default ({ children }) => (
<div>
<Header />
{children}
</div>
)

View file

@ -0,0 +1,17 @@
export const GA_TRACKING_ID = '<YOUR_GA_TRACKING_ID>'
// https://developers.google.com/analytics/devguides/collection/gtagjs/pages
export const pageview = url => {
window.gtag('config', GA_TRACKING_ID, {
page_location: url,
})
}
// https://developers.google.com/analytics/devguides/collection/gtagjs/events
export const event = ({ action, category, label, value }) => {
window.gtag('event', action, {
event_category: category,
event_label: label,
value: value,
})
}

View file

@ -0,0 +1,13 @@
{
"name": "with-google-analytics",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "latest",
"react": "^16.2.0",
"react-dom": "^16.2.0"
}
}

View file

@ -0,0 +1,41 @@
import React from 'react'
import Document, { Head, Main, NextScript } from 'next/document'
import flush from 'styled-jsx/server'
import { GA_TRACKING_ID } from '../lib/gtag'
export default class extends Document {
static getInitialProps ({ renderPage }) {
const { html, head, errorHtml, chunks } = renderPage()
const styles = flush()
return { html, head, errorHtml, chunks, styles }
}
render () {
return (
<html>
<Head>
{/* Global Site Tag (gtag.js) - Google Analytics */}
<script
async
src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
/>
<script
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${GA_TRACKING_ID}');
`}}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</html>
)
}
}

View file

@ -0,0 +1,8 @@
import React from 'react'
import Page from '../components/Page'
export default () => (
<Page>
<h1>This is the About page</h1>
</Page>
)

View file

@ -0,0 +1,39 @@
import React, { Component } from 'react'
import Page from '../components/Page'
import * as gtag from '../lib/gtag'
export default class extends Component {
state = { message: '' }
handleInput = e => {
this.setState({ message: e.target.value })
}
handleSubmit = e => {
e.preventDefault()
gtag.event({
action: 'submit_form',
category: 'Contact',
label: this.state.message
})
this.setState({ message: '' })
}
render () {
return (
<Page>
<h1>This is the Contact page</h1>
<form onSubmit={this.handleSubmit}>
<label>
<span>Message:</span>
<textarea onInput={this.handleInput} value={this.state.message} />
</label>
<button type='submit'>submit</button>
</form>
</Page>
)
}
}

View file

@ -0,0 +1,8 @@
import React from 'react'
import Page from '../components/Page'
export default () => (
<Page>
<h1>This is the Home page</h1>
</Page>
)

View file

@ -0,0 +1,43 @@
[![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-now-env)
# Now-env example
## How to use
### Using `create-next-app`
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
```bash
npx create-next-app --example with-now-env with-now-env-app
# or
yarn create next-app --example with-now-env with-now-env-app
```
### Download manually
Download the example [or clone the repo](https://github.com/zeit/next.js):
```bash
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-now-env
cd with-now-env
```
Install it and run:
```bash
npm install
npm run dev
```
Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download))
```bash
now
```
keep in mind that in order to deploy the app to `now` the env [secrets](https://zeit.co/docs/getting-started/secrets) defined in `now.json` should be listed in your account
## The idea behind the example
This example shows the usage of [now-env](https://github.com/zeit/now-env), it allows to use secrets in development that will be replaced in production by the secrets defined with [now](https://zeit.co/docs/getting-started/secrets)

View file

@ -0,0 +1,27 @@
/**
* After the next require you can use process.env to get your secrets
*/
require('now-env')
console.log({
SECRET: process.env.SECRET,
ANOTHER_SECRET: process.env.ANOTHER_SECRET,
SECRET_FAIL: process.env.SECRET_FAIL
})
/**
* If some of the envs are public, like a google maps key, but you still
* want to keep them secret from the repo, the following code will allow you
* to share some variables with the client, configured at compile time.
*/
module.exports = {
webpack: config => {
config.plugins.forEach(plugin => {
if (plugin.constructor.name === 'DefinePlugin') {
plugin.definitions['process.env.SECRET'] = JSON.stringify(process.env.SECRET)
}
})
return config
}
}

View file

@ -0,0 +1,4 @@
{
"@my-secret-key": "keep-it-secret",
"@my-other-secret-key": "keep-it-secret-too"
}

View file

@ -0,0 +1,7 @@
{
"env": {
"SECRET": "@my-secret-key",
"ANOTHER_SECRET": "@my-other-secret-key",
"SECRET_FAIL": "@this-is-not-defined"
}
}

View file

@ -0,0 +1,17 @@
{
"name": "with-now-env",
"version": "1.0.0",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "latest",
"react": "^16.2.0",
"react-dom": "^16.2.0"
},
"devDependencies": {
"now-env": "^3.0.4"
}
}

View file

@ -0,0 +1,22 @@
export default () => (
<div className='hello'>
<p>
Hello World! Here's a secret shared with the client using webpack
DefinePlugin: <strong>{process.env.SECRET}</strong>, the secret is shared
at compile time, which means every reference to the secret is replaced
with its value
</p>
<style jsx>{`
.hello {
font: 15px Helvetica, Arial, sans-serif;
background: #eee;
padding: 100px;
text-align: center;
transition: 100ms ease-in background;
}
.hello:hover {
background: #ccc;
}
`}</style>
</div>
)

View file

@ -1,13 +1,11 @@
/* eslint no-extend-native: 0 */
// Add your polyfills
// This files runs at the very beginning (even before React and Next.js core)
console.log('Load your polyfills')
// core-js comes with Next.js. So, you can import it like below
import includes from 'core-js/library/fn/string/virtual/includes'
import repeat from 'core-js/library/fn/string/virtual/repeat'
// Add your polyfills
// This files runs at the very beginning (even before React and Next.js core)
console.log('Load your polyfills')
String.prototype.includes = includes
String.prototype.repeat = repeat

View file

@ -39,5 +39,5 @@ export default connect(
character: state.character,
error: state.error,
isFetchedOnServer: state.isFetchedOnServer
}),
})
)(CharacterInfo)

View file

@ -46,5 +46,5 @@ export default withRedux(
{
startFetchingCharacters: actions.startFetchingCharacters,
stopFetchingCharacters: actions.stopFetchingCharacters
},
}
)(Counter)

View file

@ -1,4 +1,4 @@
[![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-analytics)
[![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-segment-analytics)
# Example app with analytics
@ -9,9 +9,9 @@
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
```bash
npx create-next-app --example with-analytics with-analytics-app
npx create-next-app --example with-segment-analytics with-segment-analytics-app
# or
yarn create next-app --example with-analytics with-analytics-app
yarn create next-app --example with-segment-analytics with-segment-analytics-app
```
### Download manually
@ -19,8 +19,8 @@ yarn create next-app --example with-analytics with-analytics-app
Download the example [or clone the repo](https://github.com/zeit/next.js):
```bash
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-analytics
cd with-analytics
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-segment-analytics
cd with-segment-analytics
```
Install it and run:

View file

@ -1,5 +1,5 @@
{
"name": "with-analytics",
"name": "with-segment-analytics",
"scripts": {
"dev": "next",
"build": "next build",

View file

@ -15,7 +15,7 @@ module.exports = {
query: { id: post.id }
}
}),
{},
{}
)
// combine the map of post pages with the home

View file

@ -14,7 +14,7 @@
"devDependencies": {
"@types/next": "^2.4.7",
"@types/react": "^16.0.36",
"@zeit/next-typescript": "0.0.8",
"@zeit/next-typescript": "0.0.11",
"typescript": "^2.7.1"
}
}

View file

@ -15,7 +15,7 @@ export default class EventEmitter {
emit (event, ...data) {
if (!this.listeners[event]) return
this.listeners[event].forEach(cb => cb(...data))
this.listeners[event].forEach(cb => cb(...data)) // eslint-disable-line standard/no-callback-literal
}
off (event, cb) {

View file

@ -2,3 +2,4 @@ export const PHASE_EXPORT = 'phase-export'
export const PHASE_PRODUCTION_BUILD = 'phase-production-build'
export const PHASE_PRODUCTION_SERVER = 'phase-production-server'
export const PHASE_DEVELOPMENT_SERVER = 'phase-development-server'
export const PAGES_MANIFEST = 'pages-manifest.json'

View file

@ -355,7 +355,7 @@ export default class Router {
}
async fetchRoute (route) {
return await this.pageLoader.loadPage(route)
return this.pageLoader.loadPage(route)
}
abortComponentLoad (as) {

View file

@ -113,11 +113,12 @@
"@taskr/esnext": "1.1.0",
"@taskr/watch": "1.1.0",
"@zeit/next-css": "0.0.7",
"babel-eslint": "8.0.1",
"babel-eslint": "8.2.2",
"babel-jest": "21.2.0",
"babel-plugin-istanbul": "4.1.5",
"babel-plugin-transform-remove-strict-mode": "0.0.2",
"babel-preset-es2015": "6.24.1",
"babel-preset-flow": "6.23.0",
"benchmark": "2.1.4",
"cheerio": "0.22.0",
"chromedriver": "2.32.3",
@ -139,7 +140,7 @@
"react": "16.2.0",
"react-dom": "16.2.0",
"rimraf": "2.6.2",
"standard": "9.0.2",
"standard": "11.0.1",
"taskr": "1.1.0",
"wd": "1.4.1"
},

View file

@ -1012,7 +1012,7 @@ module.exports = {
Or use a function:
```js
module.exports = (phase, {defaultConfig}){
module.exports = (phase, {defaultConfig}) => {
//
// https://github.com/zeit/
return {
@ -1185,6 +1185,7 @@ module.exports = {
```js
// pages/index.js
import getConfig from 'next/config'
// Only holds serverRuntimeConfig and publicRuntimeConfig from next.config.js nothing else.
const {serverRuntimeConfig, publicRuntimeConfig} = getConfig()
console.log(serverRuntimeConfig.mySecret) // Will only be available on the server side
@ -1258,7 +1259,7 @@ Simply develop your app as you normally do with Next.js. Then create a custom Ne
```js
// next.config.js
module.exports = {
exportPathMap: function() {
exportPathMap: function(defaultPathMap) {
return {
'/': { page: '/' },
'/about': { page: '/about' },

View file

@ -0,0 +1,30 @@
// @flow
import { RawSource } from 'webpack-sources'
import { MATCH_ROUTE_NAME } from '../../utils'
import {PAGES_MANIFEST} from '../../../lib/constants'
// This plugin creates a pages-manifest.json from page entrypoints.
// This is used for mapping paths like `/` to `.next/dist/bundles/pages/index.js` when doing SSR
// It's also used by next export to provide defaultPathMap
export default class PagesManifestPlugin {
apply (compiler: any) {
compiler.plugin('emit', (compilation, callback) => {
const {entries} = compilation
const pages = {}
for (const entry of entries) {
const pagePath = MATCH_ROUTE_NAME.exec(entry.name)[1]
if (!pagePath) {
continue
}
const {name} = entry
pages[`/${pagePath.replace(/\\/g, '/')}`] = name
}
compilation.assets[PAGES_MANIFEST] = new RawSource(JSON.stringify(pages))
callback()
})
}
}

View file

@ -10,6 +10,7 @@ import PagesPlugin from './plugins/pages-plugin'
import NextJsSsrImportPlugin from './plugins/nextjs-ssr-import'
import DynamicChunksPlugin from './plugins/dynamic-chunks-plugin'
import UnlinkFilePlugin from './plugins/unlink-file-plugin'
import PagesManifestPlugin from './plugins/pages-manifest-plugin'
import findBabelConfig from './babel/find-config'
const nextDir = path.join(__dirname, '..', '..', '..')
@ -254,6 +255,7 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer
'process.env.NODE_ENV': JSON.stringify(dev ? 'development' : 'production')
}),
!dev && new webpack.optimize.ModuleConcatenationPlugin(),
isServer && new PagesManifestPlugin(),
!isServer && new PagesPlugin(),
!isServer && new DynamicChunksPlugin(),
isServer && new NextJsSsrImportPlugin(),

View file

@ -9,7 +9,7 @@ export async function getPages (dir, {dev, isServer, pageExtensions}) {
return getPageEntries(pageFiles, {isServer, pageExtensions})
}
async function getPagePaths (dir, {dev, isServer, pageExtensions}) {
export async function getPagePaths (dir, {dev, isServer, pageExtensions}) {
let pages
if (dev) {

View file

@ -1,3 +1,4 @@
// @flow
import findUp from 'find-up'
const cache = new Map()
@ -11,28 +12,29 @@ const defaultConfig = {
configOrigin: 'default',
useFileSystemPublicRoutes: true,
generateEtags: true,
pageExtensions: ['jsx', 'js'] // jsx before js because otherwise regex matching will match js first
pageExtensions: ['jsx', 'js']
}
export default function getConfig (phase, dir, customConfig) {
export default function getConfig (phase: string, dir: string, customConfig?: ?Object) {
if (!cache.has(dir)) {
cache.set(dir, loadConfig(phase, dir, customConfig))
}
return cache.get(dir)
}
export function loadConfig (phase, dir, customConfig) {
export function loadConfig (phase: string, dir: string, customConfig?: ?Object) {
if (customConfig && typeof customConfig === 'object') {
customConfig.configOrigin = 'server'
return withDefaults(customConfig)
}
const path = findUp.sync('next.config.js', {
const path: string = findUp.sync('next.config.js', {
cwd: dir
})
let userConfig = {}
if (path && path.length) {
// $FlowFixMe
const userConfigModule = require(path)
userConfig = userConfigModule.default || userConfigModule
if (typeof userConfigModule === 'function') {
@ -44,6 +46,6 @@ export function loadConfig (phase, dir, customConfig) {
return withDefaults(userConfig)
}
function withDefaults (config) {
function withDefaults (config: Object) {
return Object.assign({}, defaultConfig, config)
}

View file

@ -5,10 +5,9 @@ import walk from 'walk'
import { extname, resolve, join, dirname, sep } from 'path'
import { existsSync, readFileSync, writeFileSync } from 'fs'
import getConfig from './config'
import {PHASE_EXPORT} from '../lib/constants'
import {PHASE_EXPORT, PAGES_MANIFEST} from '../lib/constants'
import { renderToHTML } from './render'
import { getAvailableChunks } from './utils'
import { printAndExit } from '../lib/utils'
import { setAssetPrefix } from '../lib/asset'
import * as envConfig from '../lib/runtime-config'
@ -17,7 +16,7 @@ export default async function (dir, options, configuration) {
const nextConfig = configuration || getConfig(PHASE_EXPORT, dir)
const nextDir = join(dir, nextConfig.distDir)
log(` using build directory: ${nextDir}`)
log(`> using build directory: ${nextDir}`)
if (!existsSync(nextDir)) {
console.error(
@ -27,6 +26,17 @@ export default async function (dir, options, configuration) {
}
const buildId = readFileSync(join(nextDir, 'BUILD_ID'), 'utf8')
const pagesManifest = require(join(nextDir, 'dist', PAGES_MANIFEST))
const pages = Object.keys(pagesManifest)
const defaultPathMap = {}
for (const page of pages) {
if (page === '/_document') {
continue
}
defaultPathMap[page] = { page }
}
// Initialize the output directory
const outDir = options.outdir
@ -73,13 +83,13 @@ export default async function (dir, options, configuration) {
// Get the exportPathMap from the `next.config.js`
if (typeof nextConfig.exportPathMap !== 'function') {
printAndExit(
'> Could not find "exportPathMap" function inside "next.config.js"\n' +
'> "next export" uses that function to build html pages.'
)
console.log('> No "exportPathMap" found in "next.config.js". Generating map from "./pages"')
nextConfig.exportPathMap = async (defaultMap) => {
return defaultMap
}
}
const exportPathMap = await nextConfig.exportPathMap()
const exportPathMap = await nextConfig.exportPathMap(defaultPathMap)
const exportPaths = Object.keys(exportPathMap)
// Start the rendering process
@ -115,7 +125,7 @@ export default async function (dir, options, configuration) {
}
for (const path of exportPaths) {
log(` exporting path: ${path}`)
log(`> exporting path: ${path}`)
if (!path.startsWith('/')) {
throw new Error(`path "${path}" doesn't start with a backslash`)
}

View file

@ -1,3 +1,4 @@
/* eslint-disable import/first, no-return-await */
require('@zeit/source-map-support').install()
import { resolve, join, sep } from 'path'
import { parse as parseUrl } from 'url'

View file

@ -1,5 +1,5 @@
import {join, parse, normalize, sep} from 'path'
import fs from 'mz/fs'
import {join, posix} from 'path'
import {PAGES_MANIFEST} from '../lib/constants'
export function pageNotFoundError (page) {
const err = new Error(`Cannot find module for page: ${page}`)
@ -18,13 +18,8 @@ export function normalizePagePath (page) {
page = `/${page}`
}
// Windows compatibility
if (sep !== '/') {
page = page.replace(/\//g, sep)
}
// Throw when using ../ etc in the pathname
const resolvedPage = normalize(page)
const resolvedPage = posix.normalize(page)
if (page !== resolvedPage) {
throw new Error('Requested and resolved page mismatch')
}
@ -33,7 +28,8 @@ export function normalizePagePath (page) {
}
export function getPagePath (page, {dir, dist}) {
const pageBundlesPath = join(dir, dist, 'dist', 'bundles', 'pages')
const serverBuildPath = join(dir, dist, 'dist')
const pagesManifest = require(join(serverBuildPath, PAGES_MANIFEST))
try {
page = normalizePagePath(page)
@ -42,24 +38,14 @@ export function getPagePath (page, {dir, dist}) {
throw pageNotFoundError(page)
}
const pagePath = join(pageBundlesPath, page) // Path to the page that is to be loaded
// Don't allow wandering outside of the bundles directory
const pathDir = parse(pagePath).dir
if (pathDir.indexOf(pageBundlesPath) !== 0) {
console.error('Resolved page path goes outside of bundles path')
if (!pagesManifest[page]) {
throw pageNotFoundError(page)
}
return pagePath
return join(serverBuildPath, pagesManifest[page])
}
export default async function requirePage (page, {dir, dist}) {
const pagePath = getPagePath(page, {dir, dist}) + '.js'
const fileExists = await fs.exists(pagePath)
if (!fileExists) {
throw pageNotFoundError(page)
}
const pagePath = getPagePath(page, {dir, dist})
return require(pagePath)
}

View file

@ -2,7 +2,7 @@ import React from 'react'
export default class AsyncProps extends React.Component {
static async getInitialProps () {
return await fetchData()
return fetchData()
}
render () {

View file

@ -0,0 +1,3 @@
module.exports = {
test: 'error'
}

View file

@ -0,0 +1,6 @@
{
"/index": "bundles/pages/index.js",
"/world": "bundles/pages/world.js",
"/_error": "bundles/pages/_error.js",
"/non-existent-child": "bundles/pages/non-existent-child.js"
}

View file

@ -1,12 +1,10 @@
/* global describe, it, expect */
import { join, sep } from 'path'
import { join } from 'path'
import requirePage, {getPagePath, normalizePagePath, pageNotFoundError} from '../../dist/server/require'
const dir = '/path/to/some/project'
const dist = '.next'
const pathToBundles = join(dir, dist, 'dist', 'bundles', 'pages')
const sep = '/'
const pathToBundles = join(__dirname, '_resolvedata', 'dist', 'bundles', 'pages')
describe('pageNotFoundError', () => {
it('Should throw error with ENOENT code', () => {
@ -42,17 +40,17 @@ describe('normalizePagePath', () => {
describe('getPagePath', () => {
it('Should append /index to the / page', () => {
const pagePath = getPagePath('/', {dir, dist})
expect(pagePath).toBe(join(pathToBundles, `${sep}index`))
const pagePath = getPagePath('/', {dir: __dirname, dist: '_resolvedata'})
expect(pagePath).toBe(join(pathToBundles, `${sep}index.js`))
})
it('Should prepend / when a page does not have it', () => {
const pagePath = getPagePath('_error', {dir, dist})
expect(pagePath).toBe(join(pathToBundles, `${sep}_error`))
const pagePath = getPagePath('_error', {dir: __dirname, dist: '_resolvedata'})
expect(pagePath).toBe(join(pathToBundles, `${sep}_error.js`))
})
it('Should throw with paths containing ../', () => {
expect(() => getPagePath('/../../package.json', {dir, dist})).toThrow()
expect(() => getPagePath('/../../package.json', {dir: __dirname, dist: '_resolvedata'})).toThrow()
})
})

1574
yarn.lock

File diff suppressed because it is too large Load diff