diff --git a/lib/builder/webpack/client.config.js b/lib/builder/webpack/client.config.js index 1b7872d4f9..004e9628f4 100644 --- a/lib/builder/webpack/client.config.js +++ b/lib/builder/webpack/client.config.js @@ -1,6 +1,7 @@ const { each } = require('lodash') const webpack = require('webpack') -const VueSSRClientPlugin = require('vue-server-renderer/client-plugin') +// const VueSSRClientPlugin = require('vue-server-renderer/client-plugin') +const VueSSRClientPlugin = require('./plugins/vue/client') const HTMLPlugin = require('html-webpack-plugin') const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin') const StylishPlugin = require('webpack-stylish') diff --git a/lib/builder/webpack/plugins/vue/RADME.md b/lib/builder/webpack/plugins/vue/RADME.md new file mode 100644 index 0000000000..56ae9831a0 --- /dev/null +++ b/lib/builder/webpack/plugins/vue/RADME.md @@ -0,0 +1 @@ +Temporary fork until vuejs/vue#7839 arrives. diff --git a/lib/builder/webpack/plugins/vue/client.js b/lib/builder/webpack/plugins/vue/client.js new file mode 100644 index 0000000000..76fcad5ed2 --- /dev/null +++ b/lib/builder/webpack/plugins/vue/client.js @@ -0,0 +1,70 @@ +const hash = require('hash-sum') +const uniq = require('lodash.uniq') +const { isJS, onEmit } = require('./util') + +module.exports = class VueSSRClientPlugin { + constructor(options = {}) { + this.options = Object.assign({ + filename: 'vue-ssr-client-manifest.json' + }, options) + } + + apply(compiler) { + onEmit(compiler, 'vue-client-plugin', (compilation, cb) => { + const stats = compilation.getStats().toJson() + + const allFiles = uniq(stats.assets + .map(a => a.name)) + + const initialFiles = uniq(Object.keys(stats.entrypoints) + .map(name => stats.entrypoints[name].assets) + .reduce((assets, all) => all.concat(assets), []) + .filter(isJS)) + + const asyncFiles = allFiles + .filter(isJS) + .filter(file => initialFiles.indexOf(file) < 0) + + const manifest = { + publicPath: stats.publicPath, + all: allFiles, + initial: initialFiles, + async: asyncFiles, + modules: { /* [identifier: string]: Array */ } + } + + 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) + if (!chunk || !chunk.files) { + return + } + 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() + }) + } +} diff --git a/lib/builder/webpack/plugins/vue/server.js b/lib/builder/webpack/plugins/vue/server.js new file mode 100644 index 0000000000..e3cc74940a --- /dev/null +++ b/lib/builder/webpack/plugins/vue/server.js @@ -0,0 +1,66 @@ +const { validate, isJS, onEmit } = require('./util') + +module.exports = class VueSSRServerPlugin { + constructor(options = {}) { + this.options = Object.assign({ + filename: 'vue-ssr-server-bundle.json' + }, options) + } + + apply(compiler) { + validate(compiler) + + onEmit(compiler, 'vue-server-plugin', (compilation, cb) => { + const stats = compilation.getStats().toJson() + const entryName = Object.keys(stats.entrypoints)[0] + const entryInfo = stats.entrypoints[entryName] + + if (!entryInfo) { + // #5553 + return cb() + } + + const entryAssets = entryInfo.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(/\.js\.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() + }) + } +} diff --git a/lib/builder/webpack/plugins/vue/util.js b/lib/builder/webpack/plugins/vue/util.js new file mode 100644 index 0000000000..0f5377946a --- /dev/null +++ b/lib/builder/webpack/plugins/vue/util.js @@ -0,0 +1,43 @@ +const { red, yellow } = require('chalk') + +const prefix = `[vue-server-renderer-webpack-plugin]` +const warn = exports.warn = msg => console.error(red(`${prefix} ${msg}\n`)) // eslint-disable-line no-console +const tip = exports.tip = msg => console.log(yellow(`${prefix} ${msg}\n`)) // eslint-disable-line no-console + +exports.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 in the server build for ' + + 'better build performance.' + ) + } +} + +exports.onEmit = (compiler, name, hook) => { + if (compiler.hooks) { + // Webpack >= 4.0.0 + compiler.hooks.emit.tap(name, + (compilation) => new Promise((resolve, reject) => { + try { + hook(compilation, resolve) + } catch (e) { + reject(e) + } + })) + } else { + // Webpack < 4.0.0 + compiler.plugin('emit', hook) + } +} + +exports.isJS = (file) => /\.js(\?[^.]+)?$/.test(file) + +exports.isCSS = (file) => /\.css(\?[^.]+)?$/.test(file) diff --git a/lib/builder/webpack/server.config.js b/lib/builder/webpack/server.config.js index 7e42f1935e..25f0017cd0 100644 --- a/lib/builder/webpack/server.config.js +++ b/lib/builder/webpack/server.config.js @@ -1,5 +1,6 @@ const webpack = require('webpack') -const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') +// const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') +const VueSSRServerPlugin = require('./plugins/vue/server') const nodeExternals = require('webpack-node-externals') const { each } = require('lodash') const { resolve } = require('path')