From a2bff57b0d1ba6bd8241f151d496e5e808b5936c Mon Sep 17 00:00:00 2001 From: Evan You Date: Sun, 16 Apr 2017 21:02:58 +0800 Subject: [PATCH] include ssr webpack plugin in main repo --- build/config.js | 70 ++++++++++++++--------- package.json | 1 + packages/vue-server-renderer/package.json | 2 + src/server/webpack-plugin/client.js | 66 +++++++++++++++++++++ src/server/webpack-plugin/server.js | 59 +++++++++++++++++++ src/server/webpack-plugin/util.js | 25 ++++++++ yarn.lock | 6 +- 7 files changed, 198 insertions(+), 31 deletions(-) create mode 100644 src/server/webpack-plugin/client.js create mode 100644 src/server/webpack-plugin/server.js create mode 100644 src/server/webpack-plugin/util.js diff --git a/build/config.js b/build/config.js index c9bcb679..64330e41 100644 --- a/build/config.js +++ b/build/config.js @@ -1,8 +1,8 @@ const path = require('path') -const flow = require('rollup-plugin-flow-no-whitespace') const buble = require('rollup-plugin-buble') -const replace = require('rollup-plugin-replace') const alias = require('rollup-plugin-alias') +const replace = require('rollup-plugin-replace') +const flow = require('rollup-plugin-flow-no-whitespace') const version = process.env.VERSION || require('../package.json').version const weexVersion = process.env.WEEX_VERSION || require('../packages/weex-vue-framework/package.json').version @@ -22,18 +22,20 @@ const weexFactoryPlugin = { } } +const resolve = p => path.resolve(__dirname, '../' + p) + const builds = { // Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify 'web-runtime-cjs': { - entry: path.resolve(__dirname, '../src/entries/web-runtime.js'), - dest: path.resolve(__dirname, '../dist/vue.runtime.common.js'), + entry: resolve('src/entries/web-runtime.js'), + dest: resolve('dist/vue.runtime.common.js'), format: 'cjs', banner }, // Runtime+compiler CommonJS build (CommonJS) 'web-full-cjs': { - entry: path.resolve(__dirname, '../src/entries/web-runtime-with-compiler.js'), - dest: path.resolve(__dirname, '../dist/vue.common.js'), + entry: resolve('src/entries/web-runtime-with-compiler.js'), + dest: resolve('dist/vue.common.js'), format: 'cjs', alias: { he: './entity-decoder' }, banner @@ -41,39 +43,39 @@ const builds = { // Runtime only (ES Modules). Used by bundlers that support ES Modules, // e.g. Rollup & Webpack 2 'web-runtime-esm': { - entry: path.resolve(__dirname, '../src/entries/web-runtime.js'), - dest: path.resolve(__dirname, '../dist/vue.runtime.esm.js'), + entry: resolve('src/entries/web-runtime.js'), + dest: resolve('dist/vue.runtime.esm.js'), format: 'es', banner }, // Runtime+compiler CommonJS build (ES Modules) 'web-full-esm': { - entry: path.resolve(__dirname, '../src/entries/web-runtime-with-compiler.js'), - dest: path.resolve(__dirname, '../dist/vue.esm.js'), + entry: resolve('src/entries/web-runtime-with-compiler.js'), + dest: resolve('dist/vue.esm.js'), format: 'es', alias: { he: './entity-decoder' }, banner }, // runtime-only build (Browser) 'web-runtime-dev': { - entry: path.resolve(__dirname, '../src/entries/web-runtime.js'), - dest: path.resolve(__dirname, '../dist/vue.runtime.js'), + entry: resolve('src/entries/web-runtime.js'), + dest: resolve('dist/vue.runtime.js'), format: 'umd', env: 'development', banner }, // runtime-only production build (Browser) 'web-runtime-prod': { - entry: path.resolve(__dirname, '../src/entries/web-runtime.js'), - dest: path.resolve(__dirname, '../dist/vue.runtime.min.js'), + entry: resolve('src/entries/web-runtime.js'), + dest: resolve('dist/vue.runtime.min.js'), format: 'umd', env: 'production', banner }, // Runtime+compiler development build (Browser) 'web-full-dev': { - entry: path.resolve(__dirname, '../src/entries/web-runtime-with-compiler.js'), - dest: path.resolve(__dirname, '../dist/vue.js'), + entry: resolve('src/entries/web-runtime-with-compiler.js'), + dest: resolve('dist/vue.js'), format: 'umd', env: 'development', alias: { he: './entity-decoder' }, @@ -81,8 +83,8 @@ const builds = { }, // Runtime+compiler production build (Browser) 'web-full-prod': { - entry: path.resolve(__dirname, '../src/entries/web-runtime-with-compiler.js'), - dest: path.resolve(__dirname, '../dist/vue.min.js'), + entry: resolve('src/entries/web-runtime-with-compiler.js'), + dest: resolve('dist/vue.min.js'), format: 'umd', env: 'production', alias: { he: './entity-decoder' }, @@ -90,38 +92,50 @@ const builds = { }, // Web compiler (CommonJS). 'web-compiler': { - entry: path.resolve(__dirname, '../src/entries/web-compiler.js'), - dest: path.resolve(__dirname, '../packages/vue-template-compiler/build.js'), + entry: resolve('src/entries/web-compiler.js'), + dest: resolve('packages/vue-template-compiler/build.js'), format: 'cjs', external: Object.keys(require('../packages/vue-template-compiler/package.json').dependencies) }, // Web server renderer (CommonJS). 'web-server-renderer': { - entry: path.resolve(__dirname, '../src/entries/web-server-renderer.js'), - dest: path.resolve(__dirname, '../packages/vue-server-renderer/build.js'), + entry: resolve('src/entries/web-server-renderer.js'), + dest: resolve('packages/vue-server-renderer/build.js'), + format: 'cjs', + external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies) + }, + 'web-server-renderer-webpack-server-plugin': { + entry: resolve('src/server/webpack-plugin/server.js'), + dest: resolve('packages/vue-server-renderer/server-plugin.js'), + format: 'cjs', + external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies) + }, + 'web-server-renderer-webpack-client-plugin': { + entry: resolve('src/server/webpack-plugin/client.js'), + dest: resolve('packages/vue-server-renderer/client-plugin.js'), format: 'cjs', external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies) }, // Weex runtime factory 'weex-factory': { weex: true, - entry: path.resolve(__dirname, '../src/entries/weex-factory.js'), - dest: path.resolve(__dirname, '../packages/weex-vue-framework/factory.js'), + entry: resolve('src/entries/weex-factory.js'), + dest: resolve('packages/weex-vue-framework/factory.js'), format: 'cjs', plugins: [weexFactoryPlugin] }, // Weex runtime framework (CommonJS). 'weex-framework': { weex: true, - entry: path.resolve(__dirname, '../src/entries/weex-framework.js'), - dest: path.resolve(__dirname, '../packages/weex-vue-framework/index.js'), + entry: resolve('src/entries/weex-framework.js'), + dest: resolve('packages/weex-vue-framework/index.js'), format: 'cjs' }, // Weex compiler (CommonJS). Used by Weex's Webpack loader. 'weex-compiler': { weex: true, - entry: path.resolve(__dirname, '../src/entries/weex-compiler.js'), - dest: path.resolve(__dirname, '../packages/weex-template-compiler/build.js'), + entry: resolve('src/entries/weex-compiler.js'), + dest: resolve('packages/weex-template-compiler/build.js'), format: 'cjs', external: Object.keys(require('../packages/weex-template-compiler/package.json').dependencies) } diff --git a/package.json b/package.json index 357e97cf..633f9b83 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "babel-preset-es2015": "^6.9.0", "babel-preset-flow-vue": "^1.0.0", "buble": "^0.15.2", + "chalk": "^1.1.3", "chromedriver": "^2.21.2", "codecov.io": "^0.1.6", "cross-spawn": "^5.0.1", diff --git a/packages/vue-server-renderer/package.json b/packages/vue-server-renderer/package.json index 20a60780..a2ddcbcf 100644 --- a/packages/vue-server-renderer/package.json +++ b/packages/vue-server-renderer/package.json @@ -18,6 +18,8 @@ "url": "https://github.com/vuejs/vue/issues" }, "dependencies": { + "chalk": "^1.1.3", + "hash-sum": "^1.0.2", "he": "^1.1.0", "de-indent": "^1.0.2", "resolve": "^1.2.0", diff --git a/src/server/webpack-plugin/client.js b/src/server/webpack-plugin/client.js new file mode 100644 index 00000000..dbce4f85 --- /dev/null +++ b/src/server/webpack-plugin/client.js @@ -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 */ } + } + + 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() + }) + } +} diff --git a/src/server/webpack-plugin/server.js b/src/server/webpack-plugin/server.js new file mode 100644 index 00000000..4678cb1c --- /dev/null +++ b/src/server/webpack-plugin/server.js @@ -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() + }) + } +} diff --git a/src/server/webpack-plugin/util.js b/src/server/webpack-plugin/util.js new file mode 100644 index 00000000..3ae5738c --- /dev/null +++ b/src/server/webpack-plugin/util.js @@ -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) diff --git a/yarn.lock b/yarn.lock index ae6c4f44..4d442821 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5058,9 +5058,9 @@ void-elements@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" -vue-ssr-webpack-plugin@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/vue-ssr-webpack-plugin/-/vue-ssr-webpack-plugin-2.0.2.tgz#80709e7d53e1fe7085ad3dc73afff71c069be715" +vue-ssr-webpack-plugin@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/vue-ssr-webpack-plugin/-/vue-ssr-webpack-plugin-3.0.0.tgz#db47769ed8e71c8eb53aa9ae7be9ff04baf546fa" dependencies: chalk "^1.1.3" hash-sum "^1.0.2"