kiinlam / kiinlam.github.io Goto Github PK
View Code? Open in Web Editor NEWMy Blog On GitHub Discussions
Home Page: https://github.com/kiinlam/kiinlam.github.io/discussions
License: Apache License 2.0
My Blog On GitHub Discussions
Home Page: https://github.com/kiinlam/kiinlam.github.io/discussions
License: Apache License 2.0
写于 2016-04-27
(注:最新版本gulp部分方法有变)
插件页面:http://gulpjs.com/plugins/,或者在npm
搜索gulpplugin
taoboa NPM: http://npm.taobao.org/
js目录下包含了压缩和未压缩的JavaScript文件,现在我们想要创建一个任务来压缩还没有被压缩的文件,我们需要先匹配目录下所有的JavaScript文件,然后排除后缀为.min.js的文件:
gulp.src(['js/**/*.js', '!js/**/*.min.js'])
监听文件变化执行回调
// 当文件变化时执行任务数组中的任务build
gulp.task('watch', function () {
gulp.watch('templates/*.tmpl.html', ['build']);
});
或者
// 当文件变化时执行回调函数,包含事件对象event
gulp.watch('templates/*.tmpl.html', function (event) {
console.log('Event type: ' + event.type); // added, changed, or deleted
console.log('Event path: ' + event.path); // The path of the modified file
});
gulp.watch()
返回一个watcher
对象,watcher
可以监听事件,或向watch
中添加文件。
// 监听change事件
var watcher = gulp.watch('templates/*.tmpl.html', ['build']);
watcher.on('change', function (event) {
console.log('Event type: ' + event.type); // added, changed, or deleted
console.log('Event path: ' + event.path); // The path of the modified file
});
可监听事件
Watcher
对象一些可以调用的方法:
参数:任务名称,执行函数
gulp.task('greet', function () {
console.log('Hello world!');
});
// 运行
gulp greet // Hello world
// 运行gulp build时,同时无序执行css、js、imgs三个任务
gulp.task('build', ['css', 'js', 'imgs']);
// 运行gulp css时,先执行greet任务,结束后执行回调函数
gulp.task('css', ['greet'], function () {
// Deal with CSS here
});
// 运行gulp时,执行default任务
gulp.task('default', function () {
// Your default task
});
gulp.task('copy', function () {
gulp.src('src/fonts/**/*')
.pipe(gulp.dest('dist/fonts/'))
})
用git初始化目录,该目录称为工作目录
git init
查看工作目录状态
git status
添加文件到工作目录暂存区,小数点表示所有文件
git add <.|file|directory>
提交暂存区里的文件到本地仓库
git commit -m "<commmit message>"
比较工作目录与最后一次提交(HEAD)
git diff
比较工作目录与指定提交
git diff <commit-hash>
比较指定文件
git diff <file-name>
比较最近两次commit
git diff HEAD~2 HEAD
指定文件比较的外部编辑器
git difftool
比较暂存区与最后一次提交
git diff --staged
查看commit历史记录
git log
git log --oneline --decorate //查看各个分支当前所指向的对象
git log --grep="<string>" //筛选包含指定串的commit
git log -p -n 2 //查看完整详情,-n指定数量
git log -g master //查看带有引用记录的日志
git log --graph --pretty=oneline --abbrev-commit //查看分支合并图
自定义log格式
git log --pretty=format:"%h %an %ar - %s"
通过帮助文档查看更多log详情
git log --help
查看包含比较信息的commit详情,默认为 HEAD
commit
git show
git show <commit-hash> //查看指定commit详情
git show HEAD@{5} //查看5次前的commit记录
git show master@{yesterday} //查看master分支昨天的提交
git show HEAD@{2.months.ago} //查看2个月的记录
查看引用日志,引用日志只在本地仓库存在
git reflog
关联当前目录到远程仓库,通常使用 origin
作为别名
git remote add <ref-name> <remote-url>
克隆远程仓库到本地目录,别名自动设置为 origin
,目录名与远程仓库的目录名一致
git clone <remote-url>
克隆远程仓库并指定本地目录名
git clone <url> <foldname>
将本地所有commit推送到远程仓库,默认分支名为 master
git push <ref-name> <branch-name>
拉取远程库并自动合并
git pull <ref-name> <branch-name>
拉取远程库但不合并,拉取的文件将下载到独立分支,命名格式为
remotes/<remote-name>/<remote-branch-name>
git fetch <ref-name>
推送分支上的commit
git push <remote_name> <branch_name>
取消文件暂存,不改变工作目录
git reset HEAD <.|files|directories>
撤销最后一次commit,返回前一次commit的状态,不改变工作目录
git reset HEAD^
将最后一次HEAD commit指向给定提交,不改变暂存与工作目录
git reset --soft HEAD^
还原到前一次commit,会改变工作目录
git reset --hard HEAD^
撤销工作目录的修改,还原到最后一次commit
git reset --hard HEAD <.|files|directories>
撤销指定提交,并创建新的提交来应用变更
git revert HEAD...HEAD~2
将fetch下来的分支合并
git merge remotes/origin/master
用外部工具进行合并
git mergetool
直接使用本地文件解决冲突
git checkout --ours <file-name>
直接使用远程文件解决冲突
git checkout --theirs <file-name>
使用变基来移植分支,branch上的提交被依次应用到master上
git rebase master branch
git checkout master
git merge branch
合并指定commit
git cherry-pick <hash-id|ref>
git cherry-pick --abort //放弃合并
git cherry-pick --continue //出现冲突时需要走冲突解决流程,再继续
创建分支
git branch dev
git branch <branch-name> <starting-commit> //基于指定分支的最后一次提交创建分支
切换分支
git checkout <branch-name>
git checkout -m dev //检出分支同时合并本地修改
git checkout -b dev //新建并切换分支
git checkout -b dev origin/dev //从远程检出分支
列出分支
git branch
git branch -r //列出远程分支
git branch -av //列出分支,包含远程分支和commit信息
git show-branch //分支详情
git show-branch -a
git show-branch -r
删除分支
git branch -d <branch-name>
git branch -D <branch-name> // 强制删除分支
git rm test.txt
git rm --cached test.txt //仅从暂存区删除
修改最后一次commit消息
git commit --amend
进入rebase交互模式,使用编辑器修改commit记录
git rebase --interactive --root
git stash //保存当前工作区状态
git stash list //查看已保存的工作区状态
git stash apply stash@{0} //恢复指定工作区状态
git stash drop stash@{0} //删除指定工作区状态
git stash pop //恢复并删除工作区
git config --global user.name "Your Name"
git config --global user.email "[email protected]"
# 加上-u参数,将远程仓库分支master关联起来
git push -u origin master
# 再次推送
git push origin master
git remote
git remote -v
git remote show
git remote show <remote-name>
git push <remote-name> <branch-name>
git push origin dev
git push
git branch --set-upstream dev origin/dev
git remote set-url origin <NEW_URL>
git tag tagname //添加标签
git tag //查看标签
git tag v0.9 <hash-id> //在指定commit id上打标签
git show tagname //查看标签信息
git tag -a v0.1 -m "version 0.1 released" <hash-id> //-a 标签名, -m 标签说明
git tag -d tagname //删除标签
git push origin tagname //推送指定标签到远程仓库
git push origin --tags //推送全部标签到远程仓库
git push origin :refs/tags/tagname //删除远程仓库上本地已删除的标签
git config --global color.ui true
git config --global alias.st status
git config --global alias.ci commit
# 使用字符串方式
# git unstage test.py --> git reset HEAD test.py
git config --global alias.unstage 'reset HEAD'
# 显示最后一次提交信息
git config --global alias.last 'log -1'
# 格式化最后一次的提交信息
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
# 配置Git的时候,加上--global是针对当前用户起作用的,如果不加,那只针对当前的仓库起作用。
# 每个仓库的Git配置文件都放在.git/config文件中
# 当前用户的Git配置文件放在用户主目录下的一个隐藏文件.gitconfig中
AssemblyScript 是一个 TypeScript 到 WebAssembly 的编译器。由 Microsoft 开发的 TypeScript 为 JavaScript 添加了类型。它已经非常流行了,但就算用户不怎么熟悉 TS,AssemblyScript 也只支持 TypeScript 功能的一个有限子集,因此不需要花很长时间就能上手。
因为它与 JavaScript 非常相似,所以 AssemblyScript 使 Web 开发人员可以轻松地将 WebAssembly 整合到他们的网站中,而不必使用某种完全不同的语言。
相关缩写说明:
assemblyscript
依赖,它提供了一个用来创建AS项目的脚手架,同时提供将AS代码转换为WebAssembly代码的功能。asinit
命令创建脚手架文件,项目已经准备好了。assembly
目录下开始编写AssemblyScript 源代码。npm init
npm install --save-dev assemblyscript
npx asinit .
现在项目目录下多了一些文件夹及目录
add
,该文件将以模块形式导出add
函数,以便在JS中调用:export function add(a: i32, b: i32): i32 {
return a + b;
}
build 目录:AS构建后生成的WA代码,就存放在此,编译后会出现以下文件:
optimized.wasm
optimized.wasm.map
optimized.wat
untouched.wasm
untouched.wasm.map
untouched.wat
optimized为优化版本,untouched为普通版本;
.wasm:二进制文件
.wasm.map:源码映射文件
.wat:二进制文件的文本表示形式
index.js 文件:用户Nodejs环境,读取.wasm文件并导出模块:
const fs = require("fs");
const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/optimized.wasm"));
const imports = {
env: {
abort(_msg, _file, line, column) {
console.error("abort called at index.ts:" + line + ":" + column);
}
}
};
Object.defineProperty(module, "exports", {
get: () => new WebAssembly.Instance(compiled, imports).exports
});
"asbuild:untouched": "asc assembly/index.ts -b build/untouched.wasm -t build/untouched.wat --sourceMap --validate --debug",
"asbuild:optimized": "asc assembly/index.ts -b build/optimized.wasm -t build/optimized.wat --sourceMap --validate --optimize",
"asbuild": "npm run asbuild:untouched && npm run asbuild:optimized"
脚手架已经创建了示例函数,可以直接使用,接下来需要构建WA文件:
npm run asbuild
编译后的文件存放到了build目录下。
如果是用于Nodejs环境,现在已经可用了,脚手架创建的index.js就是入口。
可以进入nodejs测试:
$ node
> const add = require('./index').add;
undefined
> add(3, 5)
8
创建一个新的demo.js文件:
(async () => {
const importObject = {
env: {
abort(_msg, _file, line, column) {
console.error("abort called at index.ts:" + line + ":" + column);
}
}
};
const module = await WebAssembly.instantiateStreaming(
fetch("build/optimized.wasm"),
importObject
);
const add = module.instance.exports.add;
const result = document.querySelector("#result");
document.querySelector("#add").addEventListener("submit", event => {
event.preventDefault();
result.innerText = "";
const number = event.target.elements.number.value;
result.innerText = `${number} + ${number} = ${add(number, number)}.`;
});
})();
这个脚本会获取输入框里的数字,然后调用add函数进行计算。
要加载 WebAssembly 模块有多种方法,但最有效的方法是使用 WebAssembly.instantiateStreaming 函数,以流方式编译和实例化这些模块。请注意,我们需要提供一个中止函数,如果断言失败就会调用这个中止函数。
创建一个新的index.html文件:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<form id="add">
<label for="number">Enter a number:</label>
<input name="number" type="number" />
<button type="submit">Submit</button>
</form>
<p id="result"></p>
<script src="demo.js"></script>
</body>
</html>
这个页面会提供一个输入框用于输入数字,点击submit后,会在下方显示计算结果。
demo.js需要通过fetch方法来加载WA文件,我们需要一个简易的网络服务器。
npm install --save-dev static-server
然后在package.json添加脚本:
{
"scripts": {
"serve-demo": "static-server"
}
}
运行命令启动服务器:
npm run serve-demo
在浏览器打开URL,输入数字,点submit。
到此,我们完成了一个AS项目。
安装onchange依赖,监控assembly目录的修改,自动运行构建:
npm install --save-dev onchange
在package.json添加监视脚本:
{
"scripts": {
"asbuild:watch": "onchange -i 'assembly/**/*' -- npm run asbuild"
}
}
现在运行watch命令就可以自动构建,不需要每次修改都重新运行构建命令:
npm run asbuild:watch
写于2015-05-29
通过分析flux-todomvc源码,学习如何通过react构建web程序,了解编写react应用程序的一般步骤,同时掌握Flux的单向数据流动架构**
react一个最吸引我的特性是组件,它是模块化的,所有的组件是独立的,又可以通过嵌套来构建更大型的组件,一个个小组件经过层层组装,最终形成web应用程序,它让我开始重新思考如何去构建大型的web应用程序。
Flux是一个**而非框架,强调数据自上而下传递的单向流动理念,已经有很多种实现,比较有名气的是RefluxJs。单向流动让数据走向更清晰:用户UI交互触发事件,事件回调执行Action,并将所有的变化数据作为参数payload传递进去,各种Stores从payload中取出自己所需的数据进行更新,最终重新渲染发生变化的DOM结点。
以下简要分析 react + flux 实现的todomvc源码,主要研究其构建方式与单向数据流动**,对具体代码编写不做深入分析。
getInitialStates
中设置初始值;componentDidMount
中添加store监听指定事件_onChange
;componentWillUnmount
中清除store监听的事件及回调;render
中将state赋值给各个component的prop,子组件包含Header
组件、MainSection
组件、Footer
组件;_onChange
事件回调方法,触发该回调时,执行setState
,更新UI。Store只在顶级的组件中接入,子组件通过prop层层传递,所有子组件不直接从Store里检索数据,这是一种很好的设计方式,有助降低数据耦合度。
render
中包含TodoTextInput
组件,并将自身的_onSave
方法作为TodoTextInput
组件的prop;_onSave
方法,在调用时,执行相应Action方法create
。Header
组件统一管理_onSave
方法,该方法将提供给子组件调用。
基础的prop验证
在render
中分类处理,数据为空时,返回null,隐藏元素,有数据时,显示列表内容,包含TodoItem
组件,同时提供一个checkbox控件用于设置是否全部完成;
添加切换设置全部完成的回调方法_onToggleCompleteAll
,在调用时,执行相应Action方法toggleCompleteAll
。
基础的prop验证
在render
中计算未完成项目数量,并根据是否存在已完成项来显示清除全部完成项按钮,按钮绑定onClick
事件;
添加清除全部完成项的回调方法_onClearCompletedClick
,在调用时,执行相应Action方法destroyCompleted
。
基础的prop验证
在getInitialStates
中设置初始值;
在render
中根据是否处于编辑状态,显示不同的元素,编辑状态下,包含了TodoTextInput
组件,并设置了onSave
和value
的prop,非编辑状态下,显示切换是否完成的按钮,绑定了onChange
事件,显示项目文字内容,绑定了onDoubleClick
事件,显示删除按钮,绑定了onClick
事件;
添加切换是否完成的回调方法_onToggleComplete
,在调用时,执行相应Action方法toggleComplete
;
添加双击文字时的回调方法_onDoubleClick
,在调用时,通过setState
设置当前项目为编辑状态;
添加触发保存时的回调方法_onSave
,在调用时,执行相应Action方法updateText
,并通过设置setState
退出编辑状态;
添加删除时的回调方法_onDestroyClick
,在调用时,执行相应Action方法destroy
;
基础的prop验证
className
、id
、placeholder
、value
为字符串;onSave
方法在getInitialStates
中设置初始值;
在render
中返回输入文本框,绑定多个事件onBlur
、onChange
、onKeyDown
;
添加失去焦点时的回调方法_save
,在调用时,执行父级元素提供的onSave
方法,并调用setState清空文字;
添加内容变化时的回调方法_onChange
,在调用时,调用setState更新虚拟DOM并刷新DOM元素;
添加按下键盘时的回调方法onKeyDown
,在调用时,检测是否按下回车键以执行onSave
方法;
TodoTextInput组件仅依赖于父级通过prop传递下来的数据与方法,不直接与Action
、Store
进行操作
AppDispatcher
发出一个具体的action(携带数据payload
),并设置payload
的actionType
为action对应的类型。TodoActions
中的具体方法中触发)。AppDispatcher
既分发actions,又是actions的接收者,在接到actions后负责调用先前注册的Action管理回调方法。
Action管理回调方法中根据actionType
来完成不同的操作,并在操作结束时触发TodoStore
绑定的事件。
react组件通过用户UI交互触发event,event回调函数被执行,在这些函数中调用Action对象对应的方法,发出action,事件分发器接收到action后,做出相应处理,然后触发Store改变事件,通过setState重新渲染DOM元素。
以TodoMVC为例,分两个阶段完成该应用程序的设计。
初始化阶段:
change
事件,发生变化时,更新组件的数据;交互阶段(以新增一个todo项为例):
actionType
进行处理;change
事件;交互阶段的数据环
Event --> Action --> Dispatcher(携带数据) --> Store --> View --> Event
Flux主要包括三部分:Dispatcher、Store和Views(React组件),与传统的MVC(model-View-Controller)不同。Flux中的Dispatcher以controller-view的形式存在,所有数据操作都在Dispatcher的回调中进行,并反映到View上。view通常处于应用的顶层,它从Stores中获取数据,同时将这些数据传递给它的后代节点。
写于 2016-10-11
gulp-changed
https://github.com/sindresorhus/gulp-changed
Only pass through changed files
npm install --save-dev gulp-changed
gulp-cached
https://github.com/contra/gulp-cached
in-memory file cache, not for operation on sets of files
gulp-remember
https://github.com/ahaurw01/gulp-remember
pairs nicely with gulp-cached
gulp-newer
https://github.com/tschaub/gulp-newer
npm install gulp-newer --save-dev
pass through newer source files only, supports many:1 source:dest
gulp-load-plugins
http://npm.taobao.org/package/gulp-load-plugins
npm install -–save-dev gulp-load-plugins
// 自动加载package.json中的插件,并将插件作为plugins的属性
// 属性名为插件的驼峰式命名,gulp-ruby-sass将被加载成plugins.rubySass
// 仅在回调函数中使用到时才加载(lazyLoad)
var gulp = require('gulp'),
gulpLoadPlugins = require('gulp-load-plugins'),
plugins = gulpLoadPlugins();
gulp.task('js', function () {
return gulp.src('js/*.js')
.pipe(plugins.jshint())
.pipe(plugins.jshint.reporter('default'))
.pipe(plugins.uglify())
.pipe(plugins.concat('app.js'))
.pipe(gulp.dest('build'));
});
gulp-imagemin
http://npm.taobao.org/package/gulp-imagemin
npm install --save-dev gulp-imagemin
const gulp = require('gulp');
const imagemin = require('gulp-imagemin');
const pngquant = require('imagemin-pngquant');
gulp.task('default', () => {
return gulp.src('src/images/*')
.pipe(imagemin({
progressive: true,
svgoPlugins: [{removeViewBox: false}],
use: [pngquant()]
}))
.pipe(gulp.dest('dist/images'));
});
gulp-autoprefixer
npm install --save-dev gulp-autoprefixer
http://npm.taobao.org/package/gulp-autoprefixer
var gulp = require('gulp');
var autoprefixer = require('gulp-autoprefixer');
gulp.task('default', function () {
return gulp.src('src/app.css')
.pipe(autoprefixer({
browsers: ['last 2 versions'],
cascade: false
}))
.pipe(gulp.dest('dist'));
});
gulp-cssnano
http://npm.taobao.org/package/gulp-cssnano
npm install --save-dev gulp-cssnano
var gulp = require('gulp');
var cssnano = require('gulp-cssnano');
gulp.task('default', function() {
return gulp.src('./main.css')
.pipe(cssnano())
.pipe(gulp.dest('./out'));
});
gulp-minify-css
gulp-ruby-sass
http://npm.taobao.org/package/gulp-ruby-sass
npm install --save-dev gulp-ruby-sass
var gulp = require('gulp');
var sass = require('gulp-ruby-sass');
gulp.task('sass', function () {
return sass('source/file.scss')
.on('error', sass.logError)
.pipe(gulp.dest('result'));
});
gulp-uglify
http://npm.taobao.org/package/gulp-uglify
npm install --save-dev gulp-uglify
var gulp = require('gulp'),
uglify = require('gulp-uglify');
gulp.task('minify', function () {
gulp.src('js/app.js')
.pipe(uglify())
.pipe(gulp.dest('build'))
});
gulp-jshint
http://npm.taobao.org/package/gulp-jshint
npm install --save-dev gulp-jshint
gulp-concat
http://npm.taobao.org/package/gulp-concat
npm install --save-dev gulp-concat
var concat = require('gulp-concat');
gulp.task('scripts', function() {
return gulp.src('./lib/*.js')
.pipe(concat('all.js'))
.pipe(gulp.dest('./dist/'));
});
指定顺序
var concat = require('gulp-concat');
gulp.task('scripts', function() {
return gulp.src(['./lib/file3.js', './lib/file1.js', './lib/file2.js'])
.pipe(concat('all.js'))
.pipe(gulp.dest('./dist/'));
});
gulp-livereload
http://npm.taobao.org/package/gulp-livereload
livereload需要浏览器扩展支持
// 监听文件的变化,生成css,重新加载网页
var gulp = require('gulp'),
less = require('gulp-less'),
livereload = require('gulp-livereload'),
watch = require('gulp-watch');
gulp.task('less', function() {
gulp.src('less/*.less')
.pipe(watch())
.pipe(less())
.pipe(gulp.dest('css'))
.pipe(livereload());
});
browser-sync
http://npm.taobao.org/package/browser-sync
npm install --save-dev browser-sync
browser-sync
与LiveReload
非常相似,但是它有更多的功能。改变代码时,BrowserSync
会重新加载页面,如果是css文件,则直接添加进css中,页面并不需要再次刷新。在开发单页面应用时尤其高效。BrowserSync
也可以在不同浏览器之间同步点击翻页、表单操作、滚动位置。
var gulp = require('gulp'),
browserSync = require('browser-sync');
gulp.task('browser-sync', function () {
var files = [
'app/**/*.html',
'app/assets/css/**/*.css',
'app/assets/imgs/**/*.png',
'app/assets/js/**/*.js'
];
browserSync.init(files, {
server: {
baseDir: './app'
}
});
});
gulp-usemin
npm install --save-dev gulp-usemin
http://npm.taobao.org/package/gulp-usemin
https://github.com/zont/gulp-usemin
var usemin = require('gulp-usemin');
var uglify = require('gulp-uglify');
var minifyHtml = require('gulp-minify-html');
var minifyCss = require('gulp-minify-css');
var rev = require('gulp-rev');
gulp.task('usemin', function() {
return gulp.src('./*.html')
.pipe(usemin({
css: [ rev() ],
html: [ minifyHtml({ empty: true }) ],
js: [ uglify(), rev() ],
inlinejs: [ uglify() ],
inlinecss: [ minifyCss(), 'concat' ]
}))
.pipe(gulp.dest('build/'));
});
gulp-watch-path
npm install --save-dev gulp-watch-path
http://npm.taobao.org/package/gulp-watch-path
gulp.watch('src/**/*', function (event) {
var paths = require('gulp-watch-path')(event, 'src/', 'dist/', 'node');
console.log(paths)
/*
paths {srcPath: 'src/file.js',
srcDir: 'src/',
distPath: 'dist/file.node',
distDir: 'dist/',
srcFilename: 'file.js',
distFilename: 'file.node' }
*/
gulp.src(paths.srcPath)
.pipe(uglify())
.pipe(gulp.dest(paths.distDir))
})
stream-combiner2
http://npm.taobao.org/package/stream-combiner2
var Combine = require('stream-combiner')
var es = require('event-stream')
Combine( // connect streams together with `pipe`
process.openStdin(), // open stdin
es.split(), // split stream to break on newlines
es.map(function (data, callback) { // turn this async function into a stream
var repr = inspect(JSON.parse(data)) // render it nicely
callback(null, repr)
}),
process.stdout // pipe it to stdout !
)
gulp-sourcemaps
http://npm.taobao.org/package/gulp-sourcemaps
var gulp = require('gulp');
var plugin1 = require('gulp-plugin1');
var plugin2 = require('gulp-plugin2');
var sourcemaps = require('gulp-sourcemaps');
gulp.task('javascript', function() {
gulp.src('src/**/*.js')
.pipe(sourcemaps.init())
.pipe(plugin1())
.pipe(plugin2())
.pipe(sourcemaps.write())
.pipe(gulp.dest('dist'));
});
GitHub提供了Discussions讨论区,以后,博客内容写到Discussions。
https://github.com/kiinlam/blog/discussions
国内访问慢或资源加载不了,建议使用代理。
完全免费,页面有广告,唯一收入来源,不影响使用体验,不建议屏蔽。
高级特性:
SCSS
,样式重置可选择使用Normalize.css
CoffeeScript
,Babel JSX
,Type Script
,Vue
,React
,Preact
<script>
标签属性attribute
页面有广告,不影响使用体验。
高级特性:
Open bin...
来选择Console
窗口Markdown
,Jade
,并提供转换为HTML
功能Less
,Myth
,Sass
,SCSS
,Stylus
,并提供转换为CSS
功能ES6 / Babel
,JSX
,CoffeeScript
,Traceur
,TypeScript
,Processing
,LiveScript
,ClojureScript
,并提供转换为原生JavaScript
功能Sumlime
快捷键升级为付费用户:
平台特色
markdown
语法创建文章,文章可嵌入代码集高级特性:
Haml
,Markdown
,Slim
,Pug
Less
,PostCSS
,Sass
,SCSS
,Stylus
,样式重置可选择使用Normalize.css
,Reset.css
,前缀生成可选择Autoprefixer
,Prefixfree
Babel
,TypeScript
,CoffeeScript
,LiveScript
升级为付费用户/团队:
codesandbox更新像是在线IDE,可配置首选项,与GitHub、ZEIT集成,以项目为单位,免费用户可创建50个项目。
这里只涉及与代码分享相关的功能。
高级特性:
package.json
,.babelrc
,.prettierrc
,sandbox.config.json
升级为付费用户:
codepen 只能分享最新代码,要比较代码变更情况,需要先Fork再编辑。
codesandbox 接近一个完整的IDE,功能强大,可创建公开的多文件项目,适合用在各种框架配置教程中。
JSFiddle、JS Bin 更适合用于在线分享、学习、制作demo、测试代码。
JS Bin 支持代码下载,保存到Gist。
JSFiddle 提供了一些开箱即用的功能,无需复杂的配置,支持代码提示。
写于 2016-10-11
npm init
npm install -g gulp
npm install --save-dev gulp
npm install
gulpfile.js
gulp [taskname]
gulpfile.js
var gulp = require('gulp');
var coffee = require('gulp-coffee');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var imagemin = require('gulp-imagemin');
var sourcemaps = require('gulp-sourcemaps');
var del = require('del');
var paths = {
scripts: ['client/js/**/*.coffee', '!client/external/**/*.coffee'],
images: 'client/img/**/*'
};
// Not all tasks need to use streams
// A gulpfile is just another node program and you can use any package available on npm
gulp.task('clean', function() {
// You can use multiple globbing patterns as you would with `gulp.src`
return del(['build']);
});
gulp.task('scripts', ['clean'], function() {
// Minify and copy all JavaScript (except vendor scripts)
// with sourcemaps all the way down
return gulp.src(paths.scripts)
.pipe(sourcemaps.init())
.pipe(coffee())
.pipe(uglify())
.pipe(concat('all.min.js'))
.pipe(sourcemaps.write())
.pipe(gulp.dest('build/js'));
});
// Copy all static images
gulp.task('images', ['clean'], function() {
return gulp.src(paths.images)
// Pass in options to the task
.pipe(imagemin({optimizationLevel: 5}))
.pipe(gulp.dest('build/img'));
});
// Rerun the task when a file changes
gulp.task('watch', function() {
gulp.watch(paths.scripts, ['scripts']);
gulp.watch(paths.images, ['images']);
});
// The default task (called when you run `gulp` from cli)
gulp.task('default', ['watch', 'scripts', 'images']);
http://slproweb.com/products/Win32OpenSSL.html
在环境变量中新建变量名:OPENSSL_HOME
,变量值为openssl安装位置,如:C:\OpenSSL-Win64\bin
。
在Path变量下添加值:%OPENSSL_HOME%
。
在nginx安装目录中创建ssl文件夹用于存放证书
以管理员身份进入命令行模式,进入ssl文件夹
在命令行中执行命令:
openssl genrsa -des3 -out name.key 1024 #name为文件名,可自定义
输入密码后,再次重复输入确认密码。记住此密码,后面会用到。
在命令行中执行命令:
openssl req -new -key name.key -out name.csr #name为上一步的文件名
执行上述命令后,需要输入信息。其中的Common Name
,需要输入要使用https访问的域名。
其他信息根据提示,随意输入。
完成后可得到两个文件,文件名分别为name.key
、name.csr
为避免启动nginx要求输入密码,需要去除证书密码
复制name.key
并重命名为name.key.org
在命令行中执行命令:
openssl rsa -in name.key.org -out name.key
此时提示输入创建私钥时的密码
在命令行中执行此命令:
openssl x509 -req -days 365 -in name.csr -signkey name.key -out name.crt
完成后可得到4个文件,配置SSL时需要用到crt、key两个文件
找到HTTPS server一节:
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
复制一份配置代码,然后修改为:
# HTTPS server
server {
listen 443 ssl;
server_name www.name.com;
ssl_certificate C:/nginx/ssl/name.crt;
ssl_certificate_key C:/nginx/ssl/name.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
root C:/nginx/root;
index index.html index.htm;
}
}
写于 2015-06-19
客户端(浏览器)通过ajax请求第三方服务器,会遇到跨域问题,可通过nodejs服务器做中介来解决跨域问题。
客户端发起请求到nodejs服务器,nodejs收到后请求第三方服务器取得数据,返回给客户端。
client ajax --> nodejs recived --> nodejs send request --> respone to client
var http = require('http');
// 创建http服务
var app = http.createServer(function (req, res) {
// 查询本机ip
var sreq = http.request({
host: 'sneezryworks.sinaapp.com', // 目标主机
path: '/ip.php', // 目标路径
method: req.method // 请求方式
}, function(sres){
sres.pipe(res);
sres.on('end', function(){
console.log('done');
});
});
if (/POST|PUT/i.test(req.method)) {
req.pipe(sreq);
} else {
sreq.end();
}
});
// 访问127.0.0.1:3001查看效果
app.listen(3001);
console.log('server started on 127.0.0.1:3001');
var http = require('http');
// 创建http服务
var app = http.createServer(function (req, res) {
// 使用了superagent来发起请求
var superagent = require('superagent');
// 查询本机ip,这里需要根据实际情况选择get还是post
var sreq = superagent.get('http://sneezryworks.sinaapp.com/ip.php');
sreq.pipe(res);
sreq.on('end', function(){
console.log('done');
});
});
// 访问127.0.0.1:3001查看效果
app.listen(3001);
console.log('server started on 127.0.0.1:3001');
var express = require('express');
var app = express();
app.get('/', function (req, res) {
// 使用了superagent来发起请求
var superagent = require('superagent');
// 查询本机ip,这里需要根据实际情况选择get还是post
var sreq = superagent.get('http://sneezryworks.sinaapp.com/ip.php');
sreq.pipe(res);
sreq.on('end', function(){
console.log('done');
});
});
app.listen(3001);
console.log('Express started on 127.0.0.1:3001');
如果需要对第三方服务器返回的内容做处理,可以在sreq
的data
事件的回调方法中进行,而不是直接使用pipe
方式。
1、在 VSCode 中安装 ESLint 插件。
2、在 [文件 > 首选项 > 设置]中,添加以下配置:
{
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript"
]
}
3、 package.json
增加脚本命令
{
"scripts": {
"eslint": "eslint src --ext .ts"
}
}
4、安装使用 AlloyTeam 的 ESLint 配置
npm install --save-dev eslint typescript @typescript-eslint/eslint-plugin [email protected]
在项目根目录下创建 .eslintrc.js
,并将以下内容复制到文件中:
module.exports = {
extends: [
'eslint-config-alloy/typescript',
],
globals: {
// 这里填入你的项目需要的全局变量
// 这里值为 false 表示这个全局变量不允许被重新赋值,比如:
//
// jQuery: false,
// $: false
},
rules: {
// 这里填入你的项目需要的个性化配置,比如:
//
// // 一个缩进必须用两个空格替代
// 'indent': [
// 'error',
// 2,
// {
// SwitchCase: 1,
// flatTernaryExpressions: true
// }
// ]
// // 一个缩进必须用两个空格替代
// '@typescript-eslint/indent': [
// 'error',
// 2,
// {
// SwitchCase: 1,
// flatTernaryExpressions: true
// }
// ]
}
};
5、增加 tsx
支持
需要增加安装 eslint-plugin-react
npm install --save-dev eslint-plugin-react
package.json
中的 script.eslint
添加 .tsx
后缀
{
"scripts": {
"eslint": "eslint src --ext .ts,.tsx"
}
}
VSCode 的配置中新增 typescriptreact
检查
{
"eslint.validate": [
"javascript",
"javascriptreact",
"vue",
"typescript",
"typescriptreact"
]
}
修改 .eslintrc.js
中的 extends
项:
extends: [
'eslint-config-alloy/react',
'eslint-config-alloy/typescript',
]
函数执行时创建的局部变量能被返回到外部作用域的嵌套函数所访问的特性。
写于 2015-11-27
doSomething().then(function () {
return doSomethingElse();
}).then(finalHandler);
doSomethin().then(functiuoin () {
doSomethingElse();
}).then(finalHandler);
doSomething().then(doSomethingElse())
.then(finalHandler);
doSomething().then(doSomethingElse)
.then(finalHandler);
doSomething().then(function () {
return doSomethingElse();
}).then(finalHandler);
答案:
doSomething
|-----------------|
doSomethingElse(undefined)
|------------------|
finalHandler(resultOfDoSomethingElse)
|------------------|
doSomething().then(function () {
doSomethingElse();
}).then(finalHandler);
答案:
doSomething
|-----------------|
doSomethingElse(undefined)
|------------------|
finalHandler(undefined)
|------------------|
doSomething().then(doSomethingElse())
.then(finalHandler);
答案
doSomething
|-----------------|
doSomethingElse(undefined)
|---------------------------------|
finalHandler(resultOfDoSomething)
|------------------|
doSomething().then(doSomethingElse)
.then(finalHandler);
答案
doSomething
|-----------------|
doSomethingElse(resultOfDoSomething)
|------------------|
finalHandler(resultOfDoSomethingElse)
|------------------|
Promise.resolve({
'then':function(resolve, reject){
setTimeout(function(){
if(Math.random()*10 > 5) {
resolve(1);
} else {
reject(0);
}
},1000);
}
}).then(function(result){
console.log(result);
}).catch(function(error){
console.error(error);
})
通过 Promise.resolve(Thenable对象)
形式将 Thenable对象
转为 Promise对象
。
重点在于创建具有 then
方法的对象,方法参数与 Promise
的 then
方法一致,在确定时执行 resolve
方法,拒绝时调用 reject
方法。
Thenable主要是用于 Promise类库 之间的转换
题目如下:
var b = 10;
(function b(){
b = 20
console.log(b)
})()
问:控制台打印了什么?
解释:
使用function
声明函数时,可以给一个可选的函数名称标识符,如果提供了,实际上是声明了一个变量并赋值给该变量。函数名字只存在于函数体中,指代函数本身,成为函数局部作用域内的一个局部变量,该名字被绑定到函数对象上,不可修改。
举例:
var c = function d(){
console.log(c === d) // => true
}
c() // => 打印函数定义
d() // => Error: d is not defined。 函数d只在函数体内有定义
回到开头的题目:
立即执行函数内部的b
,指代函数本身,不可修改,重新赋值没有效果,因此,控制台打印的是函数定义本身。
axios有个interceptor
的请求拦截api,官方文档的示例代码如下:
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
它可以设置两个函数参数fulfilled
和rejected
,分别对应resolve
与reject
两种情况。
奇怪的是,不管如何尝试抛出错误或返回Promise.reject()
,它永远都不会触发rejected
处理函数。
查看源码后发现,原来在promise
执行时,它的状态已经是fulfilled
,也就是说,请求拦截永远不会触发rejected
函数。
如果在fulfilled
函数里引发了错误,由于源码采用的是then(fulfilled, rejected)
的做法,而不是then(fulfilled).catch(rejected)
,导致这个抛出的错误不能被rejected
函数接收,只能被后续链式调用的响应拦截接收,或者继续向上抛出。
如果采用promise.then().catch()
做法,那么,then
方法里的错误是可以被后面的catch
捕获的。
比较如下两个例子就知道了:
// eg1:`then`方法里返回的reject不会被捕获
var promise1 = Promise.resolve('p1')
.then(v=>{
console.log('done:', v); // done: p1
return Promise.reject('p1 reject') // Uncaught (in promise) p1 reject
},
e => console.log('error:', e) // never execute
)
// eg2: `then`方法里返回的reject被`catch`方法捕获
var promise2 = Promise.resolve('p2')
.then(v=>{
console.log('done:', v); // done: p2
return Promise.reject('p2 reject')
})
.catch(e => console.log('error:', e)) // error: p2 reject
相关源码:
https://github.com/axios/axios/blob/0d8765562401910c1c509f6739a3bc558721e123/lib/core/Axios.js#L50
在第50行创建的promise
已经resolve
,
然后从interceptors
里分别取出拦截函数,
拦截函数相关代码:https://github.com/axios/axios/blob/master/lib/core/InterceptorManager.js
在第61行调用then
方法时,就直接进入fulfilled
函数,导致rejected
函数形同虚设。
那axios官方文档的示例代码那样子写是什么意思呢?
写于 2015-05-05
通过SSH密钥的方式来识别受信任的计算机,不需要输入密码。下面的步骤将引导你生成SSH密钥和添加公钥到你的GitHub帐户。
建议:定期检查你的SSH密钥列表,并删除任何用不着的SSH密钥。
首先,我们需要先检查你电脑上已有的SSH密钥。打开命令行窗口bash(如果安装了Git的Windows版本,可以打开Git Bash)并输入:
ls -al ~/.ssh
# 列出.ssh文件夹下的所有文件
检查列出来的目录中是否已有SSH公钥。默认情况下,公钥文件名将是下列中的一个:
id_dsa.pub
id_ecdsa.pub
id_ed25519.pub
id_rsa.pub
通常我们使用的是RSA的密钥,默认命名为id_rsa
,ssh连接时默认读取该文件,可以通过ssh-agent
添加额外的密钥文件,后面会提到。
复制下面的内容,并粘贴到打开的命令行窗口中。确保你已经替换为你的email地址。
ssh-keygen -o -t rsa -b 4096 -C "[email protected]"
# 创建一个新的SSH密钥,`-C`表示注释,以提供的email为标识,用于区分,可选
# `-o`表示使用更新、安全的编码格式
# `-b`表示自定义密钥长度,最小1024,默认2048,可选
# 生成rsa密钥对
强烈建议保持默认的设置,当出现"Enter a file in which to save the key"时,按下回车键即可。
# Enter file in which to save the key (/Users/you/.ssh/id_rsa): [Press enter]
如果你打算生成新的密钥,则需要输入新的文件路径,然后在~/.ssh/config
文件中添加新的声明,这样ssh连接时才能识别并读取,声明方式后面会提到。
输入一个口令
# Enter passphrase (empty for no passphrase): [Type a passphrase]
# Enter same passphrase again: [Type passphrase again]
Tip:强烈建议设置一个很好的,安全的口令。更多信息,见“使用SSH密钥的口令”。
该密码可选,你也可以直接按两次回车跳过。设置密码后,连接时需要输入密码。
如果想添加或修改密码,可以使用-p
标识
ssh-keygen -p -o -f <keyname>
输入口令之后,你会得到一个指纹码或id,这就是你的SSH密钥。它看起来像这样:
# Your identification has been saved in /Users/you/.ssh/id_rsa.
# Your public key has been saved in /Users/you/.ssh/id_rsa.pub.
# The key fingerprint is:
# 01:0f:f4:3b:ca:85:d6:17:a1:7d:f0:68:9d:f0:a2:db [email protected]
配置你的GitHub帐户使用你的SSH密钥:
警告: 复制时不能有额外的新行或空白。
将复制的密钥添加到GitHub/Gitlab:
为了确保一切工作正常,你现在尝试连接到SSH。当你这样做时,会要求输入密码进行验证,这是你之前创建的SSH密钥口令。
打开命令行窗口然后输入:
ssh -T [email protected]
# 如果是gitlab则是
# ssh -T [email protected]
你将看到警告内容:
# The authenticity of host 'github.com (207.97.227.239)' can't be established.
# RSA key fingerprint is 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48.
# Are you sure you want to continue connecting (yes/no)?
检查验证信息中的指纹是否匹配,然后输入“yes”:
# Hi username! You've successfully authenticated, but GitHub does not
# provide shell access.
如果信息中的username是你的,表明你成功设置了SSH密钥!
如果你收到一个关于“拒绝访问”的信息,你可以阅读这些问题诊断。
或使用-vvvT
打印debug信息
# ssh -vvvT [email protected]
如果你从HTTPS切换到SSH,你需要更新远程仓库的URL。有关更多信息,请参阅更改远程仓库的URL。
如果你生成密钥时使用非默认的文件名,则需配置SSH代理程序来使用你生成的SSH密钥:
确保SSH代理已开启:
# bash运行SSH代理
eval "$(ssh-agent -s)"
# Agent pid 59566
添加生成的SSH密钥到代理中:
ssh-add ~/.ssh/other_id_rsa
如果出现Could not open a connection to your authentication agent
,则在bash窗口中执行:
ssh-agent bash
如果想保持这些设置,不会因为bash关闭而失效,则需要配置~/.ssh/config
文件
配置示例如下,替换host及文件路径即可
# GitLab.com
Host gitlab.com
Preferredauthentications publickey
IdentityFile ~/.ssh/gitlab_com_rsa
# Private GitLab instance
Host gitlab.company.com
Preferredauthentications publickey
IdentityFile ~/.ssh/example_com_rsa
targetWindow.postMessage(message, targetOrigin, [transfer]);
window.postMessage()向目标窗口派发一个 MessageEvent 消息。
targetWindow
:接收消息的窗口引用,包括:
message
:具体的信息内容
targetOrigin
:接收消息的窗口的源(origin)。设为*
,表示不限制域名,向任意窗口发送。应当避免使用*
,以防窗口被重定向,导致发送的消息被恶意获取。
transfer
:可选,一个序列化的Transferable
对象,所有权将被转移到目标窗口
message
事件event.data
:信息内容
event.origin
:发送消息的源窗口,包括scheme://host:port
event.source
:发送消息窗口的引用,可用于建立双向通信
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event) {
if (event.origin !== "http://example.org:8080")
return;
// ...
}
/*
* In window A's scripts, with A being on <http://example.com:8080>:
*/
var popup = window.open(...popup details...);
popup.postMessage("hello there!", "http://example.com");
function receiveMessage(event)
{
if (event.origin !== "http://example.com")
return;
// event.source is popup
// event.data is "hi there yourself! the secret response is: rheeeeet!"
}
window.addEventListener("message", receiveMessage, false);
/*
* In the popup's scripts, running on <http://example.com>:
*/
function receiveMessage(event)
{
if (event.origin !== "http://example.com:8080")
return;
// event.source is window.opener
// event.data is "hello there!"
// a convenient idiom for replying to a
// message is to call postMessage on event.source and provide
// event.origin as the targetOrigin.
event.source.postMessage("hi there yourself! the secret response " +
"is: rheeeeet!",
event.origin);
}
window.addEventListener("message", receiveMessage, false);
// main window
<button id="send">send</button>
<script>
window.onload = function(){
var button = document.getElementById('send');
window.addEventListener('message', function(event){
console.log(event);
}, false);
var iframe = document.createElement('iframe');
iframe.src = "http://iframe.local.com/";
iframe.style = "display:none";
document.body.appendChild(iframe);
button.addEventListener('click', function () {
iframe.contentWindow.postMessage('from webroot', 'http://iframe.local.com/');
}, false);
}
</script>
//iframe
<script>
window.onload = function () {
targetWindow = window.opener || window.parent
targetWindow.postMessage('from iframe.local ', '*');
window.addEventListener('message', function (event) {
console.log(event);
event.source.postMessage('roger that. (origin message: ' + event.data + ')', event.origin);
}, false)
}
</script>
参考资料:
https://unix.stackexchange.com/questions/233327/is-it-possible-to-clone-only-part-of-a-git-project
git 命令操作复杂,具体查看参考资料
svn export https://github.com/${userName}/${repoName}/trunk/${directory}
需要将URL里的tree/master
替换为trunk
https://github.com/HR/github-clone
提供了chrome、Firefox扩展
底部有github源码仓库地址
写于 2017-10-31
var gulp = require('gulp');
var coffee = require('gulp-coffee');
var uglify = require('gulp-uglify');
gulp.task('js', function(){
return gulp.src('./js/src/*.coffee')
.pipe(coffee())
.pipe(uglify())
.pipe(gulp.dest('./js/'));
});
gulp.task('watch', function(){
gulp.watch('./js/src/*.coffee', ['js']);
});
var gulp = require('gulp');
var autoprefixer = require('gulp-autoprefixer');
var minifyCss = require('gulp-minify-css');
var rename = require('gulp-rename');
gulp.task('css', function(){
return gulp.src('./css/src/style.css')
.pipe(autoprefixer('last 2 versions'))
.pipe(gulp.dest('./css/'))
.pipe(minifyCss())
.pipe(rename({extname: '.min.css'}))
.pipe(gulp.dest('./css/'));
});
var gulp = require('gulp');
var cached = require('gulp-cached');
var uglify = require('gulp-uglify');
var remember = require('gulp-remember');
var concat = require('gulp-concat');
gulp.task('script', function(){
return gulp.src('./js/src/*.js')
.pipe(cached())
.pipe(uglify())
.pipe(remember())
.pipe(concat('app.js'))
.pipe(gulp.dest('./js/'));
});
var gulp = require('gulp');
var changed = require('gulp-changed');
var uglify = require('gulp-uglify');
gulp.task('css', function(){
return gulp.src('./src/*.js')
.pipe(changed('./dist/'))
.pipe(uglify())
.pipe(gulp.dest('./dist/'));
});
var gulp = require('gulp');
var merge = require('merge-stream');
var less = require('gulp-less');
var autoprefixer = require('gulp-remember');
gulp.task('css', function(){
return merge(
gulp.src('./css/src/first.less')
.pipe(less())
.pipe(gulp.dest('./css/')),
gulp.src('./css/src/second.css')
.pipe(autoprefixer())
.pipe(gulp.dest('./css/'))
);
})
var gulp = require('gulp');
var streamqueue = require('streamqueue');
var less = require('gulp-less');
var cssimport = require('gulp-cssimport');
var autoprefier = require('gulp-autoprefixer');
gulp.task('css', function(){
return streamqueue({ objectMode: true },
gulp.src('./css/src/first.less')
.pipe(less()),
gulp.src('./css/src/second.css')
.pipe(cssimport())
.pipe(autoprefixer('last 2 versions')))
.pipe(concat('app.css'))
.pipe(minifyCss())
.pipe(gulp.dest('./css/'))
});
var gulp = require('gulp');
var es = require('event-stream');
var consolidate = require('gulp-consolidate');
var rename = require('gulp-rename');
data = [
{
name: 'apple',
title: 'Apple Cake',
attrs: some_data
},
{
name: 'orange',
title: 'Orange Cookie',
attrs: some_data
}
];
gulp.task('page', function(){
var tasks = data.map(function(entry){
return gulp.src('./templates/a.js')
.pipe(consolidate({
title: entry.title,
attrs: entry.attrs
}))
.pipe(rename({
basename: entry.name,
extname: '.html'
}))
.pipe(gulp.dest('./html/'));
});
return es.concat.apply(null, tasks);
});
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.