fe
v2.0.27
Published
Front-End workflow & stack
Downloads
142
Readme
FE
Front-End workflow & stack
业务代码回归 最简单 的组织形式:
index.js server modules/ user/ index.js client pages/ index.vue
多人协作 / 多 stage 模式下 统一的 环境管理
# dev stage $ brew install fe # test stage $ apt-get install fe # production stage # docker intergration $ npm i fe -g
性能
fastify - The fastest and low overhead web framework
最小化业务接口耦合, 根据社区最佳实践持续为生产环境优化, 不再业务中考虑性能优化点
less is more
最小约束, 最广泛的项目类似梳理, 支持所有类型项目: SSR / SPA / vue-only / react-only / server-only
- 同构 模型
基于 next | nuxt 实现 react / vue 同构
- 完善的 生态 圈
# List resource in fe stack
$ fe list
# Add deps
$ fe add
node 环境依赖
node 8+
npm / yarn(优先)
安装
🎉🎉 推荐 内网急速安装 🎉🎉
$ curl https://code.byted.org/snippets/162/raw -L -o - | sh
标准安装
$ yarn global add fe
# or
$ npm i fe -g
快速开始
方式一
一般项目开发流程为 1. 通过脚手架创建项目 2. 启动体验良好的开发模式 3. 部署管理
对应命令为:
fe init
» fe dev
» fe start
方式二
此外也可以建一个空的项目目录原地启动, 当执行 fe dev
时候会检查和通过询问交互的方式最小化配置项目
$ mkdir project && cd project
$ fe dev
代码风格:
严格使用 prettier, 请基于自己的编辑器配置 Editor Support
格式如下
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": false,
"parser": "babylon",
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": true,
"jsxBracketSameLine": false,
"overrides": [
{
"files": ".prettierrc",
"options": { "parser": "json", "trailingComma": "none" }
},
{
"files": ".babelrc",
"options": { "parser": "json", "trailingComma": "none" }
},
{
"files": "*.json",
"options": { "trailingComma": "none" }
}
]
}
功能集:
dev
- 前/后端热更新
- mock
- proxy
start
- 启动生产环境(需要先 build)
add
- 安装插件并初始化配置?
build
- 构建生产部署代码
static
- 静态化产出物(用以实现 H5 离线化)
env
- 检测并展示环境信息, 便于 DEBUG
- 实现自检 / 自动修复 / 警告
list
- 展示模板 / 组件 / 中间件 / UI 等生态环境
fd
- 对接公司 consul
前端路由策略
"/" =>
pages/[index.vue | index.js]
"/detail" =>
pages/detail.vue
"/sub/detail" =>
pages/sub/detail.vue
基础业务类型和项目结构:
vue-only
├── pages └── *.vue
or
├── client
└── pages
└── *.vue
react-only
├── pages └── *.js
├── client └── pages └── *.js
server-only
└── index.js
server+vue
├── index.js └── pages └── *.vue
server+react
├── index.js └── pages └── *.js
log 日志
吐槽:fastify 的日志与框架耦合得太深,导致日志的自定义配置比较麻烦
//index.js 入口文件
module.exports = async (app, options) => {
// ....业务代码
}
// opt参考:https://github.com/fastify/fastify/blob/master/docs/Logging.md
module.exports.logger = opt
bytedance 内部使用
//index.js 入口文件
const { opt, hook } = require('byted-fastify-logger')()
module.exports = async (app, options) => {
hook(app)
// ....业务代码
}
module.exports.logger = opt
业务自定义策略
配置文件
入口文件: /fe.json
对接 fe
框架的配置文件全部放在 CONFIG_DIR
选用 json 格式, 考虑到
fe
某些情况下需要覆写(比如 0 配置启动时, 检测完网络情况后)
默认配置
fe.json
中自定义配置, 优先级策略:
- babel 配置优先级: .babelrc(如果存在) > BABEL_CONFIG > 默认配置
- 优先
fe.json
, 与下列默认配置浅合并
向后兼容策略, 只增不改
一般地, 值为
""
的项需要由项目模板中指明, 或者会通过文件检查和询问方式来自动配置
// 当前项目依赖版本, 便于生产环境实施向后版本兼容(遵循 npm version)
// 如果在 package.json 中安装了 `fe` 依赖, 优先级 package.json > fe.json
FE_VERSION: '^2.0.0'
// 指定后端服务的入口文件, 如果值为 `null` 意为不需要入口(纯前端项目)
ENTRY_FILE: 'index.js'
// [vue | react] 指定前端框架类型, 如果值为 `null` 意为不依赖前端框架
CLIENT_MODE: ''
// 前端目录, 纯前端项目: "."; 包含前后端业务, 推荐: "client",
CLIENT_DIR: ''
// 无需编译的 public 资源目录(如 favicon)
// 默认访问规则 prefix + PUBLIC_DIR
// 默认文件目录为:
// - project
// + public
// + client
// + server
// 例如:
// http://a.com/web/public/favicon.ico
PUBLIC_DIR: 'public'
// URL path prefix
// 访问前缀
// 同时影响: client / server / public 三层访问方式都会增加访问前缀
PREFIX_PATH: ''
// 编译后的静态资源访问路径
// 默认为 "/"
// 当需要配置 CDN 时, 这里配置为 CDN 地址
// 例如:
// "PUBLIC_PATH": "//s3a.pstatp.com/caijing/caijing_insurance_quiz/"
PUBLIC_PATH: '/'
// 必须的项目配置文件(同时标示项目根目录)
FE_CONFIG_FILE: 'fe.json'
// 指定 polyfills 文件名(置于 config 目录中)
// 如需覆盖默认的提供对应文件 (config/polyfills.js), 如需禁用默认的, 提供空文件
POLYFILLS_FILE: 'polyfills.js'
// 指定 setupTests(单测环境配置) 文件名
SETUP_TESTS_FILE: 'setupTests.js'
// 前端构建目录
DIST_DIR: 'build'
// 项目级别扩展配置文件目录(非业务相关)
CONFIG_DIR: 'config'
// 开发模式下会启动一个 mock 服务, 通过 `/_mock` 访问
MOCK_PREFIX: '/_mock'
// mock 存放目录
MOCK_DIR: 'mock'
// 是否启动服务端渲染
ENABLE_SSR: true
// 是否启用 PWA
ENABLE_PWA: false
// 默认在开发环境添加 Access-Control-Allow-Origin CORS header
// 生成环境请在 .env 中配置
CORS_ORIGIN: '*'
CORS_METHODS: 'GET,HEAD,PUT,PATCH,POST,DELETE'
// 是否启用 https
ENABLE_HTTPS: false
// 开发服务器 ip
DEV_SERVER_IP: '0.0.0.0'
// 开发服务器占用端口(生产环境配置以 `.env` 方式提供)
DEV_SERVER_PORT: 3000
// 判定是否在国内, 影响从哪安装 npm 包等策略, 项目初始化时检测一次, 后续手动执行 `fe env` 再次检查并更新
IN_CHINA: true
其他内置插件配置文件:
config/plugins/
webpack.config.js
babel.config.js
# 开发模式下会启动一个 proxy 服务
proxy.config.js
postcss.config.js
...
环境变量
每次上线前保证 .env
, 用以生产环境
# 项目唯一标示
PSM=caijing.xx.xx
TCE 上线策略
生产环境 server 启动时会检查环境变量: IS_HOST_NETWORK
, 命中 "HOST" 模式后, 会优先绑定 PORT0
(由 TCE 注入环境变量), 否则会命中 "bridge" 模式, 读取 .env
文件中的 SERVER_PORT
作为绑定端口, 简单来说: HOST / AUTOHOST 模式下绑定端口由 TCE 自动分配, BRIDGE 模式下绑定端口从 .env > SERVER_PORT 中读取
热更新策略
- 前端 Hot Module Reloading (尽量不刷新页面)
- 后端的更新避免触发前端重新编译
- 支持
fe.json
- 支持
package.json
安装新模块后热更新
fe dev
启动策略:
- 检查是否存在
fe.json
- NO
- 询问是否在当前目录自动创建
fe.json
或 "You'd better usefe init [template name]
initialize a new project"
- 询问是否在当前目录自动创建
- YES - GOTO 2.
检查
CLIENT_MODE
null
- 按纯服务端启动如果为 ""
检查
CLIENT_DIR
, 如果为 "":- 如果存在目录
/pages
//client/pages
更新CLIENT_DIR
, - 如果存在
CLIENT_DIR
对应目录, 检查/pages/*.vue
//client/pages/*.vue
自动判断前端框架, 更新CLIENT_MODE
- 如果存在目录
否则询问 "Do you need vue / react as front-env framework and auto create pages folder?"
询问 "Includes server business in your project?"
- NO => 创建
pages/index.vue
并更新CLIENT_MODE
和CLIENT_DIR
- YES => 创建
client/pages/index.vue
和server/.gitkeep
, 并更新CLIENT_MODE
和CLIENT_DIR
- NO => 创建
检查
ENTRY_FILE
文件是否存在
- 如果不存在询问 "Not found index.js as entry file, auto generate a new file?", 如果选 NO, 配置
ENTRY_FILE
为null
, 否则创建:
module.exports = async (app, options) => {
app.get('/', (req, res) => {
hello: 'world'
})
// app.register(require('./server/modules/user/'), { prefix: '/api/user' })
}
fe start
启动策略
- 检查是否存在
fe.json
抛出错误 "Not specified fe.json" - 检查是否存在 build 产出物
fe init
策略
download => replace => check in china => yarn / npm install
start | dev 区别
dev 模式用于本地开发, 忽略 .env
环境变量, 相关配置汇总在 fe.json
;
包含 mock / 文档等开发服务
start 模式就是线上运行环境, 优先以 .env
方式配置环境变量;
最小化启动, 性能优先
环境判断:
- 按不同执行命令 => 显示注入到 process.env.NODE_ENV = production|development
- 业务中判断环境模式: const dev = process.env.NODE_ENV !== 'production'
目前 require 方式导致 fe 主命令启动太慢
=> actions 按文件分拆出去
css 方案梳理
主要基于 postcss
设计图
尺寸约定为 750 x 1334
配置优先级
project/config/postcss.config.js
> fe default
方案选型
资源引入
- postcss-import (default)
- postcss-url (default)
屏幕适配
- postcss-px-to-viewport
- postcss-viewport-units
- viewport-units-buggyfill
新特性
- postcss-cssnext (default 已包含 autoprefixer)
压缩 / 清理无用
- cssnano
1px
- postcss-write-svg
默认配置:
const createResolver = require('postcss-import-resolver')
module.exports = runtime => ({
plugins: [
require('postcss-import')({
resolve: createResolver({
alias: {
'~': runtime.project.CLIENT_DIR,
'~~': runtime.project.appRoot,
'@': runtime.project.CLIENT_DIR,
'@@': runtime.project.appRoot
},
modules: [
runtime.project.CLIENT_DIR,
runtime.project.appRoot,
'node_modules',
runtime.ENV.internalModulePath
]
})
}),
require('postcss-url')(),
require('postcss-cssnext')()
]
})
自定义配置: config/postcss.config.js
module.exports = (config, runtime) => {
config.plugins.push(
require('autoprefixer')({
/* ...options */
})
)
return config
}
H5 的自定义配置梳理: config/postcss.config.js
module.exports = (config, runtime) => {
config.plugins.concat([
require('postcss-write-svg')({
utf8: false
}),
require('postcss-px-to-viewport')({
viewportWidth: 750,
viewportHeight: 1334,
unitPrecision: 3,
viewportUnit: 'vw',
selectorBlackList: ['.nvw', '.hairlines'],
minPixelValue: 1,
mediaQuery: false
}),
require('postcss-viewport-units')()
])
return config
}
使用指南
- 资源引入
client 目录 => ~
| @
root 目录 => ~~
| @@
@import 'cssrecipes-defaults'; /* == @import "../node_modules/cssrecipes-defaults/index.css"; */
@import 'normalize.css'; /* == @import "../node_modules/normalize.css/normalize.css"; */
@import 'foo.css'; /* relative to css/ according to `from` option above */
@import 'bar.css' (min-width: 25em);
.element {
background: url('images/sprite.png');
}
<img src="~assets/lark.png" alt="">
<img src="/public/lark.png" alt="">
https://github.com/postcss/postcss-url > https://github.com/postcss/postcss-url
https://github.com/postcss/postcss-import
- 使用 cssnext
/* custom properties */
:root {
--fontSize: 1rem;
--mainColor: #12345678;
--highlightColor: hwb(190, 35%, 20%);
}
/* custom properties set & @apply rule */
:root {
--centered: {
display: flex;
align-items: center;
justify-content: center;
}
}
.centered {
@apply --centered;
}
/* custom media queries */
@custom-media --viewport-medium (width <= 50rem);
/* some var() & calc() */
body {
color: var(--mainColor);
font-size: var(--fontSize);
line-height: calc(var(--fontSize) * 1.5);
padding: calc((var(--fontSize) / 2) + 1px);
}
/* custom media query usage */
@media (--viewport-medium) {
body {
font-size: calc(var(--fontSize) * 1.2);
}
}
/* custom selectors */
@custom-selector :--heading h1, h2, h3, h4, h5, h6;
:--heading {
margin-top: 0;
}
/* image-set function */
.foo {
background-image: image-set(url(img/test.png) 1x, url(img/test-2x.png) 2x);
}
/* colors stuff */
a {
color: var(--highlightColor);
transition: color 1s; /* autoprefixed ! */
}
a:hover {
color: gray(255, 50%);
}
a:active {
color: rebeccapurple;
}
a:focus {
background-color: rgb(255 153 0 / 33%);
outline: 3px solid hsl(1turn 60% 50%);
}
a:any-link {
color: color(var(--highlightColor) blackness(+20%));
}
/* font stuff */
h2 {
font-variant-caps: small-caps;
}
table {
font-variant-numeric: lining-nums;
}
/* filters */
.blur {
filter: blur(4px);
}
.sepia {
filter: sepia(0.8);
}
/* overflow-wrap fallback */
body {
overflow-wrap: break-word;
}
/* attribute case insensitive */
[frame='hsides' i] {
border-style: solid none;
}
/* system-ui font-family */
body {
font-family: system-ui;
}
http://cssnext.io/postcss/
- 1px 适配
/*border-image*/
@svg 1px-border {
height: 2px;
@rect {
fill: var(--color, black);
width: 100%;
height: 50%;
}
}
.example {
border: 10px solid transparent;
border-image: svg(1px-border param(--color #00b1ff)) 2 2 stretch;
}
/*background-image*/
@svg square {
@rect {
fill: var(--color, black);
width: 100%;
height: 100%;
}
}
#example {
background: white svg(square param(--color #00b1ff));
}
https://github.com/jonathantneal/postcss-write-svg
- 屏幕适配
基于 vw 方案, android 4.4 下 polyfill 处理:
require('viewport-units-buggyfill').init();
启用 polyfill 后, 可大胆使用 vw
vh
vmin
vmax
单位, 在基于设计稿开发时, 请确保设计图为 750 x 1334(否则自定义 config/postcss.config.js
), 在 css 中直接按设计图尺寸书写 px
单位, 预编译阶段会将 px
单位转换为 vw
, 如果需要忽略转换, 请添加 .nvw
class
https://github.com/rodneyrehm/viewport-units-buggyfill
项目开发贡献指南
开发
fork => MR => review => merge
开发时建议测试驱动, 在
test
目录先写 case, 再写业务代码
$ npm run dev
TIPS 增加
DEBUG=inspect
环境变量(DEBUG=inspect fe dev
) 执行可以用 chrome devtools 来 debug node
- 注意 git commit 的代码格式
# git commit 格式化
$ npm run commit
- 注意代码风格
发版流程
Commit changes
$ git add . $ npm run commit
Bump version via npm
$ npm version patch
Generate
CHANGELOG.md
$ npm run changelog
Commit CHANGELOG
$ npm run commit
Release new version
$ npm run release # scp ...
then, github release.. etc.
DEBUG 方法:
- pgrep -n node | xargs kill -USR1
- node -e "process._debugProcess(pid)"
- INSPECT=--inspect fe d