Skip to content

Commit

Permalink
Add basePath support (#9872)
Browse files Browse the repository at this point in the history
* Add basePath support

* Add tests including copy of HMR tests

* Add production tests

* Add tests for serverless target

* Add missing quotes
  • Loading branch information
timneutkens authored Dec 29, 2019
1 parent 2d0ad7d commit 86808bb
Show file tree
Hide file tree
Showing 36 changed files with 824 additions and 5 deletions.
1 change: 1 addition & 0 deletions packages/next/build/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export function createEntrypoints(
generateEtags: config.generateEtags,
ampBindInitData: config.experimental.ampBindInitData,
canonicalBase: config.canonicalBase,
basePath: config.experimental.basePath,
}

Object.keys(pages).forEach(page => {
Expand Down
3 changes: 3 additions & 0 deletions packages/next/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,9 @@ export default async function getBaseWebpackConfig(
'process.env.__NEXT_REACT_MODE': JSON.stringify(
config.experimental.reactMode
),
'process.env.__NEXT_ROUTER_BASEPATH': JSON.stringify(
config.experimental.basePath
),
...(isServer
? {
// Fix bad-actors in the npm ecosystem (e.g. `node-formidable`)
Expand Down
22 changes: 22 additions & 0 deletions packages/next/build/webpack/loaders/next-serverless-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type ServerlessLoaderQuery = {
ampBindInitData: boolean | string
generateEtags: string
canonicalBase: string
basePath: string
}

const nextServerlessLoader: loader.Loader = function() {
Expand All @@ -36,8 +37,10 @@ const nextServerlessLoader: loader.Loader = function() {
absoluteDocumentPath,
absoluteErrorPath,
generateEtags,
basePath,
}: ServerlessLoaderQuery =
typeof this.query === 'string' ? parse(this.query.substr(1)) : this.query

const buildManifest = join(distDir, BUILD_MANIFEST).replace(/\\/g, '/')
const reactLoadableManifest = join(distDir, REACT_LOADABLE_MANIFEST).replace(
/\\/g,
Expand Down Expand Up @@ -128,6 +131,16 @@ const nextServerlessLoader: loader.Loader = function() {
export default async (req, res) => {
try {
await initServer()
${
basePath
? `
if(req.url.startsWith('${basePath}')) {
req.url = req.url.replace('${basePath}', '')
}
`
: ''
}
const parsedUrl = parse(req.url, true)
const params = ${
Expand Down Expand Up @@ -181,6 +194,15 @@ const nextServerlessLoader: loader.Loader = function() {
export const config = ComponentInfo['confi' + 'g'] || {}
export const _app = App
export async function renderReqToHTML(req, res, fromExport, _renderOpts, _params) {
${
basePath
? `
if(req.url.startsWith('${basePath}')) {
req.url = req.url.replace('${basePath}', '')
}
`
: ''
}
const options = {
App,
Document,
Expand Down
10 changes: 8 additions & 2 deletions packages/next/next-server/lib/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ import { isDynamicRoute } from './utils/is-dynamic'
import { getRouteMatcher } from './utils/route-matcher'
import { getRouteRegex } from './utils/route-regex'

function addBasePath(path: string): string {
// @ts-ignore variable is always a string
const p: string = process.env.__NEXT_ROUTER_BASEPATH
return path.indexOf(p) !== 0 ? p + path : path
}

function toRoute(path: string): string {
return path.replace(/\/$/, '') || '/'
}
Expand Down Expand Up @@ -284,7 +290,7 @@ export default class Router implements BaseRouter {
if (!options._h && this.onlyAHashChange(as)) {
this.asPath = as
Router.events.emit('hashChangeStart', as)
this.changeState(method, url, as)
this.changeState(method, url, addBasePath(as))
this.scrollToHash(as)
Router.events.emit('hashChangeComplete', as)
return resolve(true)
Expand Down Expand Up @@ -346,7 +352,7 @@ export default class Router implements BaseRouter {
}

Router.events.emit('beforeHistoryChange', as)
this.changeState(method, url, as, options)
this.changeState(method, url, addBasePath(as), options)
const hash = window.location.hash.substring(1)

if (process.env.NODE_ENV !== 'production') {
Expand Down
42 changes: 39 additions & 3 deletions packages/next/next-server/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const defaultConfig: { [key: string]: any } = {
deferScripts: false,
reactMode: 'legacy',
workerThreads: false,
basePath: '',
},
future: {
excludeDefaultMomentLocales: false,
Expand Down Expand Up @@ -106,9 +107,44 @@ function assignDefaults(userConfig: { [key: string]: any }) {
`Specified assetPrefix is not a string, found type "${typeof result.assetPrefix}" https://err.sh/zeit/next.js/invalid-assetprefix`
)
}
if (result.experimental && result.experimental.css) {
// The new CSS support requires granular chunks be enabled.
result.experimental.granularChunks = true
if (result.experimental) {
if (result.experimental.css) {
// The new CSS support requires granular chunks be enabled.
result.experimental.granularChunks = true
}

if (typeof result.experimental.basePath !== 'string') {
throw new Error(
`Specified basePath is not a string, found type "${typeof result
.experimental.basePath}"`
)
}

if (result.experimental.basePath !== '') {
if (result.experimental.basePath === '/') {
throw new Error(
`Specified basePath /. basePath has to be either an empty string or a path prefix"`
)
}

if (!result.experimental.basePath.startsWith('/')) {
throw new Error(
`Specified basePath has to start with a /, found "${result.experimental.basePath}"`
)
}

if (result.experimental.basePath !== '/') {
if (result.experimental.basePath.endsWith('/')) {
throw new Error(
`Specified basePath should not end with /, found "${result.experimental.basePath}"`
)
}

if (result.assetPrefix === '') {
result.assetPrefix = result.experimental.basePath
}
}
}
}
return result
}
Expand Down
10 changes: 10 additions & 0 deletions packages/next/next-server/server/next-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,16 @@ export default class Server {
parsedUrl.query = parseQs(parsedUrl.query)
}

if (parsedUrl.pathname!.startsWith(this.nextConfig.experimental.basePath)) {
// If replace ends up replacing the full url it'll be `undefined`, meaning we have to default it to `/`
parsedUrl.pathname =
parsedUrl.pathname!.replace(
this.nextConfig.experimental.basePath,
''
) || '/'
req.url = req.url!.replace(this.nextConfig.experimental.basePath, '')
}

res.statusCode = 200
return this.run(req, res, parsedUrl).catch(err => {
this.logError(err)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default () => <div>test chunkfilename</div>
13 changes: 13 additions & 0 deletions test/integration/basepath/components/hello-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react'
import PropTypes from 'prop-types'

export default class extends React.Component {
static contextTypes = {
data: PropTypes.object,
}

render() {
const { data } = this.context
return <div>{data.title}</div>
}
}
1 change: 1 addition & 0 deletions test/integration/basepath/components/hello1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default () => <p>Hello World 1</p>
1 change: 1 addition & 0 deletions test/integration/basepath/components/hello2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default () => <p>Hello World 2</p>
1 change: 1 addition & 0 deletions test/integration/basepath/components/hello3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default () => <p>Hello World 1</p>
1 change: 1 addition & 0 deletions test/integration/basepath/components/hello4.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default () => <p>Hello World 2</p>
12 changes: 12 additions & 0 deletions test/integration/basepath/components/hmr/dynamic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default () => {
return (
<div id="dynamic-component">
Dynamic Component
<style jsx>{`
div {
font-size: 100px;
}
`}</style>
</div>
)
}
10 changes: 10 additions & 0 deletions test/integration/basepath/components/nested1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import dynamic from 'next/dynamic'

const Nested2 = dynamic(() => import('./nested2'))

export default () => (
<div>
Nested 1
<Nested2 />
</div>
)
12 changes: 12 additions & 0 deletions test/integration/basepath/components/nested2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import dynamic from 'next/dynamic'

const BrowserLoaded = dynamic(async () => () => <div>Browser hydrated</div>, {
ssr: false,
})

export default () => (
<div>
<div>Nested 2</div>
<BrowserLoaded />
</div>
)
17 changes: 17 additions & 0 deletions test/integration/basepath/components/welcome.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react'

export default class Welcome extends React.Component {
state = { name: null }

componentDidMount() {
const { name } = this.props
this.setState({ name })
}

render() {
const { name } = this.state
if (!name) return null

return <p>Welcome, {name}</p>
}
}
9 changes: 9 additions & 0 deletions test/integration/basepath/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
onDemandEntries: {
// Make sure entries are not getting disposed.
maxInactiveAge: 1000 * 60 * 60,
},
experimental: {
basePath: '/docs',
},
}
9 changes: 9 additions & 0 deletions test/integration/basepath/pages/hello.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Link from 'next/link'

export default () => (
<Link href="/other-page">
<a id="other-page-link">
<h1>Hello World</h1>
</a>
</Link>
)
7 changes: 7 additions & 0 deletions test/integration/basepath/pages/hmr/about.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default () => {
return (
<div className="hmr-about-page">
<p>This is the about page.</p>
</div>
)
}
7 changes: 7 additions & 0 deletions test/integration/basepath/pages/hmr/about1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default () => {
return (
<div className="hmr-about-page">
<p>This is the about page.</p>
</div>
)
}
7 changes: 7 additions & 0 deletions test/integration/basepath/pages/hmr/about2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default () => {
return (
<div className="hmr-about-page">
<p>This is the about page.</p>
</div>
)
}
7 changes: 7 additions & 0 deletions test/integration/basepath/pages/hmr/about3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default () => {
return (
<div className="hmr-about-page">
<p>This is the about page.</p>
</div>
)
}
7 changes: 7 additions & 0 deletions test/integration/basepath/pages/hmr/about4.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default () => {
return (
<div className="hmr-about-page">
<p>This is the about page.</p>
</div>
)
}
7 changes: 7 additions & 0 deletions test/integration/basepath/pages/hmr/about5.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default () => {
return (
<div className="hmr-about-page">
<p>This is the about page.</p>
</div>
)
}
7 changes: 7 additions & 0 deletions test/integration/basepath/pages/hmr/about6.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default () => {
return (
<div className="hmr-about-page">
<p>This is the about page.</p>
</div>
)
}
7 changes: 7 additions & 0 deletions test/integration/basepath/pages/hmr/about7.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default () => {
return (
<div className="hmr-about-page">
<p>This is the about page.</p>
</div>
)
}
5 changes: 5 additions & 0 deletions test/integration/basepath/pages/hmr/contact.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default () => (
<div className="hmr-contact-page">
<p>This is the contact page.</p>
</div>
)
19 changes: 19 additions & 0 deletions test/integration/basepath/pages/hmr/counter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react'

export default class Counter extends React.Component {
state = { count: 0 }

incr() {
const { count } = this.state
this.setState({ count: count + 1 })
}

render() {
return (
<div>
<p>COUNT: {this.state.count}</p>
<button onClick={() => this.incr()}>Increment</button>
</div>
)
}
}
11 changes: 11 additions & 0 deletions test/integration/basepath/pages/hmr/error-in-gip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react'
export default class extends React.Component {
static getInitialProps() {
const error = new Error('an-expected-error-in-gip')
throw error
}

render() {
return <div>Hello</div>
}
}
9 changes: 9 additions & 0 deletions test/integration/basepath/pages/hmr/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Link from 'next/link'

export default () => (
<div>
<Link href="/hmr/error-in-gip">
<a id="error-in-gip-link">Bad Page</a>
</Link>
</div>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react'
import dynamic from 'next/dynamic'

const HmrDynamic = dynamic(import('../../components/hmr/dynamic'))

export default () => {
return <HmrDynamic />
}
Loading

0 comments on commit 86808bb

Please sign in to comment.