Giter VIP home page Giter VIP logo

servor's Introduction

Servør

A dependency free dev server for modern web application development

A very compact but capable static file server with https, live reloading, gzip and other useful features to support modern web app development on localhost and over a local network. The motivation here was to write a package from the ground up with no dependencies; using only native, node and browser APIs to do some specific tasks with minimal code.

Servør can be invoked via the command line or programmatically using the node API.

Quickstart Example

The following command instructs servør to; clone perflink, start a server at the project root, open the url in a browser, open the source code in an editor and reload the browser when files change.

npx servor gh:lukejacksonn/perflink --browse --editor --reload

Most features are disabled by default but you can customize behaviour by passing positional arguments and flags to enable features.


servor

Features

  • 🗂 Serves static content like scripts, styles, images from a given directory
  • ♻️ Reloads the browser when project files get added, removed or modified
  • 🗜 Uses gzip on common filetypes like html, css, js and json
  • 🔐 Supports https and http2 with trusted self signed certificates
  • 🖥 Redirects all path requests to a single file for frontend routing
  • 📦 Accepts both HTML and JavaScript files as the root file for a directory
  • 🔎 Discovers freely available ports to start if the default is in use
  • 📄 Renders directory listing for urls ending with a trailing slash
  • 🗃 Opens browser tab and code editor to streamline quick start

CLI Usage

Run as a terminal command without adding it as a dependency using npx:

npx servor <root> <fallback> <port>

You can pass a GitHub repo as <root> using the syntax gh:<user>/<repository>

  • <root> path to serve static files from (defaults to current directory .)
  • <fallback> the file served for all non-file requests (defaults to index.html)
  • <port> what port you want to serve the files from (defaults to 8080)

Optional flags passed as non-positional arguments:

  • --browse causes the browser to open when the server starts
  • --reload causes the browser to reload when files change
  • --secure starts the server with https using generated credentials
  • --silent prevents the server node process from logging to stdout
  • --module causes the server to wrap the root in script type module tags
  • --static causes the server to route nested index files if they exist
  • --editor opens a code editor (currently only vscode) at the project root

Example usage with npm scripts in a package.json file after running npm i servor -D:

{
  "devDependencies": {
    "servor": "4.0.0"
  },
  "scripts": {
    "start": "servor www index.html 8080 --reload --browse"
  }
}

Generating Credentials

NOTE: This process depends on the openssl command existing (tested on macOS and linux only)

The files servor.crt and servor.key need to exist for the server to start using https. If the files do not exist when the --secure flag is passed, then certify.sh is invoked which:

  • Creates a local certificate authority used to generate self signed SSL certificates
  • Runs the appropriate openssl commands to produce:
    • a root certificate (pem) so the system will trust the self signed certificate
    • a public certificate (crt) that the server sends to clients
    • a private key for the certificate (key) to encrypt and decrypt traffic

Adding credentials to the trusted store

NOTE: This process depends on the sudo and security commands existing (tested on macOS only)

For the browser to trust self signed certificates the root certificate must be added to the system trusted store. This can be done automatically by running sudo servor --secure which:

  • Adds the root certificate to the system Keychain Access
  • Prevents the "⚠️ Your connection is not private" screen
  • Makes the 🔒 icon appear in the browsers address bar

The approach was adopted from @kingkool68/generate-ssl-certs-for-local-development

API Usage

Use servor programmatically with node by requiring it as a module in your script:

const servor = require('servor');
const instance = await servor({
  root: '.',
  fallback: 'index.html',
  module: false,
  static: false,
  reload: false,
  inject: ''
  credentials: null,
  port: 8080,
});

The servor function accepts a config object with optional props assigned the above default values if none are provided. Calling the servor function starts up a new server and returns an object describing its configuration.

const { url, root, protocol, port, ips } = await servor(config);

Inject

The inject property accepts a string that gets appended to the servers root document (which is index.html by default). This could be used to inject config or extend the development servers behavior and capabilities to suit specific environments.

const config = require('package.json');
servor({ inject: `<script>window.pkg=${config}</script>` });

Credentials

The credentials property accepts an object containing the entries cert and key which must both be valid for the server to start successfully. If valid credentials are provided then the server will start serving over https.

It is possible to generate the appropriate credentials using the --secure CLI flag.

Notes

Thanks to all the contributors to this projects so far. If you find a bug please create an issue or if you have an idea for a new feature then fork the project and create a pull request. Let me know how you are using servør on twitter.

servor's People

Contributors

andyrichardson avatar haydn avatar lukejacksonn avatar lwblackledge avatar marc-ed-raffalli avatar ooooooo-q avatar osdevisnot avatar pklknr avatar saintedlama avatar thelastzombie avatar tobiasbueschel avatar zegnat avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

servor's Issues

Option for disable the injection of base tag

With the new version when I reload a custom route (for example /profile) servor injects a <meta charset="UTF-8" > and a ` tags at the start of my index.html, so my files are not found because they expect that base has to be "/".

Can I use any option to disable this behavior?

index.html as default file in subfolders?

Currently, when I open something like localhost:8080/path, I get the fallback /index.html file instead of an existing file /path/index.html. Most servers, including GitHub pages, are by default configured "smartly" and return the index.html file (if it exists) when a folder is requested. Would be nice for this to be configurable, or maybe even default?

Starting two instances of Servor without port cause them having the same port

Using the API, I start two instances of Servor without waiting the first promise to finish, without port specified, the net library cause them to have the same port.

If I just do this it works:

const usePort = (port = 0) =>
  new Promise((ok, x) => {
    setTimeout(() => {
      const s = net.createServer();
      s.on('error', x);
      s.listen(port, () => (a = s.address()) && s.close(() => ok(a.port)));
    })
  });

They have two differents ports, (51159 and 51160 for example).

Media streaming issues with Chrome

Hey mate, I've been running into a couple issues with how Chrome handles media (audio/video):

  • Without the response header Content-Length, checking media.duration returns Infinity.

  • Without serving the requested chunk (Content-Range), changing media.currentTime just makes the file play from the beginning (as the whole file is returned).

In comparison, Firefox has neither issue.

I recall reading about this a long time ago, and found it weird how Chrome kept re-downloading the video assets with experiences that involve changing the currentTime, but I'm not sure how best to work streaming into Servor. Any ideas/thoughts?

Cheers.

Support already encoded files (gz, br)

Currently many file types like 'js', 'css', 'html', 'json', 'xml', 'svg' get automatically encoded as gzip and the Header content-encoding will be set to gzip. In my project I use esbuild with an compression plugin to automatically encode all bundles as brötli. Since servor doesn't check for gz or br file extension these files get served wrong.

  • Proposol 1: Check for file extensions gz and br and set the right content headers
    • Advantages: Very simple
    • Disadvantages: Not very flexibel
  • Proposal 2: Give the user the possibilitry to add configuration file which adds
    • Advantages:
      • Gives the power back to the user of the package to freely add headers depending on his needs
      • Also handles other use cases like shown in the example above
    • Disadvantages:
      • More Complex
    • Example: With other servers I could for example add an file like:
{
  "headers": [
    {
      "source": "dist/serviceWorker.js",
      "headers": [{ "key": "Service-Worker-Allowed", "value": "/" }]
    },
    {
      "source" : "dist/*.br",
      "headers" : [{ "key" : "Content-Encoding", "value" : "br" }]
    }
  ]
}

Add flow support

I know this project comes with no dependencies at the moment, but having static typing is very important to me.

Flowtype is a nice fit for this project since flow-remove-type is a very simple tool.

I have a fork which adds support for flow: master...taylorgoolsby:master

If you still want to keep no dependencies, how do you feel about implementing a hook/plugin system? I have something like this in mind:

// Add `transform` as a parameter to `servor`:
module.exports = async ({
  root = '.',
  module = false,
  fallback = module ? 'index.js' : 'index.html',
  reload = true,
  static = false,
  inject = '',
  credentials,
  port,
  transform
} = {}) => {  

// sendFile passes files through `transform`:
const sendFile = (res, status, file, ext, encoding = 'binary') => {
  if (transform) {
    file = transform(file, ext)
  }

  if (['js', 'css', 'html', 'json', 'xml', 'svg'].includes(ext)) {
    res.setHeader('content-encoding', 'gzip');
    file = zlib.gzipSync(utf8(file));
    encoding = 'utf8';
  }
  res.writeHead(status, { 'content-type': mimeTypes(ext) });
  res.write(file, encoding);
  res.end();
};

// flowtype + servor using the transform plugin:
const flowRemoveTypes = require('flow-remove-types');
const servor = require('servor');
const instance = await servor({
  root: '.',
  fallback: 'index.html',
  module: false,
  static: false,
  reload: false,
  inject: '',
  credentials: null,
  port: 8080,
  transform: (file, ext) => {
    if (ext === 'js') {
      file = flowRemoveTypes(file).toString()
    }
    return file
  }
});

IE11 Compatibility? Windows Compatibility?

Hello! I am trying to get this working with my react app to test on IE11 in windows and can't get it to work. Seems to work fine in other browsers. I have a little express server (super basic) that will serve and render my app in IE11 no problem.

Are there known incompatibilities with Windows and/or IE11?

image

crt and key file location for secure start

I am trying to start with --secure option , i have the crt and key file placed in the directory where i am starting the servor but it is not picking up the file.

Question is where should i put these files in?

Specify port without fallback?

I appreciate the simplicity of the command without too many flags, but I change the port much often than I change the fallback file. In fact, I've never changed the fallback file 😅

Would love to see --port so I can do yarn servor dist --port 3001 instead of yarn servor dist dist/index.html 3001.

[Feature Request] Allow multiple roots

Consider the following project tree:

project-root
  ├ src
  │ └ main.js
  └ static
    └ index.html

Currently, for index.html be able to link to main.js, servor must run in the project-root folder and the script tag in html must be <script src="../src/main.js">

$ cd ~/project-root
$ servor .

It would be really useful to allow servor to serve more folders than just the root. Something like:

$ cd ~/project-root
$ servor ./static --extern ./src

This would serve the ./src folder as if it and ./static were the same.
So the index.html would link the script at same level (<script src="./main.js">)

The --extern param could be repeatable to allow multiple entrypoints

$ cd ~/project-root
$ servor ./static --extern ./src --extern ./libs

Duplicate paths (e.g.: ./static/style.css, ./src/style.css and ./libs/style.css) could be served in order (left-to-right), returning the first found path.

Port behavior/configuration

Spinoff of a convo here: FredKSchott/snowpack#145

I know that I'll commonly have to restart my dev server multiple times during development, just as I switch around different terminals and tasks and such. When the dev server switches ports on me, I have to go update any URLs in any open browser tabs, instead of just refreshing the page. There's something less confusing about it as well, to have less changing/moving parts.

Obviously you can configure the port, but the fact that I also need to know the two options that come before it makes that less simple. Putting those three options behind flags so that I could do something like servor -p 1234/servor --port 1234 would be an improvement as well.

^ This is half personal opinion, and half me giving feedback based on writing the Snowpack docs and trying to keep things simple.

cors issues with localhost:5000 and service worker

Hi Luke,

This might not at all be an issue relevant to servør, but rather my total noobesse wrt service-workers. Just now starting to toy with service workers and made a real simple one:

const CACHE_NAME = 'cache'
self.addEventListener('install', event => {
    caches.open(CACHE_NAME).then(cache => cache.addAll(['/index.html', '/index.js']))
})

self.addEventListener('fetch', function(event) {
    event.respondWith(
        caches.match(event.request).then(function(response) {
            return response || fetch(event.request).then((response = {}))
        })
    )
})

When my index.html contains code for registering this service worker, servor starts up fine. But if I either reload once manually, or if I make a change to a file (to trigger a reload via the eventsource), I start getting these errors plopping out in my console:

Access to fetch at 'http://localhost:5000/' from origin 'http://localhost:8080' has been blocked by CORS policy: Request header field cache-control is not allowed by Access-Control-Allow-Headers in preflight response.
service-worker.js:1
Uncaught (in promise) TypeError: Failed to fetch
:5000/:1
GET http://localhost:5000/ net::ERR_FAILED

Somehow it seems when the service worker is intercepting the event source, it gets angry about cors headers.

Again, I'm just too much of a beginner to know wether this is something to fix in servor or with my service worker. Do you know?

Service Workers

Hello!
I am using servor for a web application I am building and I was wondering if there is an option to set up the service-worker-allowed for the site service worker.

Thanks in advance

--reload causes browsers to enter quirks mode

When using the --reload option, the browser disobeys the specified doctype I have. This is apparently because placing any content before the doctype forces the browser into quirks mode.

Make sure you put the DOCTYPE right at the beginning of your HTML document. Anything before the DOCTYPE, like a comment or an XML declaration will trigger quirks mode in Internet Explorer 9 and older.

Well... this happens to break Safari, Firefox and Chrome for me in weird ways. It led me to write invalid/incomplete flexbox css which is broken in standards mode 😅 . After doing a non-dev build, I found out a lot of my site was messed up.

This is what the browser is receiving:
before doctype

These are the two problem lines that can cause this behaviour.

if (isRoute && inject) file = inject + file;
if (isRoute && reload) file = livereload + file;

Possible solution

Would doing file + livereload work instead maybe?

Wait for write to finish

I have noticed then when watching large files the browser might reload before the file write is complete and show an error. One possible solution could be to check if stat.mtime is changing (doc). I think chokidar solves this in a similar way.

The problem is that fs.watch fires an event as soon as the file is changed - not when it is finished changing (in case of a longer write).

If you want I can try and make a PR?

setuid breaks local secure serving on linux

When I add servor as a dev dependency and try run serve a local instance with the "--secure" flag (along with providing some server.key and server.crt files) I get an error at the line where you call
process.setuid(501);
stating:

UnhandledPromiseRejectionWarning: Error: EPERM, Operation not permitted
If I remove that call, it passes effortlessly. Maybe there's a reason to changing the process uid that I am not competent enough. Anyways - if it can be made explicit you are breaking some security laws, it should be allowed, I think. Moreover, the browser is warning me as well any time I hit my homepage.

Kudos,
Kosta

unpkg CDN as a fallback?

Hi @lukejacksonn, first off, thank you so much for putting this together.

While using servor for my prototype work, I wanted an ability to use servor without even npm installing my dependencies 🎉 🎉

One way I was thinking to solve this, is by adding a check in servor to see if we are serving a JS file. If a JS file is not available locally, we rewrite the URL to UNPKG CDN URL.

For Example:

Let's say I have code like this in say index.js:

import { patch, h } from 'superfine';
import store from '@vanillajs/store';

servor would rewrite this to something like this (when node_modules is not available):

import { patch, h } from 'https://unpkg.com/[email protected]?type=module';
import store from 'https://unpkg.com/@vanillajs/[email protected]?type=module';

To take it a step further, if I do not have package.json, we simply rewrite the URL to something like this:

import { patch, h } from 'https://unpkg.com/superfine?type=module';
import store from 'https://unpkg.com/@vanillajs/store?type=module';

This idea has been explored before with owc-dev-server and works quite well using express-transform-bare-module-specifiers

However, I would like a no-dependency solution for this to be available in servor

thoughts/concerns?

Doesn't detect change in nested files (on Linux)

Hey, Luke 👋🏻

So I was trying snowpack out, and the docs brought me here. Their doc highly recommends servor. 😉 I've watched you in YT on pushing the web forward on a bundle-less future. I'm excited that it's becoming more mainstream.

So I'm using this in Ubuntu via WSL / Windows 10; while it reloads fine when I change the index.html, it doesn't reload if I change the JS file.

Here's a screencast I made to document the error.

Exception handling in the `decodeURI` function is required.

error occurred and server is killed.

/usr/local/lib/node_modules/http-server-spa/server.js:76
    const resource = path.join(cwd, root, decodeURI(uri));
                                          ^

URIError: URI malformed
    at decodeURI (<anonymous>)
    at Server.<anonymous> (/usr/local/lib/node_modules/http-server-spa/server.js:76:43)
    at Server.emit (node:events:513:28)
    at parserOnIncoming (node:_http_server:1091:12)
    at HTTPParser.parserOnHeadersComplete (node:_http_common:119:17)

Proxy support

I am using servor for SPA app development, and would like to proxy my /api requests to my node api backend. Does servor support proxying requests?

ignore folders and/or ignore .git folder by default

If I start servor at the root of my git repo a refresh is triggered whenever I run git status, git add, git commit etc.

It's not a huge issue, but there's no way currently of specifying folders for the watch to ignore. It might be nice to include such an option, it might also make sense to ignore some common folders like .git or node_modules.

[Feature Request] Inject livereload to each html file

Each served html should have livereloading functional. For now it works only for index.html files.

Why

To use servor livereload feature not only for spa, but for traditional sites as well.

How

I believe this should work as default, but it could be opt-in.

Workaround

I found only one way to achieve this - place separate index.html into nested directories (one dir per html), link them with /nested_dir and run servor with servor --reload --static

Serve to local IP address?

create-react-app automatically serves to localhost and a local IP address so I can easily access the website from my phone. I have literally 0 idea on how this works or how difficult it is to do, but would love to see in servor!

BaseUrl support?

As a simplicity project, servor is excellent and very easy to use.

But do you have plans to support baseUrl?

Considering that many people will use webpack( options.output.publicPath or others), I think this option is very useful to support SPA development/preview.

Then we can:

servor dist --baseUrl admin

↓↓

http://localhost:8080/          --> Error 404 or Friendly Tips.
http://localhost:8080/admin/    --> ./dist/index.html

Opt-out of automatically opening url

In some cases, like running along side Cypress, I don't need Chrome to auto-open, since Cypress has it's own isolated browser. Would it be possible to have a --no-open flag to prevent this feature. I think it's fine and usually preferable as a default to open, so I think the flag just needs to be for opt-out of this behaviour rather than opt-in.

Nested routes

Not sure if I'm doing something wrong, but...

Running the server with this

$ yarn http-server-spa . index.html

in a simple folder with an index.html that points to index.js, the following work as expected.

http://localhost:8080/
http://localhost:8080/foo

However, this one breaks.

http://localhost:8080/foo/bar

The error:

GET http://localhost:8080/foo/index.js 404 (Not Found)

Here's the console output since running, through all steps above, until the last one.

----------------------------------------------
[OK] Serving static files from ./.
[OK] Using the fallback file index.html
[OK] Listening on http://localhost:8080
----------------------------------------------
[OK] GET /foo/bar
[ER] GET /foo/index.js
[OK] GET /
[OK] GET /index.js
[OK] GET /foo
[OK] GET /index.js
[OK] GET /foo/bar
[ER] GET /foo/index.js

`sudo servor --secure` throws `EACCES: permission denied`

I'm trying to install the certs so that I won't see the "⚠️ Your connection is not private" screen.

I ran sudo npm i -g servor then sudo servor --secure, and it threw and error saying

(node:4316) UnhandledPromiseRejectionWarning: Error: EACCES: permission denied, uv_cwd
    at module.exports (/Users/me/.nvm/versions/node/v10.20.1/lib/node_modules/servor/servor.js:38:58)
    at process._tickCallback (internal/process/next_tick.js:68:7)
    at Function.Module.runMain (internal/modules/cjs/loader.js:834:11)
    at startup (internal/bootstrap/node.js:283:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:623:3)
(node:4316) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:4316) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

I'm on macOS Catalina 10.15.5 (19F96).
npm -v 6.14.4
node -v v10.20.1
servor 4.0.2

Support https for LAN IP

Thanks for putting this together! Unfortunately it doesn't work for my use case:

I'm accessing my dev box from my iOS device over a LAN. When I enter my IP address into Safari, I see "This Connection Is Not Private". If I tap on "Show Details", then "visit this website", it just leads me back to the original screen showing "This Connection Is Not Private".

I have been using an extremely hacking workaround described here. It would be great if I could use servor instead!

Vertical scroll position resets when servor triggers reloading in Firefox

This is probably firefox issue (or even just me). But maybe is it related to servor configuration?

  1. Serve some page with servor --reload, open it in FF
  2. Scroll the page vertically
  3. Trigger a reload with style changing
  4. Page reloads but the scroll position is reset

Additional info:

Firefox 89.0.1, Win 8.1
Tested in Chromium 88.0.4324.150 and there is no problem. There is also no problem in Firefox if I run servor without --reload flag and do reload with F5.

Trailing slash on SPA route

Hey.
I'm using Servor to serve an SPA with create-react-app
The problem is - whenever my SPA route ends with a slash I have 404.

For example, let's say I'm serving my app from https://myapp.com, and I have a react route /hello-world.
This URL works fine https://myapp.com/hello-world
But this one gives me 404 https://myapp.com/hello-world/

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.