mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-02 02:08:24 +08:00
This CL adds the `ElementsBreadcrumbs` component and documentation to the component server. Important: this CL *does not add the component to the UI*. That will be done in a follow up CL once this has shipped and we have done some other infrastructure work that is currently blocking components going into production. This is also why the file is called `NewElementsBreadcrumbs`, to avoid a nameclash with `ElementsBreadcrumbs.js`. In the CL that puts this component into production I will change the filename as the clashing won't be an issue. Change-Id: I8039d314ba02c834c3f8ec26f9bdb547858aa2dd Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2236405 Commit-Queue: Jack Franklin <jacktfranklin@chromium.org> Reviewed-by: Paul Lewis <aerotwist@chromium.org> Reviewed-by: Tim van der Lippe <tvanderlippe@chromium.org>
175 lines
5.5 KiB
JavaScript
175 lines
5.5 KiB
JavaScript
// Copyright (c) 2020 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
const fs = require('fs');
|
|
const http = require('http');
|
|
const path = require('path');
|
|
const parseURL = require('url').parse;
|
|
const {argv} = require('yargs');
|
|
|
|
const serverPort = parseInt(process.env.PORT, 10) || 8090;
|
|
|
|
const target = argv.target || 'Default';
|
|
const devtoolsFrontendFolder = path.resolve(path.join(__dirname, '..', '..', 'out', target, 'gen', 'front_end'));
|
|
|
|
if (!fs.existsSync(devtoolsFrontendFolder)) {
|
|
console.error(`ERROR: Generated front_end folder (${devtoolsFrontendFolder}) does not exist.`);
|
|
console.log(
|
|
'The components server works from the built Ninja output; you may need to run Ninja to update your built DevTools.');
|
|
console.log('If you build to a target other than default, you need to pass --target=X as an argument');
|
|
process.exit(1);
|
|
}
|
|
|
|
http.createServer(requestHandler).listen(serverPort);
|
|
console.log(`Started components server at http://localhost:${serverPort}\n`);
|
|
|
|
function createComponentIndexFile(componentPath, componentExamples) {
|
|
const componentName = componentPath.replace('/', '').replace('_', ' ');
|
|
// clang-format off
|
|
return `<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width" />
|
|
<title>DevTools component: ${componentName}</title>
|
|
<style>
|
|
h1 { text-transform: capitalize; }
|
|
|
|
.example {
|
|
padding: 5px;
|
|
margin: 10px;
|
|
}
|
|
iframe { display: block; width: 100%; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>${componentName}</h1>
|
|
${componentExamples.map(example => {
|
|
const fullPath = path.join('component_docs', componentPath, example);
|
|
return `<div class="example">
|
|
<h3><a href="${fullPath}">${example}</a></h3>
|
|
<iframe src="${fullPath}"></iframe>
|
|
</div>`;
|
|
}).join('\n')}
|
|
</body>
|
|
</html>`;
|
|
// clang-format on
|
|
}
|
|
|
|
function createServerIndexFile(componentNames) {
|
|
// clang-format off
|
|
return `<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width" />
|
|
<title>DevTools components</title>
|
|
<style>
|
|
a { text-transform: capitalize; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>DevTools components</h1>
|
|
<ul>
|
|
${componentNames.map(name => {
|
|
return `<li><a href='/${name}'>${name}</a></li>`;
|
|
}).join('\n')}
|
|
</ul>
|
|
</body>
|
|
</html>`;
|
|
// clang-format on
|
|
}
|
|
|
|
async function getExamplesForPath(filePath) {
|
|
const componentDirectory = path.join(devtoolsFrontendFolder, 'component_docs', filePath);
|
|
const contents = await fs.promises.readdir(componentDirectory);
|
|
|
|
return createComponentIndexFile(filePath, contents);
|
|
}
|
|
|
|
function respondWithHtml(response, html) {
|
|
response.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
response.writeHead(200);
|
|
response.write(html, 'utf8');
|
|
response.end();
|
|
}
|
|
|
|
function send404(response, message) {
|
|
response.writeHead(404);
|
|
response.write(message, 'utf8');
|
|
response.end();
|
|
}
|
|
|
|
async function checkFileExists(filePath) {
|
|
try {
|
|
const errorsAccessingFile = await fs.promises.access(filePath, fs.constants.R_OK);
|
|
return !errorsAccessingFile;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function requestHandler(request, response) {
|
|
const filePath = parseURL(request.url).pathname;
|
|
|
|
if (filePath === '/favicon.ico') {
|
|
send404(response, '404, no favicon');
|
|
return;
|
|
}
|
|
|
|
if (filePath === '/' || filePath === '/index.html') {
|
|
const components = await fs.promises.readdir(path.join(devtoolsFrontendFolder, 'component_docs'));
|
|
const html = createServerIndexFile(components);
|
|
respondWithHtml(response, html);
|
|
} else if (path.extname(filePath) === '') {
|
|
// This means it's a component path like /breadcrumbs.
|
|
const componentHtml = await getExamplesForPath(filePath);
|
|
respondWithHtml(response, componentHtml);
|
|
} else {
|
|
// This means it's an asset like a JS file.
|
|
const fullPath = path.join(devtoolsFrontendFolder, filePath);
|
|
|
|
if (!fullPath.startsWith(devtoolsFrontendFolder)) {
|
|
console.error(`Path ${fullPath} is outside the DevTools Frontend root dir.`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const fileExists = await checkFileExists(fullPath);
|
|
|
|
if (!fileExists) {
|
|
send404(response, '404, File not found');
|
|
return;
|
|
}
|
|
|
|
let encoding = 'utf8';
|
|
if (fullPath.endsWith('.wasm') || fullPath.endsWith('.png') || fullPath.endsWith('.jpg')) {
|
|
encoding = 'binary';
|
|
}
|
|
|
|
const fileContents = await fs.promises.readFile(fullPath, encoding);
|
|
|
|
encoding = 'utf8';
|
|
if (fullPath.endsWith('.js')) {
|
|
response.setHeader('Content-Type', 'text/javascript; charset=utf-8');
|
|
} else if (fullPath.endsWith('.css')) {
|
|
response.setHeader('Content-Type', 'text/css; charset=utf-8');
|
|
} else if (fullPath.endsWith('.wasm')) {
|
|
response.setHeader('Content-Type', 'application/wasm');
|
|
encoding = 'binary';
|
|
} else if (fullPath.endsWith('.svg')) {
|
|
response.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
|
} else if (fullPath.endsWith('.png')) {
|
|
response.setHeader('Content-Type', 'image/png');
|
|
encoding = 'binary';
|
|
} else if (fullPath.endsWith('.jpg')) {
|
|
response.setHeader('Content-Type', 'image/jpg');
|
|
encoding = 'binary';
|
|
}
|
|
|
|
response.writeHead(200);
|
|
response.write(fileContents, encoding);
|
|
response.end();
|
|
}
|
|
}
|