cli-generate.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. /*!
  2. * @nuxt/cli v2.15.8 (c) 2016-2021
  3. * Released under the MIT License
  4. * Repository: https://github.com/nuxt/nuxt.js
  5. * Website: https://nuxtjs.org
  6. */
  7. 'use strict';
  8. const utils = require('@nuxt/utils');
  9. const consola = require('consola');
  10. const index = require('./cli-index.js');
  11. const path = require('path');
  12. const upath = require('upath');
  13. const fs = require('fs-extra');
  14. const crc32 = require('crc/lib/crc32');
  15. const globby = require('globby');
  16. const destr = require('destr');
  17. require('@nuxt/config');
  18. require('exit');
  19. require('chalk');
  20. require('std-env');
  21. require('wrap-ansi');
  22. require('boxen');
  23. require('minimist');
  24. require('hable');
  25. require('defu');
  26. require('semver');
  27. require('fs');
  28. require('execa');
  29. function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
  30. const consola__default = /*#__PURE__*/_interopDefaultLegacy(consola);
  31. const path__default = /*#__PURE__*/_interopDefaultLegacy(path);
  32. const upath__default = /*#__PURE__*/_interopDefaultLegacy(upath);
  33. const fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
  34. const crc32__default = /*#__PURE__*/_interopDefaultLegacy(crc32);
  35. const globby__default = /*#__PURE__*/_interopDefaultLegacy(globby);
  36. const destr__default = /*#__PURE__*/_interopDefaultLegacy(destr);
  37. async function generate$1 (cmd) {
  38. const nuxt = await getNuxt({ server: true }, cmd);
  39. const generator = await cmd.getGenerator(nuxt);
  40. await nuxt.server.listen(0);
  41. const { errors } = await generator.generate({ build: false });
  42. await nuxt.close();
  43. if (cmd.argv['fail-on-error'] && errors.length > 0) {
  44. throw new Error('Error generating pages, exiting with non-zero code')
  45. }
  46. }
  47. async function ensureBuild (cmd) {
  48. const nuxt = await getNuxt({ _build: true, server: false }, cmd);
  49. const { options } = nuxt;
  50. if (options.generate.cache === false || destr__default['default'](process.env.NUXT_BUILD) || cmd.argv['force-build']) {
  51. const builder = await cmd.getBuilder(nuxt);
  52. await builder.build();
  53. await nuxt.close();
  54. return
  55. }
  56. // Default build ignore files
  57. const ignore = [
  58. options.buildDir,
  59. options.dir.static,
  60. options.generate.dir,
  61. 'node_modules',
  62. '.**/*',
  63. '.*',
  64. 'README.md'
  65. ];
  66. // Extend ignore
  67. const { generate } = options;
  68. if (typeof generate.cache.ignore === 'function') {
  69. generate.cache.ignore = generate.cache.ignore(ignore);
  70. } else if (Array.isArray(generate.cache.ignore)) {
  71. generate.cache.ignore = generate.cache.ignore.concat(ignore);
  72. }
  73. await nuxt.callHook('generate:cache:ignore', generate.cache.ignore);
  74. // Take a snapshot of current project
  75. const snapshotOptions = {
  76. rootDir: options.rootDir,
  77. ignore: generate.cache.ignore.map(upath__default['default'].normalize),
  78. globbyOptions: generate.cache.globbyOptions
  79. };
  80. const currentBuildSnapshot = await snapshot(snapshotOptions);
  81. // Detect process.env usage in nuxt.config
  82. const processEnv = {};
  83. if (nuxt.options._nuxtConfigFile) {
  84. const configSrc = await fs__default['default'].readFile(nuxt.options._nuxtConfigFile);
  85. const envRegex = /process.env.(\w+)/g;
  86. let match;
  87. // eslint-disable-next-line no-cond-assign
  88. while (match = envRegex.exec(configSrc)) {
  89. processEnv[match[1]] = process.env[match[1]];
  90. }
  91. }
  92. // Current build meta
  93. const currentBuild = {
  94. // @ts-ignore
  95. nuxtVersion: nuxt.constructor.version,
  96. ssr: nuxt.options.ssr,
  97. target: nuxt.options.target,
  98. snapshot: currentBuildSnapshot,
  99. env: nuxt.options.env,
  100. 'process.env': processEnv
  101. };
  102. // Check if build can be skipped
  103. const nuxtBuildFile = path__default['default'].resolve(nuxt.options.buildDir, 'build.json');
  104. if (fs__default['default'].existsSync(nuxtBuildFile)) {
  105. const previousBuild = destr__default['default'](fs__default['default'].readFileSync(nuxtBuildFile, 'utf-8')) || {};
  106. // Quick diff
  107. let needBuild = false;
  108. for (const field of ['nuxtVersion', 'ssr', 'target', 'env', 'process.env']) {
  109. if (JSON.stringify(previousBuild[field]) !== JSON.stringify(currentBuild[field])) {
  110. needBuild = true;
  111. consola__default['default'].info(`Doing webpack rebuild because ${field} changed`);
  112. break
  113. }
  114. }
  115. // Full snapshot diff
  116. if (!needBuild) {
  117. const changed = compareSnapshots(previousBuild.snapshot, currentBuild.snapshot);
  118. if (!changed) {
  119. consola__default['default'].success('Skipping webpack build as no changes detected');
  120. return
  121. } else {
  122. consola__default['default'].info(`Doing webpack rebuild because ${changed} modified`);
  123. }
  124. }
  125. }
  126. // Webpack build
  127. const builder = await cmd.getBuilder(nuxt);
  128. await builder.build();
  129. // Write build.json
  130. fs__default['default'].writeFileSync(nuxtBuildFile, JSON.stringify(currentBuild, null, 2), 'utf-8');
  131. await nuxt.close();
  132. }
  133. async function getNuxt (args, cmd) {
  134. const config = await cmd.getNuxtConfig({ dev: false, ...args });
  135. if (config.target === utils.TARGETS.static) {
  136. config._export = true;
  137. } else {
  138. config._legacyGenerate = true;
  139. }
  140. config.buildDir = (config.static && config.static.cacheDir) || path__default['default'].resolve(config.rootDir, 'node_modules/.cache/nuxt');
  141. config.build = config.build || {};
  142. // https://github.com/nuxt/nuxt.js/issues/7390
  143. config.build.parallel = false;
  144. config.build.transpile = config.build.transpile || [];
  145. if (!config.static || !config.static.cacheDir) {
  146. config.build.transpile.push('.cache/nuxt');
  147. }
  148. const nuxt = await cmd.getNuxt(config);
  149. return nuxt
  150. }
  151. function compareSnapshots (from, to) {
  152. const allKeys = Array.from(new Set([
  153. ...Object.keys(from).sort(),
  154. ...Object.keys(to).sort()
  155. ]));
  156. for (const key of allKeys) {
  157. if (JSON.stringify(from[key]) !== JSON.stringify(to[key])) {
  158. return key
  159. }
  160. }
  161. return false
  162. }
  163. async function snapshot ({ globbyOptions, ignore, rootDir }) {
  164. const snapshot = {};
  165. const files = await globby__default['default']('**/*.*', {
  166. ...globbyOptions,
  167. ignore,
  168. cwd: rootDir,
  169. absolute: true
  170. });
  171. await Promise.all(files.map(async (p) => {
  172. const key = path.relative(rootDir, p);
  173. try {
  174. const fileContent = await fs__default['default'].readFile(p);
  175. snapshot[key] = {
  176. checksum: await crc32__default['default'](fileContent).toString(16)
  177. };
  178. } catch (e) {
  179. // TODO: Check for other errors like permission denied
  180. snapshot[key] = {
  181. exists: false
  182. };
  183. }
  184. }));
  185. return snapshot
  186. }
  187. const generate = {
  188. name: 'generate',
  189. description: 'Generate a static web application (server-rendered)',
  190. usage: 'generate <dir>',
  191. options: {
  192. ...index.common,
  193. ...index.locking,
  194. build: {
  195. type: 'boolean',
  196. default: true,
  197. description: 'Only generate pages for dynamic routes, used for incremental builds. Generate has to be run once without this option before using it'
  198. },
  199. devtools: {
  200. type: 'boolean',
  201. default: false,
  202. description: 'Enable Vue devtools',
  203. prepare (cmd, options, argv) {
  204. options.vue = options.vue || {};
  205. options.vue.config = options.vue.config || {};
  206. if (argv.devtools) {
  207. options.vue.config.devtools = true;
  208. }
  209. }
  210. },
  211. quiet: {
  212. alias: 'q',
  213. type: 'boolean',
  214. description: 'Disable output except for errors',
  215. prepare (cmd, options, argv) {
  216. // Silence output when using --quiet
  217. options.build = options.build || {};
  218. if (argv.quiet) {
  219. options.build.quiet = true;
  220. }
  221. }
  222. },
  223. modern: {
  224. ...index.common.modern,
  225. description: 'Generate app in modern build (modern mode can be only client)',
  226. prepare (cmd, options, argv) {
  227. if (index.normalizeArg(argv.modern)) {
  228. options.modern = 'client';
  229. }
  230. }
  231. },
  232. 'force-build': {
  233. type: 'boolean',
  234. default: false,
  235. description: 'Force to build the application with webpack'
  236. },
  237. 'fail-on-error': {
  238. type: 'boolean',
  239. default: false,
  240. description: 'Exit with non-zero status code if there are errors when generating pages'
  241. }
  242. },
  243. async run (cmd) {
  244. const config = await cmd.getNuxtConfig({ dev: false });
  245. // Disable analyze if set by the nuxt config
  246. config.build = config.build || {};
  247. config.build.analyze = false;
  248. // Full static
  249. if (config.target === utils.TARGETS.static) {
  250. await ensureBuild(cmd);
  251. await generate$1(cmd);
  252. return
  253. }
  254. // Forcing static target anyway
  255. config.target = utils.TARGETS.static;
  256. consola__default['default'].warn(`When using \`nuxt generate\`, you should set \`target: 'static'\` in your \`nuxt.config\`\n 👉 Learn more about it on https://go.nuxtjs.dev/static-target`);
  257. // Set flag to keep the prerendering behaviour
  258. config._legacyGenerate = true;
  259. if (config.build) {
  260. // https://github.com/nuxt/nuxt.js/issues/7390
  261. config.build.parallel = false;
  262. }
  263. const nuxt = await cmd.getNuxt(config);
  264. if (cmd.argv.lock) {
  265. await cmd.setLock(await index.createLock({
  266. id: 'build',
  267. dir: nuxt.options.buildDir,
  268. root: config.rootDir
  269. }));
  270. nuxt.hook('build:done', async () => {
  271. await cmd.releaseLock();
  272. await cmd.setLock(await index.createLock({
  273. id: 'generate',
  274. dir: nuxt.options.generate.dir,
  275. root: config.rootDir
  276. }));
  277. });
  278. }
  279. const generator = await cmd.getGenerator(nuxt);
  280. await nuxt.server.listen(0);
  281. const { errors } = await generator.generate({
  282. init: true,
  283. build: cmd.argv.build
  284. });
  285. await nuxt.close();
  286. if (cmd.argv['fail-on-error'] && errors.length > 0) {
  287. throw new Error('Error generating pages, exiting with non-zero code')
  288. }
  289. }
  290. };
  291. exports.default = generate;