Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ssr): vue-ssr-webpack-plugin compatible with webpack 5 #12002

Merged
merged 2 commits into from Jun 2, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/vue-server-renderer/package.json
Expand Up @@ -31,5 +31,8 @@
"devDependencies": {
"vue": "file:../.."
},
"peerDependencies": {
"webpack": "*"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is an optional peer dependency.
Users should be able to use this package without webpack.

},
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/vue-server-renderer#readme"
}
10 changes: 6 additions & 4 deletions src/server/webpack-plugin/client.js
@@ -1,6 +1,6 @@
const hash = require('hash-sum')
const uniq = require('lodash.uniq')
import { isJS, isCSS, onEmit } from './util'
import { isJS, isCSS, getAssetName, onEmit, stripModuleIdHash } from './util'

export default class VueSSRClientPlugin {
constructor (options = {}) {
Expand All @@ -10,7 +10,8 @@ export default class VueSSRClientPlugin {
}

apply (compiler) {
onEmit(compiler, 'vue-client-plugin', (compilation, cb) => {
const stage = 'PROCESS_ASSETS_STAGE_ADDITIONAL'
onEmit(compiler, 'vue-client-plugin', stage, (compilation, cb) => {
const stats = compilation.getStats().toJson()

const allFiles = uniq(stats.assets
Expand All @@ -19,6 +20,7 @@ export default class VueSSRClientPlugin {
const initialFiles = uniq(Object.keys(stats.entrypoints)
.map(name => stats.entrypoints[name].assets)
.reduce((assets, all) => all.concat(assets), [])
.map(getAssetName)
.filter((file) => isJS(file) || isCSS(file)))

const asyncFiles = allFiles
Expand All @@ -34,7 +36,7 @@ export default class VueSSRClientPlugin {
}

const assetModules = stats.modules.filter(m => m.assets.length)
const fileToIndex = file => manifest.all.indexOf(file)
const fileToIndex = asset => manifest.all.indexOf(getAssetName(asset))
stats.modules.forEach(m => {
// ignore modules duplicated in multiple chunks
if (m.chunks.length === 1) {
Expand All @@ -43,7 +45,7 @@ export default class VueSSRClientPlugin {
if (!chunk || !chunk.files) {
return
}
const id = m.identifier.replace(/\s\w+$/, '') // remove appended hash
const id = stripModuleIdHash(m.identifier)
const files = manifest.modules[hash(id)] = chunk.files.map(fileToIndex)
// find all asset modules associated with the same chunk
assetModules.forEach(m => {
Expand Down
21 changes: 12 additions & 9 deletions src/server/webpack-plugin/server.js
@@ -1,4 +1,4 @@
import { validate, isJS, onEmit } from './util'
import { validate, isJS, getAssetName, onEmit } from './util'

export default class VueSSRServerPlugin {
constructor (options = {}) {
Expand All @@ -10,7 +10,8 @@ export default class VueSSRServerPlugin {
apply (compiler) {
validate(compiler)

onEmit(compiler, 'vue-server-plugin', (compilation, cb) => {
const stage = 'PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just put the stage name in the onEmit function? It's not used anywhere else

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sodatea client plugin and server plugin are applied in different stage, so I think it's better to specify the stage name here.

onEmit(compiler, 'vue-server-plugin', stage, (compilation, cb) => {
const stats = compilation.getStats().toJson()
const entryName = Object.keys(stats.entrypoints)[0]
const entryInfo = stats.entrypoints[entryName]
Expand All @@ -20,7 +21,9 @@ export default class VueSSRServerPlugin {
return cb()
}

const entryAssets = entryInfo.assets.filter(isJS)
const entryAssets = entryInfo.assets
.map(getAssetName)
.filter(isJS)

if (entryAssets.length > 1) {
throw new Error(
Expand All @@ -42,14 +45,14 @@ export default class VueSSRServerPlugin {
maps: {}
}

stats.assets.forEach(asset => {
if (isJS(asset.name)) {
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())
Object.keys(compilation.assets).forEach(name => {
if (isJS(name)) {
bundle.files[name] = compilation.assets[name].source()
} else if (name.match(/\.js\.map$/)) {
bundle.maps[name.replace(/\.map$/, '')] = JSON.parse(compilation.assets[name].source())
}
// do not emit anything else for server
delete compilation.assets[asset.name]
delete compilation.assets[name]
})

const json = JSON.stringify(bundle, null, 2)
Expand Down
47 changes: 43 additions & 4 deletions src/server/webpack-plugin/util.js
@@ -1,16 +1,27 @@
const { red, yellow } = require('chalk')
const webpack = require('webpack')

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`))

const isWebpack5 = !!(webpack.version && webpack.version[0] > 4)

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.output) {
if (compiler.options.output.library) {
// Webpack >= 5.0.0
if (compiler.options.output.library.type !== 'commonjs2') {
warn('webpack config `output.library.type` should be "commonjs2".')
}
} else if (compiler.options.output.libraryTarget !== 'commonjs2') {
// Webpack < 5.0.0
warn('webpack config `output.libraryTarget` should be "commonjs2".')
}
}

if (!compiler.options.externals) {
Expand All @@ -21,8 +32,20 @@ export const validate = compiler => {
}
}

export const onEmit = (compiler, name, hook) => {
if (compiler.hooks) {
export const onEmit = (compiler, name, stageName, hook) => {
if (isWebpack5) {
// Webpack >= 5.0.0
compiler.hooks.compilation.tap(name, compilation => {
if (compilation.compiler !== compiler) {
// Ignore child compilers
return
}
const stage = webpack.Compilation[stageName]
compilation.hooks.processAssets.tapAsync({ name, stage }, (assets, cb) => {
hook(compilation, cb)
})
})
} else if (compiler.hooks) {
// Webpack >= 4.0.0
compiler.hooks.emit.tapAsync(name, hook)
} else {
Expand All @@ -31,4 +54,20 @@ export const onEmit = (compiler, name, hook) => {
}
}

export const stripModuleIdHash = id => {
if (isWebpack5) {
// Webpack >= 5.0.0
return id.replace(/\|\w+$/, '')
}
// Webpack < 5.0.0
return id.replace(/\s\w+$/, '')
}

export const getAssetName = asset => {
if (typeof asset === 'string') {
return asset
}
return asset.name
}

export { isJS, isCSS } from '../util'