2023-03-14 14:24:00 +00:00
import { pathToFileURL } from 'node:url'
2023-04-19 21:02:52 +00:00
import type { EventType } from '@parcel/watcher'
2023-05-18 13:44:24 +00:00
import type { FSWatcher } from 'chokidar'
2021-05-20 11:42:41 +00:00
import chokidar from 'chokidar'
2023-09-19 21:26:15 +00:00
import { isIgnored , logger , tryResolveModule , useNuxt } from '@nuxt/kit'
2023-04-19 21:02:52 +00:00
import { interopDefault } from 'mlly'
2022-03-16 11:11:30 +00:00
import { debounce } from 'perfect-debounce'
2023-07-26 09:16:01 +00:00
import { normalize , relative , resolve } from 'pathe'
2023-08-08 11:33:10 +00:00
import type { Nuxt , NuxtBuilder } from 'nuxt/schema'
2021-04-02 11:47:01 +00:00
2023-04-07 16:02:47 +00:00
import { generateApp as _generateApp , createApp } from './app'
2023-08-08 11:33:10 +00:00
import { checkForExternalConfigurationFiles } from './external-config-files'
2023-03-11 21:16:01 +00:00
2021-05-20 11:42:41 +00:00
export async function build ( nuxt : Nuxt ) {
const app = createApp ( nuxt )
2023-05-09 22:46:03 +00:00
nuxt . apps . default = app
2022-03-16 11:11:30 +00:00
const generateApp = debounce ( ( ) = > _generateApp ( nuxt , app ) , undefined , { leading : true } )
2022-03-15 10:56:16 +00:00
await generateApp ( )
2020-08-17 18:02:10 +00:00
if ( nuxt . options . dev ) {
2021-05-20 11:42:41 +00:00
watch ( nuxt )
2023-07-26 09:16:01 +00:00
nuxt . hook ( 'builder:watch' , async ( event , relativePath ) = > {
if ( event === 'change' ) { return }
const path = resolve ( nuxt . options . srcDir , relativePath )
const relativePaths = nuxt . options . _layers . map ( l = > relative ( l . config . srcDir || l . cwd , path ) )
const restartPath = relativePaths . find ( relativePath = > /^(app\.|error\.|plugins\/|middleware\/|layouts\/)/i . test ( relativePath ) )
if ( restartPath ) {
if ( restartPath . startsWith ( 'app' ) ) {
2022-08-12 17:47:58 +00:00
app . mainComponent = undefined
2021-07-23 14:42:49 +00:00
}
2023-07-26 09:16:01 +00:00
if ( restartPath . startsWith ( 'error' ) ) {
2022-08-12 17:47:58 +00:00
app . errorComponent = undefined
2022-03-11 08:22:16 +00:00
}
2022-03-15 10:56:16 +00:00
await generateApp ( )
2021-05-20 11:42:41 +00:00
}
} )
2022-10-24 08:53:02 +00:00
nuxt . hook ( 'builder:generateApp' , ( options ) = > {
// Bypass debounce if we are selectively invalidating templates
if ( options ) { return _generateApp ( nuxt , app , options ) }
return generateApp ( )
} )
2020-08-17 18:02:10 +00:00
}
2020-07-02 13:02:35 +00:00
2022-10-27 10:36:37 +00:00
await nuxt . callHook ( 'build:before' )
2022-01-24 13:28:47 +00:00
if ( ! nuxt . options . _prepare ) {
2023-08-08 11:33:10 +00:00
await Promise . all ( [ checkForExternalConfigurationFiles ( ) , bundle ( nuxt ) ] )
2022-10-27 10:36:37 +00:00
await nuxt . callHook ( 'build:done' )
2022-01-24 13:28:47 +00:00
}
2021-07-23 14:45:06 +00:00
if ( ! nuxt . options . dev ) {
await nuxt . callHook ( 'close' , nuxt )
}
2020-07-02 13:02:35 +00:00
}
2023-04-19 21:02:52 +00:00
const watchEvents : Record < EventType , ' add ' | ' addDir ' | ' change ' | ' unlink ' | ' unlinkDir ' > = {
create : 'add' ,
delete : 'unlink' ,
2024-04-05 18:08:32 +00:00
update : 'change' ,
2023-04-19 21:02:52 +00:00
}
async function watch ( nuxt : Nuxt ) {
if ( nuxt . options . experimental . watcher === 'parcel' ) {
2023-05-18 13:44:24 +00:00
const success = await createParcelWatcher ( )
if ( success ) { return }
2023-04-19 21:02:52 +00:00
}
2023-05-18 13:44:24 +00:00
if ( nuxt . options . experimental . watcher === 'chokidar' ) {
return createWatcher ( )
2023-04-11 14:19:45 +00:00
}
2023-05-18 13:44:24 +00:00
return createGranularWatcher ( )
}
function createWatcher ( ) {
const nuxt = useNuxt ( )
2022-08-12 17:47:58 +00:00
const watcher = chokidar . watch ( nuxt . options . _layers . map ( i = > i . config . srcDir as string ) . filter ( Boolean ) , {
2021-05-20 11:42:41 +00:00
. . . nuxt . options . watchers . chokidar ,
ignoreInitial : true ,
ignored : [
2022-02-28 16:11:46 +00:00
isIgnored ,
2024-04-05 18:08:32 +00:00
'node_modules' ,
] ,
2021-05-20 11:42:41 +00:00
} )
2022-03-15 10:56:16 +00:00
2023-07-26 09:16:01 +00:00
// TODO: consider moving to emit absolute path in 3.8 or 4.0
2024-04-30 19:34:32 +00:00
watcher . on ( 'all' , ( event , path ) = > nuxt . callHook ( 'builder:watch' , event , nuxt . options . experimental . relativeWatchPaths ? normalize ( relative ( nuxt . options . srcDir , path ) ) : normalize ( path ) ) )
2023-07-19 14:43:28 +00:00
nuxt . hook ( 'close' , ( ) = > watcher ? . close ( ) )
2023-05-18 13:44:24 +00:00
}
function createGranularWatcher ( ) {
const nuxt = useNuxt ( )
2023-04-11 14:19:45 +00:00
if ( nuxt . options . debug ) {
2023-09-19 21:26:15 +00:00
// eslint-disable-next-line no-console
2023-05-18 13:44:24 +00:00
console . time ( '[nuxt] builder:chokidar:watch' )
2023-04-11 14:19:45 +00:00
}
2023-05-18 13:44:24 +00:00
let pending = 0
const ignoredDirs = new Set ( [ . . . nuxt . options . module sDir , nuxt . options . buildDir ] )
2023-07-26 09:16:01 +00:00
const pathsToWatch = nuxt . options . _layers . map ( layer = > layer . config . srcDir || layer . cwd ) . filter ( d = > d && ! isIgnored ( d ) )
2023-05-25 21:38:34 +00:00
for ( const pattern of nuxt . options . watch ) {
if ( typeof pattern !== 'string' ) { continue }
const path = resolve ( nuxt . options . srcDir , pattern )
2023-05-18 13:44:24 +00:00
if ( pathsToWatch . some ( w = > path . startsWith ( w . replace ( /[^/]$/ , '$&/' ) ) ) ) { continue }
pathsToWatch . push ( path )
}
for ( const dir of pathsToWatch ) {
pending ++
2023-07-19 14:43:28 +00:00
const watcher = chokidar . watch ( dir , { . . . nuxt . options . watchers . chokidar , ignoreInitial : false , depth : 0 , ignored : [ isIgnored , '**/node_modules' ] } )
2023-05-18 13:44:24 +00:00
const watchers : Record < string , FSWatcher > = { }
watcher . on ( 'all' , ( event , path ) = > {
2023-05-25 21:38:34 +00:00
path = normalize ( path )
2023-05-18 13:44:24 +00:00
if ( ! pending ) {
2023-07-26 09:16:01 +00:00
// TODO: consider moving to emit absolute path in 3.8 or 4.0
2024-04-30 19:34:32 +00:00
nuxt . callHook ( 'builder:watch' , event , nuxt . options . experimental . relativeWatchPaths ? relative ( nuxt . options . srcDir , path ) : path )
2023-05-18 13:44:24 +00:00
}
if ( event === 'unlinkDir' && path in watchers ) {
2023-07-19 14:43:28 +00:00
watchers [ path ] ? . close ( )
2023-05-18 13:44:24 +00:00
delete watchers [ path ]
}
2023-05-31 13:11:46 +00:00
if ( event === 'addDir' && path !== dir && ! ignoredDirs . has ( path ) && ! pathsToWatch . includes ( path ) && ! ( path in watchers ) && ! isIgnored ( path ) ) {
2023-05-18 13:44:24 +00:00
watchers [ path ] = chokidar . watch ( path , { . . . nuxt . options . watchers . chokidar , ignored : [ isIgnored ] } )
2023-07-26 09:16:01 +00:00
// TODO: consider moving to emit absolute path in 3.8 or 4.0
2024-04-30 19:34:32 +00:00
watchers [ path ] . on ( 'all' , ( event , p ) = > nuxt . callHook ( 'builder:watch' , event , nuxt . options . experimental . relativeWatchPaths ? normalize ( relative ( nuxt . options . srcDir , p ) ) : normalize ( p ) ) )
2023-07-19 14:43:28 +00:00
nuxt . hook ( 'close' , ( ) = > watchers [ path ] ? . close ( ) )
2023-05-18 13:44:24 +00:00
}
} )
watcher . on ( 'ready' , ( ) = > {
pending --
if ( nuxt . options . debug && ! pending ) {
2023-09-19 21:26:15 +00:00
// eslint-disable-next-line no-console
2023-05-18 13:44:24 +00:00
console . timeEnd ( '[nuxt] builder:chokidar:watch' )
}
} )
}
}
async function createParcelWatcher ( ) {
const nuxt = useNuxt ( )
if ( nuxt . options . debug ) {
2023-09-19 21:26:15 +00:00
// eslint-disable-next-line no-console
2023-05-18 13:44:24 +00:00
console . time ( '[nuxt] builder:parcel:watch' )
}
const watcherPath = await tryResolveModule ( '@parcel/watcher' , [ nuxt . options . rootDir , . . . nuxt . options . module sDir ] )
2023-10-22 07:23:03 +00:00
if ( ! watcherPath ) {
logger . warn ( 'Falling back to `chokidar-granular` as `@parcel/watcher` cannot be resolved in your project.' )
return false
}
const { subscribe } = await import ( pathToFileURL ( watcherPath ) . href ) . then ( interopDefault ) as typeof import ( '@parcel/watcher' )
for ( const layer of nuxt . options . _layers ) {
if ( ! layer . config . srcDir ) { continue }
const watcher = subscribe ( layer . config . srcDir , ( err , events ) = > {
if ( err ) { return }
for ( const event of events ) {
if ( isIgnored ( event . path ) ) { continue }
// TODO: consider moving to emit absolute path in 3.8 or 4.0
2024-04-30 19:34:32 +00:00
nuxt . callHook ( 'builder:watch' , watchEvents [ event . type ] , nuxt . options . experimental . relativeWatchPaths ? normalize ( relative ( nuxt . options . srcDir , event . path ) ) : normalize ( event . path ) )
2023-10-22 07:23:03 +00:00
}
} , {
ignore : [
. . . nuxt . options . ignore ,
2024-04-05 18:08:32 +00:00
'node_modules' ,
] ,
2023-10-22 07:23:03 +00:00
} )
watcher . then ( ( subscription ) = > {
if ( nuxt . options . debug ) {
// eslint-disable-next-line no-console
console . timeEnd ( '[nuxt] builder:parcel:watch' )
}
nuxt . hook ( 'close' , ( ) = > subscription . unsubscribe ( ) )
} )
2023-05-18 13:44:24 +00:00
}
2023-10-22 07:23:03 +00:00
return true
2020-08-17 18:02:10 +00:00
}
2021-05-20 11:42:41 +00:00
async function bundle ( nuxt : Nuxt ) {
2021-10-20 18:50:01 +00:00
try {
2022-03-18 12:57:05 +00:00
const { bundle } = typeof nuxt . options . builder === 'string'
2023-03-11 23:11:28 +00:00
? await loadBuilder ( nuxt , nuxt . options . builder )
2022-03-18 12:57:05 +00:00
: nuxt . options . builder
2023-08-08 11:33:10 +00:00
await bundle ( nuxt )
2022-08-12 17:47:58 +00:00
} catch ( error : any ) {
2021-10-20 18:50:01 +00:00
await nuxt . callHook ( 'build:error' , error )
2022-03-18 12:57:05 +00:00
if ( error . toString ( ) . includes ( 'Cannot find module \'@nuxt/webpack-builder\'' ) ) {
2023-08-08 11:33:10 +00:00
throw new Error ( 'Could not load `@nuxt/webpack-builder`. You may need to add it to your project dependencies, following the steps in `https://github.com/nuxt/framework/pull/2812`.' )
2022-03-18 12:57:05 +00:00
}
2021-10-20 18:50:01 +00:00
throw error
}
2020-08-17 15:25:06 +00:00
}
2023-03-11 23:11:28 +00:00
2023-08-08 11:33:10 +00:00
async function loadBuilder ( nuxt : Nuxt , builder : string ) : Promise < NuxtBuilder > {
2023-03-13 10:14:27 +00:00
const builderPath = await tryResolveModule ( builder , [ nuxt . options . rootDir , import . meta . url ] )
2023-08-08 11:33:10 +00:00
if ( ! builderPath ) {
2023-10-18 10:59:43 +00:00
throw new Error ( ` Loading \` ${ builder } \` builder failed. You can read more about the nuxt \` builder \` option at: \` https://nuxt.com/docs/api/nuxt-config#builder \` ` )
2023-03-11 23:11:28 +00:00
}
2023-08-08 11:33:10 +00:00
return import ( pathToFileURL ( builderPath ) . href )
2023-03-11 23:11:28 +00:00
}