发起一个github/npm工程协作项目,门槛太高了!!

最基础的问题,你都要花很久去研究:

  • 如何在项目中全线使用es2017代码? 答案是babel
  • 如何统一所有协作者的代码风格? 答案是eslint + prettier
  • 如何测试驱动开发,让项目更健壮? 答案是jest
  • 如何持续化集成,方便更多协作者参与项目? 答案是circleci

这四样工具的配置,是每个github项目都会用上的。另外,gitignore配置\editconfig\readme\lisence。。。也是必不可缺的。

你可能需要花数天时间去研究文档、数天时间去做基础配置。

这样的时间成本,可以直接劝退大多数人。

但,假如几秒钟,就可以按需求配置好这一切呢?

你可以先来体验一下“轮子工厂”,在命令行输入:

npx lunz myapp
复制代码

一路回车,然后试一试yarn lint,yarn test,yarn build命令。

一个完备的拥有ES代码、代码规范、测试开发、持续化集成、npm标准配置的开发栈,瞬间就生成了。


第一部分: 2019年github + npm工程化协作开发栈最佳实践

第二部分: 使用脚手架,10秒钟构建可自由配置的开发栈。


2019年github + npm工程化协作开发栈最佳实践

我们将花半小时实战撸一个包含package.json, babel, jest, eslint, prettify, gitignore, readme, lisence的标准的用于github工程协作的npm包开发栈

如果能实际操作,就实际操作。

如果不能实际操作,请在bash下输入npx lunz npmdev获得同样的效果。

1. 新建文件夹

mkdir npmdev && cd npmdev
复制代码

2. 初始化package.json

npm init
复制代码
package name: 回车version: 回车description: 自己瞎写一个,不填也行entry point:  输入`dist/index.js`test command: 输入`npx jest`git repository: 输入你的英文名加上包名,例如`wanthering/npmdev`keywords: 自己瞎写一个,不填也行author: 你的英文名,例如`wanthering`license: 输入`MIT`复制代码

在package.json中添加files字段,使npm发包时只发布dist

  ..."files": ["dist"],...
复制代码

之前不是创建了.editorconfigLICENSEcircle.yml.gitignoreREADME.md吗,这四个复制过来。

3. 初始化eslint

npx eslint --init
复制代码
How would you like to use ESLint? 选第三个What type of modules does your project use?
选第一个Which framework does your project use?
选第三个NoneWhere does your code run?
选第二个 NodeHow would you like to define a style for your project? 选第一个popularWhich style guide do you want to follow?
选第一个standardWhat format do you want your config file to be in?
选第一个 javascript
复制代码

在package.json中添加一条srcipts命令:

   ..."scripts": {"test": "npx jest","lint": "npx eslint src/**/*.js test/**/*.js --fix"},...
复制代码

4. 初始化prettier

为了兼容eslint,需要安装三个包

yarn add prettier eslint-plugin-prettier eslint-config-prettier -D
复制代码

在package.json中添加prettier字段

  ..."prettier": {"singleQuote": true,"semi": false},...
复制代码

在.eslintrc.js中,修改extends字段:

...'extends': ['standard',"prettier","plugin:prettier/recommended"],
...
复制代码

5. 创建源文件

mkdir src && touch src/index.js
复制代码

src/index.js中,我们用最简单的add函数做示意

const add = (a,b)=>{
return a+b}export default add
复制代码

这时命令行输入

yarn lint
复制代码

这会看到index.js自动排齐成了

const add = (a, b) => {return a + b
}
export default add
复制代码

6. 配置jest文件

所有的npm包,均采用测试驱动开发。

现在流行的框架,无非jest和ava,其它的mocha之类的框架已经死在沙滩上了。

我们安装jest

npm i jest -D
复制代码

然后根目录下新建一个test文件夹,放置进jest/index.spec.js文件

mkdir test && touch test/index.spec.js
复制代码

在index.spec.js内写入:

import add from "../src/index.js";
test('add',()=>{
expect(add(1,2)).toBe(3)})
复制代码

配置一下eslint+jest:

yarn add eslint-plugin-jest -D
复制代码

在.eslintrc.js中,更新env字段,添加plugins字段:

  'env': {'es6': true,'node': true,'jest/globals': true},'plugins': ['jest'],...
复制代码

因为需要jest中使用es6语句,需要添加babel支持

yarn add babel-jest @babel/core @babel/preset-env -D
复制代码

创建一下.babelrc配置,注意test字段,是专门为了转化测试文件的:

{"presets": [["@babel/preset-env",{"targets": {"node": 6}}]],"env": {"test": {"presets": [["@babel/preset-env",{"targets": {"node": "current"}}]]}}
}
复制代码

好,跑一下yarn lint,以及yarn test

yarn lintyarn test
复制代码

构建打包

比起使用babel转码(安装@babel/cli,再调用npx babel src --out-dir dist),我更倾向于使用bili进行打包。

yarn add bili -D
复制代码

然后在package.json的script中添加

  "scripts": {"test": "npx jest","lint": "npx eslint src/**/*.js test/**/*.js --fix","build": "bili"},
复制代码

.gitignore

创建 .gitignore,复制以下内容到文件里

node_modules
.DS_Store
.idea
*.log
dist
output
examples/*/yarn.lock
复制代码

.editorconfig

创建.editorconfig,复制以下内容到文件里

root = true[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true[*.md]
trim_trailing_whitespace = false
复制代码

circle.yml

创建circle.yml,复制以下内容到文件内

version: 2
jobs:build:working_directory: ~/projectdocker:- image: circleci/node:latestbranches:ignore:- gh-pages # list of branches to ignore- /release\/.*/ # or ignore regexessteps:- checkout- restore_cache:key: dependency-cache-{{ checksum "yarn.lock" }}- run:name: install dependencescommand: yarn install- save_cache:key: dependency-cache-{{ checksum "yarn.lock" }}paths:- ./node_modules- run:name: testcommand: yarn test复制代码

README.md

创建README.md,复制以下内容到文件内

# npm-dev> my laudable project
复制代码

好了,现在我们的用于github工程协作的npm包开发栈已经完成了,相信我,你不会想再配置一次。

这个项目告一段落。

事实上,这个npm包用npm publish发布出去,人们在安装它之后,可以作为add函数在项目里使用。

使用脚手架,10秒钟构建可自由配置的开发栈。

同样,这一章节如果没时间实际操作,请输入

git clone https://github.com/wanthering/lunz.git

当你开启新项目,复制粘贴以前的配置和目录结构,浪费时间且容易出错。

package.json、webpack、jest、git、eslint、circleci、prettify、babel、gitigonre、editconfig、readme的强势劝退组合,让你无路可走。

所以有了vue-cli,非常强大的脚手架工具,但你想自定义自己的脚手架,你必须学透了vue-cli。

以及yeoman,配置贼麻烦,最智障的前端工具,谁用谁sb。

还有人求助于docker,

有幸,一位来自成都的宝藏少年egoist开发了前端工具SAO.js。

SAO背景不错,是nuxt.js的官方脚手架。

作为vue的亲弟弟nuxt,不用vue-cli反而用sao.js,你懂意思吧?

因为爽!!!!!!!!

因为,一旦你学会批量构建npm包,未来将可以把精力集中在“造轮子”上。

新建sao.js

全局安装

npm i sao -g
复制代码

快速创建sao模板

sao generator sao-npm-dev
复制代码

一路回车到底

ok,当前目录下出现了一个sao-npm-dev

打开看一下:

├── .editorconfig
├── .git
├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── circle.yml
├── package.json
├── saofile.js
├── template
│   ├── .editorconfig
│   ├── .gitattributes
│   ├── LICENSE
│   ├── README.md
│   └── gitignore
├── test
│   └── test.js
└── yarn.lock
复制代码

别管其它文件,都是用于github工程协作的文件。

有用的只有两个:template文件夹, 和saofile.js

template文件夹删空,我们要放自己的文件。

生成SAO脚手架

好,把npmdev整个文件夹内的内容,除了node_modules/、package-lock.json和dist/,全部拷贝到清空的sao-npm-dev/template/文件夹下

现在的sao-npm-dev/template文件夹结构如下:

├── template
│   ├── .babelrc
│   ├── .editorconfig
│   ├── .eslintrc.js
│   ├── .gitignore
│   ├── LICENSE
│   ├── README.md
│   ├── circle.yml
│   ├── package.json
│   ├── src
│   │   └── index.js
│   ├── test
│   │   └── index.spec.js
│   └── yarn.lock复制代码

配置文件改名

模板文件中.eslint.js .babelrc .gitignore package.json,很容易造成配置冲突,我们先改名使它们失效:

mv .eslintrc.js _.eslintrc.jsmv .babelrc _.babelrcmv .gitignore _gitignoremv package.json _package.json
复制代码

配置saofile.js

现在所见的saofile,由三部分组成: prompts, actions, completed。

分别表示: 询问弹窗、自动执行任务、执行任务后操作。

大家可以回忆一下vue-cli的创建流程,基本上也是这三个步骤。

弹窗询问的,即是我们用于github工程协作的npm包开发栈每次开发时的变量,有哪些呢?

我来列一张表:

字段 输入方式 可选值 意义
name input 默认为文件夹名 项目名称
description input 默认为my xxx project 项目简介
author input 默认为gituser 作者名
features checkbox eslint和prettier 安装插件
test confirm yes 和no 是否测试
build choose babel 和 bili 选择打包方式
pm choose npm 和yarn 包管理器

根据这张表,我们修改一下saofile.js中的prompts,并且新增一个templateData(){},用于向template中引入其它变量

   prompts() {return [{name: 'name',message: 'What is the name of the new project',default: this.outFolder},{name: 'description',message: 'How would you descripe the new project',default: `my ${superb()} project`},{name: 'author',message: 'What is your GitHub username',default: this.gitUser.username || this.gitUser.name,store: true},{name: 'features',message: 'Choose features to install',type: 'checkbox',choices: [{name: 'Linter / Formatter',value: 'linter'},{name: 'Prettier',value: 'prettier'}],default: ['linter', 'prettier']},{name: 'test',message: 'Use jest as test framework?',type: 'confirm',default: true},{name: 'build',message: "How to bundle your Files?",choices: ['bili', 'babel'],type: 'list',default: 'bili'},{name: 'pm',message: 'Choose a package manager',choices: ['npm', 'yarn'],type: 'list',default: 'yarn'}]},templateData() {const linter = this.answers.features.includes('linter')const prettier = this.answers.features.includes('prettier')return {linter, prettier}},
复制代码

先把saofile放下,我们去修改一下template文件,使template中的文件可以应用这些变量

修改template/中的变量

template下的文件,引入变量的方式是ejs方式,不熟悉的可以看一看ejs官方页面,非常简单的一个模板引擎

现在我们一个一个审视文件,看哪些文件需要根据变量变动。

1. src/index.js

无需变动

2. test/index.spec.js

如果test为false,则文件无需加载。test为true,则加载文件。

3. .editorconfig

无需改动

4. _.gitignore

无需改动

5. _.babelrc

如果build采用的babel,或test为true,则导入文件。

并且,如果test为true,应当开启env,如下设置文件

_.babelrc

{"presets": [["@babel/preset-env",{"targets": {"node": 6}}]]<% if( test ) { %>,"env": {"test": {"presets": [["@babel/preset-env",{"targets": {"node": "current"}}]]}}<% } %>
}复制代码

6. _.eslintrc.js

在打开test的情况下,加载env下的jest/globals及设置plugins下的jest

在开启prettier的情况下,加载extends下的prettierplugin:prettier/recommend

所以文件应当这样改写

_.eslintrc.js

module.exports = {'env': {'es6': true,'node': true<% if(test) { %>,'jest/globals': true<% } %>}<% if(test) { %>,'plugins': ['jest']<% } %>,'extends': ['standard'<% if(prettier) { %>,'prettier','plugin:prettier/recommended'<% } %>],'globals': {'Atomics': 'readonly','SharedArrayBuffer': 'readonly'},'parserOptions': {'ecmaVersion': 2018,'sourceType': 'module'}
}复制代码

7. _package.json

name字段,加载name变量 description字段,加载description变量 author字段,加载author变量

bugs,homepage,url跟据author和name设置

prettier为true时,设置prettier字段,以及devDependence加载eslint-plugin-prettier、eslint-config-prettier以及prettier

eslint为true时,加载eslint下的其它依赖。

jest为true时,加载eslint-plugin-jest、babel-jest、@babel/core和@babel/preset-env,且设置scripts下的lint语句

build为bili时,设置scripts下的build字段为bili

build为babel时,设置scripts下的build字段为npx babel src --out-dir dist

最后实际的文件为:(注意里面的ejs判断语句)

{"name": "<%= name %>","version": "1.0.0","description": "<%= description %>","main": "dist/index.js","scripts": {"build": "<% if(build === 'bili') { %>bili<% }else{ %>npx babel src --out-dir dist<% } %>"<% if(test){ %>,"test": "npx jest"<% } %><% if(linter){ %>,"lint": "npx eslint src/**/*.js<% } if(linter && test){ %> test/**/*.js<% } if(linter){ %> --fix"<% } %>},"repository": {"type": "git","url": "git+https://github.com/<%= author %>/<%= name %>.git"},"author": "<%= author %>","license": "MIT","bugs": {"url": "https://github.com/<%= author %>/<%= name %>/issues"}<% if(prettier){ %>,"prettier": {"singleQuote": true,"semi": false}<% } %>,"homepage": "https://github.com/<%= author %>/<%= name %>#readme","devDependencies": {<% if(build === 'bili'){ %>"bili": "^4.7.4"<% } %><% if(build === 'babel'){ %>"@babel/cli": "^7.4.4"<% } %><% if(build === 'babel' || test){ %>,"@babel/core": "^7.4.4","@babel/preset-env": "^7.4.4"<% } %><% if(test){ %>,"babel-jest": "^24.8.0","jest": "^24.8.0"<% } %><% if(linter){ %>,"eslint": "^5.16.0","eslint-config-standard": "^12.0.0","eslint-plugin-import": "^2.17.2","eslint-plugin-node": "^9.0.1","eslint-plugin-promise": "^4.1.1","eslint-plugin-standard": "^4.0.0"<% } %><% if(linter && test){ %>,"eslint-plugin-jest": "^22.5.1"<% } %><% if (prettier){ %>,"prettier": "^1.17.0","eslint-plugin-prettier": "^3.1.0","eslint-config-prettier": "^4.2.0"<% } %>}
}
复制代码

8. circle.yml

判断使用的lockFile文件是yarn.lock还是package-lock.json

<% const lockFile = pm === 'yarn' ? 'yarn.lock' : 'package-lock.json' -%>
version: 2
jobs:build:working_directory: ~/projectdocker:- image: circleci/node:latestbranches:ignore:- gh-pages # list of branches to ignore- /release\/.*/ # or ignore regexessteps:- checkout- restore_cache:key: dependency-cache-{{ checksum "<%= lockFile %>" }}- run:name: install dependencescommand: <%= pm %> install- save_cache:key: dependency-cache-{{ checksum "<%= lockFile %>" }}paths:- ./node_modules- run:name: testcommand: <%= pm %> test复制代码

9. README.md

# <%= name %>> <%= description %>
复制代码

填入name和desc变量。

并跟据linter、test、build变量来选择提示命令。

具体文件略。


好,文件的变量导入完成,现在回到saofile.js:

处理actions

当我们通过弹窗询问到了变量。

当我们在构建好模板文件,只等变量导入了。

现在就需要通过saofile.js中的actions进行导入。

把actions进行如下改写:

  actions() {return [{type: 'add',files: '**',filters: {'_.babelrc': this.answers.test || this.answers.build === 'babel','_.eslintrc.js': this.answers.features.includes('linter'),'test/**': this.answers.test}}, {type: 'move',patterns: {'_package.json': 'package.json','_gitignore': '.gitignore','_.eslintrc.js': '.eslintrc.js','_.babelrc': '.babelrc'}}]},
复制代码

其实很好理解! type:'add'表示将模板文件添加到目标文件夹下,files表示是所有的, filters表示以下这三个文件存在的条件。

type:'move'就是改名或移动的意思,将之前加了下划线的四个文件,改回原来的名字。

处理competed

当文件操作处理完之后,我们还需要做如下操作:

  1. 初始化git
  2. 安装package里的依赖
  3. 输出使用指南
  async completed() {this.gitInit()await this.npmInstall({ npmClient: this.answers.pm })this.showProjectTips()}
复制代码

跑通测试

SAO已经帮你写好了测试文件,在test文件夹下。

因为我们要测试很多个选项,原来的sao.mock和snapshot要写很多次。所以我们把它提炼成一个新的函数verifyPkg()

我们进行一下改写,同时将package.json、.eslintrc.js打印在snapshot文件中。

import path from 'path'
import test from 'ava'
import sao from 'sao'const generator = path.join(__dirname, '..')const verifyPkg = async (t, answers) => {const stream = await sao.mock({ generator }, answers)const pkg = await stream.readFile('package.json')t.snapshot(stream.fileList, 'Generated files')t.snapshot(getPkgFields(pkg), 'package.json')if(answers && answers.features.includes('linter')){const lintFile = await stream.readFile('.eslintrc.js')t.snapshot(lintFile, '.eslintrc.js')}
}const getPkgFields = (pkg) => {pkg = JSON.parse(pkg)delete pkg.descriptionreturn pkg
}test('defaults', async t => {await verifyPkg(t)
})test('only bili', async t => {await verifyPkg(t,{features: [],test: false,build: 'bili'})
})test('only babel', async t => {await verifyPkg(t,{features: [],test: false,build: 'babel'})
})test('launch test', async t => {await verifyPkg(t,{features: [],test: true})
})test('launch linter', async t => {await verifyPkg(t,{features: ['linter']})
})test('launch prettier', async t => {await verifyPkg(t,{features: ['prettier']})
})复制代码

ok,这时候跑一下测试就跑通了 测试文件打印在snapshots/test.js.md中,你需要一项一项检查,输入不同变量时候,得到的文件结构和package.json 以及.eslintrc.js的内容。

这个时候,整个项目也就完成了。

我们先在npmjs.com下注册一个帐号,登录一下npm login登录一下。

然后,直接npm publish成功之后,就可以使用

sao npm-dev myapp
复制代码

初始化一个github工程化协作开发栈了。

进阶: 本地使用sao.js,发布自定义前端工具

大部分人,不会专门去安装sao之后再调用脚手架,而更喜欢使用

npx lunz myapp
复制代码

那就新添加一个cli.js文件

#!/usr/bin/env node
const path = require('path')
const sao = require('sao')const generator = path.resolve(__dirname, './')
const outDir = path.resolve(process.argv[2] || '.')console.log(`> Generating lunz in ${outDir}`)sao({ generator, outDir, logLevel: 2 }).run().catch((err) => {console.trace(err)process.exit(1)})复制代码

通过sao函数,可以轻松调用于来sao脚手架。

然后,将package.json中的name改名成你想发布npm全局工具名称,比如我创建的是lunz

并且,加入bin字段,且修改files字段

..."bin": "cli.js","files": ["cli.js","saofile.js","template"],...
复制代码

这时,应用一下npm link命令,就可以本地模拟出

lunz myapp
复制代码

的效果了。

如果效果ok的话,就可以使用npm publish发包。

注意要先登录,登录不上的话可能是因为你处在淘宝源下,请切换到npm正版源。

结语:

现在,你有什么想法,只需要随时随刻 npx lunz myapp一下,就可以得到当前最新、最标准、最现代化的github+npm工程化实践。

把时间集中花在轮子的构建逻辑上,而不是基础配置上。

与前端之“神”并肩,通过你的经验,让前端的生态更繁荣。

如果实在想研究基础配置,不如帮助我完善这个“轮子工厂”

欢迎大家提交pull request,交最新的实践整合到项目中

github地址: github.com/wanthering/…

一起加入,构造更完美的最佳实佳!

  1. 点击右上角的Fork按钮。
  2. 新建一个分支:git checkout -b my-new-feature
  3. 上报你的更新:git commit -am 'Add some feature'
  4. 分支上传云端:git push origin my-new-feature
  5. 提交 pull request?

转载于:https://juejin.im/post/5cd963ddf265da039f0f3169

10秒钟构建你自己的”造轮子”工厂! 2019年github/npm工程化协作开发栈最佳实践...相关推荐

  1. LINQ快速开发设计最佳实践(二) 构建Model模型

    一.摘要 第一篇文章我简要介绍了项目的设计框架和LINQ实现思想. 本篇文章将是最实际和具有技巧性的地方, 就是如何创建LINQ TO SQL 的模型对象. 二.前言 1.LINQ与LINQ TO S ...

  2. 造轮子是什么意思_程序员为什么热衷于造轮子,升职加薪吗?

    作者:小傅哥 博客: https://bugstack.cn- 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 哪个架构师没造过轮子? 你想过这样一件事吗? 是先具备能力在安排职位,还是先安排 ...

  3. 程序员该造轮子吗,造轮子能升职加薪吗?

    持续坚持原创输出,点击蓝字关注我吧 作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获!???? 目录 一.前言 二.我造过的轮子 1. RPC 通 ...

  4. 从零到一构建完整知识体系,阿里最新SpringBoot原理最佳实践真香

    Spring Boot不用多说,是咱们Java程序员必须熟练掌握的基本技能.工作上它让配置.代码编写.部署和监控都更简单,面试时互联网企业招聘对于Spring Boot这个系统开发的首选框架也是考察的 ...

  5. 不是“重复”造轮子,百度飞桨框架2.0如何俘获人心

    2016 年,百度 PaddlePaddle 打响了国产深度学习框架开源的第一枪. 2019 年 4 月,在 Wave Summit 深度学习开发者峰会上,首次发布了PaddlePaddle 的中文名 ...

  6. 经常造轮子的 AI 工程师水平通常不会太差

    题图 | 邵姺画 引子 | 造轮子还是不造轮子,这是一个问题 理查德·费曼教授去世后,人们在他的黑板上发现了 What I cannot create, I do not understand 这句话 ...

  7. 七月新增开源项目:你学习的速度跟得上前端造轮子的速度吗?

    2019独角兽企业重金招聘Python工程师标准>>> 每月新增开源项目.顾名思义,每月更新一期.我们会从社区上个月新收录的开源项目中,挑选出有价值的.有用的.优秀的.或者好玩的开源 ...

  8. (造轮子)C 创建队列和图实现广度优先算法(BFS)和深度优先算法(DFS)(数据结构)

    链表.队列和图实现BFS和DFS算法(C+造轮子+详细代码注释) 1.队列的链式存储结构   队列的链式表示称为链队列,它实际上是一个同时带有队头指针和队尾指针的单链表.头指针指向队头节点,尾指针指向 ...

  9. 微软 CEO 萨提亚·纳德拉:不要重复造轮子,提升技术强密度

    扫描二维码,查看精彩回顾 据中国互联网络信息中心(CNNIC)发布的第 44 次<中国互联网络发展状况统计报告>显示,截至 2019 年 6 月,我国网民规模达 8.54 亿,较 2018 ...

最新文章

  1. GDT,LDT,GDTR,LDTR 详解,包你理解透彻
  2. Oracle临时表和SQL Server临时表的不同点对比
  3. 启明云端分享|ESP32-S3开发环境搭建,这里我们会介绍两个比较常用的开发环境搭建:WINDOWS\LINUX
  4. zipkin使用_Sleuth和Zipkin进行分布式链路跟踪,一点课堂(多岸学院)
  5. 自定义EventSource(二)PollingCounter
  6. zrender zlevel层叠控制和Group使用笔记
  7. 数据结构和算法详解(三)——递归、排序、散列表
  8. iOS底层探索之KVO(五)—FBKVOController分析
  9. 计算机二级用的ms什么版本,计算机二级ms office用的哪个版本
  10. 【BERT-多标签文本分类实战】之四——数据集预处理
  11. 2022年危险化学品经营单位安全管理人员考试练习题及答案
  12. 【CSS3学习笔记】16:边框图片效果
  13. Excel如何输入负数
  14. 〖Python 数据库开发实战 - Python与MySQL交互篇⑰〗- 项目实战 - 实现用户管理 - 修改用户
  15. 守望先锋世界观架构 ——(一款好的游戏是怎么来的)
  16. Elementui蓝色阴影边框相关问题的解决方案
  17. 聚观早报 | 抖音推出可颂App;马斯克终止收购 Twitter
  18. 笔记(待续)-动力学逆问题相关基础知识
  19. Android App Shortcuts
  20. Pulsar 社区周报 2020-09-12 ~ 09-18

热门文章

  1. java treeview控件,来自treeview控件的绑定错误
  2. 《弱监督/半监督的DCNN图像分割》笔记
  3. 人们对一件事情的评价很容易偏激
  4. 当switch遇到null
  5. LiveGBS国标GB/T28181流媒体服务查看通道设备录像查看接入设备的前端录像
  6. 电子科技大学 高级计算机系统结构 考试回忆
  7. mysql:mysql的优势
  8. 真爱梦想:如何打造使命组织学习笔记
  9. Nacos2.1.0下载安装启动配置
  10. 老式笔记本电脑还能如何利用?