npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

koa-web

v1.2.13

Published

koa web

Downloads

8

Readme

koa-web

  • 解决WEB开发者不易发现的困惑,WEB开发原来可以这么玩。
  • 可使用koa或者koa-web扩展更多的功能实现。
  • 是全栈开发的桥梁,它将改变繁琐开发的习惯,让代码一目了然。

应用场景

  • 开发WEB页面实时预览效果(无需重启服务)
  • SEO、CSS和JS设置更轻松
  • 简化GET和POST请求处理
  • 也适用于API、web代理、压力测试
  • 代码层次分明,开发大项目优势明显
  • 域名灵活绑定路径,适合开发多项目
  • 更多不确定的用途可能也会在这里 (待发现...)

依赖包

下载DEMO源码

安装 koa-web

npm i koa-web

项目配置

const Koa = require("koa");
const KoaWeb = require('koa-web');

const app = new Koa();

app.use(
  KoaWeb(
    {
      // 项目根路径
      path: __dirname,

      // 静态文件路径组,默认指向static
      // 模式1: static: "static"
      // 模式2: static: ["static"]
      // 模式3: static: {static: "static"}
      // 模式1、2、3效果相同,如果想指定项目外目录须使用模式3
      static: {
        static: "static"
      },

      // 静态文件缓存时间,默认0毫秒
      staticMaxage: 0,

      // 视图模块设置
      view: {
        // 视图默认目录 html
        path: "html",

        // 视图文件默认扩展名 html, 如 "hello.html"
        ext: "html",

        // 域名(前缀)绑定路径设置
        domains: {
          // 如访问: http://www.hello.myweb.com:90
          // 实际解析到: myweb90/tools 目录
          // 相当于 view 中的 path 设置为: html/myweb90/tools
          // 左边: www.*.{abc}.com:{port}不能混合参数
          // 如 www.*.{abc}{def} 中的 {abc}{def} 将无法解析
          // 右边: {abc}{port}/tools 可以随意混合参数
          "www.*.{abc}.com:{port}": "{abc}{port}/tools",

          // 权重1: 域名前缀数量越多,权重越高
          // 如访问: http://www.a.com:88 将解析到 xyz
          // 如访问: http://www.b.com:88 将解析到 abc
          "www:88": "abc",
          "www.a:88": "xyz",

          // 权重2: 参数越少,权重越高
          // 如访问: http://www.a.com 将解析到 hello
          // 如访问: http://www.b.com 将解析到 world
          "www.a.com": "hello",
          "www.{name}.com": "world", // 等价于: "www.*.com": "world"

          // 绑定所有域名/IP的90端口, 但权重最低
          ":90": "path",
        
          // :90 权重高于 :{path}
          // ":{path}": "new/path",
        },

        // 默认解析路径,不支持变量设置,如: {abc}/tpl
        // 当domains中配对不成功时生效
        // 默认为空,即根目录 html
        domainDefault: ""
      },

      // 热加载,如果不缓存,每次文件改动会重新加载
      // 生产环境可设置为true提高性能
      cache: false,

      // 错误页面默认设置,只支持 404 和 500 错误
      errors: {
        404: "errors/404",
        500: "errors/500"
      },

      // json配置文件全局配置,会和视图目录中的 ".json" 文件合并
      json: {
        // js: "hello.js",
        // css: ['hello.css', 'world.css'],
        // layout: "layout/test"
      },

      // 扩展页面默认Content-Type设置,如果不设置则以mime-types扩展名为准
      // extTypes: {
      //   // 例如 /hello/world.txt 的设置 Content-Type:"text/plan"
      //   txt: "text/plan"
      // },

      // 扩展页面执行后回调
      extCalls: {
        // 访问: http://localhost:3000/任意路径.sh
        // 同步模式
        // extData: 调用任意路径下的扩展页面数据
        sh: (extData, hd, data, files) => {
          // 优先级高于 extTypes 配置
          hd.ctx.type = 'text/plain';

          return extData;
        },

        // 访问: http://localhost:3000/任意路径
        // 异步模式
        // 默认页面 htm、html和无扩展
        // html: async () => {
        //   return "hello world!";
        // }
      },

      // 如果不习惯默认的nunjucks模板引擎, 可以使用render进行重设
      // template: 模板页面源码
      // viewData: 渲染的数据
      // info: 如果template不能满足要求,info中有更多信息提供使用
      // render: (template, viewData, info) => {
      //   // ****字符串模式****
      //   let ejs = require('ejs');
      //   return ejs.render(template, viewData);

      //   // *****文件模式*****
      //   let error;
      //   let html;
      //   ejs.renderFile(info.filename, viewData, (e, h) => {
      //     error = e;
      //     html = h;
      //   });

      //   if (error) {
      //     info.handle.ctx.response.status = 500;
      //     return error['message'];
      //   }
      //   return html;
      // },
    }
  )
);

// app.use(KoaWeb({path: __dirname})); // 快捷默认配置

app.listen(3000, () => {
  console.log("server is running at http://localhost:3000");
});

基本用法

创建主页面

  • 默认路径对应 view > path 配置
  • 默认路径: /html 默认扩展: .html
  • 新建文件/html/index.html 并填写 Hello World !
  • 访问方式(效果一样):
    • http://localhost:3000
    • http://localhost:3000/index
    • http://localhost:3000/index.htm
    • http://localhost:3000/index.html
  • 当cache=false(默认false)时不需要重启Koa服务,就可以看到实时效果

静态文件

  • 创建/static/test.jpg文件
  • 访问路径: http://localhost:3000/static/test.jpg

错误页面

  • 404: /html/errors/404.html
  • 500: /html/errors/500.html
  • 页面中设置 {{ code }}: 错误状态码
  • 页面中设置 {{ message }}: 错误消息

创建页面DEMO

  • 视图文件:/html/hello/world.html
  • 模板文件:/html/hello/world.js
  • 配置文件:/html/hello/world.json
  • 这3个文件至少存在一个
/html/hello/world.html
<div>{{ sayHello }}</div>
<div>{{ sayWorld }}</div>
模板引擎说明文档 nunjucks
/html/hello/world.js
/**
 * 入口文,hd、data和files都可以不设置
 * 如 module.exports = async () => {}; 也是可以的
 * data和files是通过 require("formidable") 实现
 * @param {*} hd 主调用函数
 * @param {*} data form-data、x-www-form-urlencoded和JSON数据会自动转化为该对象
 * @param {*} files 只有在form-data类型中产生
 * @returns 
 */
module.exports = async (hd, data, files) => {

  // hd.ctx.body = '设置了就只会显示这一段';

  // console.log(hd.isPost()); // 判断是否是POST提交
  
  // console.log(hd.ctx.query); // url 传递的参数
  // console.log(hd.ctx.header); // http 头文件
  // console.log(data); // data == hd.ctx.app.info.data
  // console.log(files); // files == hd.ctx.app.info.files

  // hd.ctx.app.info.status: 解析状态
  // hd.ctx.app.info.errMessage: 解析错误提示
  // hd.ctx.app.info.body: 原始POST提交字符串
  // hd.ctx.app.info.type: Content-Type标记 form、xform、json
  // form: multipart/form-data
  // xform: application/x-www-form-urlencoded
  // json: application/json
  // console.log(hd.ctx.app.info); 

  // 获取项目中文件路径: /项目路径/hello.js
  // console.log(hd.path("hello.js"));
  // 获取视图中文件路径: /项目路径/html/hello.js
  // console.log(hd.viewPath("hello.js"));

  // 设置模板页面中函数(nunjucks模板)
  hd.view('sayHello', "hello hello!");
  // 多函数设置
  // hd.view({sayHello: "hello hello!", sayOther: "hello other"});
  // 获取函数
  // console.log(hd.getView());
  // console.log(hd.getView('sayHello'));

  // SEO优化(TDK)
  // hd.title('页面标题');
  // console.log(hd.getTitle());
  hd.keywords('关键词');
  // console.log(hd.getKeywords());
  // hd.description('描述');
  // console.log(hd.getDescription());

  // 动态设置页面内CSS和JS
  hd.style(".body{color:#F00;}"); // style只能在head中
  hd.script("var top = 'top';", true); // js 代码在 head 中
  hd.script("var bottom = 'bottom';"); // js 代码在body标签尾部

  // 动态加载css和js文件
  // 加载css不管加不加@都在head标签中或页面顶部
  // hd.css('/static/c1.css', {class: "test"}); // 可设置属性
  hd.css('/static/c1.css');
  hd.css(['/static/c2.css', '@/static/c3.css']);
  // 动态js中加@表示在head标签中或页面顶部
  // hd.js(['/static/j2.js', '@/static/j3.js'], {class: "test"}); // 可设置属性
  hd.js('/static/j1.js');
  hd.js(['/static/j2.js', '@/static/j3.js']);

  // 设置页面缓存10秒
  // hd.age(10);
  // 如不设置,hd.age(), 则页面缓存默认是 staticMaxage 配置值

  return {
    sayWorld: "hello world!"
  };
}
/html/hello/world.json
  • layout默认为不设置
  • css和js可以是字符串或数组
{
  "layout": "hello/layout",
  "css": "/static/abc.css",
  "js": [
    "/static/abc.js",
    "@/static/def.js"
  ],
  "title": "this title is json",
  "keywords": "this keywords is json",
  "description": "this description is json"
}
/html/hello/layout.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
</head>
<body>
<div>{{ sayLayout }}</div>
{{ __layout__ | safe }}
</body>
</html>
/html/hello/layout.js
module.exports = async (hd) => {
  hd.view('sayLayout', "hello layout!");
}

页面源码效果DEMO

访问页面 http://localhost:3000/hello/world 源码如下
<!DOCTYPE html>
<html lang="en">
<head>
  <title>this title is json</title>
  <meta name="keywords" content="关键词" />
  <meta name="description" content="this description is json" />
  <meta charset="UTF-8" />
  <link rel="stylesheet" href="/static/abc.css" />
  <link rel="stylesheet" href="/static/c1.css" />
  <link rel="stylesheet" href="/static/c2.css" />
  <link rel="stylesheet" href="/static/c3.css" />
  <script src="/static/def.js"></script>
  <script src="/static/j3.js"></script>
  <style>
    .body{color:#F00;}
  </style>
  <script>
    var top = 'top';
  </script>
</head>
<body>
<div>hello layout!</div>
<div>hello hello!</div>
<div>hello world!</div>
<script src="/static/abc.js"></script>
<script src="/static/j1.js"></script>
<script src="/static/j2.js"></script>
<script>
  var bottom = 'bottom';
</script>
</body>
</html>
访问页面 http://localhost:3000/hello/world.json 源码如下
{"sayWorld":"hello world!"}
  • 扩展名.json不会渲染/html/hello/world.html页面

页面之间的调用 call

创建调用页面
  • /html/test/data.html
<div>{{ sayHello }}</div>
<div>{{ sayWorld }}</div>
  • /html/test/data.js
module.exports = async (hd) => {
  return {
    sayHello: "hello",
    sayWorld: hd.getView('sayWorld')
  };
};
内部使用示例
/html/test/call.js
module.exports = async (hd) => {
  return await hd.call(
    // 与hd.http模式不同
    // hd.call 内部访问 (相对路径、绝对路径)
    // hd.http 外部访问 (url模式)
    // 可以带参数访问,如:
    // /test/data.js?say=hello
    // data 解析为:/test/data
    // ./data 解析为:/test/data
    // ../data 解析为:/data
    // $.abc 解析为:/test/call.abc
    '/test/data',
    {
      sayWorld: "MyWorld"
    }
  );
};
外部使用示例(以koa-route为例)
  • 如果koa-web不能满足当下要求时,可与其他路由框架进行结合使用
  • 需要另行安装: npm i koa-router
const Koa = require("koa");
const KoaWeb = require('koa-web');
const router = require("koa-router")();

const app = new Koa();

app.use(KoaWeb({path: __dirname}));

router.get("/route/web", async (ctx, next) => {
  await next();
  if (!ctx.body) {
    let ret = await ctx.app.call(
      // 因为是外部调用,等同于 /test/data
      'test/data',
      {
        sayWorld: "MyWorld"
      }
    );
    ctx.body = ret;
  }
});

app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000, () => {
  console.log("server is running at http://localhost:3000");
});
访问以下两个链接
  • http://localhost:3000/test/call
  • http://localhost:3000/route/web
其结果相同,预览源码如下
<div>hello</div>
<div>MyWorld</div>

HTTP快捷应用

单个请求实例

  • 支持 http 和 https
  • /html/test/http.js

const fs = require("fs");

/**
 * 提交方式说明
 * get: 非POST提交
 * form: multipart/form-data
 * xform: application/x-www-form-urlencoded
 * json: application/json
 * @param {*} hd 
 * @returns 
 */
module.exports = async (hd) => {
  let options = {
    url: 'http://localhost:3000/test/call?a=123&b=456',

    // 数据代理,支持:get、form、xform、json
    // get 通过浏览器URL和配置url进行合并,其他通过POST和配置data进行合并
    // 默认 {get: false, post: false, header:false}
    // {get: false, all: true} 等价于 {get: false, post: true, header:true, method:true}
    proxy: {
      // get: false,
      // post: false,
      // header: false,
      // method: false,
      all: true
    },

    // 数据覆盖模式: 默认 asc
    // asc: POST覆盖配置
    // desc: 配置覆盖POST
    proxyCover: 'asc',

    // POST提交数据
    data: {
      abc: "test",
      hello: {
        world: "Is Ok"
      }
    },

    // 提交方式: get、form、xform、json
    // 如果不设置,默认以浏览器提交方式一致
    // type: 'json',

    // 如果未设定,默认以type设置自动产生get或post自动匹配
    // 不区分大小写: GET POST PUT DELETE HEAD 等
    // method: 'post',

    // 上传文件
    files: {
      // 直接以文件路径提交
      str: "/tmp/abc.jpg",
      // Buffer
      buffer: new Buffer(10),
      bufferFrom: Buffer.from([0, 0]),
      // 使用 fs.createReadStream 需判断文件是否存在,否则会报错
      // fs: fs.createReadStream("/tmp/abc.jpg", { highWaterMark: 10 * 1024 * 1024 }),
      // 数组模式
      array: {
        // path可以是str、buffer和fs
        path: "/tmp/abc.jpg",
        // 可选,默认从path抽取
        name: "abc.jpg",
        // 可选,默认后缀名.jpg进行判断
        type: "image/jpeg"
      }
    },

    // 上传文件最大字节数,默认 10M
    fileMaxSize: 10 * 1024 * 1024,

    // 头文件设置,以下参数将设置无效
    // host, referer, content-length, transfer-encoding, content-type
    header: {
      'User-Agent': 'Mozilla/5.0'
    },

    // 超时设置,默认15秒
    timeout: 15000,

    // 数据返回类型,默认full, HTML是String或Buffer
    // full: 全部返回,{"status": true, "code": 200, "data": HTML, "ms": 62}
    // text:HTML
    // json: JSON.parse("HTML")
    dataType: 'full',

    // 获取二进制,默认为false,如果获取图片之类的须设置为true
    // 当设置为true时对dataType: "json" 无效
    // buffer: false,

    // 数据结果调试
    // index: 请求下标,在httpAll下效果显著
    // data: 页面信息
    // debug: (index, data) => {},
    // debug: async (index, data) => {},
  };

  let info = await hd.http(
    options, // 配置信息
    false // 可选,是否仅返回生成后的配置信息,默认 false
  );

  // 通常写法: await hd.http(options);

  return info;
};

并发请求实例

  • /html/test/httpAll.js
/**
 * 并发处理请求, 采用Promise.all实现
 * @param {*} hd 
 * @returns 
 */
module.exports = async (hd) => {
  // 第二个参数可选,是否仅返回生成后的配置信息,默认 false
  // hd.httpAll(options, false);

  // Array 列表模式
  let [al, bl] = await hd.httpAll([{
      url: 'http://localhost:3000/test/call?flag=a',
      dataType: 'full'
    },
    {
      url: 'http://localhost:3000/test/call?flag=b'
    },
    // ...
  ]);

  // Object 对象模式
  let { a, b } = await hd.httpAll({
    a: {
      url: 'http://localhost:3000/test/call?flag=a'
    },
    b: {
      url: 'http://localhost:3000/test/call?flag=b'
    },
    // ...
  });

  return {
    al,
    bl,
    a,
    b
  }
};

页面扩展

以下三种模式效果等效(页面默认设置)

  • /html/test/demo.js
// 模式1: 默认模式
module.exports = async hd => { return "hello ext!"; };

// 模式2:直接返回字符串
module.exports = 'hello ext!';

// 模式3: 对象模式
module.exports.html = 'hello ext!';
module.exports = { html: 'hello ext!' };
module.exports.html = async hd => { return 'hello ext!'; };
module.exports = { html: async hd => { return 'hello ext!'; } };
  • http://localhost:3000/test/demo
  • http://localhost:3000/test/demo.htm
  • http://localhost:3000/test/demo.html
  • http://localhost:3000/test/demo.json
  • 默认情况下的页面扩展 demo、demo.htm、demo.html、 demo.json 共四种
  • 如果要统一控制需使用全局配置中的 extTypes 或 extCalls
  • 全局中可以设置 html 和 json 扩展
  • 设置 module.exports.htm 和 module.exports.json 是无效的

自定义页面扩展

  • /html/test/ext.js
/**
 * 默认首页
 * 访问: http://localhost:3000/test/ext
 * @param {*} hd 
 * @returns 
 */
module.exports.html = (hd) => {
  hd.css('ext.css');
  hd.js('ext.js');
  return "这是首页";
};

/**
 * 图片预览
 * 访问: http://localhost:3000/test/ext.jpg
 * @param {*} hd 
 * @returns 
 */
module.exports.jpg = (hd) => {
  // 浏览器页面缓存时间100秒
  // 不设置则不缓存
  // 设置 hd.age() 则页面缓存默认是 staticMaxage 配置值
  hd.age(100);

  // Content-Type 设置
  // 设置优先级如下
  // hd.ctx.type > KoaWeb 中的 extTypes > require("mime-types").types['当前扩展: jpg']
  // hd.ctx.type = 'image/png';
  
  // 文件读取模式
  // hd.read(): /项目根路径/html/test/ext.jpg
  // hd.read('txt'): /项目根路径/html/test/ext.txt
  // hd.read('/home/user/test.txt'): 绝对路径:/home/user/test.txt
  return hd.read();
};

/**
 * 获取其他网页图片并展示
 * 访问: http://localhost:3000/test/ext.png
 * @param {*} hd 
 * @returns 
 */
module.exports.png = async (hd) => {
  let info = await hd.http({
    url: "https://www.baidu.com/img/flexible/logo/pc/result.png",
    buffer: true
  });

  // 获取图片成功
  if (info.code === 200) {
    return Buffer.from(info.data);
  }

  // 获取图片失败
  // 修改掉默认的 image/png
  hd.ctx.type = "text/plain";
  
  return "无效图片!";
};

/**
 * css文件预览
 * 头文件会自动设置:Content-Type: text/css; charset=utf-8
 * 页面不会缓存
 * 访问: http://localhost:3000/test/ext.css
 */
module.exports.css = `body{ color:#F33; }`;

/**
 * js文件预览
 * 访问: http://localhost:3000/test/ext.js
 * 页面会缓存
 * @param {*} hd 
 * @returns 
 */
module.exports.js = hd => {
  // 这里hd.age就发挥了很大的作用,设置页面缓存将减少请求获取更好的性能
  hd.age(1000);

  // 读取文件 /html/test/ext.x.js 文件
  // return hd.read('x.js');
  
  // 注意:一般不使用 hd.read() 读取本文件 /html/test/ext.js

  // 渲染 /html/test/ext.x.js 文件,默认使用nunjucks模板引擎
  // read是读取二进制文件,render是强制转换成字符串
  // return hd.render({ test: "Hello Test!" }, 'x.js');

  return `console.log('打印测试');`;
};
  • http://localhost:3000/test/ext
  • http://localhost:3000/test/ext.jpg
  • http://localhost:3000/test/ext.png
  • http://localhost:3000/test/ext.css
  • http://localhost:3000/test/ext.js

全局调用

  • 全局调用是指与 view 配置中的 domains 无关联调用
  • 全局调用使用 @ 符号为前缀进行设置
// 域名绑定设置
// 假设访问: http://www.test.com
view: {
  domains: {
    "www.test.com": "test"
  }
}

// layout普通设置
// 布局路径指向为: /html/test/abc
json: {
  "layout": "abc"
}

// layout全局设置
// 布局路径指向为: /html/abc
json: {
  "layout": "@abc",
  // "module": "@module",
  // "view": "@view"
}

// 全局错误页面
errors: {
  404: "@errors/404",
  500: "@errors/500"
}

// 全局函数调用
hd.call("@test")

// 全局视图路径
hd.viewPath("@test")

中间件和数据设置

  • 非常适用于开发插件或扩展
  • 默认不绑定任何中间件和数据设置
  • 自定义函数可放入 hd.app 中
const Koa = require("koa");
const KoaWeb = require('koa-web');

const app = new Koa();

/**
 * setMid: 中间件设置,可叠加,顺序执行
 * setData: 数据设置,可叠加,顺序执行
 * setLoad: koa中间件设置,仅加载一次
 * KoaWeb((setMid, setData, setLoad) => {})
 * 或者
 * KoaWeb(async (setMid, setData, setLoad) => {})
 */
KoaWeb(async (setMid, setData, setLoad) => {
  // 中间件设置
  setMid("mid", (hd, data, files) => {
    hd.app.test = "koa-web";

    // 如果未设置return或设置以下将继续执行下一个中间件
    // 反之程序不再向下执行并显示返回值
    // return;
    // return null;
    // return undefined;
  });

  setMid("mid", (hd, data, files) => {
    // 访问 http://localhost:3000/任意路径.mid
    // 结果为: {"show":"koa-web"}
    // 不会再向下执行
    return {
      show: hd.app.test
    };
  });

  // 程序执行完成后的数据处理
  // 与extCalls配置不同的是
  // extCalls是在js文件方法调用完之后,所以extCalls是优先执行的
  // 访问 http://localhost:3000/任意路径.src
  setData("src", (src, hd, data, files) => {
    hd.ctx.type = "application/json; charset=utf-8";

    // return后不影响下一个数据处理
    // 并将src数传入到下一个数据处理
    return src;
  });

  // 全局设置, 任何页面都会执行
  // setMid("*", () => {});
  // setData("*", () => {});

  // 默认扩展页面设置: 空、htm、html扩展页面
  // setMid("html", () => {});
  // setData("html", () => {});

  // 可再次对koa中间件进行设置
  // koaApp: new Koa()
  // config: koa-web配置
  setLoad((koaApp, config) => {
    // console.log('此处仅运行一次');
    koaApp.use(async (ctx, next) => {
      await next();
    });
  });
});

app.use(KoaWeb({path: __dirname}));
app.listen(3000, () => {
  console.log("server is running at http://localhost:3000");
});