Installations
npm install dvlp
Developer Guide
Typescript
Yes
Module System
ESM
Min. Node Version
>=18
Node Version
23.3.0
NPM Version
10.9.0
Score
54.9
Supply Chain
92.3
Quality
89.8
Maintenance
100
Vulnerability
99.3
License
Releases
Contributors
Unable to fetch Contributors
Languages
JavaScript (99.23%)
HTML (0.53%)
TypeScript (0.13%)
CSS (0.11%)
Developer
popeindustries
Download Statistics
Total Downloads
160,137
Last Day
72
Last Week
616
Last Month
2,891
Last Year
37,994
GitHub Statistics
113 Stars
1,633 Commits
3 Forks
3 Watching
2 Branches
8 Contributors
Package Meta Information
Latest Version
16.4.3
Package Id
dvlp@16.4.3
Unpacked Size
1.14 MB
Size
270.49 kB
File Count
16
NPM Version
10.9.0
Node Version
23.3.0
Publised On
09 Dec 2024
Total Downloads
Cumulative downloads
Total Downloads
160,137
Last day
-72.9%
72
Compared to previous day
Last week
-43.5%
616
Compared to previous week
Last month
3.8%
2,891
Compared to previous month
Last year
20.4%
37,994
Compared to previous year
Daily Downloads
Weekly Downloads
Monthly Downloads
Yearly Downloads
Dev Dependencies
36
💥 dvlp
dvlp is a no-configuration, no-conditionals, no-middleware, no-nonsense (no-vowels!) dev server toolkit to help you develop quickly and easily for the web. You shouldn't have to jump through hoops to get a development environment up and running, and you definitely shouldn't have to include development-only stuff in your high-quality production code! dvlp is full of hacks so your code doesn't have to be!
Philosophy
- No bundling: write JS modules and load them directly in the browser
- No middleware: write application servers without special dev/build/bundle middleware
- No infrastructure: mock external JSON/EventSource/WebSocket resources
- No waiting: restart application servers in the blink of an eye
- No refreshing: automatically reload clients on file change
How it works
dvlp allows you to easily serve resources from one or more project directories (static
mode), from your custom application server (app
mode), or from your Electron desktop application (electron
mode). In all cases, dvlp creates a proxy server in front of your content, automatically injecting the necessary reload script into HTML responses to enable reloading, and watches all files for changes, restarts the app
server/ electron
application if necessary, and reloads all connected clients.
In addition, when working with JS modules, dvlp will ensure that so-called bare imports (import "lodash"
), which are not natively supported by browsers, work by re-writing all import paths to valid urls. Since some node_modules
packages are still published as CommonJS modules, non-ESM packages are bundled and converted to an ESM module using esbuild. These bundles are versioned and cached for efficient reuse in the .dvlp
directory under your project root.
Bonus!
dvlp also includes a testServer
for handling various network request scenarios (mocking, latency, errors, offline, etc.) during testing.
Installation
Install globally or locally in your project with npm/yarn:
1$ npm install dvlp
Usage
1Usage: dvlp [options] [path...] 2 3Start a development server, restarting and reloading connected clients on file changes. 4 Serves static files from one or more "path" directories, or a custom application 5 server if "path" is a single application server file. 6 7Options: 8 -p, --port <port> port number 9 -m, --mock <path> path to mock files (directory, file, glob pattern) 10 -k, --hooks <path> path to optional hooks registration file 11 -e, --electron run "path" file as electron.js entry file 12 --ssl <path> enable https mode by specifying path to directory containing ".crt" and ".key" files (directory, 13 glob pattern) 14 -s, --silent suppress all logging 15 --verbose enable verbose logging 16 --no-reload disable reloading connected clients on file change 17 -v, --version output the current version 18 -h, --help display help for command
Add a script to your package.json scripts
:
1{ 2 "scripts": { 3 "dev": "dvlp --port 8000 src/app.js" 4 } 5}
...and launch:
1$ npm run dev
Hooks
In some cases, source code may need to be transformed into a valid format before it is executed, or a response body modified before sending it to the browser. In these cases, you can register hooks
to convert file contents on the fly when imported by an application server or requested by the browser.
Registering hooks
Create a Node.js module that exposes one or more supported lifecycle hook functions:
1// scripts/hooks.js 2import sass from 'sass'; 3 4const RE_SASS = /\.s[ac]ss$/; 5 6export default { 7 /** 8 * Bundle non-esm node_modules dependency requested by the browser. 9 * This hook is run after file read. 10 * 11 * @param { string } id 12 * @param { string } filePath 13 * @param { string } fileContents 14 * @param { { esbuild: Pick<import("esbuild"), 'build'> } } context 15 */ 16 async onDependencyBundle(id, filePath, fileContents, context) { 17 if (id === 'some/package') { 18 // Transform 19 } 20 } 21 22 /** 23 * Transform file contents for file requested by the browser. 24 * This hook is run after file read, and before any modifications by dvlp. 25 * 26 * @param { string } filePath 27 * @param { string } fileContents 28 * @param { { client: { manufacturer: string, name: string, ua: string, version: string }, esbuild: Pick<import("esbuild"), 'build', 'transform'> } } context 29 */ 30 async onTransform(filePath, fileContents, context) { 31 // Note: .ts, .tsx, .jsx files are transformed by default 32 33 if (RE_SASS.test(filePath)) { 34 return sass.renderSync({ 35 file: filePath, 36 }).css; 37 } 38 }, 39 40 /** 41 * Manually resolve import specifier. 42 * This hook is run for each import statement. 43 * If returns "false", import re-writing is skipped. 44 * If returns "undefined", import specifier is re-written using default resolver. 45 * If "context.isDynamic", also possible to return replacement for whole expression. 46 * 47 * @param { string } specifier 48 * @param { { importer: string, isDynamic: boolean } } context 49 * @param { (specifier: string, importer: string) => string | undefined } defaultResolve 50 */ 51 onResolveImport(specifier, context, defaultResolve) { 52 if (context.isDynamic) { 53 return `dynamicImport('./some-path-prefix/${specifier}.js', '${context.importer}')`; 54 } 55 }, 56 57 /** 58 * Manually handle response for incoming server request. 59 * If returns "true", further processing by dvlp will be aborted. 60 * 61 * @param { IncomingMessage | Http2ServerRequest } request 62 * @param { ServerResponse | Http2ServerResponse } response 63 * @returns { Promise<boolean> | boolean | undefined } 64 */ 65 onRequest(request, response) { 66 if (request.url === '/something') { 67 response.writeHead(200); 68 response.end('handled'); 69 return true; 70 } 71 }, 72 73 /** 74 * Modify response body before sending to the browser. 75 * This hook is run after all modifications by dvlp, and before sending to the browser. 76 * 77 * @param { string } filePath 78 * @param { string } responseBody 79 */ 80 onSend(filePath, responseBody) { 81 if (RE_JS.test(filePath)) { 82 return responseBody.replace('__VERSION__', '1.0.0'); 83 } 84 }, 85 86 /** 87 * Transform file contents for application server. 88 * 89 * @param { string } filePath 90 * @param { { format?: string } } context 91 * @param { NodeLoadLoaderHook } defaultTransform 92 * @returns { { format: string; source: string | SharedArrayBuffer | Uint8Array } } 93 */ 94 onServerTransform(filePath, context, defaultTransform) { 95 // Note: .ts, .tsx, .jsx files are transformed by default 96 // @see https://nodejs.org/api/esm.html#loadurl-context-defaultload 97 }, 98 99 /** 100 * Manually resolve import specifiers for application server. 101 * 102 * @param { string } specifier 103 * @param { { conditions: Array<string>; parentURL?: string } } context 104 * @param { NodeResolveLoaderHook } defaultResolve 105 * @returns { { format?: string; url: string } } 106 */ 107 onServerResolve(specifier, context, defaultResolve){ 108 // @see https://nodejs.org/api/esm.html#resolvespecifier-context-defaultresolve 109 } 110};
...reference the original file as you normally would:
1<link rel="stylesheet" href="src/index.sass" />
...and pass a reference to the hooks.js
file with the -k, --hooks
option:
1{ 2 "scripts": { 3 "dev": "dvlp --hooks scripts/hooks.js --port 8000 src/app.js" 4 } 5}
Mocking
When developing locally, it's often useful to mock responses for requests made by your server or browser application, especially when working with an external API. dvlp lets you quickly and easily mock endpoints by intercepting requests that match those registered with the -m, --mock
option.
Mocking request/response
Mock a response by creating a .json
file describing the mocked request/response
:
1{ 2 "request": { 3 "url": "http://www.someapi.com/v1/id/101010", 4 "ignoreSearch": true 5 }, 6 "response": { 7 "headers": { 8 "x-custom": "custom header" 9 }, 10 "body": { 11 "user": { 12 "name": "Nancy", 13 "id": "101010" 14 } 15 } 16 } 17}
(Setting request.ignoreSearch = true
will ignore query parameters when matching an incoming request with the mocked response)
Bad responses can also be mocked by setting hang
, error
, missing
, or offline
response properties:
1{ 2 "request": { 3 "url": "http://www.someapi.com/v1/id/101010" 4 }, 5 "response": { 6 "error": true, 7 "body": {} 8 } 9}
Multiple mocked responses may also be included in a single file:
1[ 2 { 3 "request": { 4 "url": "http://www.someapi.com/v1/id/101010" 5 }, 6 "response": { 7 "body": {} 8 } 9 }, 10 { 11 "request": { 12 "url": "http://www.someapi.com/v1/id/202020" 13 }, 14 "response": { 15 "body": {} 16 } 17 } 18]
Though JSON responses are probably the most common, it's also possible to mock other types of payloads by linking the response.body
to an external file:
1{ 2 "request": { 3 "url": "http://www.someplace.com/images/avatar.jpg" 4 }, 5 "response": { 6 "body": "../assets/avatar.jpg" 7 } 8}
(File paths referenced in response.body
are relative to the mock file, not the web/project root)
Register mocked responses with the command-line option -m, --mock
and a path to your mock files:
1{ 2 "scripts": { 3 "dev": "dvlp --mock path/to/mock/files --port 8000 src/app.js" 4 } 5}
Your path/to/mock/files
could be one of the following:
- path to directory of files:
path/to/mock/directory
- path to a single file:
path/to/mock.json
(The following require wrapping in ""
)
- globbed path to multiple files/directories:
"path/to/mock/{api,assets}"
- multiple files/directories separated by space,
,
,:
, or;
:"path/to/mock1.json, path/to/mock2.json"
Mocking stream/events
Mock a WebSocket
or EventStream
by creating a .json
file describing the mocked stream/events
:
1{ 2 "stream": { 3 "url": "ws://www.somesocket.com/stream", 4 "ignoreSearch": true, 5 "protocol": "socket.io" 6 }, 7 "events": [ 8 { 9 "name": "hello Bob", 10 "connect": true, 11 "message": { 12 "people": ["Bob Builder"] 13 }, 14 "options": { 15 "event": "update", 16 "namespace": "/people" 17 } 18 }, 19 { 20 "name": "hello Ernie", 21 "message": { 22 "people": ["Bob Builder", "Ernie Engineer"] 23 }, 24 "options": { 25 "event": "update", 26 "namespace": "/people" 27 } 28 } 29 ] 30}
(Setting request.ignoreSearch = true
will ignore query parameters when matching an incoming request with the mocked response)
(Specifying a stream.protocol = "socket.io"
will negotiate WebSocket responses using the Socket.io protocol)
An event's name
is a custom, unique string used to identify the event for manual triggering (see below). Adding the property connect: true
will flag an event to be triggered automatically on initial connection.
A sequence of events may also be described by nesting events under the sequence
property:
1{ 2 "stream": { 3 "url": "http://www.someeventsource.com/stream" 4 }, 5 "events": [ 6 { 7 "name": "a sequence of unfortunate events", 8 "sequence": [ 9 { 10 "message": "oh", 11 "options": { 12 "event": "update" 13 } 14 }, 15 { 16 "message": "no", 17 "options": { 18 "event": "update", 19 "delay": 100 20 } 21 }, 22 { 23 "message": "not", 24 "options": { 25 "event": "update", 26 "delay": 50 27 } 28 }, 29 { 30 "message": "again!", 31 "options": { 32 "event": "update", 33 "delay": 10 34 } 35 } 36 ] 37 } 38 ] 39}
Register mocked responses with the command-line option -m, --mock
and a path to your mock files:
1{ 2 "scripts": { 3 "dev": "dvlp --mock path/to/mock/files --port 8000 src/app.js" 4 } 5}
Your path/to/mock/files
could be one of the following:
- path to directory of files:
path/to/mock/directory
- path to a single file:
path/to/mock.json
(Note that the following require wrapping in ""
)
- globbed path to multiple files/directories:
"path/to/mock/{api,assets}"
- multiple files/directories separated by space,
,
, or;
:"path/to/mock1.json, path/to/mock2.json"
Triggering mocked stream events
Once registered, mocked stream events may be triggerd from your browser's console:
1dvlp.pushEvent('ws://www.somesocket.com/stream', 'hello Ernie');
Mocking in the browser
All mocks registered with the -m, --mock
option are also enabled by default in the browser. In addition, similar to the testServer
, you can register mocks programatically:
1import { testBrowser } from 'dvlp/test-browser'; 2 3describe('some test', () => { 4 before(() => { 5 testBrowser.disableNetwork(); 6 }); 7 after(() => { 8 testBrowser.enableNetwork(); 9 }); 10 11 it('should fetch mock data', async () => { 12 const href = 'https://www.google.com'; 13 testBrowser.mockResponse( 14 href, 15 (req, res) => { 16 res.writeHead(500); 17 res.end('error'); 18 }, 19 true, 20 ); 21 const res = await fetch(href); 22 assert.equal(res.status, 500); 23 }); 24});
Bundling
As mentioned in How it works, dvlp will bundle CommonJS packages imported from node_modules
in order to convert them to es6 modules. esbuild is used to create these bundles, and they are then cached on disk inside the .dvlp
directory under your project root.
In the (rare) case you need to customise bundling to work with the packages you're importing, you can register a onDependencyBundle
hook.
SSL
Enable development against a secure http2 server by passing the path or glob pattern to your .crt
and .key
files with the --ssl
option.
Follow the directions here to generate a self-signed certificate for local development
Debugging
dvlp uses the debug.js debugging utility internally. Set the following environment variable before running to see detailed debug messages:
1$ DEBUG=dvlp* npm run dev
JS API
- server(filePath: string|[string]|() => void, [options]): Promise<{ destroy: () => void }>
Serve files at filePath
, starting static file server if one or more directories, or app server if a single file or function (which starts an application server when imported/called).
options
include:
certsPath: string|[string]
: the path or glob pattern containing ".crt" and ".key" files (default''
)directories: [string]
: additional directories to use for resolving file requests (default[]
)hooksPath: string
: the path to a hooks registration file (default''
)mockPath: string|[string]
the path(s) to load mock files from (default''
)port: number
: port to expose onlocalhost
. Will useprocess.env.PORT
if not specified here (default8080
)reload: boolean
: enable/disable browser reloading (defaulttrue
)silent: boolean
: disable/enable default logging (defaultfalse
)
1import { server } from 'dvlp'; 2const appServer = await server('path/to/app.js', { port: 8080 });
- testServer([options]): Promise<TestServer>
Create a server for handling network requests during testing.
options
include:
autorespond: boolean
enable/disable automatic dummy responses. If unable to resolve a request to a local file or mock, the server will respond with a dummy response of the appropriate type (defaultfalse
)latency: number
the amount of artificial latency to introduce (inms
) for responses (default50
)port: number
the port to expose onlocalhost
. Will useprocess.env.PORT
if not specified here (default8080
)webroot: String
the subpath fromprocess.cwd()
to prepend to relative paths (default''
)
1import { testServer } from 'dvlp/test'; 2const mockApi = await testServer({ port: 8080, latency: 20, webroot: 'src' });
Returns a TestServer
instance with the following methods:
loadMockFiles(filePath: string|[string]): void
load and register mock response files (see mocking)
1{ 2 "request": { 3 "url": "http://www.someapi.com/v1/id/101010" 4 }, 5 "response": { 6 "body": { 7 "user": { 8 "name": "Nancy", 9 "id": "101010" 10 } 11 } 12 } 13}
1mockApi.loadMockFiles('path/to/mock/101010.json'); 2const res = await fetch('http://www.someapi.com/v1/id/101010'); 3console.log(await res.json()); // => { user: { name: "nancy", id: "101010" } }
mockResponse(request: string|object, response: object|(req, res) => void, once: boolean, onMockCallback: () => void): () => void
add a mockresponse
forrequest
, optionally removing it after first use, and/or triggering a callback when successfully mocked (see mocking). Returns a function that may be called to remove the added mock at any time.
1mockApi.mockResponse( 2 '/api/user/1234', 3 { 4 body: { 5 id: '1234', 6 name: 'bob', 7 }, 8 }, 9 true, 10); 11const res = await fetch('http://localhost:8080/api/user/1234'); 12console.log(await res.json()); // => { id: "1234", name: "bob" }
Or pass a response handler:
1const removeMock = mockApi.mockResponse( 2 '/api/user/1234', 3 (req, res) => { 4 res.writeHead(200, { 5 'Content-Type': 'application/json', 6 }); 7 res.end(JSON.stringify({ id: '1234', name: 'bob' })); 8 }, 9 true, 10); 11const res = await fetch('http://localhost:8080/api/user/1234'); 12console.log(await res.json()); // => { id: "1234", name: "bob" } 13removeMock();
mockPushEvents(stream: string|object, events: object|[object]): () => void
add one or more mockevents
for a WebSocket/EventSourcestream
(see mocking). Returns a function that may be called to remove the added mock at any time.
1const removeMock = mockApi.mockPushEvents('ws://www.somesocket.com/stream', [
2 {
3 name: 'hi',
4 message: 'hi!',
5 },
6 {
7 name: 'so scary',
8 message: 'boo!',
9 },
10]);
11ws = new WebSocket('ws://www.somesocket.com/stream');
12ws.addEventListener('message', (event) => {
13 console.log(event.data); // => hi!
14 removeMock();
15});
pushEvent(stream: string|object, event: string|object’):void
push data to WebSocket/EventSource clients. A string passed as 'event' will be handled as a named mock push event (see mocking)
1mockApi.pushEvent('ws://www.somesocket.com/stream', 'so scary');
-
ref(): void
prevent process from exiting while this server is active -
unref(): void
allow process to exit if this is the only active -
destroy(): Promise<void>
stop and clean up running server
In addition, testServer
supports the following special query parameters:
offline
simulate an offline state by terminating the request (fetch('http://localhost:3333/foo.js?offline')
)error
return a 500 server error response (fetch('http://localhost:3333/foo.js?error')
)missing
return a 404 not found response (fetch('http://localhost:3333/foo.js?missing')
)maxage=value
configureCache-Control: public, max-age={value}
cache header (fetch('http://localhost:3333/foo.js?maxage=10')
)hang
hold connection open without responding (fetch('http://localhost:3333/foo.js?hang')
)
- testServer.disableNetwork(rerouteAllRequests: boolean): void
Disable all network requests with origin that is not localhost
. Prevents all external network requests for the current Node.js process. If rerouteAllRequests
is set to true
, all external requests will be re-routed to the current running server.
1testServer.disableNetwork(); 2await fetch('https://github.com/popeindustries/dvlp'); 3// => Error "network connections disabled"
- testServer.enableNetwork(): void
Re-enables all previously disabled external network requests for the current Node.js process.
JS API (browser)
- testBrowser.mockResponse(request: string|object, response: object|(req, res) => void, once: boolean, onMockCallback: () => void): () => void
Add a mock response
for request
, optionally removing it after first use, and/or triggering a callback when successfully mocked (see mocking). Returns a function that may be called to remove the added mock at any time.
1// Also available as "window.dvlp" 2import { testBrowser } from 'dvlp/test-browser'; 3 4testBrowser.mockResponse( 5 'http://localhost:8080/api/user/1234', 6 { 7 body: { 8 id: '1234', 9 name: 'bob', 10 }, 11 }, 12 true, 13);
- testBrowser.pushEvent(stream: string|object, event: string|object’):void
Push data to WebSocket/EventSource clients. A string passed as 'event' will be handled as a named mock push event (see mocking).
1testBrowser.pushEvent('ws://www.somesocket.com/stream', 'so scary');
- testBrowser.disableNetwork(rerouteAllRequests: boolean): void
Disable all network requests with origin that is not localhost
. Prevents all external AJAX/Fetch/EventSource/WebSocket requests originating from the current browser window. If rerouteAllRequests
is set to true
, all external requests will be re-routed to the running dvlp service.
1testBrowser.disableNetwork(); 2await fetch('https://github.com/popeindustries/dvlp'); 3// => Error "network connections disabled"
- testServer.enableNetwork(): void
Re-enables all previously disabled requests originating from the current browser window.
No vulnerabilities found.
Reason
14 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
Reason
no binaries found in the repo
Reason
no dangerous workflow patterns detected
Reason
license file detected
Details
- Info: project has a license file: LICENSE:0
- Info: FSF or OSI recognized license: MIT License: LICENSE:0
Reason
3 existing vulnerabilities detected
Details
- Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg
- Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275
- Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv
Reason
Found 1/26 approved changesets -- score normalized to 0
Reason
detected GitHub workflow tokens with excessive permissions
Details
- Warn: no topLevel permission defined: .github/workflows/main.yml:1
- Info: no jobLevel write permissions found
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/main.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/popeindustries/dvlp/main.yml/main?enable=pin
- Warn: third-party GitHubAction not pinned by hash: .github/workflows/main.yml:22: update your workflow using https://app.stepsecurity.io/secureworkflow/popeindustries/dvlp/main.yml/main?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/main.yml:27: update your workflow using https://app.stepsecurity.io/secureworkflow/popeindustries/dvlp/main.yml/main?enable=pin
- Info: 0 out of 2 GitHub-owned GitHubAction dependencies pinned
- Info: 0 out of 1 third-party GitHubAction dependencies pinned
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
- Warn: no security policy file detected
- Warn: no security file to analyze
- Warn: no security file to analyze
- Warn: no security file to analyze
Reason
project is not fuzzed
Details
- Warn: no fuzzer integrations found
Reason
branch protection not enabled on development/release branches
Details
- Warn: branch protection not enabled for branch 'main'
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
- Warn: 0 commits out of 5 are checked with a SAST tool
Score
4.1
/10
Last Scanned on 2024-12-16
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn MoreOther packages similar to dvlp
@snipsonian/dvlp
Snippets used for development purposes
dvlp-tiptap-2
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
dvlp-commons
Developpez.com shared code for md-file-converter implementation packages
dvlp-faq-xml
Developpez.com FAQ XML impl for md-file-converter