@snacking/see-u

vue 2.6.x脚手架...

Usage no npm install needed!

<script type="module">
  import snackingSeeU from 'https://cdn.skypack.dev/@snacking/see-u';
</script>

README

@snacking/see-u

说明

这是一个开发vue应用的脚手架...

主要是为了解决工程拆分,以及不同项目中,将可复用的工程的重新组合成新的应用...

支持多个工程作为一个应用,同时开发、编译和部署,其中每个工程都是独立的上下文...

封装了多种插件,尽量以开关的方式简化配置...

支持webpack和vite作为开发和打包工具...

内置View UI、Element UI、Vant等UI库...

安装

在工作空间里执行以下命令,安装脚手架...

npm install @snacking/see-u

或者...

yarn add @snacking/see-u

初始化

在工作空间里执行以下命令,进行工作空间初始化...

npx sn-seeu init [-d] [-y]

在搭建脚手架时,最好是加上-d创建一个工程,如果这时不创建,也可以通过下文的一个命令创建...

npx sn-seeu init -d

-y参见[选项]说明...

[选项]

  • [-d --demo] 在初始化前,创建工程,并生成样例文件...

  • [-y --yarn] 初始化后,使用yarn安装插件,否则使用npm安装插件...

执行初始化命令后,在控制台会以问答方式采集信息...

1)选择ui

2)如果选了ui库,需要选择是否按需加载

3)选择css预处理语言

4)选择渲染方式(客户端渲染服务端渲染

5)选择一些插件,例如babeleslint等等

6)选择是否需要后端服务

如果在初始化命令里加了-d选项,还要继续采集工程名称...

初始化完成后,工作空间里会生成以下文件和目录...

  • see-u.js 工作空间的描述文件,在下文中说明

  • package.json node模块的描述文件

  • 如果加了-d选项,还会有个工程目录,目录里会有index.vue入口文件和see-u.js工程描述文件,如果在上述问答中选了“需要后端服务”,还会有个service.js作为后端服务样例

工作空间样例

workspace
  demo
    index.vue(入口文件)
    router.js(路由文件)
    store.js(仓库文件)
    see-u.js(工程描述文件)
    service.js(启用后端服务才会生成)
  project1
  project2
  ……
  see-u.js(工作空间描述文件)
  package.json

应用启动

在工作空间里执行以下命令...

npx sn-seeu run (webpack)

或者...

npx sn-seeu dev (vite)

默认端口为8080,可在工作空间描述文件see-u.js中修改devServer.port属性...

如果在上面初始化的时候,创建了一个工程,举例demo,那么可以打开工作空间描述文件see-u.js,修改devServer.openPage为"demo/index.html",那么应用启动后将自动打开默认浏览器访问该界面...

关于各种描述文件

see-u.js

这是脚手架的描述文件,同时存在于工作空间和工程目录里...

工程目录里的描述,主要是描述工程自身的界面入口、后端服务入口、以及该工程的依赖(同package.json里dependencies和devDependencies)...

工作空间里的描述,是开发、打包的基本设置,是脚手架运行的根本...

工作空间的see-u.js = 脚手架预设 + 通过命令行人机交互采集的信息...

package.json

这是node模块描述文件,一般情况下仅存在于工作空间中,代表当前工作空间/应用的描述...

但也允许存在于工程目录里,作为工程的描述,主要描述工程的依赖...

由于上述工程描述已于see-u.js中描述,所以工程目录中的模块描述大可不必,以减少描述文件数量,便于集中描述...

如果工程目录里存在模块描述,将会在编译和运行时,合并到工作空间的模块描述文件中,优先级:package.json > see-u.js...

工作空间的package.json = 工作空间的see-u.js + 各工程中的package.json + 工作空间的package.json,优先级从左到右依次提高...

webpack.config.js

这是webpack的描述文件,一般情况下不需要,大多数选项都可以在工作空间的see-u.js中描述,以减少描述文件数量,便于集中描述...

但也允许存在于工作空间和工程目录里,作为各自编译描述...

在编译和运行时,将会合并工作空间和工程目录里的webpack描述,优先级:工作空间描述 > 工程描述 > see-u.js...

脚手架并不会生成工作空间的webpack.config.js,只是在编译和运行时,会依次合并相关描述,作为最终的编译参数:工作空间的see-u.js < 各工程中的webpack.config.js < 工作空间的webpack.config.js...

vite.config.js

脚手架并不会生成工作空间的vite.config.js,只是运行时,根据see-u.js的配置,构建vite所需的配置对象供vite使用...

命令行

初始化

用来初始化工作空间,并安装相关依赖,可以直接创建一个工程...

npx sn-seeu init [-d] [-y]

[选项]

  • [-d --demo] 在初始化前,创建工程,并生成样例文件...

  • [-y --yarn] 初始化后,使用yarn安装插件,否则使用npm安装插件...

新增工程

在工作空间里新增一个工程...

npx sn-seeu new app

webpack编译

编译工作空间,在客户端渲染时,会在工作空间的@entry目录里生成真正的入口...

npx sn-seeu build [-y]

[选项]

  • [-y --yarn] 用yarn安装插件...

在编译时,会校验包依赖变化,如果有变化,默认使用npm重新安装插件...

vite编译

编译工作空间,在客户端渲染时,会在工作空间的@entry目录里生成真正的入口...

npx sn-seeu compile [-y]

[选项]

  • [-y --yarn] 用yarn安装插件...

在编译时,会校验包依赖变化,如果有变化,默认使用npm重新安装插件...

启动应用服务

启动应用服务...

npx sn-seeu run/dev [-y]

[选项]

  • [-y --yarn] 用yarn安装插件...

启动前,会校验包依赖变化,如果有变化,默认使用npm重新安装插件...

应用启动后,通过{http}://{ip}:{port}/{project}/{entry}.html访问...

其中,run是在express中注册webpack开发中间件,如果存在后端服务,同时启动后端服务...

而dev暂时是执行vite命令启动服务...

预览webpack选项

在工作空间里,生成编译和运行时webpack配置的预览webpack.preview.json,用于校验是否符合预期...

npx sn-seeu preview

see-u.js的描述说明

{
  mode: "production",                   // webpack/vite的内置优化模式,production | development | none
  devServer: {
    port: 8080,                         // 端口
    hot: true,                          // 模块热替换
    openPage: "demo/index.html"         // 应用启动后打开的默认页
  },
  development: {
    "vue": {
      "runtime": true,                  // 引用运行时模块(将不能动态解析模板)             
      "route": {
        "paramsToProps": true           // 将路由参数送入组件的props属性
      },
      "config": {
          "devtools": false,            // 开发工具
          "productionTip": false        // 生产模式的消息
      },
      "plugins": []                     // vue插件列表,具体参见下文专项说明
    },
    "ui": {
      "lib": "Vant",                    // ui库,目前Vue支持View UI、Element UI、Vant
      "asNeeded": false                 // ui按需加载
    },          
    "css": {
      "inline": false,                  // 内联css
      "minified": true                  // 压缩
      "preprocessor": "Less",           // css预处理语言,目前支持Less、Sass,如果不使用设置None
      "variable": null                  // 自定义变量文件,在使用css预处理器时导入
    },
    "ajax": {
      "formData": false,                // post/put等请求body中的数据格式,false默认使用application/json,true默认使用application/x-www-form-urlencode
      "security": {
        "crypto": false,                // 解密功能开关
        "placeholder": "SayHi"          // 框架用来识别加密请求的占位符
      }
    },
    "log": {
      "domEvent": false,                // dom事件日志开关
      "domEventTypes": ["click"],       // 需要监听的dom事件
      "method": false,                  // 组件方法调用日志开关
      "ajax": false,                    // ajax请求日志开关
      "serverPath": null                // 日志发送服务地址,如果没设置,则默认在控制台输出
    },
    "csr": true,                        // 客户端渲染开关
    "ssr": false,                       // 服务端渲染开关
    "eslint": false,                    // eslint开关
    "babel": false,                     // babel开关
    "polyfill": false,                  // 垫片开关,要先开启babel开关
    "clear": true,                      // 编译前清空编译目录
    "mock": false,                      // mock开关
    "proxy": false,                     // proxy开关
    "service": false                    // 后端服务开关
  },
  chunk: {                              // 需要剥离的模块
      vue: "vue"
  },
  entry: null,                            // webpack的入口,非必须
  context: null,                          // 基础目录,非必须,默认为cwd
  output: null,                           // 非必须
  plugins: null,                          // 插件,非必须
  externals: null,                        // 外部扩展,非必须
  module: null,                           // 非必须
  optimization: null,                     // 优化选项,非必须
  performance: null,                      // 性能,非必须
  resolve: null                           // 非必须
  
  -----------------------------  以上仅在工作空间里描述使用
  
  -----------------------------  以下仅在工程里描述使用
  
  dependencies: {},                     // 依赖,非必须
  devDependencies: {},                  // 开发依赖,非必须
  entryForVue: {                        // vue单文件入口,需开启客户端渲染
      index: {
          "path": "./index.vue",        // vue单文件入口
          "title": "hello! see u!",     // 页面title
          "router": null,               // 路由
          "store": null,                // 仓库
          "favicon": null,              // 图标
          "meta": null
      }      
  },
  service: {                            // 后端服务入口,需开启后端服务
    "/test": "./service/test.js"
  },
  ssrForVue: {                          // 服务端渲染入口,需开启服务端渲染
    "/": {
      "path": "./index.vue",
      "router": "./router.js",
      "store": "./store.js"
    }
  }
}

development.vue.plugins说明

这里用来指定使用哪些vue插件...

插件约定:

export default function (option) {
    return function (Vue) {
        // Vue插件内容
    }
};

配置约定:

plugins: [
    "@snacking/see-u/bin/vue/plugins/prototype/event",           // 字符串形式,插件位置
    {                                                            // 对象形式
      Ajax: "@snacking/see-u/bin/vue/plugins/prototype/ajax",    // 插件名称以及位置
      option: "ajax"                                             // 选项,送入插件的参数,即插件约定中的option
    }
]

在对象形式中,选项的键必须是option,值可以是字符串、对象,或者是包含这两者的数组...

如果值是字符串,则表示取自development下的指定属性,比如development.ajax...

如果值是对象,可以是对象字面量,或者某个对象的引用...

如果值是包含字符串或者对象的数组,则按上述规则按顺序合并成一个对象...

客户端渲染

在工程的see-u.js里的entryForVue属性,以键值对的方式,映射入口名称与实际模块的关系...

"entryForVue": {
    "index": {
        "path": "./index.vue",
        "title": "hello! see u!"
        "router": "./router.js",
        "store": "./store.js",
        "favicon": null,
        "meta": null
    }
}

理论上支持 html-webpack-plugin 的所有选项(不过有些选项还没处理-。-!)...

服务端渲染

在工程的see-u.js里的ssrForVue属性,以键值对的方式,映射路由名与实际模块的关系...

"ssrForVue": {
    "/": {
      "path": "./index.vue",
      "router": "./router.js",
      "store": "./store.js"
    }
}

在需要多页面时,只要设置上下文,区分路由名...

"ssrForVue": {
    "/index1": {
      "path": "./index1.vue",
      "router": "./router1.js",
      "store": "./store1.js"
    },
    "/index2": {
      "path": "./index2.vue",
      "router": "./router2.js",
      "store": "./store2.js"
    }
}

后端服务

在工程的see-u.js里的service属性,以键值对的方式,映射服务名与实际模块的关系...

"service": {
    "/test": "./service/test.js"
}

在同一个工程中可以通过fetch("./test")请求...

如果是其他工程,可以通过fetch("/工程名/test")请求...

在服务名前面可以加入Method指定方法名,则只能使用相应方法才能访问,如果不设置则不限制

  "service": {
    "GET ./test": "./services/test.js"
  }

在实际模块前面可以加入Router标识,表明该模块将返回一个express的Router对象,而不是单一服务

  "service": {
    "./test": "Router ./services/test.js"
  }

注意:Method名称和Router标识不能同时使用,因为在Router里已经由模块控制了Method名称,以下是错误例子,可能返回404

  "service": {
    "POST ./test": "Router ./services/test.js"
  }

mock说明

在工程目录下创建mock.js...

module.exports = {
    'GET /api/user': { id: 1, username: 'test' },
    'POST /api/user/list': [
        { id: 2, username: 'temp' }
    ]
};

配置可参考mocker-api

proxy说明

在工程目录下创建proxy.js...

module.exports = {
    '/widgets/*': {
        target: 'http://127.0.0.1/demo/',
        secure: false,
        changeOrigin: true
    }
}

或者在工程或者工作空间里的see-u.js或者webpack.config.jsdevServer节点里加入配置...

proxy: {
   '/widgets/*': {
       target: 'http://127.0.0.1/demo/',
       secure: false,
       changeOrigin: true
   }
}

配置可参考http-proxy-middleware

服务地址切换

以上的mock和proxy,都是在本地服务器创建代理...

提供一种服务地址切换方案...

services.js

这是独立的服务地址文件,文件名任意,允许多个文件,内容以////// autoService开头,服务地址需要以对象字面量作为默认导出...

////// autoService
const base = "http://10.6.8.1:8060"

export default {
    "service1": `/widgets/widget | ${base}/widgets/widget`
};

每个服务地址是一个字符串或者字符串模板,测试地址和生产地址之间使用|(3个字符,为空格 + | + 空格)隔开,前面的是测试地址,后面的生产地址...

test.js

请求服务时,导入上述对象,用对象的属性代替字符串字面量或者变量作为入参...

import services from "./services";

fetch(services.service1)

当工作空间的see-u.js描述中的mode属性为production时,将会使用生产地址,其他值会使用测试地址...

如果地址中没有|,即不区分测试和生产时,mode无论是什么值都会直接使用这个地址...

该方案只是做服务地址切换,而不做跨域处理。实际生产地址可以是nginx代理地址,或者服务端增加access-control-allow-origin属性等跨域手段...

对Vue的扩展

http请求

在Vue的原型里扩展了$http属性,在组件中通过this.$http可获取到ajax实例,该实例提供以下方法...

request - 通用的请求

this.$http.request(url, options)
this.$http.request(options)

delete - 请求delete服务

this.$http.delete(url, options)

get - 请求get服务

this.$http.get(url, options)

head - 请求head服务

this.$http.head(url, options)

options - 请求options服务

this.$http.options(url, options)

post - 请求post服务

this.$http.post(url, data, options)

put - 请求put服务

this.$http.put(url, data, options)

patch - 请求patch服务

this.$http.patch(url, data, options)

all - 并发请求

this.$http.all(requests)

create - 创建新的ajax实例

this.$http.create(options)

getCancelToken - 获取取消令牌

this.$http.getCancelToken()

以上可以参考axios说明文档...

自定义ajax

在组件中通过this.$http.create(options)方法创建新的ajax实例,具体选项可以参考axios说明文档...

全局事件

在Vue的原型里扩展了$event属性,在组件中通过this.$event可获取到事件实例...

this.$event.$on()
this.$event.$emit()

跨文档消息

在Vue的原型里扩展了$xdm属性,允许在组件里以事件的方式进行跨文档消息传输...

on - 绑定事件

this.$xmd.on(identifier, callback)
  • identifier 事件标识
  • callback 回调函数

off - 解绑事件

this.$xmd.off(identifier, callback)
  • identifier 事件标识
  • callback 回调函数

emit - 触发事件

this.$xmd.emit(target, identifier, data)
  • target 消息发往的目标,可以是iframe,可以是open的窗口,或者其他window窗口
  • identifier 事件标识
  • data 发送的数据

事件防抖指令

v-debounce

在该指令的修饰符中,指定相关事件的防抖参数,一个完整的修饰符如event,wait,maxWait,leading,trailing,支持多个修饰符...

修饰符说明

event: 事件名称,例如click、change等,必须
wait: 延迟的毫秒数,非必须,默认为0
maxWait: 允许被延迟的最大值,无默认
leading: 指定在延迟开始前调用,默认false
trailing: 指定在延迟结束后调用,默认true

例子

<Button v-debounce.click,500 @click="btnClick">防抖指令测试</Button>

多个修饰符

<Button v-debounce.click,500.change,200 @click="btnClick">防抖指令测试</Button>

事件节流指令

v-throttle

在该指令的修饰符中,指定相关事件的防抖参数,一个完整的修饰符如event,wait,leading,trailing,支持多个修饰符...

修饰符说明

event: 事件名称,例如click、change等,必须
wait: 延迟的毫秒数,非必须,默认为0
leading: 指定在延迟开始前调用,默认false
trailing: 指定在延迟结束后调用,默认true

例子

<Button v-throttle.click,500 @click="btnClick">节流指令测试</Button>

多个修饰符

<Button v-throttle.click,500.change,200 @click="btnClick">节流指令测试</Button>

权限校验指令

v-auth

支持disable修饰符,如果指定disable,则在无权限的时候,将元素设置为disabled,否则删除当前元素...

支持role修饰符,如果指定,则把指令值(value)作为角色标识,否则将指令值作为权限标识...

指令值(value)为字符串,多个则以逗号分隔...

<Button v-auth="submit">指令测试</Button>
<Button v-auth.disable="submit">指令测试</Button>
<Button v-auth.disable.role="monitor">指令测试</Button>

使用该指令前,需要在see-u.js中的development.vue.plugins插件配置中,给@snacking/see-u/bin/vue/plugins/directives/auth插件传递一个用来校验权限的方法

const auth = require("./auth");
……
{
    "Auth": "@snacking/see-u/bin/vue/plugins/directives/auth",
    "option": {
        "handler": auth
    }
}

该方法约定为function(type, values),其中type为类型,值为role或者auth;values是权限或者角色,是一个字符串数组...

关于vue-router

路由文件配置在see-u.js描述中的entryForVue或者ssrForVue入口的router属性中...

路由文件允许导出以下对象

  • VueRouter实例,即new VueRouter(options)
  • VueRouter的选项,即options(推荐)
  • VueRouter选项中的routes属性,即options里的routes属性

推荐导出VueRouter的选项(options)

  • 由脚手架根据选项生成唯一路由实例
  • see-u.js描述中,通过route.paramsToProps开关,自动设置路由配置中的props属性,将params通过组件的props属性传递
  • 全局守卫可在options中描述,脚手架会将其注册在生成的路由实例中

关于Vue组件大量导入和局部注册的解决方案

当ui库按需加载,或者大量导入组件,或者需要按目录导入组件时,导入和局部注册都是繁琐的...

提供一种编写注释块来实现自动导入和局部注册的方案:

/**
 * @loader autoImport
 * @ui Button,Col,Row,Checkbox
 * @comps ./user,./order.vue
 * @path ./page,../image
 * @recursivePath ./components,../flows
 */
  • @loader autoImport 注释快描述
  • @ui ui组件名,根据当前项目配置的ui库进行导入和注册,多个用逗号分隔
  • @comps 明确的组件名,多个用逗号分隔
  • @path 指定目录下的所有组件,多个用逗号分隔
  • @recursivePath 指定目录下的所有组件,含所有子目录里的组件,多个用逗号分隔

注意:使用逗号分隔时,中间不能有空格...