Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add link to docs for prerender indicator and allow disabling #8610

Merged
merged 7 commits into from
Sep 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions packages/next/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2155,6 +2155,20 @@ There is no configuration or special handling required.
> **Note**: If you have a [custom `<Document>`](#custom-document) with `getInitialProps` be sure you check if `ctx.req` is defined before assuming the page is server-side rendered.
> `ctx.req` will be `undefined` for pages that are prerendered.

## Automatic Prerender Indicator

When a page qualifies for automatic prerendering we show an indicator to let you know. This is helpful since the automatic prerendering optimization can be very beneficial and knowing immediately in development if it qualifies can be useful. See above for information on the benefits of this optimization.

In some cases this indicator might not be as useful like when working on electron applications. For these cases you can disable the indicator in your `next.config.js` by setting

```js
module.exports = {
devIndicators: {
autoPrerender: false
}
}
```

## 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:
Expand Down
15 changes: 12 additions & 3 deletions packages/next/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -514,9 +514,18 @@ export default async function getBaseWebpackConfig(
'process.env.__NEXT_EXPORT_TRAILING_SLASH': JSON.stringify(
config.exportTrailingSlash
),
'process.env.__NEXT_MODERN_BUILD': config.experimental.modern && !dev,
'process.env.__NEXT_GRANULAR_CHUNKS':
config.experimental.granularChunks && !dev,
'process.env.__NEXT_MODERN_BUILD': JSON.stringify(
config.experimental.modern && !dev
),
'process.env.__NEXT_GRANULAR_CHUNKS': JSON.stringify(
config.experimental.granularChunks && !dev
),
'process.env.__NEXT_BUILD_INDICATOR': JSON.stringify(
config.devIndicators.buildActivity
),
'process.env.__NEXT_PRERENDER_INDICATOR': JSON.stringify(
config.devIndicators.autoPrerender
),
...(isServer
? {
// Fix bad-actors in the npm ecosystem (e.g. `node-formidable`)
Expand Down
99 changes: 74 additions & 25 deletions packages/next/client/dev/prerender-indicator.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ export default function initializeBuildWatcher () {
shadowHost.style.height = 0
shadowHost.style.zIndex = 99998
shadowHost.style.transition = 'all 100ms ease'
shadowHost.title = 'Click to hide for page'

document.body.appendChild(shadowHost)

Expand All @@ -36,8 +35,15 @@ export default function initializeBuildWatcher () {
const css = createCss(prefix)
shadowRoot.appendChild(css)

const expandEl = container.querySelector('a')
const closeEl = container.querySelector(`#${prefix}close`)

// State
let isVisible = window.__NEXT_DATA__.nextExport
const dismissKey = '__NEXT_DISMISS_PRERENDER_INDICATOR'
const dismissUntil = parseInt(window.localStorage.getItem(dismissKey), 10)
const dismissed = dismissUntil > new Date().getTime()

let isVisible = !dismissed && window.__NEXT_DATA__.nextExport

function updateContainer () {
if (isVisible) {
Expand All @@ -46,21 +52,36 @@ export default function initializeBuildWatcher () {
container.classList.remove(`${prefix}visible`)
}
}
const expandedClass = `${prefix}expanded`
let toggleTimeout

const toggleExpand = (expand = true) => {
clearTimeout(toggleTimeout)

toggleTimeout = setTimeout(() => {
if (expand) {
expandEl.classList.add(expandedClass)
closeEl.style.display = 'flex'
} else {
expandEl.classList.remove(expandedClass)
closeEl.style.display = 'none'
}
}, 50)
}

shadowHost.addEventListener('click', () => {
shadowHost.style.opacity = 0
shadowHost.style.pointerEvents = 'none'
})
shadowHost.addEventListener('mouseenter', () => {
container.classList.add(`${prefix}expanded`)
})
shadowHost.addEventListener('mouseleave', () => {
container.classList.remove(`${prefix}expanded`)
closeEl.addEventListener('click', () => {
const oneHourAway = new Date().getTime() + 1 * 60 * 60 * 1000
window.localStorage.setItem(dismissKey, oneHourAway + '')
isVisible = false
updateContainer()
})
closeEl.addEventListener('mouseenter', () => toggleExpand())
closeEl.addEventListener('mouseleave', () => toggleExpand(false))
expandEl.addEventListener('mouseenter', () => toggleExpand())
expandEl.addEventListener('mouseleave', () => toggleExpand(false))

Router.events.on('routeChangeComplete', () => {
isVisible = window.next.isPrerendered
shadowHost.style.opacity = 1
updateContainer()
})
updateContainer()
Expand All @@ -70,16 +91,20 @@ function createContainer (prefix) {
const container = document.createElement('div')
container.id = `${prefix}container`
container.innerHTML = `
<div id="${prefix}icon-wrapper">
<svg width="15" height="20" viewBox="0 0 60 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M36 3L30.74 41H8L36 3Z" fill="black"/>
<path d="M25 77L30.26 39H53L25 77Z" fill="black"/>
<path d="M13.5 33.5L53 39L47.5 46.5L7 41.25L13.5 33.5Z" fill="black"/>
</svg>
Prerendered Page
</div>
<button id="${prefix}close" title="Hide indicator for session">
<span>×</span>
</button>
<a href="https://nextjs.org/docs#automatic-prerender-indicator" target="_blank">
<div id="${prefix}icon-wrapper">
<svg width="15" height="20" viewBox="0 0 60 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M36 3L30.74 41H8L36 3Z" fill="black"/>
<path d="M25 77L30.26 39H53L25 77Z" fill="black"/>
<path d="M13.5 33.5L53 39L47.5 46.5L7 41.25L13.5 33.5Z" fill="black"/>
</svg>
Prerendered Page
</div>
</a>
`

return container
}

Expand All @@ -88,8 +113,32 @@ function createCss (prefix) {
css.textContent = `
#${prefix}container {
position: absolute;
display: none;
bottom: 10px;
right: 15px;
}

#${prefix}close {
top: -10px;
right: -10px;
border: none;
width: 18px;
height: 18px;
color: #333333;
font-size: 16px;
cursor: pointer;
display: none;
position: absolute;
background: #ffffff;
border-radius: 100%;
align-items: center;
flex-direction: column;
justify-content: center;
}

#${prefix}container a {
color: inherit;
text-decoration: none;
width: 15px;
height: 23px;
overflow: hidden;
Expand All @@ -110,8 +159,7 @@ function createCss (prefix) {
align-items: center;
box-shadow: 0 11px 40px 0 rgba(0, 0, 0, 0.25), 0 2px 10px 0 rgba(0, 0, 0, 0.12);

display: none;
opacity: 0;
display: flex;
transition: opacity 0.1s ease, bottom 0.1s ease, width 0.3s ease;
animation: ${prefix}fade-in 0.1s ease-in-out;
}
Expand All @@ -122,15 +170,16 @@ function createCss (prefix) {
display: flex;
flex-shrink: 0;
align-items: center;
position: relative;
}

#${prefix}icon-wrapper svg {
flex-shrink: 0;
margin-right: 3px;
}

#${prefix}container.${prefix}expanded {
width: 140px;
#${prefix}container a.${prefix}expanded {
width: 135px;
}

#${prefix}container.${prefix}visible {
Expand Down
10 changes: 8 additions & 2 deletions packages/next/client/next-dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,14 @@ window.next = next
initNext({ webpackHMR })
.then(emitter => {
initOnDemandEntries({ assetPrefix: prefix })
initializeBuildWatcher()
initializePrerenderIndicator()
if (process.env.__NEXT_BUILD_INDICATOR) initializeBuildWatcher()
if (
process.env.__NEXT_PRERENDER_INDICATOR &&
// disable by default in electron
!(typeof process !== 'undefined' && 'electron' in process.versions)
) {
initializePrerenderIndicator()
}

let lastScroll

Expand Down
4 changes: 4 additions & 0 deletions packages/next/next-server/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ const defaultConfig: { [key: string]: any } = {
target: 'server',
poweredByHeader: true,
compress: true,
devIndicators: {
buildActivity: true,
autoPrerender: true,
},
onDemandEntries: {
maxInactiveAge: 60 * 1000,
pagesBufferLength: 2,
Expand Down
93 changes: 60 additions & 33 deletions test/integration/build-indicator/test/index.test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
/* eslint-env jest */
/* global jasmine */
import fs from 'fs'
import fs from 'fs-extra'
import { join } from 'path'
import webdriver from 'next-webdriver'
import { findPort, launchApp, killApp, waitFor } from 'next-test-utils'

jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 5
const appDir = join(__dirname, '..')
const nextConfig = join(appDir, 'next.config.js')
let appPort
let app

Expand All @@ -24,42 +25,68 @@ const installCheckVisible = browser => {
}

describe('Build Activity Indicator', () => {
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(() => killApp(app))
describe('Enabled', () => {
beforeAll(async () => {
await fs.remove(nextConfig)
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(() => killApp(app))

it('Adds the build indicator container', async () => {
const browser = await webdriver(appPort, '/')
const html = await browser.eval('document.body.innerHTML')
expect(html).toMatch(/__next-build-watcher/)
await browser.close()
})
it('Adds the build indicator container', async () => {
const browser = await webdriver(appPort, '/')
const html = await browser.eval('document.body.innerHTML')
expect(html).toMatch(/__next-build-watcher/)
await browser.close()
})

it('Shows the build indicator when a page is built during navigation', async () => {
const browser = await webdriver(appPort, '/')
await installCheckVisible(browser)
await browser.elementByCss('#to-a').click()
await waitFor(500)
const wasVisible = await browser.eval('window.showedBuilder')
expect(wasVisible).toBe(true)
await browser.close()
})
it('Shows the build indicator when a page is built during navigation', async () => {
const browser = await webdriver(appPort, '/')
await installCheckVisible(browser)
await browser.elementByCss('#to-a').click()
await waitFor(500)
const wasVisible = await browser.eval('window.showedBuilder')
expect(wasVisible).toBe(true)
await browser.close()
})

it('Shows build indicator when page is built from modifying', async () => {
const browser = await webdriver(appPort, '/b')
await installCheckVisible(browser)
const pagePath = join(appDir, 'pages/b.js')
const origContent = await fs.readFile(pagePath, 'utf8')
const newContent = origContent.replace('b', 'c')

it('Shows build indicator when page is built from modifying', async () => {
const browser = await webdriver(appPort, '/b')
await installCheckVisible(browser)
const pagePath = join(appDir, 'pages/b.js')
const origContent = fs.readFileSync(pagePath, 'utf8')
const newContent = origContent.replace('b', 'c')
await fs.writeFile(pagePath, newContent, 'utf8')
await waitFor(500)
const wasVisible = await browser.eval('window.showedBuilder')

fs.writeFileSync(pagePath, newContent, 'utf8')
await waitFor(500)
const wasVisible = await browser.eval('window.showedBuilder')
expect(wasVisible).toBe(true)
await fs.writeFile(pagePath, origContent, 'utf8')
await browser.close()
})
})

describe('Disabled with next.config.js', () => {
beforeAll(async () => {
await fs.writeFile(
nextConfig,
'module.exports = { devIndicators: { buildActivity: false } }',
'utf8'
)
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
await fs.remove(nextConfig)
})

expect(wasVisible).toBe(true)
fs.writeFileSync(pagePath, origContent, 'utf8')
await browser.close()
it('Does not add the build indicator container', async () => {
const browser = await webdriver(appPort, '/')
const html = await browser.eval('document.body.innerHTML')
expect(html).not.toMatch(/__next-build-watcher/)
await browser.close()
})
})
})