nh0007 / blogs Goto Github PK
View Code? Open in Web Editor NEWblogs
blogs
公司近来有重构主网站的打算,考虑到需要SEO(搜索引擎优化),传统的基于Vue的单页应用显然不太合适,从头搭建一个服务端渲染的应用也相当复杂,所幸Nuxt,该框架默认基于Vue,集合了Vue 服务端渲染,开箱即用,可谓是Vue SSR最为简捷妥当的选择。
SSR,Server-Side Rendering简写,即服务端渲染,也称同构应用。相较于SPA项目,SSR的出现主要解决了两个问题:
需要注意的一点是,SSR项目仅仅是渲染首屏,其他页面仍然是由客户端渲染的。
关于SSR更多信息,可以参照这里,SSR方案也并非毫无争议,这篇文章提出了些许提问,可以参照阅读。
Nuxt项目搭建比较简单,可以参照官网,需要注意的一点是在搭建过程中选择Nuxt模式时有两种:SPA or Universal,不要选择SPA,否则就跟普通的Vue单页应用没啥差别,体现不出Nuxt服务端渲染的价值。
路由
Nuxt搭建的项目自带Vue-Router,因此不需要额外的安装,它会根据pages目录结构自动生成路由配置,路由出口使用Nuxt标签,入口使用nuxt-link标签,其余使用与传统的Vue项目大致相同,详情可见这里。
数据管理
当项目根目录下存在store目录时,Nuxt将引入Vuex模块,不需我们自己安装。Nuxt支持两种使用Vuex的模式,模块模式和经典模式,官方推荐模块模式,它会将store目录下的每个js(或者ts)文件转换为各个状态模块,而index.js(ts)作为根模块,不需要我们自己去export Vuex Store,Nuxt自己会帮助我们处理。详情可见这里。
入口文件
Nuxt项目没有像传统Vue项目有一个统一的入口文件,因此全局引入第三方插件或者css文件会有所不同(传统的Vue项目可以在入口文件进行导入),两者都需要到nuxt.config.js文件进行配置,第三方插件的配置可以参照这里,全局引入css样式可以参照这里。
项目结构
Nuxt搭建的项目默认帮我们对项目结构进行规划,各个目录有对应的含义,结构清晰,详情可以参见这里。
Nuxt脚手架目前暂不支持添加TypeScript选项,相关的提案正在进行中,等Vue3出来后,可能会有更多的支持。
添加TypeScript流程如下:
添加@nuxt/typescript-build
npm install --save-dev @nuxt/typescript-build
# OR
yarn add --dev @nuxt/typescript-build
修改nuxt.config.js,添加依赖
// nuxt.config.js
export default {
// 中略
buildModules: [
'@nuxtjs/eslint-module',
'@nuxt/typescript-build'
],
// 中略
}
创建tsconfig.json文件
// tsconfig.json
{
"compilerOptions": {
"target": "es2018",
"module": "esnext",
"moduleResolution": "node",
"lib": [
"esnext",
"esnext.asynciterable",
"dom"
],
"esModuleInterop": true,
"allowJs": true,
"sourceMap": true,
"strict": true,
"noEmit": true,
"baseUrl": ".",
"paths": {
"~/*": [
"./*"
],
"@/*": [
"./*"
]
},
"types": [
"@types/node",
"@nuxt/types"
]
},
"exclude": [
"node_modules"
]
}
在目录下添加types文件夹,文件夹下添加vue-shim.d.ts文件,添加如下代码:
declare module "*.vue" {
import Vue from 'vue'
export default Vue
}
安装@nuxt/typescript-runtime
npm install @nuxt/typescript-runtime
# OR
yarn add @nuxt/typescript-runtime
修改package.json中script命令,将nuxt改为nuxt-ts:
"scripts": {
"dev": "nuxt-ts",
"build": "nuxt-ts build",
"generate": "nuxt-ts generate",
"start": "nuxt-ts start"
}
将nuxt.config.js改为nuxt.config.ts,将server文件夹下的index.js改为index.ts,并将index.ts文件中的require(../nuxt.config.js)
改为require(../nuxt.config.ts)
,由于我们构建时安装了Element,所以还需把element-ui.js改为element-ui.ts,这时该文件会报错,在types文件夹下添加global.d.ts文件,添加以下代码:
declare module 'element-ui/lib/locale/lang/en' {}
在nuxt.config.ts添加typescript检查配置:
typescript: {
typeCheck: true,
ignoreNotFoundWarnings: true
}
接下来,为了在Vue中使用TypeScript更为便捷,添加装饰器vue-property-decorator依赖:
npm install --save-dev vue-property-decorator
# OR
yarn add --dev vue-property-decorator
同时,配置tsconfig.json,添加对装饰器的支持
{
"compilerOptions": {
"experimentalDecorators": true,
}
}
因为使用了TypeScript,我们需要修改eslint配置,Nuxt添加了专门的eslint配置,先移除原有的配置:
npm uninstall @nuxtjs/eslint-config
# OR
yarn remove @nuxtjs/eslint-config
添加新的eslint配置
npm i -D @nuxtjs/eslint-config-typescript
# OR
yarn add -D @nuxtjs/eslint-config-typescript
修改package.json:
"scripts": {
"dev": "nuxt-ts",
"build": "nuxt-ts build",
"start": "nuxt-ts start",
"generate": "nuxt-ts generate",
- "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
+ "lint": "eslint --ext .ts,.js,.vue --ignore-path .gitignore ."
}
修改.eslintrc.js配置:
parserOptions: {
- parser: 'babel-eslint'
},
extends: [
- '@nuxtjs',
+ '@nuxtjs/eslint-config-typescript',
'prettier',
'prettier/vue',
'plugin:prettier/recommended',
'plugin:nuxt/recommended'
]
接下来,我们就可以在vue中使用TypeScript和装饰器:
import { Vue, Component } from 'vue-property-decorator'
import Logo from '~/components/Logo.vue'
@Component({
components: {
Logo
}
})
export default class IndexPage extends Vue {}
至此,Nuxt添加TypeScript支持完毕。更多介绍可以参照这里。
路由权限是前端一个很常见的需求,登陆和未登陆下可以访问的页面、页面的内容都会有所不同,Nutx配置路由权限可以有两种方法,以下简要介绍下:
第一种方式比较简单,适用于路由路径比较确定、页面不多的项目,在plugins下添加auth.ts,添加对路由控制的代码,如下:
// 不需登陆权限的路径列表
const noPermissionList: string[] = ['/login']
export default ({ app, store }) => {
app.router.beforeEach((to, from, next) => {
if (noPermissionList.indexOf(to.path) !== -1) {
next()
} else {
next({ path: '/login' })
}
})
}
添加好后,还需要在nuxt.config.ts中进行配置,才会生效。
plugins: [
'@/plugins/auth'
]
由于公司网站项目较大,且需要登陆权限的页面与不需要登陆权限的页面都比较多,通过数组去维护路由列表的方式随着页面逐渐增多会越来越难以应付,于是我们使用了官方Auth模块,接下来我们在本项目中添加Auth模块支持。
在使用Auth模块时,我们首先要确保项目中使用了Vuex,Nuxt内置了Vuex支持,因此不需要我们自己去安装依赖,但我们要如何激活Vuex呢,首先在项目根目录下必须存在store目录,同时,在store下创建任意名字的js或者ts文件,Nuxt会根据你命名的文件在Vuex里创建一个模块,如果该文件叫index,那么这个文件将作为根模块。
为何要先激活Vuex,因为Auth Module本质上就是Vuex的一个模块,同时,Auth Module必须跟axios一起搭配使用,首先安装模块:
yarn add @nuxtjs/auth @nuxtjs/axios
# OR
npm install @nuxtjs/auth @nuxtjs/axios
接下来,在nuxt.config.ts对其进行配置:
modules: [
'@nuxtjs/axios',
'@nuxtjs/auth'
],
auth: {
strategies: {
local: {
endpoints: {
login: {
// 登陆接口url
url: '',
method: 'post',
propertyName: false
},
// 登出接口url
logout: {
url: '',
method: 'post'
},
// 获取用户信息接口url
user: {
url: '',
method: 'get',
propertyName: false
}
},
tokenRequired: true,
tokenType: false
}
},
// 路由跳转配置
redirect: {
// login: '/first',
// logout: '/',
// callback: '/login',
// home: '/'
}
}
添加配置后,我们就可以使用对应的方法进行登陆登出。
登陆时:
try {
await this.$auth.loginWith('local', {
data: {
username: 'username'
password: 'password',
}
})
// do something on success
} catch (e) {
// do something on failure
}
登出时:
await this.$auth.logout()
由于Auth是Vuex的一个模块,且内置了不少状态,如loggedIn:用于判断用户是否登陆;user:用户信息对象。
在组件内我们可以这样访问:
computed: {
...mapState('auth', ['loggedIn', 'user'])
}
或者这样:
loggedIn() {
return this.$auth.loggedIn
},
user() {
return this.$auth.user
}
接下来,我们来看看如何为页面添加权限或者放开权限,有以下两种方式:
router: {
middleware: ['auth']
}
这样所有的页面都需要登陆权限,如果部分不需要登陆权限即可查看,那么可以在该页面下设置:
export default {
auth: false
}
export default {
middleware: 'auth'
}
则该页面需要登陆权限才能查看
至此,我们完成了路由鉴权设置,详细可以参照这里。
nuxt.config.ts添加端口配置,更多可以参见这里
server: {
port: 9995, // default: 3000
host: 'localhost' // default: localhost
}
如果我们仅仅想要使用CSS预处理的话,我们仅仅需要安装上对应的npm依赖包以及Webpack 加载器就行,参见这里,但考虑到预处理器有一些变量、mixin需要全局共用,我们可以添加上style-resources-module模块,这样在各个页面任意使用我们定义的东西。
首先安装模块
yarn add @nuxtjs/style-resources
# OR
npm install @nuxtjs/style-resources
接下来安装预处理依赖,本项目我们使用的是scss,因此安装:
yarn add sass-loader sass
# OR
npm install sass-loader sass
修改nuxt.config.ts文件
export default {
modules: [
'@nuxtjs/style-resources'
],
styleResources: {
scss: './assets/variables.scss'
}
}
更多配置可以看这里。
在项目实际使用过程中,我们常常不需要用到插件的所有东西,因此我们可以通过按需加载进行优化,在Nuxt项目中进行按需加载与Vue项目大同小异,以前是操作流程:
首先安装相应模块
yarn add babel-plugin-component -dev
# OR
npm install babel-plugin-component --save-dev
修改nuxt.config.ts配置
build: {
...other
// 按需引入element-ui
babel: {
plugins: [
[ "component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
},
}
修改plugins/element-ui.ts
import Vue from 'vue'
// 全局引用
// import Element from 'element-ui'
// Vue.use(Element, { locale })
// 按需引用
import {
Button,
Input,
} from 'element-ui'
import locale from 'element-ui/lib/locale/lang/en'
Vue.use(Button, { locale })
Vue.use(Input, { locale })
至此,完成了Nuxt项目对Element UI的按需引用
由于运营系统并不支持IE10以下浏览器,所以在判断浏览器版本后,在低版本浏览器添加升级提示会更加友善,以下是添加过程中的踩坑记录:
首先我们想到的是添加中间件,中间件能在进入页面之前执行,从而在其中判断浏览器的版本情况,在低版本浏览器下进行升级提示,过程如下:
在middleware目录下添加checkIE.ts文件,添加如下内容
export default function(context) {
const userAgent = process.server
? context.req.headers['user-agent']
: navigator.userAgent
const isIE = userAgent.includes('compatible') && userAgent.includes('MSIE') // 判断是否IE<11浏览器
const isIE11 = userAgent.includes('Trident') && userAgent.includes('rv:11.0')
if (isIE && !isIE11) {
alert('浏览器版本过低,请升级浏览器')
return context.redirect(
'https://support.dmeng.net/upgrade-your-browser.html'
)
}
}
然后在nuxt.config.ts添加配置:
router: {
middleware: ['auth', 'checkIE']
}
添加配置后运行项目后发现在IE浏览器下context.req.headers['user-agent']的值为:"Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"
通过这个值并没办法判断是否是IE浏览器以及IE版本,而navigator.userAgent的值为:"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; InfoPath.3)",则可以进行判断,但是如果直接直接使用这个值就会显示navigator未定义从而报错,因为服务端是没有navigator这个变量的,网上说添加process.client判断是否在浏览器端在取这个值就不会报错,关键是如果中间件貌似默认就是在服务端运行的,因此就一直不会取到navigator.userAgent值,只会取到我们判断不出是否IE浏览器的context.req.headers['user-agent']的值。
所以就只能换一种思路了,将判断的逻辑添加到html中,那么就需要修改html模板了,Nuxt可以在根目录下添加app.html覆盖默认模板,详情可参见这里[https://zh.nuxtjs.org/guide/views],
修改后的html模板如下:
<!DOCTYPE html>
<!--[if lt IE 10]>
<script>
alert('浏览器版本过低,请升级浏览器,建议使用谷歌浏览器');
window.location = 'https://support.dmeng.net/upgrade-your-browser.html'
</script>
<![endif]-->
<!--[if (gt IE 10)|!(IE)]><!-->
<html {{ HTML_ATTRS }}>
<head {{ HEAD_ATTRS }}>
{{ HEAD }}
</head>
<body {{ BODY_ATTRS }}>
{{ APP }}
</body>
<!--<![endif]-->
</html>
从而,就可以在低版本浏览器弹出提示,从而跳转到升级浏览器页面啦~
公司网站在前阵子被发现可以通过fiddler等抓包工具抓取到明文密码,容易引发账号盗取等安全问题,于是对前端关键数据进行加密显得很有必要。
目前网上的加密方案主要分为对称加密和非对称加密;对称加密在加密和加密使用的是同一个密钥,容易被破解;非对称加密需要两个密钥,分为公钥和私钥,公钥加密,私钥解密,安全性较高,因此决定使用非对称加密算法对数据进行加密。
RSA加密算法是非对称加密中的一种代表算法。
对极大整数做因数分解的难度决定了RSA算法的可靠性。换言之,对一极大整数做因数分解愈困难,RSA算法愈可靠。假如有人找到一种快速因数分解的算法的话,那么用RSA加密的信息的可靠性就会极度下降。但找到这样的算法的可能性是非常小的。今天只有短的RSA钥匙才可能被强力方式破解。到当前为止,世界上还没有任何可靠的攻击RSA算法的方式。只要其钥匙的长度足够长,用RSA加密的信息实际上是不能被破解的。
公钥私钥可以通过open ssl生成固定的公钥私钥,分别保存在前端跟后台,也可以在后台请求时生成密钥对,如下:
// 生成密钥对
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
keyPairGen.initialize(CACHE_SIZE);
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPublicKey publicKey = (RSAPublicKey)keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey)keyPair.getPrivate();
Map<String, Object> keyMap = new HashMap<String, Object>(2);
// 公钥
keyMap.put(PUBLIC_KEY, publicKey);
// 私钥
keyMap.put(PRIVATE_KEY, privateKey);
生成密钥后后端需要将公钥返回给前端,前端通过公钥进行加密,这里需要使用到一个前端库:jsencrypt,使用npm install jsencrypt --save
安装依赖即可,以下为前端加密代码:
const encrypt = new JSEncrypt()
encrypt.setPublicKey(publicKey)
const encryptedPwd = encrypt.encrypt(password)
接下来,将加密后的数据传递给后端,后端即可使用生成的密钥进行解密,解密代码如下:
public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey) throws Exception {
byte[] keyBytes = decodeBase64(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateK);
int inputLen = encryptedData.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
return decryptedData;
}
至此,完成了一个完整的前端加密,后端解密的流程,fiddler等抓包工具也就不能再抓取到网站的明文密码了。
完结撒花~
Chrome 80版本后,组件不再支持HTML Imports,而使用更符合发展趋势的ES modules,Polymer1和polymer2均使用的是HTML Imports,在新版浏览器打开会有很多问题,因此,需将原有项目升级至Polymer3,这里简要讲一下搭建过程。
Polymer拥有自己的脚手架,可以搭建属于自己的项目,首先我们需要全局安装polymer-cli,再来搭建项目,项目搭建可以参照这里,搭建类型分为元素项目和应用项目,元素项目一般是类似于UI库之类的会选择这种,而这里我们选择polymer-3-application,搭建应用项目,window使用脚手架会有比较多的问题,可以参照这里
热加载是提高前端开发效率的手段之一,因此我们首先为项目添加热加载功能,初始打算使用webpack的webpack-dev-server实现热加载,但是由于Polymer本身已经具备开发、编译、pwa支持甚至语法转换的一套逻辑,使用webpack需要我们全部重新整理一遍,因此在这个项目中我们暂时不添加webpack,而使用browser-sync,专为同步浏览器测试而生,配置也比较简单,首先安装依赖
npm install browser-sync --save-dev
其次修改package.json中的scripts语句:
"scripts": {
"start": "npm run watch | polymer serve",
"watch": "browser-sync start --files \"src/**/*.*, index-dev.html, *.js\"",
},
运行npm start后,我们还需要在首页html文件的body标签内尾部添加上以下语句:
<script id="__bs_script__">//<![CDATA[
document.write("<script async src='http://HOST:3000/browser-sync/browser-sync-client.js?v=2.26.7'><\/script>".replace("HOST", location.hostname));
//]]></script>
端口并不一定都是3000,大致是查看空闲端口,如果端口被使用,则往下顺延,诸如3001,3002...
现在,我们修改src下面的文件,index-dev文件,以及根目录下的js文件,都会触发浏览器刷新。
browser-sync的更多使用可以参见官网
添加eslint配置有利于约束项目代码,Polymer3本身搭建项目后的polymer.json中也有lint的配置,像这样:
"lint": {
"rules": [
"polymer-3"
]
}
但这个约束似乎不能自己添加扩展,而且也不能使用编辑器自动修改,于是我们还是使用了eslint,并使用了标准配置,添加依赖:
npm install --save-dev eslint babel-eslint eslint-config-standard eslint-plugin-standard eslint-plugin-promise eslint-plugin-import eslint-plugin-node
在根目录下添加.eslintrc.js,根据自己项目所需进行配置,以下是该项目的初始配置:
module.exports = {
"env": {
"browser": true,
"es6": true,
"node": true
},
"extends": [
"eslint:recommended",
"standard"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module",
"allowImportExportEverywhere": true // 动态导入
},
"rules": {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
// allow async-await
'generator-star-spacing': 'off',
'indent': ['error', 2],
'no-var': 'error',// 禁用var,用let和const代替
'no-restricted-imports': 'off'
}
};
同时,在package.json添加检测语句:
"scripts": {
"lint": "eslint --ext .js src/"
}
结合编辑器设置,我们可以检测并自动修正一些语法风格问题;
更多设置可以参考这里
pwa(Progressive Web App),渐进式Web应用,可以在不稳定的网络环境下,也能瞬间加载并展现网站,拥有平滑且沉浸式体验,同时可以安装到桌面,pwa是2016年Google提出的概念,Polymer本身也是Google团队出的框架,因此框架内置支持的很好,节省了很多额外配置。
其实pwa的配置走了一些弯路,一开始Polymer3官方配置文档说使用的是sw-precache,但对应的仓库却说不推荐使用,推荐使用Workbox,依照Workbox官方文档进行配置,一开始本只想设置为生产模式先使用,开发模式下不开启pwa,但在配置过程中没有达到对应效果,后来在同事提醒协助下,又重新使用了官方文档介绍的sw-precache,因为Polymer官方内置给了支持,配置起来也简便,有官方背书,使用起来也较为放心,而且配置生成的项目暂时来看也满足实际应用,就没有再捣鼓Workbox。
修改polymer.json配置:
"builds": [
{
"name": "es5-bundled",
"js": {
"compile": "es5",
"minify": true,
"transformModulesToAmd": true
},
"css": {
"minify": true
},
"html": {
"minify": true
},
"bundle": true,
"addServiceWorker": true
}
]
主要是设置addServiceWorker属性为true,然后在入口html文件添加代码注册service-worker,如下:
<head>
<script>
// Feature detect for service worker capability in the browser
if ('serviceWorker' in navigator) {
// Delay registering until page load
window.addEventListener('load', function() {
navigator.serviceWorker.register('service-worker.js');
});
}
</script>
</head>
在根目录下创建空的service-worker.js文件,该文件在build模式Polymer会根据配置填充内容。
这样,在build模式下,就可以看到Service worker已经在运行了。
更多的配置可以参见这里,以及这里
这里有一个pwa项目的原始搭建流程,可以看看。
Polymer有一个路由组件,通常可以结合iron-pages搭配使用,首先安装依赖:
npm install @polymer/app-route @polymer/iron-pages --save
在项目中启用路由
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js'
import '@polymer/app-route/app-location.js'
import '@polymer/app-route/app-route.js'
import '@polymer/iron-pages'
/**
* @customElement
* @polymer
*/
class OssPolymer3App extends PolymerElement {
static get template () {
return html`
<style>
:host {
display: block;
}
<app-location route="{{route}}"></app-location>
<app-route route="{{route}}" pattern="/:page" data="{{routeData}}" tail="{{subroute}}"></app-route>
<ul>
<li>
<a href="/home">Home</a>
</li>
<li>
<a href="/about">About</a>
</li>
</ul>
<iron-pages selected="[[page]]" attr-for-selected="name" selected-attribute="visible" fallback-selection="404">
<home-page name="home"></home-page>
<about-page name="about"></about-page>
</iron-pages>
`
}
static get properties () {
return {
page: {
type: String,
reflectToAttribute: true,
observer: '_pageChanged'
}
}
}
static get observers () {
return ['_routerChanged(routeData.page)']
}
_routerChanged (page) {
this.page = page || 'home'
}
_pageChanged (currentPage) {
switch (currentPage) {
case 'home':
import('../pages/home-page.js').then()
break
case 'about':
import('../pages/about-page.js').then()
break
default:
import('../pages/home-page.js').then()
break
}
}
}
window.customElements.define('oss-polymer3-app', OssPolymer3App)
上面可以实现路由懒加载,更多的使用有待以后在更丰富的使用场景中去挖掘,更多使用可以参照这里
一开始,以为Polymer的数据管理也是类似于vuex,但实际使用却发现不是这样,Polymer数据管理使用到了@polymer/app-storage,主要是通过localStorage和indexeddb去存储数据,从而对存储的数据进行监听,更多的使用详见官网,由于目前只是简单的搭建,更多的使用还待后续在使用过程中进行探索。
关于Polymer3的搭建大致就是这样,后续在使用过程中会继续进行完善修改,若有错误之处,还望指出。
完结撒花~
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.