Skip to content

Commit

Permalink
Refactor loading remark plugins from tsconfig.json (#383)
Browse files Browse the repository at this point in the history
`@mdx-js/language-service` now exposes a function
`resolveRemarkPlugins`, which resolves remark plugins in a environment
agnostic manner.

The language server implements Node.js specific logic to resolve this
from `tsconfig.json`.
  • Loading branch information
remcohaszing committed Dec 27, 2023
1 parent ca6a805 commit 135f633
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 85 deletions.
5 changes: 5 additions & 0 deletions .changeset/afraid-dodos-rhyme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@mdx-js/language-service": patch
---

Expose function `resolveRemarkPlugins`
51 changes: 43 additions & 8 deletions packages/language-server/index.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
#!/usr/bin/env node

/**
* @typedef {import('unified').PluggableList} PluggableList
* @typedef {import('unified').Plugin} Plugin
*/

import assert from 'node:assert'
import path from 'node:path'
import process from 'node:process'
import {
createMdxLanguagePlugin,
createMdxServicePlugin
createMdxServicePlugin,
resolveRemarkPlugins
} from '@mdx-js/language-service'
import {
createConnection,
createNodeServer,
createTypeScriptProjectProvider
} from '@volar/language-server/node.js'
import {loadPlugin} from 'load-plugin'
import remarkFrontmatter from 'remark-frontmatter'
import remarkGfm from 'remark-gfm'
import {create as createMarkdownServicePlugin} from 'volar-service-markdown'
import {create as createTypeScriptServicePlugin} from 'volar-service-typescript'
import {loadPlugins} from './lib/configuration.js'

process.title = 'mdx-language-server'

/** @type {PluggableList} */
const defaultPlugins = [[remarkFrontmatter, ['toml', 'yaml']], remarkGfm]
const connection = createConnection()
const server = createNodeServer(connection)

Expand Down Expand Up @@ -45,14 +57,37 @@ connection.onInitialize((parameters) =>
},

async getLanguagePlugins(serviceEnvironment, projectContext) {
assert(server.modules.typescript, 'TypeScript module is missing')
const ts = server.modules.typescript
assert(ts, 'TypeScript module is missing')

const configFileName = projectContext?.typescript?.configFileName

/** @type {PluggableList | undefined} */
let plugins

const plugins = await loadPlugins(
projectContext?.typescript?.configFileName,
server.modules.typescript
)
if (configFileName) {
const cwd = path.dirname(configFileName)
const configSourceFile = ts.readJsonConfigFile(
configFileName,
ts.sys.readFile
)
const commandLine = ts.parseJsonSourceFileConfigFileContent(
configSourceFile,
ts.sys,
cwd,
undefined,
configFileName
)
plugins = await resolveRemarkPlugins(
commandLine.raw?.mdx,
(name) =>
/** @type {Promise<Plugin>} */ (
loadPlugin(name, {prefix: 'remark', cwd})
)
)
}

return [createMdxLanguagePlugin(plugins)]
return [createMdxLanguagePlugin(plugins || defaultPlugins)]
}
})
)
Expand Down
77 changes: 0 additions & 77 deletions packages/language-server/lib/configuration.js

This file was deleted.

1 change: 1 addition & 0 deletions packages/language-service/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export {createMdxLanguagePlugin} from './lib/language-plugin.js'
export {createMdxServicePlugin} from './lib/service-plugin.js'
export {resolveRemarkPlugins} from './lib/tsconfig.js'
60 changes: 60 additions & 0 deletions packages/language-service/lib/tsconfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* @typedef {import('typescript').ParsedCommandLine} ParsedCommandLine
* @typedef {import('unified').Pluggable} Pluggable
* @typedef {import('unified').PluggableList} PluggableList
* @typedef {import('unified').Plugin} Plugin
*/

/**
* Resolve remark plugins from TypeScript’s parsed command line options.
*
* @param {unknown} mdxConfig
* The parsed command line options from which to resolve plugins.
* @param {(name: string) => Plugin | PromiseLike<Plugin>} resolvePlugin
* A function to resolve a single remark plugin.
* @returns {Promise<PluggableList | undefined>}
* An array of resolved plugins, or `undefined` in case of an invalid configuration.
*/
export async function resolveRemarkPlugins(mdxConfig, resolvePlugin) {
if (
typeof mdxConfig !== 'object' ||
!mdxConfig ||
!('plugins' in mdxConfig)
) {
return
}

const pluginConfig = mdxConfig.plugins

if (typeof pluginConfig !== 'object' || !pluginConfig) {
return
}

const pluginArray = Array.isArray(pluginConfig)
? pluginConfig
: Object.entries(pluginConfig)

if (pluginArray.length === 0) {
return
}

/** @type {Promise<Pluggable>[]} */
const plugins = []
for (const maybeTuple of pluginArray) {
const [name, ...options] = Array.isArray(maybeTuple)
? maybeTuple
: [maybeTuple]

if (typeof name !== 'string') {
continue
}

plugins.push(
Promise.resolve(name)
.then(resolvePlugin)
.then((plugin) => [plugin, ...options])
)
}

return Promise.all(plugins)
}

0 comments on commit 135f633

Please sign in to comment.