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

Load chunks in SSR mode only if they exists in the filesystem (#2196)

* Always check with the fs when gettings chunks.

* Add a new set of test cases for dynamic imports in dev.

* Add dynamic import test cases for production.

* Add availableChunks support for static exports.
This commit is contained in:
Arunoda Susiripala 2017-06-08 23:11:22 +05:30 committed by Tim Neutkens
parent 9c1898f2c8
commit 0c4362e440
18 changed files with 208 additions and 20 deletions

View file

@ -6,12 +6,13 @@ import { resolve, join, dirname, sep } from 'path'
import { existsSync, readFileSync, writeFileSync } from 'fs'
import getConfig from './config'
import { renderToHTML } from './render'
import { getAvailableChunks } from './utils'
import { printAndExit } from '../lib/utils'
export default async function (dir, options) {
dir = resolve(dir)
const config = getConfig(dir)
const nextDir = join(dir, config.distDir || '.next')
const nextDir = join(dir, config.distDir)
log(` using build directory: ${nextDir}`)
@ -79,7 +80,8 @@ export default async function (dir, options) {
assetPrefix: config.assetPrefix.replace(/\/$/, ''),
dev: false,
staticMarkup: false,
hotReloader: null
hotReloader: null,
availableChunks: getAvailableChunks(dir, config.distDir)
}
// We need this for server rendering the Link component.

View file

@ -14,6 +14,7 @@ import {
import Router from './router'
import HotReloader from './hot-reloader'
import { resolveFromList } from './resolve'
import { getAvailableChunks } from './utils'
import getConfig from './config'
// We need to go up one more level since we are in the `dist` directory
import pkg from '../../package'
@ -42,7 +43,8 @@ export default class Server {
hotReloader: this.hotReloader,
buildStats: this.buildStats,
buildId: this.buildId,
assetPrefix: this.config.assetPrefix.replace(/\/$/, '')
assetPrefix: this.config.assetPrefix.replace(/\/$/, ''),
availableChunks: dev ? {} : getAvailableChunks(this.dir, this.dist)
}
this.defineRoutes()

View file

@ -1,4 +1,5 @@
import { join } from 'path'
import { existsSync } from 'fs'
import { createElement } from 'react'
import { renderToString, renderToStaticMarkup } from 'react-dom/server'
import send from 'send'
@ -40,6 +41,7 @@ async function doRender (req, res, pathname, query, {
buildStats,
hotReloader,
assetPrefix,
availableChunks,
dir = process.cwd(),
dev = false,
staticMarkup = false,
@ -81,7 +83,7 @@ async function doRender (req, res, pathname, query, {
} finally {
head = Head.rewind() || defaultHead()
}
const chunks = flushChunks()
const chunks = loadChunks({ dev, dir, dist, availableChunks })
if (err && dev) {
errorHtml = render(createElement(ErrorDebug, { error: err }))
@ -242,3 +244,18 @@ async function ensurePage (page, { dir, hotReloader }) {
await hotReloader.ensurePage(page)
}
function loadChunks ({ dev, dir, dist, availableChunks }) {
const flushedChunks = flushChunks()
const validChunks = []
for (var chunk of flushedChunks) {
const filename = join(dir, dist, 'chunks', chunk)
const exists = dev ? existsSync(filename) : availableChunks[chunk]
if (exists) {
validChunks.push(chunk)
}
}
return validChunks
}

View file

@ -1,2 +1,21 @@
import { join } from 'path'
import { readdirSync, existsSync } from 'fs'
export const IS_BUNDLED_PAGE = /^bundles[/\\]pages.*\.js$/
export const MATCH_ROUTE_NAME = /^bundles[/\\]pages[/\\](.*)\.js$/
export function getAvailableChunks (dir, dist) {
const chunksDir = join(dir, dist, 'chunks')
if (!existsSync(chunksDir)) return {}
const chunksMap = {}
const chunkFiles = readdirSync(chunksDir)
chunkFiles.forEach(filename => {
if (/\.js$/.test(filename)) {
chunksMap[filename] = true
}
})
return chunksMap
}

View file

@ -0,0 +1,19 @@
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>
)
}
}

View file

@ -0,0 +1,7 @@
import Link from 'next/link'
export default () => (
<div>
<Link href='/dynamic/no-chunk'><a>No Chunk</a></Link>
</div>
)

View file

@ -0,0 +1,11 @@
import dynamic from 'next/dynamic'
import Welcome from '../../components/welcome'
const Welcome2 = dynamic(import('../../components/welcome'))
export default () => (
<div>
<Welcome name='normal' />
<Welcome2 name='dynamic' />
</div>
)

View file

@ -0,0 +1,61 @@
/* global describe, it, expect */
import webdriver from 'next-webdriver'
import cheerio from 'cheerio'
import { waitFor } from 'next-test-utils'
export default (context, render) => {
describe('Dynamic import', () => {
describe('with SSR', () => {
async function get$ (path, query) {
const html = await render(path, query)
return cheerio.load(html)
}
it('should render dynmaic import components', async () => {
const $ = await get$('/dynamic/ssr')
expect($('p').text()).toBe('Hello World 1')
})
it('should stop render dynmaic import components', async () => {
const $ = await get$('/dynamic/no-ssr')
expect($('p').text()).toBe('loading...')
})
it('should stop render dynmaic import components with custom loading', async () => {
const $ = await get$('/dynamic/no-ssr-custom-loading')
expect($('p').text()).toBe('LOADING')
})
})
describe('with browser', () => {
it('should render the page client side', async () => {
const browser = await webdriver(context.appPort, '/dynamic/no-ssr-custom-loading')
while (true) {
const bodyText = await browser
.elementByCss('body').text()
if (/Hello World 1/.test(bodyText)) break
await waitFor(1000)
}
browser.close()
})
it('should render even there are no physical chunk exists', async () => {
const browser = await webdriver(context.appPort, '/dynamic/no-chunk')
while (true) {
const bodyText = await browser
.elementByCss('body').text()
if (
/Welcome, normal/.test(bodyText) &&
/Welcome, dynamic/.test(bodyText)
) break
await waitFor(1000)
}
browser.close()
})
})
})
}

View file

@ -15,6 +15,7 @@ import rendering from './rendering'
import misc from './misc'
import clientNavigation from './client-navigation'
import hmr from './hmr'
import dynamic from './dynamic'
const context = {}
context.app = nextServer({
@ -64,5 +65,6 @@ describe('Basic Features', () => {
xPoweredBy(context)
misc(context)
clientNavigation(context, (p, q) => renderViaHTTP(context.appPort, p, q))
dynamic(context, (p, q) => renderViaHTTP(context.appPort, p, q))
hmr(context, (p, q) => renderViaHTTP(context.appPort, p, q))
})

View file

@ -79,20 +79,5 @@ export default function ({ app }, suiteName, render) {
expect($('h1').text()).toBe('404')
expect($('h2').text()).toBe('This page could not be found.')
})
test('render dynmaic import components via SSR', async () => {
const $ = await get$('/dynamic/ssr')
expect($('p').text()).toBe('Hello World 1')
})
test('stop render dynmaic import components in SSR', async () => {
const $ = await get$('/dynamic/no-ssr')
expect($('p').text()).toBe('loading...')
})
test('stop render dynmaic import components in SSR with custom loading', async () => {
const $ = await get$('/dynamic/no-ssr-custom-loading')
expect($('p').text()).toBe('LOADING')
})
})
}

View file

@ -0,0 +1,3 @@
export default () => (
<p>Hello World 1</p>
)

View file

@ -0,0 +1,19 @@
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>
)
}
}

View file

@ -0,0 +1,7 @@
import Link from 'next/link'
export default () => (
<div>
<Link href='/dynamic/no-chunk'><a>No Chunk</a></Link>
</div>
)

View file

@ -0,0 +1,11 @@
import dynamic from 'next/dynamic'
import Welcome from '../../components/welcome'
const Welcome2 = dynamic(import('../../components/welcome'))
export default () => (
<div>
<Welcome name='normal' />
<Welcome2 name='dynamic' />
</div>
)

View file

@ -0,0 +1,8 @@
import dynamic from 'next/dynamic'
const Hello = dynamic(import('../../components/hello1'), {
ssr: false,
loading: () => (<p>LOADING</p>)
})
export default Hello

View file

@ -0,0 +1,5 @@
import dynamic from 'next/dynamic'
const Hello = dynamic(import('../../components/hello1'), { ssr: false })
export default Hello

View file

@ -0,0 +1,5 @@
import dynamic from 'next/dynamic'
const Hello = dynamic(import('../../components/hello1'))
export default Hello

View file

@ -11,6 +11,7 @@ import {
} from 'next-test-utils'
import webdriver from 'next-webdriver'
import fetch from 'node-fetch'
import dynamicImportTests from '../../basic/test/dynamic'
const appDir = join(__dirname, '../')
let appPort
@ -18,6 +19,8 @@ let server
let app
jasmine.DEFAULT_TIMEOUT_INTERVAL = 40000
const context = {}
describe('Production Usage', () => {
beforeAll(async () => {
await nextBuild(appDir)
@ -28,7 +31,7 @@ describe('Production Usage', () => {
})
server = await startApp(app)
appPort = server.address().port
context.appPort = appPort = server.address().port
})
afterAll(() => stopApp(server))
@ -77,4 +80,6 @@ describe('Production Usage', () => {
browser.close()
})
})
dynamicImportTests(context, (p, q) => renderViaHTTP(context.appPort, p, q))
})