From 5c6aee35212f36714000284c84d5d7c15fe03306 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 3 Sep 2019 09:20:28 -0500 Subject: [PATCH 1/5] Add link to docs for prerender indicator and allow disabling --- packages/next-server/server/config.ts | 4 + packages/next/README.md | 14 +++ packages/next/build/webpack-config.ts | 15 ++- .../next/client/dev/prerender-indicator.js | 32 ++++--- packages/next/client/next-dev.js | 4 +- .../build-indicator/test/index.test.js | 93 ++++++++++++------- 6 files changed, 109 insertions(+), 53 deletions(-) diff --git a/packages/next-server/server/config.ts b/packages/next-server/server/config.ts index a92195ca1639e..dc4b92d548612 100644 --- a/packages/next-server/server/config.ts +++ b/packages/next-server/server/config.ts @@ -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, diff --git a/packages/next/README.md b/packages/next/README.md index b71e50617d634..4685ea535db73 100644 --- a/packages/next/README.md +++ b/packages/next/README.md @@ -2152,6 +2152,20 @@ There is no configuration or special handling required. > **Note**: If you have a [custom ``](#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: diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index db3e7282f8c71..480dd49408ca5 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -510,9 +510,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`) diff --git a/packages/next/client/dev/prerender-indicator.js b/packages/next/client/dev/prerender-indicator.js index 9938c4180341a..e0aff386e14c2 100644 --- a/packages/next/client/dev/prerender-indicator.js +++ b/packages/next/client/dev/prerender-indicator.js @@ -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) @@ -47,10 +46,6 @@ export default function initializeBuildWatcher () { } } - shadowHost.addEventListener('click', () => { - shadowHost.style.opacity = 0 - shadowHost.style.pointerEvents = 'none' - }) shadowHost.addEventListener('mouseenter', () => { container.classList.add(`${prefix}expanded`) }) @@ -70,16 +65,17 @@ function createContainer (prefix) { const container = document.createElement('div') container.id = `${prefix}container` container.innerHTML = ` -
- - - - - - Prerendered Page -
+ +
+ + + + + + Prerendered Page +
+
` - return container } @@ -116,12 +112,18 @@ function createCss (prefix) { animation: ${prefix}fade-in 0.1s ease-in-out; } + #${prefix}container a { + color: inherit; + text-decoration: none; + } + #${prefix}icon-wrapper { width: 140px; height: 20px; display: flex; flex-shrink: 0; align-items: center; + position: relative; } #${prefix}icon-wrapper svg { @@ -130,7 +132,7 @@ function createCss (prefix) { } #${prefix}container.${prefix}expanded { - width: 140px; + width: 135px; } #${prefix}container.${prefix}visible { diff --git a/packages/next/client/next-dev.js b/packages/next/client/next-dev.js index b45c93ce26b29..af7a9e29365b4 100644 --- a/packages/next/client/next-dev.js +++ b/packages/next/client/next-dev.js @@ -27,8 +27,8 @@ 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) initializePrerenderIndicator() let lastScroll diff --git a/test/integration/build-indicator/test/index.test.js b/test/integration/build-indicator/test/index.test.js index 7983e6b994dec..6a9be57de7693 100644 --- a/test/integration/build-indicator/test/index.test.js +++ b/test/integration/build-indicator/test/index.test.js @@ -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 @@ -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() + }) }) }) From 5afe74805e436608279a3e05897d8eb94a45033a Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 3 Sep 2019 09:25:51 -0500 Subject: [PATCH 2/5] Fix lint error from example --- examples/with-stomp/next.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/with-stomp/next.config.js b/examples/with-stomp/next.config.js index 557180214d3f1..cc0b55e5b0cdc 100644 --- a/examples/with-stomp/next.config.js +++ b/examples/with-stomp/next.config.js @@ -3,4 +3,4 @@ module.exports = { env: { STOMP_SERVER: process.env.STOMP_SERVER } -} \ No newline at end of file +} From 443aa5a167c8812b4a45382caf623f6c7777c14b Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 3 Sep 2019 14:22:33 -0500 Subject: [PATCH 3/5] Disable by default when electron is detected --- packages/next-server/server/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next-server/server/config.ts b/packages/next-server/server/config.ts index dc4b92d548612..60cf940909a60 100644 --- a/packages/next-server/server/config.ts +++ b/packages/next-server/server/config.ts @@ -23,7 +23,7 @@ const defaultConfig: { [key: string]: any } = { compress: true, devIndicators: { buildActivity: true, - autoPrerender: true, + autoPrerender: !('electron' in process.versions), }, onDemandEntries: { maxInactiveAge: 60 * 1000, From 8eaebcacd7075eab9a4e920a08eb5128274effb9 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 3 Sep 2019 14:35:42 -0500 Subject: [PATCH 4/5] Add button to dismiss indicator for session --- .../next/client/dev/prerender-indicator.js | 77 +++++++++++++++---- 1 file changed, 62 insertions(+), 15 deletions(-) diff --git a/packages/next/client/dev/prerender-indicator.js b/packages/next/client/dev/prerender-indicator.js index e0aff386e14c2..54b7698f69620 100644 --- a/packages/next/client/dev/prerender-indicator.js +++ b/packages/next/client/dev/prerender-indicator.js @@ -35,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) { @@ -45,17 +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('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() @@ -65,6 +91,9 @@ function createContainer (prefix) { const container = document.createElement('div') container.id = `${prefix}container` container.innerHTML = ` +
@@ -84,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; @@ -106,17 +159,11 @@ 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; } - #${prefix}container a { - color: inherit; - text-decoration: none; - } - #${prefix}icon-wrapper { width: 140px; height: 20px; @@ -131,7 +178,7 @@ function createCss (prefix) { margin-right: 3px; } - #${prefix}container.${prefix}expanded { + #${prefix}container a.${prefix}expanded { width: 135px; } From 86678216f97ad922e53b0e709546655a0fe10189 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 3 Sep 2019 14:46:25 -0500 Subject: [PATCH 5/5] Update disabling indicator in electron --- packages/next-server/server/config.ts | 2 +- packages/next/client/next-dev.js | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/next-server/server/config.ts b/packages/next-server/server/config.ts index 60cf940909a60..dc4b92d548612 100644 --- a/packages/next-server/server/config.ts +++ b/packages/next-server/server/config.ts @@ -23,7 +23,7 @@ const defaultConfig: { [key: string]: any } = { compress: true, devIndicators: { buildActivity: true, - autoPrerender: !('electron' in process.versions), + autoPrerender: true, }, onDemandEntries: { maxInactiveAge: 60 * 1000, diff --git a/packages/next/client/next-dev.js b/packages/next/client/next-dev.js index af7a9e29365b4..61f7ebbd2f863 100644 --- a/packages/next/client/next-dev.js +++ b/packages/next/client/next-dev.js @@ -28,7 +28,13 @@ initNext({ webpackHMR }) .then(emitter => { initOnDemandEntries({ assetPrefix: prefix }) if (process.env.__NEXT_BUILD_INDICATOR) initializeBuildWatcher() - if (process.env.__NEXT_PRERENDER_INDICATOR) initializePrerenderIndicator() + if ( + process.env.__NEXT_PRERENDER_INDICATOR && + // disable by default in electron + !(typeof process !== 'undefined' && 'electron' in process.versions) + ) { + initializePrerenderIndicator() + } let lastScroll