builder.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968
  1. /*!
  2. * @nuxt/builder 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. Object.defineProperty(exports, '__esModule', { value: true });
  9. const path = require('path');
  10. const chalk = require('chalk');
  11. const chokidar = require('chokidar');
  12. const consola = require('consola');
  13. const fsExtra = require('fs-extra');
  14. const Glob = require('glob');
  15. const hash = require('hash-sum');
  16. const pify = require('pify');
  17. const upath = require('upath');
  18. const lodash = require('lodash');
  19. const utils = require('@nuxt/utils');
  20. const vueApp = require('@nuxt/vue-app');
  21. const webpack = require('@nuxt/webpack');
  22. const ignore = require('ignore');
  23. const serialize = require('serialize-javascript');
  24. const devalue = require('@nuxt/devalue');
  25. function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
  26. const path__default = /*#__PURE__*/_interopDefaultLegacy(path);
  27. const chalk__default = /*#__PURE__*/_interopDefaultLegacy(chalk);
  28. const chokidar__default = /*#__PURE__*/_interopDefaultLegacy(chokidar);
  29. const consola__default = /*#__PURE__*/_interopDefaultLegacy(consola);
  30. const fsExtra__default = /*#__PURE__*/_interopDefaultLegacy(fsExtra);
  31. const Glob__default = /*#__PURE__*/_interopDefaultLegacy(Glob);
  32. const hash__default = /*#__PURE__*/_interopDefaultLegacy(hash);
  33. const pify__default = /*#__PURE__*/_interopDefaultLegacy(pify);
  34. const upath__default = /*#__PURE__*/_interopDefaultLegacy(upath);
  35. const ignore__default = /*#__PURE__*/_interopDefaultLegacy(ignore);
  36. const serialize__default = /*#__PURE__*/_interopDefaultLegacy(serialize);
  37. const devalue__default = /*#__PURE__*/_interopDefaultLegacy(devalue);
  38. class Ignore {
  39. constructor (options) {
  40. this.rootDir = options.rootDir;
  41. this.ignoreOptions = options.ignoreOptions;
  42. this.ignoreArray = options.ignoreArray;
  43. this.addIgnoresRules();
  44. }
  45. static get IGNORE_FILENAME () {
  46. return '.nuxtignore'
  47. }
  48. findIgnoreFile () {
  49. if (!this.ignoreFile) {
  50. const ignoreFile = path__default['default'].resolve(this.rootDir, Ignore.IGNORE_FILENAME);
  51. if (fsExtra__default['default'].existsSync(ignoreFile) && fsExtra__default['default'].statSync(ignoreFile).isFile()) {
  52. this.ignoreFile = ignoreFile;
  53. this.ignore = ignore__default['default'](this.ignoreOptions);
  54. }
  55. }
  56. return this.ignoreFile
  57. }
  58. readIgnoreFile () {
  59. if (this.findIgnoreFile()) {
  60. return fsExtra__default['default'].readFileSync(this.ignoreFile, 'utf8')
  61. }
  62. }
  63. addIgnoresRules () {
  64. const content = this.readIgnoreFile();
  65. if (content) {
  66. this.ignore.add(content);
  67. }
  68. if (this.ignoreArray && this.ignoreArray.length > 0) {
  69. if (!this.ignore) {
  70. this.ignore = ignore__default['default'](this.ignoreOptions);
  71. }
  72. this.ignore.add(this.ignoreArray);
  73. }
  74. }
  75. filter (paths) {
  76. if (this.ignore) {
  77. return this.ignore.filter([].concat(paths || []))
  78. }
  79. return paths
  80. }
  81. reload () {
  82. delete this.ignore;
  83. delete this.ignoreFile;
  84. this.addIgnoresRules();
  85. }
  86. }
  87. class BuildContext {
  88. constructor (builder) {
  89. this._builder = builder;
  90. this.nuxt = builder.nuxt;
  91. this.options = builder.nuxt.options;
  92. this.target = builder.nuxt.options.target;
  93. }
  94. get buildOptions () {
  95. return this.options.build
  96. }
  97. get plugins () {
  98. return this._builder.plugins
  99. }
  100. }
  101. class TemplateContext {
  102. constructor (builder, options) {
  103. this.templateFiles = Array.from(builder.template.files);
  104. this.templateVars = {
  105. nuxtOptions: options,
  106. features: options.features,
  107. extensions: options.extensions
  108. .map(ext => ext.replace(/^\./, ''))
  109. .join('|'),
  110. messages: options.messages,
  111. splitChunks: options.build.splitChunks,
  112. uniqBy: lodash.uniqBy,
  113. isDev: options.dev,
  114. isTest: options.test,
  115. isFullStatic: utils.isFullStatic(options),
  116. debug: options.debug,
  117. buildIndicator: options.dev && options.build.indicator,
  118. vue: { config: options.vue.config },
  119. fetch: options.fetch,
  120. mode: options.mode,
  121. router: options.router,
  122. env: options.env,
  123. head: options.head,
  124. store: options.features.store ? options.store : false,
  125. globalName: options.globalName,
  126. globals: builder.globals,
  127. css: options.css,
  128. plugins: builder.plugins,
  129. appPath: './App.js',
  130. layouts: Object.assign({}, options.layouts),
  131. loading:
  132. typeof options.loading === 'string'
  133. ? builder.relativeToBuild(options.srcDir, options.loading)
  134. : options.loading,
  135. pageTransition: options.pageTransition,
  136. layoutTransition: options.layoutTransition,
  137. rootDir: options.rootDir,
  138. srcDir: options.srcDir,
  139. dir: options.dir,
  140. components: {
  141. ErrorPage: options.ErrorPage
  142. ? builder.relativeToBuild(options.ErrorPage)
  143. : null
  144. }
  145. };
  146. }
  147. get templateOptions () {
  148. let lodash = null;
  149. return {
  150. imports: {
  151. serialize: serialize__default['default'],
  152. serializeFunction: utils.serializeFunction,
  153. devalue: devalue__default['default'],
  154. hash: hash__default['default'],
  155. r: utils.r,
  156. wp: utils.wp,
  157. wChunk: utils.wChunk,
  158. // Legacy support: https://github.com/nuxt/nuxt.js/issues/4350
  159. _: new Proxy({}, {
  160. get (target, prop) {
  161. if (!lodash) {
  162. consola__default['default'].warn('Avoid using _ inside templates');
  163. lodash = utils.requireModule('lodash');
  164. }
  165. return lodash[prop]
  166. }
  167. })
  168. },
  169. interpolate: /<%=([\s\S]+?)%>/g
  170. }
  171. }
  172. }
  173. const glob = pify__default['default'](Glob__default['default']);
  174. class Builder {
  175. constructor (nuxt, bundleBuilder) {
  176. this.nuxt = nuxt;
  177. this.plugins = [];
  178. this.options = nuxt.options;
  179. this.globals = utils.determineGlobals(nuxt.options.globalName, nuxt.options.globals);
  180. this.watchers = {
  181. files: null,
  182. custom: null,
  183. restart: null
  184. };
  185. this.supportedExtensions = ['vue', 'js', ...(this.options.build.additionalExtensions || [])];
  186. // Helper to resolve build paths
  187. this.relativeToBuild = (...args) => utils.relativeTo(this.options.buildDir, ...args);
  188. this._buildStatus = STATUS.INITIAL;
  189. // Hooks for watch lifecycle
  190. if (this.options.dev) {
  191. // Start watching after initial render
  192. this.nuxt.hook('build:done', () => {
  193. consola__default['default'].info('Waiting for file changes');
  194. this.watchClient();
  195. this.watchRestart();
  196. });
  197. // Enable HMR for serverMiddleware
  198. this.serverMiddlewareHMR();
  199. // Close hook
  200. this.nuxt.hook('close', () => this.close());
  201. }
  202. if (this.options.build.analyze) {
  203. this.nuxt.hook('build:done', () => {
  204. consola__default['default'].warn('Notice: Please do not deploy bundles built with "analyze" mode, they\'re for analysis purposes only.');
  205. });
  206. }
  207. // Resolve template
  208. this.template = this.options.build.template || vueApp.template;
  209. if (typeof this.template === 'string') {
  210. this.template = this.nuxt.resolver.requireModule(this.template).template;
  211. }
  212. // Create a new bundle builder
  213. this.bundleBuilder = this.getBundleBuilder(bundleBuilder);
  214. this.ignore = new Ignore({
  215. rootDir: this.options.srcDir,
  216. ignoreArray: this.options.ignore
  217. });
  218. }
  219. getBundleBuilder (BundleBuilder) {
  220. if (typeof BundleBuilder === 'object') {
  221. return BundleBuilder
  222. }
  223. const context = new BuildContext(this);
  224. if (typeof BundleBuilder !== 'function') {
  225. BundleBuilder = webpack.BundleBuilder;
  226. }
  227. return new BundleBuilder(context)
  228. }
  229. forGenerate () {
  230. this.options.target = utils.TARGETS.static;
  231. this.bundleBuilder.forGenerate();
  232. }
  233. async build () {
  234. // Avoid calling build() method multiple times when dev:true
  235. if (this._buildStatus === STATUS.BUILD_DONE && this.options.dev) {
  236. return this
  237. }
  238. // If building
  239. if (this._buildStatus === STATUS.BUILDING) {
  240. await utils.waitFor(1000);
  241. return this.build()
  242. }
  243. this._buildStatus = STATUS.BUILDING;
  244. if (this.options.dev) {
  245. consola__default['default'].info('Preparing project for development');
  246. consola__default['default'].info('Initial build may take a while');
  247. } else {
  248. consola__default['default'].info('Production build');
  249. if (this.options.render.ssr) {
  250. consola__default['default'].info(`Bundling for ${chalk__default['default'].bold.yellow('server')} and ${chalk__default['default'].bold.green('client')} side`);
  251. } else {
  252. consola__default['default'].info(`Bundling only for ${chalk__default['default'].bold.green('client')} side`);
  253. }
  254. consola__default['default'].info(`Target: ${chalk__default['default'].bold.cyan(this.options.target)}`);
  255. }
  256. // Wait for nuxt ready
  257. await this.nuxt.ready();
  258. // Call before hook
  259. await this.nuxt.callHook('build:before', this, this.options.build);
  260. await this.validatePages();
  261. consola__default['default'].success('Builder initialized');
  262. consola__default['default'].debug(`App root: ${this.options.srcDir}`);
  263. // Create or empty .nuxt/, .nuxt/components and .nuxt/dist folders
  264. await fsExtra__default['default'].emptyDir(utils.r(this.options.buildDir));
  265. const buildDirs = [utils.r(this.options.buildDir, 'components')];
  266. if (!this.options.dev) {
  267. buildDirs.push(
  268. utils.r(this.options.buildDir, 'dist', 'client'),
  269. utils.r(this.options.buildDir, 'dist', 'server')
  270. );
  271. }
  272. await Promise.all(buildDirs.map(dir => fsExtra__default['default'].emptyDir(dir)));
  273. // Call ready hook
  274. await this.nuxt.callHook('builder:prepared', this, this.options.build);
  275. // Generate routes and interpret the template files
  276. await this.generateRoutesAndFiles();
  277. // Add vue-app template dir to watchers
  278. this.options.build.watch.push(this.globPathWithExtensions(this.template.dir));
  279. await this.resolvePlugins();
  280. // Start bundle build: webpack, rollup, parcel...
  281. await this.bundleBuilder.build();
  282. // Flag to set that building is done
  283. this._buildStatus = STATUS.BUILD_DONE;
  284. // Call done hook
  285. await this.nuxt.callHook('build:done', this);
  286. return this
  287. }
  288. // Check if pages dir exists and warn if not
  289. async validatePages () {
  290. this._nuxtPages = typeof this.options.build.createRoutes !== 'function';
  291. if (
  292. !this._nuxtPages ||
  293. await fsExtra__default['default'].exists(path__default['default'].resolve(this.options.srcDir, this.options.dir.pages))
  294. ) {
  295. return
  296. }
  297. const dir = this.options.srcDir;
  298. if (await fsExtra__default['default'].exists(path__default['default'].join(this.options.srcDir, '..', this.options.dir.pages))) {
  299. throw new Error(
  300. `No \`${this.options.dir.pages}\` directory found in ${dir}. Did you mean to run \`nuxt\` in the parent (\`../\`) directory?`
  301. )
  302. }
  303. this._defaultPage = true;
  304. consola__default['default'].warn(`No \`${this.options.dir.pages}\` directory found in ${dir}. Using the default built-in page.`);
  305. }
  306. globPathWithExtensions (path) {
  307. return `${path}/**/*.{${this.supportedExtensions.join(',')}}`
  308. }
  309. createTemplateContext () {
  310. return new TemplateContext(this, this.options)
  311. }
  312. async generateRoutesAndFiles () {
  313. consola__default['default'].debug('Generating nuxt files');
  314. this.plugins = Array.from(await this.normalizePlugins());
  315. const templateContext = this.createTemplateContext();
  316. await Promise.all([
  317. this.resolveLayouts(templateContext),
  318. this.resolveRoutes(templateContext),
  319. this.resolveStore(templateContext),
  320. this.resolveMiddleware(templateContext)
  321. ]);
  322. this.addOptionalTemplates(templateContext);
  323. await this.resolveCustomTemplates(templateContext);
  324. await this.resolveLoadingIndicator(templateContext);
  325. await this.compileTemplates(templateContext);
  326. consola__default['default'].success('Nuxt files generated');
  327. }
  328. async normalizePlugins () {
  329. // options.extendPlugins allows for returning a new plugins array
  330. if (typeof this.options.extendPlugins === 'function') {
  331. const extendedPlugins = this.options.extendPlugins(this.options.plugins);
  332. if (Array.isArray(extendedPlugins)) {
  333. this.options.plugins = extendedPlugins;
  334. }
  335. }
  336. // extendPlugins hook only supports in-place modifying
  337. await this.nuxt.callHook('builder:extendPlugins', this.options.plugins);
  338. const modes = ['client', 'server'];
  339. const modePattern = new RegExp(`\\.(${modes.join('|')})(\\.\\w+)*$`);
  340. return lodash.uniqBy(
  341. this.options.plugins.map((p) => {
  342. if (typeof p === 'string') {
  343. p = { src: p };
  344. }
  345. const pluginBaseName = path__default['default'].basename(p.src, path__default['default'].extname(p.src)).replace(
  346. /[^a-zA-Z?\d\s:]/g,
  347. ''
  348. );
  349. if (p.ssr === false) {
  350. p.mode = 'client';
  351. } else if (p.mode === undefined) {
  352. p.mode = 'all';
  353. p.src.replace(modePattern, (_, mode) => {
  354. if (modes.includes(mode)) {
  355. p.mode = mode;
  356. }
  357. });
  358. } else if (!['client', 'server', 'all'].includes(p.mode)) {
  359. consola__default['default'].warn(`Invalid plugin mode (server/client/all): '${p.mode}'. Falling back to 'all'`);
  360. p.mode = 'all';
  361. }
  362. return {
  363. src: this.nuxt.resolver.resolveAlias(p.src),
  364. mode: p.mode,
  365. name: 'nuxt_plugin_' + pluginBaseName + '_' + hash__default['default'](p.src)
  366. }
  367. }),
  368. p => p.name
  369. )
  370. }
  371. addOptionalTemplates (templateContext) {
  372. if (this.options.build.indicator) {
  373. templateContext.templateFiles.push('components/nuxt-build-indicator.vue');
  374. }
  375. if (this.options.loading !== false) {
  376. templateContext.templateFiles.push('components/nuxt-loading.vue');
  377. }
  378. }
  379. async resolveFiles (dir, cwd = this.options.srcDir) {
  380. return this.ignore.filter(await glob(this.globPathWithExtensions(dir), {
  381. cwd,
  382. follow: this.options.build.followSymlinks
  383. }))
  384. }
  385. async resolveRelative (dir) {
  386. const dirPrefix = new RegExp(`^${dir}/`);
  387. return (await this.resolveFiles(dir)).map(file => ({ src: file.replace(dirPrefix, '') }))
  388. }
  389. async resolveLayouts ({ templateVars, templateFiles }) {
  390. if (!this.options.features.layouts) {
  391. return
  392. }
  393. if (await fsExtra__default['default'].exists(path__default['default'].resolve(this.options.srcDir, this.options.dir.layouts))) {
  394. for (const file of await this.resolveFiles(this.options.dir.layouts)) {
  395. const name = file
  396. .replace(new RegExp(`^${this.options.dir.layouts}/`), '')
  397. .replace(new RegExp(`\\.(${this.supportedExtensions.join('|')})$`), '');
  398. // Layout Priority: module.addLayout > .vue file > other extensions
  399. if (name === 'error') {
  400. if (!templateVars.components.ErrorPage) {
  401. templateVars.components.ErrorPage = this.relativeToBuild(
  402. this.options.srcDir,
  403. file
  404. );
  405. }
  406. } else if (this.options.layouts[name]) {
  407. consola__default['default'].warn(`Duplicate layout registration, "${name}" has been registered as "${this.options.layouts[name]}"`);
  408. } else if (!templateVars.layouts[name] || /\.vue$/.test(file)) {
  409. templateVars.layouts[name] = this.relativeToBuild(
  410. this.options.srcDir,
  411. file
  412. );
  413. }
  414. }
  415. }
  416. // If no default layout, create its folder and add the default folder
  417. if (!templateVars.layouts.default) {
  418. await fsExtra__default['default'].mkdirp(utils.r(this.options.buildDir, 'layouts'));
  419. templateFiles.push('layouts/default.vue');
  420. templateVars.layouts.default = './layouts/default.vue';
  421. }
  422. }
  423. async resolveRoutes ({ templateVars }) {
  424. consola__default['default'].debug('Generating routes...');
  425. const { routeNameSplitter, trailingSlash } = this.options.router;
  426. if (this._defaultPage) {
  427. templateVars.router.routes = utils.createRoutes({
  428. files: ['index.vue'],
  429. srcDir: this.template.dir + '/pages',
  430. routeNameSplitter,
  431. trailingSlash
  432. });
  433. } else if (this._nuxtPages) {
  434. // Use nuxt createRoutes bases on pages/
  435. const files = {};
  436. const ext = new RegExp(`\\.(${this.supportedExtensions.join('|')})$`);
  437. for (const page of await this.resolveFiles(this.options.dir.pages)) {
  438. const key = page.replace(ext, '');
  439. // .vue file takes precedence over other extensions
  440. if (/\.vue$/.test(page) || !files[key]) {
  441. files[key] = page.replace(/(['"])/g, '\\$1');
  442. }
  443. }
  444. templateVars.router.routes = utils.createRoutes({
  445. files: Object.values(files),
  446. srcDir: this.options.srcDir,
  447. pagesDir: this.options.dir.pages,
  448. routeNameSplitter,
  449. supportedExtensions: this.supportedExtensions,
  450. trailingSlash
  451. });
  452. } else { // If user defined a custom method to create routes
  453. templateVars.router.routes = await this.options.build.createRoutes(
  454. this.options.srcDir
  455. );
  456. }
  457. await this.nuxt.callHook(
  458. 'build:extendRoutes',
  459. templateVars.router.routes,
  460. utils.r
  461. );
  462. // router.extendRoutes method
  463. if (typeof this.options.router.extendRoutes === 'function') {
  464. // let the user extend the routes
  465. const extendedRoutes = await this.options.router.extendRoutes(
  466. templateVars.router.routes,
  467. utils.r
  468. );
  469. // Only overwrite routes when something is returned for backwards compatibility
  470. if (extendedRoutes !== undefined) {
  471. templateVars.router.routes = extendedRoutes;
  472. }
  473. }
  474. // Make routes accessible for other modules and webpack configs
  475. this.routes = templateVars.router.routes;
  476. }
  477. async resolveStore ({ templateVars, templateFiles }) {
  478. // Add store if needed
  479. if (!this.options.features.store || !this.options.store) {
  480. return
  481. }
  482. templateVars.storeModules = (await this.resolveRelative(this.options.dir.store))
  483. .sort(({ src: p1 }, { src: p2 }) => {
  484. // modules are sorted from low to high priority (for overwriting properties)
  485. let res = p1.split('/').length - p2.split('/').length;
  486. if (res === 0 && p1.includes('/index.')) {
  487. res = -1;
  488. } else if (res === 0 && p2.includes('/index.')) {
  489. res = 1;
  490. }
  491. return res
  492. });
  493. templateFiles.push('store.js');
  494. }
  495. async resolveMiddleware ({ templateVars, templateFiles }) {
  496. if (!this.options.features.middleware) {
  497. return
  498. }
  499. const middleware = await this.resolveRelative(this.options.dir.middleware);
  500. const extRE = new RegExp(`\\.(${this.supportedExtensions.join('|')})$`);
  501. templateVars.middleware = middleware.map(({ src }) => {
  502. const name = src.replace(extRE, '');
  503. const dst = this.relativeToBuild(this.options.srcDir, this.options.dir.middleware, src);
  504. return { name, src, dst }
  505. });
  506. templateFiles.push('middleware.js');
  507. }
  508. async resolveCustomTemplates (templateContext) {
  509. // Sanitize custom template files
  510. this.options.build.templates = this.options.build.templates.map((t) => {
  511. const src = t.src || t;
  512. return {
  513. src: utils.r(this.options.srcDir, src),
  514. dst: t.dst || path__default['default'].basename(src),
  515. custom: true,
  516. ...(typeof t === 'object' ? t : undefined)
  517. }
  518. });
  519. const customTemplateFiles = this.options.build.templates.map(t => t.dst || path__default['default'].basename(t.src || t));
  520. const templatePaths = lodash.uniq([
  521. // Modules & user provided templates
  522. // first custom to keep their index
  523. ...customTemplateFiles,
  524. // @nuxt/vue-app templates
  525. ...templateContext.templateFiles
  526. ]);
  527. const appDir = path__default['default'].resolve(this.options.srcDir, this.options.dir.app);
  528. templateContext.templateFiles = await Promise.all(templatePaths.map(async (file) => {
  529. // Use custom file if provided in build.templates[]
  530. const customTemplateIndex = customTemplateFiles.indexOf(file);
  531. const customTemplate = customTemplateIndex !== -1 ? this.options.build.templates[customTemplateIndex] : null;
  532. let src = customTemplate ? (customTemplate.src || customTemplate) : utils.r(this.template.dir, file);
  533. // Allow override templates using a file with same name in ${srcDir}/app
  534. const customAppFile = path__default['default'].resolve(this.options.srcDir, this.options.dir.app, file);
  535. const customAppFileExists = customAppFile.startsWith(appDir) && await fsExtra__default['default'].exists(customAppFile);
  536. if (customAppFileExists) {
  537. src = customAppFile;
  538. }
  539. return {
  540. src,
  541. dst: file,
  542. custom: Boolean(customAppFileExists || customTemplate),
  543. options: (customTemplate && customTemplate.options) || {}
  544. }
  545. }));
  546. }
  547. async resolveLoadingIndicator ({ templateFiles }) {
  548. if (!this.options.loadingIndicator.name) {
  549. return
  550. }
  551. let indicatorPath = path__default['default'].resolve(
  552. this.template.dir,
  553. 'views/loading',
  554. this.options.loadingIndicator.name + '.html'
  555. );
  556. let customIndicator = false;
  557. if (!await fsExtra__default['default'].exists(indicatorPath)) {
  558. indicatorPath = this.nuxt.resolver.resolveAlias(
  559. this.options.loadingIndicator.name
  560. );
  561. if (await fsExtra__default['default'].exists(indicatorPath)) {
  562. customIndicator = true;
  563. } else {
  564. indicatorPath = null;
  565. }
  566. }
  567. if (!indicatorPath) {
  568. consola__default['default'].error(
  569. `Could not fetch loading indicator: ${
  570. this.options.loadingIndicator.name
  571. }`
  572. );
  573. return
  574. }
  575. templateFiles.push({
  576. src: indicatorPath,
  577. dst: 'loading.html',
  578. custom: customIndicator,
  579. options: this.options.loadingIndicator
  580. });
  581. }
  582. async compileTemplates (templateContext) {
  583. // Prepare template options
  584. const { templateVars, templateFiles, templateOptions } = templateContext;
  585. await this.nuxt.callHook('build:templates', {
  586. templateVars,
  587. templatesFiles: templateFiles,
  588. resolve: utils.r
  589. });
  590. templateOptions.imports = {
  591. ...templateOptions.imports,
  592. resolvePath: this.nuxt.resolver.resolvePath,
  593. resolveAlias: this.nuxt.resolver.resolveAlias,
  594. relativeToBuild: this.relativeToBuild
  595. };
  596. // Interpret and move template files to .nuxt/
  597. await Promise.all(
  598. templateFiles.map(async (templateFile) => {
  599. const { src, dst, custom } = templateFile;
  600. // Add custom templates to watcher
  601. if (custom) {
  602. this.options.build.watch.push(src);
  603. }
  604. // Render template to dst
  605. const fileContent = await fsExtra__default['default'].readFile(src, 'utf8');
  606. let content;
  607. try {
  608. const templateFunction = lodash.template(fileContent, templateOptions);
  609. content = utils.stripWhitespace(
  610. templateFunction({
  611. ...templateVars,
  612. ...templateFile
  613. })
  614. );
  615. } catch (err) {
  616. throw new Error(`Could not compile template ${src}: ${err.message}`)
  617. }
  618. // Ensure parent dir exits and write file
  619. const relativePath = utils.r(this.options.buildDir, dst);
  620. await fsExtra__default['default'].outputFile(relativePath, content, 'utf8');
  621. })
  622. );
  623. }
  624. resolvePlugins () {
  625. // Check plugins exist then set alias to their real path
  626. return Promise.all(this.plugins.map(async (p) => {
  627. const ext = '{?(.+([^.])),/index.+([^.])}';
  628. const pluginFiles = await glob(`${p.src}${ext}`);
  629. if (!pluginFiles || pluginFiles.length === 0) {
  630. throw new Error(`Plugin not found: ${p.src}`)
  631. }
  632. if (pluginFiles.length > 1 && !utils.isIndexFileAndFolder(pluginFiles)) {
  633. consola__default['default'].warn({
  634. message: `Found ${pluginFiles.length} plugins that match the configuration, suggest to specify extension:`,
  635. additional: '\n' + pluginFiles.map(x => `- ${x}`).join('\n')
  636. });
  637. }
  638. p.src = this.relativeToBuild(p.src);
  639. }))
  640. }
  641. // TODO: Uncomment when generateConfig enabled again
  642. // async generateConfig() {
  643. // const config = path.resolve(this.options.buildDir, 'build.config.js')
  644. // const options = omit(this.options, Options.unsafeKeys)
  645. // await fsExtra.writeFile(
  646. // config,
  647. // `export default ${JSON.stringify(options, null, ' ')}`,
  648. // 'utf8'
  649. // )
  650. // }
  651. createFileWatcher (patterns, events, listener, watcherCreatedCallback) {
  652. const options = this.options.watchers.chokidar;
  653. const watcher = chokidar__default['default'].watch(patterns, options);
  654. for (const event of events) {
  655. watcher.on(event, listener);
  656. }
  657. // TODO: due to fixes in chokidar this isnt used anymore and could be removed in Nuxt v3
  658. const { rewatchOnRawEvents } = this.options.watchers;
  659. if (rewatchOnRawEvents && Array.isArray(rewatchOnRawEvents)) {
  660. watcher.on('raw', (_event) => {
  661. if (rewatchOnRawEvents.includes(_event)) {
  662. watcher.close();
  663. listener();
  664. this.createFileWatcher(patterns, events, listener, watcherCreatedCallback);
  665. }
  666. });
  667. }
  668. if (typeof watcherCreatedCallback === 'function') {
  669. watcherCreatedCallback(watcher);
  670. }
  671. }
  672. assignWatcher (key) {
  673. return (watcher) => {
  674. if (this.watchers[key]) {
  675. this.watchers[key].close();
  676. }
  677. this.watchers[key] = watcher;
  678. }
  679. }
  680. watchClient () {
  681. let patterns = [
  682. utils.r(this.options.srcDir, this.options.dir.layouts),
  683. utils.r(this.options.srcDir, this.options.dir.middleware)
  684. ];
  685. if (this.options.store) {
  686. patterns.push(utils.r(this.options.srcDir, this.options.dir.store));
  687. }
  688. if (this._nuxtPages && !this._defaultPage) {
  689. patterns.push(utils.r(this.options.srcDir, this.options.dir.pages));
  690. }
  691. patterns = patterns.map((path, ...args) => upath__default['default'].normalizeSafe(this.globPathWithExtensions(path), ...args));
  692. const refreshFiles = lodash.debounce(() => this.generateRoutesAndFiles(), 200);
  693. // Watch for src Files
  694. this.createFileWatcher(patterns, ['add', 'unlink'], refreshFiles, this.assignWatcher('files'));
  695. // Watch for custom provided files
  696. const customPatterns = lodash.uniq([
  697. ...this.options.build.watch,
  698. ...Object.values(lodash.omit(this.options.build.styleResources, ['options']))
  699. ]).map(this.nuxt.resolver.resolveAlias).map(upath__default['default'].normalizeSafe);
  700. if (customPatterns.length === 0) {
  701. return
  702. }
  703. this.createFileWatcher(customPatterns, ['change'], refreshFiles, this.assignWatcher('custom'));
  704. // Watch for app/ files
  705. this.createFileWatcher([utils.r(this.options.srcDir, this.options.dir.app)], ['add', 'change', 'unlink'], refreshFiles, this.assignWatcher('app'));
  706. }
  707. serverMiddlewareHMR () {
  708. // Check nuxt.server dependency
  709. if (!this.nuxt.server) {
  710. return
  711. }
  712. // Get registered server middleware with path
  713. const entries = this.nuxt.server.serverMiddlewarePaths();
  714. // Resolve dependency tree
  715. const deps = new Set();
  716. const dep2Entry = {};
  717. for (const entry of entries) {
  718. for (const dep of utils.scanRequireTree(entry)) {
  719. deps.add(dep);
  720. if (!dep2Entry[dep]) {
  721. dep2Entry[dep] = new Set();
  722. }
  723. dep2Entry[dep].add(entry);
  724. }
  725. }
  726. // Create watcher
  727. this.createFileWatcher(
  728. Array.from(deps),
  729. ['all'],
  730. lodash.debounce((event, fileName) => {
  731. if (!dep2Entry[fileName]) {
  732. return // #7097
  733. }
  734. for (const entry of dep2Entry[fileName]) {
  735. // Reload entry
  736. let newItem;
  737. try {
  738. newItem = this.nuxt.server.replaceMiddleware(entry, entry);
  739. } catch (error) {
  740. consola__default['default'].error(error);
  741. consola__default['default'].error(`[HMR Error]: ${error}`);
  742. }
  743. if (!newItem) {
  744. // Full reload if HMR failed
  745. return this.nuxt.callHook('watch:restart', { event, path: fileName })
  746. }
  747. // Log
  748. consola__default['default'].info(`[HMR] ${chalk__default['default'].cyan(newItem.route || '/')} (${chalk__default['default'].grey(fileName)})`);
  749. }
  750. // Tree may be changed so recreate watcher
  751. this.serverMiddlewareHMR();
  752. }, 200),
  753. this.assignWatcher('serverMiddleware')
  754. );
  755. }
  756. watchRestart () {
  757. const nuxtRestartWatch = [
  758. // Custom watchers
  759. ...this.options.watch
  760. ].map(this.nuxt.resolver.resolveAlias);
  761. if (this.ignore.ignoreFile) {
  762. nuxtRestartWatch.push(this.ignore.ignoreFile);
  763. }
  764. if (this.options._envConfig && this.options._envConfig.dotenv) {
  765. nuxtRestartWatch.push(this.options._envConfig.dotenv);
  766. }
  767. // If default page displayed, watch for first page creation
  768. if (this._nuxtPages && this._defaultPage) {
  769. nuxtRestartWatch.push(path__default['default'].join(this.options.srcDir, this.options.dir.pages));
  770. }
  771. // If store not activated, watch for a file in the directory
  772. if (!this.options.store) {
  773. nuxtRestartWatch.push(path__default['default'].join(this.options.srcDir, this.options.dir.store));
  774. }
  775. this.createFileWatcher(
  776. nuxtRestartWatch,
  777. ['all'],
  778. async (event, fileName) => {
  779. if (['add', 'change', 'unlink'].includes(event) === false) {
  780. return
  781. }
  782. await this.nuxt.callHook('watch:fileChanged', this, fileName); // Legacy
  783. await this.nuxt.callHook('watch:restart', { event, path: fileName });
  784. },
  785. this.assignWatcher('restart')
  786. );
  787. }
  788. unwatch () {
  789. for (const watcher in this.watchers) {
  790. this.watchers[watcher].close();
  791. }
  792. }
  793. async close () {
  794. if (this.__closed) {
  795. return
  796. }
  797. this.__closed = true;
  798. // Unwatch
  799. this.unwatch();
  800. // Close bundleBuilder
  801. if (typeof this.bundleBuilder.close === 'function') {
  802. await this.bundleBuilder.close();
  803. }
  804. }
  805. }
  806. const STATUS = {
  807. INITIAL: 1,
  808. BUILD_DONE: 2,
  809. BUILDING: 3
  810. };
  811. function getBuilder (nuxt) {
  812. return new Builder(nuxt)
  813. }
  814. function build (nuxt) {
  815. return getBuilder(nuxt).build()
  816. }
  817. exports.Builder = Builder;
  818. exports.build = build;
  819. exports.getBuilder = getBuilder;