1.0.202004272011 • Published 4 years ago

@mm-works/p000005 v1.0.202004272011

Weekly downloads
-
License
MIT
Repository
-
Last release
4 years ago

示例

https://mm-works.github.io/

1. vscode扩展

以下操作说明均以安装mmstudio扩展为前提.

2. 项目调试

2.1. 启动调试

启动项目调试的命令为npm t,简单的,可以通过alt+d快速打开终端并启动调试

2.2. 停止调试

找到启动命令的终端,按下ctrl+c停止命令,注意windows上经常会出现无法停止的情况,这种情况下,杀掉该终端即可。

2.3. 重启调试

停止调试,再次启动调试即可

需要重启调试的情况:

  1. 第一次添加某个公共的客户端原子操作后
  2. 第一次添加某个公共控件后
  3. 添加自定义路由之后
  4. 添加前置路由过滤器之后

3. 页面初始化

3.1. 服务端渲染和浏览器端渲染的差别

比如我们要访问一个页面,这个页面初始化需要呈现出来:"render-demo",它的html片段见pages/render.html

如果是以上的html片段保存成一个文件render.html,直接在浏览器打开就能正确呈现出来。但是它是完全静态的,我们试着把它进行动态化

我们需要先简单说明一下这个页面呈现的的详细过程

  1. 浏览器打开这个文件,读取文件内容,即以上代码,实际上它是一段文本
  2. 浏览器尝试解析这段文本,识别里面的各个标签,像html,body,h1,h2,分析它们的结构。
  3. 浏览器将这些标签用正确的方式进行处理,其中会把h1h2用不同的字号以及合适的字体通过像素显示到显示设备上。
  4. 如果标签中有<script>标签,也会将其里面的脚本使用js引擎(chrome中为v8)执行。
  5. 在脚本执行过程中,如果脚本有对dom进行操作,页面会重新渲染

基于以上步骤,我们有两种不同的处理方式

  1. 如果我们在1和2之间将整个页面的文本处理好,浏览器在3就能正确解析并显示出来。
  2. 如果我们在4进行处理,同样在5时也能正确呈现出我们想要的效果

以上的两种不同的处理方式,第一种就是“服务端渲染”的思路,第二种就是“浏览器端”处理的逻辑

3.1.1. 在实际项目中应该选用哪种方式

  1. 服务端渲染任务在客户端进行,不占用服务器cpu资源
  2. 服务端渲染可以使用服务器缓存,大并发站点节省服务器cpu资源
  3. 综上,使用哪种方式需要综合评定,一般业务型站点,如无特殊需求,建议选择开发效率高,成本低的一种,无须理论拘泥。

3.2. 浏览器端渲染

相对来讲,这种方式较容易被开发人员理解和接受,其过程就是取数,然后渲染页面,如果页面有条件,则使用该条件查询和排序。以下为实战的操作顺序

  1. 新建页面(alt+p)pg001
  2. 因页面较为简单,作为示例,我们将其内容划分为一个组件
  3. 添加一个响应a001
  4. 将其设置为初始化事件,在s.ts中添加初始化事件:

    'mm-events-init': 'a001'
  5. 将tpl中的部分内容生成渲染块,具体操作为:选中<div><h1>mm</h1></h2>studio</h2></div>,按下快捷键(alt+x)即可自动生成p01.tpl。注意渲染原子操作使用的是一个非常优秀的dot模板引擎

  6. 在响应a001.ts中引用渲染原子操作。alt+t a,选择页面操作类回车,再次选择渲染原子操作即可插入代码模板,填入相应参数值就可以,或者在alt+t a直接输入原子操作编号即可快速定位至原子操作,回车插入代码
  7. 渲染的原子操作第二个参数为需要渲染的数据,通常这里会调用一个自定义的服务。我们来快速创建一个服务alt+s
  8. 为了演示方便,这里我直接返回一个数组,但也模拟了排序条件改变,完整的服务代码见pg001/zj-001/s001.ts

  9. 我们在组件的初始化响应中调用该服务

    // 调用nodejs服务
    const r1583660193 = await (() => {
    	const service_name = 'pg001/zj-001/s001';	// 服务名称
    	const msg = { sort: r1583724505 };	// 参数
    	return aw4<string[]>(service_name, msg);
    })();
  10. 渲染的第三个参数我们通过另外一个原子操作获得

    const r1583724505 = (() => {
    	const key = 'sort';	// url参数名
    	const default_value = 'asc';	// 默认值,如果不希望使用默认值,可以删除该参数或者传入空值
    	return aw3(mm, key, default_value);
    })();
  11. 将以上步骤中服务返回的结果作为渲染的第二个参数,p01作为渲染的第三个参数,填入渲染的实参。注意渲染原子操作使用的是一个非常优秀的dot模板引擎

    // html渲染
    (() => {
    	const data = r1583660193;	// 要渲染的数据
    	const position = 'inner';	// inner 替换全部子结点 after 当前结点之后 before 当前结点之前 firstin 第一个子结点之前 lastin 最后一个子结点之后
    	return aw5(mm, data, p01, 'p01', position);
    })();
  12. 经过以上步骤,我们就完成了浏览器端的渲染,再复杂一些,可以使用原子操作把渲染的条件放到url参数中,渲染时取出。逻辑比较简单这里不作解释。

3.3. 服务端渲染

服务端的渲染整个思路较长,并且参数修改往往还会牵涉到一个页面生命周期迭代的问题。上面浏览器端的例子最后提到的条件放在url参数中即为这种方式的一个引子,如果各位能够理解,那么服务端渲染就好理解得多。

必须强调,虽然在调试时为了调试方便,服务端的部分代码是在浏览器中运行。但是实际运行环境中,一定一定不要在服务端的代码中使用任何浏览器特性的东西,比如alert,比如window,webStorage关键字等等,原子操作已经做过过滤,使用vscode扩展自动添加的代码不会存在这种问题,不能使用的原子操作是无法选择的。但如果存在大量开发人员手动添加的代码的情况,一定一定要保证这一点,或者出现某个页面本地调试正常,部署后无法正常加载的问题,也可以从这个思路进行排查问题。

步骤:

  1. 同浏览器端渲染1,2步。
  2. 添加服务端初始化响应,具体操作为:在组件的n.ts文件中按下快捷键alt+a创建响应,注意该操作一个组件只能操作一次,多次操作生成的多个响应并非系统问题,多余的响应也不会执行,请注意。设计如此,非bug。
  3. 创建tpl,步骤同浏览器端原子操作
  4. 添加一个服务s001.ts,方法和内容参见浏览器端原子操作
  5. 在响应na001.ts中添加服务调用并渲染,内容见pg002/zj-001/na001.ts

这个时候启动调试(alt+m d)页面应该就可以看到效果了(如果之前开启过调试,也许需要先手动关闭,具体操作为在终端界面按下ctrl+d)

4. 原子操作

在响应,服务,项目级原子操作中,均可引用原子操作。

4.1. 分类

原子操作分为通用型原子操作和项目级原子操作,通用型原子操作可以在任何项目中使用,其实现要求比较高。项目级原子操作则只要该项目可用即可。

4.2. 创建方法

通过vscode命令mmstudio: Add new atom创建。

4.3. 通用型原子操作

注意事项:

  1. 最好先确定好原子操作的名称,因为它将展示给所有开发人员,尽可能将其描述精确、简练。
  2. 希望加入团队的人员请联系我.我将非常乐意接受社区的贡献。
  3. 原子操作要有单元测试,否则有可能将无法通过审核合并。

4.4. 项目级原子操作

可以在项目中实现某个通用操作,其代码实现制作为一个原子操作index.ts(目前限定其为某一个函数),并将其插入代码模板写入use.snippet。也可以将项目中常用的几个原子操作的使用编写为一个原子操作,在项目中快速引用。

4.5. snippet的写法

  1. 普通的文本按原样插入到使用位置
  2. $为一个特殊字符,如果要在代码模板中输入一个字符$,必须加上转义符\,即\$方可。
  3. 如果$它后跟一个数字,如$1,$2,则表示一个停止符,在插入原子操作时通过tab键可以按$后的数字切换光标位置。
  4. $后如果后跟大括号,如${1},${2},其作用同$1,$2
  5. $后大括号中数字后如跟:,如${1:val},则:后为默认插入内容,当光标停留时默认内容将被选中,且可修改
  6. $后大括号中数字后如跟|,需要保证大括号结束前也要有一个|,如${1:|a,b,c|},则|之间的内容当光标停留时将按,分隔,作为枚举列表供选择,注意应当按实际使用场景调整顺序。
  7. $CURRENT_SECONDS_UNIX为一个数字值,通常它是唯一的,可以用它作为变量名

5. 控件

在tpl中可引用控件,在项目中可以调用控件。

5.1. 创建方法

通过vscode命令mmstudio: Add new widgets创建。

5.2. 通用型控件

注意事项同通用型原子操作

5.3. 项目级控件

可以在项目中实现某个通用控件,控件用到一些封装方法和web组件及shadowdom的技术,不过其基础代码已添加,开发人员按照示例的方法添加自己的实现代码就好。并需其插入代码模板写入use.snippet。也可以将项目中固定搭配的控件合并为一个,在项目中快速引用。

注意:

引用控件时请先选定要插入控件的位置,这样插入的代码模板才不会乱。

5.4. 控件方法的调用

因为typescript类型的关系,如果要使用控件mm-000001,需在客户端响应中手动引入控件类型

import Widget1 from '@mmstudio/ww000001';
// 引入项目内控件
import Widget2 from '../../widgets/pw001';

const w1 = document.querySelector<Widget1>('#widgetid1')!;
w1.method01('foo', 'bar');

const w2 = document.querySelector<Widget2>('#widgetid2')!;
w2.method01('foo', 'bar');

6. 文件转换

6.1. 转pdf

使用服务端渲染的方法制作页面即可,然后将页面后缀(html)修改为pdf即可查看效果(如果是开发阶段,需要修改页面的端口为8889)

6.2. 转word文档

使用服务端渲染的方法制作页面,然后,然后将页面后缀(html)修改为xlsx即可查看效果(如果是开发阶段,需要修改页面的端口为8889)

6.3. 转xlsx表格

使用服务端渲染的方法制作页面,注意必须将页面使用table渲染,然后,然后将页面后缀(html)修改为xlsx即可查看效果(如果是开发阶段,需要修改页面的端口为8889),这是一种相对简单的做法,无法满足复杂表格(如带有公式等高级功能的表格)

7. 文件服务

通常使用控件和原子操作来完成。以下为简单介绍。

7.1. 配置

需要配置一个文件数据库

mm.json

{
	"minio": {
		"endPoint": "127.0.0.1",
		"port": 9000,
		"accessKey": "mmstudio",
		"secretKey": "Mmstudio111111",
		"useSSL": false,
		"region": "cn-north-1",
		"partSize": 5242880
	}
}

同时需要启动一个文件数据库minio

docker-compose安置

[sudo] docker-compose -f db.yaml up
version: '3.7'

services:
  minio:
    image: minio/minio
    container_name: minio
    command: server /data
    volumes:
      - /home/taoqf/data/minio:/data
    ports:
      - 9000:9000
    environment:
      MINIO_ACCESS_KEY: mmstudio
      MINIO_SECRET_KEY: Mmstudio123

7.2. 上传

当前项目页面地址/fsweb/upload,支持一次上传多个文件

7.3. 下载

当前项目页面地址/fsweb/getfile?id=xxx,如果是图片,不添加download参数直接展示在页面展示,如果要下载,请添加 download 参数,download参数支持以下几种形态.

  1. /fsweb/getfile?id=xxx&downlaod
  2. /fsweb/getfile?id=xxx&downlaod=false
  3. /fsweb/getfile?id=xxx&downlaod=abc.jpe

如果一次性下载多个文件,请使用 /fsweb/getfile?id=xxx,yyy,zzz

7.4. 上传office文档,转换为ppt和图片

当前项目页面地址/fsweb/upload-office,支持选择性转换为图片。

7.5. 重新上传

当前项目页面地址/fsweb/reupload?id=xxx

7.6. 删除

当前项目页面地址/fsweb/delfile?id=xxx

8. 获取ip

在任意一个服务(s000.ts)中,可以通过msg.realip获得客户端ip。注意,如果使用了反向代理,请设置请求头x-real-ipx-forwarded-for,如果反向代理设置了其它的ip(比如真实情况的反向代理为第三方),一般也可以通过msg.headers.xxx获取。

9. 自定义路由

在某些情况下,比如支付的回调等,我们需要第三方服务回调提供一些路由。

9.1. 添加通用服务

首先必须要先添加一个服务,由该服务响应该路由的请求,但也有可能是可能是某个组件下已完成的服务,这里以新建通用服务为例说明新建通用服务的方法。

因为我们希望把通用的服务单独列出目录来区分,比如希望通用的服务都放在src/services目录下以方便管理。

因为新建服务的逻辑是需要打开一个可编辑文件,这样新建的服务会位于该可编辑文件相同的目录下。所以我们新建第一个服务时会用到一些技巧。

  1. 我们先在vscode中新建一个文件src/services/mm

    touch src/services/mm
  2. 点击打开这个文件.

  3. alt+s添加服务
  4. 删除第一次添加的文件

    rm src/services/mm
  5. 编写服务逻辑

9.2. 添加路由

  1. alt+r添加路由
  2. 选中服务services/s001
  3. 使用get请求访问该路由(也有可能是post,put,delete,all,根据第三方服务情况而定)
  4. 重启项目调试
  5. curl http://localhost:8889/r001即可触发调用服务

9.3. 添加附加数据

有时候,一个服务的逻辑几乎能被多个路由调用,又有一些细微的差别,这个时候,为了项目维护方便,通常不建议复制并修改原服务。我们有两种方法来实现:

  1. 添加项目级服务端原子操作,将服务的逻辑封装起来,暴躁出某个参数,在不同服务中调用该原子操作。
  2. 在路由定义中附加上该参数,将多个路由同时关联同一个服务,在服务中通过msg.foo获取到参数进行逻辑判定处理。

路由定义中附加参数的方法

在mm.json中找到routers下的多个路由,分别为它们附加参数(如foo)

{
	"routers": [
		{
			"method": "get",
			"service": "searvices/s001",
			"url": "/r001",
			"data: {
				"foo": "bar1"
			}
		},
		{
			"method": "get",
			"service": "searvices/s001",
			"url": "/r002",
			"data: {
				"foo": "bar2"
			}
		}
	]
}

10. 前置路由过滤器

前置路由过滤器将提前将某个请求进行处理,其操作类似于添加路由。以下列出不同点:

  1. 前置路由过滤器对应的服务返回的结果中如果有data,该请求将提前返回,后续路由逻辑全部跳过,但可以设置cookie,和响应头header。
  2. 返回结果中可以有重定向redirect,如果有,请求也请提前返回,后续路由逻辑全部跳过,可同时设置cookie和响应头
  3. 前置路由示波器通常使用通配符作为url,以下几种示例,都是正确的写法

    {
    	"filters": [
    		{
    			"method": "get",
    			"service": "searvices/s001",
    			"url": "/*",
    			"data: {}
    		},
    		{
    			"method": "get",
    			"service": "searvices/s001",
    			"url": "/*.html",
    			"data: {}
    		},
    		{
    			"method": "get",
    			"service": "searvices/s001",
    			"url": "/mmstudio",
    			"data: {}
    		{
    			"method": "get",
    			"service": "searvices/s001",
    			"url": "/m?studio",
    			"data: {}
    		{
    			"method": "get",
    			"service": "searvices/s001",
    			"url": "/m+studio",
    			"data: {}
    		{
    			"method": "get",
    			"service": "searvices/s001",
    			"url": "/mm(studio)?",
    			"data: {}
    		}
    	]
    }

11. 负载均衡

通常的nginx的反向代理,负载均衡就可以满足绝大多数大型应用(体量小的应用不用担心负载问题)

12. 两个单例服务

有些服务不能启动多个实例,所以需要独立部署为单例。这类服务在负荷大(非常大)时,将会拖慢整个应用的速度,所以在业务设计时,需要非常注意。

原理上来讲,这类服务是不可能通过增加结点实现的,除非修改设计,比如不在必要的时候不生成唯一的编码,使用uuid替代。

12.1. 编码服务

详见调用编码服务原子操作

12.2. 定时任务

使用vscode扩展添加定时任务配置。

服务详情见定时任务服务

13. 第三方库的引用

使用第三方库的时候,由于第三方库的代码质量我们无法保证,所以可能会有以下问题,列出以待查阅分析:

13.1. 添加依赖

  1. 服务端原子操作如果引用第三方包,将务必将包依赖添加到package.json中的dependencies中。
  2. 客户端原子操作如果引用第三方包,将务必将包依赖添加到package.json中的devDependencies中。
  3. 控件如果引用第三方包,将务必将包依赖添加到package.json中的devDependencies中。

13.2. 全局引用

客户端原子操作或控件引用的第三包如果有全局引用,最常见的有jquery,这一类引用在使用时通常会有两种问题:

  1. 需要在页面中通过script全局引入js文件
  2. 打包时多个对jquery依赖的控件有可能会冲突,造成找不到jquery对象的问题
  3. ts定义问题,使用全局还是模块引用需要权衡,多数国内jquery依赖的包质量不高,非常乱。

这里只指出解决问题的线索,具体问题请自行解决。

  1. 根目录下的gulpfile.js,本地调试及打包相关
  2. n.ts,打包相关

13.3. amd

虽然一些脚手架工具已经可以比较方便进行页面调试了,但是依然,会存在各种各样的奇怪问题,项目开发人员技术水平不过硬时,出现问题很难自行解决。并且通常地,当项目比较大时,执行速度过慢,从而导致开发效率降低。所以我们在开发过程中使用的是amd的加载方式,这种技术相对成熟,各种工具和第三方包的支持也比较好,万一出现问题也相对容易解决。但这会产生一些问题,即有部分第三方包并不提供amd的版本,虽然在打包时理论上可以使用,但无法开发调试。这就需要项目使用人员去开源贡献或是自行解决(配置amd.json文件,同时项目下添加amd的版本以供开发时使用,将commonjs版本修改umd版本并不复杂,通常只需要添加头尾即可)。amd.json的格式参看amd加载器中的相关说明。

13.4. TypeScript定义

有些第三方类库可能会缺少ts定义,如果是比较流行的类库,试试到@types下找一找。示例(xxx为类库名称):

yarn add --dev @types/xxx

如果没有,可以到开源社区贡献。

13.5. 简单粗野的做法

如果不想贡献,还要使用第三方类库,在许可允许的范围内(哈哈),可以源码级引入,修改为ts,去掉全局依赖。当然,这种作法我个人并不推荐,但是当今国内开发的圈子里,这样做的不在少数,在项目工期比较紧且项目开发人员普遍素质不很高的情况下,这确实是项目开发成本比较低的一种方法,祝好运!