index.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. const defaults = {
  2. clean: true,
  3. target: 'app',
  4. module: true,
  5. formats: 'commonjs,umd,umd-min'
  6. }
  7. const buildModes = {
  8. lib: 'library',
  9. wc: 'web component',
  10. 'wc-async': 'web component (async)'
  11. }
  12. const modifyConfig = (config, fn) => {
  13. if (Array.isArray(config)) {
  14. config.forEach(c => fn(c))
  15. } else {
  16. fn(config)
  17. }
  18. }
  19. module.exports = (api, options) => {
  20. api.registerCommand('build', {
  21. description: 'build for production',
  22. usage: 'vue-cli-service build [options] [entry|pattern]',
  23. options: {
  24. '--mode': `specify env mode (default: production)`,
  25. '--dest': `specify output directory (default: ${options.outputDir})`,
  26. '--no-module': `build app without generating <script type="module"> chunks for modern browsers`,
  27. '--target': `app | lib | wc | wc-async (default: ${defaults.target})`,
  28. '--inline-vue': 'include the Vue module in the final bundle of library or web component target',
  29. '--formats': `list of output formats for library builds (default: ${defaults.formats})`,
  30. '--name': `name for lib or web-component mode (default: "name" in package.json or entry filename)`,
  31. '--filename': `file name for output, only usable for 'lib' target (default: value of --name)`,
  32. '--no-clean': `do not remove the dist directory contents before building the project`,
  33. '--report': `generate report.html to help analyze bundle content`,
  34. '--report-json': 'generate report.json to help analyze bundle content',
  35. '--skip-plugins': `comma-separated list of plugin names to skip for this run`,
  36. '--watch': `watch for changes`,
  37. '--stdin': `close when stdin ends`
  38. }
  39. }, async (args, rawArgs) => {
  40. for (const key in defaults) {
  41. if (args[key] == null) {
  42. args[key] = defaults[key]
  43. }
  44. }
  45. args.entry = args.entry || args._[0]
  46. if (args.target !== 'app') {
  47. args.entry = args.entry || 'src/App.vue'
  48. }
  49. process.env.VUE_CLI_BUILD_TARGET = args.target
  50. const { log, execa } = require('@vue/cli-shared-utils')
  51. const { allProjectTargetsSupportModule } = require('../../util/targets')
  52. let needsDifferentialLoading = args.target === 'app' && args.module
  53. if (allProjectTargetsSupportModule) {
  54. log(
  55. `All browser targets in the browserslist configuration have supported ES module.\n` +
  56. `Therefore we don't build two separate bundles for differential loading.\n`
  57. )
  58. needsDifferentialLoading = false
  59. }
  60. args.needsDifferentialLoading = needsDifferentialLoading
  61. if (!needsDifferentialLoading) {
  62. await build(args, api, options)
  63. return
  64. }
  65. process.env.VUE_CLI_MODERN_MODE = true
  66. if (!process.env.VUE_CLI_MODERN_BUILD) {
  67. // main-process for legacy build
  68. const legacyBuildArgs = { ...args, moduleBuild: false, keepAlive: true }
  69. await build(legacyBuildArgs, api, options)
  70. // spawn sub-process of self for modern build
  71. const cliBin = require('path').resolve(__dirname, '../../../bin/vue-cli-service.js')
  72. await execa('node', [cliBin, 'build', ...rawArgs], {
  73. stdio: 'inherit',
  74. env: {
  75. VUE_CLI_MODERN_BUILD: true
  76. }
  77. })
  78. } else {
  79. // sub-process for modern build
  80. const moduleBuildArgs = { ...args, moduleBuild: true, clean: false }
  81. await build(moduleBuildArgs, api, options)
  82. }
  83. })
  84. }
  85. async function build (args, api, options) {
  86. const fs = require('fs-extra')
  87. const path = require('path')
  88. const webpack = require('webpack')
  89. const { chalk } = require('@vue/cli-shared-utils')
  90. const formatStats = require('./formatStats')
  91. const validateWebpackConfig = require('../../util/validateWebpackConfig')
  92. const {
  93. log,
  94. done,
  95. info,
  96. logWithSpinner,
  97. stopSpinner
  98. } = require('@vue/cli-shared-utils')
  99. log()
  100. const mode = api.service.mode
  101. if (args.target === 'app') {
  102. const bundleTag = args.needsDifferentialLoading
  103. ? args.moduleBuild
  104. ? `module bundle `
  105. : `legacy bundle `
  106. : ``
  107. logWithSpinner(`Building ${bundleTag}for ${mode}...`)
  108. } else {
  109. const buildMode = buildModes[args.target]
  110. if (buildMode) {
  111. const additionalParams = buildMode === 'library' ? ` (${args.formats})` : ``
  112. logWithSpinner(`Building for ${mode} as ${buildMode}${additionalParams}...`)
  113. } else {
  114. throw new Error(`Unknown build target: ${args.target}`)
  115. }
  116. }
  117. if (args.dest) {
  118. // Override outputDir before resolving webpack config as config relies on it (#2327)
  119. options.outputDir = args.dest
  120. }
  121. const targetDir = api.resolve(options.outputDir)
  122. const isLegacyBuild = args.needsDifferentialLoading && !args.moduleBuild
  123. // resolve raw webpack config
  124. let webpackConfig
  125. if (args.target === 'lib') {
  126. webpackConfig = require('./resolveLibConfig')(api, args, options)
  127. } else if (
  128. args.target === 'wc' ||
  129. args.target === 'wc-async'
  130. ) {
  131. webpackConfig = require('./resolveWcConfig')(api, args, options)
  132. } else {
  133. webpackConfig = require('./resolveAppConfig')(api, args, options)
  134. }
  135. // check for common config errors
  136. validateWebpackConfig(webpackConfig, api, options, args.target)
  137. if (args.watch) {
  138. modifyConfig(webpackConfig, config => {
  139. config.watch = true
  140. })
  141. }
  142. if (args.stdin) {
  143. process.stdin.on('end', () => {
  144. process.exit(0)
  145. })
  146. process.stdin.resume()
  147. }
  148. // Expose advanced stats
  149. if (args.dashboard) {
  150. const DashboardPlugin = require('../../webpack/DashboardPlugin')
  151. modifyConfig(webpackConfig, config => {
  152. config.plugins.push(new DashboardPlugin({
  153. type: 'build',
  154. moduleBuild: args.moduleBuild,
  155. keepAlive: args.keepAlive
  156. }))
  157. })
  158. }
  159. if (args.report || args['report-json']) {
  160. const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
  161. modifyConfig(webpackConfig, config => {
  162. const bundleName = args.target !== 'app'
  163. ? config.output.filename.replace(/\.js$/, '-')
  164. : isLegacyBuild ? 'legacy-' : ''
  165. config.plugins.push(new BundleAnalyzerPlugin({
  166. logLevel: 'warn',
  167. openAnalyzer: false,
  168. analyzerMode: args.report ? 'static' : 'disabled',
  169. reportFilename: `${bundleName}report.html`,
  170. statsFilename: `${bundleName}report.json`,
  171. generateStatsFile: !!args['report-json']
  172. }))
  173. })
  174. }
  175. if (args.clean) {
  176. await fs.emptyDir(targetDir)
  177. }
  178. return new Promise((resolve, reject) => {
  179. webpack(webpackConfig, (err, stats) => {
  180. stopSpinner(false)
  181. if (err) {
  182. return reject(err)
  183. }
  184. if (stats.hasErrors()) {
  185. return reject(new Error('Build failed with errors.'))
  186. }
  187. if (!args.silent) {
  188. const targetDirShort = path.relative(
  189. api.service.context,
  190. targetDir
  191. )
  192. log(formatStats(stats, targetDirShort, api))
  193. if (args.target === 'app' && !isLegacyBuild) {
  194. if (!args.watch) {
  195. done(`Build complete. The ${chalk.cyan(targetDirShort)} directory is ready to be deployed.`)
  196. info(`Check out deployment instructions at ${chalk.cyan(`https://cli.vuejs.org/guide/deployment.html`)}\n`)
  197. } else {
  198. done(`Build complete. Watching for changes...`)
  199. }
  200. }
  201. }
  202. // test-only signal
  203. if (process.env.VUE_CLI_TEST) {
  204. console.log('Build complete.')
  205. }
  206. resolve()
  207. })
  208. })
  209. }
  210. module.exports.defaultModes = {
  211. build: 'production'
  212. }