大家好,我是若川。持续组织了6个月源码共读活动,感兴趣的可以点此加我微信 ruochuan02 参与,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列

在Verdaccio搭建npm私有服务器中,我们介绍了如何搭建一个Npm私有服务器;服务器搭建完成后,我们本文来学习一下如何上传我们自己的npm包。

前端模块化作为前端必备的一个技能,已经在前端开发中不可或缺;而模块化带来项目的规模不断变大,项目的依赖越来越多;随着项目的增多,如果每个模块都通过手动拷贝的方式无异于饮鸩止渴,我们可以把功能相似的模块或组件抽取到一个npm包中;然后上传到私有npm服务器,不断迭代npm包来更新管理所有项目的依赖。

npm包的基本了解

首先我们来了解一下实现一个npm包需要包含哪些内容。

打包

通常,我们把打包好的一些模块文件放在一个目录下,便于统一进行加载;是的,npm包也是需要进行打包的,虽然也能直接写npm包模块的代码(并不推荐),但我们经常会在项目中用到typescript、babel、eslint、代码压缩等等功能,因此我们也需要对npm包进行打包后再进行发布。

在深入对比Webpack、Parcel、Rollup打包工具中,我们总结了,rollup相比于webpack更适合打包一些第三方的类库,因此本文主要通过rollup来进行打包。

npm域级包

随着npm包越来越多,而且包名也只能是唯一的,如果一个名字被别人占了,那你就不能再使用这个名字;假设我想要开发一个utils包,但是张三已经发布了一个utils包,那我的包名就不能叫utils了;此时我们可以加一些连接符或者其他的字符进行区分,但是这样就会让包名不具备可读性。

在npm的包管理系统中,有一种scoped packages机制,用于将一些npm包以@scope/package的命名形式集中在一个命名空间下面,实现域级的包管理。

域级包不仅不用担心会和别人的包名重复,同时也能对功能类似的包进行统一的划分和管理;比如我们用vue脚手架搭建的项目,里面就有@vue/cli-plugin-babel@vue/cli-plugin-eslint等等域级包。

我们在初始化项目时可以使用命令行来添加scope:

npm init --scope=username

相同域级范围内的包会被安装在相同的文件路径下,比如node_modules/@username/,可以包含任意数量的作用域包;安装域级包也需要指明其作用域范围:

npm install @username/package

在代码中引入时同样也需要作用域范围:

require("@username/package")

加载规则

在npm包中的package.json文件,我们经常会看到mainjsnext:mainmodulebrowser等字段,那么这些字段都代表了什么意思呢?其实这跟npm包的工作环境有关系,我们知道,npm包分为以下几种类型的包:

  • 只能在浏览器端使用的

  • 只能在服务器端使用的

  • 浏览器/服务器端都可使用

假如我们现在开发一个npm包,既要支持浏览器端,也要支持服务器端(比如axios、lodash等),需要在不同的环境下加载npm包的不同入口文件,只通过一个字段已经不能满足需求。

首先我们来看下main字段,它是nodejs默认文件入口, 支持最广泛,主要使用在引用某个依赖包的时候需要此属性的支持;如果不使用main字段的话,我们可能需要这样来引用依赖:

import('some-module/dist/bundle.js')

所以它的作用是来告诉打包工具,npm包的入口文件是哪个,打包时让打包工具引入哪个文件;这里的文件一般是commonjs(cjs)模块化的。

有一些打包工具,例如webpack或rollup,本身就能直接处理import导入的esm模块,那么我们可以将模块文件打包成esm模块,然后指定module字段;由包的使用者来决定如何引用。

jsnext:mainmodule字段的意义是一样的,都可以指定esm模块的文件;但是jsnext:main是社区约定的字段,并非官方,而module则是官方约定字段,因此我们经常将两个字段同时使用。

在Webpack配置全解析中我们介绍到,mainFields就是webpack用来解析模块的,默认会按照顺序解析browser、module、main字段。

有时候我们还想要写一个同时能够跑在浏览器端和服务器端的npm包(比如axios),但是两者在运行环境上还是有着细微的区别,比如浏览器请求数据用的是XMLHttpRequest,而服务器端则是http或者https;那么我们要怎样来区分不同的环境呢?

除了我们可以在代码中对环境参数进行判断(比如判断XMLHttpRequest是否为undefined),也可以使用browser字段,在浏览器环境来替换main字段。browser的用法有以下两种,如果browser为单个的字符串,则替换main成为浏览器环境的入口文件,一般是umd模块的:

{"browser": "./dist/bundle.umd.js"
}

browser还可以是一个对象,来声明要替换或者忽略的文件;这种形式比较适合替换部分文件,不需要创建新的入口。key是要替换的module或者文件名,右侧是替换的新的文件,比如在axios的packages.json中就用到了这种替换:

{"browser": {"./lib/adapters/http.js": "./lib/adapters/xhr.js"}
}

打包工具在打包到浏览器环境时,会将引入来自./lib/adapters/http.js的文件内容替换成./lib/adapters/xhr.js的内容。

在有一些包中我们还会看到types字段,指向types/index.d.ts文件,这个字段是用来包含了这个npm包的变量和函数的类型信息;比如我们在使用lodash-es包的时候,有一些函数的名称想不起来了,只记得大概的名字;比如输入fi就能自动在编译器中联想出fill或者findIndex等函数名称,这就为包的使用者提供了极大的便利,不需要去查看包的内容就能了解其导出的参数名称,为用户提供了更加好的IDE支持。

发布哪些文件

在npm包中,我们可以选择哪些文件发布到服务器中,比如只发布压缩后的代码,而过滤源代码;我们可以通过配置文件来进行指定,可以分为以下几种情况:

  • 存在.npmignore文件,以.npmignore文件为准,在文件中的内容都会被忽略,不会上传;即使有.gitignore文件,也不会生效。

  • 不存在.npmignore文件,以.gitignore文件为准,一般是无关内容,例如.vscode等环境配置相关的。

  • 不存在.npmignore也不存在.gitignore,所有文件都会上传。

  • package.json中存在files字段,可以理解为files为白名单。

ignore相当于黑名单,files字段就是白名单,那么当两者内容冲突时,以谁为准呢?答案是files为准,它的优先级最高。

我们可以通过npm pack命令进行本地模拟打包测试,在项目根目录下就会生成一个tgz的压缩包,这就是将要上传的文件内容。

项目依赖

在package.json文件中,所有的依赖包都会在dependencies和devDependencies字段中进行配置管理:

  • dependencies:表示生产环境下的依赖管理,--save 简写 -S;

  • devDependencies:表示开发环境下的依赖管理,--save-dev 简写 -D;

dependencies字段指定了项目上线后运行所依赖的模块,可以理解为我们的项目在生产环境运行中要用到的东西;比如vue、jquery、axios等,项目上线后还是要继续使用的依赖。

devDependencies字段指定了项目开发所需要的模块,开发环境会用到的东西;比如webpack、eslint等等,我们打包的时候会用到,但是项目上线运行时就不需要了,所以放到devDependencies中去就好了。

除了dependencies和devDependencies字段,我们在一些npm包中还会看到peerDependencies字段,没有写过npm插件的童鞋可能会对这个字段比较陌生,它和上面两个依赖有什么区别呢?

假设我们的项目MyProject,有一个依赖PackageA,它的package.json中又指定了对PackageB的依赖,因此我们的项目结构是这样的:

MyProject
|- node_modules|- PackageA|- node_modules|- PackageB

那么我们在MyProject中是可以直接引用PackageA的依赖的,但如果我们想直接使用PackageB,那对不起,是不行的;即使PackageB已经被安装了,但是node只会在MyProject/node_modules目录下查找PackageB。

为了解决这样问题,peerDependencies字段就被引入了,通俗的解释就是:如果你安装了我,你最好也安装以下依赖。比如上面如果我们在PackageA的package.json中加入下面代码:

{"peerDependencies": {"PackageB": "1.0.0"}
}

这样如果你安装了PackageA,那会自动安装PackageB,会形成如下的目录结构:

MyProject
|- node_modules|- PackageA|- PackageB

我们在MyProject项目中就能愉快的使用PackageA和PackageB两个依赖了。

比如,我们熟悉的element-plus组件库,它本身不可能单独运行,必须依赖于vue3环境才能运行;因此在它的package.json中我们看到它对宿主环境的要求:

{"peerDependencies": {"vue": "^3.2.0"},
}

这样我们看到它在组件中引入的vue的依赖,其实都是宿主环境提供的vue3依赖:

import { ref, watch, nextTick } from 'vue'

许可证

license字段使我们可以定义适用于package.json所描述代码的许可证。同样,在将项目发布到npm注册时,这非常重要,因为许可证可能会限制某些开发人员或组织对软件的使用。拥有清晰的许可证有助于明确定义该软件可以使用的术语。

借用知乎上Max Law的一张图来解释所有的许可证:

许可证

版本号

npm包的版本号也是有规范要求的,通用的就是遵循semver语义化版本规范,版本格式为:major.minor.patch,每个字母代表的含义如下:

  1. 主版本号(major):当你做了不兼容的API修改

  2. 次版本号(minor):当你做了向下兼容的功能性新增

  3. 修订号(patch):当你做了向下兼容的问题修正

先行版本号是加到修订号的后面,作为版本号的延伸;当要发行大版本或核心功能时,但不能保证这个版本完全正常,就要先发一个先行版本。

先行版本号的格式是在修订版本号后面加上一个连接号(-),再加上一连串以点(.)分割的标识符,标识符可以由英文、数字和连接号([0-9A-Za-z-])组成。例如:

1.0.0-alpha
1.0.0-alpha.1
1.0.0-0.3.7

常见的先行版本号有:

  1. alpha:不稳定版本,一般而言,该版本的Bug较多,需要继续修改,是测试版本

  2. beta:基本稳定,相对于Alpha版已经有了很大的进步,消除了严重错误

  3. rc:和正式版基本相同,基本上不存在导致错误的Bug

  4. release:最终版本

版本号

每个npm包的版本号都是唯一的,我们每次更新npm包后,都是需要更新版本号,否则会报错提醒:

版本号报错

当主版本号升级后,次版本号和修订号需要重置为0,次版本号进行升级后,修订版本需要重置为0。

但是如果每次都要手动来更新版本号,那可就太麻烦了;那么是否有命令行能来自动更新版本号呢?由于版本号的确定依赖于内容决定的主观性的动作,因此不能完全做到全自动化更新,谁知道你是改了大版本还是小版本,因此只能通过命令行实现半自动操作;命令的取值和语义化的版本是对应的,会在相应的版本上加1:

命令行更新版本号

在package.json的一些依赖的版本号中,我们还会看到^~或者>=这样的标识符,或者不带标识符的,这都代表什么意思呢?

  1. 没有任何符号:完全百分百匹配,必须使用当前版本号

  2. 对比符号类的:>(大于)  >=(大于等于) <(小于) <=(小于等于)

  3. 波浪符号~:固定主版本号和次版本号,修订号可以随意更改,例如~2.0.0,可以使用 2.0.0、2.0.2 、2.0.9 的版本。

  4. 插入符号^:固定主版本号,次版本号和修订号可以随意更改,例如^2.0.0,可以使用 2.0.1、2.2.2 、2.9.9 的版本。

  5. 任意版本*:对版本没有限制,一般不用

  6. 或符号:||可以用来设置多个版本号限制规则,例如 >= 3.0.0 || <= 1.0.0

npm包开发

通过上面对package.json的介绍,相信各位小伙伴已经对npm包有了一定的了解,现在我们就进入代码实操阶段,开发并上传一个npm包。

工具类包

相信不少童鞋在业务开发时都会遇到重复的功能,或者开发相同的工具函数,每次遇到时都要去其他项目中拷贝代码;如果一个项目的代码逻辑有优化的地方,需要同步到其他项目,则需要再次挨个项目的拷贝代码,这样不仅费时费力,而且还重复造轮子。

我们可以整合各个项目的需求,开发一个适合自己项目的工具类的npm包,包的结构如下:

hello-npm
|-- lib/(存放打包后的文件)
|-- src/(源码)
|-- package.json
|-- rollup.config.base.js(rollup基础配置)
|-- rollup.config.dev.js(rollup开发配置)
|-- rollup.config.js(rollup正式配置)
|-- README.md
|-- tsconfig.json

首先看下package.json的配置,rollup根据开发环境区分不同的配置:

{"name": "hello-npm","version": "1.0.0","description": "我是npm包的描述","main": "lib/bundle.cjs.js","jsnext:main": "lib/bundle.esm.js","module": "lib/bundle.esm.js","browser": "lib/bundle.browser.js","types": "types/index.d.ts","author": "","scripts": {"dev": "npx rollup -wc rollup.config.dev.js","build": "npx rollup -c rollup.config.js && npm run build:types","build:types": "npx tsc",},"license": "ISC"
}

然后配置rollup的base config文件:

import typescript from "@rollup/plugin-typescript";
import pkg from "./package.json";
import json from "rollup-plugin-json";
import resolve from "rollup-plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import eslint from "@rollup/plugin-eslint";
import { babel } from '@rollup/plugin-babel'
const formatName = "hello";
export default {input: "./src/index.ts",output: [{file: pkg.main,format: "cjs",},{file: pkg.module,format: "esm",},{file: pkg.browser,format: "umd",name: formatName,},],plugins: [json(),commonjs({include: /node_modules/,}),resolve({preferBuiltins: true,jsnext: true,main: true,brower: true,}),typescript(),eslint(),babel({ exclude: "node_modules/**" }),],
};

这里我们将打包成commonjs、esm和umd三种模块规范的包,然后是生产环境的配置,加入terser和filesize分别进行压缩和查看打包大小:

import { terser } from "rollup-plugin-terser";
import filesize from "rollup-plugin-filesize";import baseConfig from "./rollup.config.base";export default {...baseConfig,plugins: [...baseConfig.plugins, terser(), filesize()],
};

然后是开发环境的配置:

import baseConfig from "./rollup.config.base";
import serve from "rollup-plugin-serve";
import livereload from "rollup-plugin-livereload";export default {...baseConfig,plugins: [...baseConfig.plugins,serve({contentBase: "",port: 8020,}),livereload("src"),],
};

环境配置好后,我们就可以打包了

# 测试环境
npm run dev
# 生产环境
npm run build

全局包

还有一类npm包比较特殊,是通过npm i -g [pkg]进行全局安装的,比如常用的vue createstatic-serverpm2等命令,都是通过全局命令安装的;那么全局npm包如何开发呢?

我们来实现一个全局命令的计算器功能,新建一个项目然后运行:

cd my-calc
npm init -y

在package.json中添加bin属性,它是一个对象,键名是告诉node在全局定义一个全局的命令,值则是执行命令的脚本文件路径,可以同时定义多个命令,这里我们定义一个calc命令

{"name": "my-calc","version": "1.0.0","description": "","main": "index.js","scripts": {"test": "echo \"Error: no test specified\" && exit 1"},"bin": {"calc": "./src/calc.js",},"license": "ISC",
}

命令定义好了,我们来实现calc.js中的内容:

#!/usr/bin/env nodeif (process.argv.length <= 2) {console.log("请输入运算的数字");return;
}let total = process.argv.slice(2).map((el) => {let parseEl = parseFloat(el);return !isNaN(parseEl) ? parseEl : 0;}).reduce((total, num) => {total += num;return total;}, 0);console.log(`运算结果:${total}`);

需要注意的是,文件头部的#!/usr/bin/env node是必须的,告诉node这是一个可执行的js文件,如果不写会报错;然后通过process.argv.slice(2)来获取执行命令的参数,前两个参数分别是node的运行路径和可执行脚本的运行路径,第三个参数开始才是命令行的参数,因此我们在命令行运行来看结果:

calc 1 2 3 -4

如果我们的脚本比较复杂,想调试一下脚本,那么每次都需要发布到npm服务器,然后全局安装后才能测试,这样比较费时费力,那么有没有什么方法能够直接运行脚本呢?这里就要用到npm link命令,它的作用是将调试的npm模块链接到对应的运行项目中去,我们也可以通过这个命令把模块链接到全局。

在我们的项目中运行命令:

npm link

可以看到全局npm目录下新增了calc文件,calc命令就指向了本地项目下的calc.js文件,然后我们就可以尽情的运行调试;调试完成后,我们又不需要将命令指向本地项目了,这个时候就需要下面的命令进行解绑操作

npm unlink

解绑后npm会把全局的calc文件删除,这时候我们就可以去发布npm包然后进行真正的全局安装了。

vue组件库

在Vue项目中,我们在很多项目中也会用到公共组件,可以将这些组件提取到组件库,我们可以仿照element-ui来实现一个我们自己的ui组件库;首先来构建我们的项目目录:

|- lib
|- src|- MyButton|- index.js|- index.vue|- index.scss|- MyInput|- index.js|- index.vue|- index.scss|- main.js
|- rollup.config.js

我们构建MyButton和MyInput两个组件,vue文件和scss不再赘述,我们来看下导出组件的index.js:

import MyButton from "./index.vue";MyButton.install = function (Vue) {Vue.component(MyButton.name, MyButton);
};
export default MyButton;

组件导出后在main.js中统一组件注册:

import MyButton from "./MyButton/index.js";
import MyInput from "./MyInput/index";
import { version } from "../package.json";import "./MyButton/index.scss";
import "./MyInput/index.scss";const components = [MyButton, MyInput];const install = function (Vue) {components.forEach((component) => {Vue.component(component.name, component);});
};
if (typeof window !== "undefined" && window.Vue) {install(window.Vue);
}
export { MyButton, MyInput, install };
export default { version, install };

然后配置rollup.config.js:

import resolve from "rollup-plugin-node-resolve";
import vue from "rollup-plugin-vue";
import babel from "@rollup/plugin-babel";
import commonjs from "@rollup/plugin-commonjs";
import scss from "rollup-plugin-scss";
import json from "@rollup/plugin-json";const formatName = "MyUI";
const config = {input: "./src/main.js",output: [{file: "./lib/bundle.cjs.js",format: "cjs",name: formatName,exports: "auto",},{file: "./lib/bundle.js",format: "iife",name: formatName,exports: "auto",},],plugins: [json(),resolve(),vue({css: true,compileTemplate: true,}),babel({exclude: "**/node_modules/**",}),commonjs(),scss(),],
};
export default config;

这里我们打包出commonjs和iife两个模块规范,一个可以配合打包工具使用,另一个可以直接在浏览器中script引入。我们通过rollup-plugin-vue插件来解析vue文件,需要注意的是5.x版本解析vue2,最新的6.x版本解析vue3,默认安装6.x版本;如果我们使用的是vue2,则需要切换老版本的插件,还需要安装以下vue的编译器:

npm install --save-dev vue-template-compiler

打包成功后我们就能看到lib目录下的文件了,我们就能像element-ui一样,愉快的使用自己的ui组件了,在项目中引入我们的UI:

/* 全局引入 main.js */
import MyUI from "my-ui";
// 引入样式
import "my-ui/lib/bundle.cjs.css";Vue.use(MyUI);/* 在组件中按需引入 */
import { MyButton } from "my-ui";
export default {components: {MyButton}
}

如果想要在本地进行调试,也可以使用link命令创建链接,首先在my-ui目录下运行npm link将组件挂载到全局,然后在vue项目中运行下面命令来引入全局的my-ui:

npm link my-ui

我们会看到下面的输出表示vue项目中my-ui模块已经链接到my-ui项目了:

D:\project\vue-demo\node_modules\my-ui
->
C:\Users\XXXX\AppData\Roaming\npm\node_modules\my-ui
->
D:\project\my-ui

npm包发布

我们的npm包完成后就可以准备发布了,首先我们需要准备一个账号,可以使用--registry来指定npm服务器,或者直接使用nrm来管理:

npm adduser
npm adduser --registry=http://example.com

然后进行登录,输入你注册的账号密码邮箱:

npm login

还可以用下面命令退出当前账号

npm logout

如果不知道当前登录的账号可以用who命令查看身份:

npm who am i

登录成功就可以将我们的包推送到服务器上去了,执行下面命令,会看到一堆的npm notice:

npm publish

如果某版本的包有问题,我们还可以将其撤回

npm unpublish [pkg]@[version]

················· 若川简介 ·················

你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》20余篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经写了7篇,点击查看年度总结。
同时,最近组织了源码共读活动,帮助3000+前端人学会看源码。公众号愿景:帮助5年内前端人走向前列。

识别方二维码加我微信、拉你进源码共读

今日话题

略。分享、收藏、点赞、在看我的文章就是对我最大的支持~

从零开始发布自己的NPM包相关推荐

  1. 如何发布自己的NPM包(模块)?

    1.注册NPM 账号 注册地址:https://www.npmjs.com/. 2.初始化自己要发布的项目 搭建本地环境:安装node.js,包含了npm命令. 新建目录,在该目录下,初始化项目:np ...

  2. 如何发布自己的npm包(超详细步骤,博主都在用)

    发布自己的npm超详细步骤 前沿: 从去年毕业,vue掌握的还算熟练应用了,做过的vue项目也有十几个了吧,每次做项目的时候,有些组件老是使用,拷贝来拷贝去的使用.我就想把这些组件封装到一起,以后做项 ...

  3. 注册发布自己的npm包

    1. 注册npm 地址 输入账号,密码,邮箱 然后去邮箱验证,验证完再继续下面的操作,不验证的话,npm publish会报403 2. 创建npm包 1.npm init 生成package.jso ...

  4. npm 查看登陆账号_发布第一个npm包

    这篇文章主要介绍如何发布一个npm包,这个过程并不复杂,学完之后你可以发布任何你喜欢的代码到npm网站上,之后你可以使用npm命令安装在其他项目中. 发布到npm网站之前,你必须在npm网站上创建一个 ...

  5. npm包开发测试与发布

    NPM 包开发测试与发布 NPM 包开发测试与发布 引言 1. 开发步骤 1.1. 项目创建 1.2. 工具类功能实现 1.3. ts文件编译 2. npm包本地测试 2.1. 将npm包文件引入项目 ...

  6. npm收录了哪些包_手把手教你制作一个小而美丽的 npm 包并发布

    第1步:npm账户 你需要一个 npm 账户,如果米有,注册地址是:npmjs.com/signup 第2步:登录 进入你自己电脑的终端(cmd)并输入: npm adduser 也可以使用以下命令: ...

  7. 手撸一个npm包,安利一下duiba-sprite

    背景 我所在组负责我司线上H5互动小游戏的开发,其中一部分开发者负责皮肤的开发.大致流程为:视觉出psd,开发者切图,开发者开发,开发者上传皮肤代码,运营验收.这里边有个奇葩的动作:开发者切图,为什么 ...

  8. 五分钟创建一个自己的NPM包

    创建NPM包 介绍 npm 可以非常方便地发布一个包,比 pip.gem.pear 要简单得多.在发布之前,首先 需要让我们的包符合npm的规范,npm有一套以CommonJS为基础包规范,但与Com ...

  9. webpack创建library及从零开始发布一个npm包

    最近公司有个需求,我们部门开发一个平台项目之后,其他兄弟部门开发出的插件我们可以拿来直接用,并且不需要我们再进行打包,只是做静态的文件引入,研究一波后发现,webpack创建library可以实现. ...

最新文章

  1. R语言进行主成分分析(PCA)、使用prcomp函数进行主成分分析:碎石图可视化(scree plot)、R通过条形图(bar plot)来可视化主成分分析的碎石图(scree plot)
  2. 遗传算法c语言程序,遗传算法c语言代码.doc
  3. w7怎么查看电脑配置_学室内设计,对电脑配置有何要求?不懂戳这!
  4. Mysql之增加数据_INSERT INTO
  5. mysql into_MYSQL中replace into的用法
  6. linux-如何限制普通用户的磁盘使用空间-磁盘配额quota,Linux系统下如何进行磁盘配额Quota的设置...
  7. 浅谈HR谈薪水的技巧
  8. OpenGL ES入门详解
  9. AI产品开发的核心原则:以研究为核心驱动
  10. 白萝卜梨汤止咳防感冒
  11. FusionCharts Free(2)
  12. 注册表修改服务器连接数量,如何通过Win10注册表更改时间服务器参数值?
  13. matlab配置VLFeat
  14. 微信emoji表情json文档
  15. C++三种方法求解两个数最大公因数和最小公倍数
  16. Android新浪微博分页加载,使用LoadMoreWrapper为RecyclerView实现分页加载
  17. 坐火车硬座20小时是怎样的体验?
  18. 斑马打印机 android驱动,斑马ZC300驱动-斑马Zebra ZC300打印机驱动下载 v01.03.00官方版 - 51驱动网...
  19. K8s - 札记 - 脑裂
  20. 软件测试人员必备思维,软件测试人员的思维

热门文章

  1. java 循环标记_深入浅析Java 循环中标签的作用
  2. 卸载失败_Windows 10可能的新功能-自动卸载失败的补丁更新
  3. 前后端分离的项目部署到tomcat_如何在开发时部署和运行前后端分离的JavaWeb项目...
  4. 医学计算机应用研究的意义,医学图像感兴趣区域的自动提取-计算机应用研究.PDF...
  5. 转载------------java equals 方法
  6. Cocos2d-x v3.0物理系统 利用PhysicsEditor创建多边形
  7. poj1969---找规律
  8. hdu 5045 Contest(状态压缩DP)
  9. HDU 2818 Building Block
  10. Ajax 模糊查询的简单实现