如何製做一款在線編譯器

在文章開始以前先展現一下我本身作的在線編譯器 JS-Encoder:javascript

點此預覽css

截圖未命名.jpg

大概三四個月以前我開始有了製做在線編譯器的想法,在此以前我接觸過不少的在線編譯器,如CodePenJsBinJsFiddle等,這些都很是優秀且有着龐大的用戶羣體的編譯器。html

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

codemirror

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

原來這些編譯器是依靠 codemirror 來實現的,codemirror 是一個很是複雜的工具,以致於我花了兩天時間才熟悉它的配置項。codemirror 自己是採用直接操做 DOM 的方式,而個人項目是使用 Vue + Webpack 構建的,這違反了 Vue 數據驅動 的宗旨,因而我在 npm 上發現了 vue-codemirror 這個工具,採用 Vue 的方式構建代碼編輯器git

codemirror 有許多配置項,我在本身的項目中用到了以下配置,若是你想看所有配置,能夠看這裏github

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 // 激活當前行樣式
      },

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

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

theme 表示編輯器使用的配色,官方支持不少種配色,但確沒有配色預覽,因此我直接使用我熟悉的 monokai 做爲主題,由於我比較喜歡 vscode 的配色,因此我找到 monokai.css 文件並修改了許多樣式,雖然最後仍是和真正的 vscode 主題有差別,但我真的盡力了😭sass

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上關注這個項目。隨着更多功能的實現,我會繼續更新這篇文章。

相關文章
相關標籤/搜索