README
Snowball
snowball
是一个一站式前端开发框架,你可以使用snowball
轻松构建出一套web app/hybrid app
。snowball
内置了view
层,但同时也支持React
。它比React
全家桶轻量又支持更多功能,如下:- 依赖注入:通过注解进行依赖注入。
- 路由系统:拥有多工程跨工程加载、页面切换前进后退动画效果、手势返回、动态管理DOM等功能。
- 状态管理:immutable、响应式,和
redux
不同,snowball
的状态管理更符合OOP
思想。 - 视图:fiber模式渲染,高性能,双向绑定。 支持字符串模版,采用运行时模版编译。
- 路由系统和状态管理都完全适配
React
。 - 业务项目采用分层架构,主要分为
Controller
、ViewModel
、Service
、View
。
路由
该路由方案专为多团队协作开发设计,将多个库整合成一个单页应用,让所有业务都使用相同的跳转动画、手势返回、页面缓存。
发布后到业务库共用一份核心库的js/css/image/iconfont,减少下载资源的大小。
一个核心框架库+多个业务库。业务库之间不依赖,可单独发布。
多工程跨工程加载
- 核心框架
snowball
统一控制路由,需要在snowball
中注册需要加载的业务 - 业务库打包后会生成
asset-manifest.json
文件,snowball
通过路由匹配到业务,并加载manifest中的js和css。 - 业务js加载时调用
registerRoutes({...})
方法注册子路由 snowball
在业务js/css加载完成后,根据业务注册的子路由跳至对应页面。
跳转动画和手势返回
- 应用启动后,可使用
navigation.forward
和navigation.back
方法来控制页面跳转的动画效果。使用navigation.forward
跳转页面后,点击浏览器返回上一页
会自带返回动画。若无需跳转动画可使用navigation.transitionTo
方法。 - 应用默认开启
手势返回
功能,navigation.forward
跳转到新页面之后,左滑页面可返回上一页。 - 页面
render
时会监听dom数量,若dom数量超过指定数量(默认20k),会自动umount老页面的dom。
状态管理
- 内置多种数据类型,如
Model
和Collection
,Collection
类中包含多种常用数组操作方法 immutable
,数据变更后对比非常方便- 使用观察者模式并且提供多种操作函数,轻松监听数据的变化
内置视图
snowball
的视图层采用专有的模版语言、实时模版编译和fiber
模式渲染。视图层接收string
类型模版,组件实例化后,snowball
会对模版进行实时编译,生成虚拟dom
。渲染阶段会对实体dom
的生成和变更进行分片
渲染,避免界面卡顿。
开发
Use Snowball
- run
git clone git@github.com:sorrymeika/snowball.git
- run
cd snowball && npm install
- run
npm run project yourProjectName
to create your own project import { env, Model } from "snowball"
- see
https://github.com/sorrymeika/juicy
orhttps://github.com/sorrymeika/nuclear
to get the full example!
Getting Start
- run
cd yourProject && npm start
to start development server, it'll open the project url in browser automatically! - run
npm run test
to run test cases! - run
npm run build
to build the production bundle. - run
npm run sprity
to build sprity images. - to see the built project, please visit
http://localhost:3000/dist/#/
打包
业务项目打包后会剔除掉`react`,`react-dom`,`polyfill`等框架和框架中的公共组件/公共样式
snowball
会将React
等框架注册到window.Snowball
上- 使用
snowball-loader
, 该loader会将import React from "react"
替换成const React = window.Snowball._React
框架版本管理
snowball
会分大版本(1.x和2.x)和小版本(1.x.x和1.x.x),小版本升级(自动化测试)业务不感知。大版本升级业务需处理。snowball
会尽量保证兼容性。让大版本升级尽量平滑。
项目结构
- 项目结构主要分为
Controller
、ViewModel
、Service
、View
层
snowball-project
├── package.json
├── index.js
├── app
| ├── router.js
│ └── home <!-- 业务文件夹 -->
│ ├── controllers <!-- 页面控制器 -->
│ ├── view-models <!-- 视图状态/业务逻辑 -->
│ ├── services <!-- api调用/业务逻辑 -->
│ ├── scss
│ ├── containers <!-- 页面组件 -->
│ └── components <!-- 组件 -->
├── shared
│ ├── view-models
│ └── services <!-- 公共服务 -->
启动应用
index.js
创建并启动应用
import { createApplication, lazy } from 'snowball/app';
const app = createApplication({
projects: {
"^/(subroute1|subroute2)/": 'http://localhost/subproject/assets-manifest.json'
},
routes: {
'/': require('./app/home/controllers/HomeController'),
// 异步加载
'/item/\\d+:id': import('./app/item/controllers/ItemController'),
// 懒加载
'/type/\\d+:type(?:/\\d+:subType)?': lazy(() => import('./app/type/controllers/TypeController')),
},
configuration: configuration({
modules: {
// 公共模块注册,注册的模块可使用`autowired`自动加载依赖
userService: singleton(UserService)
}
}),
options: {
// 禁用跳转切换动画
disableTransition: true
},
// 对app进行扩展
extend() {
return {
env: {
api: 'https://**'
},
get server() {
if (!this[SymbolServer]) {
this[SymbolServer] = new Server({
baseUrl: this.env.api
})
}
return this[SymbolServer];
}
}
}
}, document.getElementById('root'), callback);
domain/services/UserService.js
// Service 的接口必须定义
interface IUserService {
getUser(): Promise<IUser>;
}
class UserService implements IUserService {
getUser() {
// Service 和 Controller 都可直接使用 app
return this.app.server.post('/getUser');
}
}
app/home/configuration.js
import { configuration } from "snowball/app";
export const HomeConfiguration = configuration({
modules: {
typeViewModel: TypeViewModel
}
});
app/home/controllers/HomeController.js
import { controller, autowired, configuration } from "snowball/app";
// Controller
@controller({
component: Home,
configuration: HomeConfiguration
})
class HomeController {
@autowired
typeViewModel;
onInit() {
}
}
app/shared/view-models/TypeViewModel.js
import { observable } from 'snowball';
import { ViewModel, disposable } from 'snowball/app';
class TypeViewModel extends ViewModel {
@observable types = [];
@observable subTypes = [];
@disposable
_eventEmitter = new EventEmitter();
constructor() {
super();
this._eventEmitter.on((e, typeId) => this.onTypeChange(typeId));
}
// 初始化事件,页面`onInit`完成且`autowired`第一次调用时触发
// `ViewModel`的生命周期事件同`Controller`基本一样
onInit() {
this.types = await this.app.server.post('/getTypes');
}
dispatch = (e) => this._eventEmitter.emit(e);
onTypeChange(typeId) {
this.subTypes = await this.app.server.post('/getSubTypes', typeId);
}
}
app/home/containers/Home.jsx
import { observable } from "snowball";
import { observer } from "snowball/app";
@observer
class Home extends Component {
// 在 React.Component 中 `attributes` 可和 `observer` 配合使用
@observable.string
ohNo = 'oh, no!!';
ohYes = () => {
this.ohNo = 'oh, yeah!!';
}
render() {
return (
<div>
<p onClick={this.ohYes}>{this.ohNo}</p>
<TypeSelect />
</div>
)
}
}
app/home/containers/TypeSelect.jsx
import { inject, loadModule } from "snowball/app";
// 可通过 `inject` 方法将 `controller` 的属性注入到组件的 `props` 中
// `inject`方法内使用`autowired`可根据注册名称自动加载并实例化依赖
// `controller` 中以 `_`和'