A dev server for modern web development workflows.
Without any flags, es-dev-server
acts as a simple static file server. Through flags different features can be enabled, such as:
- reloading the browser on file changes
- resolve bare module imports using node resolution
- history API fallback for SPA routing
- Smart caching to speed up file serving
- Compatibility mode for older browsers
Compatibility mode enables bundle-free development with modern javascript, es modules and import maps on older browsers, including IE11.
We recommend following this guide for a step by step overview of different workflows with es-dev-server
.
npm i -D es-dev-server
// package.json
"scripts": {
"start": "es-dev-server"
}
// bash
npm run start
// or via npx
npx es-dev-server
name | type | description |
---|---|---|
port | number | The port to use, uses a random free port if not set. |
hostname | string | The hostname to use. Default: localhost |
open | boolean/string | Opens the browser on app-index, root dir or a custom path |
app-index | string | The app's index.html file, sets up history API fallback for SPA routing |
root-dir | string | The root directory to serve files from. Default: working directory |
base-path | string | Base path the app is served on. Example: /my-app |
config | string | The file to read configuration from (js or json) |
help | none | See all options |
name | type | description |
---|---|---|
watch | boolean | Reload the browser when files are edited |
http2 | boolean | Serve files over HTTP2. Sets up HTTPS with self-signed certificates |
name | type | description |
---|---|---|
compatibility | string | Compatibility mode for older browsers. Can be: esm , modern or all |
node-resolve | number | Resolve bare import imports using node resolve |
preserve-symlinks | boolean | Preserve symlinks when resolving modules. Default false. |
module-dirs | string/array | Directories to resolve modules from. Used by node-resolve |
babel | boolean | Transform served code through babel. Requires .babelrc |
file-extensions | number/array | Extra file extentions to use when transforming code. |
babel-exclude | number/array | Patterns of files to exclude from babel compilation. |
babel-modern-exclude | number/array | Patterns of files to exclude from babel compilation on modern browsers. |
Most commands have an alias/shorthand. You can view them by using --help
.
We pick up an es-dev-server.config.js
file automatically if it is present in the current working directory. You can specify a custom config path using the config
flag.
Configuration options are the same as command line flags, using their camelCased names. Example:
module.exports = {
port: 8080,
watch: true,
nodeResolve: true,
appIndex: 'demo/index.html',
moduleDirs: ['node_modules', 'custom-modules']
}
In addition to the command line flags, the configuration file accepts these additional options:
name | type | description |
---|---|---|
middlewares | array | Koa middlewares to add to the server, read more below. |
babelConfig | object | Babel config to run with the server |
es-dev-server
serves static files using the same structure as your file system. It cannot serve any files outside of the root of the web server. You need to make sure any files requested, including node modules, are accessible for the web server.
Click read more to view different strategies for setting up your project's folder structure.
Read more
The simplest setup where all files are accessible is to place your index.html at the root of your project:
node_modules/...
src/...
index.html
If you run es-dev-server
regularly from the root of this project, you can access your app at /
or /index.html
in the browser.
If you move your index.html
inside a subfolder:
node_modules/...
src/...
src/index.html
You can access your app in the browser at /src/
or /src/index.html
. You can tell es-dev-server
to explicitly open at this path:
# with app-index flag
es-dev-server --app-index src/index.html --open
# without app-index flag
es-dev-server --open src/
You can also change the root directory of the dev server:
es-dev-server --root-dir src --open
Now your index.html
is accessible at /
or /index.html
. However the dev server cannot serve any files outside of the root directory. So if your app uses any node modules, they will no longer because accessible.
If you want your index in a sub folder without this being visible in the browser url, you can set up a file rewrite rule. Read more here
If you are using es-dev-server
in a monorepo, your node modules are in two different locations. In the package's folder and the repository root:
node_modules/...
packages/my-package/node_modules/...
packages/my-package/index.html
You will need make sure the root node_modules folder is accessible to the dev server.
If your working directory is packages/my-package
you can use this command:
# with app-index
es-dev-server --root-dir ../../ --app-index packages/my-package/index.html --open
# without app-index
es-dev-server --root-dir ../../ --open packages/my-package/index.html
If your working directory is the root of the repository you can use this command:
es-dev-server --app-index packages/my-package/index.html --open
This is the same approach as serving an index.html in a subdirectory, so the section above applies here as well.
You can set up a <base href="">
element to modify how files are resolved relatively to your index.html. You can be very useful when your index.html is not in the root of your project.
If you use SPA routing, using a base element is highly recommended. Read more
You can install custom middlewares, using the middlewares
property.
Read more
The middleware should be a standard koa middleware. Read more about koa here.
You can use custom middlewares to set up a proxy, for example:
const proxy = require('koa-proxies');
module.exports = {
port: 9000,
middlewares: [
proxy('/api', {
target: 'http://localhost:9001',
})
],
};
You can rewrite certain file requests using a simple custom middleware. This can be useful for example to serve your index.html
from a different file location.
Read more
Set up a configuration file with a custom middleware:
module.exports = {
middlewares: [
function rewriteIndex(context, next) {
if (context.url === '/' || context.url === '/index.html') {
context.url = '/src/index.html';
}
return next();
}
],
};
This way from the browser you can request /
or /index.html
and it will serve /src/index.html
. This middleware is run before the dev server's own file serving logic, which will use the rewritten url.
es-dev-server
is based around developing without any build tools, however you can make it work with typescript as well.
Read more
The easiest way to use the server with typescript is to compile your typescript to javascript before running the server. Just run tsc
in watch mode and include the compiled js files from your index.html
.
You can also configure the dev server to consume your typescript files directly. This is done by running the server with a babel plugin to compile your typescript files to javascript.
Note that when compiling typescript with babel it does not do any type checking or special typescript compilation such as decorators, class fields and enums. You can configure babel to cover most of these, but not all. Read more about babel typescript here.
- Install the preset:
npm i --save-dev @babel/preset-typescript
- Add a
babel.config.js
or.babelrc
to your project:
{
"presets": [
"@babel/preset-typescript"
]
}
- Import a typescript file from your
index.html
<html>
<head></head>
<body>
<script type="module" src="./my-app.ts"></script>
</body>
</html>
- Run
es-dev-server
with these flags:
es-dev-server --file-extensions .ts --node-resolve --babel --open
To add support for experimental features which are normally handled by the typescript compiler, you can add extra babel plugins. Because typescript implements the legacy decorators proposal, you need to add the legacy flag and add class properties in loose mode:
- Install the plugins:
npm i --save-dev @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties
- Update your babel configuration:
{
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose": true }]
],
"presets": [
"@babel/preset-typescript"
]
}
Compatibility mode enables bundle-free development with features such as es modules and import maps on older browsers, including IE11.
Read more
If you want to make use of import maps, you can provide an import map in your index.html
. To generate an import map, you can check out our package import-maps-generate, or alternatively you can add one manually.
There are three modes that can be enabled:
esm
mode adds es-module-shims to enable new module features such as dynamic imports and import maps.
This mode has a neglible performance impact, and is great when working on modern browsers.
modern
mode expands esm
mode, adding a babel transform and a polyfill loader.
The babel transform uses the present-env plugin. This transforms standard syntax which isn't yet supported by all browsers. By default it targets latest two versions of Chrome, Safari, Firefox and Edge. This can be configured with a browserslist configuration.
The polyfill loader does lightweight feature detection to determine which polyills to load. By default it loads polyfills for webcomponents, these can be turned off or custom polyfills can be added in the configuration.
This mode has a moderate performance impact. Use this when using new javascript syntax that is not yet supported on all browsers.
all
mode expands modern
mode by making your code compatible with browsers which don't yet support modules.
In addition to the web component polyfills, it loads the general core-js polyfills and a polyfill for fetch
When loading your application it detects module support. If it is not supported, your app is loaded through system-js and your code is transformed to es5
.
The es5
transformation is only done for browsers which don't support modules, so you can safely use this mode on modern browsers where it acts the same way as modern
mode.
all
mode has the same moderate impact as modern
mode on browsers that support modules. On browsers which don't support modules it has a heavier impact. Use this mode if you want to verify if your code runs correctly on older browsers without having to run a build.
You can use different components from es-dev-server
as a library and integrate it with other tools:
Read more
When using the server from javascript you are going to need a config object to tell the server what options to turn on and off. It's best to use createConfig
for this as this converts the public API to an internal config structure and sets up default values.
By default all options besides static file serving is turned off, so it's easy to configure based on your own requirements.
The config structure is the same as the configuration explained in the configuration files section
import { createConfig } from 'es-dev-server';
const config = createConfig({
http2: true,
babel: true,
open: true,
});
createMiddlewares
creates the dev server's middlewares based on your configuration. You can use this to hook them up to your own koa server.
Returns an array of koa middleware functions.
import Koa from 'koa';
import { createConfig, createMiddlewares } from 'es-dev-server';
const config = createConfig({ });
const middlewares = createMiddlewares(config);
const app = new Koa();
middlewares.forEach(middleware => {
app.use(middleware);
});
createServer
creates an instance of the dev server including all middlewares, but without starting the server. This is useful if you want to be in control of starting the server yourself.
Returns the koa app and a node http or http2 server.
import Koa from 'koa';
import { createConfig, createServer } from 'es-dev-server';
const config = createConfig({ ... });
const { app, server } = createServer(config);
server.listen(3000);
createMiddlewares
and createServer
requires a chokidar fileWatcher if watch mode is enabled. You need to pass this separately because the watcher needs to be killed explicitly when the server closes.
import Koa from 'koa';
import chokidar from 'chokidar';
import { createConfig, createMiddlewares, createServer } from 'es-dev-server';
const config = createConfig({ ... });
const fileWatcher = chokidar.watch([]);
// if using createMiddlewares
createMiddlewares(config, fileWatcher);
// if using createServer
createServer(config, fileWatcher);
// close filewatcher when no longer necessary
fileWatcher.close();
startServer
creates and starts the server, listening on the configured port. It opens the browser if configured and logs a startup message.
Returns the koa app and a node http or http2 server.
import Koa from 'koa';
import { createConfig, startServer } from 'es-dev-server';
const config = createConfig({ ... });
const { app, server } = startServer(config, fileWatcher);