vue + vuex + vue router + TypeScript(支持 JavaScript) 模板
Node.js
: >= v12yarn
: 最新
Git
: 最新 代码管理Visual Studio Code
(VSCode): 最新 IDE
VSCode 插件
Volar
: 最新 vue 开发必备GitLens
: 最新 Git 可视化工具ESLint
: 最新 脚本代码检查stylelint
: 最新 样式代码检查Prettier - Code formatter
: 最新 代码格式化
浏览器插件
Vue Devtools
: 最新
推荐工具:
@vue/cli
(最新), 全局安装时可使用vue ui
命令启动图形化界面管理工程
推荐开发字体: FiraCode
支持现代浏览器及 IE10+
IE / Edge |
Firefox |
Chrome |
Safari |
---|---|---|---|
IE10, IE11, Edge | last 2 versions | last 2 versions | last 2 versions |
git config core.ignorecase false # 使git对文件名大小写敏感
# 或者修改 .git/config [core] ignorecase=false
# 安装慢可以使用淘宝镜像
yarn config set registry 'https://registry.npm.taobao.org'
# 安装
yarn
yarn dev # --port 9876 : 本次启动使用9876端口 (可以在 .env.development.local 文件中设置)
yarn lint
yarn test:unit # --watch : 跟踪文件变化
yarn test:e2e
yarn build # --watch: 跟踪文件变化 --report: 生成打包分析
同时会生成文件名/chunk名映射文件 (公共代码抽到_
开头的文件里了)
- 使用 .env
_ENTRIES
配置, 示例:_ENTRIES=foo,bar
- 通过cli命令:
entries
(覆盖1):
yarn dev --entries=foo
yarn build --entries=foo,bar
可通过环境变量访问: process.env.ENTRIES: string[]
route
目录结构
├── route # 全部路由配置文件夹(缺省指令)
├── route.foo # foo配置 指令: foo
├── route.bar # bar配置 指令: bar
# ...
- 配置 .env
_ALIAS
, 示例:
# 配置别名, json: [
# [别名, 路径(相对根目录或入口), 默认指令(可省略)] 或者
# [别名, 路径, [允许的指令, ...], 默认指令(可省略)]
# ... ], 可使用cli命令指定指令(优先 --alias=foo.bar,bar.foo )
_ALIAS=[["@fRoute", "foo/route", "foo"], ["@bRoute", "src/pages/bar/route", ["foo", "bar"], "bar"]]
- 通过cli命令:
alias
:入口.指令,...
yarn dev --alias=foo.bar
yarn build --alias=bar # 省略形式: 指令唯一时
yarn build --alias=foo.bar,bar.foo
可通过环境变量访问: process.env.ALIAS: { [key in process.env.ENTRIES]: string[] }
yarn vue-cli-service help # [命令] : 比如 yarn vue-cli-service help test:e2e
支持通过浏览器地址栏动态传递调试参数
-
环境变量
SEARCH_FIELD
: 所有ajax请求将会把满足该正则的参数传递给服务端 -
动态代理字段, 通过
PROXY_FIELD
指定, 数字与静态代理(BASE_PATH
+PROXY_TARGET
)一致, 可以指定为环境变量名(比如 PROXY_TARGET)/ip/域名/完整url. 匹配的代理字段将不会传递给服务器# 若文件 .env.development 配置如下: # 相对路径(hash router) # BASE_PATH=foo # PROXY_TARGET=http://foo.com:666/foo # BASE_PATH1=bar # PROXY_TARGET1=https://bar.com:999/bar # PROXY_FIELD=proxy # SEARCH_FIELD=$PROXY_FIELD\d* # 访问如下url: http://{ip}:{port}/{path}?proxy=http://127.0.0.1/foo&proxy1=0.0.0.0 # 结果: BASE_PATH 为 foo 的接口代理到 http://127.0.0.1/foo # BASE_PATH 为 bar 的接口代理到 https://0.0.0.0:999/bar
- 偶现
tsconfig.json
被清空 @typescript-eslint
升级到 v3 (ESLint v7 & TypeScript v3.9+ 完整支持)- 图标概率乱码:
scss
压缩模式下 (compressed mode) 使用 UTF-8 byte order mark (UTF-8 with BOM) 代替 @charset 声明语句导致, 暂时先使用 cssimport 'element-ui/lib/theme-chalk/base.css'
- 开发环境代理(devServer)第三方网站 set-cookie path
├── build # 脚本
│ └── ~fileName # 部署文件chunk名字典
├── dist # 生成的部署文件目录
├── public # 静态文件目录, 除SPA模板(*.html)外, 直接复制到输出目录下
├── src # 源码目录
│ │── api # http通信
│ │── assets # 静态资源文件目录, 使用到的会被解析处理(比如图片等)
│ │── components # 提取的复用组件(文件夹分类, 未分类的基本就是基础组件了)
│ │── config # 配置目录(输出到内联js, 可以直接修改而不用重新打包)
│ │── enums # 枚举目录
│ │── functions # 提取的复用逻辑(文件夹分类, 未分类的基本就是公用逻辑了)
│ │── lang # 多语言目录
│ │── libs # 存储不(能)通过 npm 管理的第三方库/依赖库等相关
│ │── route # 路由配置
│ │── router # 路由逻辑
│ │── pipe # 数据管道, 用于处理数据流(比如api响应的数据)
│ │── scss # 样式/CSS 对象(.module).scss 文件
│ │── skin # 皮肤文件(夹)
│ │── store # 状态管理
│ │ └── modules # 各模块状态管理
│ │── types # ts 接口/申明文件
│ │── utils # 工具集(业务无关, 一般为幂等函数/单例对象/Class...)
│ │── views # 视图
│ │── pages # 【可选】多页时页面的存储目录
│ │── html模板名 # 【可选】存放页面代码目录
│ └── (html模板名/main/index/entry/app/page).(ts|tsx|js|jsx) # 默认入口文件
├── tests # 测试用例目录
│ │── e2e # e2e 测试(cypress): https://www.cypress.io
│ └── unit # unit 测试(jest): https://jestjs.io
├── .env # 所有环境的环境变量(满足正则/^[A-Z]+(?:_[A-Z]+)?$/的可通过process.env访问)
├── .env.[mode] # 指定环境的环境变量(mode: development/production/test)
├── .env.local/env.[mode].local # 本地环境变量(git忽略)
├── ... # 其他配置文件
└── vue.config.js # 配置入口(vue cli)
目录结构说明:
-
只支持对
public
目录下的 html 模板(不包括子文件夹下的)自动设置入口, 规则为:- 从 public 目录得到一个 html 模板
- 依次查找 src 目录、 src/pages 目录:
- 是否存在与 html 模板同名的(ts/tsx/js/jsx)文件且未占用
- 依次检查下列文件名: main/index/entry/app/page 是否存在且未占用
- 在与 html 模板同名的目录下重复上两步查找, 仍未找到则忽略
建议: 单页入口直接放 src 目录下, 多页时入口分别放在 pages 目录下与 html 模板同名的文件夹下
-
@
=>src
@com
=>src/components
@{entry}
=> 页面入口文件所在目录, 如:@index
@{entry}Com
=> 页面入口文件所在目录下的components
目录(若存在), 如:@indexCom
- 配置的别名
Tips: 在
scss
中使用~
解析别名
/依赖包
, 示例:<!-- SomeView.vue --> <template> <div :class="$style.wrapper"> <!-- 视为ts/js --> <img src="@/assets/logo.png" /> </div> </template> <style lang="scss"> @import '~normalize.css'; </style> <style lang="scss" module> .wrapper { background: url(~@index/assets/bg.png); } </style>
-
config
目录下的所有内容都会被内联到对应html
中(需要保留的注释请使用:/*! 注释内容 */
), 用于支持直接修改配置而不必重新打包代码 -
api
层统一按照微服务划分, 放到src/api
目录下, 有不同交互逻辑的, 就近添加api
目录 (接口数据类型可从文档复制然后正则替换) -
测试用例目录层级与文件名应尽量与源码对应
提示和建议
-
src
下新建目录时应尽量复用现有目录名, 在保证结构清晰的情况下减少目录层级 -
目录及文件命名:
文件夹及其它文件(js/scss/图片等)使用
camelCase
(即: 首字母小写驼峰 = lowerCamelCase)vue 单文件组件(含tsx/
ts
/jsx
/js
)使用PascalCase
( 即: 首字母大写驼峰 = CamelCase = UpperCamelCase)例外情况:
-
设计或重构(拆分)组件为多个部分的, 使用文件夹包裹, 比如:
// BillList组件 BillList │── index.(vue|tsx|ts|jsx|js) # 可以例外 │── Item.vue └── ... // 使用BillList组件 import BillList from '{path}/BillList'
-
-
先设计功能模块/组件再(目录层级)向下细分或向上提取
-
越接近 src 目录的, 测试覆盖率也应越高; 被测试的代码应加注释
@test: true
表示在对应目录下包含测试用例, 否则指明测试代码路径或就近建__tests__
目录; 修改了测试覆盖的代码后, 应视情况增加测试内容 -
尽量不要使用全局注册(插件/组件/指令/混入等)以优化性能并且代码更清晰、更易维护
-
尽量按照依赖库的文档描述来使用她, 从其源码(src)引入模块(css/scss/.../js/mjs/ts/jsx/tsx/vue), 将可能不会被处理且更可能随版本更新改变, 需要时可以从其构建后的 lib/dist 等目录引入或者增加一些配置(需要了解模块解析及转码规则和相关插件, 不推荐)
-
若开发环境出现缓存相关错误信息导致热更新慢, 可以删除
node_modules/.cache
文件夹再试 -
路由路径不应出现符号
.
, 以方便history 路由
模式开发/部署
推荐使用 TypeScript
-
JavaScript/TypeScript 代码风格为 JavaScript standard, 主要有以下区别:
- 使用单引号
- 不要句尾分号
- 多行末尾保留逗号
- 方法名后不要空格
(.vscode 文件夹为 VSCode 的工作区设置, 只在本工程生效, 已包含相关设置)
-
optional/OOP/functional
之争看来大趋势是functional
了, 故推荐optional
组件, 方便迁移到 Vue3 -
请直接使用
ES6+
, 除了Proxy
等极少数不能shim和polyfill的(实际业务也极少用到), 不需要考虑兼容, 也不要做没必要/效果微弱的的优化(我检讨), 优先考虑代码可读性 -
引用
vue/tsx/ts/js/jsx
不要加文件扩展名 且省略/index
, 有利于重构代码 -
在
tsx/jsx
中使用全局组件时应使用kebab-case
, 否则会在控制台输出错误 ┐(: ´ ゞ`)┌import { CreateElement } from 'vue' import { Component, Vue } from 'vue-property-decorator' @Component export default class extends Vue { protected render(h: CreateElement) { return ( <el-row> <el-button>这是个按钮</el-button> </el-row> ) } }
-
enum/type/interface
需要导出的直接export
(否则可能会得到 undefined), 其他的除了字典(硬编码)和只有默认导出的外, 先定义再export
(IDE 提示更友好), 并且export
语句放到最后 -
不要使用
$
作为组件事件名, 该名字已被异步组件刷新占用 -
私有属性命名规则(避免 属性名 冲突):
$_
实例属性名前缀(在保证易维护的前提下可以使用单字母, 但应尽量避免)_$
全局/跨组件/hack属性名前缀, 命名前应先全局搜索是否有重复/^[^$_]+_$/
注入vue data 选项的属性名应满足该正则, 即以_
结尾(以$_
其中一个字符开头的Vue会略过), 命名前应先全局搜索是否有重复
-
除了以下情况外, 不应使用全局样式:
应使用 CSSModule(开发环境class名:
[folder]__[name]_[local]-[emoji]$
), 以更好地实现模块化(支持复用) -
CSS Modules class 名使用
camelCase
, 选择器嵌套不应超过三层 -
皮肤文件(
*.scss
) (包含各别名下GLOBAL_SCSS环境变量指定的文件) 中不要出现具体样式, 也不要有:export{}
(:export{}
应在scss/export
目录下或export*.scss
中使用) -
为保证在
ts/js
中引入scss文件时, 变量注入正确(使用缓存会导致无法对相同文件注入不同变量,不用缓存显然不好而且也不支持), 应在合适的位置新建 scss 文件来中转:/// el.scss @import '~element-ui/packages/theme-chalk/src/button';
<template> <ElButton>默认按钮</ElButton> </template> <script lang="ts"> import { Component, Vue } from 'vue-property-decorator' // 没有打包ESM, 不支持 tree-shaking ┐(: ´ ゞ`)┌ // import { ElButton } from 'element-ui' import ElButton from 'element-ui/lib/button' import './el.scss' @Component({ components: { ElButton } }) export default class extends Vue {} </script> <!-- 也可以在这儿引用 <style lang="scss"> @import '~element-ui/packages/theme-chalk/src/button'; </style> -->
或
import { CreateElement } from 'vue' import { Component, Vue } from 'vue-property-decorator' import ElButton from 'element-ui/lib/button' import './el.scss' @Component export default class extends Vue { protected render(h: CreateElement) { return <ElButton>默认按钮</ElButton> } }
(注: 可能出现注入不正确的情况比如各个入口的 skin 不一样但是使用了相同 scss 文件)
-
不要用全局样式覆盖全局样式, 应使用
CSSModule
并使优先级相等(注意顺序, 包括同步/异步)或更高:// 以下默认local // bad →_→ :global(.content .title.active) { color: $colorHighlight; } // good b( ̄▽ ̄)d .content :global(.title.active) { color: $colorHighlight; } // good b( ̄▽ ̄)d .content .title { &:global(.active) { color: $colorHighlight; } }
-
尽量使用代码模板, 现有模板有(VSCode 输入左侧字符, 其他 IDE 查看模板):
ts
:.vue
文件中使用,TypeScript + scss module
vue
:.tsx
文件中使用,TypeScript
ts-class
:.vue
文件中使用,TypeScript(Class) + scss module
vue-class
:.tsx
文件中使用,TypeScript(Class)
vuex
:.ts
文件中使用,dynamic vuex class module
js
:.vue
文件中使用,JavaScript + scss module
vue
:.jsx
文件中使用,JavaScript
js-class
:.vue
文件中使用,JavaScript(Class) + scss module
vue-class
:.jsx
文件中使用,JavaScript(Class)
vuex
:.js
文件中使用,dynamic vuex class module
-
Feat
增加新功能Fix
修复问题/BUGStyle
代码风格相关无影响运行结果的Perf
优化/性能提升Refactor
重构Polish
润色Revert
撤销修改Test
测试相关Docs
文档/注释Chore
依赖更新/脚手架配置修改等Workflow
工作流改进Ci
持续集成Mod
不确定分类的修改
-
注意 IDE 使用的 TypeScript 版本, 应使用依赖的版本
-
关于换肤方案, 本模板采用
alternate stylesheet
方案, 基于scss
变量注入同时构建多个皮肤. 可通过环境变量.env进行相关配置, 在import
scss 文件时可以指定皮肤和使用的 scss 变量<script lang="ts"> /// 基础样式(所有皮肤下都生效) /// import './scss/a.scss?skin=' // 指定scss变量文件相对路径(别名|皮肤文件(相对皮肤文件夹)) import './scss/b.scss?skin=|foo.scss' /// 皮肤样式 /// import './scss/c.scss' import D from './scss/d.module.scss' /// 指定皮肤样式 /// import './scss/e.scss?skin=dark' import './scss/f.scss?skin=light' // CSS Module import getSkin from '@/skin' import dark from './scss/g.module.scss?skin=dark|foo.scss' import light from './scss/g.module.scss?skin=light|bar.scss' import { Component, Vue } from 'vue-property-decorator' const G = getSkin({ dark, light }) @Component export default class extends Vue { protected get D() { return D } protected get G() { return G } } </script> <!-- 基础样式 --> <style lang="scss" module skin="|"> .bar { display: none; } </style> <style lang="scss" module skin="|foo.scss"> .bar { color: $red; } </style> <!-- 皮肤样式 --> <style lang="scss" module> .foo { color: $red; } </style> <!-- 指定皮肤样式【不支持CSS Module】 --> <style lang="scss" skin="dark"> .foo { color: $red; } </style> <style lang="scss" skin="light"> .foo { color: $red; } </style>
-
正确规范(JSDoc)简洁适当的各种注释:
/** 二维点 */ interface IPoint { x: number y: number /** 描述 */ desc?: string } /** 角度转弧度常量 */ const ANGLE_RADIAN = Math.PI / 180 /** 计算圆上的点 * * @param center 圆心 * @param radius 半径 * @param angle 角度 * * @returns 圆上的点坐标 */ function getPointOnCircle(center: IPoint, radius: number, angle: number): IPoint { const redian = angle * ANGLE_RADIAN // 弧度 return { x: center.x + radius * Math.sin(redian), y: center.y - radius * Math.cos(redian), } } export { getPointOnCircle }
分支注释:
// if return / 策略 b( ̄▽ ̄)d if(...) { // 说明 } else if(...) { // 说明 if(...) { // 说明 } else { // 说明 } } else { // 说明 } // 使用枚举或字典时可不写注释 switch(expression) { case value1: // 说明 ... break case value2: // 说明 ... break default: ... }
-
异步 chunk 使用层次命名(方便排查问题和碎文件合并), 比如: index 页面下的 home 视图命名为
index_home
, 其下的用户视图命名为index_home_me
, 用户基础信息命名为index_home_me_baseinfo
. 为避免文件名太长, 可以缩写为:iHome
,ihMe
,ihmBaseInfo
-
libs 下的库文件模块化懒加载示例(只会成功加载一次):
/// src/libs/somelib/index.ts /** 模块化异步引入somelib及其插件(全局类似) * @param plugins 需要加载的somelib插件名列表: * * plugin1: 插件1 * * plugin2: 插件2 * * ... * * @returns {Promise<Array<Module>>} 模块 */ export default (plugins: string[] = []): Promise<any> => { let somelib: any return import(/* webpackChunkName: "lSomelib" */ './somelib.min') .then((module: any) => { somelib = module.default return Promise.all(plugins.map((plugin: string) => { switch (plugin) { case 'plugin1': return import(/* webpackChunkName: "lsPlugins" */ './somelib.plugin1.min') case 'plugin2': return import(/* webpackChunkName: "lsPlugins" */ './somelib.plugin2.min') // 上面两个插件合并到同一个chunk里 // ... } }) as Array<Promise<any>>) }) // 注册插件(略), 返回somelib .then((modules: Array<Promise<any>>) => somelib) } /// src/pages/index/components/Foo.vue import get from '@/libs/somelib' @Component export default class extends Vue { protected init() { get(['plugin2']).then((somelib: any) => somelib.init(this.$refs.panel)) } }
-
所有视图组件可接收props:
route
代替this.$route
, 区别是: 只在首次进入当前视图或当前视图url发生变化时改变 -
路由视图不需要被缓存的, 可以在
meta
申明/deactivated
钩子销毁实例(this.$destroy()
)或activated
钩子进行更新 -
@/functions/router
对vue-router
的push/replace/resolve
API 做了以下增强- 支持相对路径解析, 比如, 当前路径为
/home/foo
则:/bar
=>/bar
,bar
=>/home/bar
,./
=>/home/
,../bar
=>/bar
,../../
=>/
- 支持模块路径解析, 比如:
{ id: 'foo', path: '../bar' }
, 若模块foo
的path
不为空将作为当前路径, 否则使用当前路径, 按1
的方式解析 - 支持刷新目标页, 比如:
{ path: 'foo', refresh: true }
- 支持相对路径解析, 比如, 当前路径为
-
url 地址跳转,
SPA间
使用@/config
的g
方法进行跳转,SPA内
跳转this.jump
(或eventBus
仅修改了 vue-router 默认解析规则, 支持相对路径, 如 'foo'='./foo' '../bar'等, 查看定义) -
为避免渲染错误, 请务必为 循环创建的组件 加上
key
, 需要特别注意tsx/ts/jsx/js
文件(没有代码提示)
可通过 vue.config.js (入口)文件配置工具链; .env.*
配置环境变量; 根目录下各配置文件配置相应工具
-
减小图片大小(比如背景图片等)
-
对多个 js chunk 共同依赖的模块进行缓存/单独提取, 大模块只出现在一个chunk里(cacheGroups)
-
相同chunk下的基础样式和各个皮肤样式文件分别合并 或 其他合理的合并策略【webpack 5 支持设置 css chunk 的 minSize/maxSize 啦】
-
打包/热更新慢:
- [热更新]使
hard-source-webpack-plugin
(动态缓存结果文件)在开发环境排除src
文件夹生效 - [热更新]开发环境减少js(ts必须编译)转码/编译工作量, 比如保留最新ES标准语法, 更进一步可以配合模块加载方式(
<script type="module">
参考vite), 但这需要使用最新浏览器调试且调试时不会暴露兼容性问题 - [热更新]开发时指定需要的模块, 比如指定路由
- 优点: 只需构建指定模块, 提升热更新效率
- 缺点: 需要其他模块时需要重新指定
- (第三方)依赖静态化[不推荐]
- 优点: 静态化依赖只需一次构建; 支持CDN加载(可供多个应用使用)
- 缺点: 通常会增加文件体积(无法tree-shaking)/数量(不能合并), 可能导致全局变量污染; CDN维护更新/版本管理
- 下一代方案: 使用 esbuild 提升构建/压缩速度
- [热更新]使
-
chunk hash 长度: 修改 webpack.optimize.SplitChunksPlugin
/* 23 */ const hashFilename = name => { /* 24 */ return crypto /* 25 */ .createHash("md4") /* 26 */ .update(name) /* 27 */ .digest("hex") /* 28 */ .slice(0, 5); // 默认8无法配置 /* 29 */ };
- url 重写
rewrite
兼容旧版 - 反向代理, 绕过同源策略限制(api/图片等资源跨域等)
- 开启
gzip
压缩, 并重用已有gz
文件gzip_static on;
- 缓存静态资源(html 可减少缓存时间)
- HTTP2 Server Push 服务器推送, 需要
nginx
版本1.13.9及以上, 文档链接 - 多个SPA(即
html
文件)以history路由(访问url不以#
号标识)部署:- 一个
html
一个 location 或 待运维大佬完善(示例如下) - 按照注释修改每个
html
的配置, 其中base
改为对应访问路径
- 一个
配置示例( nginx.conf
文件, xxx
换成对应值):
http {
include xxx/mime.types;
default_type application/octet-stream;
# log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
# access_log xxx/access.log main;
sendfile on;
# tcp_nopush on;
keepalive_timeout 90;
# underscores_in_headers on; # 允许带下划线的请求头
# 开启gZip(图片除外)
gzip on;
gzip_vary on;
gzip_static on;
gzip_proxied any;
gzip_comp_level 3;
gzip_min_length 3k;
gzip_buffers 32 16k;
gzip_types application/xml application/json application/ld+json application/rss+xml application/atom+xml application/xhtml+xml application/font-woff application/x-font-ttf application/x-javascript application/javascript application/x-httpd-php application/x-font-woff application/vnd.geo+json application/octet-stream application/manifest+json application/vnd.ms-fontobject application/x-web-app-manifest+json font/opentype text/vtt text/css text/plain text/vcard text/javascript text/x-component text/cache-manifest text/vnd.rim.location.xloc text/x-cross-domain-policy image/svg+xml;
# server {
# http 跳转到 https
# server_name xxx;
# listen 80;
# listen [::]:80;
# return 301 https://$host$request_uri; # 得用 $host,不造为啥 $server_name 不行
# }
server {
# server_name xxx;
# http
listen 80;
listen [::]:80;
# https + http2
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate xxx.crt; # 证书
ssl_certificate_key xxx.key; # 私匙
ssl_session_cache shared:SSL:5m; # 共享会话缓存大小
ssl_session_timeout 15m; # 会话超时时间
# ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
# ssl_ciphers HIGH:!aNULL:!MD5; # 定义算法
# ssl_prefer_server_ciphers on; # 优先采取服务器算法
# http2_push_preload on; # 开启服务器推送
# add_header Set-Cookie "session=1"; # 添加cookie标识首次访问
# add_header Link $res; # 添加响应头 Link 指定要推送的文件, 使用自定义变量 res
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; # HSTS策略
add_header X-Frame-Options DENY; # 减少点击劫持
add_header X-Content-Type-Options nosniff; # 禁止服务器自动解析资源类型
add_header X-Xss-Protection 1; # 防XSS攻擊
client_max_body_size 10m; # 请求体大小限制 for上传文件
fastcgi_intercept_errors on; # 拦截(替换)代理服务的错误页
error_page 500 502 503 504 413 /50x.html; # 错误页
error_page 404 403 /404.html; # 未知页
location /(50x|404).html {
expires 7d;
root /xxx;
}
location / {
# rewrite ^/(?:path|path-alias)/(.*)$ /$1 last; # 兼容某些url
# 设置静态资源缓存(文件名已带内容哈希了)
if ($uri ~ .*\.(?:js|css|jpg|jpeg|gif|png|ico|gz|svg|svgz|ttf|eot|mp4)$) {
expires 7d; # d: 天
}
# html(文件名不变)
if ($uri ~ .*\.(?:htm|html)$) {
expires 25m; # m: 分钟
# add_header Cache-Control private,no-store,no-cache,must-revalidate,proxy-revalidate;
}
index index.html;
alias xxx/;
set $u /; # for 多页history路由 其他location: ^/location([^/]+)
if ($uri ~ ^/([^/]+)) {
set $u $1.html; # 待测试并完善
}
try_files $uri $uri/ $uri.html $u / =404;
# try_files $uri $uri/ $uri.html /location$u /location =404;
}
# 服务接口代理
location /api {
proxy_pass https?://xxx:xxx/xxx;
# http2_push_preload on; # 转发 Link 响应头
# 缓存策略...
}
# iframe 集成外部网站
location /foo/ {
add_header X-Frame-Options SAMEORIGIN;
proxy_cookie_path /bar /foo;
proxy_pass https?://xxx:xxx/bar/;
}
}
# 根据cookie映射变量res
# map $http_cookie $res {
# "~*session=1" ""; # session为1时返回空字符串
# # nopush: 若客户端存在缓存就不推送了
# default "</style.css>; as=style; rel=preload; nopush, </image2.jpg>; as=image; rel=preload; nopush, </index.js>; as=script; rel=preload; nopush";
# }
}
(似乎也没必要)补全字体图标配置(mime.types
文件):
font/woff woff;
font/woff2 woff2;
+ font/ttf ttf;
+ font/opentype otf;
application/vnd.ms-fontobject eot;
| scss | vue | vuex | vue-router | vuex-class | vue-class-component | vuex-module-decorators | vue-property-decorator | vue-i18n | element-ui | axios | crypto-js | jsencrypt | npm
2D
| ECharts | zrender | d3 | zdog | pixi.js (WebGL2/WebGL)
3D
| three.js (WebGL2/WebGL) | luma.gl (WebGL2/WebGL)
-
在
ts/js
中使用assets
目录下的图片可以import IMG from '@/assets/img/*.png'
(或 require),IMG
为图片输出路径或 base64 字符串, 其他类似(新的文件格式需要配置 loader 和补充ts 定义) -
在
scss
中引入css
(@import) 有两种方式- 【推荐】不带文件后缀, css 文件内容会被合并到当前文件, 比如:
@import '~normalize.css';
- 带文件后缀, 会处理成 css 的@import, 比如:
@import '~normalize.css/normalize.css';
- 【推荐】不带文件后缀, css 文件内容会被合并到当前文件, 比如:
-
文件下载(需要token且在请求头设置)
import { get } from '@/utils/ajax' // 登陆后已设置请求头携带token get('', { id: '' }, { responseType: 'blob' }).then(res => { let fileName = res.headers['content-disposition'].split(';') fileName = fileName[fileName.length - 1].split('=') fileName = fileName[fileName.length - 1] const href = window.URL.createObjectURL(new Blob([res], { type: res.type })) const link = document.createElement('a') link.style.display = 'none' link.download = fileName link.href = href document.body.appendChild(link) link.click() document.body.removeChild(link) window.URL.revokeObjectURL(href) })
-
提交时
lint-stage
【10.0.8已修复】会删除未暂存的新增文件(撤销全部未暂存修改), 待相关命令(比如lint)执行完后再恢复, 所以若报错, 被删除的文件将无法恢复, 解决方案有如下几种:- 确保未暂存的文件里没有新增的, 或者在编辑器打开或手动备份新增的文件用以恢复. 最好能控制好提交粒度, 全部暂存后再提交
- 先执行
yarn lint
等相关命令, 待消除所有错误后, 再提交 - 确保开发时无相关报错(浏览器中页面遮罩显示错误, 控制台打印警告), 特别注意
console.log
,debugger
和定义但未使用变量
, 只在开发环境是警告, 其他都是错误
-
class 组件不要使用全局 API 函数, 比如 refresh 等
- Vue 异步组件加载失败重试: 最好还是 Vue 对异步组件提供支持#9788
- 现代模式(只针对 js 文件): 优先使用模块加载(体积更小执行更快,
<script type="module">
&<link rel="modulepreload">
); 不支持时回退到普通加载(<script nomodule>
&<link rel="preload">
) - #714: 【不再考虑支持】可追踪引用, 使在 js 中引用 scss 时可正确注入 scss 变量
- scss 模块化: 已出 beta 但生态不完善, 草案
- 微前端化: 应考虑基于 Web Components (vue 友好, 可以兼容其他) 的集成和通信.
tsx
类型支持(去掉as any
, 利于重构)- 只下载当前皮肤(暂无必要, 不影响性能, 未验证移动端换肤方案是否可行)
vue-router
不再支持scrollBehavior
, 可在afterEach
自行处理
- 观望 deno
- 期待 vue3.0及其生态正式版 & webpack 5.0 正式版 沒空
crypto-js
v4 不支持 IE10