## mya-golang-template

基于 mya 的针对 go html/template 模板的前端工程解决方案

### 背景

利用后端模板的能力实现后端模块化（组件化）和静态资源按需加载

### 方案构成

* html/template 扩展，负责解析自定义标签，并根据静态资源映射表渲染最终页面
* `mya-golang-template`（mya插件），输出静态资源映射表，编译打包相关配置，路径修正等
* 其他：脚手架

### 预备工作

在阅读以下内容之前，建议可以先安装一下mya，然后利用脚手架初始化一个项目，对着项目看，会更容易理解以下内容。

```bash
npm install -g mya
mya init go your_project_name
```

### 原生标签

#### template

**page/home/index.html**

```html
{{ template "component/common/button/index.html" . }} <!--  . 符号表示将当前上下文变量传递给模板 -->
```

go html/template 自带的标签，类似 jinja2/django 中的 `include`。在项目中可以通过此标签引用一个组件。

在构建过程中，我们会为每个模板自动插入 `require` 标签（见自定义标签），这样仅需要使用 template 引用模板文件，其同目录下同名的 css 和 js 就会被当作依赖收集并自动插入到页面。

比如：

**component/common/button/index.html**

```html
<div>button</div>
```

**output/template/component/common/button/index.html**

```html
<div>button</div>
{{ require "web:component/common/button/index.html" }}
```

`web` 为命名空间，用于区分前端项目，在 `fis-conf.js` 中配置

### 自定义标签

#### framework

```html
{{ framework "static/script/lib/mya.js" }}

<!DOCTYPE html>
<html>
    ...
</html>
```

指定项目依赖的模块加载器

**注意**:

1. 如果在页面中手动引用了模块加载器（eg. mya.js/mod.js）或者包含模块加载器的文件（eg. core.js），则无需添加 framework 标签
2. 可以通过命名空间引用其他模块的文件，比如 `{{ framework="common:static/lib/mya.js" }}`

#### require

```html
{{ require src="static/script/lib/base.js" }}
{{ require src="common:static/script/lib/mya.js" }}
{{ require src="common:static/style/lib/core.css" }}
```

`require` 标签用于引用js或者css文件，支持跨模块引用。通过 require 引用的文件会按顺序收集起来，以js文件为例，最终和组件js（template引用的组件）一起插入到body最后或者指定位置（在 {% script %} 标签收集的js之前）。

非跨模块调用时，引用路径无需携带命名空间，构建时会自动添加上。

#### script

```html
<div>
</div>
{{ script }}
    require('page/home/index').init({{ .user }});
{{ endscript }}
```

`script` 标签包裹的js语句会被收集起来，和页面依赖的js文件（组件js、页面入口js等）一同插入到body最后其中页面依赖的js文件在前，`script` 标签包裹的js语句在后。多个 `script` 标签包含的语句会被合并到一对 `<script></script>` 当中，每个语句块会被自执行函数包裹起来。

比如上面例子实际渲染的结果是：

```html
<body>
    ...
    <script src="pkg.js"></script> <!-- 打包合并后的js -->
    <script>
    (function() {
        require('page/home/index').init({{ .user }});
    })();
    </script>
</body>
```

`script` 标签主要的用途是包裹 **组件调用** 的语句。如果有些内嵌脚本，如统计脚本，不想被收集到最后面，直接使用原生标签即可。

**注意：**

* 被 `script` 包裹的js语句不能使用es6，因为这些语句可能包含模板语法，在babel编译时会报错。
* require可以通过命名空间引用其他模块的文件，比如 `require('common:component/util/index')`

#### style

```html
{{ style }}
.home-comp1 {
    color: red
}
{{ endstyle }}
```

`style` 标签包裹的css语句会被收集起来，和页面依赖的css文件（组件css、页面入口css等）一同插入到head最后，其中页面依赖的css文件在前，`style` 标签包裹的css语句在后。多个 `style` 标签包含的语句会被合并到一对 `<style></style>` 当中。

`style` 标签使用的场景不多，其意图主要也是实现组件的自我管理。比如一个组件只有少量样式，不希望外链接，可以放在 `style` 标签中。

**注意：** `style` 标签中的内容不会参与编译，所以不能使用 `sass` 或者 `less` 语法。

### 项目开发

#### 前端项目

https://code.byted.org/mya/scaffold-go

#### 后端项目

在后端项目里引用 [gin 中间件](https://code.byted.org/mya/gin-middleware)。

### 文件合并

脚手架默认生成了以下打包合并规则：

```javascript
/**
 * 说明：以下是默认配置，可以根据自己业务进行调整
 * 规则：
 * 1. 页面组件和页面入口文件打包到一个文件中，比如
    /component/home/comp1/index.js
    /component/home/comp2/index.js
    /page/home/index.js
    ->
    /page/home.js
 * 2. 公共组件打包到 common 文件中
 */

/**
 * 页面文件配置
 */
// 页面组件和页面入口 allInOne
fis.match('/component/(*)/**.js', {
    packTo: '/pkg/page/$1.js'
});
fis.match('/page/(*)/*.js', {
    packTo: '/pkg/page/$1.js'
});

fis.match('/component/(*)/**.{scss,less}', {
    packTo: '/pkg/page/$1.css'
});
fis.match('/page/(*)/*.{scss,less}', {
    packTo: '/pkg/page/$1.css'
});

/**
 * 公共文件配置
 */

// 项目公共组件
fis.match('/component/{api,common,const,util}/**.js', {
    packTo: '/pkg/common.js'
});

fis.match('/component/{api,common,const,util}/**.{scss,less}', {
    packTo: '/pkg/common.css'
});
```

需要说明的是，按照目录规范，component 下的目录（除了common、util、const、api等公共目录）都应该对应一个页面，比如 component/reflow_video、component/reflow_person，尽量不要出现 component/reflow/video component/reflow/person 的情况。page 下面可以出现分组，比如 page/reflow/reflow_video、page/reflow/reflow_person。可以看到，页面组件目录和页面入口目录的名字是相同的，这样做方面配置打包策略，让页面组件和页面入口合并到一个文件中。

比如：
```javascript
fis.match('/component/(*)/**.js', {
    packTo: '/pkg/page/$1.js'
});
fis.match('/page/reflow/(*)/*.js', {
    packTo: '/pkg/page/$1.js'
});
```

这样 component/reflow_video 下的js 和 page/reflow/reflow_video 下的js 都会打到 /pkg/page/reflow_video.js 中。

我们的初衷是，如果你完全按照目录规范来组织项目（component和page下面的一级目录都是页面，没有分组），那么使用默认打包配置就能满足最小文件数的需求，如果你的目录结构下存在分组，可以通过自定义规则来实现精细化的打包。
