denoland / deno

A modern runtime for JavaScript and TypeScript.
https://deno.com
MIT License
97.82k stars 5.38k forks source link

Support server without entrypoint for `deno serve` #26040

Open vrugtehagel opened 1 month ago

vrugtehagel commented 1 month ago

For local development I often need a simple server that just spits out the files on my system as they are. Some runtimes (for other languages) provide such functionality; usually I end up using php -S localhost:8080 which does just that. Deno has a serve command, which is exciting but doesn't quite reach the use-case I need, since it requires an entry point that defines how the server should behave. I think it'd be nice to have a default server that responds with the files on the system, inferring content type from the file extensions.

This could look like a plain deno serve (without entry point) but to protect users from mistakes like accidentally forgetting the entry point, perhaps an additional flag would be good, e.g. deno serve --plain (or --default, or --passthrough, etcetera).

Is this something the Deno team would be interested in?

lowlighter commented 1 month ago

While a bit more verbose, you can use deno run -A jsr:@std/http/file-server to achieve the same I think

satyarohith commented 1 month ago

As lowlighter suggested, we have https://jsr.io/@std/http/doc/file-server for your use case. You can also install it to your path using the below commands:

> # install
> deno install --allow-net --allow-read --allow-sys --global jsr:@std/http/file-server
> # start server
> file-server
> # show help
> file-server --help
vrugtehagel commented 1 month ago

It's a bit of a bummer that you'd need an internet connection to go that route even if you have deno installed, but I guess it does suit the use-case. Thanks, people!

satyarohith commented 1 month ago

@vrugtehagel, you don't need internet connection after the first run, or once file-server is installed.

bartlomieju commented 1 month ago

I think we should really consider adding deno serve --fs, it's very convenient.

vrugtehagel commented 1 month ago

Not sure if this is helpful, but here's a snippet that I wrote to boot up a no-dependency server with Deno:

const root = './src'

const mimeTypes = {
    css: 'text/css',
    html: 'text/html',
    js: 'text/javascript',
    // …
    txt: 'text/plain',
}

const handler = async request => {
    const {pathname} = new URL(request.url)
    const filepath = `${root}${pathname}`.replace(/\/$/, '/index.html')
    const url = new URL(filepath, import.meta.url)
    const response = await fetch(url).catch(() => null)
    if(response == null) return new Response('Not found', {status: 404})
    if(response.status != 200) return response
    const [extension] = url.pathname.match(/(?<=\.)\w+$/) ?? []
    const contentType = mimeTypes[extension] ?? mimeTypes.txt
    const headers = new Headers(response.headers)
    headers.set('Content-Type', contentType)
    return new Response(response.body, {headers})
}

export default {fetch: handler}

One thing to keep in mind when building this into Deno is that it must be aware of extension-to-Content-Type conversions. If this is going to be built-in, perhaps an API of sorts could be good? If Deno manages MIME types then it might as well expose that info 😉

Hypothetically, fetch() should cover the general use-case because fetch() in Deno handles file URLs, but (perhaps intentionally?) it throws an error when a file is not found rather than resolving with a 404 response, which is what browsers do. This is why the snippet above manually handles 404s.

Other than this, I don't reckon it's too difficult to implement - willing to give it a shot if the specifications are clear.

Also, as a sidenote, I think --fs is a good name for the flag and we can even accept a root for serving, like deno serve --fs=./src. Alternatively, we can keep the --fs flag boolean and do deno serve --fs --fs-root=./src, but this is nice-to-have functionality since it's equivalent to cd ./src && deno serve --fs. Just some thoughts 😉