1
0
Fork 0
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:
nkzawa 2016-10-24 01:42:13 +09:00
parent ec774a39da
commit 07b95ae080
6 changed files with 204 additions and 8 deletions

View 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)
}
}

View 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)
})
}
}

View 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'
}
}

View 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)
}
}

View 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$/, '')

View file

@ -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()
})