2023-03-02 17:50:20 +00:00
import type { AstroIntegration , AstroConfig } from 'astro' ;
2023-03-03 14:59:41 +00:00
import { InlineConfig , normalizePath } from 'vite' ;
2023-03-01 18:39:29 +00:00
import type { Config } from '@markdoc/markdoc' ;
import Markdoc from '@markdoc/markdoc' ;
2023-03-02 17:50:20 +00:00
import { getAstroConfigPath , MarkdocError , parseFrontmatter } from './utils.js' ;
2023-02-07 21:01:34 +00:00
import { fileURLToPath } from 'node:url' ;
2023-02-10 14:00:53 +00:00
import fs from 'node:fs' ;
2023-02-07 21:01:34 +00:00
2023-03-02 17:50:20 +00:00
export default function markdoc ( markdocConfig : Config = { } ) : AstroIntegration {
2023-02-06 16:13:57 +00:00
return {
name : '@astrojs/markdoc' ,
hooks : {
2023-03-01 18:35:53 +00:00
'astro:config:setup' : async ( { updateConfig , config , addContentEntryType } : any ) = > {
2023-03-02 18:53:30 +00:00
function getEntryInfo ( { fileUrl , contents } : { fileUrl : URL ; contents : string } ) {
const parsed = parseFrontmatter ( contents , fileURLToPath ( fileUrl ) ) ;
return {
data : parsed.data ,
body : parsed.content ,
slug : parsed.data.slug ,
rawData : parsed.matter ,
} ;
}
2023-02-13 19:13:02 +00:00
const contentEntryType = {
2023-02-15 14:43:04 +00:00
extensions : [ '.mdoc' ] ,
2023-03-02 18:53:30 +00:00
getEntryInfo ,
2023-02-13 19:13:02 +00:00
contentModuleTypes : await fs . promises . readFile (
new URL ( '../template/content-module-types.d.ts' , import . meta . url ) ,
'utf-8'
) ,
} ;
2023-02-07 21:01:34 +00:00
addContentEntryType ( contentEntryType ) ;
2023-02-10 14:10:06 +00:00
2023-02-06 16:21:17 +00:00
const viteConfig : InlineConfig = {
plugins : [
{
name : '@astrojs/markdoc' ,
async transform ( code , id ) {
2023-02-15 14:43:04 +00:00
if ( ! id . endsWith ( '.mdoc' ) ) return ;
2023-03-01 18:35:53 +00:00
2023-03-02 17:50:20 +00:00
validateRenderProperties ( markdocConfig , config ) ;
2023-03-02 18:53:30 +00:00
const body =
2023-03-03 15:10:54 +00:00
getEntryInfo ( { fileUrl : new URL ( '/' + normalizePath ( id ) , 'file://' ) , contents : code } ) . body ;
2023-03-01 18:38:53 +00:00
const ast = Markdoc . parse ( body ) ;
const content = Markdoc . transform ( ast , markdocConfig ) ;
2023-03-01 18:35:53 +00:00
2023-03-01 18:38:53 +00:00
return ` import { jsx as h } from 'astro/jsx-runtime'; \ nimport { Renderer } from '@astrojs/markdoc/components'; \ nconst transformedContent = ${ JSON . stringify (
content
) } ; \ nexport async function Content ( { components } ) { return h ( Renderer , { content : transformedContent , components } ) ; } \ nContent [ Symbol . for ( 'astro.needsHeadRendering' ) ] = true ; ` ;
2023-02-06 16:21:17 +00:00
} ,
} ,
] ,
} ;
updateConfig ( { vite : viteConfig } ) ;
2023-02-06 16:13:57 +00:00
} ,
} ,
} ;
}
2023-03-02 17:50:20 +00:00
function validateRenderProperties ( markdocConfig : Config , astroConfig : AstroConfig ) {
const tags = markdocConfig . tags ? ? { } ;
const nodes = markdocConfig . nodes ? ? { } ;
for ( const [ name , config ] of Object . entries ( tags ) ) {
validateRenderProperty ( { type : 'tag' , name , config , astroConfig } ) ;
}
for ( const [ name , config ] of Object . entries ( nodes ) ) {
validateRenderProperty ( { type : 'node' , name , config , astroConfig } ) ;
}
}
function validateRenderProperty ( {
name ,
config ,
type ,
astroConfig ,
} : {
name : string ;
config : { render? : string } ;
type : 'node' | 'tag' ;
astroConfig : Pick < AstroConfig , ' root ' > ;
} ) {
if ( typeof config . render === 'string' && config . render . length === 0 ) {
throw new Error (
` Invalid ${ type } configuration: ${ JSON . stringify (
name
) } . The "render" property cannot be an empty string . `
) ;
}
if ( typeof config . render === 'string' && ! isCapitalized ( config . render ) ) {
const astroConfigPath = getAstroConfigPath ( fs , fileURLToPath ( astroConfig . root ) ) ;
throw new MarkdocError ( {
message : ` Invalid ${ type } configuration: ${ JSON . stringify (
name
2023-03-02 18:53:30 +00:00
) } . The "render" property must reference a capitalized component name . ` ,
hint : 'If you want to render to an HTML element, see our docs on rendering Markdoc manually [TODO docs link].' ,
2023-03-02 17:50:20 +00:00
location : astroConfigPath
? {
file : astroConfigPath ,
}
: undefined ,
} ) ;
}
}
function isCapitalized ( str : string ) {
return str . length > 0 && str [ 0 ] === str [ 0 ] . toUpperCase ( ) ;
}