diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts index 160ca9795..85cc6712d 100644 --- a/playground/nuxt.config.ts +++ b/playground/nuxt.config.ts @@ -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'] diff --git a/src/module.ts b/src/module.ts index 2db1bd89f..09d05703a 100644 --- a/src/module.ts +++ b/src/module.ts @@ -7,7 +7,6 @@ import { addAutoImport, addComponentsDir, templateUtils, - useLogger, addTemplate } from '@nuxt/kit' import type { ListenOptions } from 'listhen' @@ -22,6 +21,7 @@ import { name, version } from '../package.json' import { CACHE_VERSION, createWebSocket, + logger, MOUNT_PREFIX, processMarkdownOptions, PROSE_TAGS, @@ -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 } @@ -58,7 +58,7 @@ export interface ModuleOptions { * * @default ['content'] */ - sources: Array + sources: Record | Array /** * List of ignore pattern that will be used for excluding content from parsing and rendering. * @@ -192,7 +192,7 @@ export default defineNuxtModule({ showURL: false } }, - sources: ['content'], + sources: {}, ignores: ['\\.', '-'], locales: [], defaultLocale: undefined, @@ -209,9 +209,6 @@ export default defineNuxtModule({ 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 @@ -235,13 +232,6 @@ export default defineNuxtModule({ } ) } - // 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')) @@ -249,31 +239,37 @@ export default defineNuxtModule({ 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') diff --git a/src/utils.ts b/src/utils.ts index 48d325e8a..19f4aae42 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -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. @@ -65,25 +67,44 @@ export function getMountDriver (mount: MountOptions) { /** * Generate mounts for content storages */ -export function useContentMounts (nuxt: Nuxt, storages: Array) { +export function useContentMounts (nuxt: Nuxt, storages: Array | Record) { 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) + } 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) + return storages } /** * WebSocket server useful for live content reload. diff --git a/test/module.test.ts b/test/module.test.ts index 0753cdc8d..bcbcb22b9 100644 --- a/test/module.test.ts +++ b/test/module.test.ts @@ -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'] @@ -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'] @@ -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', @@ -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', @@ -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' } + }) + }) })