小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

Chrome DevTools 遠(yuǎn)程調(diào)試協(xié)議分析及實(shí)戰(zhàn)

 西北望msm66g9f 2020-05-06

Chrome DevTools 可以說是前端開發(fā)最常用的工具,無論是普通頁面、移動(dòng)端 webview、小程序、甚至 node 應(yīng)用,都可以用它來調(diào)試。

Chrome DevTools 提供的功能非常豐富,包含 DOM、debugger、網(wǎng)絡(luò)、性能等許多能力。

為什么 Chrome DevTools 能夠適用這么多場景?如何把 Chrome DevTools 移植到新的應(yīng)用場景?Chrome DevTools 提供的功能我們能不能拆解出模塊單獨(dú)使用?今天我們來嘗試探索這些問題。

Chrome DevTools 組成

Chrome DevTools 包括四個(gè)部分:

  • 調(diào)試器協(xié)議:devtools-protocol[1],基于 json rpc 2.0。

  • 調(diào)試器后端:實(shí)現(xiàn)了調(diào)試協(xié)議的可調(diào)試實(shí)體,例如 chrome、node.js。

  • 調(diào)試器前端:通常指內(nèi)嵌在 chrome 中的調(diào)試面板,通過調(diào)試器協(xié)議和調(diào)試器后端交互,除此之外還有 Puppeteer[2],ndb[3] 等。

  • 消息通道:前后端通信方式,例如 websocket、usb、adb 等,本質(zhì)都是 socket 通信。

Chrome DevTools

我們可以看到,Chrome DevTools 的核心是調(diào)試器協(xié)議。

Chrome DevTools Protocol

協(xié)議按域「Domain」劃分能力,每個(gè)域下有 Method、Event 和 Types。

Method 對(duì)應(yīng) socket 通信的請(qǐng)求/響應(yīng)模式,Events 對(duì)應(yīng) socket 通信的發(fā)布/訂閱模式,Types 為交互中使用到的實(shí)體。

例如:

# https://chromedevtools./devtools-protocol/1-3/Log
Log Domain 

Provides access to log entries.

Methods

Log.clear
Log.disable
Log.enable
Log.startViolationsReport
Log.stopViolationsReport

Events

Log.entryAdded

Types

LogEntry
ViolationSetting

一個(gè)調(diào)試器后端,應(yīng)當(dāng)實(shí)現(xiàn)對(duì) Method 的響應(yīng),并在適當(dāng)?shù)臅r(shí)候發(fā)布 Event。

一個(gè)調(diào)試器前端,應(yīng)當(dāng)使用 Method 請(qǐng)求需要的數(shù)據(jù),訂閱需要的 Event。

browser_protocol & js_protocol

協(xié)議分為 browser_protocol[4]js_protocol[5] 兩種。

browser_protocol 是瀏覽器后端使用,js_protocol 是 node 后端使用。除此之外,還有對(duì)應(yīng)的 Typescript 類型定義[6]

js_protocol 只有以下四個(gè)域「Console、Schema 已廢棄」:

  • Debugger
  • Profiler
  • Runtime 「js Runtime」
  • HeapProfiler

能力比 browser_protocol 少很多,這是因?yàn)轫撁嬗邢鄬?duì)固定的工作模式,node 應(yīng)用卻千差萬別。

browser_protocol 主要有以下幾個(gè)域:

  • DOM
  • DOMDebugger
  • Emulation 「環(huán)境模擬」
  • Network
  • Page
  • Performance
  • Profiler

涉及了頁面開發(fā)的方方面面。

Chrome DevTools Frontend

devtools-frontend 即調(diào)試器前端,我們平常使用的調(diào)試面板,其源碼可以從 ChromeDevTools/devtools-frontend[7] 獲得。我們先來看一下它是怎么工作的。

項(xiàng)目結(jié)構(gòu)

ChromeDevTools/devtools-frontend[8] 下載源碼后,我們進(jìn)入 front_end 目錄,可以看到如下結(jié)構(gòu):

# tree -L 1
.
├── accessibility
├── accessibility_test_runner
│   ├── AccessibilityPaneTestRunner.js
│   └── module.json
├── animation
├── application_test_runner
├── axe_core_test_runner
...
├── input
├── inspector.html
├── inspector.js
├── inspector.json
├── network
├── network_test_runner
├── node_app.html
├── node_app.js
├── node_app.json
├── worker_app.html
├── worker_app.js
└── worker_app.json

front_end 目錄下的每一個(gè) json 文件會(huì)有一個(gè)同名的 js 文件,有的還會(huì)有一個(gè)同名的 html 文件。

它們都代表一個(gè)應(yīng)用,如 inspector.json 是其配置文件。如果此應(yīng)用有界面,則帶有 html,可以在瀏覽器中打開 html 運(yùn)行應(yīng)用。

我們可以看到熟悉的應(yīng)用,inspector、node、devtools、ndb 等等。

devtools_app 即我們常用的調(diào)試面板,如圖所示:

devtools

inspector 在 devtools_app 基礎(chǔ)上增加了頁面快照,可以實(shí)時(shí)看到頁面的變化,并且可以在頁面快照上交互,如圖所示:

inspector

以 devtools_app 為例,我們來看配置文件的語義:

// devtools_frontend/front_end/devtools_app.json
{
  'modules' : [
    { 'name''emulation''type''autostart' },
    { 'name''inspector_main''type''autostart' },
    { 'name''mobile_throttling''type''autostart' },
    ...
    { 'name''timeline' },
    { 'name''timeline_model' },
    { 'name''web_audio' },
    { 'name''media' }
  ],
  'extends''shell',
  'has_html'true
}

  • modules 表示此應(yīng)用包含的模塊,每個(gè)模塊都對(duì)應(yīng) front_end 目錄下的一個(gè)目錄。
  • extends 表示此應(yīng)用是否繼承自另外一個(gè)應(yīng)用,devtools_app 繼承自 shell 應(yīng)用,我們可以在 front_end 目錄下看到 shell.js、shell.json。
  • has_html 表示此應(yīng)用有 html 界面,即同名的 devtools_app.json。

我們?cè)賮砜匆幌履K,所有的模塊都平級(jí)放在 front_end 目錄下,不存在嵌套,每個(gè)模塊都有一個(gè) module.json 文件,表示此模塊的配置。

{
    'extensions': [
        {
            'type''view',
            'location''drawer-view'
        }
    ],
    'dependencies': [
        'elements'
    ],
    'scripts': [],
    'modules': [
        'animation.js',
        'animation-legacy.js',
        'AnimationUI.js'
    ],
    'resources': [
        'animationScreenshotPopover.css',
        'animationTimeline.css'
    ]
}

  • extensions 表示此模塊的自定義屬性。
  • dependencies 表示此模塊依賴的模塊。
  • modules 表示此模塊包括的 js 文件。
  • resources 表示此模塊包括的靜態(tài)資源,主要是 css。

之所以有這些配置,是因?yàn)?,front_end 有自己的一套模塊加載邏輯,和通常的 node 應(yīng)用和前端應(yīng)用都不一樣。

初始化

front_end 各個(gè)應(yīng)用初始化的過程類似,基本如下:

  • 從對(duì)應(yīng)的 json 文件中加載配置,并根據(jù)配置加載需要的模塊
// devtools-frontend/front_end/RuntimeInstantiator.js
export async function startApplication(appName{
  console.timeStamp('Root.Runtime.startApplication');
  const allDescriptorsByName = {};
  for (let i = 0; i < Root.allDescriptors.length; ++i) {
    const d = Root.allDescriptors[i];
    allDescriptorsByName[d['name']] = d;
  }
  if (!Root.applicationDescriptor) {
    // 加載應(yīng)用配置 <appName>.json
    let data = await RootModule.Runtime.loadResourcePromise(appName + '.json');
    Root.applicationDescriptor = JSON.parse(data);
    let descriptor = Root.applicationDescriptor;
    while (descriptor.extends) {
      // 加載父級(jí)配置直到?jīng)]有父級(jí)
      data = await RootModule.Runtime.loadResourcePromise(descriptor.extends + '.json');
      descriptor = JSON.parse(data);
      Root.applicationDescriptor.modules = descriptor.modules.concat(Root.applicationDescriptor.modules);
    }
  }
  const configuration = Root.applicationDescriptor.modules;
  const moduleJSONPromises = [];
  const coreModuleNames = [];
  for (let i = 0; i < configuration.length; ++i) {
    const descriptor = configuration[i];
    const name = descriptor['name'];
    const moduleJSON = allDescriptorsByName[name];
    // 根據(jù)每個(gè)模塊的 module.json 加載模塊
    if (moduleJSON) { 
      moduleJSONPromises.push(Promise.resolve(moduleJSON));
    } else {
      moduleJSONPromises.push(
          RootModule.Runtime.loadResourcePromise(name + '/module.json').then(JSON.parse.bind(JSON)));
    }
  }
    // ...
}
  • 實(shí)例化模塊

雖然 js 代碼都是通過 import 來引用依賴,但是 front_end 并非使用 import 來加載模塊,而是自己寫了一個(gè)模塊加載邏輯,先請(qǐng)求模塊文件,然后在根據(jù)依賴關(guān)系把代碼 eval。

// devtools-frontend/front_end/root/Runtime.js
function evaluateScript(sourceURL, scriptSource{
    loadedScripts[sourceURL] = true;
    if (!scriptSource) {
      // Do not reject, as this is normal in the hosted mode.
      console.error('Empty response arrived for script \'' + sourceURL + '\'');
      return;
    }
    self.eval(scriptSource + '\n//# sourceURL=' + sourceURL);
}
  • 模塊加載完成后,才是真正的初始化

作為調(diào)試器前端,socket 通信是不可或缺的,初始化的主要工作就是對(duì)調(diào)試器后端建立 socket 連接,準(zhǔn)備好調(diào)試協(xié)議。

對(duì)于頁面應(yīng)用來說,還需要初始化 UI,front_end 未使用任何渲染框架,全部都是原生 DOM 操作。

// devtools-frontend/front_end/main/MainImpl.js
new MainImpl(); // 初始化SDK(協(xié)議),初始化socket連接,初始化通信

應(yīng)用

遠(yuǎn)程調(diào)試

我們可以用 front_end 來實(shí)現(xiàn)遠(yuǎn)程調(diào)試頁面,例如:用戶在自己的 PC、APP 上操作頁面,開發(fā)人員在另外一臺(tái)電腦上觀察頁面、網(wǎng)絡(luò)、控制臺(tái)里發(fā)生的變化,甚至通過協(xié)議控制頁面。

開啟調(diào)試端口

不同后端打開調(diào)試端口的方式不同,以 chrome 為例:

chrome 和內(nèi)嵌的調(diào)試面板使用 Embedder channel 通信,這個(gè)消息通道不能被用來做遠(yuǎn)程調(diào)試,遠(yuǎn)程調(diào)試我們需要使用 websocket channel。

使用 websocket channel 我們還需要打開 chrome 的遠(yuǎn)程調(diào)試端口,以命令行參數(shù) remote-debugging-port 打開 chrome。

[path]/chrome.exe --remote-debugging-port=9222

或者使用腳本 devtools-frontend/scripts/hosted_mode/launch_chrome.js。

調(diào)試端口打開后,chrome 會(huì)啟動(dòng)一個(gè)內(nèi)置的 http 服務(wù),我們可以從中獲取 chrome 的基本信息,其中最重要的是各個(gè) tab 頁的 websocket 通信地址。

chrome 提供的 http 接口如下,訪問方式全部為 GET:

  • /json/protocol 獲取當(dāng)前 chrome 支持的協(xié)議,協(xié)議為 json 格式。

  • /json/list  獲取可調(diào)試的目標(biāo)列表,一般每個(gè) tab 就是一個(gè)可調(diào)試目標(biāo),可調(diào)試目標(biāo)的 webSocketDebuggerUrl 屬性就是我們需要的 websocket 通信地址。例如:

[{
   'description''',
   'devtoolsFrontendUrl''/devtools/inspector.html?ws=localhost:9222/devtools/page/8ED9DABCE2A6BD36952657AEBAA0DE02',
   'faviconUrl''https://github./favicon.ico',
   'id''8ED9DABCE2A6BD36952657AEBAA0DE02',
   'title''GitHub - Unitech/pm2: Node.js Production Process Manager with a built-in Load Balancer.',
   'type''page',
   'url''https://github.com/Unitech/pm2',
   'webSocketDebuggerUrl''ws://localhost:9222/devtools/page/8ED9DABCE2A6BD36952657AEBAA0DE02'
}]

  • /json/new  創(chuàng)建新的 tab 頁

  • /json/activate/:id 根據(jù) id 激活 tab 頁

  • /json/close/:id 根據(jù) id 關(guān)閉 tab 頁

  • /json/version 獲取瀏覽器/協(xié)議/v8/webkit 版本,例如:

{
   'Browser''Chrome/80.0.3987.149',
   'Protocol-Version''1.3',
   'User-Agent''Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36',
   'V8-Version''8.0.426.27',
   'WebKit-Version''537.36 (@5f4eb224680e5d7dca88504586e9fd951840cac6)',
   'webSocketDebuggerUrl''ws://localhost:9222/devtools/browser/ad007235-aa36-4465-beb1-70864067ea49'
}

注意:這些接口都不能跨域,可以通過服務(wù)器訪問,或者直接在瀏覽器中打開,但是不能使用 ajax 訪問。

連接

獲取到 webSocketDebuggerUrl 后,我們就可以用此連接來調(diào)試頁面。front_end 下的 devtool、inspector 等應(yīng)用均可使用。

觀察 初始化 socket 鏈接的代碼可以得知,我們需要把 webSocketDebuggerUrl 以 url 參數(shù)的形式傳給應(yīng)用,參數(shù)名為 ws。

// devtools-frontend/front_end/sdk/Connections.js
export function _createMainConnection(websocketConnectionLost{
  const wsParam = Root.Runtime.queryParam('ws');
  const wssParam = Root.Runtime.queryParam('wss');
  if (wsParam || wssParam) {
    const ws = wsParam ? `ws://${wsParam}` : `wss://${wssParam}`;
    return new WebSocketConnection(ws, websocketConnectionLost); 
  }
  if (Host.InspectorFrontendHost.InspectorFrontendHostInstance.isHostedMode()) {
    return new StubConnection();
  }
  return new MainConnection();
}

我們?cè)?front_end 目錄下啟動(dòng)靜態(tài)服務(wù)器。

serve -p 8002

然后訪問 http://localhost:8002/inspector?ws=localhost:9222/devtools/page/8ED9DABCE2A6BD36952657AEBAA0DE02

我們可以看到頁面上的一切變化都會(huì)出現(xiàn)在 inspector 的界面中。

跨域

如果前端和后端都在同一網(wǎng)段,我們使用以上方式就可以進(jìn)行調(diào)試了,但是如果前后端在不同的內(nèi)網(wǎng)內(nèi),我們?nèi)绾螌?shí)現(xiàn)遠(yuǎn)程調(diào)試?

只要我們有一臺(tái)放在公網(wǎng)的服務(wù)器就可以調(diào)試。

前端和后端都在各自的內(nèi)網(wǎng)內(nèi),因此相互之間肯定無法直接訪問。但是它們都可以訪問公網(wǎng)的服務(wù)器,并且,websocket 是可以跨域的。

因此我們可以通過兩次轉(zhuǎn)發(fā),讓不同內(nèi)網(wǎng)的前端和后端交互,具體步驟如下:

  • 創(chuàng)建一個(gè)轉(zhuǎn)發(fā)用的 websocket 服務(wù),放在公網(wǎng)。

  • 我們?cè)诒徽{(diào)試的頁面中增加一個(gè)自定義的 launcher.js,對(duì)公網(wǎng)的 websocket 服務(wù)建立連接,把頁面的基本信息傳遞給服務(wù)器,同時(shí)通過 json/list 接口找出自身的 webSocketDebuggerUrl 建立連接。

注意:因?yàn)?json/list 是 http 接口,無法跨域,這一步必須手動(dòng)獲取,然后把 webSocketDebuggerUrl 放在 url 參數(shù)上傳給 launcher.js

手動(dòng)獲取 webSocketDebuggerUrl
  • 把 front_end 頁面 url 的 ws 參數(shù)改為公網(wǎng)的 websocket 服務(wù)。

這樣,我們的 socket 鏈路上有了四個(gè)節(jié)點(diǎn),分別是:

  • front_end(調(diào)試器前端)
  • 公網(wǎng)服務(wù)器(server)
  • laucher.js
  • debugger(調(diào)試器后端)

server 和 laucher 完全作為轉(zhuǎn)發(fā)器,轉(zhuǎn)發(fā)兩邊傳來的信息,即可實(shí)現(xiàn) front_end 到 debugger 的交互。

注意:如果 front_end 請(qǐng)求了 Network.enable, 就不能把 laucher.js 所在的頁面作為調(diào)試頁面,因?yàn)?laucher.js 收到 debugger 傳來的數(shù)據(jù)會(huì)觸發(fā) Network.webSocketFrameReceived 推送,這個(gè)推送本身又會(huì)觸發(fā) Network.webSocketFrameReceived ,造成無限循環(huán)。處理方式有兩種,一是攔截掉 Network.enable 請(qǐng)求,這樣會(huì)取消掉所有的 Network 的推送。二是不把 laucher.js 所在的頁面作為調(diào)試頁面,僅作數(shù)據(jù)中轉(zhuǎn)用。

遠(yuǎn)程調(diào)試

websocket 服務(wù)代碼示例:

// server.js
var WebSocketServer = require('websocket').server;
var http = require('http');
var server = http.createServer(function(request, response{
    response.writeHead(404);
    response.end();
});
server.listen(3232function({
    console.log((new Date()) + ' Server is listening on port 3232');
});
wsServer = new WebSocketServer({
    httpServer: server
});
var frontendConnection;
var debugConnection;

wsServer.on('request'async function(request{
    var requestedProtocols = request.requestedProtocols;
    if(requestedProtocols.indexOf('frontend') != -1){  // 處理來自調(diào)試器前端的請(qǐng)求
        frontendConnection = request.accept('frontend', request.origin);
        frontendConnection.on('message'function(message{
            if (message.type === 'utf8') {
                // 把調(diào)試器前端的請(qǐng)求直接轉(zhuǎn)發(fā)給被調(diào)試頁面
                if(debugConnection){
                    debugConnection.sendUTF(message.utf8Data)
                }else{
                    frontendConnection.sendUTF(JSON.stringify({msg:'調(diào)試器后端未準(zhǔn)備好,先打開被調(diào)試的頁面'}))
                }  
            }
        })
        frontendConnection.on('close'function(reasonCode, description{
            console.log('frontendConnection disconnected.');
        });
    }
    if(requestedProtocols.indexOf('remote-debug') != -1){ // 處理來自被調(diào)試頁面的請(qǐng)求
        debugConnection = request.accept('remote-debug', request.origin);
        debugConnection.on('message'function(message{
            if (message.type === 'utf8') {
                var feed = JSON.parse(message.utf8Data);
                if(feed.type == 'remote_debug_page'){   // 確認(rèn)連接
                    debugConnection.sendUTF(JSON.stringify({'type':'start_debug'}));
                }else if(feed.type == 'start_debug_ready'){
                    // 被調(diào)試頁面已連接好
                } else{
                    // 把被調(diào)試頁面的數(shù)據(jù)全部轉(zhuǎn)發(fā)給調(diào)試器前端
                    if(frontendConnection){
                        frontendConnection.sendUTF(message.utf8Data)
                    }else{
                        console.log('無法轉(zhuǎn)發(fā)給frontend,沒有建立連接')
                    }
                }                
            }
        });
        debugConnection.on('close'function(reasonCode, description{
            console.log((new Date()) + ' Peer remote' + debugConnection.remoteAddress + ' disconnected.');
        });
    }
});

laucher.js 代碼示例:


var host = 'localhost:3232'
var ws = new WebSocket(`ws://${host}`,'remote-debug');       
var search = location.search.slice(1);
var urlParams = {};
search.split('&').forEach(s=>{
    var pair = s.split('=');
    if(pair.length == 2){
        urlParams[pair[0]] = pair[1]
    }
})
ws.onopen = function({
    ws.send(JSON.stringify({type:'remote_debug_page',url:location.href}))
};
ws.onmessage = function (evt)  
    var feed = JSON.parse(received_msg);
    if(feed.type == 'start_debug') {
        // 連接到 webSocketDebuggerUrl
        var debugWS = new WebSocket(`ws://${urlParams.ws}`);  
        debugWS.onopen = function({  
            ws.send(JSON.stringify({type:'start_debug_ready'})); // 確認(rèn)可以開始調(diào)試
            ws.onmessage = function (evt// 轉(zhuǎn)發(fā)到 debugger
                debugWS.send(evt.data);
            }
            ws.onclose = function (evt{
                debugWS.close()
            }
        }
        debugWS.onmessage = function (evt)  
            ws.send(evt.data); // 轉(zhuǎn)發(fā)到 server
        }
        debugWS.onclose = function(
            ws.send(JSON.stringify({type:'remote_page_lost',url:location.href}))
        };
    }
};
ws.onclose = function(
    console.log('連接已關(guān)閉...'); 
};

回放

使用 inspector 時(shí)我們可以發(fā)現(xiàn),只要開啟了 Page.enable 和 Network.enable,就可以一直接收到調(diào)試器后端推送的頁面快照和網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)。

我們可以略微改造一下 server.js 的代碼,把所有收到的推送數(shù)據(jù)打時(shí)間戳后保存到一個(gè)文件,持久化存儲(chǔ)起來。

if (message.type === 'utf8') {
    var feed = JSON.parse(message.utf8Data);
    if(feed.type == 'remote_debug_page'){  
        debugConnection.sendUTF(JSON.stringify({'type':'start_debug'}));
    }else if(feed.type == 'start_debug_ready'){
        writeStream = fs.createWriteStream(saveFilePath,{flags:'as',encoding'utf8'});
    } else{
        // 全部轉(zhuǎn)發(fā)給 frontendConnection
        if(frontendConnection){
            frontendConnection.sendUTF(message.utf8Data)
        }else{
            console.log('無法轉(zhuǎn)發(fā)給frontend,沒有建立連接')
        }
        // 保存數(shù)據(jù)到文件
        if(feed.method)writeStream.write(message.utf8Data+'\n'
    }                
}

然后我們給 websocket 服務(wù)增加一個(gè)協(xié)議類型,和 inspector 建立連接后,讀取文件中保存的數(shù)據(jù),按照時(shí)間戳上的時(shí)間間隔推送數(shù)據(jù)。

這樣就實(shí)現(xiàn)了回放功能,把之前調(diào)試時(shí)的現(xiàn)場重現(xiàn)一遍。

if(requestedProtocols.indexOf('feedback') != -1){
    feedbackConnection = request.accept('feedback', request.origin);
    feedbackConnection.on('message'function(message{
        // 忽略來的消息
    })
    const fileStream = fs.createReadStream(saveFilePath);
    const rl = readline.createInterface({
        input: fileStream,
        crlfDelayInfinity
    });
    for await (const line of rl) {  // 逐行讀取數(shù)據(jù)
        feedbackConnection.sendUTF(line)
        rl.pause();
        setTimeout(_=>{rl.resume()},1000)
    }
    feedbackConnection.on('close'function(reasonCode, description{
        console.log('feedbackConnection disconnected.');
    });
}

甚至可以更進(jìn)一步,創(chuàng)建一個(gè) websocket 服務(wù)作為調(diào)試器前端,模擬 inspector 發(fā)送請(qǐng)求的邏輯并保存推送數(shù)據(jù)到文件,這樣就實(shí)現(xiàn)了一個(gè)錄制服務(wù)器,可以隨時(shí)錄制調(diào)試現(xiàn)場,然后在需要的時(shí)候播放,因?yàn)橛涗浟藭r(shí)間戳,pause、seek、resume、stop 都可以實(shí)現(xiàn)。

devtools-frontend 的調(diào)用方式

一般來說,我們習(xí)慣用 require/import 的方式調(diào)用模塊,devtools-frontend 雖然也是個(gè) npm 包 ,chrome-devtools-frontend[9],但是卻不方便用 require/import 的方式直接引用。

主要是因?yàn)橹八龅?front_end 應(yīng)用有自己的一套模塊加載邏輯,應(yīng)用的 js、json 配置文件必須在同一個(gè)目錄下,模塊也必須在同一個(gè)目錄下,否則就會(huì)出現(xiàn)路徑錯(cuò)誤。

如果僅使用 front_end 的某個(gè)模塊,還可以用 require/import 來引用。

如果想創(chuàng)建一個(gè)新的應(yīng)用,最好是把整個(gè) front_end 復(fù)制過來修改。

Chrome DevTools Extensions

如果想在 chrome 內(nèi)嵌的調(diào)試面板中增加自定義的能力,可以用 chrome 插件的方式實(shí)現(xiàn),例如vue-devtools[10]。

參考資料

ChromeDevTools/awesome-chrome-devtools[11]

ChromeDevTools/devtools-protocol[12]

參考資料

[1]

devtools-protocol: https://github.com/chromedevtools/devtools-protocol

[2]

Puppeteer: https://github.com/GoogleChrome/puppeteer/

[3]

ndb: https://github.com/GoogleChromeLabs/ndb

[4]

browser_protocol: https://github.com/ChromeDevTools/devtools-protocol/blob/master/json/browser_protocol.json

[5]

js_protocol: https://github.com/ChromeDevTools/devtools-protocol/blob/master/json/js_protocol.json

[6]

Typescript 類型定義: https://github.com/ChromeDevTools/devtools-protocol/tree/master/types

[7]

ChromeDevTools/devtools-frontend: https://github.com/ChromeDevTools/devtools-frontend

[8]

ChromeDevTools/devtools-frontend: https://github.com/ChromeDevTools/devtools-frontend

[9]

chrome-devtools-frontend: https://www./package/chrome-devtools-frontend

[10]

vue-devtools: https://github.com/vuejs/vue-devtools

[11]

ChromeDevTools/awesome-chrome-devtools: https://github.com/ChromeDevTools/awesome-chrome-devtools

[12]

ChromeDevTools/devtools-protocol: https://github.com/chromedevtools/devtools-protocol

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多