偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

前端在線代碼編輯器技術(shù)雜談

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

01、背景

這段時間在搞一個出碼項目,出碼后支持編輯代碼并可以在右側(cè)實時預(yù)覽,其ui如下,在調(diào)研了幾個開源(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生態(tài)豐富功能強大,還是vscode同款編輯器,帶代碼提示等功能,開發(fā)友好。

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

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

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

2.2 代碼預(yù)覽部分

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

  • 瀏覽器運行jsx

瀏覽器不認(rèn)識jsx,所以我們需要把jsx轉(zhuǎn)為js,這個轉(zhuǎn)換通常是用babel實現(xiàn)的,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)

經(jīng)過編譯,我們的代碼會變成

圖片

  • 代碼編譯的任務(wù)交給誰處理

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

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


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


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




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


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

支持第三方模塊包引入

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

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

importmap 允許您在瀏覽器環(huán)境中指定模塊路徑映射,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>

圖片

引入本地模塊

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

  • esm 模塊

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

  • commonjs模塊

Node.js 提供了一個文件系統(tǒng)模塊,它允許你在服務(wù)器端環(huán)境中訪問和操作本地文件系統(tǒng)。

那么在瀏覽器中,我們?nèi)绾谓馕?import a from './A' 對應(yīng)的模塊?

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

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


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

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

按照這個思路,在引入本次文件時,我們只要分析import語句,把對應(yīng)模塊代碼在編譯時替換為本地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就對應(yīng)ast樹的如下值

圖片


圖片

至此,react-playground 的原理已經(jīng)分析清楚了。分別用 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標(biāo)簽插入的形式做的,而是通過new Function/eval 的方式變?yōu)榭蓤?zhí)行代碼。

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) 的操作,有分號會導(dǎo)致語法錯誤
  const codeTrimmed = code.trim().replace(/;$/, '')
  const opts = { transforms: ['jsx', 'imports'] as Transform[] }
  // 前邊補上一個 return,以便下邊 evalCode 后能正確拿到生成的組件
  const transformed = transform(`return (${codeTrimmed})`, opts).code.trim()
  
  // 編譯后只是一個字符串,我們通過 evalCode 函數(shù)將它變成可執(zhí)行代碼
  return evalCode(transformed, { React, ...scope })
}

重點說下,依賴的處理

1)import-map方案

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

2)scope 方案

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

  • 優(yōu)點:直接提供變量和組件,簡化了依賴管理
  • 缺點:不能自動解析復(fù)雜的模塊依賴,依賴關(guān)系需要手動管理。

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

比如codesandbox、stackblitz。

04、codesandbox

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

對應(yīng)codesanbox的面板來看,分別是以下組件

圖片

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

codesandbox的兩種運行環(huán)境:圖片

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

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

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

4.1 原理

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

CodeSandbox 構(gòu)建項目過程

構(gòu)建過程主要包括了三個步驟:

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

Packager--npm包加載階段

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

{
  "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,我只需要調(diào)用 dll_bundle(3),然后我就得到了React。

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

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

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

manifest 長這個樣子:

圖片

Transpilation--編譯階段

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

Evaluation--執(zhí)行階段

項目入口文件對應(yīng)的編譯后的模塊開始,遞歸調(diào)用 eval 執(zhí)行所有被引用到的模塊。

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

圖片

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

05、stackblitz

圖片

stackblitz 核心技術(shù)是webcontainers。

5.1 什么是webcontainers?

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

以前,我們?nèi)绻胍跒g覽器內(nèi)實現(xiàn)本地化,主流想法是以electron為例,把瀏覽器的內(nèi)核引擎與node環(huán)境進(jìn)行搭配,實現(xiàn)了web應(yīng)用的本地化,但是隨著wasm的技術(shù)的發(fā)展,瀏覽器的運算能力已經(jīng)能夠比肩系統(tǒng)級,因此,也就能夠支持起來將node移植到瀏覽器內(nèi)的可行性。這種技術(shù) 就是webcontainers。

以前需要云虛擬機來執(zhí)行用戶代碼的應(yīng)用程序,現(xiàn)在在 WebContainers 中可以完全在瀏覽器中運行。

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

主要特性:

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

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

5.2 了解 WebAssembly

WebAssembly 是一種運行在現(xiàn)代網(wǎng)絡(luò)瀏覽器中的新型代碼,并且提供新的性能特性和效果。它設(shè)計的目的不是為了手寫代碼而是為諸如 C、C++和 Rust 等低級源語言提供一個高效的編譯目標(biāo)。

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

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

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

第一步:創(chuàng)建WebContainer實例,并啟動

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


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

第二步:創(chuàng)建文件系統(tǒng),并掛載到webcontainer實例上

await webContainerInstance.mount(projectFiles);

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

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

第四步:啟動服務(wù)

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

這樣,我們就使用webcontainers開發(fā)了一個簡單的node應(yīng)用。

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

5.4 原理

一句話就是,在 service worker 中,借助 wasm 實現(xiàn)一個 js runtime。

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

5.5 使用場景&支持度

1)webIDE,如stackblitz;

2)bug復(fù)現(xiàn)片段;

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

責(zé)任編輯:龐桂玉 來源: 阿里技術(shù)
相關(guān)推薦

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

編輯器在線編輯開發(fā)

2021-04-08 14:58:59

開發(fā)前端編輯器

2009-06-11 10:03:57

NetBeans代碼

2011-04-21 15:39:26

HTML

2022-09-08 09:01:41

CodePenJavaScripCSS

2023-04-17 11:03:52

富文本編輯器MTE

2011-04-07 10:42:11

編輯器云計算

2017-03-09 19:25:38

JavaScript代碼編輯器

2022-12-01 16:59:32

代碼編輯器開發(fā)前端

2016-11-08 20:14:08

eclipse程序員編輯器

2024-05-06 12:23:00

GenAI人工智能

2010-03-24 09:20:07

CentOS vi編輯

2013-07-11 11:13:51

編輯器

2011-09-19 11:39:31

Android手機Touchqode

2023-03-17 10:03:51

服務(wù)器編輯器vscode

2023-08-10 08:38:57

Project IDFlutter谷歌

2021-03-10 09:15:15

代碼文本編輯器編程
點贊
收藏

51CTO技術(shù)棧公眾號