diff --git a/.gitignore b/.gitignore index 25f68e93ed..6ad514e017 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ node_modules jspm_packages +# Nitro +nitro.d.ts + package-lock.json # */**/yarn.lock diff --git a/README.md b/README.md index 8530ea5992..f0ee144d83 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ - Clone repository - Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable` (use `npm i -g corepack` for Node.js < 16.10) - Install dependencies using `yarn install` +- Start playground with `yarn dev` and open http://localhost:3000 ## License diff --git a/build.config.ts b/build.config.ts index a7a1e099fb..2145295769 100644 --- a/build.config.ts +++ b/build.config.ts @@ -4,6 +4,7 @@ export default defineBuildConfig({ declaration: true, entries: [ 'src/index', + 'src/cli', { input: 'src/runtime/', outDir: 'dist/runtime', format: 'esm' } ], dependencies: [ diff --git a/package.json b/package.json index c05cc0d5b6..45bf1a1aa1 100644 --- a/package.json +++ b/package.json @@ -3,22 +3,31 @@ "version": "0.0.0", "license": "MIT", "type": "module", + "exports": { + ".": "./dist/index.mjs", + "./cli": "./dist/cli.mjs" + }, "main": "./dist/index.mjs", "types": "./dist/index.d.ts", + "bin": { + "nitro": "./dist/cli.mjs", + "nitropack": "./dist/cli.mjs" + }, "files": [ "dist" ], "scripts": { "build": "unbuild", + "dev": "yarn nitro dev playground", + "dev:build": "yarn nitro build playground", "lint": "eslint --ext .ts,.mjs,.cjs .", + "nitro": "jiti ./src/cli.ts", "prepack": "yarn build" }, "dependencies": { "@cloudflare/kv-asset-handler": "^0.2.0", "@netlify/functions": "^0.11.0", - "@nuxt/design": "0.1.5", "@nuxt/devalue": "^2.0.0", - "@nuxt/kit": "npm:@nuxt/kit-edge@latest", "@rollup/plugin-alias": "^3.1.9", "@rollup/plugin-commonjs": "^21.0.1", "@rollup/plugin-inject": "^4.0.4", @@ -52,6 +61,7 @@ "listhen": "^0.2.6", "mime": "^3.0.0", "mlly": "^0.4.1", + "mri": "^1.2.0", "node-fetch": "^3.2.0", "ohmyfetch": "^0.4.15", "ora": "^6.0.1", @@ -73,7 +83,6 @@ "vue-server-renderer": "^2.6.14" }, "devDependencies": { - "@nuxt/schema": "npm:@nuxt/schema-edge@latest", "@nuxtjs/eslint-config-typescript": "^8.0.0", "@types/fs-extra": "^9.0.13", "@types/http-proxy": "^1.17.8", diff --git a/playground/public/index.html b/playground/public/index.html new file mode 100644 index 0000000000..00acbc26c5 --- /dev/null +++ b/playground/public/index.html @@ -0,0 +1,16 @@ + + + + + + Welcome to Nitro! + + + +

Welcome to Nitro!

+ + + + diff --git a/playground/server/api/test.ts b/playground/server/api/test.ts new file mode 100644 index 0000000000..02e438d09e --- /dev/null +++ b/playground/server/api/test.ts @@ -0,0 +1 @@ +export default () => 'Nitro works!' diff --git a/playground/server/middleware/test.ts b/playground/server/middleware/test.ts new file mode 100644 index 0000000000..f96bfd9244 --- /dev/null +++ b/playground/server/middleware/test.ts @@ -0,0 +1,5 @@ + +export default (_req, _res, next) => { + console.log('middleware!') + next() +} diff --git a/src/build.ts b/src/build.ts index 1e8e9e5f7b..62049b6111 100644 --- a/src/build.ts +++ b/src/build.ts @@ -4,21 +4,19 @@ import * as rollup from 'rollup' import fse from 'fs-extra' import { printFSTree } from './utils/tree' import { getRollupConfig } from './rollup/config' -import { hl, prettyPath, serializeTemplate, writeFile, isDirectory, replaceAll } from './utils' -import { NitroContext } from './context' +import { prettyPath, writeFile, isDirectory, replaceAll } from './utils' import { scanMiddleware } from './server/middleware' +import type { Nitro } from './types' -export async function prepare (nitroContext: NitroContext) { - consola.info(`Nitro preset is ${hl(nitroContext.preset)}`) +export async function prepare (nitro: Nitro) { + await cleanupDir(nitro.options.output.dir) - await cleanupDir(nitroContext.output.dir) - - if (!nitroContext.output.publicDir.startsWith(nitroContext.output.dir)) { - await cleanupDir(nitroContext.output.publicDir) + if (!nitro.options.output.publicDir.startsWith(nitro.options.output.dir)) { + await cleanupDir(nitro.options.output.publicDir) } - if (!nitroContext.output.serverDir.startsWith(nitroContext.output.dir)) { - await cleanupDir(nitroContext.output.serverDir) + if (!nitro.options.output.serverDir.startsWith(nitro.options.output.dir)) { + await cleanupDir(nitro.options.output.serverDir) } } @@ -27,58 +25,55 @@ async function cleanupDir (dir: string) { await fse.emptyDir(dir) } -export async function generate (nitroContext: NitroContext) { +export async function generate (nitro: Nitro) { consola.start('Generating public...') - await nitroContext._internal.hooks.callHook('nitro:generate', nitroContext) - - const publicDir = nitroContext._nuxt.publicDir - if (await isDirectory(publicDir)) { - await fse.copy(publicDir, nitroContext.output.publicDir) + const clientDist = resolve(nitro.options.buildDir, 'dist/client') + if (await isDirectory(clientDist)) { + await fse.copy(clientDist, join(nitro.options.output.publicDir, nitro.options.publicPath)) } - const clientDist = resolve(nitroContext._nuxt.buildDir, 'dist/client') - if (await isDirectory(clientDist)) { - const buildAssetsDir = join(nitroContext.output.publicDir, nitroContext._nuxt.buildAssetsDir) - await fse.copy(clientDist, buildAssetsDir) + const publicDir = nitro.options.publicDir + if (await isDirectory(publicDir)) { + await fse.copy(publicDir, nitro.options.output.publicDir) } - consola.success('Generated public ' + prettyPath(nitroContext.output.publicDir)) + consola.success('Generated public ' + prettyPath(nitro.options.output.publicDir)) } -export async function build (nitroContext: NitroContext) { +export async function build (nitro: Nitro) { // Compile html template - const htmlSrc = resolve(nitroContext._nuxt.buildDir, `views/${{ 2: 'app', 3: 'document' }[2]}.template.html`) - const htmlTemplate = { src: htmlSrc, contents: '', dst: '' } - htmlTemplate.dst = htmlTemplate.src.replace(/.html$/, '.mjs').replace('app.template.mjs', 'document.template.mjs') - htmlTemplate.contents = nitroContext.vfs[htmlTemplate.src] || await fse.readFile(htmlTemplate.src, 'utf-8') - await nitroContext._internal.hooks.callHook('nitro:document', htmlTemplate) - const compiled = 'export default ' + serializeTemplate(htmlTemplate.contents) - await writeFile(htmlTemplate.dst, compiled) - - nitroContext.rollupConfig = getRollupConfig(nitroContext) - await nitroContext._internal.hooks.callHook('nitro:rollup:before', nitroContext) - return nitroContext._nuxt.dev ? _watch(nitroContext) : _build(nitroContext) + // const htmlSrc = resolve(nitro.options.buildDir, `views/${{ 2: 'app', 3: 'document' }[2]}.template.html`) + // const htmlTemplate = { src: htmlSrc, contents: '', dst: '' } + // htmlTemplate.dst = htmlTemplate.src.replace(/.html$/, '.mjs').replace('app.template.mjs', 'document.template.mjs') + // htmlTemplate.contents = nitro.vfs[htmlTemplate.src] || await fse.readFile(htmlTemplate.src, 'utf-8') + // await nitro.hooks.callHook('nitro:document', htmlTemplate) + // const compiled = 'export default ' + serializeTemplate(htmlTemplate.contents) + // await writeFile(htmlTemplate.dst, compiled) + + nitro.options.rollupConfig = getRollupConfig(nitro) + await nitro.hooks.callHook('nitro:rollup:before', nitro) + return nitro.options.dev ? _watch(nitro) : _build(nitro) } -export async function writeTypes (nitroContext: NitroContext) { +async function writeTypes (nitro: Nitro) { const routeTypes: Record = {} const middleware = [ - ...nitroContext.scannedMiddleware, - ...nitroContext.middleware + ...nitro.scannedMiddleware, + ...nitro.options.middleware ] for (const mw of middleware) { if (typeof mw.handle !== 'string') { continue } - const relativePath = relative(nitroContext._nuxt.buildDir, mw.handle).replace(/\.[a-z]+$/, '') + const relativePath = relative(nitro.options.buildDir, mw.handle).replace(/\.[a-z]+$/, '') routeTypes[mw.route] = routeTypes[mw.route] || [] routeTypes[mw.route].push(`Awaited>`) } const lines = [ '// Generated by nitro', - 'declare module \'@nuxt/nitro\' {', + 'declare module \'nitopack\' {', ' type Awaited = T extends PromiseLike ? Awaited : T', ' interface InternalApi {', ...Object.entries(routeTypes).map(([path, types]) => ` '${path}': ${types.join(' | ')}`), @@ -88,58 +83,59 @@ export async function writeTypes (nitroContext: NitroContext) { 'export {}' ] - await writeFile(join(nitroContext._nuxt.buildDir, 'nitro.d.ts'), lines.join('\n')) + await writeFile(join(nitro.options.buildDir, 'nitro.d.ts'), lines.join('\n')) } -async function _build (nitroContext: NitroContext) { - nitroContext.scannedMiddleware = await scanMiddleware(nitroContext._nuxt.serverDir) - await writeTypes(nitroContext) +async function _build (nitro: Nitro) { + nitro.scannedMiddleware = await scanMiddleware(nitro.options.serverDir) + await writeTypes(nitro) consola.start('Building server...') - const build = await rollup.rollup(nitroContext.rollupConfig).catch((error) => { + const build = await rollup.rollup(nitro.options.rollupConfig).catch((error) => { consola.error('Rollup error: ' + error.message) throw error }) consola.start('Writing server bundle...') - await build.write(nitroContext.rollupConfig.output) + await build.write(nitro.options.rollupConfig.output) const rewriteBuildPaths = (input: unknown, to: string) => - typeof input === 'string' ? replaceAll(input, nitroContext.output.dir, to) : undefined + typeof input === 'string' ? replaceAll(input, nitro.options.output.dir, to) : undefined // Write build info - const nitroConfigPath = resolve(nitroContext.output.dir, 'nitro.json') + const nitroConfigPath = resolve(nitro.options.output.dir, 'nitro.json') const buildInfo = { date: new Date(), - preset: nitroContext.preset, + // preset: nitro.options.preset, commands: { - preview: rewriteBuildPaths(nitroContext.commands.preview, '.'), - deploy: rewriteBuildPaths(nitroContext.commands.deploy, '.') + preview: rewriteBuildPaths(nitro.options.commands.preview, '.'), + deploy: rewriteBuildPaths(nitro.options.commands.deploy, '.') } } await writeFile(nitroConfigPath, JSON.stringify(buildInfo, null, 2)) consola.success('Server built') - await printFSTree(nitroContext.output.serverDir) - await nitroContext._internal.hooks.callHook('nitro:compiled', nitroContext) + await printFSTree(nitro.options.output.serverDir) + await nitro.hooks.callHook('nitro:compiled', nitro) // Show deploy and preview hints - const rOutDir = relative(process.cwd(), nitroContext.output.dir) - if (nitroContext.commands.preview) { + // TODO + // const rOutDir = relative(process.cwd(), nitro.options.output.dir) + if (nitro.options.commands.preview) { // consola.info(`You can preview this build using \`${rewriteBuildPaths(nitroContext.commands.preview, rOutDir)}\``) - consola.info('You can preview this build using `nuxi preview`') + // consola.info('You can preview this build using `nuxi preview`') } - if (nitroContext.commands.deploy) { - consola.info(`You can deploy this build using \`${rewriteBuildPaths(nitroContext.commands.deploy, rOutDir)}\``) + if (nitro.options.commands.deploy) { + // consola.info(`You can deploy this build using \`${rewriteBuildPaths(nitro.options.commands.deploy, rOutDir)}\``) } return { - entry: resolve(nitroContext.rollupConfig.output.dir, nitroContext.rollupConfig.output.entryFileNames as string) + entry: resolve(nitro.options.rollupConfig.output.dir, nitro.options.rollupConfig.output.entryFileNames as string) } } -function startRollupWatcher (nitroContext: NitroContext) { - const watcher = rollup.watch(nitroContext.rollupConfig) +function startRollupWatcher (nitro: Nitro) { + const watcher = rollup.watch(nitro.options.rollupConfig) let start: number watcher.on('event', (event) => { @@ -155,31 +151,31 @@ function startRollupWatcher (nitroContext: NitroContext) { // Finished building all bundles case 'END': - nitroContext._internal.hooks.callHook('nitro:compiled', nitroContext) + nitro.hooks.callHook('nitro:compiled', nitro) consola.success('Nitro built', start ? `in ${Date.now() - start} ms` : '') return // Encountered an error while bundling case 'ERROR': consola.error('Rollup error: ' + event.error) - // consola.error(event.error) + // consola.error(event.error) } }) return watcher } -async function _watch (nitroContext: NitroContext) { - let watcher = startRollupWatcher(nitroContext) +async function _watch (nitro: Nitro) { + let watcher = startRollupWatcher(nitro) - nitroContext.scannedMiddleware = await scanMiddleware(nitroContext._nuxt.serverDir, + nitro.scannedMiddleware = await scanMiddleware(nitro.options.serverDir, (middleware, event) => { - nitroContext.scannedMiddleware = middleware + nitro.scannedMiddleware = middleware if (['add', 'addDir'].includes(event)) { watcher.close() - writeTypes(nitroContext).catch(console.error) - watcher = startRollupWatcher(nitroContext) + writeTypes(nitro).catch(console.error) + watcher = startRollupWatcher(nitro) } } ) - await writeTypes(nitroContext) + await writeTypes(nitro) } diff --git a/src/cli.ts b/src/cli.ts new file mode 100644 index 0000000000..9e992d7e1c --- /dev/null +++ b/src/cli.ts @@ -0,0 +1,46 @@ +#!/usr/bin/env node + +import mri from 'mri' +import { resolve } from 'pathe' +import { createNitro } from './nitro' +import { build, prepare } from './build' +import { createDevServer } from './server/dev' + +async function main () { + const args = mri(process.argv.slice(2)) + const command = args._[0] + const rootDir = resolve(args._[1] || '.') + + if (command === 'dev') { + const nitro = createNitro({ + rootDir, + dev: true, + preset: 'dev' + }) + const server = createDevServer(nitro) + await server.listen({}) + await prepare(nitro) + await build(nitro) + await server.reload() + return + } + + if (command === 'build') { + const nitro = createNitro({ + rootDir, + dev: false, + preset: 'server' + }) + await prepare(nitro) + await build(nitro) + process.exit(0) + } + + console.error(`Unknown command ${command}! Usage: nitro dev|build [rootDir]`) + process.exit(1) +} + +main().catch((err) => { + console.error(err) + process.exit(1) +}) diff --git a/src/context.ts b/src/context.ts deleted file mode 100644 index bb13c97f22..0000000000 --- a/src/context.ts +++ /dev/null @@ -1,210 +0,0 @@ -/* eslint-disable no-use-before-define */ -import { resolve } from 'pathe' -import defu from 'defu' -import { createHooks, Hookable, NestedHooks } from 'hookable' -import type { Preset } from 'unenv' -import type { NuxtHooks, NuxtOptions } from '@nuxt/schema' -import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer' -import { tryImport, resolvePath, detectTarget, extendPreset, evalTemplate } from './utils' -import * as PRESETS from './presets' -import type { NodeExternalsOptions } from './rollup/plugins/externals' -import type { StorageOptions } from './rollup/plugins/storage' -import type { AssetOptions } from './rollup/plugins/assets' -import type { ServerMiddleware } from './server/middleware' -import type { RollupConfig } from './rollup/config' -import type { Options as EsbuildOptions } from './rollup/plugins/esbuild' -import { runtimeDir } from './dirs' - -export interface NitroHooks { - 'nitro:document': (htmlTemplate: { src: string, contents: string, dst: string }) => void - 'nitro:rollup:before': (context: NitroContext) => void | Promise - 'nitro:compiled': (context: NitroContext) => void - 'nitro:generate': (context: NitroContext) => void | Promise - 'close': () => void -} - -export interface NitroContext { - alias: Record - timing: boolean - inlineDynamicImports: boolean - minify: boolean - sourceMap: boolean - externals: boolean | NodeExternalsOptions - analyze: false | PluginVisualizerOptions - entry: string - node: boolean - preset: string - rollupConfig?: RollupConfig - esbuild?: { - options?: EsbuildOptions - } - experiments?: { - wasm?: boolean - } - commands: { - preview: string | ((config: NitroContext) => string) - deploy: string | ((config: NitroContext) => string) - }, - moduleSideEffects: string[] - renderer: string - serveStatic: boolean - middleware: ServerMiddleware[] - scannedMiddleware: ServerMiddleware[] - hooks: NestedHooks - nuxtHooks: NestedHooks - ignore: string[] - env: Preset - vfs: Record - output: { - dir: string - serverDir: string - publicDir: string - } - storage: StorageOptions, - assets: AssetOptions, - _nuxt: { - majorVersion: number - dev: boolean - ssr: boolean - rootDir: string - srcDir: string - buildDir: string - generateDir: string - publicDir: string - serverDir: string - baseURL: string - buildAssetsDir: string - isStatic: boolean - fullStatic: boolean - staticAssets: any - modulesDir: string[] - runtimeConfig: { public: any, private: any } - } - _internal: { - runtimeDir: string - hooks: Hookable - } -} - -type DeepPartial = T extends Record ? { [P in keyof T]?: DeepPartial | T[P] } : T - -export interface NitroInput extends DeepPartial {} - -export type NitroPreset = NitroInput | ((input: NitroInput) => NitroInput) - -export function getNitroContext (nuxtOptions: NuxtOptions, input: NitroInput): NitroContext { - const defaults: NitroContext = { - alias: {}, - timing: undefined, - inlineDynamicImports: undefined, - minify: undefined, - sourceMap: undefined, - externals: undefined, - analyze: nuxtOptions.build.analyze as any, - entry: undefined, - node: undefined, - preset: undefined, - rollupConfig: undefined, - experiments: {}, - moduleSideEffects: ['unenv/runtime/polyfill/'], - renderer: undefined, - serveStatic: undefined, - commands: { - preview: undefined, - deploy: undefined - }, - middleware: [], - scannedMiddleware: [], - ignore: [], - env: {}, - vfs: {}, - hooks: {}, - nuxtHooks: {}, - output: { - dir: '{{ _nuxt.rootDir }}/.output', - serverDir: '{{ output.dir }}/server', - publicDir: '{{ output.dir }}/public' - }, - storage: { mounts: { } }, - assets: { - inline: !nuxtOptions.dev, - dirs: {} - }, - _nuxt: { - majorVersion: nuxtOptions._majorVersion || 2, - dev: nuxtOptions.dev, - ssr: nuxtOptions.ssr, - rootDir: nuxtOptions.rootDir, - srcDir: nuxtOptions.srcDir, - buildDir: nuxtOptions.buildDir, - generateDir: nuxtOptions.generate.dir, - publicDir: resolve(nuxtOptions.srcDir, nuxtOptions.dir.public || nuxtOptions.dir.static), - serverDir: resolve(nuxtOptions.srcDir, (nuxtOptions.dir as any).server || 'server'), - baseURL: nuxtOptions.app.baseURL || '/', - buildAssetsDir: nuxtOptions.app.buildAssetsDir, - isStatic: nuxtOptions.target === 'static' && !nuxtOptions.dev, - fullStatic: nuxtOptions.target === 'static' && !nuxtOptions._legacyGenerate, - staticAssets: nuxtOptions.generate.staticAssets, - modulesDir: nuxtOptions.modulesDir, - runtimeConfig: { - public: nuxtOptions.publicRuntimeConfig, - private: nuxtOptions.privateRuntimeConfig - } - }, - _internal: { - runtimeDir, - hooks: createHooks() - } - } - - defaults.preset = input.preset || process.env.NITRO_PRESET || detectTarget() || 'server' - // eslint-disable-next-line import/namespace - let presetDefaults = PRESETS[defaults.preset] || tryImport(nuxtOptions.rootDir, defaults.preset) - if (!presetDefaults) { - throw new Error('Cannot resolve preset: ' + defaults.preset) - } - presetDefaults = presetDefaults.default || presetDefaults - - const _presetInput = defu(input, defaults) - const _preset = (extendPreset(presetDefaults /* base */, input) as Function)(_presetInput) - const nitroContext: NitroContext = defu(_preset, defaults) as any - - nitroContext.output.dir = resolvePath(nitroContext, nitroContext.output.dir) - nitroContext.output.publicDir = resolvePath(nitroContext, nitroContext.output.publicDir) - nitroContext.output.serverDir = resolvePath(nitroContext, nitroContext.output.serverDir) - - if (nitroContext.commands.preview) { - nitroContext.commands.preview = evalTemplate(nitroContext, nitroContext.commands.preview) - } - if (nitroContext.commands.deploy) { - nitroContext.commands.deploy = evalTemplate(nitroContext, nitroContext.commands.deploy) - } - - nitroContext._internal.hooks.addHooks(nitroContext.hooks) - - // Dev-only storage - if (nitroContext._nuxt.dev) { - const fsMounts = { - root: resolve(nitroContext._nuxt.rootDir), - src: resolve(nitroContext._nuxt.srcDir), - build: resolve(nitroContext._nuxt.buildDir), - cache: resolve(nitroContext._nuxt.rootDir, '.nuxt/nitro/cache') - } - for (const p in fsMounts) { - nitroContext.storage.mounts[p] = nitroContext.storage.mounts[p] || { - driver: 'fs', - driverOptions: { base: fsMounts[p] } - } - } - } - - // Assets - nitroContext.assets.dirs.server = { - dir: resolve(nitroContext._nuxt.srcDir, 'server/assets'), meta: true - } - - // console.log(nitroContext) - // process.exit(1) - - return nitroContext -} diff --git a/src/index.ts b/src/index.ts index d8bc866cee..9d5bfe4498 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,4 @@ export * from './build' -export * from './context' +export * from './nitro' export * from './server/middleware' export * from './server/dev' -export { wpfs } from './utils/wpfs' diff --git a/src/nitro.ts b/src/nitro.ts new file mode 100644 index 0000000000..2196b12de8 --- /dev/null +++ b/src/nitro.ts @@ -0,0 +1,131 @@ +import { resolve } from 'pathe' +import defu from 'defu' +import { createHooks } from 'hookable' +import { tryImport, resolvePath, detectTarget } from './utils' +import * as PRESETS from './presets' +import type { Nitro, NitroOptions, NitroConfig } from './types' +// import { mergeHooks } from 'hookable' +import { pkgDir, runtimeDir } from './dirs' + +const nitroDefaults: NitroConfig = { + alias: { + '#nitro': runtimeDir + }, + unenv: {}, + analyze: false, + experiments: {}, + moduleSideEffects: ['unenv/runtime/polyfill/'], + middleware: [], + modulesDir: [], + ignore: [], + hooks: {}, + output: { + dir: '{{ rootDir }}/.output', + serverDir: '{{ output.dir }}/server', + publicDir: '{{ output.dir }}/public' + }, + storage: { mounts: {} }, + commands: {}, + assets: { + // inline: !config.dev, + dirs: {} + }, + publicDir: 'public', + serverDir: 'server', + routerBase: '/', + publicPath: '/', + runtimeConfig: { + public: { + app: { + baseURL: '/', + cdnURL: undefined, + buildAssetsDir: '_dist' + } + }, + private: {} + } +} + +export function createNitro (config: NitroConfig = {}): Nitro { + // Apply nitro defaults + config = defu(config, nitroDefaults) + + // Apply preset defaults + config.extends = config.preset = process.env.NITRO_PRESET || config.extends || config.preset || detectTarget() || 'server' + config = extendConfig(config) + + // Normalize options + const options = config as NitroOptions + options.rootDir = resolve(options.rootDir || '.') + for (const key of ['srcDir', 'publicDir', 'serverDir', 'generateDir', 'buildDir']) { + options[key] = resolve(options.rootDir, options[key]) + } + options.modulesDir.push(resolve(options.rootDir, 'node_modules')) + options.modulesDir.push(resolve(pkgDir, 'node_modules')) + + // Create context + const nitro: Nitro = { + options, + hooks: createHooks(), + vfs: {}, + scannedMiddleware: [] + } + + // Init hooks + nitro.hooks.addHooks(nitro.options.hooks) + + // Resolve output dir + options.output.dir = resolvePath(nitro, nitro.options.output.dir) + options.output.publicDir = resolvePath(nitro, nitro.options.output.publicDir) + options.output.serverDir = resolvePath(nitro, nitro.options.output.serverDir) + + // Dev-only storage + if (nitro.options.dev) { + const fsMounts = { + root: resolve(nitro.options.rootDir), + src: resolve(nitro.options.srcDir), + build: resolve(nitro.options.buildDir), + cache: resolve(nitro.options.rootDir, 'node_modules/.nitro/cache') + } + for (const p in fsMounts) { + nitro.options.storage.mounts[p] = nitro.options.storage.mounts[p] || { + driver: 'fs', + driverOptions: { base: fsMounts[p] } + } + } + } + + // Assets + nitro.options.assets.dirs.server = { + dir: resolve(nitro.options.srcDir, 'server/assets'), meta: true + } + + return nitro +} + +function extendConfig (config: NitroConfig): NitroConfig { + if (!config.extends) { + return config + } + + let _extends = config.extends + if (typeof config.extends === 'string') { + type Preset = NitroConfig['preset'] + _extends = (PRESETS as Record)[config.extends] || tryImport(config.rootDir, config.extends) || {} + if (!_extends) { + throw new Error('Cannot resolve config: ' + config.extends) + } + _extends = (_extends as any).default || _extends + } + if (typeof _extends === 'function') { + _extends = _extends(config) + } + + // TODO: Merge hooks + const preset = extendConfig(_extends as NitroConfig) + return defu(config, preset) +} + +export function defineNitroPreset (preset: NitroConfig['extends']) { + return preset +} diff --git a/src/presets/azure.ts b/src/presets/azure.ts index 1b5c0dd91c..d9b9c4a41a 100644 --- a/src/presets/azure.ts +++ b/src/presets/azure.ts @@ -2,10 +2,11 @@ import fse from 'fs-extra' import { globby } from 'globby' import { join, resolve } from 'pathe' import { writeFile } from '../utils' -import { NitroPreset, NitroContext } from '../context' +import { defineNitroPreset } from '../nitro' +import type { Nitro } from '../types' -export const azure: NitroPreset = { - entry: '{{ _internal.runtimeDir }}/entries/azure', +export const azure = defineNitroPreset({ + entry: '#nitro/entries/azure', externals: true, output: { serverDir: '{{ output.dir }}/server/functions' @@ -14,13 +15,13 @@ export const azure: NitroPreset = { preview: 'npx @azure/static-web-apps-cli start {{ output.publicDir }} --api-location {{ output.serverDir }}/..' }, hooks: { - async 'nitro:compiled' (ctx: NitroContext) { + async 'nitro:compiled' (ctx: Nitro) { await writeRoutes(ctx) } } -} +}) -async function writeRoutes ({ output }: NitroContext) { +async function writeRoutes (nitro) { const host = { version: '2.0' } @@ -32,7 +33,7 @@ async function writeRoutes ({ output }: NitroContext) { } } - const indexPath = resolve(output.publicDir, 'index.html') + const indexPath = resolve(nitro.options.output.publicDir, 'index.html') const indexFileExists = fse.existsSync(indexPath) if (!indexFileExists) { config.routes.unshift( @@ -48,10 +49,10 @@ async function writeRoutes ({ output }: NitroContext) { } const folderFiles = await globby([ - join(output.publicDir, 'index.html'), - join(output.publicDir, '**/index.html') + join(nitro.options.output.publicDir, 'index.html'), + join(nitro.options.output.publicDir, '**/index.html') ]) - const prefix = output.publicDir.length + const prefix = nitro.options.output.publicDir.length const suffix = '/index.html'.length folderFiles.forEach(file => config.routes.unshift({ @@ -60,7 +61,7 @@ async function writeRoutes ({ output }: NitroContext) { }) ) - const otherFiles = await globby([join(output.publicDir, '**/*.html'), join(output.publicDir, '*.html')]) + const otherFiles = await globby([join(nitro.options.output.publicDir, '**/*.html'), join(nitro.options.output.publicDir, '*.html')]) otherFiles.forEach((file) => { if (file.endsWith('index.html')) { return @@ -97,9 +98,9 @@ async function writeRoutes ({ output }: NitroContext) { ] } - await writeFile(resolve(output.serverDir, 'function.json'), JSON.stringify(functionDefinition)) - await writeFile(resolve(output.serverDir, '../host.json'), JSON.stringify(host)) - await writeFile(resolve(output.publicDir, 'staticwebapp.config.json'), JSON.stringify(config)) + await writeFile(resolve(nitro.options.output.serverDir, 'function.json'), JSON.stringify(functionDefinition)) + await writeFile(resolve(nitro.options.output.serverDir, '../host.json'), JSON.stringify(host)) + await writeFile(resolve(nitro.options.output.publicDir, 'staticwebapp.config.json'), JSON.stringify(config)) if (!indexFileExists) { await writeFile(indexPath, '') } diff --git a/src/presets/azure_functions.ts b/src/presets/azure_functions.ts index 30d67fec16..bfb792e0e8 100644 --- a/src/presets/azure_functions.ts +++ b/src/presets/azure_functions.ts @@ -2,22 +2,23 @@ import { createWriteStream } from 'fs' import archiver from 'archiver' import { join, resolve } from 'pathe' import { writeFile } from '../utils' -import { NitroPreset, NitroContext } from '../context' +import { defineNitroPreset } from '../nitro' +import type { Nitro } from '../types' // eslint-disable-next-line -export const azure_functions: NitroPreset = { +export const azure_functions = defineNitroPreset({ serveStatic: true, - entry: '{{ _internal.runtimeDir }}/entries/azure_functions', + entry: '#nitro/entries/azure_functions', externals: true, commands: { deploy: 'az functionapp deployment source config-zip -g -n --src {{ output.dir }}/deploy.zip' }, hooks: { - async 'nitro:compiled' (ctx: NitroContext) { + async 'nitro:compiled' (ctx: Nitro) { await writeRoutes(ctx) } } -} +}) function zipDirectory (dir: string, outfile: string): Promise { const archive = archiver('zip', { zlib: { level: 9 } }) @@ -34,7 +35,7 @@ function zipDirectory (dir: string, outfile: string): Promise { }) } -async function writeRoutes ({ output: { dir, serverDir } }: NitroContext) { +async function writeRoutes (nitro: Nitro) { const host = { version: '2.0', extensions: { http: { routePrefix: '' } } @@ -67,7 +68,7 @@ async function writeRoutes ({ output: { dir, serverDir } }: NitroContext) { ] } - await writeFile(resolve(serverDir, 'function.json'), JSON.stringify(functionDefinition)) - await writeFile(resolve(dir, 'host.json'), JSON.stringify(host)) - await zipDirectory(dir, join(dir, 'deploy.zip')) + await writeFile(resolve(nitro.options.serverDir, 'function.json'), JSON.stringify(functionDefinition)) + await writeFile(resolve(nitro.options.output.dir, 'host.json'), JSON.stringify(host)) + await zipDirectory(nitro.options.output.dir, join(nitro.options.output.dir, 'deploy.zip')) } diff --git a/src/presets/browser.ts b/src/presets/browser.ts index 5cc7a5d840..ca046c241c 100644 --- a/src/presets/browser.ts +++ b/src/presets/browser.ts @@ -2,13 +2,12 @@ import { existsSync, promises as fsp } from 'fs' import { resolve } from 'pathe' import consola from 'consola' import { joinURL } from 'ufo' -import { extendPreset, prettyPath } from '../utils' -import { NitroPreset, NitroContext, NitroInput } from '../context' -import { worker } from './worker' +import { prettyPath } from '../utils' +import { defineNitroPreset } from '../nitro' -export const browser: NitroPreset = extendPreset(worker, (input: NitroInput) => { - // TODO: Join base at runtime - const baseURL = input._nuxt.baseURL +export const browser = defineNitroPreset((_input) => { + // TODO + const baseURL = '/' const script = `