Evan You
8 years ago
7 changed files with 198 additions and 31 deletions
@ -0,0 +1,66 @@ |
|||||
|
const hash = require('hash-sum') |
||||
|
import { isJS } from './util' |
||||
|
|
||||
|
export default class VueSSRClientPlugin { |
||||
|
constructor (options = {}) { |
||||
|
this.options = Object.assign({ |
||||
|
filename: 'vue-ssr-client-manifest.json' |
||||
|
}, options) |
||||
|
} |
||||
|
|
||||
|
apply (compiler) { |
||||
|
compiler.plugin('emit', (compilation, cb) => { |
||||
|
const stats = compilation.getStats().toJson() |
||||
|
|
||||
|
const allFiles = stats.assets |
||||
|
.map(a => a.name) |
||||
|
|
||||
|
const initialScripts = Object.keys(stats.entrypoints) |
||||
|
.map(name => stats.entrypoints[name].assets) |
||||
|
.reduce((assets, all) => all.concat(assets), []) |
||||
|
.filter(isJS) |
||||
|
|
||||
|
const asyncScripts = allFiles |
||||
|
.filter(isJS) |
||||
|
.filter(file => initialScripts.indexOf(file) < 0) |
||||
|
|
||||
|
const manifest = { |
||||
|
publicPath: stats.publicPath, |
||||
|
all: allFiles, |
||||
|
initial: initialScripts, |
||||
|
async: asyncScripts, |
||||
|
modules: { /* [identifier: string]: Array<index: number> */ } |
||||
|
} |
||||
|
|
||||
|
const assetModules = stats.modules.filter(m => m.assets.length) |
||||
|
const fileToIndex = file => manifest.all.indexOf(file) |
||||
|
stats.modules.forEach(m => { |
||||
|
// ignore modules duplicated in multiple chunks
|
||||
|
if (m.chunks.length === 1) { |
||||
|
const cid = m.chunks[0] |
||||
|
const chunk = stats.chunks.find(c => c.id === cid) |
||||
|
const files = manifest.modules[hash(m.identifier)] = chunk.files.map(fileToIndex) |
||||
|
// find all asset modules associated with the same chunk
|
||||
|
assetModules.forEach(m => { |
||||
|
if (m.chunks.some(id => id === cid)) { |
||||
|
files.push.apply(files, m.assets.map(fileToIndex)) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
// const debug = (file, obj) => {
|
||||
|
// require('fs').writeFileSync(__dirname + '/' + file, JSON.stringify(obj, null, 2))
|
||||
|
// }
|
||||
|
// debug('stats.json', stats)
|
||||
|
// debug('client-manifest.json', manifest)
|
||||
|
|
||||
|
const json = JSON.stringify(manifest, null, 2) |
||||
|
compilation.assets[this.options.filename] = { |
||||
|
source: () => json, |
||||
|
size: () => json.length |
||||
|
} |
||||
|
cb() |
||||
|
}) |
||||
|
} |
||||
|
} |
@ -0,0 +1,59 @@ |
|||||
|
import { validate, isJS } from './util' |
||||
|
|
||||
|
export default class VueSSRServerPlugin { |
||||
|
constructor (options = {}) { |
||||
|
this.options = Object.assign({ |
||||
|
filename: 'vue-ssr-server-bundle.json' |
||||
|
}, options) |
||||
|
} |
||||
|
|
||||
|
apply (compiler) { |
||||
|
validate(compiler) |
||||
|
|
||||
|
compiler.plugin('emit', (compilation, cb) => { |
||||
|
const stats = compilation.getStats().toJson() |
||||
|
const entryName = Object.keys(stats.entrypoints)[0] |
||||
|
const entryAssets = stats.entrypoints[entryName].assets.filter(isJS) |
||||
|
|
||||
|
if (entryAssets.length > 1) { |
||||
|
throw new Error( |
||||
|
`Server-side bundle should have one single entry file. ` + |
||||
|
`Avoid using CommonsChunkPlugin in the server config.` |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
const entry = entryAssets[0] |
||||
|
if (!entry || typeof entry !== 'string') { |
||||
|
throw new Error( |
||||
|
`Entry "${entryName}" not found. Did you specify the correct entry option?` |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
const bundle = { |
||||
|
entry, |
||||
|
files: {}, |
||||
|
maps: {} |
||||
|
} |
||||
|
|
||||
|
stats.assets.forEach(asset => { |
||||
|
if (asset.name.match(/\.js$/)) { |
||||
|
bundle.files[asset.name] = compilation.assets[asset.name].source() |
||||
|
} else if (asset.name.match(/\.map$/)) { |
||||
|
bundle.maps[asset.name.replace(/\.map$/, '')] = JSON.parse(compilation.assets[asset.name].source()) |
||||
|
} |
||||
|
// do not emit anything else for server
|
||||
|
delete compilation.assets[asset.name] |
||||
|
}) |
||||
|
|
||||
|
const json = JSON.stringify(bundle, null, 2) |
||||
|
const filename = this.options.filename |
||||
|
|
||||
|
compilation.assets[filename] = { |
||||
|
source: () => json, |
||||
|
size: () => json.length |
||||
|
} |
||||
|
|
||||
|
cb() |
||||
|
}) |
||||
|
} |
||||
|
} |
@ -0,0 +1,25 @@ |
|||||
|
const { red, yellow, gray } = require('chalk') |
||||
|
|
||||
|
const prefix = `[vue-server-renderer-webpack-plugin]` |
||||
|
const warn = exports.warn = msg => console.error(red(`${prefix} ${msg}\n`)) |
||||
|
const tip = exports.tip = msg => console.log(yellow(`${prefix} ${msg}\n`)) |
||||
|
|
||||
|
export const validate = compiler => { |
||||
|
if (compiler.options.target !== 'node') { |
||||
|
warn('webpack config `target` should be "node".') |
||||
|
} |
||||
|
|
||||
|
if (compiler.options.output && compiler.options.output.libraryTarget !== 'commonjs2') { |
||||
|
warn('webpack config `output.libraryTarget` should be "commonjs2".') |
||||
|
} |
||||
|
|
||||
|
if (!compiler.options.externals) { |
||||
|
tip( |
||||
|
'It is recommended to externalize dependencies for better ssr performance.\n' + |
||||
|
`See ${gray('https://github.com/vuejs/vue/tree/dev/packages/vue-server-renderer#externals')} ` + |
||||
|
'for more details.' |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const isJS = file => /\.js($|\?)/.test(file) |
Loading…
Reference in new issue