NodeJs分为四个阶段 第二阶段~
begin enjoy👇

简易版Apache

apache是一款成熟的web服务软件,它不仅能够监听处理Web请求,并且设置了很多有用的规则:

  • 站点根目录:在apache的主配置文件中拥有 DocumentRoot 配置项,用来指定站点文件的根目录,然后根据url中的请求路径加载指定的文件。
  • 错误提示:例如找不到该文件时返回404错误!这里可以先将apache的404页面CTRL+S保存起来借鉴使用。
  • 默认首页:当请求路径为目录地址时,加载默认首页文件,一般为 index.html
  • 文件列表:当请求路径为目录地址时,如果没有index文件 则显示文件列表,这里也先将该页面内容保存以备使用。

请求站点文件

检测路径是否合法需要使用 fs.existsSync(path) 方法,如果路径存在,该方法返回 true ,否则返回 false

const http = require("http"),
fs = require("fs");
const server = http.createServer();
//定义站点根目录地址 使用绝对路径
const DOCUMENT_ROOT = "D:/wamp/www";
server.on("request", (request, response) => {
	//监听请求来源 解析文件编码 中文在http中被编码后 不能正常读写
	let requestURI = decodeURIComponent(request.url);
	//设置默认首页
	if (requestURI == "/")  {
		requestURI = "/index.html";
	}
	//组装请求文件
	let filename = DOCUMENT_ROOT + requestURI;
	//如果路径不存在 则加载404页面
    if (!fs.existsSync(filename)) {
        //404错误页面中包含动态渲染的文件名数据 需要使用模板引擎操作
        filename = DOCUMENT_ROOT + "/404.html";
    }
    console.log(filename);
    //读取文件内容
    fs.readFile(filename, (err, data) => {
        if (err) {
            //读取错误 输出提示信息
            data = err.toString();

        }
        response.end(data);
    });
})
server.listen(8000, (err) => {
    if (err) {
        console.log(err.toString());
    } else {
        console.log("服务器已经启动...");
    }
})

生成目录列表

fs.Stats()

apache访问目录地址时 会以列表形式呈现内容。这里我们首先需要借用一个 fs.stat() 方法来检查一个文件的状态和用户权限。 fs.stat() 回调返回的是一个Stats对象

  • fs.stat(path, [option], callback)
    • path :为检查的文件路径
    • option :可选项 标记返回的Stats对象是否采用bigint长整型的值
    • callback :回调函数,有两个参数 (err, stats),其中 stats 是 fs.Stats 不建议在调用 fs.open() 、 fs.readFile() 或fs.writeFile() 之前使用 fs.stat() 检查文件是否存在。 而是应该直接打开、读取或写入文件,如果文件不可用则处理引发的错误。要检查文件是否存在但随后并不对其进行操作,则建议使用 fs.access()
  • Stats对象的常用成员如下:

    • stats.isDirectory() :如果 fs.Stats 对象描述文件系统目录,则返回 true 。

    • stats.isFile() :如果 fs.Stats 对象描述常规文件,则返回 true 。

    • stats.size :文件的大小(以字节为单位),如果是目录则返回 0

    • stats.birthtime :文件的创建时间

    • stats.mtime :最近一次修改的时间

const fs = require("fs");
let path = "d:/wamp/www";
path = "d:/wamp/www/index.html";
/*
  * 检测路径是否合法
  */
console.log(fs.existsSync(path));
/*
	* 检测文件状态
	*/
fs.stat(path, (err, stats) => {
    //stats为一个Stats对象
    console.log(stats);
    //判断是否目录
    console.log(stats.isDirectory()); //false
    //判断是否文件
    console.log(stats.isFile()); //true
    //读取文件大小
    console.log(stats.size); //28字节
    //最后修改日期
    console.log(Date.parse(stats.mtime)); //将2019-04-01T07:46:10.905Z解析为时间戳
});

fs.readdir()

接下来,如果检测出来是一个目录 则读取该目录的文件列表信息 需要使用 fs.readdir() ,用法如下:

  • fs.readdir(path, option, callback)

    • path:要读取的目录地址

    • options : 可选的参数 一个对象 ,包含两项: withFileTypesencoding

      • 如果 options.withFileTypes 设置为 true ,则 files 数组将包含 fs.Dirent 对象 options.encoding 属性指定用于传给回调的文件名的字符编码;
      • 如果 encoding 设置为 'buffer' ,则返回的文件名是 Buffer 对象。
    • callback:回调函数,包含err和files两个参数。读取成功则files为一个数组。

let path
path = "d:/wamp/www";
fs.readdir(path, (err, files) => {
    if (err) {
        console.dir(err);
    } else {
        //files为一个文件列表数组
        console.dir(files);
    }
});

//设置options项
fs.readdir(path, {
    withFileTypes : true,
    encoding: 'utf-8'
}, (err, files) => {
    if (err) {
        console.dir(err);
    } else {
        //files为一个文件列表数组
        console.log(files);
        for (let item of files) {
            //查找所有的symbol类型 返回数组形式
            let syms = Object.getOwnPropertySymbols(item);
            console.log(item.name, item[syms[0]]);
        }
    }
});
  1. 保存apache列表页模板文件为 list.html

  2. 创建服务器并监听请求 判断请求路径为文件或目录,如果是文件 读取内容,如果是目录则获取列表

    const http = require("http"),
          fs = require("fs");
    const server = http.createServer();
    //配置站点根目录 不可修改的常量
    const DOCUMENT_ROOT = "D:/wamp/www";
    //监听来自客户端 浏览器发起的请求
    server.on("request", (request, response) => {
        //解析文件 编码中文在http中被编码后 不能正常读写
        let requestURI = decodeURIComponent(request.url);
        //默认首页
        if (requestURI == "/") requestURI = "/index.html";
        //组装路径
        let filename = DOCUMENT_ROOT + requestURI;
        //检测路径
        if (!fs.existsSync(filename)) {
            fullpath = DOCUMENT_ROOT + "/404.html";
        }
        console.log(filename);
        //fs.stat()返回一个Stats对象
        //Stats对象用来检查文件状态和用户权限
        fs.stat(filename, (err, stats) => {
            if (err) {
                console.dir(err);
                return; //停止脚本继续运行
            }
            // console.log(stats);
            switch(true) {
                case stats.isFile() :
                    //读取文件内容
                    fs.readFile(filename, "utf-8", (err, data) => {
                        response.end(data);
                    });
                    break;
                case stats.isDirectory() :
                    //读取目录列表
                    fs.readdir(filename, (err, files) => {
                        //组装到list.html中
                    });
                    break;
                default :
            }
        })
    })
    server.listen(8000, (err) => {
        if (err)console.dir(err);
        else console.log("服务器已经启动....");
    })
    
  3. 替换模板中的动态内容为占位符形式,将标题替换为 %title% ,列表项替换为 %items%

    //读取目录列表
    fs.readdir(fullpath, (err, files) => {
        //组装标题和列表项
        let title = requestURI,
            items = files.map((item, index) => {
                return `<li><a href='${path.join(requestURI, item)}'>${item}</a>
    </li>`;}).join("");
        //替换list.html模板内容,类似于置换行模板
        fs.readFile(DOCUMENT_ROOT + "/list.html", "utf-8", (err, data) => {
            //str.replace()方法 需要先将二进制的数据 转化成字符串
            let htmlStr = data.replace(/%(\w+?)%/g, (match, quote) => {
                console.log(match, quote);
                //编译字符串为变量的值
                return eval(quote);
            });
            response.end(htmlStr);
        });
    });
    
  4. 整个过程的注意事项如下:

    1. http传输中会对url中的特殊字符进行urlencode编码,需要手动的使用 decodeURIComponent() 解码

    2. 每次遍历目录时需要及时更新路径,该路径在重构时避免 ./ 前缀 ,因为request.url里面已经包含了 /

    3. replace替换模板时需要确保模板内容为字符串而非buffer,可以使用 toString() 或者设置 utf-8 编码

模板引擎技术

模板引擎作为服务端一项传统技术,在node中也是必须的。模板引擎替代原始的脚本嵌入式方式填充数据,能够有效的将业务逻辑和界面表现分离 并有利于团队的协同开发;

<ul>
    <script>
    //使用脚本渲染的数据 业务逻辑和页面表现耦合
    //可读性和维护性差,需要使用模板引擎分离
    //程序中处理页面表现层的过程
    //1、准备数据
    //2、遍历数据
    //3、组装数据
    //4、innerHTML填充到容器或者在容器中直接输出
    [1, 2, 3, 4].forEach((item, index) => {
    document.write(`<li><a href="">${item}</a></li>`);
});
</script>
</ul>	
<!-- 模板引擎将数据和表现层分离,它的语法也更简洁 -->
    <script type="text/html" id="tpl">
        <ul>
        {{ each [1, 2, 3, 4] }}
            <li><a href="">{{ $value }}</a></li>
        {{ /each }}
        </ul>
    </script>

模板引擎有自己的模板语法,但学习成本低,容易驾驭!

事实上,在浏览器环境中 也存在模板引擎的概念,它能让用户更方便的将数据渲染到页面中,例如 art-template ,则既可以用于浏览器 也可用于node中。

  • 使用npm下载和本地安装 art-template: npm install art-template

  • 在需要使用模板引擎的页面使用 require('art-template') 包名就是 art-template

  • 查询文档,关于art-template的使用https://aui.github.io/art-template/zh-cn/docs/

在node中使用art-template

  • 引入art-template模块

const at = require('art-template')

  • 使用art语法重构模板
//循环基本语法
{{each target}}
{{$index}} {{$value}}
{{/each}}

target 支持 array 与 object 的迭代,其默认值为 datadata。value 与 $index 可以自定义:{{each target val key}}

  • 发送数据到模板编译

    let title = requestURI,
        origin = request.headers.referer;
    fs.readdir(filename, (err, files) => {
        //发送数据到模板
        let htmlStr = template(DOCUMENT_ROOT + "/list2.html", {
            //左边是模板变量名 后边是数据值
            // title : title,
            // origin : origin,
            // files : files,
            //使用简洁属性设置
            title,
            origin,
            files,
        });
        response.end(htmlStr);});
    

浏览器中使用art-template

首先在浏览器环境中需要以 script:src 请求核心文件 template-web.js ;其次模板内容被放置在 <script type='text/html' id='tpl'></script> 中。因为浏览器不支持文件系统,所以 template(filename, data) 无效,需要将filename替换成tpl容器,它内部使用 document.getElementById(tpl).innerHTML 来获取模板。

  • 引入核心文件

<script src='node_modules/art-template/libs/template-web.js'></script>

  • 设置模板内容

    <script type="text/html" id="tpl">
        <table class="table">
            <tr>
                <th>#</th>
                <th>商品名称</th>
                <th>商品描述</th>
    		</tr>
    	{{ each data }}
    	<tr>
            <td> {{ $value.gid }}</td>
        	<td> {{ $value.gname }}</td>
        	<td> {{ $value.gdesc }}</td>
    	</tr>
    	{{ /each }}
      </table>
    </script>
      <div id="container">
          <!--存放模板解析的内容-->
      </div>
    
  • 渲染模板内容

    const goods = [{
        gid: 1,
        gname: "飞利浦电动剃须刀",
        gdesc: "好用,针对男士护理"
    },{
        gid: 2,
        gname: "美的电冰箱",
        gdesc: "节能环保,家用首选"
    }];
    //使用模板引擎来渲染页面
    let htmlStr = template("tpl", {
        data : goods,
    });
    console.log(htmlStr);
    container.innerHTML = htmlStr;
    

art-T模板语法

art-template 支持标准语法与原始语法。标准语法可以让模板易读写,而原始语法拥有强大的逻辑表达能力。

标准语法支持基本模板语法以及基本 JavaScript 表达式;原始语法支持任意 JavaScript 语句,这和 EJS 一样。

输出

标准语法

{{value}}
{{data.key}}
{{data['key']}}
{{a ? b : c}}
{{a || b}}
{{a + b}}

原始语法

<%= value %>
<%= data.key %>
<%= data['key'] %>
<%= a ? b : c %> 5 <%= a || b %>
<%= a + b %>

模板一级特殊变量可以使用 $data 加下标的方式访问:

{{$data['user list']}}

原文输出

标准语法

{{@ value }}

原始语法

<%- value %>

原文输出语句不会对 HTML 内容进行转义处理,可能存在安全风险,请谨慎使用。

条件语句

标准语法

{{if value}} ... {{/if}}
{{if v1}} ... {{else if v2}} ... {{/if}}

原始语法

<% if (value) { %> ... <% } %>
<% if (v1) { %> ... <% } else if (v2) { %> ... <% } %>

循环语句

art-Template中的each可以遍历数组元素或者对象属性

标准语法

{{each target}}
{{$index}} {{$value}} 3 {{/each}}

原始语法

<% for(var i = 0; i < target.length; i++){ %>
<%= i %> <%= target[i] %>
<%}%>
  1. target 支持 array 与 object 的迭代,其默认值为 $data 。

  2. $value 与 $index 可以自定义: {{each target val key}} 。

模板变量

标准语法

{{set temp = data.sub.content}}

原始语法

<% var temp = data.sub.content; %>

模板继承

标准语法

{{extend './layout.art'}}
{{block 'head'}} ... {{/block}}

原始语法

<% extend('./layout.art') %>
<% block('head', function(){ %> ... <% }) %>

模板继承允许你构建一个包含你站点共同元素的基本模板“骨架”。范例:

<!--layout.art-->
<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <title>{{block 'title'}}My Site{{/block}}</title>
        {{block 'head'}}
        <link rel="stylesheet" href="main.css">
        {{/block}}
    </head>
    <body>
        {{block 'content'}}{{/block}}
    </body>
</html>

渲染 index.art 后,将自动应用布局骨架。VSCode中可以下载Art Template Helper来高亮 .art 文件。

<!--index.art-->
{{extend './layout.art'}}
{{block 'title'}}{{title}}{{/block}}
{{block 'head'}}
<link rel="stylesheet" href="custom.css">
{{/block}}
{{block 'content'}}
<p>This is just an awesome page.</p>
{{/block}}

子模板

标准语法

{{include './header.art'}}
{{include './header.art' data}}

原始语法

<% include('./header.art') %>
<% include('./header.art', data) %>
  1. data 默认值为 $data ;标准语法不支持声明 object 与 array ,只支持引用变量,而原始语法不受限制。

  2. art-template 内建 HTML 压缩器,请避免书写 HTML 非正常闭合的子模板,否则开启压缩后标签可能会被意外“优化。

过滤器

注册过滤器

template.defaults.imports.dateFormat = function(date, format){/*[code..]*/};
template.defaults.imports.timestamp = function(value){return value * 1000};

过滤器函数第一个参数接受目标值。

标准语法

{{date | timestamp | dateFormat 'yyyy-MM-dd hh:mm:ss'}}

{{value | filter}} 过滤器语法类似管道操作符,它的上一个输出作为下一个输入。

原始语法

<%= $imports.dateFormat($imports.timestamp(date), 'yyyy-MM-dd hh:mm:ss') %>

渲染模型

服务端渲染中 模板文件不能直接运行 必须由控制层引入执行,控制层即是node程序文件。

客户端渲染

服务端渲染

posted @ Zycin (非转载 来自个人学习资料整理)