monaco使用的踩坑记录——配置prettier格式

1,082 阅读4分钟

最近想实现一个在线编写代码的平台,可以在线编写react代码和vue代码并编译预览结果,搜了一下编辑器,常用的有ace,monaco,其中vsocde使用的就是monaco,所以最终就想着使用monaco去作为在线的编辑器,并且在配置格式化代码的时候也遇到了挺多的问题,这里就来分享一下monaco在react+vite+electron项目中的使用的踩坑记录以及prettier的一些配置

monaco简介

monaco是一个基于Web技术的代码编辑器,由微软开发并开源。它提供了丰富的代码编辑功能,包括语法高亮、自动补全、代码折叠、多光标编辑等。monaco可以作为独立的编辑器使用,也可以集成到其他Web应用程序中,为用户提供优秀的代码编辑体验。它支持多种编程语言,包括JavaScript、TypeScript、HTML、CSS等,同时还可以通过插件扩展支持更多的语言和功能。monaco的轻量化和高性能使其成为许多开发者和企业的首选代码编辑工具之一。

这里不得不吐槽一下monaco官方文档,是真的不好读,API找不到不说,找到之后看着也很难受,不好理解,网上的博客虽然也有不少,但是并没有附带prettier格式化的内容,以及react配置代码提示的一个现成代码,以及接入prettier的完整案例。 总的来说还是因为monaco官方文档太难读,很多API怎么用都是看ts声明文件来猜着使用的

monaco在react项目中配置及简单使用

  1. 安装
yarn add monaco-editor
yarn add react-monaco-editor
  1. 配置

安装好第三方依赖之后还需要额外配置对应的语言处理loader,在webpack和vite中配置各有不同,网上也挺多对应的配置,但是vite项目的话挺少的 配置:在src文件夹中新建userWork.ts(文件名自定义),

import * as monaco from 'monaco-editor'
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'

import prettier from 'prettier/standalone'
import parserTypeScript from 'prettier/plugins/typescript'
import parseBabel from 'prettier/plugins/babel'
import parseHtml from 'prettier/plugins/html'
import parseEsTree from 'prettier/plugins/estree'
import parseMd from 'prettier/plugins/markdown'
import parseCss from 'prettier/plugins/postcss'
// 编辑器的language对应上之后会有对应的work,会有对应的代码提示
self.MonacoEnvironment = {
  // 提供一个定义worker路径的全局变量
  getWorker(_: any, label: string) {
    if (label === 'json') {
      return new jsonWorker()
    }
    if (label === 'css' || label === 'scss' || label === 'less') {
      return new cssWorker()
    }
    if (label === 'html' || label === 'handlebars' || label === 'razor') {
      return new htmlWorker()
    }
    if (label === 'typescript' || label === 'javascript') {
      return new tsWorker()
    }
    return new editorWorker() // 基础功能文件, 提供了所有语言通用功能 无论使用什么语言,monaco都会去加载他。
  }
}

// 关闭报错
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
  noSemanticValidation: true,
  noSyntaxValidation: true
})
// 注册对应的格式化plugin(要先安装prettier)
// monaco.languages.registerDocumentFormattingEditProvider('typescript', {
//   provideDocumentFormattingEdits(model) {
//     let code = model.getValue()
//     prettier
//       .format(code, {
//         parser: 'vue',// 格式化vue
//         jsxSingleQuote: true,
//         plugins: [parseBabel, parseEsTree, parseCss, parserTypeScript, parseMd, parseHtml],
//         vueIndentScriptAndStyle: true
//       })
//       .then((res) => {
//         model.setValue(res)
//       })
//     return []
//   }
// })

// 这里需要注意一下同样的language会覆盖掉对应的格式化代码的规则
// monaco.languages.registerDocumentFormattingEditProvider('typescript', {
//   provideDocumentFormattingEdits(model) {
//     let code = model.getValue()
//     prettier
//       .format(code, {
//         parser: 'typescript',// 格式化react
//         jsxSingleQuote: true,
//         plugins: [parseBabel, parseEsTree, parseCss, parserTypeScript, parseMd, parseHtml],
//         vueIndentScriptAndStyle: true
//       })
//       .then((res) => {
//         model.setValue(res)
//       })
//     return []
//   }
// })

monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true)

// 自定义代码提示
// var jsCode = ['"use strict"', '', 'Rectangle1.top()'].join('\n')
// monaco.editor.create(document.getElementById('a') as any, {
//   language: 'typescript',
//   value: ''
// })
// const transSug = (items) => {
//   const newSug = [...items, 'and', 'or', '(', ')'].map((item) => {
//     return {
//       label: item, // 显示的label
//       detail: !items.includes(item) ? '符号' : '字段', // 描述
//       insertText: item, // 选择后插入的value
//       icon: items.includes(item)
//     }
//   })
//   return newSug
// }
// monaco.languages.registerCompletionItemProvider('typescript', {
//   // @ts-ignore
//   provideCompletionItems: () => {
//     const suggestions = transSug(['代码提示'])
//     return {
//       suggestions: suggestions.map((item) => ({
//         ...item,
//         kind: item.icon
//           ? monaco.languages.CompletionItemKind.Variable // 图标
//           : null
//       }))
//     }
//   },
//   triggerCharacters: [] // 触发代码提示的关键字,ps:可以有多个
// })

配置对应的types文件可以快速的生成对应的代码提示以react为例

// 这个是我根据常用的react方法写的对应的类型声明文件,只是简单的写了一下,没有写那么多
const str=`
interface React {
  createElement: (
    type: string | Function,
    props?: object,
    ...children: any[]
  ) => any;
  Fragment: (props: object) => any;
  useState: <T>(initialState: T | (() => T)) => [T, (newValue: T) => void];
  useEffect: (effect: () => void, deps?: any[]) => void;
  useContext: <T>(context: React.Context<T>) => T;
  useRef: <T>(initialValue: T) => { current: T };
  useMemo: <T>(factory: () => T, deps?: any[]) => T;
  useCallback: <T>(callback: T, deps?: any[]) => T;
  useReducer: <S, A>(
    reducer: (state: S, action: A) => S,
    initialState: S,
  ) => [S, (action: A) => void];
  createContext: <T>(defaultValue: T) => React.Context<T>;
  useContext: <T>(context: React.Context<T>) => T;
  useState: <T>(initialState: T | (() => T)) => [T, (newValue: T) => void];
  useLayoutEffect: (effect: () => void, deps?: any[]) => void;
  useImperativeHandle: <T>(
    ref: React.Ref<T>,
    createHandle: () => T,
    deps?: any[],
  ) => void;
  useDebugValue: (value: any) => void;
  forwardRef: <T, P>(
    component: (props: P, ref: React.Ref<T>) => React.ReactNode,
  ) => (props: P) => React.ReactNode;
  memo: <T>(
    component: T,
    areEqual?: (prevProps: any, nextProps: any) => boolean,
  ) => T;
  lazy: <T>(factory: () => Promise<{ default: T }>) => T;
  Suspense: (props: { fallback: React.ReactNode }) => React.ReactNode;
  useState: <T>(initialState: T | (() => T)) => [T, (newValue: T) => void];
  useReducer: <S, A>(
    reducer: (state: S, action: A) => S,
    initialState: S,
  ) => [S, (action: A) => void];
  useMutationEffect: (effect: () => void, deps?: any[]) => void;
  useTransition: () => [boolean, (startPending: () => void) => void];
  useDeferredValue: <T>(value: T, config?: { timeoutMs: number }) => T;
  useTransition: () => [boolean, (startPending: () => void) => void];
  useTransition: () => [boolean, (startPending: () => void) => void];
  useDeferredValue: <T>(value: T, config?: { timeoutMs: number }) => T;
  useOpaqueIdentifier: () => any;
  useOpaqueIdentifier: () => any;
  useOpaqueIdentifier: () => any;
  useOpaqueIdentifier: () => any;
  useOpaqueIdentifier: () => any;
  useOpaqueIdentifier: () => any;
  useOpaqueIdentifier: () => any;
  useOpaqueIdentifier: () => any;
}

// react.d.ts

declare module JSX {
  interface Element {}
  interface IntrinsicElements {
    div: React.DetailedHTMLProps<
      React.HTMLAttributes<HTMLDivElement>,
      HTMLDivElement
    >;
    span: React.DetailedHTMLProps<
      React.HTMLAttributes<HTMLSpanElement>,
      HTMLSpanElement
    >;
    // ... other HTML elements
  }
}

declare module 'react' {
  export = react;
}

declare var react: React;
`
monaco.languages.typescript.typescriptDefaults.addExtraLib(str)
  1. 使用
import MonacoEditor from 'react-monaco-editor'
export default function MarkdownCodeEditor() {
  const handleCssCodeChange = (e: string) => {
    console.log(e)
  }
  return (
    <div>
      <div className="coderBox-body" style={{ display: 'flex' }}>
        <div className="coderBox-code" style={{ width: '50%' }}>
          <MonacoEditor
            height={400}
            onChange={handleCssCodeChange}
            language={'typescript'}
            value={''}
          />
        </div>
      </div>
    </div>
  )
}