Skip to content

Commit

Permalink
feat(source): allow overwriting default source (#1273)
Browse files Browse the repository at this point in the history
* feat(source): allow overwriting default source

* feat: sources object syntax

* chore: update nuxt config

* test: add test

* docs: fix build

* Apply suggestions from code review

Co-authored-by: Sébastien Chopin <seb@nuxtjs.com>

* fix: respect source name

* chore: remove log

Co-authored-by: Sébastien Chopin <seb@nuxtjs.com>
  • Loading branch information
farnabaz and Atinux committed Sep 7, 2022
1 parent 15a0b29 commit ed69346
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 59 deletions.
7 changes: 3 additions & 4 deletions playground/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,13 @@ export default defineNuxtConfig({
navigation: {
fields: ['icon']
},
sources: [
{
name: 'translation-fa',
sources: {
'translation-fa': {
prefix: '/fa',
driver: 'fs',
base: resolve(__dirname, 'content-fa')
}
],
},
highlight: {
theme: 'one-dark-pro',
preload: ['json', 'js', 'ts', 'html', 'css', 'vue']
Expand Down
66 changes: 31 additions & 35 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
addAutoImport,
addComponentsDir,
templateUtils,
useLogger,
addTemplate
} from '@nuxt/kit'
import type { ListenOptions } from 'listhen'
Expand All @@ -22,6 +21,7 @@ import { name, version } from '../package.json'
import {
CACHE_VERSION,
createWebSocket,
logger,
MOUNT_PREFIX,
processMarkdownOptions,
PROSE_TAGS,
Expand All @@ -30,9 +30,9 @@ import {
import type { MarkdownPlugin } from './runtime/types'

export type MountOptions = {
name: string
prefix?: string
driver: 'fs' | 'http' | string
name?: string
prefix?: string
[options: string]: any
}

Expand All @@ -58,7 +58,7 @@ export interface ModuleOptions {
*
* @default ['content']
*/
sources: Array<string | MountOptions>
sources: Record<string, MountOptions> | Array<string | MountOptions>
/**
* List of ignore pattern that will be used for excluding content from parsing and rendering.
*
Expand Down Expand Up @@ -192,7 +192,7 @@ export default defineNuxtModule<ModuleOptions>({
showURL: false
}
},
sources: ['content'],
sources: {},
ignores: ['\\.', '-'],
locales: [],
defaultLocale: undefined,
Expand All @@ -209,9 +209,6 @@ export default defineNuxtModule<ModuleOptions>({
async setup (options, nuxt) {
const { resolve } = createResolver(import.meta.url)
const resolveRuntimeModule = (path: string) => resolveModule(path, { paths: resolve('./runtime') })

const logger = useLogger('@nuxt/content')

const contentContext: ContentContext = {
transformers: [
// Register internal content plugins
Expand All @@ -235,45 +232,44 @@ export default defineNuxtModule<ModuleOptions>({
}
)
}
// Tell Nuxt to ignore content dir for app build
options.sources.forEach((source) => {
if (typeof source === 'string') {
nuxt.options.ignore.push(join('content', '**'))
}
// TODO: handle object format and make sure to ignore urls
})

// Add Content plugin
addPlugin(resolveRuntimeModule('./plugin'))

nuxt.hook('nitro:config', (nitroConfig) => {
// Add server handlers
nitroConfig.handlers = nitroConfig.handlers || []
nitroConfig.handlers.push({
method: 'get',
route: `/api/${options.base}/query/:qid`,
handler: resolveRuntimeModule('./server/api/query')
})
nitroConfig.handlers.push({
method: 'get',
route: `/api/${options.base}/query`,
handler: resolveRuntimeModule('./server/api/query')
})
nitroConfig.handlers.push({
method: 'get',
route: `/api/${options.base}/cache`,
handler: resolveRuntimeModule('./server/api/cache')
})
nitroConfig.handlers.push(
{
method: 'get',
route: `/api/${options.base}/query/:qid`,
handler: resolveRuntimeModule('./server/api/query')
}, {
method: 'get',
route: `/api/${options.base}/query`,
handler: resolveRuntimeModule('./server/api/query')
}, {
method: 'get',
route: `/api/${options.base}/cache`,
handler: resolveRuntimeModule('./server/api/cache')
}
)

if (!nuxt.options.dev) {
nitroConfig.prerender.routes.push('/api/_content/cache')
}

// Register source storages
const sources = useContentMounts(nuxt, contentContext.sources || [])
nitroConfig.devStorage = Object.assign(
nitroConfig.devStorage || {},
sources
)
const sources = useContentMounts(nuxt, contentContext.sources)
nitroConfig.devStorage = Object.assign(nitroConfig.devStorage || {}, sources)

// Tell Nuxt to ignore content dir for app build
for (const source of Object.values(sources)) {
if (source.driver === 'fs') {
nuxt.options.ignore.push(join(source.base, '**'))
}
}

nitroConfig.bundledStorage = nitroConfig.bundledStorage || []
nitroConfig.bundledStorage.push('/cache/content')

Expand Down
47 changes: 34 additions & 13 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import type { Nuxt } from '@nuxt/schema'
import fsDriver from 'unstorage/drivers/fs'
import httpDriver from 'unstorage/drivers/http'
import { WebSocketServer } from 'ws'
import { useLogger } from '@nuxt/kit'
import type { ModuleOptions, MountOptions } from './module'
import type { MarkdownPlugin } from './runtime/types'

export const logger = useLogger('@nuxt/content')
/**
* Internal version that represents cache format.
* This is used to invalidate cache when the format changes.
Expand Down Expand Up @@ -65,25 +67,44 @@ export function getMountDriver (mount: MountOptions) {
/**
* Generate mounts for content storages
*/
export function useContentMounts (nuxt: Nuxt, storages: Array<string | MountOptions>) {
export function useContentMounts (nuxt: Nuxt, storages: Array<string | MountOptions> | Record<string, MountOptions>) {
const key = (path: string, prefix: string = '') => `${MOUNT_PREFIX}${path.replace(/[/:]/g, '_')}${prefix.replace(/\//g, ':')}`

return storages.reduce((mounts, storage) => {
if (typeof storage === 'string') {
mounts[key(storage)] = {
name: storage,
driver: 'fs',
prefix: '',
base: resolve(nuxt.options.srcDir, storage)
if (Array.isArray(storages)) {
logger.warn('Using array syntax to define sources is deprecated. Consider using object syntax.')
storages = storages.reduce((mounts, storage) => {
if (typeof storage === 'string') {
mounts[key(storage)] = {
name: storage,
driver: 'fs',
prefix: '',
base: resolve(nuxt.options.srcDir, storage)
}
}

if (typeof storage === 'object') {
mounts[key(storage.name, storage.prefix)] = storage
}
}

if (typeof storage === 'object') {
mounts[key(storage.name, storage.prefix)] = storage
return mounts
}, {} as Record<string, MountOptions>)
} else {
storages = Object.entries(storages).reduce((mounts, [name, storage]) => {
mounts[key(storage.name || name, storage.prefix)] = storage
return mounts
}, {})
}

const defaultStorage = key('content')
if (!storages[defaultStorage]) {
storages[defaultStorage] = {
name: defaultStorage,
driver: 'fs',
base: resolve(nuxt.options.srcDir, 'content')
}
}

return mounts
}, {} as Record<string, MountOptions>)
return storages
}
/**
* WebSocket server useful for live content reload.
Expand Down
53 changes: 46 additions & 7 deletions test/module.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { useContentMounts } from '../src/utils'

const nuxtDummy = { options: { rootDir: '/test', srcDir: '/test' } } as Nuxt

describe('Content sources', () => {
test('Relative path', () => {
describe('module', () => {
test('[sources] [array] Relative path', () => {
const mounts = useContentMounts(nuxtDummy, ['content'])
const mount = mounts['content:source:content']

Expand All @@ -14,7 +14,7 @@ describe('Content sources', () => {
assert(mount.base === '/test/content')
})

test('Absolute path', () => {
test('[sources] [array] Absolute path', () => {
const mounts = useContentMounts(nuxtDummy, ['/content'])
const mount = mounts['content:source:_content']

Expand All @@ -23,7 +23,7 @@ describe('Content sources', () => {
assert(mount.base === '/content')
})

test('Custom driver', () => {
test('[sources] [array] Custom driver', () => {
const mounts = useContentMounts(nuxtDummy, [
{
name: 'repo1',
Expand All @@ -32,14 +32,12 @@ describe('Content sources', () => {
}
])

assert(Object.keys(mounts).length === 1)

expect(mounts).toMatchObject({
'content:source:repo1': { driver: 'http', base: 'https://cdn.com' }
})
})

test('Multiple storages', () => {
test('[sources] [array] Multiple storages', () => {
const mounts = useContentMounts(nuxtDummy, [
'content',
'/repo1/docs',
Expand All @@ -60,4 +58,45 @@ describe('Content sources', () => {
'content:source:repo1': { driver: 'http', base: 'https://cdn.com' }
})
})
test('[sources] [array] overwrite default source', () => {
const mounts = useContentMounts(nuxtDummy, [
{
name: 'content',
driver: 'http',
base: 'https://cdn.com'
}
])

assert(Object.keys(mounts).length === 1)

expect(mounts).toMatchObject({
'content:source:content': { driver: 'http', base: 'https://cdn.com' }
})
})

test('[sources] [object] overwrite default source', () => {
const mounts = useContentMounts(nuxtDummy, {
content: {
driver: 'http',
base: 'https://cdn.com'
}
})

expect(mounts).toMatchObject({
'content:source:content': { driver: 'http', base: 'https://cdn.com' }
})
})

test('[sources] [object] secondary source', () => {
const mounts = useContentMounts(nuxtDummy, {
secondary: {
driver: 'http',
base: 'https://cdn.com'
}
})

expect(mounts).toMatchObject({
'content:source:secondary': { driver: 'http', base: 'https://cdn.com' }
})
})
})

0 comments on commit ed69346

Please sign in to comment.