Skip to content

Commit 967f948

Browse files
authored
feat!: upgrade to webpack-dev-server v4 (#6669)
1 parent 662153b commit 967f948

File tree

8 files changed

+353
-121
lines changed

8 files changed

+353
-121
lines changed

docs/migrations/migrate-from-v4.md

+11-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,17 @@ Besides the internal changes that are only noticeable for custom configurations,
6060
1. Named exports from JSON modules are no longer supported. Instead of `import { version } from './package.json'; console.log(version);` use `import package from './package.json'; console.log(package.version);`
6161
2. Webpack 5 does no longer include polyfills for Node.js modules by default. You shall see an informative error message if your code relies on any of these modules. A detailed list of previously polyfilled modules is also available [here](https://github.com/webpack/webpack/pull/8460/commits/a68426e9255edcce7822480b78416837617ab065).
6262

63-
#### Changes to the `build` command and modern mode
63+
#### Dev Server
64+
65+
`webpack-dev-server` has been updated from v3 to v4. So there are breaking changes with regard to the `devServer` option in `vue.config.js`. Please check out the [`webpack-dev-server` migration guide](https://github.com/webpack/webpack-dev-server/blob/master/migration-v4.md) for more details.
66+
67+
Most notably:
68+
69+
* The `disableHostCheck` option was removed in favor `allowedHosts: 'all'`;
70+
* `public`, `sockHost`, `sockPath`, and `sockPort` options were removed in favor `client.webSocketURL` option.
71+
* IE9 support of the dev server is not enabled by default. If you need to develop under IE9, please manually set the `devServer.webSocketServer` option to `sockjs`.
72+
73+
#### The `build` Command and Modern Mode
6474

6575
Starting with v5.0.0-beta.0, running `vue-cli-service build` will automatically generate different bundles based on your browserslist configurations.
6676
The `--modern` flag is no longer needed because it is turned on by default.

packages/@vue/cli-plugin-eslint/__tests__/eslintPlugin.spec.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,8 @@ test(`should use formatter 'codeframe'`, async () => {
260260
} else if (data.match(/semi/)) {
261261
// check the format of output
262262
// https://eslint.org/docs/user-guide/formatters/#codeframe
263-
expect(data).toMatch(`error: Missing semicolon (semi) at src${path.sep}main.js`)
263+
expect(data).toMatch(`error`)
264+
expect(data).toMatch(`Missing semicolon (semi) at src${path.sep}main.js`)
264265

265266
server.stdin.write('close')
266267
done()

packages/@vue/cli-service/__tests__/multiPage.spec.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,10 @@ test('serve w/ multi page', async () => {
7070
async ({ page, url, helpers }) => {
7171
expect(await helpers.getText('h1')).toMatch(`Welcome to Your Vue.js App`)
7272

73-
await page.goto(`${url}/foo.html`)
73+
await page.goto(`${url}foo.html`)
7474
expect(await helpers.getText('h1')).toMatch(`Foo`)
7575

76-
await page.goto(`${url}/bar.html`)
76+
await page.goto(`${url}bar.html`)
7777
expect(await helpers.getText('h1')).toMatch(`Welcome to Your Vue.js App`)
7878

7979
await page.goto(`${url}foo`)

packages/@vue/cli-service/__tests__/serve.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ test('use a single websocket connection for HMR', async () => {
177177
const msg = `Welcome to Your Vue.js App`
178178
expect(await helpers.getText('h1')).toMatch(msg)
179179

180-
expect(requestUrls.filter(url => url.includes('sockjs-node')).length).toBe(1)
180+
expect(requestUrls.filter(url => url.includes('ws://')).length).toBe(1)
181181
}
182182
)
183183
})

packages/@vue/cli-service/lib/commands/serve.js

+82-90
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ const {
33
error,
44
hasProjectYarn,
55
hasProjectPnpm,
6-
openBrowser,
76
IpcMessenger
87
} = require('@vue/cli-shared-utils')
98

@@ -37,7 +36,6 @@ module.exports = (api, options) => {
3736
const isInContainer = checkInContainer()
3837
const isProduction = process.env.NODE_ENV === 'production'
3938

40-
const url = require('url')
4139
const { chalk } = require('@vue/cli-shared-utils')
4240
const webpack = require('webpack')
4341
const WebpackDevServer = require('webpack-dev-server')
@@ -56,20 +54,17 @@ module.exports = (api, options) => {
5654
.devtool('eval-cheap-module-source-map')
5755
}
5856

59-
webpackConfig
60-
.plugin('hmr')
61-
.use(require('webpack/lib/HotModuleReplacementPlugin'))
62-
6357
// https://github.com/webpack/webpack/issues/6642
6458
// https://github.com/vuejs/vue-cli/issues/3539
6559
webpackConfig
6660
.output
6761
.globalObject(`(typeof self !== 'undefined' ? self : this)`)
6862

6963
if (!process.env.VUE_CLI_TEST && options.devServer.progress !== false) {
64+
// the default progress plugin won't show progress due to infrastructreLogging.level
7065
webpackConfig
7166
.plugin('progress')
72-
.use(webpack.ProgressPlugin)
67+
.use(require('progress-webpack-plugin'))
7368
}
7469
}
7570
})
@@ -90,7 +85,7 @@ module.exports = (api, options) => {
9085
// expose advanced stats
9186
if (args.dashboard) {
9287
const DashboardPlugin = require('../webpack/DashboardPlugin')
93-
;(webpackConfig.plugins = webpackConfig.plugins || []).push(new DashboardPlugin({
88+
webpackConfig.plugins.push(new DashboardPlugin({
9489
type: 'serve'
9590
}))
9691
}
@@ -131,38 +126,50 @@ module.exports = (api, options) => {
131126
)
132127

133128
// inject dev & hot-reload middleware entries
129+
let webSocketURL
134130
if (!isProduction) {
135-
const sockPath = projectDevServerOptions.sockPath || '/sockjs-node'
136-
const sockjsUrl = publicUrl
131+
if (publicHost) {
137132
// explicitly configured via devServer.public
138-
? `?${publicUrl}&sockPath=${sockPath}`
139-
: isInContainer
140-
// can't infer public network url if inside a container...
141-
// use client-side inference (note this would break with non-root publicPath)
142-
? ``
143-
// otherwise infer the url
144-
: `?` + url.format({
145-
protocol,
146-
port,
147-
hostname: urls.lanUrlForConfig || 'localhost'
148-
}) + `&sockPath=${sockPath}`
149-
const devClients = [
150-
// dev server client
151-
require.resolve(`webpack-dev-server/client`) + sockjsUrl,
152-
// hmr client
153-
require.resolve(projectDevServerOptions.hotOnly
154-
? 'webpack/hot/only-dev-server'
155-
: 'webpack/hot/dev-server')
156-
// TODO custom overlay client
157-
// `@vue/cli-overlay/dist/client`
158-
]
133+
webSocketURL = {
134+
protocol: protocol === 'https' ? 'wss' : 'ws',
135+
hostname: publicHost,
136+
port
137+
}
138+
} else if (isInContainer) {
139+
// can't infer public network url if inside a container
140+
// infer it from the browser instead
141+
webSocketURL = 'auto://0.0.0.0:0/ws'
142+
} else {
143+
// otherwise infer the url from the config
144+
webSocketURL = {
145+
protocol: protocol === 'https' ? 'wss' : 'ws',
146+
hostname: urls.lanUrlForConfig || 'localhost',
147+
port
148+
}
149+
}
150+
159151
if (process.env.APPVEYOR) {
160-
devClients.push(`webpack/hot/poll?500`)
152+
webpackConfig.plugins.push(
153+
new webpack.EntryPlugin(__dirname, 'webpack/hot/poll?500', { name: undefined })
154+
)
161155
}
162-
// inject dev/hot client
163-
addDevClientToEntry(webpackConfig, devClients)
164156
}
165157

158+
const { projectTargets } = require('../util/targets')
159+
const supportsIE = !!projectTargets
160+
if (supportsIE) {
161+
webpackConfig.plugins.push(
162+
// must use undefined as name,
163+
// to avoid dev server establishing an extra ws connection for the new entry
164+
new webpack.EntryPlugin(__dirname, 'whatwg-fetch', { name: undefined })
165+
)
166+
}
167+
168+
// fixme: temporary fix to suppress dev server logging
169+
// should be more robust to show necessary info but not duplicate errors
170+
webpackConfig.infrastructureLogging = { ...webpackConfig.infrastructureLogging, level: 'none' }
171+
webpackConfig.stats = 'errors-only'
172+
166173
// create compiler
167174
const compiler = webpack(webpackConfig)
168175

@@ -173,9 +180,7 @@ module.exports = (api, options) => {
173180
})
174181

175182
// create server
176-
const server = new WebpackDevServer(compiler, Object.assign({
177-
logLevel: 'silent',
178-
clientLogLevel: 'silent',
183+
const server = new WebpackDevServer(Object.assign({
179184
historyApiFallback: {
180185
disableDotRule: true,
181186
htmlAcceptHeaders: [
@@ -184,47 +189,58 @@ module.exports = (api, options) => {
184189
],
185190
rewrites: genHistoryApiFallbackRewrites(options.publicPath, options.pages)
186191
},
187-
contentBase: api.resolve('public'),
188-
watchContentBase: !isProduction,
189-
hot: !isProduction,
190-
injectClient: false,
191-
compress: isProduction,
192-
publicPath: options.publicPath,
193-
overlay: isProduction // TODO disable this
194-
? false
195-
: { warnings: false, errors: true }
192+
hot: !isProduction
196193
}, projectDevServerOptions, {
194+
host,
195+
port,
197196
https: useHttps,
198197
proxy: proxySettings,
199-
public: publicHost,
198+
199+
static: {
200+
directory: api.resolve('public'),
201+
publicPath: options.publicPath,
202+
watch: !isProduction,
203+
204+
...projectDevServerOptions.static
205+
},
206+
207+
client: {
208+
webSocketURL,
209+
210+
logging: 'none',
211+
overlay: isProduction // TODO disable this
212+
? false
213+
: { warnings: false, errors: true },
214+
progress: !process.env.VUE_CLI_TEST,
215+
216+
...projectDevServerOptions.client
217+
},
218+
219+
open: args.open || projectDevServerOptions.open,
220+
setupExitSignals: true,
221+
200222
// eslint-disable-next-line no-shadow
201-
before (app, server) {
223+
onBeforeSetupMiddleware (server) {
202224
// launch editor support.
203225
// this works with vue-devtools & @vue/cli-overlay
204-
app.use('/__open-in-editor', launchEditorMiddleware(() => console.log(
226+
server.app.use('/__open-in-editor', launchEditorMiddleware(() => console.log(
205227
`To specify an editor, specify the EDITOR env variable or ` +
206228
`add "editor" field to your Vue project config.\n`
207229
)))
230+
208231
// allow other plugins to register middlewares, e.g. PWA
209-
api.service.devServerConfigFns.forEach(fn => fn(app, server))
210-
// apply in project middlewares
211-
projectDevServerOptions.before && projectDevServerOptions.before(app, server)
212-
},
213-
// avoid opening browser
214-
open: false
215-
}))
232+
// todo: migrate to the new API interface
233+
api.service.devServerConfigFns.forEach(fn => fn(server.app, server))
216234

217-
;['SIGINT', 'SIGTERM'].forEach(signal => {
218-
process.on(signal, () => {
219-
server.close(() => {
220-
process.exit(0)
221-
})
222-
})
223-
})
235+
if (projectDevServerOptions.onBeforeSetupMiddleware) {
236+
projectDevServerOptions.onBeforeSetupMiddleware(server)
237+
}
238+
}
239+
}), compiler)
224240

225241
if (args.stdin) {
226242
process.stdin.on('end', () => {
227-
server.close(() => {
243+
server.stopCallback(() => {
228244
process.exit(0)
229245
})
230246
})
@@ -238,7 +254,7 @@ module.exports = (api, options) => {
238254
process.stdin.on('data', data => {
239255
if (data.toString() === 'close') {
240256
console.log('got close signal!')
241-
server.close(() => {
257+
server.stopCallback(() => {
242258
process.exit(0)
243259
})
244260
}
@@ -301,13 +317,6 @@ module.exports = (api, options) => {
301317
}
302318
console.log()
303319

304-
if (args.open || projectDevServerOptions.open) {
305-
const pageUri = (projectDevServerOptions.openPage && typeof projectDevServerOptions.openPage === 'string')
306-
? projectDevServerOptions.openPage
307-
: ''
308-
openBrowser(localUrlForBrowser + pageUri)
309-
}
310-
311320
// Send final app URL
312321
if (args.dashboard) {
313322
const ipc = new IpcMessenger()
@@ -330,28 +339,11 @@ module.exports = (api, options) => {
330339
}
331340
})
332341

333-
server.listen(port, host, err => {
334-
if (err) {
335-
reject(err)
336-
}
337-
})
342+
server.start().catch(err => reject(err))
338343
})
339344
})
340345
}
341346

342-
function addDevClientToEntry (config, devClient) {
343-
const { entry } = config
344-
if (typeof entry === 'object' && !Array.isArray(entry)) {
345-
Object.keys(entry).forEach((key) => {
346-
entry[key] = devClient.concat(entry[key])
347-
})
348-
} else if (typeof entry === 'function') {
349-
config.entry = entry(devClient)
350-
} else {
351-
config.entry = devClient.concat(entry)
352-
}
353-
}
354-
355347
// https://stackoverflow.com/a/20012536
356348
function checkInContainer () {
357349
if ('CODESANDBOX_SSE' in process.env) {

packages/@vue/cli-service/package.json

+5-3
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"@soda/friendly-errors-webpack-plugin": "^1.8.0",
2828
"@soda/get-current-script": "^1.0.2",
2929
"@types/minimist": "^1.2.0",
30-
"@types/webpack-dev-server": "^3.11.0",
30+
"@types/webpack-dev-server": "^4.1.0",
3131
"@vue/cli-overlay": "^5.0.0-beta.3",
3232
"@vue/cli-plugin-router": "^5.0.0-beta.3",
3333
"@vue/cli-plugin-vuex": "^5.0.0-beta.3",
@@ -68,6 +68,7 @@
6868
"portfinder": "^1.0.26",
6969
"postcss": "^8.2.6",
7070
"postcss-loader": "^6.1.1",
71+
"progress-webpack-plugin": "^1.0.12",
7172
"ssri": "^8.0.1",
7273
"terser-webpack-plugin": "^5.1.1",
7374
"thread-loader": "^3.0.0",
@@ -77,9 +78,10 @@
7778
"webpack": "^5.22.0",
7879
"webpack-bundle-analyzer": "^4.4.0",
7980
"webpack-chain": "^6.5.1",
80-
"webpack-dev-server": "^3.11.2",
81+
"webpack-dev-server": "^4.1.0",
8182
"webpack-merge": "^5.7.3",
82-
"webpack-virtual-modules": "^0.4.2"
83+
"webpack-virtual-modules": "^0.4.2",
84+
"whatwg-fetch": "^3.6.2"
8385
},
8486
"peerDependencies": {
8587
"@vue/compiler-sfc": "^3.0.0-beta.14",

packages/@vue/cli-test-utils/launchPuppeteer.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,14 @@ module.exports = async function launchPuppeteer (url) {
1818
interceptedRequest.continue()
1919
})
2020

21-
await page.goto(url)
21+
const f12 = await page.target().createCDPSession()
22+
await f12.send('Network.enable')
23+
await f12.send('Page.enable')
24+
25+
f12.on('Network.webSocketCreated', ({ url: wsUrl }) => {
26+
requestUrls.push(wsUrl)
27+
})
2228

29+
await page.goto(url)
2330
return { browser, page, logs, requestUrls }
2431
}

0 commit comments

Comments
 (0)