如何制作一款在線編譯器


在文章開始之前先展示一下我自己做的在線編譯器 JS-Encoder:

點此預覽

截圖未命名.jpg

大概三四個月之前我開始有了制作在線編譯器的想法,在此之前我接觸過很多的在線編譯器,如CodePenJsBinJsFiddle等,這些都非常優秀且有着龐大的用戶群體的編譯器。

我一直對在線編譯器的實現抱有濃厚興趣,這些在線編譯器支持很多種語言,代碼變色,諸多的快捷鍵以及一些個性化設置,這使得在線編譯器看上去和我們在本地下載的編譯器軟件也不會有太大的區別,我完全不知道這些復雜的功能要怎么實現,於是我觀察 CodePenJsBin 代碼發現他倆都使用了一個叫 codemirror 的工具。

codemirror

codemirror 是一個用於瀏覽器的 JavaScript 實現的多功能文本編輯器。它專門用於編輯代碼,並帶有許多語言模式和插件 ,可實現更高級的編輯功能。

原來這些編譯器是依靠 codemirror 來實現的,codemirror 是一個非常復雜的工具,以至於我花了兩天時間才熟悉它的配置項。codemirror 本身是采用直接操作 DOM 的方式,而我的項目是使用 Vue + Webpack 構建的,這違反了 Vue 數據驅動 的宗旨,於是我在 npm 上發現了 vue-codemirror 這個工具,采用 Vue 的方式構建代碼編輯器

codemirror 有許多配置項,我在自己的項目中用到了如下配置,如果你想看全部配置,可以看這里

cmOptions: {
        // codemirror config
        flattenSpans: false, // 默認情況下,CodeMirror會將使用相同class的兩個span合並成一個。通過設置此項為false禁用此功能
        tabSize: 2, // tab縮進空格數
        mode: '', // 模式
        theme: 'monokai', // 主題
        smartIndent: true, // 是否智能縮進
        lineNumbers: true, // 顯示行號
        matchBrackets: true, // 匹配符號
        lineWiseCopyCut: true, // 如果在復制或剪切時沒有選擇文本,那么就會自動操作光標所在的整行
        indentWithTabs: true, // 在縮進時,是否需要把 n*tab寬度個空格替換成n個tab字符
        electricChars: true, // 在輸入可能改變當前的縮進時,是否重新縮進
        indentUnit: 2, // 縮進單位,默認2
        autoCloseTags: true, // 自動關閉標簽
        autoCloseBrackets: true, // 自動輸入括弧
        foldGutter: true, // 允許在行號位置折疊
        cursorHeight: 1, // 光標高度
        keyMap: 'sublime', // 快捷鍵集合
        extraKeys: {
          'Ctrl-Alt': 'autocomplete',
          'Ctrl-Q': cm => {
            cm.foldCode(cm.getCursor())
          }
        }, //智能提示
        gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], // 用來添加額外的gutter
        styleActiveLine: true // 激活當前行樣式
      },

這些配置只是一小部分,但足夠實現我想要的功能了

mode 表示當前編輯器使用的語言

theme 表示編輯器使用的配色,官方支持很多種配色,但確沒有配色預覽,所以我直接使用我熟悉的 monokai 作為主題,因為我比較喜歡 vscode 的配色,所以我找到 monokai.css 文件並修改了許多樣式,雖然最后還是和真正的 vscode 主題有差異,但我真的盡力了😭

keymap 我設置為 sublimesublime上大部分快捷鍵都是可用的

其他的配置我在注釋里應該已經說明白了,這里就不解釋了

codemirror 的效果還是不錯的

截圖未命名.jpg

有了 codemirror 這個神器,可以說最難的問題已經解決了,但是還有很多數不清的小問題需要解決

布局

布局方面有很多是參考 JsBin 的,因為我覺得它的界面看起來很簡潔,舒服

JsBin 的布局是醬嬸兒的:

截圖未命名.jpg

分為五個窗口,鼠標放到兩個窗口的邊界上可以拖動改變窗口大小

GIF.gif

鼠標的拖動會使得一個窗口寬度增加,而另一個窗口寬度減少,但是兩個窗口寬度之和是不會改變的

我的思路是:

在點擊邊界的時候獲取兩個相鄰窗口的寬度,鼠標拖動的時候計算鼠標水平移動距離,並對兩個窗口的寬度進行相應增減

由於這五個窗口都是同級的子組件,一個窗口獲取另外一個窗口的寬度比較麻煩,於是我將這五個窗口的寬度都放在 Vuex 中儲存以便使用,每一個窗口的寬度都隨着 Vuex 中寬度信息的改變而改變

成功實現效果:

GIF.gif

為了避免兩個窗口重合問題,我設置了 min-width: 100px; 的樣式

除了兩個窗口的問題之外,還要做到所有窗口寬度隨着瀏覽器寬度變化而改變:

GIF.gif

這個效果也很容易實現,只要在瀏覽器寬度改變的時候每個窗口的寬度加上或減去 改變寬度/窗口數量 就可以了

Iframe

這是我第一次真正接觸 iframe 這個東西,可能他很簡單,但我確實在它身上花了不小的力氣

我已經解決了窗口拖動的問題,但這對 iframe 是無效的,我一直很困惑,找不出原因,最后突然想到:

iframe 是一個獨立的新頁面,在 iframe 之外觸發的事件不會影響到 iframe 本身,當我用鼠標拖動邊界的時候,如果鼠標進入了 iframe 中,那么這個拖動事件就失效了,所以在拖動時候需要先給 iframe 上面加一個透明的遮罩層,這樣就不會出現拖不動的問題了

在用戶一段時間內不輸入任何字符或者用戶直接點擊運行按鈕的時候,需要將編輯器中的 HTMLCSSJavaScript 代碼放到 iframe 中,iframe 就會將最終效果展示出來,於是編輯器中的內容我也會放在 Vuex

編譯

codemirror 可以實現很多功能,但編譯這件事兒他是不干的,像 JsBinCodePen 這樣的編譯器不只是支持普通的 HTMLCSSJavaScript 而已,他們還支持很多這三種語言的預處理語言

比如我選擇了 TypeScript 作為預處理語言,那么編譯器就需要先將 TypeScript 轉化為 JavaScript 再傳給 iframe

由於 JS-Encoder 是一個完全沒有后台的編譯器,所以要引入其他預處理語言的 npm 包和文件來編譯,比如在實現 SassScss 的編譯上, 我引入了 Sass.jsSass.worker.js 來編譯:

async function compileSass(code) {
  // scss&sass
  if (!loadFiles.get('sass')) {
    const Sass = await require('./sass')
    Sass.setWorkerUrl('static/js/sass.worker.js')
    loadFiles.set('sass', Sass)
  }

  const defSass = loadFiles.get('sass')
  const sass = new defSass()
  
  return new Promise((resolve, reject) => {
    sass.compile(code, result => {
      if (result.status === 0) resolve(result.text)
      else reject(new Error('fail to get result'))
    })
  })
}

這里 loadFiles 只是用於判斷是否已經引入過這些文件而已,我是在官方文檔上看到這個編譯方法的

目前 JS-Encoder 支持MarkDownSassScssLessStylusTypeScriptCoffeeScript, 之后會考慮支持 LiveScriptJSX(React)

設置

JS-Encoder 中除了預處理語言的選擇之外,還有以下設置

  • 延遲執行時間
    • 每一個可編輯窗口我都設置了 watch 監聽值的變化, 頻繁的輸入會導致方法的頻繁觸發,所以我設置了防抖函數,在設置的延遲時間內用戶沒有輸入任何字符,才會執行代碼
  • 將和tab等寬度的space轉化為tab
  • CDN
    • 可以添加外部的 CDN,這樣會在執行 JavaScript 之前先引入 CDN
  • CSS
    • 可以添加外部的 CSS,這樣會在執行 CSS 之前先通過 link 引入

總結

JS-Encoder 從正式開發到現在已經有兩個月,因為學業原因,也沒有過多的時間投入到開發中。目前 JS-Encoder 還是一個半成品,除了一些基本的之外其實還有很多功能沒有或者正在實現,如果感興趣的話可以在github上關注這個項目。隨着更多功能的實現,我會繼續更新這篇文章。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM