Giter Club home page Giter Club logo

notes's People

Contributors

ohhoney1 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

xiumark

notes's Issues

21年年终总结:一位前端 Leader 的碎碎念

21年过去了,还是写一下记录下吧。计划公开的,索性比较随意写写,反思和展望基本省略了。在目前公司已经5年整了,从一个前端小白到一个前端老白,就借用我曾在一次新人培训会中总结的感受:感恩 & 值得。

工作篇

招聘

上半年基本上有50%的精力扑在了招聘上:组内3位小伙伴离职了(下家都很不错,开心),加上本来的人员缺口,所以招人是首要任务。最多时候一天面试了 5 人,一周面了 17 人,心力交瘁,面试、offer被鸽时却又有心无力。但上半年入职率很低,这也是需要反省的一个点,付出和收获差距太大。但所幸9月份招聘目标基本达成,接着是团队管理问题:新人融入、技术规划等,这个后面复盘时想想其实很重要:团队本来不大,怎么让新人快速融入,又能充分利用新人的技术能力格外重要。

目前组内有10人(4个实习生)是今年招聘的,现在来看招聘事宜,对自己评个及格分数没问题的。最近一次周会上话题是21年收获相关的,不少同学提到换了新工作,表达了对现状的认可等,其实于我而言,一样的道理,我也是遇到了新的工作伙伴,接触了新的视野和想法,感恩遇见。

管理

今年算是自己的真正意义上的管理元年,虽然编码量并没减少,但在管理方式、理念及实践上,可畏迈出了很重要的一步:开始尝试去跟别人好好沟通;去站在第三视角看待问题;敢于夸奖和批评等等。当然,做得不好的地方也很多,被Leader批评也不少,所幸不是pua,能及时地得到批评和反馈,是件很幸运的事。

和其他团队的争吵也不少:UI同学的呵斥与不满,上百条的UI修改意见让我们改到大半夜;对产品功能交互的吐槽,谁都不服却又互相试探;与后端同学的争执,及开玩笑般的谩骂等等。管理的路上,这些避免不了,方法论谁都会,但各种因素糅杂在一起,加上作为一个管理新人,实践起来偏差还是挺大的。后续提升点还是很多的,把本岗位的事情做对、做好作为最基本的前提,赋能业务,带着团队多做自我突破。

技术

还是继续围绕 React + umi + ts 相关技术栈做事情,用 react 重构了两个 vue 项目,hooks库、组件库、utils库持续完善中,最明显的一个变化是,组内同学对 TypeScript 掌握得更熟练了:现在 code review 时,IDE里的红色提示基本都消失了。今年在工程化建设方面又做了一些事情:npm私服、规范化更严谨、构建提速、CI、YApi二次开发、新的项目模板等等,大多是组内小伙伴完成的,我“发号施令”。

我认为最重要的是内部文档的梳理与维护,这是一个很宝贵的知识财富。通过文档达成最基本的共识,帮助新人快速融入,消除部分差异认知,避免无效的争论。后续继续保持文档共建模式,持续完善,去提高团队成员的积极性也是管理者的一个命题作文。

前端文档

做了的事情还有很多,比如图可视化的算法、**的优化、node应用等,新的技术点、陌生领域的探索与挑战还会更多。

单单就自己而言,今年的技术视野拓宽很多,比如看历史收藏文章及分享时,每次都有新的收获,豁然开朗的次数更多了。但,但是呀,自己的输出却很少很少,前端组内全年技术分享21次,我只有一次。22年会继续保持节奏感,更希望的是去做一些新的事情辅助开发、辅助业务,多分享,保持内外部交流。

生活篇

运动

夏天是运动高峰期,平均一周去两次健身房,最多的运动就是游泳,从之前的狗刨到现在悠闲自如的蛙泳,自由泳只会一点点。12月份和公司小伙伴再次开启了羽毛球之约,基本是周二或者周四晚上8点,去公司对面的球馆打球,每次1-2小时,很爽很开心。但体重依然居高不下,BMI还是超了些,立志五一前必须得减一些了。

读书

年初定的目标是20本书籍:10本技术+10本非技术,9月份技术相关书籍的阅读目标已完成,后面看书就没啥计划了,随便看,导致10-11两月,基本没在固定的时间点系统地阅读。加上LOL手游出来了,每晚打得那叫一个热火朝天、酣畅淋漓,最高上了翡翠。有一天晚上,为了拿一次首胜,从11点打到半夜2点,打了8局大乱斗模式。给我一种要吐的感觉,突然进入了贤者模式,也就没再玩了。22年元旦,开黑又玩了几把,游戏权当娱乐就行,或者消磨消磨时间。打游戏如此,好多事情一样道理,一味满足自己的虚荣心和好胜心,得不偿失呀。

扯得远了,回归年终总结,回归读书话题。

关于为什么读书,说个亲身经历。大学期间,大概14年左右,迷上了”逻辑思维“和”蛮子文摘“,几乎天天早上听 60s 语音,看热乎的新闻和文摘,沉迷快餐文化,但却感觉自己”D的不行“。毕业工作后,经历和常识稍多些了吧,对人和事的想法每年都有改变,没了”D的不行“的想法,更多的是知道了自己视野和能力的各种不足。读书不一定能带给我们颜如玉、黄金屋,但有足够的视野去扩展,广袤的**去碰撞,而快餐文化和奶头乐一定做不到的。

所以,22年还是加量读书,题材继续多涉及些。注:不一定非得是书,看视频、纪录片、优秀文章、演讲等,同一个道理。

总结

晚上写了快俩小时,跟记流水账一样,想到哪写哪。21年整体上没太大波动,处于不断的小焦虑中,烦心事也不少,还没到崩溃的边缘。21年年初立了一些 Flag,年底完成 50% 左右,还是挺打脸的。今年元旦当天还是写了新一年的flag,放宽心看待吧,不一定达成,但希望有 flag 之外的收获。

axios:取消请求的使用案例分析

document

https://github.com/axios/axios#cancellation

取消http请求,axios文档里提供了两种用法:

// 第一种:使用 CancelToken
const { CancelToken, isCanCel } = axios;
const source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(thrown => {
  if (isCancel(thrown)) {
  	// 获取 取消请求 的相关信息
    console.log('Request canceled', thrown.message);
  } else {
    // 处理其他异常
  }
});

axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})

// 取消请求。参数是可选的,参数传递一个取消请求的相关信息,在 catch 钩子函数里能获取到
source.cancel('Operation canceled by the user.');

// 第二种:给构造函数 CancelToken 传递一个 executor 函数作为参数。这种方法的好处是,可以用同一个 cancel token 来取消多个请求
const CancelToken = axios.CancelToken;
let cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // 参数 c 也是个函数
    cancel = c;
  })
});

// 取消请求,参数用法同上
cancel();

项目中用法示例

在一个真实的项目中,一般都会对axios进行二次封装,针对请求、响应、状态码、code等做处理。贴一个项目里常用的request.js:

import axios from 'axios'
import store from '@/store'
import { getToken } from '@/utils/auth'

// 创建一个 axios 实例,并改变默认配置
const service = axios.create({
  baseURL: process.env.BASE_API, // api 的 base_url
  timeout: 5000 // request timeout
})

// 请求拦截
service.interceptors.request.use(
  config => {
    // Do something before request is sent
    if (store.getters.token) {
      // 让每个请求携带token-- ['X-Token']为自定义key 请根据实际情况自行修改
      config.headers['X-Token'] = getToken()
    }
    return config
  },
  error => {
    // Do something with request error
    console.log(error) // for debug
    Promise.reject(error)
  }
)

// 响应拦截
service.interceptors.response.use(
  response => response,
  error => {
    alert(error)
    return Promise.reject(error)
  }
)

export default service

对于某一个请求添加取消的功能,要在调用api时,加上cancelToken选项,使用时的示例:

// api.js
import request from 'request'
export function getUsers(page, options) {
  return request({
    url: 'api/users',
    params: {
      page
    },
    ...options
  })
}


// User.vue
import { CancelToken, isCancel } from 'axios'
import { getUsers } from 'api'

...

cancel: null

...

toCancel() {
  this.cancel('取消请求')
}

getUsers(1,
  {
    cancelToken:  new CancelToken(c => (this.cancel = c))
  }
)
.then(...)
.catch(err => {
  if (isCancel) {
    console.log(err.message)
  } else {
    ...
  }
})

以上,我们就可以顺顺利利地使用封装过的axios,取消某一个请求了。其原理无非就是把cancelToken的配置项,在调用api时加上,然后就可以在业务代码取消特定请求了。

批量取消请求

document 里的第二种方法已经说过:通过指定同一个cancel token来取消。但是,在上面的项目示例中,不能控制拿到相同的cancel token。我们可以换个思路:用数组保存每个需要取消的cancel token,然后逐一调用数组里的每一项即可:

// User.vue
import { CancelToken, isCancel } from 'axios'
import { getUsers } from 'api'

...

cancel: []

...

toCancel() {
  while (this.cancel.length > 0) {
    this.cancel.pop()('取消请求')
  }
}

getUser1(1,
  {
    cancelToken:  new CancelToken(c1 => (this.cancel.push(c1)))
  }
)

getUser2(2,
 {
  cancelTokem: new CancleTokem(c2 => (this.cancel.push(c2)))
 }
)

切换路由时,取消请求

上面讲了取消一个请求及页面内批量abort的方法,此外,还有一种需求——切换路由时,取消所有。

这里不详细赘述了,大概思路就是在请求拦截器里,统一加个token,并设置全局变量source控制一个cancel token,在路由变化时调用cancel方法。具体实现,见 参考链接4

取消请求的实现原理

cancelTokensource方法维护了一个对象,里面包括了token令牌和cancel方法,token来自与构造函数CancelToken,调用cancel方法后,token的promise状态为resolved,进而又调用了xhr的abort方法,取消请求成功。具体见 参考链接5

参考链接

  1. axios
  2. vue-element-admin
  3. axios cancelToken 如何在切换路由的时候来取消所有请求
  4. 路由变化时使用axios取消所有请求
  5. axios源码分析——取消请求

Chrome: 网络慢原因,导致的字体加载错误—Slow network is detected. Fallback font will be used while loading

问题

加载一些字体文件时,如果网络环境较差,Chrome的开发者工具里可能会报错:

Slow network is detected. Fallback font will be used while loading

出现此错误信息,影响项目中调试bug。这个log一般不会影响页面的正常访问,只是没加载到字体而已。

分析

StackOverflow上有这个问题的相关讨论:why-does-this-slow-network-detected-log-appear-in-chrome,给出了出现此错误的原因:网速较慢时,当加载不到对应字体时,为了不出现空白,Chrome浏览器默认降级使用默认字体。

也给出了对应解决方法:

  • 进入chrome://flags/#enable-webfonts-intervention-v2,把enable-webfonts-intervention-v2禁用掉即可。
  • 如果能直接修改引用相关css文件时,可以直接添加属性:font-dispaly: block;了解有关css4新属性font-display的知识,MDN文档: font-display

在新版本浏览器中(68.0.3440.106),报错信息变了,见下图:
error
谷歌针对该log已经给出参考答案,链接为https://www.chromestatus.com/feature/5636954674692096。即用上面的第2种方法。

总结

这个错误出现时机特殊,一般不会影响功能。Chrome更新频繁,这个问题后面可能又会有其他方法。
而在实际项目中,由于字体文件只有6kb左右,采用了把字体文件用Data URL协议,以base64的方式加载文件。举个实际应用的例子,在vue-cli3创建的工程项目中,修改url-loader处理font文件的大小限制。

chainWebpack: config => {
  config.module
    .rule('fonts')
    .use('url-loader')
    .loader('url-loader')
    .tap(options => {
       return {
          ...options,
	    limit: 10000 // 覆盖默认值4096
	  }
       })
}

下载文件 和 链接跳转(打开新窗口被拦截),没关系的!

问题来源

开发中,各种log下载到本地的需求特别多,由于交互方式各不相同,封装了一个方法,专门用来处理文件的下载。这个也比较常见,就是通过a标签处理:

export function handleDownload(url) {
  const a = document.createElement('a')
  const $body = document.body

  a.href = url
  // a.target = '_blank'
  a.style.display = 'none'
  $body.appendChild(a)
  a.click()
  $body.removeChild(a)
}

这种下载文件的方式有个前提:后端已对接口的返回数据做了处理,相应的响应头见下图:
image
主要在content-type:"application/octet-stream",告诉浏览器这是字节流,浏览器对此的操作默认是下载。

问题来了

代码简单,可问题就出现在a.target = '_blank'这句。没注释之前,下载的时候会新开一个页面,然后下载,页面关闭。
突然有次,浏览器阻止了新开的页面,自然而然下载不了了,然后...然后就有个这个笔记。

下载文件的问题

问题来源里已经给出一个下载字节流的方式,还有几种方式:

  • window.open()方法
  • 动态创建form,模拟表单提交操作

对于非文本流的下载,H5里,给a标签提供了downlaod属性,见张鑫旭的文章——JS前端创建html或json文件并浏览器导出下载,文章详细介绍了借助downloadBlobBase64三种方式,实现文件下载功能,但是IE系列全军覆没。

浏览器打开新页面问题

上面啰里啰嗦描述了本人的误解,把下载文件和打开新页面混为一谈了,可能那天敲代码迷糊了。下面简单总结下浏览器打开新页面的问题。

浏览器阻止新建页面,这个问题太常见了,这其实也不算问题,是现阶段各浏览器厂商的一个基本安全策略,为此,js被广告商滥用的时代也结束了。
可是,各种需求积压过来了,需要前端不得不做一些新建页面跳转的功能。常见的一些方式有:

  1. 上面下载的方法是一种,打开新页面要加上 a.target = '_blank'
  2. 使用window.open,异步打开新页面时,为了防止浏览器拦截,可以这样做
function openNewWindow() {
  window.open('/')
  http.get(url).then(() => {
    window.location.href = url
  })
}

对于第二种方法,有一个疑问。见问题js打开新窗口被拦的问题,其中有提到非异步加载时,直接点击同步请求时即可正常跳转。后面用setTimeout模拟异步请求,发现临界值点是1000ms,小于1000ms的setTimeout异步可以正常跳转。但是ajax请求和这个数字没关系了——不管是多少ms,结果都是被拦截。
后网上查阅资料,window.open() 某些情况会被浏览器阻止弹出窗口博文里很详细的介绍了window.open方法,其中提供了一种对于浏览器拦截时的提示方案:

var windowOpen = window.open('http://lingyi.red/', 'lingyired', 'status=no,menubar=no,titlebar=no,toolbar=no,directories=no, width=800,height=600, top=0, left=0')
// 利用返回值来判断 window.open 操作是否被正常地执行 
if (windowOpen == null || typeof(windowOpen)=='undefined'){
  $('#feedback').html('已触发:窗口无法打开,请检查你的浏览器设置。')
} else {
  $('#feedback').html('已触发:窗口打开成功了')
} 

总结

做的是下载文件的功能,排查问题时居然一直搜索浏览器阻止新页面打开的问题。其实这两个功能没关系,唯一的相同点是动态创建a标签,这里把自己迷惑住了而已。对每一句代码都应该思考其存在的意义,不写冗余代码,不偷工减料。

真是“基础不牢,地动山摇”。

前端常量维护:TypeScript 项目中维护常量引发的思考

背景:项目基于antd + typescript 开发,经常用到字段映射列表项,这里叫做常量,并配合 Select 筛选联动。具体需求:

  1. Select 中水果列表严格按顺序展示;
  2. “种类”一栏里,通过接口返回的 fruitType 字段匹配对应的中文。

image

第一种方式:我们首先想到的是维护一个对象格式的常量 FRUITS_OBJECT,这样很容易完成需求2。然后用工具函数 mapObjectToArray,把 FRUITS_OBJECT 转成 FRUITS_LIST 数组,这样可以满足需求1,循环展示列表项。而且,我们可以借助 ts 的索引类型拿到 fruitType 的类型。

interface ListItem {
  label: string
  value: string
}

export const FRUITS_OBJECT = {
  apple: '苹果',
  banana: '香蕉',
  pear: '梨'
}

function mapObjectToArray(o: Record<string, string>) {
  const arr: ListItem[] = []
  Object.keys(o).forEach(item => {
    arr.push({ label: o[item], value: item })
  })
  return arr
}

export const FRUITS_LIST = mapObjectToArray(FRUITS_OBJECT)

export type FruitType = keyof typeof FRUITS_OBJECT // 'apple' | 'banana' | 'pear'

此时,问题出现了:对 Object 遍历的时候,我们无法保证 key 的顺序。比如我们的数据源 FRUITS_OBJECT 新增了一个特殊值:{ '2': '两个未知水果' },调用 mapObjectToArray 后的输出为:

const FRUITS_OBJECT = {
  apple: '苹果',
  banana: '香蕉',
  pear: '梨',
  2: '两个未知水果'
}

mapObjectToArray(FRUITS_OBJECT)
// 结果为:[{label: "两个未知水果", value: "2"}, {label: "苹果", value: "apple"}, {label: "香蕉", value: "banana"}, {label: "梨", value: "pear"}]

这种情况,列表渲染(比如 Select 的下拉选择)就出现了我们不想要的数据顺序展示,显然不满足需求2:列表项第一个不是 “苹果”。探究原因首先会想到,遍历对象的 key 时,不能保证的 key 的顺序。但是为什么呢?怎么让 Object 的遍历输出时保证有序?

题外探讨下,遍历 Object 的 key 时,顺序是怎样的呢?上网查下资料,大多直接写着:

Chrome Opera 的 JavaScript 解析引擎遵循的是 ECMA-262 第五版规范。因此,使用 for-in 语句遍历对象属性时遍历顺序并非属性的构建顺序。而 IE6 IE7 IE8 Firefox Safari 的 JavaScript 解析引擎遵循的是较老的 ECMA-262 第三版规范,属性遍历顺序由属性构建的顺序决定。

查了下 ECMS 文档,嗯,确实是这样。另外还有:

Chrome Opera 中使用 for-in 语句遍历对象属性时会遵循一个规律:它们会先提取所有 key 的 parseFloat 值为非负整数的属性,然后根据数字顺序对属性排序首先遍历出来,然后按照对象定义的顺序遍历余下的所有属性。

这个就不好说了,浏览器版本更新太快了,在一些现代浏览器的新版本中测试了下,发现并不会对 key 进行 parseFloat 转换。猜测是 js 解析引擎已经原生支持了 es6及以上规范。查了下文档,证明是猜测对的:es6 规范中 OrdinaryOwnPropertyKeys 明确了遍历 Object 的 key 时,顺序是可预期的,截止目前的es11(es2020)保持不变:

  1. 优先数字类型的 key,升序排列;
  2. 其次是字符串类型的 key,按照定义顺序排列;
  3. 最后是 Symbol 类型的 key,按照定义顺序排列。

以上规定只能特定语法中生效:Object.assign | Object.defineProperties | Object.getOwnPropertyNames | Object.getOwnPropertySymbols | Reflect.ownKeys。对于另外一些常用的遍历对象方法仍不能保证有序:Object.keys | for ... in | JSON.parse | JSON.stringify

说了这么多,目前看来使用 Object 或者说 JSON 有次序的保存数据都是不靠谱的。有没有一种方案能保证键值对数据结构的次序呢?答案是肯定的:Map 结构。

第二种方式:ES6新增了一种数据结构类型 Map,我们可以用它来保证顺序。通过 getMapValue 方法一次性拿到我们想要的常量:FRUITS_LIST 和 FRUITS_OBJECT。缺点:拿不到 fruitType 的类型 FruitType,这在 TypeScript 项目中有点难受,类型推断没有达到预期。

const mapFruits = new Map([
  ['apple', '苹果'],
  ['banana', '香蕉'],
  ['pear', '梨'],
  [2, '两个未知水果']
])

function getMapValue(map: Map<string, string>) {
  const o: Record<string, string> = {}
  const arr: { label: string; value: string }[] = []
  map.forEach((value, key) => {
    o[key] = value
    arr.push({
      label: value,
      value: key
    })
  })
  return { o, arr }
}

export const { o: FRUITS_OBJECT, arr: FRUITS_LIST } = getMapValue(mapFruits)

第三种方式:TypeScript 中的枚举 enum 是否更符合我们的要求呢,比如这里用字符串枚举实现需求:

export enum EFruits {
  apple = '苹果',
  banana = '香蕉',
  pear = '梨'
}

function mapEnumToList(eu: { [key in string]: string }) {
  const arr: ListItem[] = []
  Object.keys(eu).forEach(item => {
    arr.push({ label: eu[item], value: item })
  })
  return arr
}

// 这个方法也是可以拿到 fruitType 的类型。
export type FruitType = keyof typeof EFruits // 'apple' | 'banana' | 'pear'

// 渲染调用
const fruitType: FruitType = 'apple'
const currentType = EFruits[FruitType]

目前看来,enum 似乎满足我们的需求:可以拿到 fruitType 的类型 FruitType,而且利用 enum 本身的优势做映射也比较合适,如果value是数字的话,还可以做双向映射。但是,enum 的成员不能是数字,enum { '2' = '两个未知水果' } 不能通过 tsc 编译,所以还是存在局限性。

第四种方式:用一个最直接的方式,维护一个数组常量:FRUITS_LIST,通过一个通用工具函数 mapArrayToObject,把 FRUITS_LIST 转成 FRUITS_OBJECT 形式,这样既能保证顺序,也能保证 TypeScript 中高效的类型安全。下面针对数组保存常量的方式,我们写个完整的示例。

// util.ts
export interface SelectItem {
  label: string
  value: string | number
}

// 编写一个工具类型:从联合类型中找到想要的某一类型,并提取相应属性 label 的值
type ExtractValue<T, K> = T extends { value: K; label: infer R } ? R : never

export const genMapObject = <T extends Readonly<SelectItem[]>>(originData: T) => {
  const o: {
    [K in T[number]['value']]: ExtractValue<T[number], K>
  } = Object.create(null)
  originData.forEach(item => {
    // ;(o as any)[item.value] = item.value
    o[item.value as T[number]['value']] = item.label as ExtractValue<T[number], T[number]['value']>
  })
  return o
}

// constant.ts
export const FRUITS_LIST = [
  { label: '苹果', value: 'apple' },
  { label: '香蕉', value: 'banana' },
  { label: '梨', value: 'pear' }
] as const

export const FRUITS_OBJECT = genMapObject(FRUITS_LIST)

export type T_FRUITS_TYPE = keyof typeof FRUITS_OBJEC

// page.ts
const t: T_FRUITS_TYPE = 'apple'
const currentFruit = FRUITS_OBJECT[t] // '苹果'

此外,利用 TypeScript 的特性做了一些类型推论的优化。这里需要了解 ts 的几个知识点:

  1. const 断言:3.4版本新增的一个类型断言功能:不扩展字面类型;把对象断言为只读的对象属性;把数组断言为只读的元祖。例如:const foo = 1,foo 的类型为 1 ,而不是 number。
  2. 索引访问类型:可以通过索引访问类型访问类型的属性,支持 [number] 方式访问数组项的类型。
  3. infer 关键字:2.8版本新增的映射类型中的类型推论方法。相关的常见工具类型包括:提取函数返回值 ReturnType,提取函数函数 ParamType 等。

通过调用 genMapObject 我们可以获取到安全的映射对象类型,而不是: { apple: string; banana: string; pear: string }。

image
image

做个总结:讨论了上述几个维护常量列表的方法,再针对数据源我们分为两类:一是可控且固定的数据源,即 key 只是字符串类型,形如 { apple: '苹果', banana: '香蕉', pear: '梨' };二是不可控数据源,即 key 可能有数字、Symbol 类型,形如 { [Symbol()]: 'aa', 2: 'bb', name: 'cc' }。按照不同类推荐不同的方案。

对于第一类数据源:Map 方式拿不到 fruitType 的类型,不考虑。enum 满足需求,但是性能有损耗,不如直接用对象,即推荐选用第一种方式。

对于第二类数据源:enum 和 Object 都无法保证,Map 和 数组方式满足。为了保证 TypeScript 环境下有良好的类型提示,我们优先选择数组,即推荐选用第四种方式——数组:既能保证有次序遍历,又能保证更安全的类型。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.