成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

前端在線代碼編輯器技術雜談

開發 前端
這段時間在搞一個出碼項目,出碼后支持編輯代碼并可以在右側實時預覽,其ui如下,在調研了幾個開源(react-playground,react-live,minisandbox)的在線編輯運行react代碼的庫后,把所得所想分享給大家。

01、背景

這段時間在搞一個出碼項目,出碼后支持編輯代碼并可以在右側實時預覽,其ui如下,在調研了幾個開源(react-playground,react-live,minisandbox)的在線編輯運行react代碼的庫后,把所得所想分享給大家。

圖片


02、react-playground

使用方式:

const files = {
    'App.tsx': `import {title} from './const'
      function App() {
        return <h1>this is {title}</h1>
      }
      export default App
      `,
    'const.ts': {
      code: 'export const title = "demo2"',
    },
  }
    
<PlaygroundSandbox
    width={700}
    height={400}
    files={files}
    theme='dark'
  />

2.1 編輯器部分

開源的使用最多的編輯器:Monaco Editor、Ace 和 Code Mirror。

Monaco Editor生態豐富功能強大,還是vscode同款編輯器,帶代碼提示等功能,開發友好。

react-playgound是用的就是 @monaco-editor/react

<MonacoEditor
    width='600px'
    height='800px'
    notallow={(newCode) => setCode(newCode)}
    defaultValue={code}
    defaultLanguage='javascript'
  />

(比較簡單,也不是本次講解重點)

2.2 代碼預覽部分

項目中如果有全局樣式,全局變量,會影響到實時代碼運行效果,所以運行時需要一個干凈的環境去運行代碼,react-playground 選用的方案是iframe。

  • 瀏覽器運行jsx

瀏覽器不認識jsx,所以我們需要把jsx轉為js,這個轉換通常是用babel實現的,react-playground采用的是 @babel/standalone( babel的瀏覽器版)

import { transform } from '@babel/standalone'


const babelTransform = (code: string) => {
  return transform(code, {
  presets: ['react', 'typescript'],
  }).code
}


const compliedCode = babelTransform(jsxCode)

經過編譯,我們的代碼會變成

圖片

  • 代碼編譯的任務交給誰處理

交給主線程?可能會導致頁面阻塞,編輯時感到卡頓,所以好的解決方案是 新開一個web worker用于處理代碼編譯操作。

// compiler.worker.ts
self.addEventListener('message', async ({ data }) => {
   // 2. 接收到源代碼后編譯
   const compiledCode = babelTransform(jsxCode)
   //3. 編譯完成后,發送數據給index.tsx
  self.postMessage(compiledCode)
})


// index.tsx
compilerRef.current = new CompilerWorker()


useEffect(()=>{
  // 1. 源代碼變更后,發送給worker去編譯
  compilerRef.current?.postMessage(jsxCode)
},[jsxCode])




compilerRef.current.addEventListener('message', ({ data }) => {
  // 4. 接收到web worker編譯后的代碼,發送到iframe中
  iframeRef.current?.contentWindow?.postMessage(data)
})


// iframe.html
window.addEventListener('message', ({data}) => {
  // 6. 接收到編譯后的代碼執行
  // 代碼插入script標簽中或者轉為臨時文件地址賦值給script標簽
})

支持第三方模塊包引入

由于瀏覽器原生支持esm,我們選擇使用esm格式的第三方依賴包。有一些非esm的包,可以通過esm.sh 找到其對應的的esm格式文件地址。

然后通過importmap 映射模塊地址。

importmap 允許您在瀏覽器環境中指定模塊路徑映射,import "react" 時,會按importmap 中指定的 URL 來加載 react 模塊。

<script type="importmap">
  {
    "imports": {
    "react": "https://esm.sh/react@18.2.0",
    "react-dom/client": "https://esm.sh/react-dom@18.2.0",
  }
}
</script>

圖片

引入本地模塊

我們在本地項目中執行這么一行代碼 import a from './A',這樣的導入語句通常由構建工具和模塊打包器處理的(如webpack,vite等)

  • esm 模塊

依賴編譯時生成的樹形結構,尋找到對應路徑。會以此尋找A.js,A.jsx,A.ts… 。然后打包到一個文件或獨立文件中。

  • commonjs模塊

Node.js 提供了一個文件系統模塊,它允許你在服務器端環境中訪問和操作本地文件系統。

那么在瀏覽器中,我們如何解析 import a from './A' 對應的模塊?

瀏覽器絕大部分支持esm模塊,支持的是URL文件,所以我們可以把'./A'替換成url地址,瀏覽器就可以使用了

import a from './A' --> import a from 'http://xxx/A'


這由于我們沒有一個服務來提供文件,可以使用 URL.createObjectURL 生成臨時URL文件。

// 把代碼編譯后轉換成url文件
const A = URL.createObjectURL(
  new Blob([babelTransform(code)], {
  type: 'application/javascript'
  })
)
// 轉換后的地址
// blob:https://localhost:3000/e4ef352f-1c5f-414e-8009-33514b300842
// 替換 './A'
import a from 'blob:https://localhost:3000/e4ef352f-1c5f-414e-8009-33514b300842'

按照這個思路,在引入本次文件時,我們只要分析import語句,把對應模塊代碼在編譯時替換為本地URL地址。

怎么做呢?用babel插件。

babel 插件就是在 transform 的階段增刪改 AST 的:

圖片

const transformImportSourcePlugin: PluginObj = {
  visitor: {
  ImportDeclaration(path) {
    path.node.source.value = url;
  }
},
}
const res = transform(code, {
  presets: ['react', 'typescript'],
  filename: 'test.ts',
  plugins: [transformImportSourcePlugin]
}

path.node.source.value就對應ast樹的如下值

圖片


圖片

至此,react-playground 的原理已經分析清楚了。分別用 Blob + URL.createBlobURL 和 import maps 來做。

引入樣式文件

(() => {
  let stylesheet = document.getElementById('style');
  if (!stylesheet) {
    stylesheet = document.createElement('style')
    stylesheet.setAttribute('id', 'style')
    document.head.appendChild(stylesheet)
  }
  const styles = document.createTextNode(`${css}`)
  stylesheet.innerHTML = ''
  stylesheet.appendChild(styles)
})()

03、react-live

我們再來看下react-live方案,在github上4.2k star

相比 react-playground,有兩點不同:

1)編譯后的js代碼,不是通過script標簽插入的形式做的,而是通過new Function/eval 的方式變為可執行代碼。

2)依賴的處理不同。

function evalCode(code: string, scope: Record<string, any>) {
  const scopeKeys = Object.keys(scope)
  const scopeValues = Object.values(scope)
  // eslint-disable-next-line no-new-func
  return new Function(...scopeKeys, code)(...scopeValues)
}


function generateNode(props) {
  const { code = '', scope = {} } = props、
  // 刪除末尾分號,因為下邊會在 code 外包裝一個 return (code) 的操作,有分號會導致語法錯誤
  const codeTrimmed = code.trim().replace(/;$/, '')
  const opts = { transforms: ['jsx', 'imports'] as Transform[] }
  // 前邊補上一個 return,以便下邊 evalCode 后能正確拿到生成的組件
  const transformed = transform(`return (${codeTrimmed})`, opts).code.trim()
  
  // 編譯后只是一個字符串,我們通過 evalCode 函數將它變成可執行代碼
  return evalCode(transformed, { React, ...scope })
}

重點說下,依賴的處理

1)import-map方案

  • 優點:簡單易用。
  • 缺點:
  • 只支持esm模塊地址;
  • 如果一個A包內部引了B包,只在importmap中定義A包的映射地址,在實際運行A包邏輯時,會報Uncaught TypeError: Failed to resolve module specifier “B” 這樣的錯誤,所以 對于依賴復雜的包,使用importmap方案不夠友好,需要分析依賴,然后每個都在importmap定義映射。

2)scope 方案

實際傳入的是一個上下文對象,它定義了當前代碼可以訪問的所有外部資源。react-live 會將代碼中的標識符與 scope 進行匹配,以找到這些標識符的定義。

  • 優點:直接提供變量和組件,簡化了依賴管理
  • 缺點:不能自動解析復雜的模塊依賴,依賴關系需要手動管理。

介紹和對比了react-playground和react-live兩種方案,他們主要的使用場景在 「代碼片段實時預覽」 。如組件文檔實例、在線調試代碼等。那有沒有更強大的前端編輯器和實時預覽方案,答案是肯定的。

比如codesandbox、stackblitz。

04、codesandbox

codesandbox 開源了 @codesandbox/sandpack-react庫,這個React庫提供了很多開箱即用的codesandbox模塊。

對應codesanbox的面板來看,分別是以下組件

圖片

各個組件通過postMessage與SandackPreview渲染的iframe交互。

codesandbox的兩種運行環境:圖片

1)純前端項目(比如React項目、純JS項目)使用Browser Sandbox

2)需要服務端運行環境(比如Docker項目、全棧框架項目)使用Cloud Sandbox(他底層使用的是MicroVM)

對于browser sandbox來說,由于瀏覽器端并沒有 Node 環境,所以 CodeSandbox 自己實現了一個可以跑在瀏覽器端的簡化版 webpack。稱為 mini webpack。

4.1 原理

關于codesandbox的原理,文章有很多。我這里不重點解釋了

CodeSandbox 構建項目過程

構建過程主要包括了三個步驟:

  • Packager--npm 包加載階段:下載 npm 包并遞歸查找所有引用到的文件,然后提供給下個階段進行編譯
  • Transpilation--編譯階段:編譯所有代碼, 構建模塊依賴圖
  • Evaluation--執行階段:使用 eval 運行編譯后的代碼,實現項目預覽

Packager--npm包加載階段

codesandbox受WebpackDllPlugin啟發。DllPlugin 會將所有依賴都打包到一個dll文件中(存儲預打包模塊),并創建一個 manifest 文件來描述dll的元數據(描述模塊映射)。

{
  "name": "dll_bundle",
  "content": {
    "./node_modules/fbjs/lib/emptyFunction.js": 0,
    "./node_modules/fbjs/lib/invariant.js": 1,
    "./node_modules/fbjs/lib/warning.js": 2,
      "./node_modules/fbjs/lib/react.development.js": 3,
    "..."

每一個路徑都映射一個模塊id。如果我想引入 React,我只需要調用 dll_bundle(3),然后我就得到了React。

基于這個思想, CodeSandbox 構建了自己的在線打包服務, 和WebpackDllPlugin不一樣的是,CodeSandbox是在服務端預先構建Manifest文件的。

這個包叫,packager ,是基于 express框架提供的服務,其流程是,比如我現在有一個 react包16.8.0版本,首先 express 框架接收到請求中的包名以及包版本, react、16.8.0。然后通過 yarn 下載 react 以及 react 的依賴包到磁盤上,通過讀取 npm 包的 package.json 文件中的 module、main 等字段找到 npm 包入口文件,然后解析 AST 中所有的 require 語句,將被 require 的文件內容添加到 manifest 文件中,并且遞歸執行剛才的步驟,最終形成依賴圖。

這樣就實現將 npm 包文件內容轉移到 manifest.json 上的目的,同時也實現了剔除 npm 模塊中多余的文件的目的。最后返回給 Sandbox 進行編譯。

manifest 長這個樣子:

圖片

Transpilation--編譯階段

先從 Packager 服務下載 npm 依賴包對應的 manifest 文件,接著前端項目的入口文件開始對項目進行編譯,并解析 AST 遞歸編譯被 require 的文件,形成依賴圖。原理同webpack。

Evaluation--執行階段

項目入口文件對應的編譯后的模塊開始,遞歸調用 eval 執行所有被引用到的模塊。

結一下:Browser Sandbox 頁面通過內置的mini webpack與其他工具(比如babel),編譯并執行代碼。

圖片

代碼編譯、執行的信息也會通過通信協議傳遞回各個需要的模塊。比如,控制臺模塊可以根據type為console的信息打印消息。

05、stackblitz

圖片

stackblitz 核心技術是webcontainers。

5.1 什么是webcontainers?

WebContainer 是由 StackBlitz 團隊開發的一項革命性技術,它允許我們在瀏覽器中運行完整的 Node.js 環境。這使得我們可以在瀏覽器中運行現代 JavaScript 開發工具,如 Webpack、Vite 和各種 npm 包,而無需安裝任何本地依賴。

以前,我們如果想要在瀏覽器內實現本地化,主流想法是以electron為例,把瀏覽器的內核引擎與node環境進行搭配,實現了web應用的本地化,但是隨著wasm的技術的發展,瀏覽器的運算能力已經能夠比肩系統級,因此,也就能夠支持起來將node移植到瀏覽器內的可行性。這種技術 就是webcontainers。

以前需要云虛擬機來執行用戶代碼的應用程序,現在在 WebContainers 中可以完全在瀏覽器中運行。

簡而言之:webContainer 就是一個可以運行在瀏覽器頁面中的微型操作系統,提供了文件系統、運行進程的能力,同時內置了 nodejs、npm/yarn/pnpm 等包管理器。

主要特性:

  • 能夠在瀏覽器中運行 node.js 及其工具鏈(如:webpack、vite 等)
  • 靈活:在 WebContainers 支持下,編碼體驗將會大幅提升
  • 安全:所有內容都運行在瀏覽器頁面中,天然隔離,非常安全
  • 快速:毫秒級啟動整個開發環境。
  • 始終開源免費
  • 零延時。離線工作

熱更新從代碼編寫,到編譯打包,完全在瀏覽器中閉環,只要打開一個瀏覽器就完成所有的動作。

5.2 了解 WebAssembly

WebAssembly 是一種運行在現代網絡瀏覽器中的新型代碼,并且提供新的性能特性和效果。它設計的目的不是為了手寫代碼而是為諸如 C、C++和 Rust 等低級源語言提供一個高效的編譯目標。

WebAssembly 是一門低級的類匯編語言。它有一種緊湊的二進制格式,使其能夠以接近原生性能的速度運行。

WebAssembly 是 WebContainers 能夠在瀏覽器中運行的核心。通過將 Node.js 編譯為 WebAssembly,WebContainers 能夠在瀏覽器中提供一個完整的開發環境,包括運行 Node.js 代碼、安裝和管理 npm 包等功能。

5.3 案例:在瀏覽器中運行一個簡單的 Node.js 應用

第一步:創建WebContainer實例,并啟動

import { WebContainer } from '@webcontainer/api';


  // 啟動 WebContainer 實例
  const webContainerInstance = await WebContainer.boot();

第二步:創建文件系統,并掛載到webcontainer實例上

await webContainerInstance.mount(projectFiles);

第三步:以編程方式下載依賴

const install = await webContainerInstance.spawn('npm', ['i']);
await install.exit;

第四步:啟動服務

await webContainerInstance.spawn('npm', ['run', 'dev']);

這樣,我們就使用webcontainers開發了一個簡單的node應用。

我們可以從 stackblitz體驗webcontainers案例。

5.4 原理

一句話就是,在 service worker 中,借助 wasm 實現一個 js runtime。

利用 WebAssembly 來實現一個瀏覽器環境中不存在的 API(如 Node.js 的 readFile),并將其功能注入到全局對象供 JavaScript 使用。

5.5 使用場景&支持度

1)webIDE,如stackblitz;

2)bug復現片段;

3、)實驗功能,無需本地新啟一個項目,費時費力。

WebContainers 在基于 Chromium 的桌面瀏覽器上開箱即用,在 Safari 16.4 和 Firefox 上也受支持。

參考鏈接

[01] React Playground在線Dome

https://fewismuch.github.io/react-playground/#eNqNVd1u2zYUfhVOQWEbi37sxmmmJUG7bsB60Q1oL3Yx74ImjyQ2FCmQlGPX8LvvkJJsOXWBQjZAfec7

[02] @babel/standalone( babel的瀏覽器版)

https://link.juejin.cn/?target=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2F%40babel%2Fstandalone

[03] @codesandbox/sandpack-react庫

https://www.npmjs.com/package/@codesandbox/sandpack-react

[04] express

https://expressjs.com/

[05] mini webpack

https://juejin.cn/post/6844903880652750862?from=search-suggest

[06] 離線工作

https://mmbiz.qpic.cn/mmbiz_gif/meG6Vo0MevhvJfkvEYBMuCrCvjbDn6d37PcBH4tcP4c62kOhUfLHLQSOD8lhv8HR7SQDF9KA2JoIzpJkuNqgUQ/640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1

[07] stackblitz

https://stackblitz.com/edit/stackblitz-webcontainer-api-starter-4vmxqn?file=main.js

責任編輯:龐桂玉 來源: 阿里技術
相關推薦

2022-12-02 07:24:46

2020-09-18 06:00:51

開源Markdown編輯器

2024-11-11 00:00:04

前端代碼編輯器

2022-01-04 08:16:49

編輯器在線編輯開發

2021-04-08 14:58:59

開發前端編輯器

2022-09-08 09:01:41

CodePenJavaScripCSS

2009-06-11 10:03:57

NetBeans代碼

2011-04-21 15:39:26

HTML

2023-04-17 11:03:52

富文本編輯器MTE

2022-12-01 16:59:32

代碼編輯器開發前端

2011-04-07 10:42:11

編輯器云計算

2017-03-09 19:25:38

JavaScript代碼編輯器

2010-03-24 09:20:07

CentOS vi編輯

2016-11-08 20:14:08

eclipse程序員編輯器

2024-05-06 12:23:00

GenAI人工智能

2011-10-31 10:17:05

插件

2020-07-15 15:12:17

Python代碼編輯器編程語言

2024-02-21 16:40:06

Web代碼編輯器開源

2013-07-11 11:13:51

編輯器

2023-08-10 08:38:57

Project IDFlutter谷歌
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 免费一区 | 男人亚洲天堂 | 伊人网综合 | 日韩一区二区免费视频 | 亚洲精品第一页 | 香蕉视频黄色 | 国产精品二区三区在线观看 | 一区二区三区四区av | 色综合一区 | 91国内外精品自在线播放 | 精品视频在线一区 | 久久久久一区二区三区 | 亚洲精品乱码久久久久久黑人 | 午夜看看 | 无码一区二区三区视频 | 一级黄色日本片 | 国产一区二区三区四区区 | 国产一区二区三区四区五区加勒比 | 久草视频在线播放 | 免费一级片| 一区二区三区四区电影视频在线观看 | 日本电影韩国电影免费观看 | 亚洲三级在线 | 欧美三级视频在线观看 | 日韩精品一区二区三区在线 | 日本不卡一区二区三区在线观看 | 亚洲一区二区三区四区五区中文 | 国产线视频精品免费观看视频 | 超碰97人人人人人蜜桃 | 欧美日韩在线一区二区三区 | av色站 | 日本一区二区在线视频 | 国产精品久久久久久久久免费高清 | 欧美精品在线一区二区三区 | 国产成人综合久久 | 成人在线视频看看 | 欧美国产免费 | 成人网址在线观看 | 精品国产91亚洲一区二区三区www | 日韩精品福利 | 精品国产区 |