You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							374 lines
						
					
					
						
							12 KiB
						
					
					
				
			
		
		
	
	
							374 lines
						
					
					
						
							12 KiB
						
					
					
				const { | 
						|
  info, | 
						|
  error, | 
						|
  hasProjectYarn, | 
						|
  hasProjectPnpm, | 
						|
  openBrowser, | 
						|
  IpcMessenger | 
						|
} = require('@vue/cli-shared-utils') | 
						|
 | 
						|
const defaults = { | 
						|
  host: '0.0.0.0', | 
						|
  port: 8080, | 
						|
  https: false | 
						|
} | 
						|
 | 
						|
module.exports = (api, options) => { | 
						|
  api.registerCommand('serve', { | 
						|
    description: 'start development server', | 
						|
    usage: 'vue-cli-service serve [options] [entry]', | 
						|
    options: { | 
						|
      '--open': `open browser on server start`, | 
						|
      '--copy': `copy url to clipboard on server start`, | 
						|
      '--stdin': `close when stdin ends`, | 
						|
      '--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`, | 
						|
      '--skip-plugins': `comma-separated list of plugin names to skip for this run` | 
						|
    } | 
						|
  }, async function serve (args) { | 
						|
    info('Starting development server...') | 
						|
 | 
						|
    // 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 { chalk } = require('@vue/cli-shared-utils') | 
						|
    const webpack = require('webpack') | 
						|
    const WebpackDevServer = require('webpack-dev-server') | 
						|
    const portfinder = require('portfinder') | 
						|
    const prepareURLs = require('../util/prepareURLs') | 
						|
    const prepareProxy = require('../util/prepareProxy') | 
						|
    const launchEditorMiddleware = require('launch-editor-middleware') | 
						|
    const validateWebpackConfig = require('../util/validateWebpackConfig') | 
						|
    const isAbsoluteUrl = require('../util/isAbsoluteUrl') | 
						|
 | 
						|
    // configs that only matters for dev server | 
						|
    api.chainWebpack(webpackConfig => { | 
						|
      if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') { | 
						|
        webpackConfig | 
						|
          .devtool('cheap-module-eval-source-map') | 
						|
 | 
						|
        webpackConfig | 
						|
          .plugin('hmr') | 
						|
            .use(require('webpack/lib/HotModuleReplacementPlugin')) | 
						|
 | 
						|
        // https://github.com/webpack/webpack/issues/6642 | 
						|
        // https://github.com/vuejs/vue-cli/issues/3539 | 
						|
        webpackConfig | 
						|
          .output | 
						|
            .globalObject(`(typeof self !== 'undefined' ? self : this)`) | 
						|
 | 
						|
        if (!process.env.VUE_CLI_TEST && options.devServer.progress !== false) { | 
						|
          webpackConfig | 
						|
            .plugin('progress') | 
						|
            .use(require('webpack/lib/ProgressPlugin')) | 
						|
        } | 
						|
      } | 
						|
    }) | 
						|
 | 
						|
    // 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 webpack config | 
						|
    const projectDevServerOptions = Object.assign( | 
						|
      webpackConfig.devServer || {}, | 
						|
      options.devServer | 
						|
    ) | 
						|
 | 
						|
    // expose advanced stats | 
						|
    if (args.dashboard) { | 
						|
      const DashboardPlugin = require('../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 localUrlForBrowser = publicUrl || urls.localUrlForBrowser | 
						|
 | 
						|
    const proxySettings = prepareProxy( | 
						|
      projectDevServerOptions.proxy, | 
						|
      api.resolve('public') | 
						|
    ) | 
						|
 | 
						|
    // inject dev & hot-reload middleware entries | 
						|
    if (!isProduction) { | 
						|
      const sockPath = projectDevServerOptions.sockPath || '/sockjs-node' | 
						|
      const sockjsUrl = publicUrl | 
						|
        // explicitly configured via devServer.public | 
						|
        ? `?${publicUrl}&sockPath=${sockPath}` | 
						|
        : isInContainer | 
						|
          // can't infer public network 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' | 
						|
          }) + `&sockPath=${sockPath}` | 
						|
      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) | 
						|
 | 
						|
    // handle compiler error | 
						|
    compiler.hooks.failed.tap('vue-cli-service serve', msg => { | 
						|
      error(msg) | 
						|
      process.exit(1) | 
						|
    }) | 
						|
 | 
						|
    // create server | 
						|
    const server = new WebpackDevServer(compiler, Object.assign({ | 
						|
      logLevel: 'silent', | 
						|
      clientLogLevel: 'silent', | 
						|
      historyApiFallback: { | 
						|
        disableDotRule: true, | 
						|
        rewrites: genHistoryApiFallbackRewrites(options.publicPath, options.pages) | 
						|
      }, | 
						|
      contentBase: api.resolve('public'), | 
						|
      watchContentBase: !isProduction, | 
						|
      hot: !isProduction, | 
						|
      injectClient: false, | 
						|
      compress: isProduction, | 
						|
      publicPath: options.publicPath, | 
						|
      overlay: isProduction // TODO disable this | 
						|
        ? false | 
						|
        : { warnings: false, errors: true } | 
						|
    }, projectDevServerOptions, { | 
						|
      https: useHttps, | 
						|
      proxy: proxySettings, | 
						|
      // eslint-disable-next-line no-shadow | 
						|
      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, specify 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) | 
						|
      }, | 
						|
      // avoid opening browser | 
						|
      open: false | 
						|
    })) | 
						|
 | 
						|
    ;['SIGINT', 'SIGTERM'].forEach(signal => { | 
						|
      process.on(signal, () => { | 
						|
        server.close(() => { | 
						|
          process.exit(0) | 
						|
        }) | 
						|
      }) | 
						|
    }) | 
						|
 | 
						|
    if (args.stdin) { | 
						|
      process.stdin.on('end', () => { | 
						|
        server.close(() => { | 
						|
          process.exit(0) | 
						|
        }) | 
						|
      }) | 
						|
 | 
						|
      process.stdin.resume() | 
						|
    } | 
						|
 | 
						|
    // 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.close(() => { | 
						|
            process.exit(0) | 
						|
          }) | 
						|
        } | 
						|
      }) | 
						|
    } | 
						|
 | 
						|
    return new Promise((resolve, reject) => { | 
						|
      // log instructions & open browser on first compilation complete | 
						|
      let isFirstCompile = true | 
						|
      compiler.hooks.done.tap('vue-cli-service serve', stats => { | 
						|
        if (stats.hasErrors()) { | 
						|
          return | 
						|
        } | 
						|
 | 
						|
        let copied = '' | 
						|
        if (isFirstCompile && args.copy) { | 
						|
          try { | 
						|
            require('clipboardy').writeSync(localUrlForBrowser) | 
						|
            copied = chalk.dim('(copied to clipboard)') | 
						|
          } catch (_) { | 
						|
            /* catch exception if copy to clipboard isn't supported (e.g. WSL), see issue #3476 */ | 
						|
          } | 
						|
        } | 
						|
 | 
						|
        const networkUrl = publicUrl | 
						|
          ? publicUrl.replace(/([^/])$/, '$1/') | 
						|
          : urls.lanUrlForTerminal | 
						|
 | 
						|
        console.log() | 
						|
        console.log(`  App running at:`) | 
						|
        console.log(`  - Local:   ${chalk.cyan(urls.localUrlForTerminal)} ${copied}`) | 
						|
        if (!isInContainer) { | 
						|
          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:<your container's external mapped port>${options.publicPath}` | 
						|
          )}`)) | 
						|
        } | 
						|
        console.log() | 
						|
 | 
						|
        if (isFirstCompile) { | 
						|
          isFirstCompile = false | 
						|
 | 
						|
          if (!isProduction) { | 
						|
            const buildCommand = hasProjectYarn(api.getCwd()) ? `yarn build` : hasProjectPnpm(api.getCwd()) ? `pnpm run 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(localUrlForBrowser + pageUri) | 
						|
          } | 
						|
 | 
						|
          // Send final app URL | 
						|
          if (args.dashboard) { | 
						|
            const ipc = new IpcMessenger() | 
						|
            ipc.send({ | 
						|
              vueServe: { | 
						|
                url: localUrlForBrowser | 
						|
              } | 
						|
            }) | 
						|
          } | 
						|
 | 
						|
          // resolve returned Promise | 
						|
          // so other commands can do api.service.run('serve').then(...) | 
						|
          resolve({ | 
						|
            server, | 
						|
            url: localUrlForBrowser | 
						|
          }) | 
						|
        } else if (process.env.VUE_CLI_TEST) { | 
						|
          // signal for test to check HMR | 
						|
          console.log('App updated') | 
						|
        } | 
						|
      }) | 
						|
 | 
						|
      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) | 
						|
  } | 
						|
} | 
						|
 | 
						|
function genHistoryApiFallbackRewrites (baseUrl, pages = {}) { | 
						|
  const path = require('path') | 
						|
  const multiPageRewrites = Object | 
						|
    .keys(pages) | 
						|
    // sort by length in reversed order to avoid overrides | 
						|
    // eg. 'page11' should appear in front of 'page1' | 
						|
    .sort((a, b) => b.length - a.length) | 
						|
    .map(name => ({ | 
						|
      from: new RegExp(`^/${name}`), | 
						|
      to: path.posix.join(baseUrl, pages[name].filename || `${name}.html`) | 
						|
    })) | 
						|
  return [ | 
						|
    ...multiPageRewrites, | 
						|
    { from: /./, to: path.posix.join(baseUrl, 'index.html') } | 
						|
  ] | 
						|
} | 
						|
 | 
						|
module.exports.defaultModes = { | 
						|
  serve: 'development' | 
						|
}
 | 
						|
 |