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-05-18 13:44:24 +00:00
import { isIgnored , 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-05-25 21:38:34 +00:00
import { normalize , resolve } from 'pathe'
2023-02-13 22:42:04 +00:00
import type { Nuxt } 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-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 )
nuxt . hook ( 'builder:watch' , async ( event , path ) = > {
2022-06-27 12:10:29 +00:00
if ( event !== 'change' && /^(app\.|error\.|plugins\/|middleware\/|layouts\/)/i . test ( path ) ) {
2022-06-10 14:50:47 +00:00
if ( path . startsWith ( 'app' ) ) {
2022-08-12 17:47:58 +00:00
app . mainComponent = undefined
2021-07-23 14:42:49 +00:00
}
2022-06-10 14:50:47 +00:00
if ( path . 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 ) {
await 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' ,
update : 'change'
}
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 ,
cwd : nuxt.options.srcDir ,
ignoreInitial : true ,
ignored : [
2022-02-28 16:11:46 +00:00
isIgnored ,
2021-05-20 11:42:41 +00:00
'.nuxt' ,
'node_modules'
]
} )
2022-03-15 10:56:16 +00:00
2023-05-18 13:44:24 +00:00
watcher . on ( 'all' , ( event , path ) = > nuxt . callHook ( 'builder:watch' , event , normalize ( path ) ) )
nuxt . hook ( 'close' , ( ) = > watcher . close ( ) )
}
function createGranularWatcher ( ) {
const nuxt = useNuxt ( )
2023-04-11 14:19:45 +00:00
if ( nuxt . options . debug ) {
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 ] )
const pathsToWatch = nuxt . options . _layers . map ( layer = > layer . config . srcDir ) . 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 ++
const watcher = chokidar . watch ( dir , { . . . nuxt . options . watchers . chokidar , ignoreInitial : false , depth : 0 , ignored : [ isIgnored ] } )
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-05-25 21:38:34 +00:00
nuxt . callHook ( 'builder:watch' , event , path )
2023-05-18 13:44:24 +00:00
}
if ( event === 'unlinkDir' && path in watchers ) {
watchers [ path ] . close ( )
delete watchers [ path ]
}
if ( event === 'addDir' && path !== dir && ! ignoredDirs . has ( path ) && ! ( path in watchers ) && ! isIgnored ( path ) ) {
watchers [ path ] = chokidar . watch ( path , { . . . nuxt . options . watchers . chokidar , ignored : [ isIgnored ] } )
watchers [ path ] . on ( 'all' , ( event , path ) = > nuxt . callHook ( 'builder:watch' , event , normalize ( path ) ) )
nuxt . hook ( 'close' , ( ) = > watchers [ path ] . close ( ) )
}
} )
watcher . on ( 'ready' , ( ) = > {
pending --
if ( nuxt . options . debug && ! pending ) {
console . timeEnd ( '[nuxt] builder:chokidar:watch' )
}
} )
}
}
async function createParcelWatcher ( ) {
const nuxt = useNuxt ( )
if ( nuxt . options . debug ) {
console . time ( '[nuxt] builder:parcel:watch' )
}
const watcherPath = await tryResolveModule ( '@parcel/watcher' , [ nuxt . options . rootDir , . . . nuxt . options . module sDir ] )
if ( watcherPath ) {
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 }
nuxt . callHook ( 'builder:watch' , watchEvents [ event . type ] , normalize ( event . path ) )
}
} , {
ignore : [
. . . nuxt . options . ignore ,
'.nuxt' ,
'node_modules'
]
} )
watcher . then ( ( subscription ) = > {
if ( nuxt . options . debug ) {
console . timeEnd ( '[nuxt] builder:parcel:watch' )
}
nuxt . hook ( 'close' , ( ) = > subscription . unsubscribe ( ) )
} )
}
return true
}
console . warn ( '[nuxt] falling back to `chokidar-granular` as `@parcel/watcher` cannot be resolved in your project.' )
return false
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
2021-10-20 18:50:01 +00:00
return 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\'' ) ) {
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`.'
] . join ( '\n' ) )
}
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
async function loadBuilder ( nuxt : Nuxt , builder : string ) {
2023-03-13 10:14:27 +00:00
const builderPath = await tryResolveModule ( builder , [ nuxt . options . rootDir , import . meta . url ] )
if ( builderPath ) {
2023-03-14 14:24:00 +00:00
return import ( pathToFileURL ( builderPath ) . href )
2023-03-11 23:11:28 +00:00
}
}