blob: 7dfb0b9f6f58818a4f09e96b352c390bd19bb137 [file] [log] [blame]
Jack Franklin1557a1c2020-06-08 15:22:13 +01001// Copyright (c) 2020 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5const fs = require('fs');
6const http = require('http');
7const path = require('path');
8const parseURL = require('url').parse;
9const {argv} = require('yargs');
10
11const serverPort = parseInt(process.env.PORT, 10) || 8090;
12
13const target = argv.target || 'Default';
14const devtoolsFrontendFolder = path.resolve(path.join(__dirname, '..', '..', 'out', target, 'gen', 'front_end'));
15
16if (!fs.existsSync(devtoolsFrontendFolder)) {
17 console.error(`ERROR: Generated front_end folder (${devtoolsFrontendFolder}) does not exist.`);
18 console.log(
19 'The components server works from the built Ninja output; you may need to run Ninja to update your built DevTools.');
20 console.log('If you build to a target other than default, you need to pass --target=X as an argument');
21 process.exit(1);
22}
23
24http.createServer(requestHandler).listen(serverPort);
25console.log(`Started components server at http://localhost:${serverPort}\n`);
26
27function createComponentIndexFile(componentPath, componentExamples) {
28 const componentName = componentPath.replace('/', '');
29 // clang-format off
30 return `<!DOCTYPE html>
31 <html>
32 <head>
33 <meta charset="UTF-8" />
34 <meta name="viewport" content="width=device-width" />
35 <title>DevTools component: ${componentName}</title>
36 <style>
37 h1 { text-transform: capitalize; }
38
39 .example {
40 padding: 5px;
41 margin: 10px;
42 }
43 iframe { display: block; width: 100%; }
44 </style>
45 </head>
46 <body>
47 <h1>${componentName}</h1>
48 ${componentExamples.map(example => {
49 const fullPath = path.join('component_docs', componentName, example);
50 return `<div class="example">
51 <h3><a href="${fullPath}">${example}</a></h3>
52 <iframe src="${fullPath}"></iframe>
53 </div>`;
54 }).join('\n')}
55 </body>
56 </html>`;
57 // clang-format on
58}
59
60function createServerIndexFile(componentNames) {
61 // clang-format off
62 return `<!DOCTYPE html>
63 <html>
64 <head>
65 <meta charset="UTF-8" />
66 <meta name="viewport" content="width=device-width" />
67 <title>DevTools components</title>
68 <style>
69 a { text-transform: capitalize; }
70 </style>
71 </head>
72 <body>
73 <h1>DevTools components</h1>
74 <ul>
75 ${componentNames.map(name => {
76 return `<li><a href='/${name}'>${name}</a></li>`;
77 }).join('\n')}
78 </ul>
79 </body>
80 </html>`;
81 // clang-format on
82}
83
84async function getExamplesForPath(filePath) {
85 const componentDirectory = path.join(devtoolsFrontendFolder, 'component_docs', filePath);
86 const contents = await fs.promises.readdir(componentDirectory);
87
88 return createComponentIndexFile(filePath, contents);
89}
90
91function respondWithHtml(response, html) {
92 response.setHeader('Content-Type', 'text/html; charset=utf-8');
93 response.writeHead(200);
94 response.write(html, 'utf8');
95 response.end();
96}
97
98function send404(response, message) {
99 response.writeHead(404);
100 response.write(message, 'utf8');
101 response.end();
102}
103
104async function requestHandler(request, response) {
105 const filePath = parseURL(request.url).pathname;
106
107 if (filePath === '/favicon.ico') {
108 send404(response, '404, no favicon');
109 return;
110 }
111
112 if (filePath === '/' || filePath === '/index.html') {
113 const components = await fs.promises.readdir(path.join(devtoolsFrontendFolder, 'component_docs'));
114 const html = createServerIndexFile(components);
115 respondWithHtml(response, html);
116 } else if (path.extname(filePath) === '') {
117 // This means it's a component path like /breadcrumbs.
118 const componentHtml = await getExamplesForPath(filePath);
119 respondWithHtml(response, componentHtml);
120 } else {
121 // This means it's an asset like a JS file.
122 const fullPath = path.join(devtoolsFrontendFolder, filePath);
123
124 if (!fullPath.startsWith(devtoolsFrontendFolder)) {
125 console.error(`Path ${fullPath} is outside the DevTools Frontend root dir.`);
126 process.exit(1);
127 }
128 const errorsAccesingFile = await fs.promises.access(fullPath, fs.constants.R_OK);
129
130 if (errorsAccesingFile) {
131 console.error(`File ${fullPath} does not exist.`);
132 send404(response, '404, File not found');
133 return;
134 }
135
136 let encoding = 'utf8';
137 if (fullPath.endsWith('.wasm') || fullPath.endsWith('.png') || fullPath.endsWith('.jpg')) {
138 encoding = 'binary';
139 }
140
141 const fileContents = await fs.promises.readFile(fullPath, encoding);
142
143 encoding = 'utf8';
144 if (fullPath.endsWith('.js')) {
145 response.setHeader('Content-Type', 'text/javascript; charset=utf-8');
146 } else if (fullPath.endsWith('.css')) {
147 response.setHeader('Content-Type', 'text/css; charset=utf-8');
148 } else if (fullPath.endsWith('.wasm')) {
149 response.setHeader('Content-Type', 'application/wasm');
150 encoding = 'binary';
151 } else if (fullPath.endsWith('.svg')) {
152 response.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
153 } else if (fullPath.endsWith('.png')) {
154 response.setHeader('Content-Type', 'image/png');
155 encoding = 'binary';
156 } else if (fullPath.endsWith('.jpg')) {
157 response.setHeader('Content-Type', 'image/jpg');
158 encoding = 'binary';
159 }
160
161 response.writeHead(200);
162 response.write(fileContents, encoding);
163 response.end();
164 }
165}