美文网首页
从源码分析live-server 是如何启动本地服务并且托管文件

从源码分析live-server 是如何启动本地服务并且托管文件

作者: 相遇一头猪 | 来源:发表于2022-07-20 18:13 被阅读0次

上一篇文章提到了在小程序本地开发过程中使用 live-server 启动本地服务托管图片,接下来就来探究其原理。

在启动 live-server 后,live-server 会启动一个本地服务并且默认会打开浏览器,例如我在 live-server 内通过 node live-server.js 启动,浏览器会打开如下页面:

image.png

从源码里,发现 live-server 使用了 serve-index 中间件来提供页面展示能力:

image.png

live-server 启动时会给 staticServer 默认传入当前工作目录 process.cwd():

image.png

然后在 staticServer 内用第三方库 send 把当前文件发送给浏览器:

image.png

这里的 reqpath 就是默认是当前执行目录的地址:

image.png

也就是说 send 会去读取这个地址的内容,然后发送给访问者(这里指浏览器)。
但是,这个地址很明显是一个文件夹,为什么最后会变成一个文件列表页面呢?

这就是 serve-index 内部实现的功能了。serve-index 在返回的中间件函数里会做如下操作:

  1. 判断当前 path 是否存在文件夹内或者名称是否过长;
  2. 如果当前 path 不是文件夹 就执行下一个中间件;
  3. 如果是文件夹则用 fs.readdir 读取文件夹,并在回调内判断当前 mediaType 是否是
    'text/html'、 'text/plain'、 'application/json'这三种,如果是就执行对应的 Response 方法。
function serveIndex(root, options) {
 
     return function (req, res, next) {
          fs.stat(path, function(err, stat){
              if (err && err.code === 'ENOENT') {
                return next();
              }
        
              if (err) {
                err.status = err.code === 'ENAMETOOLONG'
                  ? 414
                  : 500;
                return next(err);
              }
        
              if (!stat.isDirectory()) return next();
            
              fs.readdir(path, function(err, files){
                // ......
        
                // content-negotiation
                var accept = accepts(req);
                var type = accept.type(mediaTypes);
        
                // not acceptable
                if (!type) return next(createError(406));
                serveIndex[mediaType[type]](req, res, files, next, originalDir, showUp, icons, path, view, template, stylesheet);
              });
            });
          };
        };
    }
}

例如,mediaType === 'text/html' ,则会执行 serveIndex.html

serveIndex.html = function _html(req, res, files, next, dir, showUp, icons, path, view, template, stylesheet) {
    var render = typeof template !== 'function'
        ? createHtmlRender(template)
        : template
    
    //.....
    
    // stat all files
    stat(path, files, function (err, stats) {
        if (err) return next(err);

        // ......
        
        // read stylesheet
        fs.readFile(stylesheet, 'utf8', function (err, style) {
            if (err) return next(err);

            // create locals for rendering
            var locals = {
                directory: dir,
                displayIcons: Boolean(icons),
                fileList: fileList,
                path: path,
                style: style,
                viewName: view
            };

            // render html
            render(locals, function (err, body) {
                if (err) return next(err);
                send(res, 'text/html', body)
            });
        });
    });
};

serve-index 提供了默认的页面模板,在返回页面前对页面内容进行替换、生成文件列表等:

/**
 * Create function to render html.
 */

function createHtmlRender(template) {
    return function render(locals, callback) {
        // read template
        fs.readFile(template, 'utf8', function (err, str) {
            if (err) return callback(err);

            var body = str
                .replace(/\{style\}/g, locals.style.concat(iconStyle(locals.fileList, locals.displayIcons)))
                .replace(/\{files\}/g, createHtmlFileList(locals.fileList, locals.directory, locals.displayIcons, locals.viewName))
                .replace(/\{directory\}/g, escapeHtml(locals.directory))
                .replace(/\{linked-path\}/g, htmlPath(locals.directory));

            callback(null, body);
        });
    };
}


function createHtmlFileList(files, dir, useIcons, view) {
    var html = '<ul id="files" class="view-' + escapeHtml(view) + '">'
        + (view == 'details' ? (
            '<li class="header">'
            + '<span class="name">Name</span>'
            + '<span class="size">Size</span>'
            + '<span class="date">Modified</span>'
            + '</li>') : '');

    html += files.map(function (file) {
        var classes = [];
        var isDir = file.stat && file.stat.isDirectory();
        var path = dir.split('/').map(function (c) { return encodeURIComponent(c); });

        if (useIcons) {
            classes.push('icon');

            if (isDir) {
                classes.push('icon-directory');
            } else {
                var ext = extname(file.name);
                var icon = iconLookup(file.name);

                classes.push('icon');
                classes.push('icon-' + ext.substring(1));

                if (classes.indexOf(icon.className) === -1) {
                    classes.push(icon.className);
                }
            }
        }

        path.push(encodeURIComponent(file.name));

        var date = file.stat && file.name !== '..'
            ? file.stat.mtime.toLocaleDateString() + ' ' + file.stat.mtime.toLocaleTimeString()
            : '';
        var size = file.stat && !isDir
            ? file.stat.size
            : '';

        return '<li><a href="'
            + escapeHtml(normalizeSlashes(normalize(path.join('/'))))
            + '" class="' + escapeHtml(classes.join(' ')) + '"'
            + ' title="' + escapeHtml(file.name) + '">'
            + '<span class="name">' + escapeHtml(file.name) + '</span>'
            + '<span class="size">' + escapeHtml(size) + '</span>'
            + '<span class="date">' + escapeHtml(date) + '</span>'
            + '</a></li>';
    }).join('\n');

    html += '</ul>';

    return html;
}

回到 live-server, live-server 启动时默认会执行 open(openURL + openPath) 打开浏览器,页面地址为 http://127.0.0.1:8080

image.png

此时,请求进入到 serve-index 中间件会生成并发送一个html文件到浏览器,也就是文章的第一张图。

从上文我们知道 live-server 是利用 send 库来返回文件内容的,而 serve-index 本身无法访问具体某个文件。

例如当我写了如下node程序 :

// hello.js
const express = require('express')
const app = express()
const serveIndex = require('serve-index');

app.use('/api',
  serveIndex(process.cwd(), { icons: true })
)

app.listen(3000);
console.log('Express started on port 3000');

用了 serve-index 作为中间件,打开浏览器访问 localhost:3000/api

image.png

serve-index 返回了一个html,这很正常。接下来点击 hello.js 会报错:

image.png

因为 hello.js 不是文件夹,serve-index 不处理,而我们的 node 程序也没有处理这个路由,所以访问不到。

接下来就是整活了,改写 serve-index 代码从而实现 可以返回具体的文件内容:

  1. 读取文件内容
  2. 利用 mime 获取文件 mime 类型
  3. 设置响应头
  4. 返回文件内容
  5. 将控制权交给下一个中间件

具体代码如下:

  if (!stat.isDirectory()) {
        // return next();
        var body = fs.readFileSync(path);
        var type = mime.lookup(extname(path));
        res.setHeader('Content-Type', type)
        res.setHeader('Content-Length', Buffer.byteLength(body, 'utf8'))
        res.send(body);
        return next();
     }

改写后,再去点击 hello.js 就可以访问到了:


image.png

总结

live-server 使用 serve-index中间件 提供展示文件列表、点击文件、搜索当前层级文件等能力。由于serve-index只能查看文件夹的局限性, live-server 内部使用 send 库实现 staticServer 中间件来提供访问文件(路由)的能力。

相关文章

网友评论

      本文标题:从源码分析live-server 是如何启动本地服务并且托管文件

      本文链接:https://www.haomeiwen.com/subject/pntpirtx.html