# 前言

  • 由于项目需求,需要完成一个支持代码提示、代码校验、代码格式化的lua编辑器

1 技术选型

精力有限,只了解了几个主流的编辑器codemirror、ace、Monaco Editor

codemirror5

用户最多,生态最好,所以插件也相对完备,能想到的基本都有,同时也被很多线上应用在用,有什么问题百度搜下基本都能搜到。

ace

ajax团队弄的一个开源编辑器,生态、文档也还不错,相对codemirror来说,语法提示和语法校验支持的语言种类更多

Monaco Editor

微软开源的一个web代码编辑器,可以看作vscode的web版,各项功能都比较强,理论上来说vscode支持的插件Monaco Editor也支持(因为vscode的代码编辑器也是用这个实现的),但是相关文档比较少

基于vue封装的组件

对比

vue实现在线编辑器python vue 在线代码编辑器_vue.js


vue实现在线编辑器python vue 在线代码编辑器_vue实现在线编辑器python_02

各个编辑器对JavaScript、SQL、Java这些热门语言的支持度都很高,其他相对冷门语言的支持度都差一点,可以看看以下文章来帮助大家选型(以上对比图片来源于以下文章):

2 技术验证

codemirror.js(vue-codemirror)

由于之前有了解过codemirror,所以第一个方案自然就尝试使用codemirror来实现lua编辑器

注意:

  • codemirror已发布v6版本,本文使用的是v5版本
  • vue-codemirror已兼容codemirror@6版本,只有@4版本才支持codemirror@5

安装

推荐使用vue-codemirror

npm i codemirror@5 -S
npm i vue-codemirror@4.0.6 -S

推荐安装@types/codemirror以支持类型推断

npm i @types/codemirror -D

基础使用

注册全局组件

// require lib
import Vue from 'vue'
import VueCodemirror from 'vue-codemirror'

// require styles
import 'codemirror/lib/codemirror.css'

// require more codemirror resource...

// you can set default global options and events when use
Vue.use(VueCodemirror, /* { 
  options: { theme: 'base16-dark', ... },
  events: ['scroll', ...]
} */)

注册局部组件

// require component
import { codemirror } from 'vue-codemirror'

// require styles
import 'codemirror/lib/codemirror.css'

// require more codemirror resource...

// component
export default {
  components: {
    codemirror
  }
}

使用组件

<template>
  <codemirror v-model="code" :options="cmOptions"></codemirror>
</template>

<script>
// 引入语言
import 'codemirror/mode/javascript/javascript.js'
// 引入样式
import 'codemirror/theme/base16-dark.css'
// 按需导入codemirror其他功能
import 'codemirror/xxx'

export default {
  data () {
    return {
      code: 'const a = 10',
      cmOptions: {
        // codemirror options
        tabSize: 4,
        mode: 'text/javascript',
        theme: 'base16-dark',
        lineNumbers: true,
        line: true,
        // more codemirror options, 更多 codemirror 的高级配置...
      }
    }
  },
  methods: {
  },
  computed: {
    codemirror() {
      return this.$refs.myCm.codemirror
    }
  },
  mounted() {
  }
}
</script>

实现lua代码编辑器

使用lua模式时,mode需要设置为text/x-lua
具体功能需要引入哪些模块已标好注释

<template>
  <codemirror
    class="lua-editor"
    ref="editor"
    :value="value"
    :options="codemirrorOptions"
    @input="handleInputChange"
    @input-read="handleInputRead"
  />
</template>

<script>
import { codemirror } from 'vue-codemirror'
import 'codemirror/lib/codemirror.css'
import 'codemirror/mode/lua/lua'

import 'codemirror/theme/neat.css'
import 'codemirror/theme/idea.css'

// #region 搜索功能
// find:Ctrl-F (PC), Cmd-F (Mac)
// findNext:Ctrl-G (PC), Cmd-G (Mac)
// findPrev:Shift-Ctrl-G (PC), Shift-Cmd-G (Mac)
// replace:Shift-Ctrl-F (PC), Cmd-Alt-F (Mac)
// replaceAll:Shift-Ctrl-R (PC), Shift-Cmd-Alt-F (Mac)
import 'codemirror/addon/dialog/dialog.css'
import 'codemirror/addon/dialog/dialog'
import 'codemirror/addon/search/searchcursor'
import 'codemirror/addon/search/search'
import 'codemirror/addon/search/jump-to-line'
import 'codemirror/addon/search/matchesonscrollbar'
import 'codemirror/addon/search/match-highlighter'
// #endregion

// #region 代码提示功能
// 具体语言可以从 codemirror/addon/hint/ 下引入多个
import 'codemirror/addon/hint/show-hint.css'
import 'codemirror/addon/hint/show-hint.js'
import 'codemirror/addon/hint/anyword-hint'  // 简易的代码提示功能
// #endregion

// #region 高亮行功能
import 'codemirror/addon/selection/active-line'
import 'codemirror/addon/selection/selection-pointer'
// #endregion

// #region 覆盖scrollbar样式功能
import 'codemirror/addon/scroll/simplescrollbars.css'
import 'codemirror/addon/scroll/simplescrollbars'
// #endregion

//  #region 自动括号匹配功能
import 'codemirror/addon/edit/matchbrackets.js'
// #endregion

// 全屏功能 由于项目复杂,自带的全屏功能一般不好使
// import 'codemirror/addon/display/fullscreen.css'
// import 'codemirror/addon/display/fullscreen.js'

// 显示自动刷新
import 'codemirror/addon/display/autorefresh.js'

// 多语言支持?
// import 'codemirror/addon/mode/overlay'
// import 'codemirror/addon/mode/multiplex'

import 'codemirror/addon/lint/lint.js'
import 'codemirror/addon/lint/lint.css'

// #region  代码段折叠功能
import 'codemirror/addon/fold/foldcode.js'
import 'codemirror/addon/fold/foldgutter.js'
import 'codemirror/addon/fold/foldgutter.css'
import 'codemirror/addon/fold/brace-fold.js' // 括号折叠
import 'codemirror/addon/fold/comment-fold.js'
import 'codemirror/addon/fold/indent-fold.js' // 缩进折叠
import 'codemirror/addon/fold/comment-fold.js'
// import 'codemirror/addon/fold/xml-fold.js'
// import 'codemirror/addon/fold/markdown-fold.js'
// #endregion

// #region  merge功能
// import 'codemirror/addon/merge/merge.css'
// import 'codemirror/addon/merge/merge.js'
// #endregion

export default {
  name: 'LuaEditor',

  // #region 组件基础
  components: { codemirror: codemirror },
  props: {
    value: {
      type: String,
      required: true
    }
  },
  // #endregion

  // #region 数据相关
  data() {
    return {
      /** @type {import('@types/codemirror/index').EditorConfiguration}*/
      codemirrorOptions: {
        // 语言及语法模式
        mode: 'text/x-lua',
        // 主题
        theme: 'neat',
        // 显示函数
        line: true,
        // 显示行号
        lineNumbers: true,
        // 软换行
        lineWrapping: true,
        // tab宽度
        tabSize: 4,
        // 允许拖入的文件类型
        allowDropFileTypes: ['text/x-lua'],
        cursorScrollMargin: 5,
        extraKeys: {},
        // 高亮行功能
        styleActiveLine: true,
        // 调整scrollbar样式功能
        // scrollbarStyle: 'overlay',
        // 自动括号匹配功能
        matchBrackets: true,
        autofocus: true,
        autoRefresh: true,
        // #region 代码折叠
        foldGutter: true,
        // foldOptions: { scanUp: true },
        gutters: [
          'CodeMirror-linenumbers',
          'CodeMirror-foldgutter',
          'CodeMirror-lint-markers'
        ],
        // #endregion
        showHint: true,
        lint: true,
        hintOptions: {
          // 避免由于提示列表只有一个提示信息时,自动填充
          completeSingle: false
        }
      }
    }
  },
  computed: {},
  watch: {},
  // #endregion

  // #region 生命周期
  created() {},
  mounted() {},
  // #endregion

  methods: {
    handleInputChange(value) {
      this.$emit('input', value)
    },
    handleInputRead(cm) {
      // 显示代码提示框
      cm.showHint()
    }
  }
}
</script>

<style lang="scss">
.lua-editor {
  textarea {
    height: 100%;
  }
}
</style>

实现效果

代码高亮:

vue实现在线编辑器python vue 在线代码编辑器_lua_03


简易的代码提示:

vue实现在线编辑器python vue 在线代码编辑器_lua_04


输入错误的代码:

vue实现在线编辑器python vue 在线代码编辑器_lua_05


由于不支持lua语言的代码段折叠、代码校验,所以无法测试

总结

优点:

  • 支持lua语法高亮
  • 简易的语法提示(根据当前代码中的关键字)
  • 可以按需导入功能与模块

缺点:

  • 不支持lua代码校验
  • 不支持lua代码格式化
  • lua语法提示不满足需求
  • 没有全量导入功能,当导入大量模块和功能时,需要写很多import,不方便管理

.
.
.

ace.js(本文使用的方案)

安装

npm i ace-builds -S

推荐安装@types/ace以提供类型推断

npm i @types/ace -D

基础使用

全量引入(不推荐,仅开发阶段使用)

ace提供了一个全量导入模块:ace-builds/webpack-resolver

  • 缺点:导致引入包过大,不需要的、使用不到的也被打包进来了,而且打包后的根目录下会有很多js文件
  • 优点:不需要再去手动查找/导入会用到的模块了,适合在开发阶段使用
import ace from 'ace-builds'
import 'ace-builds/css/ace.css'
import 'ace-builds/webpack-resolver' // 全量导入

打包后输出如下:

vue实现在线编辑器python vue 在线代码编辑器_vue.js_06


vue实现在线编辑器python vue 在线代码编辑器_vue实现在线编辑器python_07

按需引入(推荐,打包时使用)

ace-build.js建议使用按需引入,全量引入时体积过于庞大

  • 按需引入需要知道会用到哪些模块,提前import进来,适合已开发完成、维护的阶段
  • 需要注意的是,worker相关js文件,不能直接使用import导入,一定要使用ace.config.setModuleUrl + file-loader导入,否则会报错误
import ace from 'ace-builds'
import 'ace-builds/css/ace.css'
// #region lua语法高亮
import 'ace-builds/src-noconflict/mode-lua'
import 'ace-builds/src-noconflict/snippets/lua'
// #endregion

// #region 代码提示
import 'ace-builds/src-noconflict/ext-language_tools'
// #endregion

// #region 代码校验
ace.config.setModuleUrl(
  'ace/mode/base_worker',
  require('file-loader?esModule=false!ace-builds/src-noconflict/worker-base.js')
)
ace.config.setModuleUrl(
  'ace/mode/lua_worker',
  require('file-loader?esModule=false!ace-builds/src-noconflict/worker-lua.js')
)
// #endregion

// #region 主题
import 'ace-builds/src-noconflict/theme-chrome'
// #endregion

// #region 其他功能
import 'ace-builds/src-noconflict/ext-searchbox'
import 'ace-builds/src-noconflict/ext-keybinding_menu'
import 'ace-builds/src-noconflict/ext-settings_menu'
// #endregion

打包后输出如下,少了很多js文件,体积比全量引入小了10+MB

vue实现在线编辑器python vue 在线代码编辑器_vue.js_08

vue实现在线编辑器python vue 在线代码编辑器_vue.js_09

实现Lua代码编辑器

<template>
  <div class="lua-editor">
    <!-- <textarea ref="textarea"></textarea> -->
  </div>
</template>

<script>
import ace from 'ace-builds'
import 'ace-builds/css/ace.css'
import 'ace-builds/webpack-resolver' // 全量导入

export default {
  // #region 组件基础
  components: {},
  props: {
    value: {
      type: String,
      required: true
    },
    options: Object
  },
  // #endregion
  
  // #region 数据相关
  data() {
    content: this.value || '',
    return {/** @type {import('ace-builds').Ace.EditorOptions}*/
      editorOptions: {
        // keyboardHandler: '',
        mode: 'ace/mode/lua',
        theme: 'ace/theme/chrome',
        tabSize: 2,
        selectionStyle: 'text',
        // 拖动代码块
        dragEnabled: true,
        useWorker: true,
        // 自动缩进
        enableAutoIndent: true,
        // 显示行号
        showLineNumbers: true,
        useSoftTabs: true,
        // 渐变隐藏折叠按钮
        fadeFoldWidgets: true,
        // 输入边界
        showPrintMargin: false,
        // 高亮当前行
        highlightActiveLine: true,
        // 高亮选中词
        highlightSelectedWord: true,
        // 滚动动画
        autoScrollEditorIntoView: true,
        copyWithEmptySelection: true,
        // #region 启用自动完成和代码段
        // enableBasicAutocompletion: true,
        enableLiveAutocompletion: true,
        enableSnippets: true
        // #endregion
      }}
  },
  computed: {
    /** @return {import('ace-builds').Ace.Editor} */
    _editor() {
      return this.editor
    }
  },
  watch: {
    value(nval) {
      this.syncContent(nval)
    },
    options(nval) {
      this.syncOptions(nval)
    }
  },
  // #endregion
  
  // #region 生命周期
  created() {},
  mounted() {
    let editor = ace.edit(this.$el, this.editorOptions)
    this.editor = editor
  },
  beforeDestroy() {
    this._editor.destroy()
  },
  // #endregion

  methods: {
    /**
     * 同步内容
     */
    syncContent(value = this.value) {
      if (this.content != value) {
        this._editor?.setValue(value, 1)
      }
    },
    /**
     * 同步配置
     */
    syncOptions(option = this.option) {
      let keys = Object.keys(option)
      for (let key of keys) {
        // 禁止修改固定的option
        if (key in this.editorOptions) continue
        this._editor?.setOption(key, option[key])
      }
    }
  }
}
</script>

实现效果

功能

实际效果

代码高亮

vue实现在线编辑器python vue 在线代码编辑器_css_10

代码提示

vue实现在线编辑器python vue 在线代码编辑器_vue实现在线编辑器python_11

vue实现在线编辑器python vue 在线代码编辑器_css_12

vue实现在线编辑器python vue 在线代码编辑器_lua_13

vue实现在线编辑器python vue 在线代码编辑器_css_14

代码校验

vue实现在线编辑器python vue 在线代码编辑器_vue实现在线编辑器python_15

代码段折叠

vue实现在线编辑器python vue 在线代码编辑器_lua_16

vue实现在线编辑器python vue 在线代码编辑器_lua_17

总结

优点:

  • 支持lua语法高亮
  • 支持lua代码校验
  • 支持lua代码块折叠
  • 基础语法提示,提供部分代码片段
  • 可以按需导入功能与模块

缺点:

  • 不支持lua代码格式化
  • 引入的模块,打包后会在根目录下产生很多js文件

Monaco Editor

这个编辑器是在写文章的时候看到的,目前还没有尝试使用,后续有时间追加

可参考文档:

.
.
.

3 代码格式化

codemirror、ace都不支持lua的代码格式化,那只能在github找找使用javascript实现lua格式化的开源插件了,共找到如下开源库

luaparse

此开源库使用javascript语言实现将lua代码字符串分析为ast抽象语法树,但是无法直接使用,需要二次开发,有一定的开发成本

@appguru/luafmt(本文使用的方案)

此开源库基于luaparse库开发,luaparse将lua语言转换为ast树,@appguru/luafmt在此基础上对ast树进行分析,以实现代码格式化

安装&使用

安装

npm i @appguru/luafmt -S

使用

import { ast } from '@appguru/luafmt'
import { parse } from 'luaparse'

// 创建格式化器实例
let formatter = ast.formatter({
  indent: ' ',  // 缩进符号
  newline: '\n', // 换行符号
  extra_newlines: true, // 是否额外换行
  // tabWidth: 2, // tab宽度
  // useTabs: true,
  // semi: false, // 是否加分号
  inline: {  // 单行宽度设置
    block: {
      max_exp_length: 60
    },
    table: {
      max_field_count: 0,
      max_field_length: 0
    }
  }
})

// 使用
let result = formatter('lua code')

实际效果

原始代码:

vue实现在线编辑器python vue 在线代码编辑器_vue.js_18


格式化后:

vue实现在线编辑器python vue 在线代码编辑器_lua_19

lua-format

此库应该是自行实现了ast语法树分析,所以体积会比较大

安装&使用

安装

npm i lua-format -S

使用

import luafmt from 'luamin'

let result = luafmt.Beautify(code, {
  RenameVariables: false, // 重命名变量
  RenameGlobals: false, // 重命名全局变量
  SolveMath: false
})

实际效果

原始代码:

vue实现在线编辑器python vue 在线代码编辑器_lua_20

格式化后:

vue实现在线编辑器python vue 在线代码编辑器_lua_21

多次格式化后:

vue实现在线编辑器python vue 在线代码编辑器_lua_22


多行注释测试:

vue实现在线编辑器python vue 在线代码编辑器_vue实现在线编辑器python_23


vue实现在线编辑器python vue 在线代码编辑器_lua_24

luamin

此开源库也是基于luaparse库开发,不过是内部集成的方式,仅支持lua代码压缩,没有其他功能

安装&使用

安装

npm i luamin -S

使用

import luamin  from 'luamin'

let result = luamin.minify(code)

实际效果

原始代码:

vue实现在线编辑器python vue 在线代码编辑器_vue.js_25

压缩后:

vue实现在线编辑器python vue 在线代码编辑器_lua_26

总结

推荐使用@appguru/luafmt,或者基于luaparse自行实现

-

打包后体积(粗略数值)

开源协议

最近一次提交

存在的问题

@appguru/luafmt

8.59kb

MIT

2020-11

1. 同行注释会跑到代码下面(强制换行)

2. 数值变量会变成指数形式

lua-format

32.1kb(gzip: 10.4kb)

ISC

2022-07

1.会输出:–discord.gg/boronide, code generated using luamin.js™ + 换行 * 4,需要手动去除

2.会将多行注释删除

luamin

64.1kb(gzip: 15.2kb)

MIT

2019-08

只支持压缩lua代码

4 lua代码编辑器

源码

<template>
  <div class="lua-editor">
    <!-- <textarea ref="textarea"></textarea> -->
  </div>
</template>

<script>
import ace from 'ace-builds'

// import 'ace-builds/webpack-resolver'

import 'ace-builds/css/ace.css'
// #region lua语法高亮
import 'ace-builds/src-noconflict/mode-lua'
import 'ace-builds/src-noconflict/snippets/lua'
// #endregion

// #region 代码提示
import 'ace-builds/src-noconflict/ext-language_tools'
// #endregion

// #region 代码校验
// import 'ace-builds/src-noconflict/worker-base.js'
// import 'ace-builds/src-noconflict/worker-lua.js'
ace.config.setModuleUrl(
  'ace/mode/base_worker',
  require('file-loader?esModule=false!ace-builds/src-noconflict/worker-base.js')
)
ace.config.setModuleUrl(
  'ace/mode/lua_worker',
  require('file-loader?esModule=false!ace-builds/src-noconflict/worker-lua.js')
)
// #endregion

// #region 主题
import 'ace-builds/src-noconflict/theme-chrome'
// #endregion

// #region 其他功能
import 'ace-builds/src-noconflict/ext-searchbox'
import 'ace-builds/src-noconflict/ext-keybinding_menu'
import 'ace-builds/src-noconflict/ext-settings_menu'
// #endregion

import { ast } from '@appguru/luafmt'
// import { ast } from './appguru'
import { parse } from 'luaparse'

const events = [
  'bulr',
  'change',
  'changeSelectionStyle',
  'changeSession',
  'copy',
  'focus',
  'paste',
  'mousemove',
  'mouseup',
  'mousewheel',
  'click'
]

/**
 * LuaEditor
 * @author lcm
 * @createTime 
 * @description
 */
export default {
  name: 'LuaEditor',

  // #region 组件基础
  components: {},
  props: {
    value: {
      type: String,
      required: true
    },
    options: Object,
    theme: String,
    mode: String,
    readonly: Boolean
  },
  // #endregion

  // #region 数据相关
  data() {
    return {
      content: this.value || '',
      cursorPos: null,
      /** @type {import('ace-builds').Ace.EditorOptions}*/
      editorOptions: {
        // keyboardHandler: '',
        mode: 'ace/mode/lua',
        theme: 'ace/theme/chrome',
        tabSize: 2,
        selectionStyle: 'text',
        // 拖动代码块
        dragEnabled: true,
        useWorker: true,
        // 自动缩进
        enableAutoIndent: true,
        // 显示行号
        showLineNumbers: true,
        useSoftTabs: true,
        // 渐变隐藏折叠按钮
        fadeFoldWidgets: true,
        // 输入边界
        showPrintMargin: false,
        // 高亮当前行
        highlightActiveLine: true,
        // 高亮选中词
        highlightSelectedWord: true,
        // 滚动动画
        autoScrollEditorIntoView: true,
        copyWithEmptySelection: true,
        // #region 启用自动完成和代码段
        // enableBasicAutocompletion: true,
        enableLiveAutocompletion: true,
        enableSnippets: true
        // #endregion
      }
    }
  },
  computed: {
    /** @return {import('ace-builds').Ace.Editor} */
    _editor() {
      return this.editor
    }
  },
  watch: {
    value(nval) {
      this.syncContent(nval)
    },
    theme(nval) {
      this._editor?.setTheme('ace/theme/' + nval)
    },
    mode(nval) {
      this._editor?.getSession().setMode('ace/mode/' + nval)
    },
    readonly(nval) {
      this._editor?.setReadOnly(nval)
    },
    options(nval) {
      this.syncOptions(nval)
    }
  },
  // #endregion

  // #region 生命周期
  created() {},
  mounted() {
    if (this.theme) {
      this.editorOptions.theme = this.theme
    }
    // if (this.lang) {
    //   this.editorOptions.mode = this.lang
    // }

    //@appguru/luafmt
    this.formatter = ast.formatter({
      indent: ' ',
      newline: '\n',
      extra_newlines: true,
      // tabWidth: 2,
      // useTabs: true,
      // semi: false,
      inline: {
        block: {
          max_exp_length: 60
        },
        table: {
          max_field_count: 0,
          max_field_length: 0
        }
      }
    })

    let editor = ace.edit(this.$el, this.editorOptions)
    editor.setValue(this.value, 1)
    // editor.setOption('auto', 1)
    editor.commands.addCommands([
      {
        name: 'showSettingsMenu',
        bindKey: { win: 'Ctrl-q', mac: 'Ctrl-q' },
        exec(editor) {
          ace.config.loadModule('ace/ext/settings_menu', function (module) {
            module.init(editor)
            editor.showSettingsMenu()
          })
        },
        readOnly: true
      }
    ])
    // 快捷键帮助面板
    editor.commands.addCommand({
      name: 'showKeyboardShortcuts',
      bindKey: { win: 'Ctrl-Alt-h', mac: 'Command-Alt-h' },
      exec(editor) {
        ace.config.loadModule('ace/ext/keybinding_menu', function (module) {
          module.init(editor)
          editor.showKeyboardShortcuts()
        })
      }
    })
    //格式化
    editor.commands.addCommand({
      name: 'formattingCode',
      bindKey: { win: 'Shift-Alt-F', mac: 'Shift-Alt-F' },
      exec: (editor) => {
        this.formattingCode()
      }
    })
    editor.selection.on('changeCursor', (e) => {
      this.cursorPos = editor.getCursorPosition()
      this.$emit('changeCursor', e)
    })
    editor.on('change', (ev) => {
      this.cursorPos = editor.getCursorPosition()

      let value = editor.getValue()
      this.content = value

      console.log('cursorPos', this.cursorPos)

      this.$emit('input', value)
    })

    for (let name of events) {
      editor.on(name, (ev) => {
        this.$emit(name, ev)
      })
    }

    this.editor = editor
  },
  beforeDestroy() {
    this._editor.destroy()
  },
  // #endregion

  methods: {
    /**
     * 格式化代码
     */
    formattingCode() {
      try {
        const srcAST = parse(this.content)
        ast.fixRanges(srcAST)
        ast.insertComments(srcAST)

        let result = this.formatter(srcAST)
        this.content = result
        this.editor.setValue(this.content)

        // 获取当前光标位置
        // let cursorPos = this._editor.getCursorPosition()

        // this._editor.setValue(result, 0)

        // 保持光标位置不变
        // this._editor.navigateTo(cursorPos.row, cursorPos.column)

        return true
      } catch (error) {
        console.log('格式化失败 ', error)
        return false
      }
    },
    /**
     * 同步内容
     */
    syncContent(value = this.value) {
      if (this.content != value) {
        this._editor?.setValue(value, 1)
      }
    },
    /**
     * 同步配置
     */
    syncOptions(option = this.option) {
      let keys = Object.keys(option)
      for (let key of keys) {
        // 禁止修改固定的option
        if (key in this.editorOptions) continue
        this._editor?.setOption(key, option[key])
      }
    }
  }
}
</script>

<style lang="scss">
.lua-editor {
  textarea {
    height: 100%;
  }
}
</style

效果

vue实现在线编辑器python vue 在线代码编辑器_lua_27

# 后语

  • 文中的不足,可评论指出
  • 写文章不易,如果对你有用的话就点个赞吧~
  • 目前lua没有统一的编辑器/IDE,所以lua项目开发起来还是挺痛苦的,这里推荐使用vscode开发,搭配Lua or EmmyLua插件
  • 参考文档: