const { info, openBrowser, IpcMessenger } = require('@vue/cli-shared-utils') const defaults = { host: '0.0.0.0', port: 8080, https: false } module.exports = (api, options) => { api.registerCommand('uni-serve', { description: 'start development server', usage: 'vue-cli-service uni-serve [options] [entry]', options: { '--open': 'open browser on server start', '--copy': 'copy url to clipboard on server start', '--mode': 'specify env mode (default: development)', '--host': `specify host (default: ${defaults.host})`, '--port': `specify port (default: ${defaults.port})`, '--https': `use https (default: ${defaults.https})`, '--public': 'specify the public network URL for the HMR client', '--auto-host': 'specify automator host', '--auto-port': 'specify automator port' } }, async function serve (args) { info('Starting development server...') require('./util').initAutomator(args) // although this is primarily a dev server, it is possible that we // are running it in a mode with a production env, e.g. in E2E tests. const isInContainer = checkInContainer() const isProduction = process.env.NODE_ENV === 'production' const url = require('url') const path = require('path') const { chalk } = require('@vue/cli-shared-utils') const webpack = require('webpack') const WebpackDevServer = require('webpack-dev-server') const portfinder = require('portfinder') const prepareURLs = require('@vue/cli-service/lib/util/prepareURLs') const prepareProxy = require('@vue/cli-service/lib/util/prepareProxy') const launchEditorMiddleware = require('launch-editor-middleware') const validateWebpackConfig = require('@vue/cli-service/lib/util/validateWebpackConfig') const isAbsoluteUrl = require('@vue/cli-service/lib/util/isAbsoluteUrl') // resolve webpack config const webpackConfig = api.resolveWebpackConfig() // check for common config errors validateWebpackConfig(webpackConfig, api, options) // load user devServer options with higher priority than devServer // in webpck config const projectDevServerOptions = Object.assign( webpackConfig.devServer || {}, options.devServer ) // expose advanced stats if (args.dashboard) { const DashboardPlugin = require('@vue/cli-service/lib/webpack/DashboardPlugin'); (webpackConfig.plugins = webpackConfig.plugins || []).push(new DashboardPlugin({ type: 'serve' })) } // entry arg const entry = args._[0] if (entry) { webpackConfig.entry = { app: api.resolve(entry) } } // resolve server options const useHttps = args.https || projectDevServerOptions.https || defaults.https const protocol = useHttps ? 'https' : 'http' const host = args.host || process.env.HOST || projectDevServerOptions.host || defaults.host portfinder.basePort = args.port || process.env.PORT || projectDevServerOptions.port || defaults .port const port = await portfinder.getPortPromise() const rawPublicUrl = args.public || projectDevServerOptions.public const publicUrl = rawPublicUrl ? /^[a-zA-Z]+:\/\//.test(rawPublicUrl) ? rawPublicUrl : `${protocol}://${rawPublicUrl}` : null const urls = prepareURLs( protocol, host, port, isAbsoluteUrl(options.publicPath) ? '/' : options.publicPath ) const proxySettings = prepareProxy( projectDevServerOptions.proxy, api.resolve('public') ) // inject dev & hot-reload middleware entries if (!isProduction) { const sockjsUrl = publicUrl // explicitly configured via devServer.public ? `?${publicUrl}/sockjs-node` : isInContainer // can't infer public netowrk url if inside a container... // use client-side inference (note this would break with non-root publicPath) ? '' // otherwise infer the url : '?' + url.format({ protocol, port, hostname: urls.lanUrlForConfig || 'localhost', pathname: '/sockjs-node' }) const devClients = [ // dev server client require.resolve('webpack-dev-server/client') + sockjsUrl, // hmr client require.resolve(projectDevServerOptions.hotOnly ? 'webpack/hot/only-dev-server' : 'webpack/hot/dev-server') // TODO custom overlay client // `@vue/cli-overlay/dist/client` ] if (process.env.APPVEYOR) { devClients.push('webpack/hot/poll?500') } // inject dev/hot client addDevClientToEntry(webpackConfig, devClients) } // create compiler const compiler = webpack(webpackConfig) // create server let server if (webpack.version[0] > 4) { server = new WebpackDevServer(Object.assign({ historyApiFallback: { disableDotRule: true, rewrites: [{ from: /./, to: path.posix.join(options.publicPath, 'index.html') }] }, hot: !isProduction, compress: isProduction, static: { directory: api.resolve('public'), publicPath: options.publicPath, watch: !isProduction, ...projectDevServerOptions.static }, client: { logging: 'none', overlay: isProduction // TODO disable this ? false : { warnings: false, errors: true }, progress: !process.env.VUE_CLI_TEST, ...projectDevServerOptions.client } }, projectDevServerOptions, { https: useHttps, proxy: proxySettings, setupMiddlewares (middlewares, devServer) { // launch editor support. // this works with vue-devtools & @vue/cli-overlay devServer.app.use('/__open-in-editor', launchEditorMiddleware(() => console.log( `To specify an editor, specify the EDITOR env variable or ` + `add "editor" field to your Vue project config.\n` ))) // allow other plugins to register middlewares, e.g. PWA // todo: migrate to the new API interface api.service.devServerConfigFns.forEach(fn => fn(devServer.app, devServer)) if (projectDevServerOptions.setupMiddlewares) { return projectDevServerOptions.setupMiddlewares(middlewares, devServer) } return middlewares } }), compiler) } else { server = new WebpackDevServer(compiler, Object.assign({ clientLogLevel: 'none', historyApiFallback: { disableDotRule: true, rewrites: [{ from: /./, to: path.posix.join(options.publicPath, 'index.html') }] }, contentBase: api.resolve('public'), watchContentBase: !isProduction, hot: !isProduction, quiet: true, compress: isProduction, publicPath: options.publicPath, overlay: isProduction // TODO disable this ? false : { warnings: false, errors: true } }, projectDevServerOptions, { https: useHttps, proxy: proxySettings, before (app, server) { // launch editor support. // this works with vue-devtools & @vue/cli-overlay app.use('/__open-in-editor', launchEditorMiddleware(() => console.log( 'To specify an editor, sepcify the EDITOR env variable or ' + 'add "editor" field to your Vue project config.\n' ))) // allow other plugins to register middlewares, e.g. PWA api.service.devServerConfigFns.forEach(fn => fn(app, server)) // apply in project middlewares projectDevServerOptions.before && projectDevServerOptions.before(app, server) } })) } ; ['SIGINT', 'SIGTERM'].forEach(signal => { process.on(signal, () => { server[webpack.version[0] > 4 ? 'stopCallback' : 'close'](() => { process.exit(0) }) }) }) // on appveyor, killing the process with SIGTERM causes execa to // throw error if (process.env.VUE_CLI_TEST) { process.stdin.on('data', data => { if (data.toString() === 'close') { console.log('got close signal!') server[webpack.version[0] > 4 ? 'stopCallback' : 'close'](() => { process.exit(0) }) } }) } return new Promise((resolve, reject) => { const { runByHBuilderX } = require('@dcloudio/uni-cli-shared') // log instructions & open browser on first compilation complete let isFirstCompile = true compiler.hooks.done.tap('vue-cli-service uni-serve', stats => { if (stats.hasErrors()) { return } let copied = '' if (isFirstCompile && args.copy) { require('clipboardy').write(urls.localUrlForBrowser) copied = chalk.dim('(copied to clipboard)') } const networkUrl = publicUrl ? publicUrl.replace(/([^/])$/, '$1/') : urls.lanUrlForTerminal const printRunningAt = !runByHBuilderX || (runByHBuilderX && isFirstCompile) printRunningAt && console.log() printRunningAt && console.log(' App running at:') printRunningAt && console.log( ` - Local: ${chalk.cyan(urls.localUrlForTerminal)} ${copied}` ) if (!printRunningAt) { console.log('Build complete. Watching for changes...') } if (!isInContainer) { printRunningAt && console.log(` - Network: ${chalk.cyan(networkUrl)}`) } else { console.log() console.log(chalk.yellow( ' It seems you are running Vue CLI inside a container.' )) if (!publicUrl && options.publicPath && options.publicPath !== '/') { console.log() console.log(chalk.yellow( ' Since you are using a non-root publicPath, the hot-reload socket' )) console.log(chalk.yellow( ' will not be able to infer the correct URL to connect. You should' )) console.log(chalk.yellow( ` explicitly specify the URL via ${chalk.blue('devServer.public')}.` )) console.log() } console.log(chalk.yellow( ` Access the dev server via ${chalk.cyan( `${protocol}://localhost:${options.publicPath}` )}` )) } console.log() if (isFirstCompile) { isFirstCompile = false if (!isProduction) { // const buildCommand = hasProjectYarn(api.getCwd()) ? `yarn build` : `npm run build` // console.log(` Note that the development build is not optimized.`) // console.log(` To create a production build, run ${chalk.cyan(buildCommand)}.`) } else { console.log(' App is served in production mode.') console.log(' Note this is for preview or E2E testing only.') } console.log() if (args.open || projectDevServerOptions.open) { const pageUri = (projectDevServerOptions.openPage && typeof projectDevServerOptions .openPage === 'string') ? projectDevServerOptions.openPage : '' openBrowser(urls.localUrlForBrowser + pageUri) } // Send final app URL if (args.dashboard) { const ipc = new IpcMessenger() ipc.send({ vueServe: { url: urls.localUrlForBrowser } }) } // resolve returned Promise // so other commands can do api.service.run('serve').then(...) resolve({ server, url: urls.localUrlForBrowser }) } else if (process.env.VUE_CLI_TEST) { // signal for test to check HMR console.log('App updated') } }) if (server.showStatus) { server.showStatus = function () {} } if (webpack.version[0] > 4) { server.start().catch(err => reject(err)) } else { server.listen(port, host, err => { if (err) { reject(err) } }) } }) }) } function addDevClientToEntry (config, devClient) { const { entry } = config if (typeof entry === 'object' && !Array.isArray(entry)) { Object.keys(entry).forEach((key) => { entry[key] = devClient.concat(entry[key]) }) } else if (typeof entry === 'function') { config.entry = entry(devClient) } else { config.entry = devClient.concat(entry) } } // https://stackoverflow.com/a/20012536 function checkInContainer () { const fs = require('fs') if (fs.existsSync('/proc/1/cgroup')) { const content = fs.readFileSync('/proc/1/cgroup', 'utf-8') return /:\/(lxc|docker|kubepods)\//.test(content) } } module.exports.defaultModes = { serve: 'development' }