Deno's deno compile command can compile TypeScript/JavaScript code into standalone executable files, making distribution and deployment much simpler. This feature is particularly useful for creating command-line tools and standalone applications.
deno compile Overview
deno compile packages a Deno script and all its dependencies into a single executable file, eliminating the need to install Deno on the target machine.
Basic Usage
1. Simple Compilation
bash# Compile to executable deno compile app.ts # Default output filename matches input file (without extension) # app.ts → app (Linux/Mac) or app.exe (Windows)
2. Specify Output Filename
bash# Specify output filename deno compile --output=myapp app.ts # Windows deno compile --output=myapp.exe app.ts
3. Specify Target Platform
bash# Compile for Linux executable deno compile --target=x86_64-unknown-linux-gnu app.ts # Compile for macOS executable deno compile --target=x86_64-apple-darwin app.ts deno compile --target=aarch64-apple-darwin app.ts # Apple Silicon # Compile for Windows executable deno compile --target=x86_64-pc-windows-msvc app.ts
4. Include Permissions
bash# Include permissions at compile time, no need to specify at runtime deno compile --allow-net --allow-read app.ts # Include all permissions deno compile --allow-all app.ts
5. Set Environment Variables
bash# Set environment variables at compile time deno compile --env=API_KEY=secret app.ts
Practical Application Examples
1. Command-line Tool
typescript// cli.ts #!/usr/bin/env -S deno run const name = Deno.args[0] || "World"; const verbose = Deno.args.includes("--verbose"); if (verbose) { console.log("Verbose mode enabled"); } console.log(`Hello, ${name}!`); // File operation example const filename = Deno.args[1]; if (filename) { try { const content = await Deno.readTextFile(filename); console.log(`File content: ${content}`); } catch (error) { console.error(`Error reading file: ${error.message}`); } }
Compile:
bashdeno compile --allow-read --output=hello cli.ts ./hello Deno --verbose
2. Web Server
typescript// server.ts import { serve } from "https://deno.land/std@0.208.0/http/server.ts"; const PORT = Deno.env.get("PORT") || "8000"; const handler = async (req: Request): Promise<Response> => { const url = new URL(req.url); if (url.pathname === "/") { return new Response("Hello from Deno server!", { headers: { "Content-Type": "text/plain" }, }); } if (url.pathname === "/health") { return new Response(JSON.stringify({ status: "ok" }), { headers: { "Content-Type": "application/json" }, }); } return new Response("Not Found", { status: 404 }); }; console.log(`Server running on port ${PORT}`); await serve(handler, { port: parseInt(PORT) });
Compile:
bashdeno compile --allow-net --allow-env --output=server server.ts ./server
3. File Processing Tool
typescript// file-processor.ts import { walk } from "https://deno.land/std@0.208.0/fs/walk.ts"; const directory = Deno.args[0] || "."; const pattern = Deno.args[1]; console.log(`Scanning directory: ${directory}`); if (pattern) { console.log(`Pattern: ${pattern}`); } let count = 0; for await (const entry of walk(directory)) { if (entry.isFile) { if (!pattern || entry.name.includes(pattern)) { console.log(`Found: ${entry.path}`); count++; } } } console.log(`Total files found: ${count}`);
Compile:
bashdeno compile --allow-read --output=scan file-processor.ts ./scan ./src .ts
4. API Client
typescript// api-client.ts const API_URL = Deno.env.get("API_URL") || "https://api.example.com"; const API_KEY = Deno.env.get("API_KEY"); if (!API_KEY) { console.error("API_KEY environment variable is required"); Deno.exit(1); } async function fetchData(endpoint: string): Promise<any> { const response = await fetch(`${API_URL}${endpoint}`, { headers: { "Authorization": `Bearer ${API_KEY}`, "Content-Type": "application/json", }, }); if (!response.ok) { throw new Error(`API request failed: ${response.status}`); } return response.json(); } const endpoint = Deno.args[0] || "/users"; console.log(`Fetching data from: ${API_URL}${endpoint}`); try { const data = await fetchData(endpoint); console.log(JSON.stringify(data, null, 2)); } catch (error) { console.error(`Error: ${error.message}`); Deno.exit(1); }
Compile:
bashdeno compile --allow-net --allow-env --output=api-client api-client.ts API_KEY=your_key ./api-client /users
Advanced Usage
1. Cross-compilation
Compile Linux executable on macOS:
bashdeno compile --target=x86_64-unknown-linux-gnu --output=myapp-linux app.ts
2. Compress Executable
Use upx to compress compiled executable:
bash# Install upx brew install upx # macOS apt install upx # Linux # Compress upx --best myapp
3. Add Icon (Windows Only)
bash# Add icon for Windows executable deno compile --icon=icon.ico --output=myapp.exe app.ts
4. Set Metadata
bash# Set executable metadata deno compile \ --output=myapp \ --app-name="My Application" \ --app-version="1.0.0" \ app.ts
Deployment Scenarios
1. Docker Deployment
dockerfile# Multi-stage build FROM denoland/deno:1.38.0 AS builder WORKDIR /app COPY . . RUN deno compile --allow-net --allow-read --output=app server.ts FROM debian:bullseye-slim WORKDIR /app COPY /app/app . EXPOSE 8000 CMD ["./app"]
2. Cloud Function Deployment
typescript// cloud-function.ts export async function handler(event: any): Promise<any> { const name = event.name || "World"; return { statusCode: 200, body: JSON.stringify({ message: `Hello, ${name}!` }), }; }
Compile:
bashdeno compile --output=handler cloud-function.ts
3. Standalone Application Distribution
bash# Compile for different platforms deno compile --target=x86_64-unknown-linux-gnu --output=myapp-linux app.ts deno compile --target=x86_64-apple-darwin --output=myapp-macos app.ts deno compile --target=x86_64-pc-windows-msvc --output=myapp-windows.exe app.ts # Create release package mkdir release cp myapp-linux release/ cp myapp-macos release/ cp myapp-windows.exe release/
Limitations and Considerations
1. File Size
Compiled executables are typically large (about 50-100 MB) because they include the entire Deno runtime and V8 engine.
2. Dynamic Imports
Dynamically imported modules are downloaded at runtime and are not included in the executable:
typescript// This module won't be packaged const module = await import("https://example.com/module.ts");
3. Native Modules
Native modules using FFI require ensuring the target platform has the corresponding libraries.
4. Permissions
Permissions specified at compile time are effective at runtime and cannot be changed at runtime.
Best Practices
- Specify target platform: Clearly specify compilation target to avoid compatibility issues
- Minimum permissions: Only grant necessary permissions
- Test compiled result: Test executable on target platform
- Version control: Record compilation command and Deno version
- Document: Explain how to compile and run in README
- Use CI/CD: Automate compilation and release process
Comparison with Other Tools
| Feature | deno compile | pkg | nexe |
|---|---|---|---|
| Native Support | Yes | No | No |
| Cross-platform | Yes | Yes | Yes |
| File Size | Large | Medium | Medium |
| Performance | Good | Good | Good |
| Maintainability | Official Support | Community Maintained | Community Maintained |
Troubleshooting
1. Compilation Failure
bash# View detailed error information deno compile --log-level=debug app.ts
2. Runtime Errors
bash# Check permissions deno compile --allow-net --allow-read app.ts # Check environment variables API_KEY=secret ./app
3. Compatibility Issues
bash# Check target platform deno compile --target=x86_64-unknown-linux-gnu app.ts # Test with Docker docker run --rm -v $(pwd):/app debian:bullseye-slim ./app
deno compile provides a simple and powerful way to distribute Deno applications, especially suitable for scenarios requiring standalone deployment. By using this feature properly, you can greatly simplify the deployment and distribution process of applications.