From c06f09e9ab9e7272ca039563089f4b8e44db62a1 Mon Sep 17 00:00:00 2001 From: pooya parsa Date: Fri, 20 Nov 2020 01:16:31 +0100 Subject: [PATCH] sigma (#36) --- packages/nitro/src/build.ts | 97 +++++++--- packages/nitro/src/config.ts | 108 ----------- packages/nitro/src/context.ts | 110 +++++++++++ packages/nitro/src/index.ts | 85 +-------- packages/nitro/src/module/nuxt2.ts | 126 +++++++++++++ packages/nitro/src/presets/browser.ts | 40 ++++ packages/nitro/src/presets/cli.ts | 15 ++ packages/nitro/src/presets/cloudflare.ts | 19 ++ packages/nitro/src/presets/dev.ts | 11 ++ .../nitro/src/{targets => presets}/index.ts | 4 +- packages/nitro/src/presets/lambda.ts | 7 + packages/nitro/src/presets/netlify.ts | 10 + packages/nitro/src/presets/node.ts | 6 + packages/nitro/src/presets/server.ts | 16 ++ packages/nitro/src/presets/vercel.ts | 48 +++++ packages/nitro/src/presets/worker.ts | 11 ++ packages/nitro/src/rollup/config.ts | 171 +++++++----------- packages/nitro/src/rollup/dynamic-require.ts | 2 +- packages/nitro/src/rollup/externals.ts | 2 +- packages/nitro/src/rollup/timing.ts | 7 +- packages/nitro/src/server.ts | 137 ++++++++++++++ packages/nitro/src/targets/browser.ts | 44 ----- packages/nitro/src/targets/cjs.ts | 10 - packages/nitro/src/targets/cloudflare.ts | 21 --- packages/nitro/src/targets/lambda.ts | 8 - packages/nitro/src/targets/netlify.ts | 11 -- packages/nitro/src/targets/node.ts | 8 - packages/nitro/src/targets/vercel.ts | 47 ----- packages/nitro/src/targets/worker.ts | 12 -- packages/nitro/src/utils/index.ts | 37 ++-- packages/nitro/src/utils/tree.ts | 4 +- packages/nitro/src/utils/wpfs.ts | 7 + 32 files changed, 735 insertions(+), 506 deletions(-) delete mode 100644 packages/nitro/src/config.ts create mode 100644 packages/nitro/src/context.ts create mode 100644 packages/nitro/src/module/nuxt2.ts create mode 100644 packages/nitro/src/presets/browser.ts create mode 100644 packages/nitro/src/presets/cli.ts create mode 100644 packages/nitro/src/presets/cloudflare.ts create mode 100644 packages/nitro/src/presets/dev.ts rename packages/nitro/src/{targets => presets}/index.ts (72%) create mode 100644 packages/nitro/src/presets/lambda.ts create mode 100644 packages/nitro/src/presets/netlify.ts create mode 100644 packages/nitro/src/presets/node.ts create mode 100644 packages/nitro/src/presets/server.ts create mode 100644 packages/nitro/src/presets/vercel.ts create mode 100644 packages/nitro/src/presets/worker.ts create mode 100644 packages/nitro/src/server.ts delete mode 100644 packages/nitro/src/targets/browser.ts delete mode 100644 packages/nitro/src/targets/cjs.ts delete mode 100644 packages/nitro/src/targets/cloudflare.ts delete mode 100644 packages/nitro/src/targets/lambda.ts delete mode 100644 packages/nitro/src/targets/netlify.ts delete mode 100644 packages/nitro/src/targets/node.ts delete mode 100644 packages/nitro/src/targets/vercel.ts delete mode 100644 packages/nitro/src/targets/worker.ts create mode 100644 packages/nitro/src/utils/wpfs.ts diff --git a/packages/nitro/src/build.ts b/packages/nitro/src/build.ts index 121a489ac8..4c13bb2d8e 100644 --- a/packages/nitro/src/build.ts +++ b/packages/nitro/src/build.ts @@ -1,48 +1,97 @@ -import { resolve } from 'path' +import { resolve, join } from 'upath' import consola from 'consola' -import { rollup } from 'rollup' -import Hookable from 'hookable' -import { readFile, emptyDir } from 'fs-extra' +import { rollup, watch as rollupWatch } from 'rollup' +import ora from 'ora' +import { readFile, emptyDir, copy } from 'fs-extra' import { printFSTree } from './utils/tree' import { getRollupConfig } from './rollup/config' import { hl, serializeTemplate, writeFile } from './utils' -import { SLSOptions } from './config' +import { SigmaContext } from './context' -export async function build (options: SLSOptions) { - consola.info(`Generating bundle for ${hl(options.target)}`) +export async function build (sigmaContext: SigmaContext) { + consola.info(`Sigma preset is ${hl(sigmaContext.preset)}`) - const hooks = new Hookable() - hooks.addHooks(options.hooks) - - if (options.cleanTargetDir) { - await emptyDir(options.targetDir) - } + // Cleanup output dir + await emptyDir(sigmaContext.output.dir) // Compile html template - const htmlSrc = resolve(options.buildDir, `views/${{ 2: 'app', 3: 'document' }[2]}.template.html`) + const htmlSrc = resolve(sigmaContext._nuxt.buildDir, `views/${{ 2: 'app', 3: 'document' }[2]}.template.html`) const htmlTemplate = { src: htmlSrc, contents: '', dst: '', compiled: '' } htmlTemplate.dst = htmlTemplate.src.replace(/.html$/, '.js').replace('app.', 'document.') htmlTemplate.contents = await readFile(htmlTemplate.src, 'utf-8') htmlTemplate.compiled = 'module.exports = ' + serializeTemplate(htmlTemplate.contents) - await hooks.callHook('template:document', htmlTemplate) + await sigmaContext._internal.hooks.callHook('sigma:template:document', htmlTemplate) await writeFile(htmlTemplate.dst, htmlTemplate.compiled) - options.rollupConfig = getRollupConfig(options) + await generate(sigmaContext) - await hooks.callHook('rollup:before', options) + sigmaContext.rollupConfig = getRollupConfig(sigmaContext) - const build = await rollup(options.rollupConfig).catch((error) => { - error.message = '[serverless] Rollup Error: ' + error.message + await sigmaContext._internal.hooks.callHook('sigma:rollup:before', sigmaContext) + + return sigmaContext._nuxt.dev ? _watch(sigmaContext) : _build(sigmaContext) +} + +export async function generate (sigmaContext: SigmaContext) { + await copy( + resolve(sigmaContext._nuxt.buildDir, 'dist/client'), + join(sigmaContext.output.publicDir, sigmaContext._nuxt.publicPath) + ) + await copy( + resolve(sigmaContext._nuxt.rootDir, sigmaContext._nuxt.staticDir), + sigmaContext.output.publicDir + ) +} + +async function _build (sigmaContext: SigmaContext) { + const spinner = ora() + + spinner.start('Building server...') + const build = await rollup(sigmaContext.rollupConfig).catch((error) => { + spinner.fail('Rollup error: ' + error.messsage) throw error }) - await build.write(options.rollupConfig.output) + spinner.start('Wrting Sigma bundle...') + await build.write(sigmaContext.rollupConfig.output) - await printFSTree(options.targetDir) - - await hooks.callHook('done', options) + spinner.succeed('Sigma built') + await printFSTree(sigmaContext.output.serverDir) + await sigmaContext._internal.hooks.callHook('sigma:compiled', sigmaContext) return { - entry: resolve(options.rollupConfig.output.dir, options.rollupConfig.output.entryFileNames) + entry: resolve(sigmaContext.rollupConfig.output.dir, sigmaContext.rollupConfig.output.entryFileNames) } } + +function _watch (sigmaContext: SigmaContext) { + const spinner = ora() + + const watcher = rollupWatch(sigmaContext.rollupConfig) + + let start + + watcher.on('event', (event) => { + switch (event.code) { + // The watcher is (re)starting + case 'START': + return + + // Building an individual bundle + case 'BUNDLE_START': + start = Date.now() + spinner.start('Building Sigma...') + return + + // Finished building all bundles + case 'END': + sigmaContext._internal.hooks.callHook('sigma:compiled', sigmaContext) + return spinner.succeed(`Sigma built in ${Date.now() - start} ms`) + + // Encountered an error while bundling + case 'ERROR': + spinner.fail('Rollup error: ' + event.error) + // consola.error(event.error) + } + }) +} diff --git a/packages/nitro/src/config.ts b/packages/nitro/src/config.ts deleted file mode 100644 index 5fdef9687d..0000000000 --- a/packages/nitro/src/config.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { resolve } from 'path' -import defu from 'defu' -import type { NuxtOptions } from '@nuxt/types' -import Hookable, { configHooksT } from 'hookable' -import { tryImport, resolvePath, detectTarget, extendTarget } from './utils' -import * as TARGETS from './targets' - -// eslint-disable-next-line -export type UnresolvedPath = string | ((config: SLSOptions) => string) - -export interface Nuxt extends Hookable{ - options: NuxtOptions -} - -export interface ServerMiddleware { - route: string - handle: string - lazy?: boolean -} - -export interface SLSOptions { - hooks: configHooksT - nuxtHooks: configHooksT - - rootDir: string - buildDir: string - publicDir: string - routerBase: string - publicPath: string - fullStatic: boolean - staticAssets: any - - entry: UnresolvedPath - outName: string - node: false | true - target: string - minify: boolean - externals: boolean - rollupConfig?: any - timing: boolean - inlineChunks: boolean - renderer: string - analyze: boolean - cleanTargetDir: boolean - - runtimeDir: string - slsDir: string - targetDir: string - - serverMiddleware: ServerMiddleware[], - - static: string[] - generateIgnore: string[] -} - -export interface SLSConfig extends Omit, 'targetDir'> { - targetDir?: UnresolvedPath -} - -export type SLSTargetFn = (config: SLSConfig) => SLSConfig -export type SLSTarget = SLSConfig | SLSTargetFn - -export function getoptions (nuxtOptions: Nuxt['options'], serverless: SLSConfig): SLSOptions { - const defaults: SLSConfig = { - rootDir: nuxtOptions.rootDir, - buildDir: nuxtOptions.buildDir, - publicDir: nuxtOptions.generate.dir, - routerBase: nuxtOptions.router.base, - publicPath: nuxtOptions.build.publicPath, - fullStatic: nuxtOptions.target === 'static' && !nuxtOptions._legacyGenerate, - // @ts-ignore - staticAssets: nuxtOptions.generate.staticAssets, - - outName: '_nuxt.js', - timing: true, - inlineChunks: true, - minify: false, - externals: false, - cleanTargetDir: true, - - runtimeDir: resolve(__dirname, '../runtime'), - slsDir: '{{ rootDir }}/.nuxt/serverless', - targetDir: '{{ slsDir }}/{{ target }}', - - serverMiddleware: serverless.serverMiddleware || [], - - static: [], - generateIgnore: [] - } - - const target = serverless.target || process.env.NUXT_SLS_TARGET || detectTarget() - let targetDefaults = TARGETS[target] || tryImport(nuxtOptions.rootDir, target) - if (!targetDefaults) { - throw new Error('Cannot resolve target: ' + target) - } - targetDefaults = targetDefaults.default || targetDefaults - - const _defaults = defu(defaults, { target }) - const _targetInput = defu(nuxtOptions.serverless, _defaults) - const _target = extendTarget(nuxtOptions.serverless, targetDefaults)(_targetInput) - const options: SLSOptions = defu(nuxtOptions.serverless, _target, _defaults) - - options.slsDir = resolvePath(options, options.slsDir) - options.targetDir = resolvePath(options, options.targetDir) - options.publicDir = resolvePath(options, options.publicDir) - - return options -} diff --git a/packages/nitro/src/context.ts b/packages/nitro/src/context.ts new file mode 100644 index 0000000000..709ce9ddf6 --- /dev/null +++ b/packages/nitro/src/context.ts @@ -0,0 +1,110 @@ +import { resolve } from 'upath' +import defu from 'defu' +import type { NuxtOptions } from '@nuxt/types' +import Hookable, { configHooksT } from 'hookable' +import { tryImport, resolvePath, detectTarget, extendPreset } from './utils' +import * as PRESETS from './presets' + +export interface ServerMiddleware { + route: string + handle: string + lazy?: boolean +} + +export interface SigmaContext { + timing: boolean + inlineChunks: boolean + minify: boolean + externals: boolean + analyze: boolean + entry: string + node: boolean + preset: string + rollupConfig?: any + renderer: string + middleware: ServerMiddleware[] + hooks: configHooksT + ignore: string[] + output: { + dir: string + serverDir: string + publicDir: string + } + _nuxt: { + dev: boolean + rootDir: string + buildDir: string + staticDir: string + routerBase: string + publicPath: string + fullStatic: boolean + staticAssets: any + } + _internal: { + runtimeDir: string + hooks: Hookable + } +} + +export interface SigmaInput extends Partial {} + +export type SigmaPreset = SigmaInput | ((input: SigmaInput) => SigmaInput) + +export function getsigmaContext (nuxtOptions: NuxtOptions, input: SigmaInput): SigmaContext { + const defaults: SigmaContext = { + timing: true, + inlineChunks: true, + minify: true, + externals: false, + analyze: false, + entry: undefined, + node: undefined, + preset: undefined, + rollupConfig: undefined, + renderer: undefined, + middleware: [], + ignore: [], + hooks: {}, + output: { + dir: '{{ _nuxt.rootDir }}/.output', + serverDir: '{{ output.dir }}/server', + publicDir: '{{ output.dir }}/public' + }, + _nuxt: { + dev: nuxtOptions.dev, + rootDir: nuxtOptions.rootDir, + buildDir: nuxtOptions.buildDir, + staticDir: nuxtOptions.dir.static, + routerBase: nuxtOptions.router.base, + publicPath: nuxtOptions.build.publicPath, + fullStatic: nuxtOptions.preset === 'static' && !nuxtOptions._legacyGenerate, + // @ts-ignore + staticAssets: nuxtOptions.generate.staticAssets + }, + _internal: { + runtimeDir: resolve(__dirname, '../runtime'), + hooks: undefined + } + } + + defaults.preset = input.preset || process.env.SIGMA_PRESET || detectTarget() || 'server' + 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) + // @ts-ignore + const _preset = extendPreset(input, presetDefaults)(_presetInput) + const sigmaContext: SigmaContext = defu(input, _preset, defaults) as any + + sigmaContext.output.dir = resolvePath(sigmaContext, sigmaContext.output.dir) + sigmaContext.output.publicDir = resolvePath(sigmaContext, sigmaContext.output.publicDir) + sigmaContext.output.serverDir = resolvePath(sigmaContext, sigmaContext.output.serverDir) + + // console.log(sigmaContext) + // process.exit(1) + + return sigmaContext +} diff --git a/packages/nitro/src/index.ts b/packages/nitro/src/index.ts index 80e6fbf074..8c14ac283c 100644 --- a/packages/nitro/src/index.ts +++ b/packages/nitro/src/index.ts @@ -1,85 +1,6 @@ -import type { Module } from '@nuxt/types' -import { build } from './build' -import { getoptions } from './config' +import nuxt2 from './module/nuxt2' -export default function slsModule () { +export default function () { const { nuxt } = this - - if (nuxt.options.dev) { - return - } - - // Config - const options = getoptions(nuxt.options, nuxt.options.serverless || {}) - - // Tune webpack config - nuxt.options.build._minifyServer = false - nuxt.options.build.standalone = false - - // Tune generator - nuxt.options.generate.crawler = false - if (Array.isArray(nuxt.options.generate.routes)) { - nuxt.options.generate.routes = Array.from(new Set([ - ...nuxt.options.generate.routes, - ...options.static - ])) - } - nuxt.options.generate.dir = options.publicDir - - // serverMiddleware - // TODO: render:setupMiddleware hook - // TODO: support m.prefix and m.route - nuxt.hook('modules:done', () => { - const unsupported = [] - for (let m of nuxt.options.serverMiddleware) { - if (typeof m === 'string') { - m = { handler: m } - } - - const route = m.path || m.route || '/' - const handle = nuxt.resolver.resolvePath(m.handler || m.handle) - - if (typeof handle !== 'string' || typeof route !== 'string') { - unsupported.push(m) - continue - } - - options.serverMiddleware.push({ ...m, route, handle }) - } - if (unsupported.length) { - console.warn('[serverless] Unsupported Server middleware used: ', unsupported) - console.info('Supported format is `{ path: string, handler: string }` and handler should export `(req, res) => {}`') - } - }) - - if (options.nuxtHooks) { - nuxt.addHooks(options.nuxtHooks) - } - - nuxt.hook('generate:cache:ignore', (ignore: string[]) => { - ignore.push(options.slsDir) - ignore.push(options.targetDir) - ignore.push(...options.generateIgnore) - }) - - nuxt.hook('generate:page', (page) => { - // TODO: Use ssrContext - if (!options.static.includes(page.route)) { - page.exclude = true - } - }) - - nuxt.hook('generate:before', async () => { - console.info('Building light version for `nuxt generate`') - const { entry } = await build(getoptions(nuxt.options, { - target: 'cjs', - serverMiddleware: options.serverMiddleware - })) - console.info('Loading lambda') - require(entry) - }) - - nuxt.hook('generate:done', async () => { - await build(options) - }) + return nuxt2(nuxt) } diff --git a/packages/nitro/src/module/nuxt2.ts b/packages/nitro/src/module/nuxt2.ts new file mode 100644 index 0000000000..74072f998d --- /dev/null +++ b/packages/nitro/src/module/nuxt2.ts @@ -0,0 +1,126 @@ +import fetch from 'node-fetch' +import { resolve } from 'upath' +import { build } from '../build' +import { getsigmaContext, SigmaContext } from '../context' +import { createDevServer } from '../server' +import wpfs from '../utils/wpfs' + +export default function (nuxt) { + // Build in node_modules/.cache/nuxt + const oldBuildDir = nuxt.options.buildDir + nuxt.options.buildDir = resolve(nuxt.options.rootDir, 'node_modules/.cache/nuxt') + nuxt.options.build.transpile = nuxt.options.build.transpile || [] + nuxt.options.build.transpile.push(nuxt.options.buildDir) + nuxt.options.appTemplatePath = nuxt.options.appTemplatePath + .replace(oldBuildDir, nuxt.options.buildDir) + + // Create contexts + const sigmaContext = getsigmaContext(nuxt.options, nuxt.options.sigma || {}) + const sigmaDevContext = getsigmaContext(nuxt.options, { preset: 'dev' }) + + // Use nuxt as main hooks host + sigmaContext._internal.hooks = nuxt + sigmaDevContext._internal.hooks = nuxt + nuxt.addHooks(sigmaContext.hooks) + + // Replace nuxt server + if (nuxt.server) { + nuxt.server.__closed = true + nuxt.server = createNuxt2DevServer(sigmaDevContext) + nuxt.addHooks(sigmaDevContext.hooks) + } + + // serverMiddleware bridge + // TODO: render:setupMiddleware hook + // TODO: support m.prefix and m.route + nuxt.hook('modules:done', () => { + const unsupported = [] + for (let m of nuxt.options.serverMiddleware) { + if (typeof m === 'string') { m = { handler: m } } + const route = m.path || m.route || '/' + let handle = m.handler || m.handle + if (typeof handle !== 'string' || typeof route !== 'string') { + if (route === '/_loading') { + nuxt.server.setLoadingMiddleware(handle) + continue + } + unsupported.push(m) + continue + } + handle = nuxt.resolver.resolvePath(handle) + sigmaContext.middleware.push({ ...m, route, handle }) + sigmaDevContext.middleware.push({ ...m, route, handle }) + } + nuxt.options.serverMiddleware = [...unsupported] + if (unsupported.length) { + console.warn('[sigma] Unsupported Server middleware used: \n', ...unsupported) + console.info('Supported format is `{ path: string, handler: string }` and handler should export `(req, res) => {}`') + } + }) + + // nuxt build/dev + nuxt.options.build._minifyServer = false + nuxt.options.build.standalone = false + nuxt.hook('build:done', async () => { + await build(nuxt.options.dev ? sigmaDevContext : sigmaContext) + }) + + // nude dev + if (nuxt.options.dev) { + nuxt.hook('sigma:compiled', () => { nuxt.server.watch() }) + nuxt.hook('build:compile', ({ compiler }) => { compiler.outputFileSystem = wpfs }) + nuxt.hook('server:devMiddleware', (m) => { nuxt.server.setDevMiddleware(m) }) + } + + // nuxt generate + nuxt.hook('generate:cache:ignore', (ignore: string[]) => { + ignore.push(sigmaContext.output.dir) + ignore.push(sigmaContext.output.serverDir) + ignore.push(sigmaContext.output.publicDir) + ignore.push(...sigmaContext.ignore) + }) + + // generate:bfore is before webpack build that we need! + nuxt.hook('generate:extendRoutes', async () => { + await build(sigmaDevContext) + await nuxt.server.reload() + }) +} + +function createNuxt2DevServer (sigmaContext: SigmaContext) { + const server = createDevServer(sigmaContext) + + const listeners = [] + async function listen (port) { + const listener = await server.listen(port) + listeners.push(listener) + return listeners + } + + async function renderRoute (route = '/', renderContext = {}) { + const [listener] = listeners + if (!listener) { + throw new Error('There is no server listener to call `server.renderRoute()`') + } + const html = await fetch(listener.url + route, { + headers: { 'nuxt-render-context': encodeQuery(renderContext) } + }).then(r => r.text()) + + return { html } + } + + return { + ...server, + listeners, + renderRoute, + listen, + serverMiddlewarePaths () { return [] }, + ready () {} + } +} + +function encodeQuery (obj) { + return Object.entries(obj).map( + ([key, val]) => `${encodeURIComponent(key)}=${encodeURIComponent(JSON.stringify(val))}` + ).join('&') +} diff --git a/packages/nitro/src/presets/browser.ts b/packages/nitro/src/presets/browser.ts new file mode 100644 index 0000000000..cfd2875615 --- /dev/null +++ b/packages/nitro/src/presets/browser.ts @@ -0,0 +1,40 @@ +import { writeFile } from 'fs-extra' +import { resolve } from 'upath' +import consola from 'consola' +import { extendPreset, prettyPath } from '../utils' +import { SigmaPreset, SigmaContext, SigmaInput } from '../context' +import { worker } from './worker' + +export const browser: SigmaPreset = extendPreset(worker, (input: SigmaInput) => { + const script = `` + + return { + entry: '{{ _internal.runtimeDir }}/entries/service-worker', + output: { + dir: '{{ _nuxt.rootDir }}/.output/public', + publicDir: '{{ output.dir }}', + serverDir: '{{ output.dir }}' + }, + hooks: { + 'vue-renderer:ssr:templateParams' (params) { + params.APP += script + }, + 'vue-renderer:spa:templateParams' (params) { + params.APP += script + }, + 'sigma:template:document' (tmpl) { + tmpl.compiled = tmpl.compiled.replace('', script + '') + }, + async 'sigma:compiled' ({ output }: SigmaContext) { + await writeFile(resolve(output.publicDir, 'index.html'), script) // TODO + consola.info('Ready to deploy to static hosting:', prettyPath(output.publicDir)) + } + } + } +}) diff --git a/packages/nitro/src/presets/cli.ts b/packages/nitro/src/presets/cli.ts new file mode 100644 index 0000000000..ca9ca98350 --- /dev/null +++ b/packages/nitro/src/presets/cli.ts @@ -0,0 +1,15 @@ +import consola from 'consola' +import { extendPreset, prettyPath } from '../utils' +import { SigmaPreset, SigmaContext } from '../context' +import { node } from './node' + +export const cli: SigmaPreset = extendPreset(node, { + entry: '{{ _internal.runtimeDir }}/entries/cli', + externals: true, + inlineChunks: true, + hooks: { + 'sigma:compiled' ({ output }: SigmaContext) { + consola.info('Run with `node ' + prettyPath(output.serverDir) + ' [route]`') + } + } +}) diff --git a/packages/nitro/src/presets/cloudflare.ts b/packages/nitro/src/presets/cloudflare.ts new file mode 100644 index 0000000000..43715ac0fb --- /dev/null +++ b/packages/nitro/src/presets/cloudflare.ts @@ -0,0 +1,19 @@ +import { resolve } from 'upath' +import consola from 'consola' +import { extendPreset, writeFile, prettyPath } from '../utils' +import { SigmaContext, SigmaPreset } from '../context' +import { worker } from './worker' + +export const cloudflare: SigmaPreset = extendPreset(worker, { + entry: '{{ _internal.runtimeDir }}/entries/cloudflare', + ignore: [ + 'wrangler.toml' + ], + hooks: { + async 'sigma:compiled' ({ output, _nuxt }: SigmaContext) { + await writeFile(resolve(output.dir, 'package.json'), JSON.stringify({ private: true, main: './server/index.js' }, null, 2)) + await writeFile(resolve(output.dir, 'package-lock.json'), JSON.stringify({ lockfileVersion: 1 }, null, 2)) + consola.success('Ready to run `wrangler publish` in', prettyPath(_nuxt.rootDir)) + } + } +}) diff --git a/packages/nitro/src/presets/dev.ts b/packages/nitro/src/presets/dev.ts new file mode 100644 index 0000000000..d2e661b569 --- /dev/null +++ b/packages/nitro/src/presets/dev.ts @@ -0,0 +1,11 @@ +import { extendPreset } from '../utils' +import { SigmaPreset } from '../context' +import { node } from './node' + +export const dev: SigmaPreset = extendPreset(node, { + entry: '{{ _internal.runtimeDir }}/entries/dev', + minify: false, + externals: true, + inlineChunks: true, + timing: true +}) diff --git a/packages/nitro/src/targets/index.ts b/packages/nitro/src/presets/index.ts similarity index 72% rename from packages/nitro/src/targets/index.ts rename to packages/nitro/src/presets/index.ts index cba2c91749..c59d83d2fb 100644 --- a/packages/nitro/src/targets/index.ts +++ b/packages/nitro/src/presets/index.ts @@ -3,6 +3,8 @@ export * from './cloudflare' export * from './lambda' export * from './netlify' export * from './node' -export * from './cjs' +export * from './dev' +export * from './server' +export * from './cli' export * from './vercel' export * from './worker' diff --git a/packages/nitro/src/presets/lambda.ts b/packages/nitro/src/presets/lambda.ts new file mode 100644 index 0000000000..812aeeae76 --- /dev/null +++ b/packages/nitro/src/presets/lambda.ts @@ -0,0 +1,7 @@ + +import { SigmaPreset } from '../context' + +export const lambda: SigmaPreset = { + entry: '{{ _internal.runtimeDir }}/entries/lambda', + inlineChunks: false +} diff --git a/packages/nitro/src/presets/netlify.ts b/packages/nitro/src/presets/netlify.ts new file mode 100644 index 0000000000..f4ed81841f --- /dev/null +++ b/packages/nitro/src/presets/netlify.ts @@ -0,0 +1,10 @@ +import { extendPreset } from '../utils' +import { SigmaPreset } from '../context' +import { lambda } from './lambda' + +export const netlify: SigmaPreset = extendPreset(lambda, { + ignore: [ + 'netlify.toml', + '_redirects' + ] +}) diff --git a/packages/nitro/src/presets/node.ts b/packages/nitro/src/presets/node.ts new file mode 100644 index 0000000000..e9581aeb2d --- /dev/null +++ b/packages/nitro/src/presets/node.ts @@ -0,0 +1,6 @@ +import { SigmaPreset } from '../context' + +export const node: SigmaPreset = { + entry: '{{ _internal.runtimeDir }}/entries/node', + inlineChunks: false +} diff --git a/packages/nitro/src/presets/server.ts b/packages/nitro/src/presets/server.ts new file mode 100644 index 0000000000..1d331c68f6 --- /dev/null +++ b/packages/nitro/src/presets/server.ts @@ -0,0 +1,16 @@ +import consola from 'consola' +import { extendPreset, hl, prettyPath } from '../utils' +import { SigmaPreset, SigmaContext } from '../context' +import { node } from './node' + +export const server: SigmaPreset = extendPreset(node, { + entry: '{{ _internal.runtimeDir }}/entries/server', + externals: false, + inlineChunks: false, + timing: true, + hooks: { + 'sigma:compiled' ({ output }: SigmaContext) { + consola.success('Ready to run', hl('node ' + prettyPath(output.serverDir))) + } + } +}) diff --git a/packages/nitro/src/presets/vercel.ts b/packages/nitro/src/presets/vercel.ts new file mode 100644 index 0000000000..b4ed58decf --- /dev/null +++ b/packages/nitro/src/presets/vercel.ts @@ -0,0 +1,48 @@ +import { resolve } from 'upath' +import { extendPreset, writeFile } from '../utils' +import { SigmaPreset, SigmaContext } from '../context' +import { node } from './node' + +export const vercel: SigmaPreset = extendPreset(node, { + output: { + dir: '{{ _nuxt.rootDir }}/.vercel_build_output', + serverDir: '{{ output.dir }}/functions/node/_nuxt/index.js', + publicDir: '{{ output.dir }}/static' + }, + ignore: [ + 'vercel.json' + ], + hooks: { + async 'sigma:compiled' (ctx: SigmaContext) { + await writeRoutes(ctx) + } + } +}) + +async function writeRoutes ({ output }: SigmaContext) { + const routes = [ + { + src: '/sw.js', + headers: { + 'cache-control': 'public, max-age=0, must-revalidate' + }, + continue: true + }, + { + src: '/_nuxt/(.*)', + headers: { + 'cache-control': 'public,max-age=31536000,immutable' + }, + continue: true + }, + { + handle: 'filesystem' + }, + { + src: '(.*)', + dest: '/.vercel/functions/_nuxt/index' + } + ] + + await writeFile(resolve(output.dir, 'config/routes.json'), JSON.stringify(routes, null, 2)) +} diff --git a/packages/nitro/src/presets/worker.ts b/packages/nitro/src/presets/worker.ts new file mode 100644 index 0000000000..3f80a2bb44 --- /dev/null +++ b/packages/nitro/src/presets/worker.ts @@ -0,0 +1,11 @@ +import { SigmaPreset, SigmaContext } from '../context' + +export const worker: SigmaPreset = { + entry: null, // Abstract + node: false, + hooks: { + 'sigma:rollup:before' ({ rollupConfig }: SigmaContext) { + rollupConfig.output.format = 'iife' + } + } +} diff --git a/packages/nitro/src/rollup/config.ts b/packages/nitro/src/rollup/config.ts index 0cfdfc5135..a939feb191 100644 --- a/packages/nitro/src/rollup/config.ts +++ b/packages/nitro/src/rollup/config.ts @@ -1,5 +1,5 @@ import Module from 'module' -import { dirname, join, relative, resolve } from 'path' +import { dirname, join, relative, resolve } from 'upath' import { InputOptions, OutputOptions } from 'rollup' import { terser } from 'rollup-plugin-terser' import commonjs from '@rollup/plugin-commonjs' @@ -10,110 +10,64 @@ import replace from '@rollup/plugin-replace' import virtual from '@rollup/plugin-virtual' import inject from '@rollup/plugin-inject' import analyze from 'rollup-plugin-analyzer' +import * as un from '@nuxt/un' import hasha from 'hasha' -import { SLSOptions } from '../config' +import { SigmaContext } from '../context' import { resolvePath, MODULE_DIR } from '../utils' import { dynamicRequire } from './dynamic-require' import { externals } from './externals' import { timing } from './timing' -const mapArrToVal = (val, arr) => arr.reduce((p, c) => ({ ...p, [c]: val }), {}) - export type RollupConfig = InputOptions & { output: OutputOptions } -export const getRollupConfig = (options: SLSOptions) => { - const extensions: string[] = ['.ts', '.mjs', '.js', '.json', '.node'] +export const getRollupConfig = (sigmaContext: SigmaContext) => { + const extensions: string[] = ['.ts', '.js', '.json', '.node'] const external: InputOptions['external'] = [] - const injects:{ [key: string]: string| string[] } = {} + const presets = [] - const aliases: { [key: string]: string } = {} - - Object.assign(aliases, mapArrToVal('~mocks/generic', [ - // @nuxt/devalue - 'consola', - // vue2 - 'encoding', - 'stream', - 'he', - 'resolve', - 'source-map', - 'lodash.template', - 'serialize-javascript', - // vue3 - '@babel/parser', - '@vue/compiler-core', - '@vue/compiler-dom', - '@vue/compiler-ssr' - ])) - - // Uses eval 😈 - aliases.depd = '~mocks/custom/depd' - - if (options.node === false) { - // Globals - // injects.Buffer = ['buffer', 'Buffer'] <-- TODO: Make it opt-in - injects.process = '~mocks/node/process' - - // Aliases - Object.assign(aliases, { - // Node - ...mapArrToVal('~mocks/generic', Module.builtinModules), - http: '~mocks/node/http', - fs: '~mocks/node/fs', - process: '~mocks/node/process', - 'node-process': require.resolve('process/browser.js'), - // buffer: require.resolve('buffer/index.js'), - util: require.resolve('util/util.js'), - events: require.resolve('events/events.js'), - inherits: require.resolve('inherits/inherits_browser.js'), - - // Custom - 'node-fetch': '~mocks/custom/node-fetch', - etag: '~mocks/generic/noop', - - // Express - ...mapArrToVal('~mocks/generic', [ - 'serve-static', - 'iconv-lite' - ]), - - // Mime - 'mime-db': '~mocks/custom/mime-db', - 'mime/lite': require.resolve('mime/lite'), - mime: '~mocks/custom/mime' - }) + if (sigmaContext.node === false) { + presets.push(un.nodeless) } else { + presets.push(un.node) external.push(...Module.builtinModules) } - const chunksDirName = join(dirname(options.outName), 'chunks') - const serverDir = join(options.buildDir, 'dist/server') + const env = un.env(...presets, { + alias: { + depd: require.resolve('@nuxt/un/runtime/npm/depd') + } + }) + + const buildServerDir = join(sigmaContext._nuxt.buildDir, 'dist/server') + const runtimeAppDir = join(sigmaContext._internal.runtimeDir, 'app') const rollupConfig: RollupConfig = { - input: resolvePath(options, options.entry), + input: resolvePath(sigmaContext, sigmaContext.entry), output: { - dir: options.targetDir, - entryFileNames: options.outName, + dir: sigmaContext.output.serverDir, + entryFileNames: 'index.js', chunkFileNames (chunkInfo) { let prefix = '' const modules = Object.keys(chunkInfo.modules) const lastModule = modules[modules.length - 1] - if (lastModule.startsWith(serverDir)) { - prefix = join('ssr', relative(serverDir, dirname(lastModule))) - } else if (lastModule.startsWith(options.buildDir)) { - prefix = 'ssr' - } else if (lastModule.startsWith(options.runtimeDir)) { - prefix = 'runtime' - } else if (!prefix && options.serverMiddleware.find(m => lastModule.startsWith(m.handle))) { + if (lastModule.startsWith(buildServerDir)) { + prefix = join('app', relative(buildServerDir, dirname(lastModule))) + } else if (lastModule.startsWith(runtimeAppDir)) { + prefix = 'app' + } else if (lastModule.startsWith(sigmaContext._nuxt.buildDir)) { + prefix = 'nuxt' + } else if (lastModule.startsWith(sigmaContext._internal.runtimeDir)) { + prefix = 'sigma' + } else if (!prefix && sigmaContext.middleware.find(m => lastModule.startsWith(m.handle))) { prefix = 'middleware' } - return join(chunksDirName, prefix, '[name].js') + return join('chunks', prefix, '[name].js') }, - inlineDynamicImports: options.inlineChunks, + inlineDynamicImports: sigmaContext.inlineChunks, format: 'cjs', exports: 'auto', intro: '', @@ -124,28 +78,28 @@ export const getRollupConfig = (options: SLSOptions) => { plugins: [] } - if (options.timing) { + if (sigmaContext.timing) { rollupConfig.plugins.push(timing()) } // https://github.com/rollup/plugins/tree/master/packages/replace rollupConfig.plugins.push(replace({ values: { - 'process.env.NODE_ENV': '"production"', + 'process.env.NODE_ENV': sigmaContext._nuxt.dev ? '"development"' : '"production"', 'typeof window': '"undefined"', - 'process.env.ROUTER_BASE': JSON.stringify(options.routerBase), - 'process.env.PUBLIC_PATH': JSON.stringify(options.publicPath), - 'process.env.NUXT_STATIC_BASE': JSON.stringify(options.staticAssets.base), - 'process.env.NUXT_STATIC_VERSION': JSON.stringify(options.staticAssets.version), + 'process.env.ROUTER_BASE': JSON.stringify(sigmaContext._nuxt.routerBase), + 'process.env.PUBLIC_PATH': JSON.stringify(sigmaContext._nuxt.publicPath), + 'process.env.NUXT_STATIC_BASE': JSON.stringify(sigmaContext._nuxt.staticAssets.base), + 'process.env.NUXT_STATIC_VERSION': JSON.stringify(sigmaContext._nuxt.staticAssets.version), // @ts-ignore - 'process.env.NUXT_FULL_STATIC': options.fullStatic + 'process.env.NUXT_FULL_STATIC': sigmaContext.fullStatic } })) // Dynamic Require Support rollupConfig.plugins.push(dynamicRequire({ - dir: resolve(options.buildDir, 'dist/server'), - inline: options.node === false || options.inlineChunks, + dir: resolve(sigmaContext._nuxt.buildDir, 'dist/server'), + inline: sigmaContext.node === false || sigmaContext.inlineChunks, globbyOptions: { ignore: [ 'server.js' @@ -166,36 +120,39 @@ export const getRollupConfig = (options: SLSOptions) => { const getImportId = p => '_' + hasha(p).substr(0, 6) rollupConfig.plugins.push(virtual({ '~serverMiddleware': ` - ${options.serverMiddleware.filter(m => !m.lazy).map(m => `import ${getImportId(m.handle)} from '${m.handle}';`).join('\n')} + ${sigmaContext.middleware.filter(m => !m.lazy).map(m => `import ${getImportId(m.handle)} from '${m.handle}';`).join('\n')} - ${options.serverMiddleware.filter(m => m.lazy).map(m => `const ${getImportId(m.handle)} = () => import('${m.handle}');`).join('\n')} + ${sigmaContext.middleware.filter(m => m.lazy).map(m => `const ${getImportId(m.handle)} = () => import('${m.handle}');`).join('\n')} export default [ - ${options.serverMiddleware.map(m => `{ route: '${m.route}', handle: ${getImportId(m.handle)}, lazy: ${m.lazy || false} }`).join(',\n')} + ${sigmaContext.middleware.map(m => `{ route: '${m.route}', handle: ${getImportId(m.handle)}, lazy: ${m.lazy || false} }`).join(',\n')} ]; ` })) + // Polyfill + rollupConfig.plugins.push(virtual({ + '~polyfill': env.polyfill.map(p => `require('${p}');`).join('\n') + })) + // https://github.com/rollup/plugins/tree/master/packages/alias - const renderer = options.renderer || 'vue2' + const renderer = sigmaContext.renderer || 'vue2' rollupConfig.plugins.push(alias({ entries: { - '~runtime': options.runtimeDir, - '~mocks': resolve(options.runtimeDir, 'mocks'), - '~renderer': require.resolve(resolve(options.runtimeDir, 'ssr', renderer)), - '~build': options.buildDir, - '~mock': require.resolve(resolve(options.runtimeDir, 'mocks/generic')), - ...aliases + '~runtime': sigmaContext._internal.runtimeDir, + '~renderer': require.resolve(resolve(sigmaContext._internal.runtimeDir, 'app', renderer)), + '~build': sigmaContext._nuxt.buildDir, + ...env.alias } })) // External Plugin - if (options.externals) { + if (sigmaContext.externals) { rollupConfig.plugins.push(externals({ - relativeTo: options.targetDir, + relativeTo: sigmaContext.output.serverDir, include: [ - options.runtimeDir, - ...options.serverMiddleware.map(m => m.handle) + sigmaContext._internal.runtimeDir, + ...sigmaContext.middleware.map(m => m.handle) ] })) } @@ -204,12 +161,12 @@ export const getRollupConfig = (options: SLSOptions) => { rollupConfig.plugins.push(nodeResolve({ extensions, preferBuiltins: true, - rootDir: options.rootDir, + rootDir: sigmaContext._nuxt.rootDir, // https://www.npmjs.com/package/resolve customResolveOptions: { - basedir: options.rootDir, + basedir: sigmaContext._nuxt.rootDir, paths: [ - resolve(options.rootDir, 'node_modukes'), + resolve(sigmaContext._nuxt.rootDir, 'node_modules'), resolve(MODULE_DIR, 'node_modules') ] }, @@ -225,16 +182,16 @@ export const getRollupConfig = (options: SLSOptions) => { rollupConfig.plugins.push(json()) // https://github.com/rollup/plugins/tree/master/packages/inject - rollupConfig.plugins.push(inject(injects)) + rollupConfig.plugins.push(inject(env.inject)) - if (options.analyze) { + if (sigmaContext.analyze) { // https://github.com/doesdev/rollup-plugin-analyzer rollupConfig.plugins.push(analyze()) } // https://github.com/TrySound/rollup-plugin-terser - // https://github.com/terser/terser#minify-options - if (options.minify !== false) { + // https://github.com/terser/terser#minify-sigmaContext + if (sigmaContext.minify) { rollupConfig.plugins.push(terser({ mangle: { keep_fnames: true, diff --git a/packages/nitro/src/rollup/dynamic-require.ts b/packages/nitro/src/rollup/dynamic-require.ts index 8a2f42039c..2bc2bae7e9 100644 --- a/packages/nitro/src/rollup/dynamic-require.ts +++ b/packages/nitro/src/rollup/dynamic-require.ts @@ -1,4 +1,4 @@ -import { resolve } from 'path' +import { resolve } from 'upath' import globby, { GlobbyOptions } from 'globby' import type { Plugin } from 'rollup' diff --git a/packages/nitro/src/rollup/externals.ts b/packages/nitro/src/rollup/externals.ts index f0515d73a9..e05537c9b7 100644 --- a/packages/nitro/src/rollup/externals.ts +++ b/packages/nitro/src/rollup/externals.ts @@ -1,4 +1,4 @@ -import { isAbsolute, relative } from 'path' +import { isAbsolute, relative } from 'upath' export function externals ({ include = [], relativeTo }) { return { diff --git a/packages/nitro/src/rollup/timing.ts b/packages/nitro/src/rollup/timing.ts index 7b1fd3c95a..b5cc67170f 100644 --- a/packages/nitro/src/rollup/timing.ts +++ b/packages/nitro/src/rollup/timing.ts @@ -1,4 +1,4 @@ -import { extname } from 'path' +import { extname } from 'upath' import type { Plugin, RenderedChunk } from 'rollup' export interface Options { } @@ -20,7 +20,7 @@ const end = s => { const d = hrtime(s); return ((d[0] * 1e9) + d[1]) / 1e6; }; const _s = {}; const metrics = []; const logStart = id => { _s[id] = hrtime(); }; -const logEnd = id => { const t = end(_s[id]); delete _s[id]; metrics.push([id, t]); console.log('◈', id, t, 'ms'); }; +const logEnd = id => { const t = end(_s[id]); delete _s[id]; metrics.push([id, t]); console.debug('>', id + ' (' + t + 'ms)'); }; ${TIMING} = { hrtime, start, end, metrics, logStart, logEnd }; `) @@ -30,7 +30,8 @@ export function timing (_opts: Options = {}): Plugin { renderChunk (code, chunk: RenderedChunk) { let name = chunk.fileName || '' name = name.replace(extname(name), '') - return "'use strict';" + (chunk.isEntry ? HELPER : '') + `${TIMING}.logStart('import:${name}');` + code + `;${TIMING}.logEnd('import:${name}');` + const logName = name === 'index' ? 'entry' : name + return "'use strict';" + (chunk.isEntry ? HELPER : '') + `${TIMING}.logStart('${logName}');` + code + `;${TIMING}.logEnd('${logName}');` } } } diff --git a/packages/nitro/src/server.ts b/packages/nitro/src/server.ts new file mode 100644 index 0000000000..bdc8f02398 --- /dev/null +++ b/packages/nitro/src/server.ts @@ -0,0 +1,137 @@ +import { Worker } from 'worker_threads' +import { Server } from 'http' +import { resolve } from 'upath' +import debounce from 'debounce' +import connect from 'connect' +import getPort from 'get-port-please' +import chokidar from 'chokidar' +import serveStatic from 'serve-static' +import { createProxy } from 'http-proxy' +import { stat } from 'fs-extra' +import type { SigmaContext } from './context' + +export function createDevServer (sigmaContext: SigmaContext) { + // Worker + const workerEntry = resolve(sigmaContext.output.dir, sigmaContext.output.serverDir, 'index.js') + let pendingWorker: Worker + let activeWorker: Worker + let workerAddress: string + async function reload () { + if (pendingWorker) { + await pendingWorker.terminate() + workerAddress = null + pendingWorker = null + } + if (!(await stat(workerEntry)).isFile) { + throw new Error('Entry not found: ' + workerEntry) + } + return new Promise((resolve, reject) => { + const worker = pendingWorker = new Worker(workerEntry) + worker.once('exit', (code) => { + if (code) { + reject(new Error('[worker] exited with code: ' + code)) + } + }) + worker.on('error', (err) => { + err.message = '[worker] ' + err.message + reject(err) + }) + worker.on('message', (event) => { + if (event && event.port) { + workerAddress = 'http://localhost:' + event.port + activeWorker = worker + pendingWorker = null + resolve(workerAddress) + } + }) + }) + } + + // App + const app = connect() + + // _nuxt and static + app.use(sigmaContext._nuxt.publicPath, serveStatic(resolve(sigmaContext._nuxt.buildDir, 'dist/client'))) + app.use(sigmaContext._nuxt.routerBase, serveStatic(resolve(sigmaContext._nuxt.staticDir))) + + // Dev Middleware + let loadingMiddleware, devMiddleware + const setLoadingMiddleware = (m) => { loadingMiddleware = m } + const setDevMiddleware = (m) => { devMiddleware = m } + app.use((req, res, next) => { + if (loadingMiddleware && req.url.startsWith('/_loading')) { + req.url = req.url.replace('/_loading', '') + return loadingMiddleware(req, res) + } + if (devMiddleware) { + return devMiddleware(req, res, next) + } + return next() + }) + + // SSR Proxy + const proxy = createProxy() + app.use((req, res) => { + if (workerAddress) { + proxy.web(req, res, { target: workerAddress }) + } else if (loadingMiddleware) { + // TODO:serverIndex method is not exposed + // loadingMiddleware(req, res) + sigmaContext._internal.hooks.callHook('server:nuxt:renderLoading', req, res) + } else { + res.end('Worker not ready!') + } + }) + + // Listen + const listeners: Server[] = [] + async function listen (port) { + port = await getPort({ name: 'nuxt' }) + const listener = await new Promise((resolve, reject) => { + const l = app.listen(port, err => err ? reject(err) : resolve(l)) + }) + listeners.push(listener) + return { + url: 'http://localhost:' + port + } + } + + // Watch for dist and reload worker + const pattern = '**/*.{js,json}' + const events = ['add', 'change'] + let watcher + function watch () { + if (watcher) { return } + const dReload = debounce(() => reload().catch(console.warn), 200, true) + watcher = chokidar.watch([ + resolve(sigmaContext.output.serverDir, pattern), + resolve(sigmaContext._nuxt.buildDir, 'dist/server', pattern) + ]).on('all', event => events.includes(event) && dReload()) + } + + // Close handler + async function close () { + if (watcher) { + await watcher.close() + } + if (activeWorker) { + await activeWorker.terminate() + } + if (pendingWorker) { + await pendingWorker.terminate() + } + await Promise.all(listeners.map(l => new Promise((resolve, reject) => { + l.close(err => err ? reject(err) : resolve()) + }))) + } + sigmaContext._internal.hooks.hook('close', close) + + return { + reload, + listen, + close, + watch, + setLoadingMiddleware, + setDevMiddleware + } +} diff --git a/packages/nitro/src/targets/browser.ts b/packages/nitro/src/targets/browser.ts deleted file mode 100644 index deac5e57f2..0000000000 --- a/packages/nitro/src/targets/browser.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { resolve, relative } from 'path' -import { existsSync, copy } from 'fs-extra' -import consola from 'consola' -import { extendTarget } from '../utils' -import { SLSTarget } from '../config' -import { worker } from './worker' - -export const browser: SLSTarget = extendTarget(worker, (options) => { - const script = ``.replace(/\n| +/g, '') - - return { - entry: '{{ runtimeDir }}/targets/service-worker', - targetDir: '{{ publicDir }}', - outName: '_nuxt.js', - cleanTargetDir: false, - nuxtHooks: { - 'vue-renderer:ssr:templateParams' (params) { - params.APP += script - }, - 'vue-renderer:spa:templateParams' (params) { - params.APP += script - } - }, - hooks: { - 'template:document' (tmpl) { - tmpl.compiled = tmpl.compiled.replace('', script + '') - }, - async done ({ rootDir, publicDir }) { - const fallback200 = resolve(publicDir, '200.html') - const fallback404 = resolve(publicDir, '404.html') - if (!existsSync(fallback404) && existsSync(fallback200)) { - await copy(fallback200, fallback404) - } - consola.info(`Try with \`nuxt start ${relative(process.cwd(), rootDir)}\``) - } - } - } -}) diff --git a/packages/nitro/src/targets/cjs.ts b/packages/nitro/src/targets/cjs.ts deleted file mode 100644 index 4a6a8aaa00..0000000000 --- a/packages/nitro/src/targets/cjs.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { extendTarget } from '../utils' -import { SLSTarget } from '../config' -import { node } from './node' - -export const cjs: SLSTarget = extendTarget(node, { - entry: '{{ runtimeDir }}/targets/cjs', - minify: false, - externals: true, - inlineChunks: true -}) diff --git a/packages/nitro/src/targets/cloudflare.ts b/packages/nitro/src/targets/cloudflare.ts deleted file mode 100644 index c37c9dcf66..0000000000 --- a/packages/nitro/src/targets/cloudflare.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { resolve } from 'path' -import consola from 'consola' -import { extendTarget, writeFile } from '../utils' -import { SLSOptions, SLSTarget } from '../config' -import { worker } from './worker' - -export const cloudflare: SLSTarget = extendTarget(worker, { - entry: '{{ runtimeDir }}/targets/cloudflare', - outName: '_nuxt.js', - generateIgnore: [ - 'wrangler.toml' - ], - hooks: { - async done ({ targetDir }: SLSOptions) { - await writeFile(resolve(targetDir, 'package.json'), JSON.stringify({ private: true, main: './_nuxt.js' }, null, 2)) - await writeFile(resolve(targetDir, 'package-lock.json'), JSON.stringify({ lockfileVersion: 1 }, null, 2)) - - consola.success('Ready to run `wrangler publish`') - } - } -}) diff --git a/packages/nitro/src/targets/lambda.ts b/packages/nitro/src/targets/lambda.ts deleted file mode 100644 index 2c151980ed..0000000000 --- a/packages/nitro/src/targets/lambda.ts +++ /dev/null @@ -1,8 +0,0 @@ - -import { SLSTarget } from '../config' - -export const lambda: SLSTarget = { - entry: '{{ runtimeDir }}/targets/lambda', - outName: '_nuxt.js', - inlineChunks: false -} diff --git a/packages/nitro/src/targets/netlify.ts b/packages/nitro/src/targets/netlify.ts deleted file mode 100644 index 501d527d2b..0000000000 --- a/packages/nitro/src/targets/netlify.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { extendTarget } from '../utils' -import { SLSTarget } from '../config' -import { lambda } from './lambda' - -export const netlify: SLSTarget = extendTarget(lambda, { - outName: '_nuxt.js', - generateIgnore: [ - 'netlify.toml', - '_redirects' - ] -}) diff --git a/packages/nitro/src/targets/node.ts b/packages/nitro/src/targets/node.ts deleted file mode 100644 index c3bd71724d..0000000000 --- a/packages/nitro/src/targets/node.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { SLSTarget } from '../config' - -export const node: SLSTarget = { - entry: '{{ runtimeDir }}/targets/node', - outName: 'index.js', - inlineChunks: false, - minify: true -} diff --git a/packages/nitro/src/targets/vercel.ts b/packages/nitro/src/targets/vercel.ts deleted file mode 100644 index d0f24e81cd..0000000000 --- a/packages/nitro/src/targets/vercel.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { resolve } from 'path' -import { extendTarget, writeFile } from '../utils' -import { SLSTarget } from '../config' -import { node } from './node' - -export const vercel: SLSTarget = extendTarget(node, { - targetDir: '{{ rootDir }}/.vercel_build_output', - outName: 'functions/node/_nuxt/index.js', - publicDir: '{{ targetDir }}/static', - cleanTargetDir: false, - generateIgnore: [ - 'vercel.json' - ], - hooks: { - async done ({ targetDir }) { - await writeRoutes({ targetDir }) - } - } -}) - -async function writeRoutes ({ targetDir }) { - const routes = [ - { - src: '/sw.js', - headers: { - 'cache-control': 'public, max-age=0, must-revalidate' - }, - continue: true - }, - { - src: '/_nuxt/(.*)', - headers: { - 'cache-control': 'public,max-age=31536000,immutable' - }, - continue: true - }, - { - handle: 'filesystem' - }, - { - src: '(.*)', - dest: '/.vercel/functions/_nuxt/index' - } - ] - - await writeFile(resolve(targetDir, 'config/routes.json'), JSON.stringify(routes, null, 2)) -} diff --git a/packages/nitro/src/targets/worker.ts b/packages/nitro/src/targets/worker.ts deleted file mode 100644 index 1c8429dae0..0000000000 --- a/packages/nitro/src/targets/worker.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { SLSTarget } from '../config' - -export const worker: SLSTarget = { - entry: null, // Abstract - node: false, - minify: true, - hooks: { - 'rollup:before' ({ rollupConfig }) { - rollupConfig.output.format = 'iife' - } - } -} diff --git a/packages/nitro/src/utils/index.ts b/packages/nitro/src/utils/index.ts index f4bb220b71..53c468d1d0 100644 --- a/packages/nitro/src/utils/index.ts +++ b/packages/nitro/src/utils/index.ts @@ -1,15 +1,17 @@ -import { relative, dirname, resolve } from 'path' +import { relative, dirname, resolve } from 'upath' import fse from 'fs-extra' import jiti from 'jiti' import defu from 'defu' import Hookable from 'hookable' import consola from 'consola' -import type { SLSOptions, UnresolvedPath, SLSTarget, SLSTargetFn, SLSConfig } from '../config' +import chalk from 'chalk' +import { get } from 'dot-prop' +import type { SigmaPreset, SigmaInput } from '../context' export const MODULE_DIR = resolve(__dirname, '..') export function hl (str: string) { - return '`' + str + '`' + return chalk.cyan(str) } export function prettyPath (p: string, highlight = true) { @@ -18,7 +20,13 @@ export function prettyPath (p: string, highlight = true) { } export function compileTemplate (contents: string) { - return (params: Record) => contents.replace(/{{ ?(\w+) ?}}/g, (_, match) => params[match] || '') + return (params: Record) => contents.replace(/{{ ?([\w.]+) ?}}/g, (_, match) => { + const val = get(params, match) + if (!val) { + consola.warn(`cannot resolve template param '${match}' in ${contents.substr(0, 20)}`) + } + return val as string || `${match}` + }) } export function serializeTemplate (contents: string) { @@ -42,16 +50,16 @@ export async function writeFile (file, contents) { consola.info('Generated', prettyPath(file)) } -export function resolvePath (options: SLSOptions, path: UnresolvedPath, resolveBase: string = '') { +export function resolvePath (sigmaContext: SigmaInput, path: string | ((sigmaContext) => string), resolveBase: string = ''): string { if (typeof path === 'function') { - path = path(options) + path = path(sigmaContext) } if (typeof path !== 'string') { throw new TypeError('Invalid path: ' + path) } - path = compileTemplate(path)(options) + path = compileTemplate(path)(sigmaContext) return resolve(resolveBase, path) } @@ -64,22 +72,19 @@ export function detectTarget () { if (process.env.NOW_BUILDER) { return 'vercel' } - - return 'node' } -export function extendTarget (base: SLSTarget, target: SLSTarget): SLSTargetFn { - return (config: SLSConfig) => { - if (typeof target === 'function') { - target = target(config) +export function extendPreset (base: SigmaPreset, preset: SigmaPreset): SigmaPreset { + return (config: SigmaInput) => { + if (typeof preset === 'function') { + preset = preset(config) } if (typeof base === 'function') { base = base(config) } return defu({ - hooks: Hookable.mergeHooks(base.hooks, target.hooks), - nuxtHooks: Hookable.mergeHooks(base.nuxtHooks as any, target.nuxtHooks as any) - }, target, base) + hooks: Hookable.mergeHooks(base.hooks, preset.hooks) + }, preset, base) } } diff --git a/packages/nitro/src/utils/tree.ts b/packages/nitro/src/utils/tree.ts index 862347a907..15dad502b9 100644 --- a/packages/nitro/src/utils/tree.ts +++ b/packages/nitro/src/utils/tree.ts @@ -1,4 +1,4 @@ -import { resolve, dirname, relative } from 'path' +import { resolve, dirname, relative } from 'upath' import globby from 'globby' import prettyBytes from 'pretty-bytes' import gzipSize from 'gzip-size' @@ -30,5 +30,5 @@ export async function printFSTree (dir) { totalGzip += item.gzip }) - process.stdout.write(`${chalk.cyan('λ Total size:')} ${prettyBytes(totalSize)} (${prettyBytes(totalGzip)} gzip)\n`) + process.stdout.write(`${chalk.cyan('Σ Total size:')} ${prettyBytes(totalSize)} (${prettyBytes(totalGzip)} gzip)\n`) } diff --git a/packages/nitro/src/utils/wpfs.ts b/packages/nitro/src/utils/wpfs.ts new file mode 100644 index 0000000000..8636ac8567 --- /dev/null +++ b/packages/nitro/src/utils/wpfs.ts @@ -0,0 +1,7 @@ +import { join } from 'upath' +import fsExtra from 'fs-extra' + +export default { + ...fsExtra, + join +}