mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
dynamically add/remove pages
This commit is contained in:
parent
ec774a39da
commit
07b95ae080
62
server/build/plugins/dynamic-entry-plugin.js
Normal file
62
server/build/plugins/dynamic-entry-plugin.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
import SingleEntryPlugin from 'webpack/lib/SingleEntryPlugin'
|
||||
import MultiEntryPlugin from 'webpack/lib/MultiEntryPlugin'
|
||||
|
||||
export default class DynamicEntryPlugin {
|
||||
apply (compiler) {
|
||||
compiler.entryNames = getInitialEntryNames(compiler)
|
||||
compiler.addEntry = addEntry
|
||||
compiler.removeEntry = removeEntry
|
||||
compiler.hasEntry = hasEntry
|
||||
compiler.createCompilation = createCompilation(compiler.createCompilation)
|
||||
}
|
||||
}
|
||||
|
||||
function getInitialEntryNames (compiler) {
|
||||
const entryNames = new Set()
|
||||
const { entry } = compiler.options
|
||||
|
||||
if (typeof entry === 'string' || Array.isArray(entry)) {
|
||||
entryNames.add('main')
|
||||
} else if (typeof entry === 'object') {
|
||||
Object.keys(entry).forEach((name) => {
|
||||
entryNames.add(name)
|
||||
})
|
||||
}
|
||||
|
||||
return entryNames
|
||||
}
|
||||
|
||||
function addEntry (entry, name = 'main') {
|
||||
const { context } = this.options
|
||||
const Plugin = Array.isArray(entry) ? MultiEntryPlugin : SingleEntryPlugin
|
||||
this.apply(new Plugin(context, entry, name))
|
||||
this.entryNames.add(name)
|
||||
}
|
||||
|
||||
function removeEntry (name = 'main') {
|
||||
this.entryNames.delete(name)
|
||||
}
|
||||
|
||||
function hasEntry (name = 'main') {
|
||||
this.entryNames.has(name)
|
||||
}
|
||||
|
||||
function createCompilation (original) {
|
||||
return function (...args) {
|
||||
const compilation = original.apply(this, args)
|
||||
compilation.addEntry = compilationAddEntry(compilation.addEntry)
|
||||
return compilation
|
||||
}
|
||||
}
|
||||
|
||||
function compilationAddEntry (original) {
|
||||
return function (context, entry, name, callback) {
|
||||
if (!this.compiler.entryNames.has(name)) {
|
||||
// skip removed entry
|
||||
callback()
|
||||
return
|
||||
}
|
||||
|
||||
return original.call(this, context, entry, name, callback)
|
||||
}
|
||||
}
|
28
server/build/plugins/unlink-file-plugin.js
Normal file
28
server/build/plugins/unlink-file-plugin.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { join } from 'path'
|
||||
import { unlink } from 'mz/fs'
|
||||
|
||||
export default class UnlinkFilePlugin {
|
||||
constructor () {
|
||||
this.prevAssets = {}
|
||||
}
|
||||
|
||||
apply (compiler) {
|
||||
compiler.plugin('after-emit', (compilation, callback) => {
|
||||
const removed = Object.keys(this.prevAssets)
|
||||
.filter((a) => !compilation.assets[a])
|
||||
|
||||
this.prevAssets = compilation.assets
|
||||
|
||||
Promise.all(removed.map(async (f) => {
|
||||
const path = join(compiler.outputPath, f)
|
||||
try {
|
||||
await unlink(path)
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') return
|
||||
throw err
|
||||
}
|
||||
}))
|
||||
.then(() => callback(), callback)
|
||||
})
|
||||
}
|
||||
}
|
48
server/build/plugins/watch-pages-plugin.js
Normal file
48
server/build/plugins/watch-pages-plugin.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { resolve, relative, join, extname } from 'path'
|
||||
|
||||
export default class WatchPagesPlugin {
|
||||
constructor (dir) {
|
||||
this.dir = resolve(dir, 'pages')
|
||||
}
|
||||
|
||||
apply (compiler) {
|
||||
compiler.plugin('emit', (compilation, callback) => {
|
||||
// watch the pages directory
|
||||
compilation.contextDependencies =
|
||||
compilation.contextDependencies.concat([this.dir])
|
||||
|
||||
callback()
|
||||
})
|
||||
|
||||
const isPageFile = this.isPageFile.bind(this)
|
||||
const getEntryName = (f) => {
|
||||
return join('bundles', relative(compiler.options.context, f))
|
||||
}
|
||||
|
||||
compiler.plugin('watch-run', (watching, callback) => {
|
||||
Object.keys(compiler.fileTimestamps)
|
||||
.filter(isPageFile)
|
||||
.forEach((f) => {
|
||||
const name = getEntryName(f)
|
||||
if (compiler.hasEntry(name)) return
|
||||
|
||||
const entries = ['webpack/hot/only-dev-server', f]
|
||||
compiler.addEntry(entries, name)
|
||||
})
|
||||
|
||||
compiler.removedFiles
|
||||
.filter(isPageFile)
|
||||
.forEach((f) => {
|
||||
const name = getEntryName(f)
|
||||
compiler.removeEntry(name)
|
||||
})
|
||||
|
||||
callback()
|
||||
})
|
||||
}
|
||||
|
||||
isPageFile (f) {
|
||||
return f.indexOf(this.dir) === 0 && extname(f) === '.js'
|
||||
}
|
||||
}
|
||||
|
40
server/build/plugins/watch-remove-event-plugin.js
Normal file
40
server/build/plugins/watch-remove-event-plugin.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
|
||||
// watch and trigger file remove event
|
||||
// see: https://github.com/webpack/webpack/issues/1533
|
||||
|
||||
export default class WatchRemoveEventPlugin {
|
||||
constructor () {
|
||||
this.removedFiles = []
|
||||
}
|
||||
|
||||
apply (compiler) {
|
||||
compiler.removedFiles = []
|
||||
|
||||
compiler.plugin('environment', () => {
|
||||
if (!compiler.watchFileSystem) return
|
||||
|
||||
const { watchFileSystem } = compiler
|
||||
const { watch } = watchFileSystem
|
||||
|
||||
watchFileSystem.watch = (files, dirs, missing, startTime, options, callback, callbackUndelayed) => {
|
||||
const result = watch.call(watchFileSystem, files, dirs, missing, startTime, options, (...args) => {
|
||||
compiler.removedFiles = this.removedFiles
|
||||
this.removedFiles = []
|
||||
callback(...args)
|
||||
}, callbackUndelayed)
|
||||
|
||||
const watchpack = watchFileSystem.watcher
|
||||
watchpack.fileWatchers.forEach((w) => {
|
||||
w.on('remove', this.onRemove.bind(this, watchpack, w.path))
|
||||
})
|
||||
return result
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onRemove (watchpack, file) {
|
||||
this.removedFiles.push(file)
|
||||
watchpack.emit('remove', file)
|
||||
watchpack._onChange(file)
|
||||
}
|
||||
}
|
|
@ -2,6 +2,10 @@ import { resolve, join } from 'path'
|
|||
import webpack from 'webpack'
|
||||
import glob from 'glob-promise'
|
||||
import WriteFilePlugin from 'write-file-webpack-plugin'
|
||||
import UnlinkFilePlugin from './plugins/unlink-file-plugin'
|
||||
import WatchPagesPlugin from './plugins/watch-pages-plugin'
|
||||
import WatchRemoveEventPlugin from './plugins/watch-remove-event-plugin'
|
||||
import DynamicEntryPlugin from './plugins/dynamic-entry-plugin'
|
||||
|
||||
export default async function createCompiler (dir, { hotReload = false } = {}) {
|
||||
dir = resolve(dir)
|
||||
|
@ -27,17 +31,24 @@ export default async function createCompiler (dir, { hotReload = false } = {}) {
|
|||
const nodeModulesDir = join(__dirname, '..', '..', '..', 'node_modules')
|
||||
|
||||
const plugins = [
|
||||
hotReload
|
||||
? new webpack.HotModuleReplacementPlugin()
|
||||
: new webpack.optimize.UglifyJsPlugin({
|
||||
compress: { warnings: false },
|
||||
sourceMap: false
|
||||
}),
|
||||
new WriteFilePlugin({
|
||||
exitOnErrors: false,
|
||||
log: false
|
||||
log: false,
|
||||
// required not to cache removed files
|
||||
useHashIndex: false
|
||||
})
|
||||
]
|
||||
].concat(hotReload ? [
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new DynamicEntryPlugin(),
|
||||
new UnlinkFilePlugin(),
|
||||
new WatchRemoveEventPlugin(),
|
||||
new WatchPagesPlugin(dir)
|
||||
] : [
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: { warnings: false },
|
||||
sourceMap: false
|
||||
})
|
||||
])
|
||||
|
||||
const babelRuntimePath = require.resolve('babel-runtime/package')
|
||||
.replace(/[\\\/]package\.json$/, '')
|
||||
|
|
|
@ -9,6 +9,7 @@ export default class HotReloader {
|
|||
this.server = null
|
||||
this.stats = null
|
||||
this.compilationErrors = null
|
||||
this.prevAssets = {}
|
||||
}
|
||||
|
||||
async start () {
|
||||
|
@ -25,6 +26,12 @@ export default class HotReloader {
|
|||
for (const f of Object.keys(assets)) {
|
||||
deleteCache(assets[f].existsAt)
|
||||
}
|
||||
for (const f of Object.keys(this.prevAssets)) {
|
||||
if (!assets[f]) {
|
||||
deleteCache(this.prevAssets[f].existsAt)
|
||||
}
|
||||
}
|
||||
this.prevAssets = assets
|
||||
callback()
|
||||
})
|
||||
|
||||
|
|
Loading…
Reference in a new issue