Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: experimental bundled storage #160

Merged
merged 6 commits into from
May 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
"ufo": "^0.8.3",
"unenv": "^0.4.6",
"unimport": "^0.1.8",
"unstorage": "^0.3.3",
"unstorage": "^0.4.0",
"util": "^0.12.4"
},
"devDependencies": {
Expand Down
67 changes: 33 additions & 34 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 25 additions & 1 deletion src/build.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { promises as fsp } from 'fs'
import { relative, resolve, join } from 'pathe'
import { relative, resolve, join, dirname } from 'pathe'
import * as rollup from 'rollup'
import fse from 'fs-extra'
import defu from 'defu'
Expand All @@ -12,6 +12,7 @@ import { prettyPath, writeFile, isDirectory } from './utils'
import { GLOB_SCAN_PATTERN, scanHandlers } from './scan'
import type { Nitro } from './types'
import { runtimeDir } from './dirs'
import { snapshotStorage } from './storage'

export async function prepare (nitro: Nitro) {
await prepareDir(nitro.options.output.dir)
Expand Down Expand Up @@ -106,9 +107,32 @@ export async function writeTypes (nitro: Nitro) {
}
}

async function _snapshot (nitro: Nitro) {
if (!nitro.options.bundledStorage.length ||
nitro.options.preset === 'nitro-prerender'
) {
return
}
// TODO: Use virtual storage for server assets
const storageDir = resolve(nitro.options.buildDir, 'snapshot')
nitro.options.serverAssets.push({
baseName: 'nitro:bundled',
dir: storageDir
})

const data = await snapshotStorage(nitro)
await Promise.all(Object.entries(data).map(async ([path, contents]) => {
if (typeof contents !== 'string') { contents = JSON.stringify(contents) }
const fsPath = join(storageDir, path.replace(/:/g, '/'))
await fsp.mkdir(dirname(fsPath), { recursive: true })
await fsp.writeFile(fsPath, contents, 'utf8')
}))
}

async function _build (nitro: Nitro) {
await scanHandlers(nitro)
await writeTypes(nitro)
await _snapshot(nitro)

nitro.logger.start('Building server...')
const build = await rollup.rollup(nitro.options.rollupConfig).catch((error) => {
Expand Down
20 changes: 9 additions & 11 deletions src/nitro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,15 @@ export async function createNitro (config: NitroConfig = {}): Promise<Nitro> {
nitro.options.virtual['#nitro'] = 'export * from "#imports"'
}

// Dev-only storage
if (options.dev) {
const fsMounts = {
root: resolve(options.rootDir),
src: resolve(options.srcDir),
build: resolve(options.buildDir),
cache: resolve(options.buildDir, 'cache')
}
for (const p in fsMounts) {
options.storage[p] = options.storage[p] || { driver: 'fs', base: fsMounts[p] }
}
// Build-only storage
const fsMounts = {
root: resolve(options.rootDir),
src: resolve(options.srcDir),
build: resolve(options.buildDir),
cache: resolve(options.buildDir, 'cache')
}
for (const p in fsMounts) {
options.devStorage[p] = options.devStorage[p] || { driver: 'fs', base: fsMounts[p] }
}

return nitro
Expand Down
3 changes: 3 additions & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const NitroDefaults: NitroConfig = {
// Featueres
experimental: {},
storage: {},
devStorage: {},
bundledStorage: [],
publicAssets: [],
serverAssets: [],
plugins: [],
Expand Down Expand Up @@ -107,6 +109,7 @@ export async function loadOptions (userConfig: NitroConfig = {}): Promise<NitroO
})
const options = klona(config) as NitroOptions
options._config = userConfig
options.preset = preset

options.rootDir = resolve(options.rootDir || '.')
options.srcDir = resolve(options.srcDir || options.rootDir)
Expand Down
4 changes: 1 addition & 3 deletions src/rollup/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,7 @@ export const getRollupConfig = (nitro: Nitro) => {
rollupConfig.plugins.push(publicAssets(nitro))

// Storage
rollupConfig.plugins.push(storage({
mounts: nitro.options.storage
}))
rollupConfig.plugins.push(storage(nitro))

// Handlers
rollupConfig.plugins.push(handlers(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/rollup/plugins/server-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ interface ResolvedAsset {

export function serverAssets (nitro: Nitro): Plugin {
// Development: Use filesystem
if (nitro.options.dev) {
if (nitro.options.dev || nitro.options.preset === 'nitro-prerender') {
return virtual({ '#internal/nitro/virtual/server-assets': getAssetsDev(nitro) })
}

Expand Down
45 changes: 25 additions & 20 deletions src/rollup/plugins/storage.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,45 @@
import virtual from '@rollup/plugin-virtual'
import { serializeImportName } from '../../utils'

export interface StorageMounts {
[path: string]: {
driver: 'fs' | 'http' | 'memory' | 'redis' | 'cloudflare-kv',
[option: string]: any
}
}
import { builtinDrivers } from '../../storage'
import type { Nitro, StorageMounts } from '../../types'

export interface StorageOptions {
mounts: StorageMounts
}

const drivers = {
fs: 'unstorage/drivers/fs',
http: 'unstorage/drivers/http',
memory: 'unstorage/drivers/memory',
redis: 'unstorage/drivers/redis',
'cloudflare-kv': 'unstorage/drivers/cloudflare-kv'
}

export function storage (opts: StorageOptions) {
export function storage (nitro: Nitro) {
const mounts: { path: string, driver: string, opts: object }[] = []

for (const path in opts.mounts) {
const mount = opts.mounts[path]
for (const path in nitro.options.storage) {
const mount = nitro.options.storage[path]
mounts.push({
path,
driver: drivers[mount.driver] || mount.driver,
driver: builtinDrivers[mount.driver] || mount.driver,
opts: mount
})
}

const driverImports = Array.from(new Set(mounts.map(m => m.driver)))

const bundledStorageCode = `
import overlay from 'unstorage/drivers/overlay'
import memory from 'unstorage/drivers/memory'

const bundledStorage = ${JSON.stringify(nitro.options.bundledStorage)}
for (const base of bundledStorage) {
storage.mount(base, overlay({
layers: [
memory(),
// TODO
// prefixStorage(storage, base),
prefixStorage(storage, '/assets/nitro/bundled' + base)
]
}))
}`

return virtual({
'#internal/nitro/virtual/storage': `
import { createStorage } from 'unstorage'
import { createStorage, prefixStorage } from 'unstorage'
import { assets } from '#internal/nitro/virtual/server-assets'

${driverImports.map(i => `import ${serializeImportName(i)} from '${i}'`).join('\n')}
Expand All @@ -48,6 +51,8 @@ export const useStorage = () => storage
storage.mount('/assets', assets)

${mounts.map(m => `storage.mount('${m.path}', ${serializeImportName(m.driver)}(${JSON.stringify(m.opts)}))`).join('\n')}

${nitro.options.bundledStorage.length ? bundledStorageCode : ''}
`
})
}
44 changes: 44 additions & 0 deletions src/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { createStorage as _createStorage } from 'unstorage'
import type { Nitro } from './types'

export const builtinDrivers = {
fs: 'unstorage/drivers/fs',
http: 'unstorage/drivers/http',
memory: 'unstorage/drivers/memory',
redis: 'unstorage/drivers/redis',
'cloudflare-kv': 'unstorage/drivers/cloudflare-kv'
}

export async function createStorage (nitro: Nitro) {
const storage = _createStorage()

const mounts = {
...nitro.options.storage,
...nitro.options.devStorage
}

for (const [path, opts] of Object.entries(mounts)) {
const driver = await import(builtinDrivers[opts.driver] || opts.driver)
.then(r => r.default || r)
storage.mount(path, driver(opts))
}

return storage
}

export async function snapshotStorage (nitro: Nitro) {
const storage = await createStorage(nitro)
const data: Record<string, any> = {}

const allKeys = Array.from(new Set(await Promise.all(
nitro.options.bundledStorage.map(base => storage.getKeys(base))
).then(r => r.flat())))

await Promise.all(allKeys.map(async (key) => {
data[key] = await storage.getItem(key)
}))

await storage.dispose()

return data
}
Loading