index.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. const defaults = {
  2. clean: true,
  3. target: 'app',
  4. formats: 'commonjs,umd,umd-min',
  5. 'unsafe-inline': true
  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. '--modern': `build app targeting modern browsers with auto fallback`,
  27. '--no-unsafe-inline': `build app without introducing inline scripts`,
  28. '--target': `app | lib | wc | wc-async (default: ${defaults.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 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. '--watch': `watch for changes`
  36. }
  37. }, async (args, rawArgs) => {
  38. for (const key in defaults) {
  39. if (args[key] == null) {
  40. args[key] = defaults[key]
  41. }
  42. }
  43. args.entry = args.entry || args._[0]
  44. if (args.target !== 'app') {
  45. args.entry = args.entry || 'src/App.vue'
  46. }
  47. process.env.VUE_CLI_BUILD_TARGET = args.target
  48. if (args.modern && args.target === 'app') {
  49. process.env.VUE_CLI_MODERN_MODE = true
  50. if (!process.env.VUE_CLI_MODERN_BUILD) {
  51. // main-process for legacy build
  52. await build(Object.assign({}, args, {
  53. modernBuild: false,
  54. keepAlive: true
  55. }), api, options)
  56. // spawn sub-process of self for modern build
  57. const { execa } = require('@vue/cli-shared-utils')
  58. const cliBin = require('path').resolve(__dirname, '../../../bin/vue-cli-service.js')
  59. await execa(cliBin, ['build', ...rawArgs], {
  60. stdio: 'inherit',
  61. env: {
  62. VUE_CLI_MODERN_BUILD: true
  63. }
  64. })
  65. } else {
  66. // sub-process for modern build
  67. await build(Object.assign({}, args, {
  68. modernBuild: true,
  69. clean: false
  70. }), api, options)
  71. }
  72. delete process.env.VUE_CLI_MODERN_MODE
  73. } else {
  74. if (args.modern) {
  75. const { warn } = require('@vue/cli-shared-utils')
  76. warn(
  77. `Modern mode only works with default target (app). ` +
  78. `For libraries or web components, use the browserslist ` +
  79. `config to specify target browsers.`
  80. )
  81. }
  82. await build(args, api, options)
  83. }
  84. delete process.env.VUE_CLI_BUILD_TARGET
  85. })
  86. }
  87. async function build (args, api, options) {
  88. const fs = require('fs-extra')
  89. const path = require('path')
  90. const chalk = require('chalk')
  91. const webpack = require('webpack')
  92. const formatStats = require('./formatStats')
  93. const validateWebpackConfig = require('../../util/validateWebpackConfig')
  94. const {
  95. log,
  96. done,
  97. info,
  98. logWithSpinner,
  99. stopSpinner
  100. } = require('@vue/cli-shared-utils')
  101. log()
  102. const mode = api.service.mode
  103. if (args.target === 'app') {
  104. const bundleTag = args.modern
  105. ? args.modernBuild
  106. ? `modern bundle `
  107. : `legacy bundle `
  108. : ``
  109. logWithSpinner(`Building ${bundleTag}for ${mode}...`)
  110. } else {
  111. const buildMode = buildModes[args.target]
  112. if (buildMode) {
  113. const additionalParams = buildMode === 'library' ? ` (${args.formats})` : ``
  114. logWithSpinner(`Building for ${mode} as ${buildMode}${additionalParams}...`)
  115. } else {
  116. throw new Error(`Unknown build target: ${args.target}`)
  117. }
  118. }
  119. const targetDir = api.resolve(args.dest || options.outputDir)
  120. const isLegacyBuild = args.target === 'app' && args.modern && !args.modernBuild
  121. // resolve raw webpack config
  122. let webpackConfig
  123. if (args.target === 'lib') {
  124. webpackConfig = require('./resolveLibConfig')(api, args, options)
  125. } else if (
  126. args.target === 'wc' ||
  127. args.target === 'wc-async'
  128. ) {
  129. webpackConfig = require('./resolveWcConfig')(api, args, options)
  130. } else {
  131. webpackConfig = require('./resolveAppConfig')(api, args, options)
  132. }
  133. // check for common config errors
  134. validateWebpackConfig(webpackConfig, api, options, args.target)
  135. // apply inline dest path after user configureWebpack hooks
  136. // so it takes higher priority
  137. if (args.dest) {
  138. modifyConfig(webpackConfig, config => {
  139. config.output.path = targetDir
  140. })
  141. }
  142. if (args.watch) {
  143. modifyConfig(webpackConfig, config => {
  144. config.watch = true
  145. })
  146. }
  147. // Expose advanced stats
  148. if (args.dashboard) {
  149. const DashboardPlugin = require('../../webpack/DashboardPlugin')
  150. modifyConfig(webpackConfig, config => {
  151. config.plugins.push(new DashboardPlugin({
  152. type: 'build',
  153. modernBuild: args.modernBuild,
  154. keepAlive: args.keepAlive
  155. }))
  156. })
  157. }
  158. if (args.report || args['report-json']) {
  159. const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
  160. modifyConfig(webpackConfig, config => {
  161. const bundleName = args.target !== 'app'
  162. ? config.output.filename.replace(/\.js$/, '-')
  163. : isLegacyBuild ? 'legacy-' : ''
  164. config.plugins.push(new BundleAnalyzerPlugin({
  165. logLevel: 'warn',
  166. openAnalyzer: false,
  167. analyzerMode: args.report ? 'static' : 'disabled',
  168. reportFilename: `${bundleName}report.html`,
  169. statsFilename: `${bundleName}report.json`,
  170. generateStatsFile: !!args['report-json']
  171. }))
  172. })
  173. }
  174. if (args.clean) {
  175. await fs.remove(targetDir)
  176. }
  177. return new Promise((resolve, reject) => {
  178. webpack(webpackConfig, (err, stats) => {
  179. stopSpinner(false)
  180. if (err) {
  181. return reject(err)
  182. }
  183. if (stats.hasErrors()) {
  184. return reject(`Build failed with errors.`)
  185. }
  186. if (!args.silent) {
  187. const targetDirShort = path.relative(
  188. api.service.context,
  189. targetDir
  190. )
  191. log(formatStats(stats, targetDirShort, api))
  192. if (args.target === 'app' && !isLegacyBuild) {
  193. if (!args.watch) {
  194. done(`Build complete. The ${chalk.cyan(targetDirShort)} directory is ready to be deployed.`)
  195. info(`Check out deployment instructions at ${chalk.cyan(`https://cli.vuejs.org/guide/deployment.html`)}\n`)
  196. } else {
  197. done(`Build complete. Watching for changes...`)
  198. }
  199. }
  200. }
  201. // test-only signal
  202. if (process.env.VUE_CLI_TEST) {
  203. console.log('Build complete.')
  204. }
  205. resolve()
  206. })
  207. })
  208. }
  209. module.exports.defaultModes = {
  210. build: 'production'
  211. }