mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
Introduce script tag based page loading system.
This commit is contained in:
parent
1e88331baf
commit
a0945c7800
|
@ -7,6 +7,7 @@ import App from '../lib/app'
|
|||
import evalScript from '../lib/eval-script'
|
||||
import { loadGetInitialProps, getURL } from '../lib/utils'
|
||||
import ErrorDebugComponent from '../lib/error-debug'
|
||||
import PageLoader from '../lib/page-loader'
|
||||
|
||||
// Polyfill Promise globally
|
||||
// This is needed because Webpack2's dynamic loading(common chunks) code
|
||||
|
@ -24,11 +25,14 @@ const {
|
|||
props,
|
||||
err,
|
||||
pathname,
|
||||
query
|
||||
query,
|
||||
buildId
|
||||
},
|
||||
location
|
||||
} = window
|
||||
|
||||
window.NEXT_PAGE_LOADER = new PageLoader(buildId)
|
||||
|
||||
const Component = evalScript(component).default
|
||||
const ErrorComponent = evalScript(errorComponent).default
|
||||
let lastAppProps
|
||||
|
|
64
lib/page-loader.js
Normal file
64
lib/page-loader.js
Normal file
|
@ -0,0 +1,64 @@
|
|||
/* global window, document */
|
||||
import mitt from 'mitt'
|
||||
|
||||
export default class PageLoader {
|
||||
constructor (buildId) {
|
||||
this.buildId = buildId
|
||||
this.pageCache = {}
|
||||
this.pageLoadedHandlers = {}
|
||||
this.registerEvents = mitt()
|
||||
this.loadingRoutes = {}
|
||||
}
|
||||
|
||||
loadPage (route) {
|
||||
if (route[0] !== '/') {
|
||||
throw new Error('Route name should start with a "/"')
|
||||
}
|
||||
|
||||
route = route.replace(/index$/, '')
|
||||
|
||||
if (this.pageCache[route]) {
|
||||
return Promise.resolve(this.pageCache[route])
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const fire = ({ error, page }) => {
|
||||
this.registerEvents.off(route, fire)
|
||||
|
||||
if (error) {
|
||||
reject(error)
|
||||
} else {
|
||||
resolve(page)
|
||||
}
|
||||
}
|
||||
|
||||
this.registerEvents.on(route, fire)
|
||||
|
||||
// Load the script if not asked to load yet.
|
||||
if (!this.loadingRoutes[route]) {
|
||||
this.loadScript(route)
|
||||
this.loadingRoutes[route] = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
loadScript (route) {
|
||||
const script = document.createElement('script')
|
||||
const url = `/_next/${encodeURIComponent(this.buildId)}/page${route}`
|
||||
script.src = url
|
||||
script.type = 'text/javascript'
|
||||
script.onerror = () => {
|
||||
const error = new Error(`Error when loading route: ${route}`)
|
||||
this.registerEvents.emit(route, { error })
|
||||
}
|
||||
|
||||
document.body.appendChild(script)
|
||||
}
|
||||
|
||||
// This method if called by the route code.
|
||||
registerPage (route, error, page) {
|
||||
// add the page to the cache
|
||||
this.pageCache[route] = page
|
||||
this.registerEvents.emit(route, { error, page })
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ export default class JsonPagesPlugin {
|
|||
|
||||
pages.forEach((pageName) => {
|
||||
const page = compilation.assets[pageName]
|
||||
delete compilation.assets[pageName]
|
||||
// delete compilation.assets[pageName]
|
||||
|
||||
const content = page.source()
|
||||
const newContent = JSON.stringify({ component: content })
|
||||
|
|
34
server/build/plugins/pages-plugin.js
Normal file
34
server/build/plugins/pages-plugin.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
export default class PagesPlugin {
|
||||
apply (compiler) {
|
||||
const isBundledPage = /^bundles[/\\]pages.*\.js$/
|
||||
const matchRouteName = /^bundles[/\\]pages[/\\](.*)\.js$/
|
||||
|
||||
compiler.plugin('after-compile', (compilation, callback) => {
|
||||
const pages = Object
|
||||
.keys(compilation.namedChunks)
|
||||
.map(key => compilation.namedChunks[key])
|
||||
.filter(chunk => isBundledPage.test(chunk.name))
|
||||
|
||||
pages.forEach((chunk) => {
|
||||
const page = compilation.assets[chunk.name]
|
||||
const pageName = matchRouteName.exec(chunk.name)[1]
|
||||
const routeName = `/${pageName.replace(/index$/, '')}`
|
||||
|
||||
const content = page.source()
|
||||
const newContent = `
|
||||
var comp = ${content}
|
||||
NEXT_PAGE_LOADER.registerPage('${routeName}', null, comp.default)
|
||||
`
|
||||
// Replace the current asset
|
||||
// TODO: We need to move "client-bundles" back to "bundles" once we remove
|
||||
// all the JSON eval stuff
|
||||
delete compilation.assets[chunk.name]
|
||||
compilation.assets[`client-bundles/pages/${pageName}.js`] = {
|
||||
source: () => newContent,
|
||||
size: () => newContent.length
|
||||
}
|
||||
})
|
||||
callback()
|
||||
})
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ import FriendlyErrorsWebpackPlugin from 'friendly-errors-webpack-plugin'
|
|||
import CaseSensitivePathPlugin from 'case-sensitive-paths-webpack-plugin'
|
||||
import UnlinkFilePlugin from './plugins/unlink-file-plugin'
|
||||
import JsonPagesPlugin from './plugins/json-pages-plugin'
|
||||
import PagesPlugin from './plugins/pages-plugin'
|
||||
import CombineAssetsPlugin from './plugins/combine-assets-plugin'
|
||||
import getConfig from '../config'
|
||||
import * as babelCore from 'babel-core'
|
||||
|
@ -117,6 +118,7 @@ export default async function createCompiler (dir, { dev = false, quiet = false,
|
|||
'process.env.NODE_ENV': JSON.stringify(dev ? 'development' : 'production')
|
||||
}),
|
||||
new JsonPagesPlugin(),
|
||||
new PagesPlugin(),
|
||||
new CaseSensitivePathPlugin()
|
||||
]
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
} from './render'
|
||||
import Router from './router'
|
||||
import HotReloader from './hot-reloader'
|
||||
import { resolveFromList } from './resolve'
|
||||
import resolvePath, { resolveFromList } from './resolve'
|
||||
import getConfig from './config'
|
||||
// We need to go up one more level since we are in the `dist` directory
|
||||
import pkg from '../../package'
|
||||
|
@ -127,6 +127,28 @@ export default class Server {
|
|||
await this.renderJSON(req, res, pathname)
|
||||
},
|
||||
|
||||
'/_next/:buildId/page/:path*': async (req, res, params) => {
|
||||
const paths = params.path || ['']
|
||||
const pathname = `/${paths.join('/')}`
|
||||
|
||||
await this.hotReloader.ensurePage(pathname)
|
||||
|
||||
if (!this.handleBuildId(params.buildId, res)) {
|
||||
res.setHeader('Content-Type', 'text/javascript')
|
||||
// TODO: Handle buildId mismatches properly.
|
||||
res.end(`
|
||||
var error = new Error('INVALID_BUILD_ID')
|
||||
error.buildIdMismatched = true
|
||||
NEXT_PAGE_LOADER.registerPage('${pathname}', error)
|
||||
`)
|
||||
return
|
||||
}
|
||||
|
||||
const path = join(this.dir, '.next', 'client-bundles', 'pages', pathname)
|
||||
const realPath = await resolvePath(path)
|
||||
await this.serveStatic(req, res, realPath)
|
||||
},
|
||||
|
||||
'/_next/:path+': async (req, res, params) => {
|
||||
const p = join(__dirname, '..', 'client', ...(params.path || []))
|
||||
await this.serveStatic(req, res, p)
|
||||
|
|
Loading…
Reference in a new issue