mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
parent
230ae52de2
commit
5a4176cffe
|
@ -10,7 +10,8 @@ const Fragment = React.Fragment || function Fragment ({ children }) {
|
||||||
|
|
||||||
export default class Document extends Component {
|
export default class Document extends Component {
|
||||||
static childContextTypes = {
|
static childContextTypes = {
|
||||||
_documentProps: PropTypes.any
|
_documentProps: PropTypes.any,
|
||||||
|
_devOnlyInvalidateCacheQueryString: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
static getInitialProps ({ renderPage }) {
|
static getInitialProps ({ renderPage }) {
|
||||||
|
@ -20,7 +21,13 @@ export default class Document extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
getChildContext () {
|
getChildContext () {
|
||||||
return { _documentProps: this.props }
|
return {
|
||||||
|
_documentProps: this.props,
|
||||||
|
// In dev we invalidate the cache by appending a timestamp to the resource URL.
|
||||||
|
// This is a workaround to fix https://github.com/zeit/next.js/issues/5860
|
||||||
|
// TODO: remove this workaround when https://bugs.webkit.org/show_bug.cgi?id=187726 is fixed.
|
||||||
|
_devOnlyInvalidateCacheQueryString: process.env.NODE_ENV !== 'production' ? '?ts=' + Date.now() : ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
@ -36,7 +43,8 @@ export default class Document extends Component {
|
||||||
|
|
||||||
export class Head extends Component {
|
export class Head extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
_documentProps: PropTypes.any
|
_documentProps: PropTypes.any,
|
||||||
|
_devOnlyInvalidateCacheQueryString: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -68,11 +76,13 @@ export class Head extends Component {
|
||||||
|
|
||||||
getPreloadDynamicChunks () {
|
getPreloadDynamicChunks () {
|
||||||
const { dynamicImports, assetPrefix } = this.context._documentProps
|
const { dynamicImports, assetPrefix } = this.context._documentProps
|
||||||
|
const { _devOnlyInvalidateCacheQueryString } = this.context
|
||||||
|
|
||||||
return dynamicImports.map((bundle) => {
|
return dynamicImports.map((bundle) => {
|
||||||
return <link
|
return <link
|
||||||
rel='preload'
|
rel='preload'
|
||||||
key={bundle.file}
|
key={bundle.file}
|
||||||
href={`${assetPrefix}/_next/${bundle.file}`}
|
href={`${assetPrefix}/_next/${bundle.file}${_devOnlyInvalidateCacheQueryString}`}
|
||||||
as='script'
|
as='script'
|
||||||
nonce={this.props.nonce}
|
nonce={this.props.nonce}
|
||||||
crossOrigin={this.props.crossOrigin || process.crossOrigin}
|
crossOrigin={this.props.crossOrigin || process.crossOrigin}
|
||||||
|
@ -82,9 +92,10 @@ export class Head extends Component {
|
||||||
|
|
||||||
getPreloadMainLinks () {
|
getPreloadMainLinks () {
|
||||||
const { assetPrefix, files } = this.context._documentProps
|
const { assetPrefix, files } = this.context._documentProps
|
||||||
if(!files || files.length === 0) {
|
if (!files || files.length === 0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
const { _devOnlyInvalidateCacheQueryString } = this.context
|
||||||
|
|
||||||
return files.map((file) => {
|
return files.map((file) => {
|
||||||
// Only render .js files here
|
// Only render .js files here
|
||||||
|
@ -96,7 +107,7 @@ export class Head extends Component {
|
||||||
key={file}
|
key={file}
|
||||||
nonce={this.props.nonce}
|
nonce={this.props.nonce}
|
||||||
rel='preload'
|
rel='preload'
|
||||||
href={`${assetPrefix}/_next/${file}`}
|
href={`${assetPrefix}/_next/${file}${_devOnlyInvalidateCacheQueryString}`}
|
||||||
as='script'
|
as='script'
|
||||||
crossOrigin={this.props.crossOrigin || process.crossOrigin}
|
crossOrigin={this.props.crossOrigin || process.crossOrigin}
|
||||||
/>
|
/>
|
||||||
|
@ -105,6 +116,7 @@ export class Head extends Component {
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { head, styles, assetPrefix, __NEXT_DATA__ } = this.context._documentProps
|
const { head, styles, assetPrefix, __NEXT_DATA__ } = this.context._documentProps
|
||||||
|
const { _devOnlyInvalidateCacheQueryString } = this.context
|
||||||
const { page, buildId } = __NEXT_DATA__
|
const { page, buildId } = __NEXT_DATA__
|
||||||
const pagePathname = getPagePathname(page)
|
const pagePathname = getPagePathname(page)
|
||||||
|
|
||||||
|
@ -119,12 +131,11 @@ export class Head extends Component {
|
||||||
})
|
})
|
||||||
if (this.props.crossOrigin) console.warn('Warning: `Head` attribute `crossOrigin` is deprecated. https://err.sh/next.js/doc-crossorigin-deprecated')
|
if (this.props.crossOrigin) console.warn('Warning: `Head` attribute `crossOrigin` is deprecated. https://err.sh/next.js/doc-crossorigin-deprecated')
|
||||||
}
|
}
|
||||||
|
|
||||||
return <head {...this.props}>
|
return <head {...this.props}>
|
||||||
{children}
|
{children}
|
||||||
{head}
|
{head}
|
||||||
{page !== '/_error' && <link rel='preload' href={`${assetPrefix}/_next/static/${buildId}/pages${pagePathname}`} as='script' nonce={this.props.nonce} crossOrigin={this.props.crossOrigin || process.crossOrigin} />}
|
{page !== '/_error' && <link rel='preload' href={`${assetPrefix}/_next/static/${buildId}/pages${pagePathname}${_devOnlyInvalidateCacheQueryString}`} as='script' nonce={this.props.nonce} crossOrigin={this.props.crossOrigin || process.crossOrigin} />}
|
||||||
<link rel='preload' href={`${assetPrefix}/_next/static/${buildId}/pages/_app.js`} as='script' nonce={this.props.nonce} crossOrigin={this.props.crossOrigin || process.crossOrigin} />
|
<link rel='preload' href={`${assetPrefix}/_next/static/${buildId}/pages/_app.js${_devOnlyInvalidateCacheQueryString}`} as='script' nonce={this.props.nonce} crossOrigin={this.props.crossOrigin || process.crossOrigin} />
|
||||||
{this.getPreloadDynamicChunks()}
|
{this.getPreloadDynamicChunks()}
|
||||||
{this.getPreloadMainLinks()}
|
{this.getPreloadMainLinks()}
|
||||||
{this.getCssLinks()}
|
{this.getCssLinks()}
|
||||||
|
@ -135,7 +146,8 @@ export class Head extends Component {
|
||||||
|
|
||||||
export class Main extends Component {
|
export class Main extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
_documentProps: PropTypes.any
|
_documentProps: PropTypes.any,
|
||||||
|
_devOnlyInvalidateCacheQueryString: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
@ -148,7 +160,8 @@ export class Main extends Component {
|
||||||
|
|
||||||
export class NextScript extends Component {
|
export class NextScript extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
_documentProps: PropTypes.any
|
_documentProps: PropTypes.any,
|
||||||
|
_devOnlyInvalidateCacheQueryString: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -158,11 +171,13 @@ export class NextScript extends Component {
|
||||||
|
|
||||||
getDynamicChunks () {
|
getDynamicChunks () {
|
||||||
const { dynamicImports, assetPrefix } = this.context._documentProps
|
const { dynamicImports, assetPrefix } = this.context._documentProps
|
||||||
|
const { _devOnlyInvalidateCacheQueryString } = this.context
|
||||||
|
|
||||||
return dynamicImports.map((bundle) => {
|
return dynamicImports.map((bundle) => {
|
||||||
return <script
|
return <script
|
||||||
async
|
async
|
||||||
key={bundle.file}
|
key={bundle.file}
|
||||||
src={`${assetPrefix}/_next/${bundle.file}`}
|
src={`${assetPrefix}/_next/${bundle.file}${_devOnlyInvalidateCacheQueryString}`}
|
||||||
nonce={this.props.nonce}
|
nonce={this.props.nonce}
|
||||||
crossOrigin={this.props.crossOrigin || process.crossOrigin}
|
crossOrigin={this.props.crossOrigin || process.crossOrigin}
|
||||||
/>
|
/>
|
||||||
|
@ -171,9 +186,10 @@ export class NextScript extends Component {
|
||||||
|
|
||||||
getScripts () {
|
getScripts () {
|
||||||
const { assetPrefix, files } = this.context._documentProps
|
const { assetPrefix, files } = this.context._documentProps
|
||||||
if(!files || files.length === 0) {
|
if (!files || files.length === 0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
const { _devOnlyInvalidateCacheQueryString } = this.context
|
||||||
|
|
||||||
return files.map((file) => {
|
return files.map((file) => {
|
||||||
// Only render .js files here
|
// Only render .js files here
|
||||||
|
@ -183,7 +199,7 @@ export class NextScript extends Component {
|
||||||
|
|
||||||
return <script
|
return <script
|
||||||
key={file}
|
key={file}
|
||||||
src={`${assetPrefix}/_next/${file}`}
|
src={`${assetPrefix}/_next/${file}${_devOnlyInvalidateCacheQueryString}`}
|
||||||
nonce={this.props.nonce}
|
nonce={this.props.nonce}
|
||||||
async
|
async
|
||||||
crossOrigin={this.props.crossOrigin || process.crossOrigin}
|
crossOrigin={this.props.crossOrigin || process.crossOrigin}
|
||||||
|
@ -206,6 +222,7 @@ export class NextScript extends Component {
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { staticMarkup, assetPrefix, devFiles, __NEXT_DATA__ } = this.context._documentProps
|
const { staticMarkup, assetPrefix, devFiles, __NEXT_DATA__ } = this.context._documentProps
|
||||||
|
const { _devOnlyInvalidateCacheQueryString } = this.context
|
||||||
const { page, buildId } = __NEXT_DATA__
|
const { page, buildId } = __NEXT_DATA__
|
||||||
const pagePathname = getPagePathname(page)
|
const pagePathname = getPagePathname(page)
|
||||||
|
|
||||||
|
@ -214,12 +231,12 @@ export class NextScript extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Fragment>
|
return <Fragment>
|
||||||
{devFiles ? devFiles.map((file) => <script key={file} src={`${assetPrefix}/_next/${file}`} nonce={this.props.nonce} crossOrigin={this.props.crossOrigin || process.crossOrigin} />) : null}
|
{devFiles ? devFiles.map((file) => <script key={file} src={`${assetPrefix}/_next/${file}${_devOnlyInvalidateCacheQueryString}`} nonce={this.props.nonce} crossOrigin={this.props.crossOrigin || process.crossOrigin} />) : null}
|
||||||
{staticMarkup ? null : <script id="__NEXT_DATA__" type="application/json" nonce={this.props.nonce} crossOrigin={this.props.crossOrigin || process.crossOrigin} dangerouslySetInnerHTML={{
|
{staticMarkup ? null : <script id="__NEXT_DATA__" type="application/json" nonce={this.props.nonce} crossOrigin={this.props.crossOrigin || process.crossOrigin} dangerouslySetInnerHTML={{
|
||||||
__html: NextScript.getInlineScriptSource(this.context._documentProps)
|
__html: NextScript.getInlineScriptSource(this.context._documentProps)
|
||||||
}} />}
|
}} />}
|
||||||
{page !== '/_error' && <script async id={`__NEXT_PAGE__${page}`} src={`${assetPrefix}/_next/static/${buildId}/pages${pagePathname}`} nonce={this.props.nonce} crossOrigin={this.props.crossOrigin || process.crossOrigin} />}
|
{page !== '/_error' && <script async id={`__NEXT_PAGE__${page}`} src={`${assetPrefix}/_next/static/${buildId}/pages${pagePathname}${_devOnlyInvalidateCacheQueryString}`} nonce={this.props.nonce} crossOrigin={this.props.crossOrigin || process.crossOrigin} />}
|
||||||
<script async id={`__NEXT_PAGE__/_app`} src={`${assetPrefix}/_next/static/${buildId}/pages/_app.js`} nonce={this.props.nonce} crossOrigin={this.props.crossOrigin || process.crossOrigin} />
|
<script async id={`__NEXT_PAGE__/_app`} src={`${assetPrefix}/_next/static/${buildId}/pages/_app.js${_devOnlyInvalidateCacheQueryString}`} nonce={this.props.nonce} crossOrigin={this.props.crossOrigin || process.crossOrigin} />
|
||||||
{staticMarkup ? null : this.getDynamicChunks()}
|
{staticMarkup ? null : this.getDynamicChunks()}
|
||||||
{staticMarkup ? null : this.getScripts()}
|
{staticMarkup ? null : this.getScripts()}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
|
@ -67,6 +67,22 @@ export default function ({ app }, suiteName, render, fetch) {
|
||||||
expect($('#render-page-enhance-app').text().includes(nonce)).toBe(true)
|
expect($('#render-page-enhance-app').text().includes(nonce)).toBe(true)
|
||||||
expect($('#render-page-enhance-component').text().includes(nonce)).toBe(true)
|
expect($('#render-page-enhance-component').text().includes(nonce)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// This is a workaround to fix https://github.com/zeit/next.js/issues/5860
|
||||||
|
// TODO: remove this workaround when https://bugs.webkit.org/show_bug.cgi?id=187726 is fixed.
|
||||||
|
test('It adds a timestamp to link tags with preload attribute to invalidate the cache (DEV only)', async () => {
|
||||||
|
const $ = await get$('/')
|
||||||
|
$('link[rel=preload]').each((index, element) => {
|
||||||
|
const href = $(element).attr('href')
|
||||||
|
expect(href.match(/\?/g)).toHaveLength(1)
|
||||||
|
expect(href).toMatch(/\?ts=/)
|
||||||
|
})
|
||||||
|
$('script[src]').each((index, element) => {
|
||||||
|
const src = $(element).attr('src')
|
||||||
|
expect(src.match(/\?/g)).toHaveLength(1)
|
||||||
|
expect(src).toMatch(/\?ts=/)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('_app', () => {
|
describe('_app', () => {
|
||||||
|
|
|
@ -241,6 +241,27 @@ describe('Production Usage', () => {
|
||||||
browser.close()
|
browser.close()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// This is a workaround to fix https://github.com/zeit/next.js/issues/5860
|
||||||
|
// TODO: remove this workaround when https://bugs.webkit.org/show_bug.cgi?id=187726 is fixed.
|
||||||
|
it('It does not add a timestamp to link tags with preload attribute', async () => {
|
||||||
|
const browser = await webdriver(appPort, '/prefetch')
|
||||||
|
const links = await browser.elementsByCss('link[rel=preload]')
|
||||||
|
await Promise.all(
|
||||||
|
links.map(async (element) => {
|
||||||
|
const href = await element.getAttribute('href')
|
||||||
|
expect(href).not.toMatch(/\?ts=/)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const scripts = await browser.elementsByCss('script[src]')
|
||||||
|
await Promise.all(
|
||||||
|
scripts.map(async (element) => {
|
||||||
|
const src = await element.getAttribute('src')
|
||||||
|
expect(src).not.toMatch(/\?ts=/)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
browser.close()
|
||||||
|
})
|
||||||
|
|
||||||
it('should reload the page on page script error with prefetch', async () => {
|
it('should reload the page on page script error with prefetch', async () => {
|
||||||
const browser = await webdriver(appPort, '/counter')
|
const browser = await webdriver(appPort, '/counter')
|
||||||
const counter = await browser
|
const counter = await browser
|
||||||
|
|
Loading…
Reference in a new issue