This commit is contained in:
pooya parsa 2020-11-20 01:16:31 +01:00 committed by GitHub
parent 047761f8b7
commit c06f09e9ab
32 changed files with 735 additions and 506 deletions

View File

@ -1,48 +1,97 @@
import { resolve } from 'path' import { resolve, join } from 'upath'
import consola from 'consola' import consola from 'consola'
import { rollup } from 'rollup' import { rollup, watch as rollupWatch } from 'rollup'
import Hookable from 'hookable' import ora from 'ora'
import { readFile, emptyDir } from 'fs-extra' import { readFile, emptyDir, copy } from 'fs-extra'
import { printFSTree } from './utils/tree' import { printFSTree } from './utils/tree'
import { getRollupConfig } from './rollup/config' import { getRollupConfig } from './rollup/config'
import { hl, serializeTemplate, writeFile } from './utils' import { hl, serializeTemplate, writeFile } from './utils'
import { SLSOptions } from './config' import { SigmaContext } from './context'
export async function build (options: SLSOptions) { export async function build (sigmaContext: SigmaContext) {
consola.info(`Generating bundle for ${hl(options.target)}`) consola.info(`Sigma preset is ${hl(sigmaContext.preset)}`)
const hooks = new Hookable() // Cleanup output dir
hooks.addHooks(options.hooks) await emptyDir(sigmaContext.output.dir)
if (options.cleanTargetDir) {
await emptyDir(options.targetDir)
}
// Compile html template // 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: '' } const htmlTemplate = { src: htmlSrc, contents: '', dst: '', compiled: '' }
htmlTemplate.dst = htmlTemplate.src.replace(/.html$/, '.js').replace('app.', 'document.') htmlTemplate.dst = htmlTemplate.src.replace(/.html$/, '.js').replace('app.', 'document.')
htmlTemplate.contents = await readFile(htmlTemplate.src, 'utf-8') htmlTemplate.contents = await readFile(htmlTemplate.src, 'utf-8')
htmlTemplate.compiled = 'module.exports = ' + serializeTemplate(htmlTemplate.contents) 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) 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) => { await sigmaContext._internal.hooks.callHook('sigma:rollup:before', sigmaContext)
error.message = '[serverless] Rollup Error: ' + error.message
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 throw error
}) })
await build.write(options.rollupConfig.output) spinner.start('Wrting Sigma bundle...')
await build.write(sigmaContext.rollupConfig.output)
await printFSTree(options.targetDir) spinner.succeed('Sigma built')
await printFSTree(sigmaContext.output.serverDir)
await hooks.callHook('done', options) await sigmaContext._internal.hooks.callHook('sigma:compiled', sigmaContext)
return { 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)
}
})
}

View File

@ -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<Partial<SLSOptions>, '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
}

View File

@ -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<SigmaContext> {}
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
}

View File

@ -1,85 +1,6 @@
import type { Module } from '@nuxt/types' import nuxt2 from './module/nuxt2'
import { build } from './build'
import { getoptions } from './config'
export default <Module> function slsModule () { export default function () {
const { nuxt } = this const { nuxt } = this
return nuxt2(nuxt)
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)
})
} }

View File

@ -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('&')
}

View File

@ -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 = `<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('${input._nuxt.routerBase}index.js');
});
}
</script>`
return <SigmaInput> {
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('</body>', script + '</body>')
},
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))
}
}
}
})

View File

@ -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]`')
}
}
})

View File

@ -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))
}
}
})

View File

@ -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
})

View File

@ -3,6 +3,8 @@ export * from './cloudflare'
export * from './lambda' export * from './lambda'
export * from './netlify' export * from './netlify'
export * from './node' export * from './node'
export * from './cjs' export * from './dev'
export * from './server'
export * from './cli'
export * from './vercel' export * from './vercel'
export * from './worker' export * from './worker'

View File

@ -0,0 +1,7 @@
import { SigmaPreset } from '../context'
export const lambda: SigmaPreset = {
entry: '{{ _internal.runtimeDir }}/entries/lambda',
inlineChunks: false
}

View File

@ -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'
]
})

View File

@ -0,0 +1,6 @@
import { SigmaPreset } from '../context'
export const node: SigmaPreset = {
entry: '{{ _internal.runtimeDir }}/entries/node',
inlineChunks: false
}

View File

@ -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)))
}
}
})

View File

@ -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))
}

View File

@ -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'
}
}
}

View File

@ -1,5 +1,5 @@
import Module from 'module' import Module from 'module'
import { dirname, join, relative, resolve } from 'path' import { dirname, join, relative, resolve } from 'upath'
import { InputOptions, OutputOptions } from 'rollup' import { InputOptions, OutputOptions } from 'rollup'
import { terser } from 'rollup-plugin-terser' import { terser } from 'rollup-plugin-terser'
import commonjs from '@rollup/plugin-commonjs' import commonjs from '@rollup/plugin-commonjs'
@ -10,110 +10,64 @@ import replace from '@rollup/plugin-replace'
import virtual from '@rollup/plugin-virtual' import virtual from '@rollup/plugin-virtual'
import inject from '@rollup/plugin-inject' import inject from '@rollup/plugin-inject'
import analyze from 'rollup-plugin-analyzer' import analyze from 'rollup-plugin-analyzer'
import * as un from '@nuxt/un'
import hasha from 'hasha' import hasha from 'hasha'
import { SLSOptions } from '../config' import { SigmaContext } from '../context'
import { resolvePath, MODULE_DIR } from '../utils' import { resolvePath, MODULE_DIR } from '../utils'
import { dynamicRequire } from './dynamic-require' import { dynamicRequire } from './dynamic-require'
import { externals } from './externals' import { externals } from './externals'
import { timing } from './timing' import { timing } from './timing'
const mapArrToVal = (val, arr) => arr.reduce((p, c) => ({ ...p, [c]: val }), {})
export type RollupConfig = InputOptions & { output: OutputOptions } export type RollupConfig = InputOptions & { output: OutputOptions }
export const getRollupConfig = (options: SLSOptions) => { export const getRollupConfig = (sigmaContext: SigmaContext) => {
const extensions: string[] = ['.ts', '.mjs', '.js', '.json', '.node'] const extensions: string[] = ['.ts', '.js', '.json', '.node']
const external: InputOptions['external'] = [] const external: InputOptions['external'] = []
const injects:{ [key: string]: string| string[] } = {} const presets = []
const aliases: { [key: string]: string } = {} if (sigmaContext.node === false) {
presets.push(un.nodeless)
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'
})
} else { } else {
presets.push(un.node)
external.push(...Module.builtinModules) external.push(...Module.builtinModules)
} }
const chunksDirName = join(dirname(options.outName), 'chunks') const env = un.env(...presets, {
const serverDir = join(options.buildDir, 'dist/server') 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 = { const rollupConfig: RollupConfig = {
input: resolvePath(options, options.entry), input: resolvePath(sigmaContext, sigmaContext.entry),
output: { output: {
dir: options.targetDir, dir: sigmaContext.output.serverDir,
entryFileNames: options.outName, entryFileNames: 'index.js',
chunkFileNames (chunkInfo) { chunkFileNames (chunkInfo) {
let prefix = '' let prefix = ''
const modules = Object.keys(chunkInfo.modules) const modules = Object.keys(chunkInfo.modules)
const lastModule = modules[modules.length - 1] const lastModule = modules[modules.length - 1]
if (lastModule.startsWith(serverDir)) { if (lastModule.startsWith(buildServerDir)) {
prefix = join('ssr', relative(serverDir, dirname(lastModule))) prefix = join('app', relative(buildServerDir, dirname(lastModule)))
} else if (lastModule.startsWith(options.buildDir)) { } else if (lastModule.startsWith(runtimeAppDir)) {
prefix = 'ssr' prefix = 'app'
} else if (lastModule.startsWith(options.runtimeDir)) { } else if (lastModule.startsWith(sigmaContext._nuxt.buildDir)) {
prefix = 'runtime' prefix = 'nuxt'
} else if (!prefix && options.serverMiddleware.find(m => lastModule.startsWith(m.handle))) { } else if (lastModule.startsWith(sigmaContext._internal.runtimeDir)) {
prefix = 'sigma'
} else if (!prefix && sigmaContext.middleware.find(m => lastModule.startsWith(m.handle))) {
prefix = 'middleware' prefix = 'middleware'
} }
return join(chunksDirName, prefix, '[name].js') return join('chunks', prefix, '[name].js')
}, },
inlineDynamicImports: options.inlineChunks, inlineDynamicImports: sigmaContext.inlineChunks,
format: 'cjs', format: 'cjs',
exports: 'auto', exports: 'auto',
intro: '', intro: '',
@ -124,28 +78,28 @@ export const getRollupConfig = (options: SLSOptions) => {
plugins: [] plugins: []
} }
if (options.timing) { if (sigmaContext.timing) {
rollupConfig.plugins.push(timing()) rollupConfig.plugins.push(timing())
} }
// https://github.com/rollup/plugins/tree/master/packages/replace // https://github.com/rollup/plugins/tree/master/packages/replace
rollupConfig.plugins.push(replace({ rollupConfig.plugins.push(replace({
values: { values: {
'process.env.NODE_ENV': '"production"', 'process.env.NODE_ENV': sigmaContext._nuxt.dev ? '"development"' : '"production"',
'typeof window': '"undefined"', 'typeof window': '"undefined"',
'process.env.ROUTER_BASE': JSON.stringify(options.routerBase), 'process.env.ROUTER_BASE': JSON.stringify(sigmaContext._nuxt.routerBase),
'process.env.PUBLIC_PATH': JSON.stringify(options.publicPath), 'process.env.PUBLIC_PATH': JSON.stringify(sigmaContext._nuxt.publicPath),
'process.env.NUXT_STATIC_BASE': JSON.stringify(options.staticAssets.base), 'process.env.NUXT_STATIC_BASE': JSON.stringify(sigmaContext._nuxt.staticAssets.base),
'process.env.NUXT_STATIC_VERSION': JSON.stringify(options.staticAssets.version), 'process.env.NUXT_STATIC_VERSION': JSON.stringify(sigmaContext._nuxt.staticAssets.version),
// @ts-ignore // @ts-ignore
'process.env.NUXT_FULL_STATIC': options.fullStatic 'process.env.NUXT_FULL_STATIC': sigmaContext.fullStatic
} }
})) }))
// Dynamic Require Support // Dynamic Require Support
rollupConfig.plugins.push(dynamicRequire({ rollupConfig.plugins.push(dynamicRequire({
dir: resolve(options.buildDir, 'dist/server'), dir: resolve(sigmaContext._nuxt.buildDir, 'dist/server'),
inline: options.node === false || options.inlineChunks, inline: sigmaContext.node === false || sigmaContext.inlineChunks,
globbyOptions: { globbyOptions: {
ignore: [ ignore: [
'server.js' 'server.js'
@ -166,36 +120,39 @@ export const getRollupConfig = (options: SLSOptions) => {
const getImportId = p => '_' + hasha(p).substr(0, 6) const getImportId = p => '_' + hasha(p).substr(0, 6)
rollupConfig.plugins.push(virtual({ rollupConfig.plugins.push(virtual({
'~serverMiddleware': ` '~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 [ 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 // https://github.com/rollup/plugins/tree/master/packages/alias
const renderer = options.renderer || 'vue2' const renderer = sigmaContext.renderer || 'vue2'
rollupConfig.plugins.push(alias({ rollupConfig.plugins.push(alias({
entries: { entries: {
'~runtime': options.runtimeDir, '~runtime': sigmaContext._internal.runtimeDir,
'~mocks': resolve(options.runtimeDir, 'mocks'), '~renderer': require.resolve(resolve(sigmaContext._internal.runtimeDir, 'app', renderer)),
'~renderer': require.resolve(resolve(options.runtimeDir, 'ssr', renderer)), '~build': sigmaContext._nuxt.buildDir,
'~build': options.buildDir, ...env.alias
'~mock': require.resolve(resolve(options.runtimeDir, 'mocks/generic')),
...aliases
} }
})) }))
// External Plugin // External Plugin
if (options.externals) { if (sigmaContext.externals) {
rollupConfig.plugins.push(externals({ rollupConfig.plugins.push(externals({
relativeTo: options.targetDir, relativeTo: sigmaContext.output.serverDir,
include: [ include: [
options.runtimeDir, sigmaContext._internal.runtimeDir,
...options.serverMiddleware.map(m => m.handle) ...sigmaContext.middleware.map(m => m.handle)
] ]
})) }))
} }
@ -204,12 +161,12 @@ export const getRollupConfig = (options: SLSOptions) => {
rollupConfig.plugins.push(nodeResolve({ rollupConfig.plugins.push(nodeResolve({
extensions, extensions,
preferBuiltins: true, preferBuiltins: true,
rootDir: options.rootDir, rootDir: sigmaContext._nuxt.rootDir,
// https://www.npmjs.com/package/resolve // https://www.npmjs.com/package/resolve
customResolveOptions: { customResolveOptions: {
basedir: options.rootDir, basedir: sigmaContext._nuxt.rootDir,
paths: [ paths: [
resolve(options.rootDir, 'node_modukes'), resolve(sigmaContext._nuxt.rootDir, 'node_modules'),
resolve(MODULE_DIR, 'node_modules') resolve(MODULE_DIR, 'node_modules')
] ]
}, },
@ -225,16 +182,16 @@ export const getRollupConfig = (options: SLSOptions) => {
rollupConfig.plugins.push(json()) rollupConfig.plugins.push(json())
// https://github.com/rollup/plugins/tree/master/packages/inject // 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 // https://github.com/doesdev/rollup-plugin-analyzer
rollupConfig.plugins.push(analyze()) rollupConfig.plugins.push(analyze())
} }
// https://github.com/TrySound/rollup-plugin-terser // https://github.com/TrySound/rollup-plugin-terser
// https://github.com/terser/terser#minify-options // https://github.com/terser/terser#minify-sigmaContext
if (options.minify !== false) { if (sigmaContext.minify) {
rollupConfig.plugins.push(terser({ rollupConfig.plugins.push(terser({
mangle: { mangle: {
keep_fnames: true, keep_fnames: true,

View File

@ -1,4 +1,4 @@
import { resolve } from 'path' import { resolve } from 'upath'
import globby, { GlobbyOptions } from 'globby' import globby, { GlobbyOptions } from 'globby'
import type { Plugin } from 'rollup' import type { Plugin } from 'rollup'

View File

@ -1,4 +1,4 @@
import { isAbsolute, relative } from 'path' import { isAbsolute, relative } from 'upath'
export function externals ({ include = [], relativeTo }) { export function externals ({ include = [], relativeTo }) {
return { return {

View File

@ -1,4 +1,4 @@
import { extname } from 'path' import { extname } from 'upath'
import type { Plugin, RenderedChunk } from 'rollup' import type { Plugin, RenderedChunk } from 'rollup'
export interface Options { } export interface Options { }
@ -20,7 +20,7 @@ const end = s => { const d = hrtime(s); return ((d[0] * 1e9) + d[1]) / 1e6; };
const _s = {}; const _s = {};
const metrics = []; const metrics = [];
const logStart = id => { _s[id] = hrtime(); }; 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 }; ${TIMING} = { hrtime, start, end, metrics, logStart, logEnd };
`) `)
@ -30,7 +30,8 @@ export function timing (_opts: Options = {}): Plugin {
renderChunk (code, chunk: RenderedChunk) { renderChunk (code, chunk: RenderedChunk) {
let name = chunk.fileName || '' let name = chunk.fileName || ''
name = name.replace(extname(name), '') 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}');`
} }
} }
} }

View File

@ -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<Server>((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
}
}

View File

@ -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 = `<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('${options.routerBase}_nuxt.js');
});
}
</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('</body>', script + '</body>')
},
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)}\``)
}
}
}
})

View File

@ -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
})

View File

@ -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`')
}
}
})

View File

@ -1,8 +0,0 @@
import { SLSTarget } from '../config'
export const lambda: SLSTarget = {
entry: '{{ runtimeDir }}/targets/lambda',
outName: '_nuxt.js',
inlineChunks: false
}

View File

@ -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'
]
})

View File

@ -1,8 +0,0 @@
import { SLSTarget } from '../config'
export const node: SLSTarget = {
entry: '{{ runtimeDir }}/targets/node',
outName: 'index.js',
inlineChunks: false,
minify: true
}

View File

@ -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))
}

View File

@ -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'
}
}
}

View File

@ -1,15 +1,17 @@
import { relative, dirname, resolve } from 'path' import { relative, dirname, resolve } from 'upath'
import fse from 'fs-extra' import fse from 'fs-extra'
import jiti from 'jiti' import jiti from 'jiti'
import defu from 'defu' import defu from 'defu'
import Hookable from 'hookable' import Hookable from 'hookable'
import consola from 'consola' 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 const MODULE_DIR = resolve(__dirname, '..')
export function hl (str: string) { export function hl (str: string) {
return '`' + str + '`' return chalk.cyan(str)
} }
export function prettyPath (p: string, highlight = true) { export function prettyPath (p: string, highlight = true) {
@ -18,7 +20,13 @@ export function prettyPath (p: string, highlight = true) {
} }
export function compileTemplate (contents: string) { export function compileTemplate (contents: string) {
return (params: Record<string, any>) => contents.replace(/{{ ?(\w+) ?}}/g, (_, match) => params[match] || '') return (params: Record<string, any>) => 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) { export function serializeTemplate (contents: string) {
@ -42,16 +50,16 @@ export async function writeFile (file, contents) {
consola.info('Generated', prettyPath(file)) 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') { if (typeof path === 'function') {
path = path(options) path = path(sigmaContext)
} }
if (typeof path !== 'string') { if (typeof path !== 'string') {
throw new TypeError('Invalid path: ' + path) throw new TypeError('Invalid path: ' + path)
} }
path = compileTemplate(path)(options) path = compileTemplate(path)(sigmaContext)
return resolve(resolveBase, path) return resolve(resolveBase, path)
} }
@ -64,22 +72,19 @@ export function detectTarget () {
if (process.env.NOW_BUILDER) { if (process.env.NOW_BUILDER) {
return 'vercel' return 'vercel'
} }
return 'node'
} }
export function extendTarget (base: SLSTarget, target: SLSTarget): SLSTargetFn { export function extendPreset (base: SigmaPreset, preset: SigmaPreset): SigmaPreset {
return (config: SLSConfig) => { return (config: SigmaInput) => {
if (typeof target === 'function') { if (typeof preset === 'function') {
target = target(config) preset = preset(config)
} }
if (typeof base === 'function') { if (typeof base === 'function') {
base = base(config) base = base(config)
} }
return defu({ return defu({
hooks: Hookable.mergeHooks(base.hooks, target.hooks), hooks: Hookable.mergeHooks(base.hooks, preset.hooks)
nuxtHooks: Hookable.mergeHooks(base.nuxtHooks as any, target.nuxtHooks as any) }, preset, base)
}, target, base)
} }
} }

View File

@ -1,4 +1,4 @@
import { resolve, dirname, relative } from 'path' import { resolve, dirname, relative } from 'upath'
import globby from 'globby' import globby from 'globby'
import prettyBytes from 'pretty-bytes' import prettyBytes from 'pretty-bytes'
import gzipSize from 'gzip-size' import gzipSize from 'gzip-size'
@ -30,5 +30,5 @@ export async function printFSTree (dir) {
totalGzip += item.gzip 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`)
} }

View File

@ -0,0 +1,7 @@
import { join } from 'upath'
import fsExtra from 'fs-extra'
export default {
...fsExtra,
join
}