atian25 / blog Goto Github PK
View Code? Open in Web Editor NEW天猪部落阁 http://atian25.github.io
天猪部落阁 http://atian25.github.io
搬自我在知乎的问答: https://www.zhihu.com/question/50526101/answer/144952130
谢朴老师邀请。
利益相关: egg 团队成员,jsconf china 2016 的 egg topic 的演讲者。
本文较长,包含以下内容,比较忙的同学可以跳阅:
阿里是业界最早的一批使用 Node.js 来做线上大流量应用的公司,早在 2011 年的就已经开始在生产环境中使用。
众所周知,在阿里的技术栈中, Java 是最最核心的,那 Node.js 扮演怎么样的一个角色呢?
据不完全统计,目前阿里 Node.js 的开发者几百号人,线上的应用也非常之多,仅次于 Java 应用,光对外服务的进程数就超过 1w+。
Node.js 的使用是越来越多了,但整个基建和生态却跟不上:
面对上述的挑战,阿里的 Node.js 先驱者们,做了非常多的探索和努力,如:
也正是因为他们一代代的努力,Node.js 在阿里才能落地生根,才有今天这繁荣。
对这块有兴趣的同学,可以开个问题邀请苏千/死马等人讲讲他们当年在阿里的开荒史。
egg 也是这一时代洪流中的新生一员,它面向的领域是:企业级的 web 基础框架
。
egg 目前是阿里 Node.js 应用的核心基础设施,担心是 KPI 产物的同学,可以放宽心了。
有哪些人参与到 egg 的开发和维护中?
同学们也不用担心 egg 只适合阿里或电商类应用:
Think about future, Design with flexibility, But only implement for production.
一个大规模团队的基础框架,最重要的是需要遵循一定的约束和约定。
没有约定的团队,沟通成本是非常高的,比如有人会按目录分栈而其他人按目录分功能,开发者认知不一致很容易犯错。通过约定可以减少开发人员的学习成本,开发人员不再是『钉子』,可以流动起来。
egg 最核心的东西,其实就是一套约定和规范,这个规范不仅仅是开发目录的约定,还包括了开发过程中,从提案讨论,编码风格,code review 等等方方面面的规范化。
其实大家的基础框架用不用 egg 真的无所谓,最重要是有一套适合团队的约定。
egg 给社区最有价值的回馈是:
当然,我们推荐基于 egg 来定制上层框架:
插件机制是 egg 的一大特色,它不但可以保证框架核心的足够精简、稳定、高效,还可以促进业务逻辑的复用,生态圈的形成。
经典范例如 egg-security,就集合了阿里集团的多年安全经验积累,具体可以看下 egg 文档 - 安全。
同时,差异化定制不意味着没有约定,它只是下层插件实现的差异化,而上层开发体验是一致的:
上面提到的插件机制,很灵活,但是对于企业级应用来说,却还不够。
如果你的团队遇到过:
gulpfile.js
/ webpack.config.js
之类的文件。如果你的团队需要:
为此,egg 为团队架构师和技术负责人提供 框架定制
的能力,框架是一层抽象,可以基于 egg 去封装上层框架,并且 egg 支持多层继承。
+-----------------------------------+--------+
| app1, app2, app3, app4 | |
+-----+--------------+--------------+ |
| | | framework3 | |
+ | framework1 +--------------+ plugin |
| | | framework2 | |
+ +--------------+--------------+ |
| egg | |
+-----------------------------------+--------|
| koa |
+-----------------------------------+--------+
这样,整个团队就可以遵循统一的方案,并且在项目中可以根据业务场景自行使用插件做差异化,当后者验证为最佳实践后,就能下沉到框架中,其他项目仅需简单的升级下框架的版本即可享受到。
^
引入我们的模块。其实不是一个层面的,sails , hapi 这些框架,通过 egg + 对应的插件封装成上层框架,就一样了。
回顾这几年,我个人感觉是非常幸运的,13 年的时候,跟着云龙做前端工程化,15 年则是参与到 egg 的整个开发过程中。
在这旅途中,我熟悉了堪称教科书式的基于 Git Pull Request 的异步硬盘式协作模式;我学习到不少大规模应用中的经验总结;这一段经历让我受益良多,永远无法忘怀,在无数个大半夜,一帮人还在 issue 上讨论的热火朝天。
很幸运自己能参与到阿里的 Node.js 发展中搬一两块砖,这里的基建和生态真的非常完善:
有好奇心的同学,在杭州的,不妨亲自进去看看,不信,你看叔叔 @徐飞 。
而在广州的同学,也可以过来 UC 这边体验下,我们的内部交流通道非常顺畅。https://www.zhihu.com/question/55271199/answer/143741434 。
最后,回过头来看,我个人是挺感慨的,这么短时间,完全没有政治命令,大家主动拥抱共建,对于阿里这样如此大规模的多部门的公司,真可谓奇迹。
国内的开发者真的不用妄自菲薄,这几年,越来越多的国内框架如 ant design,element,weex,macaca 等等,正走出国门,拥抱世界。
以上
--
1)业务描述:
打算把微信服务号的部分页面用SPA模式重构下;
2)目前问题:
A:因为只是要把部分页面重构为SPA,所以就涉及到这个SPA的root或者说base href(应该是锚点)怎么定?
B:其实重构的主要是把原来的a标签中的链接,换成ui-sref的形式;原来我的a标签是一个从后台传过来的具体的URL(http://rostname/wx/consultant/home/p/QzkwODVGWWlTRms9/oeOGnuEEc4g5bj2wMaSRJxjx9b_k/939b888800bf4797ba5583dd7aba2463bffcd4c8/0/),
那么现在我该怎么设置这个ui-sref?
怎么设置$stateProvider.state中的各个值;url,templateUrl等。
TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.
TypeScript 的静态类型检查,智能提示,IDE 友好性等特性,对于大规模企业级应用,是非常的有价值的。详见:TypeScript体系调研报告 。
然而,此前使用 TypeScript 开发 Egg ,会遇到一些影响 开发者体验 问题:
config.{env}.js
里面修改插件提供的配置时,能校验并智能提示?tsc -w
独立进程来构建代码,带来临时文件位置纠结以及 npm scripts
复杂化。本文主要阐述:
具体的折腾过程参见:[RFC] TypeScript tool support
通过骨架快速初始化:
$ npx egg-init --type=ts showcase
$ cd showcase && npm i
$ npm run dev
上述骨架会生成一个极简版的示例,更完整的示例参见:eggjs/examples/hackernews-async-ts
一些约束:
index.d.ts
文件方便开发者使用。整体目录结构上跟 Egg 普通项目没啥区别:
typescript
代码风格,后缀名为 ts
typings
目录用于放置 d.ts
文件(大部分会自动生成)showcase
├── app
│ ├── controller
│ │ └── home.ts
│ ├── service
│ │ └── news.ts
│ └── router.ts
├── config
│ ├── config.default.ts
│ ├── config.local.ts
│ ├── config.prod.ts
│ └── plugin.ts
├── test
│ └── **/*.test.ts
├── typings
│ └── **/*.d.ts
├── README.md
├── package.json
├── tsconfig.json
└── tslint.json
// app/controller/home.ts
import { Controller } from 'egg';
export default class HomeController extends Controller {
public async index() {
const { ctx, service } = this;
const page = ctx.query.page;
const result = await service.news.list(page);
await ctx.render('home.tpl', result);
}
}
// app/router.ts
import { Application } from 'egg';
export default (app: Application) => {
const { router, controller } = app;
router.get('/', controller.home.index);
};
// app/service/news.ts
import { Service } from 'egg';
export default class NewsService extends Service {
public async list(page?: number): Promise<NewsItem[]> {
return [];
}
}
export interface NewsItem {
id: number;
title: string;
}
// app/middleware/robot.ts
import { Context } from 'egg';
export default function robotMiddleware() {
return async (ctx: Context, next: any) => {
await next();
};
}
因为 Middleware 定义是支持入参的,第一个参数为同名的 Config,如有需求,可以用完整版:
// app/middleware/news.ts
import { Context, Application } from 'egg';
import { BizConfig } from '../../config/config.default';
// 注意,这里必须要用 ['news'] 而不能用 .news,因为 BizConfig 是 type,不是实例
export default function newsMiddleware(options: BizConfig['news'], app: Application) {
return async (ctx: Context, next: () => Promise<any>) => {
console.info(options.serverUrl);
await next();
};
}
// app/extend/context.ts
import { Context } from 'egg';
export default {
isAjax(this: Context) {
return this.get('X-Requested-With') === 'XMLHttpRequest';
},
}
// app.ts
export default app => {
app.beforeStart(async () => {
await Promise.resolve('egg + ts');
});
};
Config
这块稍微有点复杂,因为要支持:
config.view = {}
的写法,也应该支持提示。config.{env}.ts
里可以用到 config.default.ts
自定义配置的提示。// app/config/config.default.ts
import { EggAppInfo, EggAppConfig, PowerPartial } from 'egg';
// 提供给 config.{env}.ts 使用
export type DefaultConfig = PowerPartial<EggAppConfig & BizConfig>;
// 应用本身的配置 Scheme
export interface BizConfig {
news: {
pageSize: number;
serverUrl: string;
};
}
export default (appInfo: EggAppInfo) => {
const config = {} as PowerPartial<EggAppConfig> & BizConfig;
// 覆盖框架,插件的配置
config.keys = appInfo.name + '123456';
config.view = {
defaultViewEngine: 'nunjucks',
mapping: {
'.tpl': 'nunjucks',
},
};
// 应用本身的配置
config.news = {
pageSize: 30,
serverUrl: 'https://hacker-news.firebaseio.com/v0',
};
return config;
};
简单版:
// app/config/config.local.ts
import { DefaultConfig } from './config.default';
export default () => {
const config: DefaultConfig = {};
config.news = {
pageSize: 20,
};
return config;
};
备注:
Conditional Types
是我们能完美解决 Config 提示的关键。PowerPartial
实现。// {egg}/index.d.ts
type PowerPartial<T> = {
[U in keyof T]?: T[U] extends {}
? PowerPartial<T[U]>
: T[U]
};
// config/plugin.ts
import { EggPlugin } from 'egg';
const plugin: EggPlugin = {
static: true,
nunjucks: {
enable: true,
package: 'egg-view-nunjucks',
},
};
export default plugin;
该目录为 TS 的规范,在里面的 \*\*/\*.d.ts
文件将被自动识别。
typings/index.d.ts
中。typings/{app,config}/\*\*.d.ts
,请勿自行修改,避免被覆盖。(见下文)现在 Egg 自带的 d.ts 还有不少可以优化的空间,遇到的同学欢迎提 issue 或 PR。
egg-bin
已经内建了 ts-node ,egg loader
在开发期会自动加载 \*.ts
并内存编译。
目前已支持 dev
/ debug
/ test
/ cov
。
开发者仅需简单配置下 package.json
:
{
"name": "showcase",
"egg": {
"typescript": true
}
}
由于 Egg 的自动加载机制,导致 TS 无法静态分析依赖,关联提示。
幸亏 TS 黑魔法比较多,我们可以通过 TS 的 Declaration Merging 编写 d.ts
来辅助。
譬如 app/service/news.ts
会自动挂载为 ctx.service.news
,通过如下写法即识别到:
// typings/app/service/index.d.ts
import News from '../../../app/service/News';
declare module 'egg' {
interface IService {
news: News;
}
}
手动写这些文件,未免有点繁琐,因此我们提供了 egg-ts-helper 工具来自动分析源码生成对应的 d.ts
文件。
只需配置下 package.json
:
{
"devDependencies": {
"egg-ts-helper": "^1"
},
"scripts": {
"dev": "egg-bin dev -r egg-ts-helper/register",
"test-local": "egg-bin test -r egg-ts-helper/register",
"clean": "ets clean"
}
}
开发期将自动生成对应的 d.ts
到 typings/{app,config}/
下,请勿自行修改,避免被覆盖。
后续该工具也会考虑支持 js 版 egg 应用的分析,可以一定程度上提升 js 开发体验。
单元测试当然少不了:
// test/app/service/news.test.ts
import * as assert from 'assert';
import { Context } from 'egg';
import { app } from 'egg-mock/bootstrap';
describe('test/app/service/news.test.js', () => {
let ctx: Context;
before(async () => {
ctx = app.mockContext();
});
it('list()', async () => {
const list = await ctx.service.news.list();
assert(list.length === 30);
});
});
运行命令也跟之前一样,并内置了 错误堆栈和覆盖率
的支持:
{
"name": "showcase",
"scripts": {
"test": "npm run lint -- --fix && npm run test-local",
"test-local": "egg-bin test -r egg-ts-helper/register",
"cov": "egg-bin cov -r egg-ts-helper/register",
"lint": "tslint ."
}
}
断点调试跟之前也没啥区别,会自动通过 sourcemap
断点到正确的位置。
{
"name": "showcase",
"scripts": {
"debug": "egg-bin debug -r egg-ts-helper/register",
"debug-test": "npm run test-local -- --inspect"
}
}
ci
上构建并打包。配置 package.json
:
{
"egg": {
"typescript": true
},
"scripts": {
"start": "egg-scripts start --title=egg-server-showcase",
"stop": "egg-scripts stop --title=egg-server-showcase",
"tsc": "ets && tsc -p tsconfig.json",
"ci": "npm run lint && npm run cov && npm run tsc",
"clean": "ets clean"
}
}
对应的 tsconfig.json
:
{
"compileOnSave": true,
"compilerOptions": {
"target": "es2017",
"module": "commonjs",
"strict": true,
"noImplicitAny": false,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"charset": "utf8",
"allowJs": false,
"pretty": true,
"noEmitOnError": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"strictPropertyInitialization": false,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"inlineSourceMap": true,
"importHelpers": true
},
"exclude": [
"app/public",
"app/web",
"app/views"
]
}
注意:
egg-ts-helper
会自动调用清除同名的 js
文件,也可 npm run clean
手动清除。线上服务的代码是经过编译后的 js,而我们期望看到的错误堆栈是指向 TS 源码。
因此:
inlineSourceMap: true
在 js 底部插入 sourcemap 信息。egg-scripts
内建了处理,会自动纠正为正确的错误堆栈,应用开发者无需担心。具体内幕参见:
指导原则:
index.d.ts
。egg
这个 module,不要用上层框架。可以参考 egg-ts-helper
自动生成的格式
// {plugin_root}/index.d.ts
import News from '../../../app/service/News';
declare module 'egg' {
// 扩展 service
interface IService {
news: News;
}
// 扩展 app
interface Application {
}
// 扩展 context
interface Context {
}
// 扩展你的配置
interface EggAppConfig {
}
// 扩展自定义环境
type EggEnvType = 'local' | 'unittest' | 'prod' | 'sit';
}
定义:
// {framework_root}/index.d.ts
import * as Egg from 'egg';
// 将该上层框架用到的插件 import 进来
import 'my-plugin';
declare module 'egg' {
// 跟插件一样拓展 egg ...
}
// 将 Egg 整个 export 出去
export = Egg;
开发者使用的时候,可以直接 import 你的框架:
// app/service/news.ts
// 开发者引入你的框架,也可以使用到提示到所有 Egg 的提示
import { Service } from 'duck-egg';
export default class NewsService extends Service {
public async list(page?: number): Promise<NewsItem[]> {
return [];
}
}
最低要求 2.8+ 版本,依赖于新支持的 Conditional Types ,黑魔法中的黑魔法。
$ npm i typescript tslib --save-dev
$ npx tsc -v
Version 2.8.1
由于 VSCode 自带的 TypeScript 版本还未更新,需手动切换:
F1 -> TypeScript: Select TypeScript Version -> Use Workspace Version 2.8.1
之前为了不显示编译后的 js 文件,会配置 .vscode/settings.json
,但由于我们开发期已经不再构建 js,且 js 和 ts 同时存在时会优先加载 js,因为 建议「不要」配置此项。
// .vscode/settings.json
{
"files.exclude": {
"**/*.map": true,
// 光注释掉 when 这行无效,需全部干掉
// "**/*.js": {
// "when": "$(basename).ts"
// }
},
"typescript.tsdk": "node_modules/typescript/lib"
}
完整的配置如下:
{
"name": "hackernews-async-ts",
"version": "1.0.0",
"description": "hackernews showcase using typescript && egg",
"private": true,
"egg": {
"typescript": true
},
"scripts": {
"start": "egg-scripts start --title=egg-server-showcase",
"stop": "egg-scripts stop --title=egg-server-showcase",
"dev": "egg-bin dev -r egg-ts-helper/register",
"debug": "egg-bin debug -r egg-ts-helper/register",
"test-local": "egg-bin test -r egg-ts-helper/register",
"test": "npm run lint -- --fix && npm run test-local",
"cov": "egg-bin cov -r egg-ts-helper/register",
"tsc": "ets && tsc -p tsconfig.json",
"ci": "npm run lint && npm run tsc && egg-bin cov --no-ts",
"autod": "autod",
"lint": "tslint .",
"clean": "ets clean"
},
"dependencies": {
"egg": "^2.6.0",
"egg-scripts": "^2.6.0"
},
"devDependencies": {
"@types/mocha": "^2.2.40",
"@types/node": "^7.0.12",
"@types/supertest": "^2.0.0",
"autod": "^3.0.1",
"autod-egg": "^1.1.0",
"egg-bin": "^4.6.3",
"egg-mock": "^3.16.0",
"egg-ts-helper": "^1.5.0",
"tslib": "^1.9.0",
"tslint": "^4.0.0",
"typescript": "^2.8.1"
},
"engines": {
"node": ">=8.9.0"
}
}
通过 TS 的装饰器,可以实现 依赖注入
/ 参数校验
/ 日志前置处理
等。
import { Controller } from 'egg';
export default class NewsController extends Controller {
@GET('/news/:id')
public async detail() {
const { ctx, service } = this;
const id = ctx.params.id;
const result = await service.news.get(id);
await ctx.render('detail.tpl', result);
}
}
目前装饰器属于锦上添花,因为暂不做约定。
交给开发者自行实践,期望能看到社区优秀实践反馈,也可以参考下:egg-di 。
友情提示:要适度,不要滥用。
未来可能还会封装一个上层框架 tegg,具体 RFC 还没出,还在孕育中,敬请期待。
名字典故:typescript + egg
-> ts-egg
-> tea egg
-> 茶叶蛋
早在一年多前,阿里内部就有很多 BU 在实践 TS + Egg 了。
随着 TS 的完善,终于能完美解决我们的开发者体验问题,也因此才有了本文。
本来以为只需要 2 个 PR 搞定的,结果变为 Hail Hydra,好长的 List:[RFC] TypeScript tool support 。
终于完成了 Egg 2.0 发布时的一大承诺,希望能通过这套最佳实践规范,提升社区开发者的研发体验。
link过来之后,项目启动报错,但是我发布了,在下载,却是没错的,是发布到了一个私有gitlab
兵马未动粮草先行,我们的第一步工作就是分析需求:我们需要怎么样的一款工具?
根据我们团队的实践,还有在社区的调研,该前端集成解决方案
应该具备:
NodeJS
一样写JS,很舒服。ngRoute
和ui-router
。organize by feature
,一个组件的JS、CSS、模板最好都在一个目录维护,维护方便。Angular
进行太耦合的hack,要原汁原味。ci平台
集成, 支持karma
和protractor
。livereload
)一个都不能少。NodeJS
作为服务器,本地要能预览,最好再能抓取线上数据,方便调试。bower
组件仓库的安装啦。yeoman
用过吧? 对, 脚手架
功能也不能少了。CoffeeScript
,Sass
之类的有意思,也给我来一个。回头看看这个列表,我怎么有点腿发软了。。。
好吧,在瓶神的鼓励下, 我们试试看吧,先把上面的需求整理下:
NodeJS
一样编码organize by feature
ngRoute
和ui-router
Angular
NodeJS
后端,基本部署规范应该参考express
项目部署CommonJS
规范请求合并
按需加载
localstorage
等方式缓存资源Angular
的依赖注入声明。ci平台``集成, 支持
karma单元测试,
protractor`端到端测试。yeoman
的脚手架, 生成项目框架,包括Controller
等CoffeeScript
,Sass
的编译bower
模块安装和使用一开始的时候,我总期望在scrat
的基础上去修订,可是浪费很多时间后才发现, 即使团队的规范看起来差异不大,但还是有很多不同的应用场景,不能一刀切。因为,团队自身的前端解决方案是需要量身定制的。
本文实现的ngfis
是一个基于fis
之上的angular
方向的解决方案, 只包含了最核心的一些需求,因此我们可以在这之上再去架构自己的方案, 如我们团队的larva
就是ngfis
+hybrid app
+uae
的结合体。
fix #24
上一次的折腾参见: #14
在刚发布的 vscode 1.6 release note 中, 可以解决该问题, 具体步骤:
.vscode/launch.json
(如果之前有, 先删除)configurations[0]
节点, 增加以下信息 "runtimeArgs": [
"--nolazy", "--debug"
],
"port": 5859,
很多公司的服务器环境没有做隔离,就是全局安装一个 Node.js Runtime,一般很少升级。
nvs
/ nvm
等可以用来切换版本,但无法同时共存。而且一般服务器不允许你随意升级。
因此很多同学都会很痛苦:「都 8012 年了,还是 Node 4.x 甚至 0.x 简直想死!」
时至今天,最好的办法就是 Docker。但奈何很多小公司还处于水深火热之中。
本文将介绍下我们很早前就使用的一套方案,可以完美解决非 Docker 情况下 Node 多版本共存问题。
首先要介绍下 npm scripts ,简单的说,就是可以在 package.json
里面定义脚本。
{
"name": "egg-showcase",
"scripts": {
"start": "node index.js",
"debug": "egg-bin debug --inspect-brk"
},
"devDependencies": {
"egg-bin": "^4.7.0",
}
}
如上,定义脚本后即可执行:
$ npm start
$ # 执行并传参,需要多一个 --
$ npm run debug -- --inspect-brk
同学们可能会比较好奇,上面的 egg-bin
是哪来的?这是 npm 的一个很重要的特性:
通过 npm run your-scripts
启动的脚本,会默认把 node_modules/.bin
加到 PATH
环境变量中。
由于,我们的依赖 egg-bin
有定义了 bin 字段 ,因此安装后会软链到 node_modules/.bin
,从而能被寻址并执行。
聪明的同学很快就会想到,如果 Node 的 Runtime 也在这个目录下,会怎么样呢?
因此,问题就可以转换为:如何把 Node Runtime 打包到项目中?
答案就是在构建期打包进去,参见我们的上一篇文章 『科普文:为什么不能在服务器上 npm install ?』
🙋🙋🙋 老师!打包能否更简单一点呢?能否就像 dependencies 那样定义一下就好了呢?
没问题!方案有几种:
定义:
{
"name": "egg-showcase",
"scripts": {
"start": "node index.js",
"debug": "egg-bin debug --inspect-brk",
"echo": "node -p process.versions"
},
"devDependencies": {
"egg-bin": "^4.7.0",
},
"engines": {
"install-node": "^8.0.0",
// AliNode 的话用这个
"install-alinode": "^3.8.0"
}
}
安装:
$ npm install nodeinstall -g
$ cd path/to/your/project
$ nodeinstall
$ # 验证
$ npm run echo
node: '8.10.0'
就这么简单,从此服务器上只要有一个任意版本的 npm 即可,各项目都可以用自己的 Node 版本,不会互相影响。
如果你是阿里员工,这一步都可以省了,因为
tnpm
内置就支持这个配置,无需单独安装nodeinstall
。
但需要注意的是:
./node_modules/.bin/node -p process.versions
就知道了。另外,除了我们写的这个工具外,在 Node 8 里面,官方终于也支持了类似的功能。
不过它是在 postinstall 里面安装的,没有优先安装 Node ,会导致 Native Addons 依赖有问题(应该先安装 Node,再用这个版本去安装其他依赖)。现在不知道改了没。
挺期待官方完善这个特性的,这样我们的又一个轮子可以完成历史使命,退休了。
那如果我有服务器权限,我可以随意升级 Node 版本,就不需要了吧?
我们 Node 工程化的理念是一个包可以快速部署,这样就不依赖 PE 来配置环境了。
如果应用多了,服务器就得根据应用名配置 Node 版本,这样应用升级就得分开两步操作。
当然,最优的解决方案,还是 Docker 化隔离,都是一样的思路,一份产物部署的理念。
如果你的 node 没有写单元测试,那可以跳过本文了。
在我们日常的单元测试中,常用的断言库有:
user.should.have.property('name', 'tz');
user.enabled.should.ok;
expect(5).to.be.a('number');
expect(window).not.to.be.an(Image);
存在什么问题呢?
user.enabled.should.ok;
这句到底有没有执行? 还是只是取值?log
再跑一次,如果在 ci 上看日志就懵逼了。require('should');
const expect = require('expect.js');
const assert = require('assert');
describe('test/showcase.test.js', () => {
const arr = [ 1, 2, 3 ];
it('should.js', () => {
arr[1].should.eql(10);
});
it('expect.js', () => {
expect(arr[1]).to.eql(10);
});
it('assert', () => {
// 用原生的话, 得到的提示更是一脸懵逼
assert(arr[1] === 10);
});
});
// output:
1) test/showcase.test.js should.js:
AssertionError: expected 2 to equal 10
...
2) test/showcase.test.js expect.js:
Error: expected 2 to sort of equal 10
...
3) test/showcase.test.js assert:
AssertionError: false == true
...
在 egg 的开发中, 我们发现了不一样的它:
Power Assert in JavaScript.
Provides descriptive assertion messages through standard assert interface.
No API is the best API.
简单的说,它的优点是:
assert
即可。const assert = require('power-assert');
describe('test/showcase.test.js', () => {
const arr = [ 1, 2, 3 ];
it('power-assert', () => {
assert(arr[1] === 10);
});
});
// output:
4) test/showcase.test.js power-assert:
AssertionError: # test/showcase.test.js:6
assert(arr[1] === 10)
| | |
| 2 false
[1,2,3]
[number] 10
=> 10
[number] arr[1]
=> 2
在线尝试:https://azu.github.io/power-assert-demo/
安装依赖:
$ npm i mocha power-assert intelli-espower-loader --save-dev
配置 npm scripts
:
{
"scripts": {
"test": "mocha -r intelli-espower-loader test/**/*.test.js",
},
"devDependencies": {
"power-assert": "^1.4.2",
"intelli-espower-loader": "^1.0.1",
}
}
编写测试:
// 简单的 require, 使用者无感知
// 下面的代码没写错, 无需 `require('power-assert')`, loader 会自动替换
const assert = require('assert');
describe('test/showcase.test.js', () => {
const arr = [ 1, 2, 3 ];
it('power-assert', () => {
// 完全兼容 node 原生的 assert API, 直接自由使用
assert(arr[1] === 10);
});
});
执行测试:
$ npm test
intelli-espower-loader
,主要是把代码转译,参见作者的 slide .require('assert')
都不需要改.assert
了, 否则会遇到如下错误require('assert')
改为 require('power-assert')
即可作为一名前端,你是否:
现在就加入我们:
我们希望你是:
我们是阿里游戏大前端团队,Leader 是 Egg 的核心开发者 - 天猪,坐标广州。
负责阿里游戏各业务线的研发与支撑,既有面向千万用户的游戏平台,也有支持业务快速发展的中后台产品,当然还有正处风口的小游戏。
想跟一帮志同道合的人,站在巨人的肩膀上,去探索和引领广州最前沿的前端技术,做一些有意思的事吗?
简历投递: liuyong.ly3#alibaba-inc.com
VSCode 早期版本,对 Node Cluster 的调试支持一直不是很友好,譬如:
早在 2016 年时就开始的折腾:#14 和 #15 ,没有太好的办法。
然后 @okoala 写了 egg-development-proxyworker
主要思路是在 agent 里面启动一个 socket proxy 来动态代理 worker 的调试端口。
很巧妙的解决了自动重启后调试端口变化问题
,但缺点是要开发者手动安装插件,并配置 vscode。
此时只能说达到可用的阶段。
接着,我写了 egg-bin debug
把 proxy 功能内置了,实现原理参见当时的 RFC 提案
并且提供了 vscode-eggjs 扩展来方便配置。
解决了:
对于一般应用开发者基本上已经非常易用了,但还存在以下问题:
launch.json
对同时 attach 多个的支持不是很友好,虽然有 compounds
。brk
的话要手动 attach 3 次,非常麻烦。而今天,[email protected] 正式支持了 Automatically attach debugger to Node.js subprocesses
因此我们之前的做法可以大幅简化了,没解决的问题也基本解决了,可以称为 完美版
了。
文档已经更新:使用 VSCode 进行调试
安装 vscode-eggjs,并初始化调试配置(如果之前有则需删除 launch.json 文件)
然后简单的一个 F5
搞定~
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Egg",
"type": "node",
"request": "launch",
"cwd": "${workspaceRoot}",
"runtimeExecutable": "npm",
"windows": { "runtimeExecutable": "npm.cmd" },
// 启动我们的 egg-bin debug 并默认是 brk
"runtimeArgs": [ "run", "debug", "--", "--inspect-brk" ],
// 日志输出到 Terminal,否则启动期的日志看不到
"console": "integratedTerminal",
"protocol": "auto",
// 进程重启后自动 attach
"restart": true,
// 因为无需再 proxy,故改回原来的 9229 端口
"port": 9229,
// 自动 attach 子进程
"autoAttachChildProcesses": true
}
]
}
vscode 扩展生成的配置里面,还支持了单元测试的断点,配置如下:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Egg Test",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"test-local",
"--",
"--inspect-brk"
],
"protocol": "auto",
"port": 9229,
"autoAttachChildProcesses": true,
"disableOptimisticBPs": false,
}
]
}
VSCode 1.22 支持了 Automatically Attach to Node.js processes
,也就是如果你开启了这个的话,无需配置什么 launch.json
,直接在 terminal 执行 npm run debug --inspect-brk
就会自动 attach 了。
Egg 的调试,跟 Node 没啥区别,因此一定要了解 Node 的基础调试知识。
其中,--inspect-brk
是指在进程第一行就暂停,等待 attach,因此:
上面这几个 attach,由于上面我们提到的 VSCode 的支持,只需要开启配置,即可无感知的一键 attach。
虽然如此,但作为开发者,大家还是需要理解 Node 的调试原理。
有些同学反馈 vscode 无法 debug egg 应用, 于是分析了下
在 vscode 的 debug 界面 launch 时:
node --debug-brk=21904 --nolazy index.js
Debugger listening on [::]:21904
2016-09-29 08:15:12,277 INFO 52531 [master] =================== egg start =====================
2016-09-29 08:15:12,278 INFO 52531 [master] egg version 0.1.3
2016-09-29 08:15:12,279 INFO 52531 [master] start with options: {"customEgg":"/Users/tz/Workspaces/eggjs/test/showcase/node_modules/.0.1.3@egg","baseDir":"/Users/tz/Workspaces/eggjs/test/showcase","port":7002,"workers":1,"plugins":null,"https":false,"key":"","cert":""}
2016-09-29 08:15:12,282 INFO 52531 [master] Agent Worker:52532 start with ["{\"customEgg\":\"/Users/tz/Workspaces/eggjs/test/showcase/node_modules/.0.1.3@egg\",\"baseDir\":\"/Users/tz/Workspaces/eggjs/test/showcase\",\"port\":7002,\"workers\":1,\"plugins\":null,\"https\":false,\"key\":\"\",\"cert\":\"\"}"]
Debugger listening on [::]:5856
而正常的 egg 控制台启动日志, 除了上面那段外, 还会有以下:
2016-09-29 08:19:59,184 INFO 52849 [egg:watcher:agent] watcher start success
2016-09-29 08:19:59,219 INFO 52848 [master] Agent Worker started (404ms)
2016-09-29 08:19:59,223 INFO 52848 [master] App Worker#1:52850 start, state: none, current workers: ["1"]
Debugger listening on [::]:19883
2016-09-29 08:19:59,897 WARN 52848 [master] App Worker#1:52850 started at 7002, remain 0 (678ms)
2016-09-29 08:19:59,897 INFO 52848 [master] egg started on http://127.0.0.1:7002 (1082ms)
--debug-brk
把 master 进程挂住, 所以 agent 和 worker 都没起来startCluster
之前加入以下代码可以临时解决process.execArgv[0] = process.execArgv[0].replace('-brk', '');
npm run dev
ps -ef|grep customEgg
后发现 egg-bin
并没有传递 --debug
给 workernode --debug index.js
, 晚点再修复下 egg-bin
egg-development
监控到文件改变时, 会自动重启 workerlaunch.json
的端口, 然后重新 attach思路:
目前进展:
vscode.commands.executeCommand('vscode.startDebug', {})
这条指令, 可以成功的自动 attach遇到问题:
vscode.startDebug
这类 API 的文档, 根本不知道去哪里找... google 不到, 源码翻得很累本 issue 用于汇总一些在社区常见的的沟通问题,方便直接丢链接砸脸。
https://coolshell.cn/articles/10804.html
1)有人想解决问题 X
2)他觉得 Y 可能是解决 X 问题的方法
3)但是他不知道 Y 应该怎么做
4)于是他去问别人 Y 应该怎么做?
简而言之,没有去问怎么解决问题 X,而是去问解决方案 Y 应该怎么去实现和操作。于是乎:
1)热心的人们帮助并告诉这个人 Y 应该怎么搞,但是大家都觉得 Y 这个方案有点怪异。
2)在经过大量地讨论和浪费了大量的时间后,热心的人终于明白了原始的问题 X 是怎么一回事。
3)于是大家都发现,Y 根本就不是用来解决X的合适的方案。
X-Y Problem 最大的严重的问题就是:在一个根本错误的方向上浪费他人大量的时间和精力!
https://zhuanlan.zhihu.com/p/25795393
『欲知后事如何,且听下回分解』
总是留个后手,不要一次性把话说完,让你的问题充满神秘感,充分调动起读者的好奇心。
正确示范:
你:我的代码出错了,不知道该怎么办?
你:我这里有一个问题,有人能帮我解决么?
你:在吗?
『接连便是难懂的话,什么"KPI","绩效","弃坑"之类,引得众人都哄笑起来』
把你的问题拔高一个层次,站在道德高地进行指责,一旦讨论涉及到政治,他们便百口莫辩。
正确示范:
原来大公司团队也就这样啊,都不好好测试的么?就这玩意还好意思拿出来,
就是个 KPI 产物,晋升完就不管了。
错误示范:
这个项目虽然是大公司的产品,在以下方面比起竞品还有劣势,个人不建议使用。
『你们把我项目搞挂了,狗屎!』
开源项目导致了你的项目出现 BUG,导致了你周六晚上还要加班,导致了男/女友抱怨你不理他/她,这必须要有人负责。你的工作和生活被他们毁了,也别让他们好过。
正确示范:
这个项目烂透了,用起来全是坑,文档也太简略了,这样做开源真是呵呵了
错误示范:
这个项目有很多细节问题,文档也不完善,请问有改进的计划么?
我收集了以下具体问题,希望持续完善。
https://juejin.im/post/5aa882eaf265da23923607bd
通常用来指 某些简单到所有人都可以发表观点,并且几乎所有人都会去发表观点的问题。
用来代表一些无关紧要但会引起大量争论的问题,如我们软件开发行业:
类似的问题还有很多,并且通常争论不休。与其参与到其中图个嘴上痛快,不如踏踏实实地关注眼前的问题。愿你能够辨别自行车棚问题、远离自行车棚会议,充实地过好每分每秒。
node 应用开发中,我们不可避免的需要使用或拆分为 npm 模块,经常遇到的一个问题是:
新开发或修改的 npm 模块,如何在项目中试验?
新同学一般会有以下几种方式:
为了方便示范,我们假设项目是 my-project
, 需要用到一个独立的 my-utils
模块
$ cd path/to/my-project
$ npm install path/to/my-utils
$ cd path/to/my-project/node_modules
$ ln -s path/to/my-utils my-utils
但其实 npm 本身已经对此类情况提供了专门的 npm link
指令。
相关文档: https://docs.npmjs.com/cli/link
下面我们简单介绍下用法:
$ cd path/to/my-project
$ npm link path/to/my-utils
简单的替换一个单词,就搞定了,cool~
如果这两种的目录不在一起,那还有一种方法:
$ # 先去到模块目录,把它 link 到全局
$ cd path/to/my-utils
$ npm link
$
$ # 再去项目目录通过包名来 link
$ cd path/to/my-project
$ npm link my-utils
该指令还可以用来调试 node cli 模块,譬如需要本地调试我们的 egg-init,可以这样:
$ cd path/to/egg-init
$ npm link
$ # 此时全局的 egg-init 指令就已经指向你的本地开发目录了
$ egg-init # 即可
想去掉 link 也很简单:
$ npm unlink my-utils
karma
的inline
不知不觉,我们团队实践
AngularJS
已经一年多了, 一直使用grunt
来作为工作流,但它存在不少问题。最近在反复拜读 @fouber 的 「前端工程系列文章」, 发现神器 FIS,并希望借此机会,量身打造了一款Angular前端工具。
可惜FIS本身的文档还不够完善,内部实现有很多黑盒,于是本文作为踩坑实践后的总结,希望能对后来者有所帮助。
本文假定读者是有前端开发经验的工程师,对
angular
有一定的实践经验,且仔细阅读过「前端工程系列文章」。
FIS
是一个构建系统内核,很好的抽象了前端集成解决方案所需的通用工具需求。它补全了前端语言缺少的三种语言能力:
- **资源定位的能力:**使用开发路径进行资源定位,项目发布后转换成部署路径
- **依赖声明的能力:**声明一个资源依赖另一个资源的能力
- **资源嵌入的能力:**把一个资源的编译内容嵌入到另一个文件中
每个团队都有自己的应用场景,不能一刀切,所以我们需要量身定制。
FIS
本身并不与任何后端语言绑定。只有基于FIS实现的具体解决方案才会有具体的规范和技术选型。
因此FIS
是一个很好的起点,目前已有的定制方案:
解决方案 | 描述 |
---|---|
fis-plus | 适合百度的业务场景,后台为PHP |
scrat | 适合UC大导航的业务场景,后台为NodeJS |
spmx | 简单实现对seajs的场景 |
jello | Java + Velocity |
ng-fis | 针对Angular 的场景,即本文实现的方案。(而我们团队的项目则是基于 ng-fis 之上进行再次封装) |
如图,截止到 2017 年 3 月, Node.js 模块的数量已经超过 43万,这么多模块难免会良莠不齐,
那我们如何高效的从中找到高质量的模块呢?请听下文分解。
npms.io - which stands for npm search - was built to empower the JavaScript community by providing a better and open source search for node packages.
最早了解到该站点,是听小右在微博说的。
选模块就像选对象,下面我们来看看非诚勿扰选优,呸呸呸,是来看官方的评估标准:
第一个维度是「质量」,毕竟这是一个看脸的时代,第一印象很重要。
相关的因子就在源码中,故计算起来相对容易些,如:
deprecated
?TODO/FIXME
多不多?第二个维度是「维护状况」,考察的是一个模块的家况,如果我们希望跟它进一步交往,当然需要了解到这个模块的活跃程度,健康度,是否被遗弃了?
第三个维度是「知名度」
最后一个维度是「个人魅力」
cnpm compare
。rn模块 npm link 之后 项目重新刷新会报错。unable to resolve module 'ecool/react-native-dllog' from my Project.这是为啥?
2018 年更新,此时我会倾向于在 语雀 https://www.yuque.com/egg 来写。
GitHub 的目录导航体验不是很好,知乎的编辑器体验太差,语雀则让我重新找到编写的兴趣。
绕了一圈圈,最终做出了选择
hexo
多说
,但是码农不喜欢。issue
的方式, 也即此处, 虽然几乎没有流量导,不过无所谓, 专心自我总结。segementfault
的印象好了不少, 技术领域,还有人气都很不错。不过还是需要观察观察。如果segementfault
能提供一种方式,博主在issue发表, segementfault
自动 git hook 同步, 导点流量啥的, 大家互利。以下是当时的思考:
选择困难症又开始了。。。
再读读浩哥的: 程序算法与人生选择
1.知乎专栏
2.简书
3.SegmentFault
4.github issue
5.GitHub page+ hexo
6.gitpress
7.ghost
我们隶属于 蚂蚁集团体验技术部,负责 前端基础技术,致力于为蚂蚁前端提供 高效省心、稳定可靠 的 Node.js 研发方案及基础设施。
作为一名前端,你是否:
现在就加入我们:
因为我们是同类人:
想跟一帮志同道合的人,站在巨人的肩膀上,去探索和引领广州最前沿的前端技术,做一些有意思的事吗?
想了解我们的发展史,传送门:『这些年的体验技术部 · Node.js 基础服务 - 摸爬滚打才不负功名尘土』 https://www.yuque.com/afx/about/nodejs
我们不自我局限,可以一杆到底也可以深钻某个领域,包括框架和命令行工具(Node.js)、基础服务(云服务)、基础设施底盘(K8S)等等。
Base 地:广州琶洲阿里中心、杭州蚂蚁 A 空间。
我们希望你是:
大概在两个月前,为了给团队小伙伴分享我们目前做的一些事的价值,写了一篇 『Serverless For Frontend 前世今生』 。
在那篇文章中,主要讲了进入每个阶段的缘由和产出的东西,但对演进背后的源动力的思考和阐述有所欠缺。
在之后这段时间,一方面跟内外的同学交流,一方面在实践中去复盘,于是有了本文: Rethink: Serverless For Frontend
。本文主要是对 Slide 的一些提炼,完整的 Slide 参见文末。
熟悉我的人会知道,我很喜欢在知乎专栏写一些科普向的文章,也非常喜欢交流。
那是因为我也曾经在小公司迷茫了多年,也作为一个小人物亲身经历了前端在蛮荒期的开疆拓土。在我有幸加入阿里,能更深入的参与到这一进程时,我却发现前端的贫富差距正在拉大,因此我希望自己能做些什么。
但在此次的分享 Slide 中,我并不打算分析技术细节,因为那毫无意义,这是当时 Topic 的大纲。
前面提到,那篇文章仅仅是记录,发出去后,有很多反馈来迫使我思考:
『从前端工程化到走向云端,从 BFF 到现在的 SFF,这一切的内在推动力是什么呢?』
在跟玉伯、平侠老师的交流后,我得到了第一个问题的答案:
是的,前端最早来源于 艺术与代码之间的 Gap
,前端这个岗位就是 设计师
和 研发
之间的桥梁。而发展到现在,『大前端』已经在涉猎到每一个屏幕或者说人机交互界面
上,包括:PC、手机、平板、手表、大屏、嵌入式设备。
那第二个问题,前端一路开疆拓土,到处跟客户端、后端『抢活』,是 KPI 的原因么?
于是我把文中的演化过程重新进行的梳理,得到了第二个答案:
前端的出现、前端接管模板层、前端接管粘合层,这一切都是为了更合理的分工,更优更高效的触达用户。
这也是我们的 前端领域价值观
,用于指导做什么,不做什么。
国内大部分团队处于这个阶段,前端工程化
已经深入人心且有一定的讨论和**指导(如云龙的前端工程 已经快 5 年了,还是那么的深刻)。
此时更多的是处于 前端工程化 -> 云端
这一阶段,受限于话语权、人才储备、基建能力等,举步维艰。阿里在这一块多走了几年,在众多前辈的努力下,我们也走出了自己的一条路。(如 蚂蚁 Node.js 演化 这一文所述)
每个公司的情况不一样,是无法简单复制的,我建议的演化路线是:
在我看来,BFF(Backend For Frontend) 对前端的价值毋庸置疑,但它带来的额外成本也历历在目。
可能前几年在扩张期的时候,这些问题都被掩盖,随着 Egg、TypeScript 等等的出现,有些前端开始飘飘然,觉得前端无所不能,到处挑衅 Java。我只能说,他们对后端一无所知,是前端的猪队友
。
在我的思考里,目前它所处的阶段,只是一个阶段性不完善的产物,我们还缺乏一个契机。
幸运的是,Serverless 终于到来了,它也许是我们所等待的那个契机点:
老实说,我也不知道,现在业界对 Serverless
的讨论,大都不是一个概念上的讨论。
就我们而言,我们只会继续遵循我们的 前端领域价值观
,继续深耕在自己的领域。因此,我们对 BFF
的概念进行了升级:Serverless For Frontend
解耦开发规范和部署规范是前端开发体系的设计重点。
术语概念 | 描述 |
---|---|
生态模块 | 生态环境中的第三方组件,包括JS模块、CSS模块。 |
工程框架模块 | 项目内部的公共模块(framwork)。 |
工程业务模块 | 项目内部的业务模块(modules)。 |
非模块化资源 | 并不是所有的开发资源都是模块化的,如页面资源, 模块化框架本身等。 |
我们的文件后缀名约定如下:
文件类型 | 后缀 | 描述 |
---|---|---|
脚本文件 | *.js , *.coffie |
|
样式文件 | *.css , *.sass |
|
模板文件 | *.tpl.html |
不会发布,需手动内嵌到JS。 |
测试脚本 | *.spec.js |
单元测试,仅在自动化测试时发布。 |
内嵌内容 | *.inline.* |
有些文件希望分开编写,但发布的时候自动合并。 |
调试代码 | *.inject.js |
在开发期自动插入到页面, 提供一些API的mock。 (针对 HybridApp ) |
- project
- component_modules **存放生态模块**
- components **存放工程模块**
- app - angular主入口
- framework - 工程框架模块
- modules - 工程业务模块
- views **存放页面以及非模块化资源**
- index.html - HTML文件
- lib - 存放其他非模块化类库
- server NodeJS服务端程序
- fis-conf.js FIS的配置文件
- bower.json 生态模块配置文件
- package.json 项目配置文件
require('modules/menu')
的方式调用,而views目录下的非模块化资源需要自行通过script/link
引入。app
, framework
, modules
等)采用organize by feature
的部署规范,- modules - 业务模块目录
- menu - 具体的业务模块
- menu.js - 该模块入口 (同模块名, 视为模块入口)
- menu.css - 该模块样式 (同模块名, 会自动依赖)
- menu.tpl.html - 该模块模板文件 (不会发布, 需手动内嵌到JS)
- menu-xx.png - 图片文件
- menu.spec.js - 该模块的单元测试
- menu-helper.inline.js - 该脚本不会发布, 需手动内嵌到JS
*.tpl.html
(需内嵌到js文件,后文描述)*.inline.*
(会被内嵌到其他文件)*.spec.js
(仅在自动化测试时发布)*.inject.js
(仅在本地调试时嵌入)fis
会读取该目录的文件,影响编译速度。目录结构如下:
- release
- public
- 项目名称
- 版本号
- lib **大部分资源都会部署到这个目录下**
- angular - 生态模块
- framework - 项目公共模块
- modules - 项目业务模块
- menu - 具体模块名
- scrat - 非模块化资源, view目录下的lib
- index.html
- server **NodeJS服务端程序**
- test **测试文件**
所谓构建,其核心任务就是将文件按照某种规则进行分类(以文件后缀分类,以模块化/非模块化分类,以前端/后端代码分类),然后针对不同的文件做不同的构建处理。
准备了下示例项目:https://github.com/ng-workflow/ngfis-showcase
同时,也准备了工具项目:https://github.com/ng-workflow/ngfis
我们通过fis的配置文件来映射目录转换,参见ngfis/config/default.js
的roadmap.path
配置, 关键配置如下:
roadmap: {
path: [
//tpl.html文件不发布
{
reg: /.*\.tpl\.html$/,
release: false
},
//component工程模块下的JS文件, 发布到public/lib目录下
{
reg : /^\/components\/(.*\.js)$/i,
id : '$1',
isMod : true,
useHash : false,
url : '${urlPrefix}/${name}/${version}/lib/$1',
release : '/public/${name}/${version}/lib/$1'
},
//view下的非模块化资源, 发布到public目录
{
reg : /^\/views\/(.*\.(?:html?|js))$/,
useCache : false,
isViews : true,
url : '${urlPrefix}/${name}/${version}/$1',
release : '/public/${name}/${version}/$1'
},
]
在示例项目下执行 ngfis release -d ../dist
即可看到发布效果,so easy~
最近以前公司很多朋友离职,我们搞拨测那批人终于都各奔东西。
整理下简历的一些注意事项,分享给大家。
原文地址:知乎专栏 https://zhuanlan.zhihu.com/p/31640541
很荣幸的宣布,Egg 正式发布 2.0 版本,距离 3.21 发布的 Egg 1.0 版本 仅时隔 8 个月。
Egg 的理念之一是渐进式增强,故我们为开发者提供渐进升级的体验。
搞定!几乎不需要修改任何一行代码,就已经完成了升级。
这得益于 Egg 对 1.x 的兼容,但为了更好的统一代码风格,以及更佳的性能和错误堆栈,我们建议开发者参考 升级指南 进一步升级。
如您所知,Egg 采用的是 『微内核 + 插件 + 上层框架』 模式。
其中微内核经过 3 年 4 个版本,以及在阿里的大规模应用,已经打磨的非常稳定。
接下来我们的重心主要在开发者体验方面的优化,包括:
同时,我们也欢迎社区更多的参与,一起打造更完善的生态。
截止至今天(2017-12-03):
社区方面:
趣味数据:
分享交流:
开源,痛并快乐着。
不好意思,(⊙﹏⊙)b我只是想测试一下,没想到真发出去了..
update ------
没想到还无法设置权限
收集我遇到的有意思的前端类库
angular
的时候,官方的chrome插件对file:///
的支持不好,所以最好在web浏览器里面。grunt
来做脚本livereload
来通知文件变更(不需要chrome livereload插件)再具体点:
grunt-contrib-connect
负责启动web服务connect-livereload
负责给middleware,动态在html底部加一条livereload的jsgrunt-contrib-watch
监控文件变化并通知nodejs
: http://nodejs.orggruntjs
(http://gruntjs.com/) :npm install -g grunt-cli
npm init
,一路回车。npm install --save-dev grunt matchdep grunt-contrib-connect grunt-contrib-watch connect-livereload grunt-open
src:'src/app/'
为你的源码目录/**
* 自动化脚本定义
*/
module.exports = function (grunt) {
'use strict';
//load all grunt tasks
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
//define tasks
grunt.registerTask('server', ['connect:server', 'open:server', 'watch:server']);
//env cfg
var pkg = grunt.file.readJSON('package.json');
var cfg = {
src: 'src/app/',
// Change 'localhost' to '0.0.0.0' to access the server from outside.
serverHost: '0.0.0.0',
serverPort: 9000,
livereload: 35729
};
//grunt config
grunt.initConfig({
//======== 配置相关 ========
pkg: pkg,
cfg: cfg,
//======== 开发相关 ========
//开启服务
connect: {
options: {
port: cfg.serverPort,
hostname: cfg.serverHost,
middleware: function(connect, options) {
return [
require('connect-livereload')({
port: cfg.livereload
}),
// Serve static files.
connect.static(options.base),
// Make empty directories browsable.
// connect.directory(options.base),
];
}
},
server: {
options: {
// keepalive: true,
base: cfg.src,
}
}
},
//打开浏览器
open: {
server: {
url: 'http://localhost:' + cfg.serverPort
}
},
//监控文件变化
watch: {
options: {
livereload: cfg.livereload,
},
server: {
files: [cfg.src + '/**'],
// tasks: [''],
},
}
});
};
grunt server
Node.js 很简单,容易上手。但也因此缺乏不少规范,使用者水平参差不齐。
最近经常看到的一个问题是:很多新手,在部署的时候,是直接在服务器上 npm install
,这是非常不推荐的。
因为安装是有较大的网络耗时的,所以你甚至无法保证集群情况下,两台服务器上npm install
下来的包是一模一样的。
如果某个库刚好更新了,并且它有 BUG,然后你就是百思不得其解:一定概率出某个问题。排查起来简直想死。
当然,很多人为了解决这个问题,就选择「锁版本」这个方案。
鉴于 「锁版本」这个方案属于 「屎色自行车问题」 ,这里不想讨论,我们的观点参见:「知乎专栏 - 死马:为什么我不使用 shrinkwrap(lock)」。
上线后,发现线上故障,要快速回滚止血的时候,就懵逼了:
npm cache
解决不了问题,如机器扩容的时候。其中,关键点是:在构建期就把依赖打包进去。
优点:
缺点:
那有同学就要问了:我是小公司,不像你们有这些基建可以服务,怎么办?
其实成本真的很低:
您好,我现在有一个后端项目用的egg,我想用vscode调试,但是前端监听后端使用的端口是8080,默认egg调试的端口是7001,那么我如何更改这个端口呢?
不知不觉,在项目中用angular已经半年多了,踩了很多坑。
趁着放假,把angular的3本书都看了遍,结合这半年的经验,是该做个总结了。
希望可以给大家带来启示,少踩点坑。本文针对的读者:
- 具备JavaScript性能优化的相关知识(
雅虎14条性能优化原则
、《高性能网站建设指南》
等)- 拥有angular实战经验。
谈起angular的脏检查机制(dirty-checking)
, 常见的误解就是认为: ng是定时轮询去检查model是否变更。
其实,ng只有在指定事件触发后,才进入$digest cycle
:
ng-click
)$http
)$location
)$timeout
, $interval
)$digest()
或$apply()
参考《mastering web application development with angularjs》 P294
传统的JS MVC框架, 数据变更是通过setter去触发事件,然后立即更新UI。
而angular则是进入$digest cycle
,等待所有model都稳定后,才批量一次性更新UI。
这种机制能减少浏览器repaint次数,从而提高性能。
参考《mastering web application development with angularjs》 P296
另, 推荐阅读: 构建自己的AngularJS,第一部分:Scope和Digest
$scope.$watch(watchExpression, modelChangeCallback)
, watchExpression可以是String或Function。console.log
也很耗时,记得发布时干掉它。(用grunt groundskeeper)ng-if vs ng-show
, 前者会移除DOM和对应的watchbindonce
)
参考《mastering web application development with angularjs》 P303~309
var unwatch = $scope.$watch("someKey", function(newValue, oldValue){
//do sth...
if(someCondition){
//当不需要的时候,及时移除watch
unwatch();
}
});
避免深度watch, 即第三个参数为true
参考《mastering web application development with angularjs》 P313
减少watch的变量长度
如下,angular不会仅对{% raw %}{{variable}}
{% endraw %}建立watcher,而是对整个p标签。
双括号应该被span包裹,因为watch的是外部element
参考《mastering web application development with angularjs》 P314
{% raw %}
<p>plain text other {{variable}} plain text other</p>
//改为:
<p>plain text other <span ng-bind='variable'></span> plain text other</p>
//或
<p>plain text other <span>{{variable}}</span> plain text other</p>
{% endraw %}
$digest cycle
, 并从$rootScope开始遍历(深度优先)检查数据变更。参考《mastering web application development with angularjs》 P308
$timeout
里面延迟执行。$apply
。$http.get('http://path/to/url').success(function(data){
$scope.name = data.name;
$timeout(function(){
//do sth later, such as log
}, 0, false);
});
$evalAsync
vs $timeout
$evalAsync
, 会在angular操作DOM之后,浏览器渲染之前执行。$evalAsync
, 会在angular操作DOM之前执行,一般不这么用。$timeout
,会在浏览器渲染之后执行。$scope.dataList = convert(dataFromServer)
刷新数据时,我们常这么做:$scope.tasks = data || [];
,这会导致angular移除掉所有的DOM,重新创建和渲染。
若优化为ng-repeat="task in tasks track by task.id
后,angular就能复用task对应的原DOM进行更新,减少不必要渲染。
参见:http://www.codelord.net/2014/04/15/improving-ng-repeat-performance-with-track-by
我们都知道angular建议一个页面最多2000个双向绑定,但在列表页面通常很容易超标。
譬如一个滑动到底部加载下页的表格,一行20+个绑定, 展示个100行就超标了。
下图这个只是一个很简单的列表,还不是表格,就已经这么多个了:
但其实很多属性显示后是几乎不会变更的, 这时候就没必要双向绑定了。(不知道angular为何不考虑此类场景)
如下图,改为bindonce或angular-once后减少了很多:
update:
1.3.0b10开始支持内建单次绑定, {% raw %}{{::variable}}
{% endraw %}
设计文档:http://t.cn/RvIYHp9
commit: http://t.cn/RvIYHpC
目前该特性的性能似乎还有待优化(2x slower)
在$digest过程中,filter会执行很多次,至少两次。
所以要避免在filter中执行耗时操作。
参考《mastering web application development with angularjs》 P136
angular.module('filtersPerf', []).filter('double', function(){
return function(input) {
//至少输出两次
console.log('Calling double on: '+input);
return input + input;
};
});
可以在controller中预先处理
//mainCtrl.js
angular.module('filtersPerf', []).controller('mainCtrl', function($scope, $filter){
$scope.dataList = $filter('double')(dataFromServer);
});
$broadcast
会遍历scope和它的子scope,而不是只通知注册了该事件的子scope。$emit
, 参见angular/angular.js#4574以DOM为中心
的思维,拥抱以数据为中心
的思维。参见
参见: http://stackoverflow.com/questions/14994391/how-do-i-think-in-angularjs-if-i-have-a-jquery-background
翻译: http://blog.jobbole.com/46589/
fix #24
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.