Skip to content

Commit

Permalink
Merge pull request #12 from author-more/build/dev-server-app
Browse files Browse the repository at this point in the history
build: add dev server for app
  • Loading branch information
Belar authored Sep 5, 2024
2 parents d3b4186 + 305bb2d commit 8e44db5
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 11 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ You need to have an environment with Node.js installed to work on the plugin.
6. Add a new plugin with the URL `http://localhost:4173/manifest.json`.
7. Use the plugin manager to open the plugin.

Currently the development setup is using the production build in watch mode. This means hot module replacement is not available and you need to reload the plugin manually after making changes.
Currently the development setup is using the production build in watch mode for the plugin script. This means development tools like source maps, hot module replacement are not available, and you need to reload the plugin manually after making changes in the script. The app part is running a development server.

## Credits

Expand Down
106 changes: 106 additions & 0 deletions bin/serve.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { createServer, request, IncomingMessage, ServerResponse } from "http";
import { readFileSync } from "fs";
import { join } from "path";
import { Duplex } from "stream";

const ALLOWED_ORIGIN = "https://early.penpot.dev";
const PROXY_TARGET_ADDRESS = "http://localhost:5173";
const LISTEN_PORT = 3000;
const PLUGIN_FILENAME = "plugin.js";

const __dirname = import.meta.dirname;
const proxyTargetUrl = new URL(PROXY_TARGET_ADDRESS);

const server = createServer((req, res) => {
if (req.url === `/${PLUGIN_FILENAME}`) {
const filePath = join(__dirname, "../dist", PLUGIN_FILENAME);

try {
const data = readFileSync(filePath);

res.writeHead(200, {
"Content-Type": "application/javascript",
"Access-Control-Allow-Origin": ALLOWED_ORIGIN,
});
res.end(data);
} catch (e) {
console.error("Failed to read plugin file: ", e);

res.writeHead(500, {
"Content-Type": "text/plain",
"Access-Control-Allow-Origin": ALLOWED_ORIGIN,
});
res.end("Internal Server Error");
}
} else {
proxyHttpRequest(req, res);
}
});

server.on("upgrade", (req, socket) => {
proxyWebSocketRequest(req, socket);
});

server.listen(LISTEN_PORT, () => {
console.log(`Server is running on http://localhost:${LISTEN_PORT}`);
});

const proxyHttpRequest = (req: IncomingMessage, res: ServerResponse) => {
const options = {
hostname: proxyTargetUrl.hostname,
port: proxyTargetUrl.port,
path: req.url,
method: req.method,
headers: req.headers,
};

const proxyReq = request(options, (proxyRes) => {
res.writeHead(proxyRes.statusCode!, proxyRes.headers);
proxyRes.pipe(res, { end: true });
});

proxyReq.on("error", (e) => {
console.error("[HTTP] Proxy request failed: ", e);

res.writeHead(500, { "Content-Type": "text/plain" });
res.end("Internal Server Error");
});

req.pipe(proxyReq, { end: true });
};

const proxyWebSocketRequest = (req: IncomingMessage, socket: Duplex) => {
const options = {
hostname: proxyTargetUrl.hostname,
port: proxyTargetUrl.port,
path: req.url,
headers: req.headers,
};

const proxyReq = request(options);

proxyReq.on("upgrade", (proxyRes, proxySocket, proxyHead) => {
// Include headers from the proxy response in the client response
const httpHeaderString = `HTTP/${proxyRes.httpVersion} ${proxyRes.statusCode} ${proxyRes.statusMessage}`;
const headersString = Object.entries(proxyRes.headers)
.map(
([key, value]) =>
`${key}: ${Array.isArray(value) ? value.join(", ") : value}`,
)
.join("\r\n");

socket.write(httpHeaderString + "\r\n" + headersString + "\r\n\r\n");
proxySocket.write(proxyHead);

// Create two-way traffic between connections
proxySocket.pipe(socket).pipe(proxySocket);
});

proxyReq.on("error", (e) => {
console.error("[WebSocket] Proxy request failed: ", e);

socket.end();
});

proxyReq.end();
};
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite build --watch & vite preview",
"dev": "vite build --watch --config ./vite.config.dev-plugin.ts & vite dev --config ./vite.config.dev-app.ts & tsx ./bin/serve.js",
"build": "tsc -b && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"format": "prettier . --write",
Expand All @@ -26,7 +26,7 @@
"devDependencies": {
"@penpot/plugin-types": "^0.10.0",
"@playwright/test": "^1.45.3",
"@types/node": "^22.0.0",
"@types/node": "^22.5.3",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.18.0",
Expand Down
7 changes: 7 additions & 0 deletions vite.config.dev-app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";

// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
});
17 changes: 17 additions & 0 deletions vite.config.dev-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";

// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
build: {
rollupOptions: {
input: {
plugin: "src/plugin.ts",
},
output: {
entryFileNames: "[name].js",
},
},
},
});

0 comments on commit 8e44db5

Please sign in to comment.