Skip to content

Commit

Permalink
feat: call hooks before and after parse (#1160)
Browse files Browse the repository at this point in the history
Co-authored-by: Sébastien Chopin <seb@nuxtjs.com>
  • Loading branch information
farnabaz and Atinux committed May 31, 2022
1 parent d78056d commit 71d5640
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 6 deletions.
79 changes: 79 additions & 0 deletions docs/content/4.api/4.advanced.md
@@ -0,0 +1,79 @@
---
title: 'Advanced'
description: 'Nuxt Content is highly customizable, giving you freedom and control over how the data is transformed.'
icon: heroicons-outline:lightning-bolt
---

## Hooks

The module adds some hooks you can use:

`content:file:*` hooks are available in nitro runtime, in order to use them you need to create custom [nitro plugin](https://nitro.unjs.io/guide/plugins.html).

- Create a plugin in the `server/plugins/` directory

```ts [server/plugins/content.ts]
export default defineNitroPlugin((nitroApp) => {
// ...
})
```

- Register the plugin in `nuxt.config.ts`

```ts [nuxt.config.ts]
export default defineNuxtConfig({
// ...
nitro: {
plugins: ['~/server/plugins/content.ts']
}
})
```

### `content:file:beforeParse`

Allows you to modify the contents of a file before it is handled by the parsers.

Arguments:
- file
- Type: `Object`
- Properties:
- _id: `String`
- body: `String`

### Example

Changing all occurrences of react to vue in all Markdown files:


```ts [server/plugins/content.ts]
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('content:file:beforeParse', (file) => {
if (file._id.endsWith('.md')) {
file.body = file.body.replace(/react/g, 'vue')
}
})
})
```

### `content:file:afterParse`

Allows you to modify a document after being parsed by parsers.

### Example

Using content's first picture as cover image.

```ts [server/plugins/content.ts]
import { visit } from 'unist-util-visit'

export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('content:file:afterParse', (file) => {
if (file._id.endsWith('.md')) {
visit(file.body, (n:any) => n.tag === 'img', (node) => {
file.coverImage = node.props.src
})
}
})
})

```
19 changes: 13 additions & 6 deletions src/runtime/server/transformers/index.ts
@@ -1,23 +1,27 @@
import { extname } from 'pathe'
import type { ParsedContent, ContentTransformer } from '../../types'
import { getParser, getTransformers } from '#content/virtual/transformers'

// eslint-disable-next-line import/named
import { useNitroApp } from '#imports'
/**
* Parse content file using registered plugins
*/
export async function parseContent (id: string, content: string) {
const nitroApp = useNitroApp()

// Call hook before parsing the file
const file = { _id: id, body: content }
await nitroApp.hooks.callHook('content:file:beforeParse', file)

const ext = extname(id)
const plugin: ContentTransformer = getParser(ext)
if (!plugin) {
// eslint-disable-next-line no-console
console.warn(`${ext} files are not supported, "${id}" falling back to raw content`)
return {
_id: id,
body: content
}
return file
}

const parsed: ParsedContent = await plugin.parse!(id, content)
const parsed: ParsedContent = await plugin.parse!(file._id, file.body)

const transformers = getTransformers(ext)
const result = await transformers.reduce(async (prev, cur) => {
Expand All @@ -26,5 +30,8 @@ export async function parseContent (id: string, content: string) {
return cur.transform!(next)
}, Promise.resolve(parsed))

// Call hook after parsing the file
await nitroApp.hooks.callHook('content:file:afterParse', result)

return result
}
3 changes: 3 additions & 0 deletions test/basic.test.ts
Expand Up @@ -11,6 +11,7 @@ import { testJSONParser } from './features/parser-json'
import { testCSVParser } from './features/parser-csv'
import { testRegex } from './features/regex'
import { testMarkdownParserExcerpt } from './features/parser-markdown-excerpt'
import { testParserHooks } from './features/parser-hooks'

describe('fixtures:basic', async () => {
await setup({
Expand Down Expand Up @@ -114,4 +115,6 @@ describe('fixtures:basic', async () => {
// testMDCComponent()

testRegex()

testParserHooks()
})
37 changes: 37 additions & 0 deletions test/features/parser-hooks.ts
@@ -0,0 +1,37 @@
import { describe, test, expect, assert } from 'vitest'
import { $fetch } from '@nuxt/test-utils'

export const testParserHooks = () => {
describe('parser:hooks', () => {
test('beforeParse', async () => {
const parsed = await $fetch('/api/parse', {
method: 'POST',
body: {
id: 'content:index.md',
content: '# hello'
}
})

expect(parsed).toHaveProperty('_id')
assert(parsed._id === 'content:index.md')

expect(parsed).toHaveProperty('__beforeParse', true)
})

test('afterParse', async () => {
const parsed = await $fetch('/api/parse', {
method: 'POST',
body: {
id: 'content:index.md',
content: '# hello'
}
})

expect(parsed).toHaveProperty('_id')
assert(parsed._id === 'content:index.md')

expect(parsed).haveOwnProperty('body')
expect(parsed).toHaveProperty('__afterParse', true)
})
})
}
17 changes: 17 additions & 0 deletions test/fixtures/basic/addons/nitro-plugin.ts
@@ -0,0 +1,17 @@
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('content:file:beforeParse', (file) => {
if (file._id.endsWith('.md')) {
if (file.body.startsWith('---')) {
const lines = file.body.split('\n')
lines.splice(1, 0, '__beforeParse: true')
file.body = lines.join('\n')
} else {
file.body = '---\n__beforeParse: true\n---\n' + file.body
}
}
})

nitroApp.hooks.hook('content:file:afterParse', (file) => {
file.__afterParse = true
})
})
5 changes: 5 additions & 0 deletions test/fixtures/basic/nuxt.config.ts
Expand Up @@ -3,6 +3,11 @@ import { resolve } from 'pathe'
import contentModule from '../../..'

export default defineNuxtConfig({
nitro: {
plugins: [
'~/addons/nitro-plugin.ts'
]
},
components: {
dirs: [
{
Expand Down

0 comments on commit 71d5640

Please sign in to comment.