├── .editorconfig ├── .gitignore ├── .prettierignore ├── .prettierrc ├── license ├── package.json ├── readme.md ├── src ├── bin.js ├── index.js ├── injectCode.js ├── listen.js ├── logMessage.js ├── mimeTypes.js └── utils │ ├── addClient.js │ ├── error.js │ ├── getFilePath.js │ ├── getIp.js │ ├── index.js │ ├── injectContent.js │ ├── log.js │ ├── mimeType.js │ ├── setRoot.js │ ├── show404.js │ ├── showError.js │ ├── showFile.js │ └── styles.js └── test ├── favicon.ico ├── images ├── apple-touch-icon.png ├── google-touch-icon.png ├── icon.svg └── mask-icon.svg ├── index.html ├── manifest.json └── scripts └── main.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.yml] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | indent_style = space 17 | trim_trailing_whitespace = false 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | package-lock.json 4 | yarn.lock 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | test 2 | src/mimeTypes.js 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none", 4 | "arrowParens": "avoid", 5 | "printWidth": 90 6 | } 7 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) Antoine Boulanger (https://github.com/ABXlink) 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-serve", 3 | "version": "1.0.1", 4 | "description": "🍛 Ultralight http server with live reload. [CLI + API]", 5 | "repository": "nativew/serve", 6 | "author": "Antoine Boulanger (https://github.com/antoineboulanger)", 7 | "license": "ISC", 8 | "exports": "./src/index.js", 9 | "main": "src/index.js", 10 | "type": "module", 11 | "bin": { 12 | "create-serve": "src/bin.js" 13 | }, 14 | "scripts": { 15 | "format": "prettier --write --ignore-unknown '**/*'", 16 | "start": "node src/bin.js test" 17 | }, 18 | "devDependencies": { 19 | "prettier": "^2.2.1" 20 | }, 21 | "files": [ 22 | "src" 23 | ], 24 | "keywords": [ 25 | "serve", 26 | "cli", 27 | "command-line", 28 | "http", 29 | "dev", 30 | "development", 31 | "server", 32 | "livereload", 33 | "live", 34 | "reload", 35 | "hot", 36 | "refresh" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # Serve 🍛 4 | 5 | Ultralight http server with live reload. 6 | CLI + API 7 | 8 |
9 | 10 | ### Simple CLI and API 11 | 12 | ### With live reload 13 | 14 | ### Light and modern 15 | 16 | ### No dependencies 17 | 18 |
19 | 20 | ### One command 21 | 22 | ```zsh 23 | npm init serve 24 | ``` 25 | 26 |
27 | 28 | ### Or one function 29 | 30 | ```js 31 | import serve from 'create-serve'; 32 | 33 | serve.start(); 34 | ``` 35 | 36 |
37 | 38 | ### To start 🍛 39 | 40 |
41 | 42 | ### CLI 43 | 44 | By default, it serves `public` if the folder exists, otherwise root `/`. 45 | Or you can specify a different folder. 46 | 47 | ```zsh 48 | npm init serve [folder] 49 | ``` 50 | 51 |
52 | 53 | ### API 54 | 55 | ```js 56 | import serve from 'create-serve'; 57 | 58 | serve.start({ 59 | port: 7000, 60 | root: '.', 61 | live: true 62 | }); 63 | ``` 64 | 65 |
66 | 67 | ### Live reload 68 | 69 | ```js 70 | serve.update(); 71 | ``` 72 | 73 |
74 | 75 | ### Use any file watcher 76 | 77 |
78 | 79 | [Chokidar](https://github.com/paulmillr/chokidar) 80 | 81 | ```js 82 | import serve from 'create-serve'; 83 | import chokidar from 'chokidar'; 84 | 85 | serve.start(); 86 | 87 | chokidar.watch('.').on('change', () => { 88 | serve.update(); 89 | }); 90 | ``` 91 | 92 |
93 | 94 | [esbuild](https://esbuild.github.io/api/#watch) 95 | 96 | Use the official wrapper for esbuild's watch   →   [esbuild-serve](https://github.com/nativew/esbuild-serve) 97 | 98 |
99 | 100 | ### Log 101 | 102 | Import the util functions to log updates with colours. 103 | 104 | ```js 105 | import serve, { error, log } from 'create-serve'; 106 | 107 | serve.update(); 108 | 109 | hasError 110 | ? error('× Failed') // Red 111 | : log('✓ Updated'); // Green 112 | ``` 113 | 114 |

115 | 116 |

117 | 118 | Native Web 119 | 120 |

121 | 122 |
123 | -------------------------------------------------------------------------------- /src/bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { start } from './index.js'; 3 | 4 | const root = process.argv[2]; 5 | 6 | start({ live: false, root }); 7 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import http from 'http'; 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | import listen from './listen.js'; 5 | import { 6 | addClient, 7 | error, 8 | getFilePath, 9 | injectContent, 10 | log, 11 | mimeType, 12 | setRoot, 13 | show404, 14 | showError, 15 | showFile 16 | } from './utils/index.js'; 17 | 18 | export let options = { 19 | port: 7000, 20 | root: '.', 21 | live: true 22 | }; 23 | 24 | export const defaultRoot = './public'; 25 | export const encoding = 'utf-8'; 26 | export const eventSource = '/create-serve'; 27 | export const clients = []; 28 | 29 | export const start = (startOptions = {}) => { 30 | Object.assign(options, startOptions); 31 | options.root = setRoot(); 32 | 33 | const { live } = options; 34 | const server = http.createServer((request, response) => { 35 | if (live && request.url == eventSource) { 36 | const client = addClient(response); 37 | 38 | return clients.push(client); 39 | } 40 | 41 | const filePath = getFilePath(request); 42 | const extension = path.extname(filePath).toLowerCase().slice(1); 43 | const contentType = mimeType(extension) || 'application/octet-stream'; 44 | const isHtml = contentType == 'text/html'; 45 | const encode = isHtml ? 'utf8' : null; 46 | 47 | fs.readFile(filePath, encode, (error, content) => { 48 | if (error) { 49 | if (error.code == 'ENOENT') { 50 | return show404(response); 51 | } 52 | 53 | return showError(response, error); 54 | } 55 | 56 | if (live && isHtml) content = injectContent(content); 57 | 58 | return showFile(response, content, contentType); 59 | }); 60 | }); 61 | 62 | listen(server); 63 | }; 64 | 65 | export const update = () => { 66 | clients.forEach(response => response.write('data: update\n\n')); 67 | clients.length = 0; 68 | }; 69 | 70 | export { error, log }; 71 | export default { start, update }; 72 | -------------------------------------------------------------------------------- /src/injectCode.js: -------------------------------------------------------------------------------- 1 | import { eventSource } from './index.js'; 2 | 3 | const injectCode = () => ` 4 | 7 | `; 8 | 9 | export default injectCode; 10 | -------------------------------------------------------------------------------- /src/listen.js: -------------------------------------------------------------------------------- 1 | import logMessage from './logMessage.js'; 2 | import { options } from './index.js'; 3 | 4 | const listen = (server, serverPort = options.port) => { 5 | server 6 | .listen(serverPort, () => { 7 | const currentPort = server.address().port; 8 | 9 | logMessage(currentPort); 10 | }) 11 | .once('error', () => { 12 | server.removeAllListeners('listening'); 13 | listen(server, 0); 14 | }); 15 | }; 16 | 17 | export default listen; 18 | -------------------------------------------------------------------------------- /src/logMessage.js: -------------------------------------------------------------------------------- 1 | import { log, error, getIp } from './utils/index.js'; 2 | import { options } from './index.js'; 3 | 4 | const logMessage = currentPort => { 5 | const { port } = options; 6 | 7 | log('\nServing 🍛\n'); 8 | log(`Local → http://localhost:${currentPort}\n`); 9 | log(`Network → http://${getIp()}:${currentPort}\n`); 10 | if (currentPort != port) error(`Port ${port} was in use.\n`); 11 | }; 12 | 13 | export default logMessage; 14 | -------------------------------------------------------------------------------- /src/mimeTypes.js: -------------------------------------------------------------------------------- 1 | export default {"application/andrew-inset":["ez"],"application/applixware":["aw"],"application/atom+xml":["atom"],"application/atomcat+xml":["atomcat"],"application/atomdeleted+xml":["atomdeleted"],"application/atomsvc+xml":["atomsvc"],"application/atsc-dwd+xml":["dwd"],"application/atsc-held+xml":["held"],"application/atsc-rsat+xml":["rsat"],"application/bdoc":["bdoc"],"application/calendar+xml":["xcs"],"application/ccxml+xml":["ccxml"],"application/cdfx+xml":["cdfx"],"application/cdmi-capability":["cdmia"],"application/cdmi-container":["cdmic"],"application/cdmi-domain":["cdmid"],"application/cdmi-object":["cdmio"],"application/cdmi-queue":["cdmiq"],"application/cu-seeme":["cu"],"application/dash+xml":["mpd"],"application/davmount+xml":["davmount"],"application/docbook+xml":["dbk"],"application/dssc+der":["dssc"],"application/dssc+xml":["xdssc"],"application/ecmascript":["ecma","es"],"application/emma+xml":["emma"],"application/emotionml+xml":["emotionml"],"application/epub+zip":["epub"],"application/exi":["exi"],"application/fdt+xml":["fdt"],"application/font-tdpfr":["pfr"],"application/geo+json":["geojson"],"application/gml+xml":["gml"],"application/gpx+xml":["gpx"],"application/gxf":["gxf"],"application/gzip":["gz"],"application/hjson":["hjson"],"application/hyperstudio":["stk"],"application/inkml+xml":["ink","inkml"],"application/ipfix":["ipfix"],"application/its+xml":["its"],"application/java-archive":["jar","war","ear"],"application/java-serialized-object":["ser"],"application/java-vm":["class"],"application/javascript":["js","mjs"],"application/json":["json","map"],"application/json5":["json5"],"application/jsonml+json":["jsonml"],"application/ld+json":["jsonld"],"application/lgr+xml":["lgr"],"application/lost+xml":["lostxml"],"application/mac-binhex40":["hqx"],"application/mac-compactpro":["cpt"],"application/mads+xml":["mads"],"application/manifest+json":["webmanifest"],"application/marc":["mrc"],"application/marcxml+xml":["mrcx"],"application/mathematica":["ma","nb","mb"],"application/mathml+xml":["mathml"],"application/mbox":["mbox"],"application/mediaservercontrol+xml":["mscml"],"application/metalink+xml":["metalink"],"application/metalink4+xml":["meta4"],"application/mets+xml":["mets"],"application/mmt-aei+xml":["maei"],"application/mmt-usd+xml":["musd"],"application/mods+xml":["mods"],"application/mp21":["m21","mp21"],"application/mp4":["mp4s","m4p"],"application/mrb-consumer+xml":["*xdf"],"application/mrb-publish+xml":["*xdf"],"application/msword":["doc","dot"],"application/mxf":["mxf"],"application/n-quads":["nq"],"application/n-triples":["nt"],"application/node":["cjs"],"application/octet-stream":["bin","dms","lrf","mar","so","dist","distz","pkg","bpk","dump","elc","deploy","exe","dll","deb","dmg","iso","img","msi","msp","msm","buffer"],"application/oda":["oda"],"application/oebps-package+xml":["opf"],"application/ogg":["ogx"],"application/omdoc+xml":["omdoc"],"application/onenote":["onetoc","onetoc2","onetmp","onepkg"],"application/oxps":["oxps"],"application/p2p-overlay+xml":["relo"],"application/patch-ops-error+xml":["*xer"],"application/pdf":["pdf"],"application/pgp-encrypted":["pgp"],"application/pgp-signature":["asc","sig"],"application/pics-rules":["prf"],"application/pkcs10":["p10"],"application/pkcs7-mime":["p7m","p7c"],"application/pkcs7-signature":["p7s"],"application/pkcs8":["p8"],"application/pkix-attr-cert":["ac"],"application/pkix-cert":["cer"],"application/pkix-crl":["crl"],"application/pkix-pkipath":["pkipath"],"application/pkixcmp":["pki"],"application/pls+xml":["pls"],"application/postscript":["ai","eps","ps"],"application/provenance+xml":["provx"],"application/pskc+xml":["pskcxml"],"application/raml+yaml":["raml"],"application/rdf+xml":["rdf","owl"],"application/reginfo+xml":["rif"],"application/relax-ng-compact-syntax":["rnc"],"application/resource-lists+xml":["rl"],"application/resource-lists-diff+xml":["rld"],"application/rls-services+xml":["rs"],"application/route-apd+xml":["rapd"],"application/route-s-tsid+xml":["sls"],"application/route-usd+xml":["rusd"],"application/rpki-ghostbusters":["gbr"],"application/rpki-manifest":["mft"],"application/rpki-roa":["roa"],"application/rsd+xml":["rsd"],"application/rss+xml":["rss"],"application/rtf":["rtf"],"application/sbml+xml":["sbml"],"application/scvp-cv-request":["scq"],"application/scvp-cv-response":["scs"],"application/scvp-vp-request":["spq"],"application/scvp-vp-response":["spp"],"application/sdp":["sdp"],"application/senml+xml":["senmlx"],"application/sensml+xml":["sensmlx"],"application/set-payment-initiation":["setpay"],"application/set-registration-initiation":["setreg"],"application/shf+xml":["shf"],"application/sieve":["siv","sieve"],"application/smil+xml":["smi","smil"],"application/sparql-query":["rq"],"application/sparql-results+xml":["srx"],"application/srgs":["gram"],"application/srgs+xml":["grxml"],"application/sru+xml":["sru"],"application/ssdl+xml":["ssdl"],"application/ssml+xml":["ssml"],"application/swid+xml":["swidtag"],"application/tei+xml":["tei","teicorpus"],"application/thraud+xml":["tfi"],"application/timestamped-data":["tsd"],"application/toml":["toml"],"application/ttml+xml":["ttml"],"application/ubjson":["ubj"],"application/urc-ressheet+xml":["rsheet"],"application/urc-targetdesc+xml":["td"],"application/voicexml+xml":["vxml"],"application/wasm":["wasm"],"application/widget":["wgt"],"application/winhlp":["hlp"],"application/wsdl+xml":["wsdl"],"application/wspolicy+xml":["wspolicy"],"application/xaml+xml":["xaml"],"application/xcap-att+xml":["xav"],"application/xcap-caps+xml":["xca"],"application/xcap-diff+xml":["xdf"],"application/xcap-el+xml":["xel"],"application/xcap-error+xml":["xer"],"application/xcap-ns+xml":["xns"],"application/xenc+xml":["xenc"],"application/xhtml+xml":["xhtml","xht"],"application/xliff+xml":["xlf"],"application/xml":["xml","xsl","xsd","rng"],"application/xml-dtd":["dtd"],"application/xop+xml":["xop"],"application/xproc+xml":["xpl"],"application/xslt+xml":["*xsl","xslt"],"application/xspf+xml":["xspf"],"application/xv+xml":["mxml","xhvml","xvml","xvm"],"application/yang":["yang"],"application/yin+xml":["yin"],"application/zip":["zip"],"audio/3gpp":["*3gpp"],"audio/adpcm":["adp"],"audio/amr":["amr"],"audio/basic":["au","snd"],"audio/midi":["mid","midi","kar","rmi"],"audio/mobile-xmf":["mxmf"],"audio/mp3":["*mp3"],"audio/mp4":["m4a","mp4a"],"audio/mpeg":["mpga","mp2","mp2a","mp3","m2a","m3a"],"audio/ogg":["oga","ogg","spx","opus"],"audio/s3m":["s3m"],"audio/silk":["sil"],"audio/wav":["wav"],"audio/wave":["*wav"],"audio/webm":["weba"],"audio/xm":["xm"],"font/collection":["ttc"],"font/otf":["otf"],"font/ttf":["ttf"],"font/woff":["woff"],"font/woff2":["woff2"],"image/aces":["exr"],"image/apng":["apng"],"image/avif":["avif"],"image/bmp":["bmp"],"image/cgm":["cgm"],"image/dicom-rle":["drle"],"image/emf":["emf"],"image/fits":["fits"],"image/g3fax":["g3"],"image/gif":["gif"],"image/heic":["heic"],"image/heic-sequence":["heics"],"image/heif":["heif"],"image/heif-sequence":["heifs"],"image/hej2k":["hej2"],"image/hsj2":["hsj2"],"image/ief":["ief"],"image/jls":["jls"],"image/jp2":["jp2","jpg2"],"image/jpeg":["jpeg","jpg","jpe"],"image/jph":["jph"],"image/jphc":["jhc"],"image/jpm":["jpm"],"image/jpx":["jpx","jpf"],"image/jxr":["jxr"],"image/jxra":["jxra"],"image/jxrs":["jxrs"],"image/jxs":["jxs"],"image/jxsc":["jxsc"],"image/jxsi":["jxsi"],"image/jxss":["jxss"],"image/ktx":["ktx"],"image/ktx2":["ktx2"],"image/png":["png"],"image/sgi":["sgi"],"image/svg+xml":["svg","svgz"],"image/t38":["t38"],"image/tiff":["tif","tiff"],"image/tiff-fx":["tfx"],"image/webp":["webp"],"image/wmf":["wmf"],"message/disposition-notification":["disposition-notification"],"message/global":["u8msg"],"message/global-delivery-status":["u8dsn"],"message/global-disposition-notification":["u8mdn"],"message/global-headers":["u8hdr"],"message/rfc822":["eml","mime"],"model/3mf":["3mf"],"model/gltf+json":["gltf"],"model/gltf-binary":["glb"],"model/iges":["igs","iges"],"model/mesh":["msh","mesh","silo"],"model/mtl":["mtl"],"model/obj":["obj"],"model/stl":["stl"],"model/vrml":["wrl","vrml"],"model/x3d+binary":["*x3db","x3dbz"],"model/x3d+fastinfoset":["x3db"],"model/x3d+vrml":["*x3dv","x3dvz"],"model/x3d+xml":["x3d","x3dz"],"model/x3d-vrml":["x3dv"],"text/cache-manifest":["appcache","manifest"],"text/calendar":["ics","ifb"],"text/coffeescript":["coffee","litcoffee"],"text/css":["css"],"text/csv":["csv"],"text/html":["html","htm","shtml"],"text/jade":["jade"],"text/jsx":["jsx"],"text/less":["less"],"text/markdown":["markdown","md"],"text/mathml":["mml"],"text/mdx":["mdx"],"text/n3":["n3"],"text/plain":["txt","text","conf","def","list","log","in","ini"],"text/richtext":["rtx"],"text/rtf":["*rtf"],"text/sgml":["sgml","sgm"],"text/shex":["shex"],"text/slim":["slim","slm"],"text/spdx":["spdx"],"text/stylus":["stylus","styl"],"text/tab-separated-values":["tsv"],"text/troff":["t","tr","roff","man","me","ms"],"text/turtle":["ttl"],"text/uri-list":["uri","uris","urls"],"text/vcard":["vcard"],"text/vtt":["vtt"],"text/xml":["*xml"],"text/yaml":["yaml","yml"],"video/3gpp":["3gp","3gpp"],"video/3gpp2":["3g2"],"video/h261":["h261"],"video/h263":["h263"],"video/h264":["h264"],"video/iso.segment":["m4s"],"video/jpeg":["jpgv"],"video/jpm":["*jpm","jpgm"],"video/mj2":["mj2","mjp2"],"video/mp2t":["ts"],"video/mp4":["mp4","mp4v","mpg4"],"video/mpeg":["mpeg","mpg","mpe","m1v","m2v"],"video/ogg":["ogv"],"video/quicktime":["qt","mov"],"video/webm":["webm"]}; 2 | -------------------------------------------------------------------------------- /src/utils/addClient.js: -------------------------------------------------------------------------------- 1 | export const addClient = response => 2 | response.writeHead(200, { 3 | 'Content-Type': 'text/event-stream', 4 | 'Cache-Control': 'no-cache', 5 | Connection: 'keep-alive' 6 | }); 7 | -------------------------------------------------------------------------------- /src/utils/error.js: -------------------------------------------------------------------------------- 1 | import { log, styles } from './index.js'; 2 | 3 | export const error = message => log(message, styles.red); 4 | -------------------------------------------------------------------------------- /src/utils/getFilePath.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { options } from '../index.js'; 3 | 4 | export const getFilePath = request => { 5 | const { root } = options; 6 | 7 | if (request.url == '/') return `${root}/index.html`; 8 | 9 | if (!request.url.includes('.')) { 10 | const testFilepath = `${root}/${request.url}.html`; 11 | 12 | if (fs.existsSync(testFilepath)) return testFilepath; 13 | } 14 | 15 | return root + request.url; 16 | }; 17 | -------------------------------------------------------------------------------- /src/utils/getIp.js: -------------------------------------------------------------------------------- 1 | import { networkInterfaces } from 'os'; 2 | 3 | export const getIp = () => 4 | Object.values(networkInterfaces()) 5 | .flat() 6 | .find(ip => ip.family == 'IPv4' && !ip.internal).address; 7 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export { addClient } from './addClient.js'; 2 | export { error } from './error.js'; 3 | export { getFilePath } from './getFilePath.js'; 4 | export { getIp } from './getIp.js'; 5 | export { injectContent } from './injectContent.js'; 6 | export { log } from './log.js'; 7 | export { mimeType } from './mimeType.js'; 8 | export { setRoot } from './setRoot.js'; 9 | export { show404 } from './show404.js'; 10 | export { showError } from './showError.js'; 11 | export { showFile } from './showFile.js'; 12 | export { styles } from './styles.js'; 13 | -------------------------------------------------------------------------------- /src/utils/injectContent.js: -------------------------------------------------------------------------------- 1 | import injectCode from '../injectCode.js'; 2 | 3 | export const injectContent = content => { 4 | const index = content.indexOf(''); 5 | const start = content.slice(0, index); 6 | const end = content.slice(index); 7 | 8 | return start + injectCode() + end; 9 | }; 10 | -------------------------------------------------------------------------------- /src/utils/log.js: -------------------------------------------------------------------------------- 1 | import { styles } from './index.js'; 2 | 3 | export const log = (message, color = styles.green) => 4 | console.log(color + message + styles.reset); 5 | -------------------------------------------------------------------------------- /src/utils/mimeType.js: -------------------------------------------------------------------------------- 1 | import mimeTypes from '../mimeTypes.js'; 2 | 3 | export const mimeType = extension => 4 | Object.keys(mimeTypes).find(key => mimeTypes[key].includes(extension)); 5 | -------------------------------------------------------------------------------- /src/utils/setRoot.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { options, defaultRoot } from '../index.js'; 3 | 4 | export const setRoot = () => { 5 | const { root } = options; 6 | const isRoot = !root || root == '.'; 7 | 8 | return isRoot && fs.existsSync(defaultRoot) ? defaultRoot : root; 9 | }; 10 | -------------------------------------------------------------------------------- /src/utils/show404.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { options, encoding } from '../index.js'; 3 | 4 | export const show404 = response => { 5 | fs.readFile(`${options.root}/404.html`, (error, content) => { 6 | response.writeHead(404, { 'Content-Type': 'text/html' }); 7 | response.end(content, encoding); 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /src/utils/showError.js: -------------------------------------------------------------------------------- 1 | export const showError = (response, error) => { 2 | response.writeHead(500); 3 | response.end(`Error ${error.code}\n`); 4 | }; 5 | -------------------------------------------------------------------------------- /src/utils/showFile.js: -------------------------------------------------------------------------------- 1 | import { encoding } from '../index.js'; 2 | 3 | export const showFile = (response, content, contentType) => { 4 | response.writeHead(200, { 'Content-Type': contentType }); 5 | response.end(content, encoding); 6 | }; 7 | -------------------------------------------------------------------------------- /src/utils/styles.js: -------------------------------------------------------------------------------- 1 | export const styles = { 2 | reset: '\x1b[0m', 3 | red: '\x1b[31m', 4 | green: '\x1b[32m' 5 | }; 6 | -------------------------------------------------------------------------------- /test/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nativew/serve/f0a7aaa866e23ae91e304a49189afac25ee92512/test/favicon.ico -------------------------------------------------------------------------------- /test/images/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nativew/serve/f0a7aaa866e23ae91e304a49189afac25ee92512/test/images/apple-touch-icon.png -------------------------------------------------------------------------------- /test/images/google-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nativew/serve/f0a7aaa866e23ae91e304a49189afac25ee92512/test/images/google-touch-icon.png -------------------------------------------------------------------------------- /test/images/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/images/mask-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Serve 🍛 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Serve", 3 | "short_name": "Serve", 4 | "icons": [ 5 | { 6 | "src": "images/google-touch-icon.png", 7 | "sizes": "512x512" 8 | } 9 | ], 10 | "background_color": "#ffffff", 11 | "theme_color": "#ffffff", 12 | "display": "fullscreen" 13 | } 14 | -------------------------------------------------------------------------------- /test/scripts/main.js: -------------------------------------------------------------------------------- 1 | var x=t=>({kind:"field",placement:"own",key:"_styles",descriptor:{},initializer(){this._setStyles(t)}});var T=t=>t.split("-").map(r=>r[0].toUpperCase()+r.slice(1)).join("");var h=(t,r)=>e=>{let{kind:i,elements:n}=e;return r&&n.push(x(r)),{kind:i,elements:n,finisher(o){o.prototype._name=T(t),window.customElements.define(t,o)}}},y=t=>{try{let r=new CSSStyleSheet;return r.replaceSync(t),r}catch(r){return t[0]}};var m=class extends HTMLElement{constructor(){super();this.attachShadow({mode:"open"})}_setStyles(t){t=[].concat(t),this.shadowRoot.adoptedStyleSheets?this.shadowRoot.adoptedStyleSheets=t:this._css=t.join("")}_insertRender(){let t=this._css?``:"";this.shadowRoot.innerHTML=t+this.render()}connectedCallback(){this._insertRender(),this.connected()}disconnectedCallback(){this.disconnected()}adoptedCallback(){this.adopted()}attributeChangedCallback(t,r,e){this.attributeChanged(t,r,e)}update(){this._insertRender()}render(){}connected(){}disconnected(){}adopted(){}attributeChanged(t,r,e){}};var z=y`:host{display:block;font-family:sans-serif} 2 | `,v=z;var R=y`:host{} 3 | `,w=R;function L(t,r,e,i){var n=k();if(i)for(var o=0;o=0;a--){var s=e[r.placement];s.splice(s.indexOf(r.key),1);var c=this.fromElementDescriptor(r),l=this.toElementFinisherExtras((0,o[a])(c)||c);r=l.element,this.addElementPlacement(r,e),l.finisher&&n.push(l.finisher);var p=l.extras;if(p){for(var d=0;d=0;n--){var o=this.fromClassDescriptor(r),a=this.toClassDescriptor((0,e[n])(o)||o);if(a.finisher!==void 0&&i.push(a.finisher),a.elements!==void 0){r=a.elements;for(var s=0;st.length)&&(r=t.length);for(var e=0,i=new Array(r);e\u{1F35B} Served 6 | `}}]}},m);function K(t,r,e,i){var n=A();if(i)for(var o=0;o=0;a--){var s=e[r.placement];s.splice(s.indexOf(r.key),1);var c=this.fromElementDescriptor(r),l=this.toElementFinisherExtras((0,o[a])(c)||c);r=l.element,this.addElementPlacement(r,e),l.finisher&&n.push(l.finisher);var p=l.extras;if(p){for(var d=0;d=0;n--){var o=this.fromClassDescriptor(r),a=this.toClassDescriptor((0,e[n])(o)||o);if(a.finisher!==void 0&&i.push(a.finisher),a.elements!==void 0){r=a.elements;for(var s=0;st.length)&&(r=t.length);for(var e=0,i=new Array(r);e 9 | `}}]}},m); 10 | --------------------------------------------------------------------------------