diff --git a/bin/serve.js b/bin/serve.js index 45e583f3..b52c99cf 100755 --- a/bin/serve.js +++ b/bin/serve.js @@ -18,6 +18,7 @@ const handler = require('serve-handler'); const schema = require('@zeit/schemas/deployment/config-static'); const boxen = require('boxen'); const compression = require('compression'); +const chardet = require('chardet'); // Utilities const pkg = require('../package'); @@ -81,6 +82,8 @@ const getHelp = () => chalk` -c, --config Specify custom path to \`serve.json\` + --charset override header charset for every http request of html, htm, css file + -n, --no-clipboard Do not copy the local address to the clipboard -S, --symlinks Resolve symlinks instead of showing 404 errors @@ -167,16 +170,84 @@ const getNetworkAddress = () => { } }; +const extractCharset = (filePath, forcedCharset) => { + if (forcedCharset) { + return forcedCharset; + } + + return chardet.detectFileSync(filePath); +}; + +const realPath = (relativePath) => { + const possiblePaths = [ + path.join(relativePath, 'index.html'), + relativePath.endsWith('/') ? relativePath.replace(/\/$/g, '.html') : `${relativePath}.html` + ].filter((item) => path.basename(item) !== '.html'); + + for (let index = 0; index < possiblePaths.length; index++) { + const related = possiblePaths[index]; + const absolutePath = path.join(process.cwd(), related); + let exist = false; + try { + exist = fs.existsSync(absolutePath); + } catch (err) { + continue; + } + + if (exist) { + return absolutePath; + } + } + + return null; +}; + const startEndpoint = (endpoint, config, args, previous) => { const {isTTY} = process.stdout; const clipboard = args['--no-clipboard'] !== true; const compress = args['--no-compression'] !== true; + const allowExt = [ + 'css', + 'html', + 'htm', + 'shtml' + ]; const server = http.createServer(async (request, response) => { if (compress) { await compressionHandler(request, response); } + let fullPath = path.resolve() + request.url; + let relativePath = request.url; + if (path.extname(fullPath) === '') { + fullPath = realPath(request.url) ? realPath(request.url) : fullPath; + relativePath = path.relative(process.cwd(), fullPath); + } + + const extention = fullPath.split(/\#|\?/)[0].split('.').pop(); + + let charset = 'utf-8'; + if (allowExt.indexOf(extention) >= 0) { + try { + const tempChar = extractCharset(fullPath, config.charset); + charset = config.charset ? config.charset : (tempChar ? tempChar : 'utf-8'); + if (!config.headers) { + config.headers = []; + } + + config.headers.push({ + source: relativePath, + headers: [{ + key: 'Content-Type', + value: `text/${extention}; charset=${config.charset ? config.charset : charset}` + }] + }); + } catch (err) { + console.log(''); + } + } // if (allowExt.indexOf(extention) >= 0) + return handler(request, response, config); }); @@ -341,6 +412,7 @@ const loadConfig = async (cwd, entry, args) => { '--single': Boolean, '--debug': Boolean, '--config': String, + '--charset': String, '--no-clipboard': Boolean, '--no-compression': Boolean, '--symlinks': Boolean, @@ -405,6 +477,10 @@ const loadConfig = async (cwd, entry, args) => { config.symlinks = true; } + if (args['--charset']) { + config.charset = args['--charset']; + } + for (const endpoint of args['--listen']) { startEndpoint(endpoint, config, args); } diff --git a/package.json b/package.json index 9f1d50ae..3c18269e 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "chalk": "2.4.1", "clipboardy": "1.2.3", "compression": "1.7.3", + "chardet": "0.8.0", "serve-handler": "6.0.2", "update-check": "1.5.2" }