热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

前端基础建设与架构03CI环境上的npm优化及更多工程化问题解析

前两讲,我们围绕着npm和Yarn的核心原理展开了讲解,实际上npm和Yarn涉及项目的方方面面,加之本身设计复杂度较高,这

前两讲,我们围绕着 npm 和 Yarn 的核心原理展开了讲解,实际上 npm 和 Yarn 涉及项目的方方面面,加之本身设计复杂度较高,这一讲我将继续讲解 CI 环境上的 npm 优化以及更多工程化相关问题。希望通过这一讲的学习你能够学习到 CI 环境上使用包管理工具的方方面面,并能够解决非本地环境下(一般是在容器上)使用包管理工具解决相关问题。


CI 环境上的 npm 优化

CI 环境下的 npm 配置和开发者本地 npm 操作有些许不同,接下来我们一起看看 CI 环境上的 npm 相关优化。


合理使用 npm ci 和 npm install

顾名思义,npm ci 就是专门为 CI 环境准备的安装命令,相比 npm install 它的不同之处在于:


  • npm ci 要求项目中必须存在 package-lock.json 或 npm-shrinkwrap.json;

  • npm ci 完全根据 package-lock.json 安装依赖,这可以保证整个开发团队都使用版本完全一致的依赖;

  • 正因为 npm ci 完全根据 package-lock.json 安装依赖,在安装过程中,它不需要计算求解依赖满足问题、构造依赖树,因此安装过程会更加迅速;

  • npm ci 在执行安装时,会先删除项目中现有的 node_modules,然后全新安装;

  • npm ci 只能一次安装整个项目所有依赖包,无法安装单个依赖包;

  • 如果 package-lock.json 和 package.json 冲突,那么 npm ci 会直接报错,并非更新 lockfiles;

  • npm ci 永远不会改变 package.json 和 package-lock.json。

基于以上特性,我们在 CI 环境使用 npm ci 代替 npm install,一般会获得更加稳定、一致和迅速的安装体验


更多 npm ci 的内容你也可以在官网查看。



使用 package-lock.json 优化依赖安装时间

上面提到过,对于应用项目,建议上传 package-lock.json 到仓库中,以保证依赖安装的一致性。事实上,如果项目中使用了 package-lock.json 一般还可以显著加速依赖安装时间。这是因为package-lock.json 中已经缓存了每个包的具体版本和下载链接,你不需要再去远程仓库进行查询,即可直接进入文件完整性校验环节,减少了大量网络请求

除了上面所述内容,CI 环境上,缓存 node_modules 文件也是企业级使用包管理工具常用的优化做法。


更多工程化相关问题解析

下面这部分,我将通过剖析几个问题,来加深你对这几讲学习概念的理解,以及对工程化中可能遇到的问题进行预演。


为什么要 lockfiles,要不要提交 lockfiles 到仓库?

从 npm v5 版本开始,增加了 package-lock.json 文件。我们知道package-lock.json 文件的作用是锁定依赖安装结构,目的是保证在任意机器上执行 npm install 都会得到完全相同的 node_modules 安装结果

你需要明确,为什么单一的 package.json 不能确定唯一的依赖树:


  • 不同版本的 npm 的安装依赖策略和算法不同;

  • npm install 将根据 package.json 中的 semver-range version 更新依赖,某些依赖项自上次安装以来,可能已发布了新版本。

因此,保证能够完整准确地还原项目依赖,就是 lockfiles 出现的原因。

首先我们了解一下 package-lock.json 的作用机制。上一讲中我们已经解析了 yarn.lock 文件结构,这里我们看下 package-lock.json 的内容举例:

"@babel/core": {"version": "7.2.0","resolved": "http://www.npm.com/@babel%2fcore/-/core-7.2.0.tgz","integrity": "sha1-pN04FJAZmOkzQPAIbphn/voWOto=","dev": true,"requires": {"@babel/code-frame": "^7.0.0",// ...},"dependencies": {"@babel/generator": {"version": "7.2.0","resolved": "http://www.npm.com/@babel%2fgenerator/-/generator-7.2.0.tgz","integrity": "sha1-6vOCH6AwHZ1K74jmPUvMGbc7oWw=","dev": true,"requires": {"@babel/types": "^7.2.0","jsesc": "^2.5.1","lodash": "^4.17.10","source-map": "^0.5.0","trim-right": "^1.0.1"}},// ...}},// ...
}

通过上述代码示例,我们看到:一个 package-lock.json 的 dependency 主要由以下部分构成。


  • Version:依赖包的版本号

  • Resolved:依赖包安装源(可简单理解为下载地址)

  • Integrity:表明包完整性的 Hash 值

  • Dev:表示该模块是否为顶级模块的开发依赖或者是一个的传递依赖关系

  • requires:依赖包所需要的所有依赖项,对应依赖包 package.json 里 dependencies 中的依赖项

  • dependencies:依赖包 node_modules 中依赖的包(特殊情况下才存在)

事实上,并不是所有的子依赖都有 dependencies 属性,只有子依赖的依赖和当前已安装在根目录的 node_modules 中的依赖冲突之后,才会有这个属性。这就涉及嵌套情况的依赖管理,我已经在前文做了说明。

至于要不要提交 lockfiles 到仓库?这就需要看项目定位决定了。


  • 如果开发一个应用,我建议把 package-lock.json 文件提交到代码版本仓库。这样可以保证项目组成员、运维部署成员或者 CI 系统,在执行 npm install 后,能得到完全一致的依赖安装内容。

  • 如果你的目标是开发一个给外部使用的库,那就要谨慎考虑了,因为库项目一般是被其他项目依赖的,在不使用 package-lock.json 的情况下,就可以复用主项目已经加载过的包,减少依赖重复和体积

  • 如果我们开发的库依赖了一个精确版本号的模块,那么提交 lockfiles 到仓库可能会造成同一个依赖不同版本都被下载的情况。如果作为库开发者,真的有使用某个特定版本依赖的需要,一个更好的方式是定义 peerDependencies

因此,一个推荐的做法是:把 package-lock.json 一起提交到代码库中,不需要 ignore。但是执行 npm publish 命令,发布一个库的时候,它应该被忽略而不是直接发布出去

理解上述概念并不够,对于 lockfiles 的处理,你需要更加精细。这里我列出几条建议供你参考。


  1. 早期 npm 锁定版本的方式是使用 npm-shrinkwrap.json,它与 package-lock.json 不同点在于:npm 包发布的时候默认将 npm-shrinkwrap.json 发布,因此类库或者组件需要慎重。

  2. 使用 package-lock.json 是 npm v5.x 版本新增特性,而 npm v5.6 以上才逐步稳定,在 5.0 - 5.6 中间,对 package-lock.json 的处理逻辑进行过几次更新。

  3. 在 npm v5.0.x 版本中,npm install 时都会根据 package-lock.json 文件下载,不管 package.json 内容究竟是什么。

  4. npm v5.1.0 版本到 npm v5.4.2,npm install 会无视 package-lock.json 文件,会去下载最新的 npm 包并且更新 package-lock.json。

  5. npm 5.4.2 版本后:


  • 如果项目中只有 package.json 文件,npm install 之后,会根据它生成一个 package-lock.json 文件;

  • 如果项目中存在 package.json 和 package-lock.json 文件,同时 package.json 的 semver-range 版本 和 package-lock.json 中版本兼容,即使此时有新的适用版本,npm install 还是会根据 package-lock.json 下载;

  • 如果项目中存在 package.json 和 package-lock.json 文件,同时 package.json 的 semver-range 版本和 package-lock.json 中版本不兼容,npm install 时 package-lock.json 将会更新到兼容 package.json 的版本;

  • 如果 package-lock.json 和 npm-shrinkwrap.json 同时存在于项目根目录,package-lock.json 将会被忽略。

以上内容你可以结合 01 讲中 npm 安装流程进一步理解。


为什么有 xxxDependencies?

npm 设计了以下几种依赖类型声明:


  • dependencies 项目依赖

  • devDependencies 开发依赖

  • peerDependencies 同版本依赖

  • bundledDependencies 捆绑依赖

  • optionalDependencies 可选依赖

它们起到的作用和声明意义各不相同。dependencies 表示项目依赖,这些依赖都会成为线上生产环境中的代码组成部分。当它关联的 npm 包被下载时,dependencies 下的模块也会作为依赖,一起被下载

devDependencies 表示开发依赖,不会被自动下载,因为 devDependencies 一般只在开发阶段起作用或只是在开发环境中需要用到。比如 Webpack,预处理器 babel-loader、scss-loader,测试工具 E2E、Chai 等,这些都是辅助开发的工具包,无须在生产环境使用。

这里需要特别说明的是:并不是只有在 dependencies 中的模块才会被一起打包,而在 devDependencies 中的依赖一定不会被打包。实际上,依赖是否被打包,完全取决于项目里是否被引入了该模块。dependencies 和 devDependencies 在业务中更多的只是一个规范作用,我们自己的应用项目中,使用 npm install 命令安装依赖时,dependencies 和 devDependencies 内容都会被下载。

peerDependencies 表示同版本依赖,简单来说就是:如果你安装我,那么你最好也安装我对应的依赖。举个例子,假设 react-ui@1.2.2 只提供一套基于 React 的 UI 组件库,它需要宿主环境提供指定的 React 版本来搭配使用,因此我们需要在 React-ui 的 package.json 中配置:

"peerDependencies": {"React": "^17.0.0"
}

举一个场景实例,对于插件类 (Plugin) 项目,比如我开发一个 Koa 中间件,很明显这类插件或组件脱离(Koa)本体是不能单独运行且毫无意义的,但是这类插件又无须声明对本体(Koa)的依赖声明,更好的方式是使用宿主项目中的本体(Koa)依赖。这就是peerDependencies 主要的使用场景。这类场景有以下特点:


  • 插件不能单独运行

  • 插件正确运行的前提是核心依赖库必须先下载安装

  • 我们不希望核心依赖库被重复下载

  • 插件 API 的设计必须要符合核心依赖库的插件编写规范

  • 在项目中,同一插件体系下,核心依赖库版本最好相同

bundledDependencies 和 npm pack 打包命令有关。假设 package.json 中有如下配置:

{"name": "test","version": "1.0.0","dependencies": {"dep": "^0.0.2",...},"devDependencies": {..."devD1": "^1.0.0"},"bundledDependencies": ["bundleD1","bundleD2"]
}

在执行 npm pack 时,就会产出一个 test-1.0.0.tgz 压缩包,且该压缩包中包含了 bundle D1 和 bundle D2 两个安装包。业务方使用 npm install test-1.0.0.tgz 命令时,也会安装 bundle D1 和 bundle D2。

这里你需要注意的是:在 bundledDependencies 中指定的依赖包,必须先在 dependencies 和 devDependencies 声明过,否则在 npm pack 阶段会进行报错

optionalDependencies 表示可选依赖,就是说即使对应依赖项安装失败了,也不会影响整个安装过程。一般我们很少使用到它,这里我也不建议大家使用,因为它大概率会增加项目的不确定性和复杂性

学习了以上内容,现在你已经知道 npm 规范中的相关依赖声明含义了,接下来我们再谈谈版本规范,帮助你进一步解析依赖库锁版本行为。


再谈版本规范——依赖库锁版本行为解析

npm 遵循 SemVer 版本规范,具体内容你可以参考语义化版本 2.0.0,这里不再展开。这部分内容我希望聚焦到工程建设的一个细节点上——依赖库锁版本行为。

Vue 官方有这样的内容:


每个 vue 包的新版本发布时,一个相应版本的 vue-template-compiler 也会随之发布。编译器的版本必须和基本的 vue 包保持同步,这样 vue-loader 就会生成兼容运行时的代码。这意味着你每次升级项目中的 vue 包时,也应该匹配升级 vue-template-compiler。


据此,我们需要考虑的是:作为库开发者,如何保证依赖包之间的强制最低版本要求?

我们先看看 create-react-app 的做法,在 create-react-app 的核心 react-script 当中,它利用 verify PackageTree 方法,对业务项目中的依赖进行比对和限制。源码如下:

function verifyPackageTree() {const depsToCheck = ['babel-eslint','babel-jest','babel-loader','eslint','jest','webpack','webpack-dev-server',];const getSemverRegex = () =>/\bv?(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[\da-z-]+(?:\.[\da-z-]+)*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?\b/gi;const ownPackageJson = require('../../package.json');const expectedVersionsByDep = {};depsToCheck.forEach(dep => {const expectedVersion = ownPackageJson.dependencies[dep];if (!expectedVersion) {throw new Error('This dependency list is outdated, fix it.');}if (!getSemverRegex().test(expectedVersion)) {throw new Error(`The ${dep} package should be pinned, instead got version ${expectedVersion}.`);}expectedVersionsByDep[dep] = expectedVersion;});

let currentDir = __dirname;

while (true) {
const previousDir = currentDir;
currentDir = path.resolve(currentDir, ‘…’);
if (currentDir === previousDir) {
// We’ve reached the root.
break;
}
const maybeNodeModules = path.resolve(currentDir, ‘node_modules’);
if (!fs.existsSync(maybeNodeModules)) {
continue;
}
depsToCheck.forEach(dep => {
const maybeDep = path.resolve(maybeNodeModules, dep);
if (!fs.existsSync(maybeDep)) {
return;
}
const maybeDepPackageJson = path.resolve(maybeDep, ‘package.json’);
if (!fs.existsSync(maybeDepPackageJson)) {
return;
}
const depPackageJson = JSON.parse(
fs.readFileSync(maybeDepPackageJson, ‘utf8’)
);
const expectedVersion = expectedVersionsByDep[dep];
if (!semver.satisfies(depPackageJson.version, expectedVersion)) {
console.error(//…);
process.exit(1);
}
});
}
}

根据上述代码,我们不难发现,create-react-app 会对项目中的 babel-eslint、babel-jest、babel-loader、ESLint、Jest、webpack、webpack-dev-server 这些核心依赖进行检索——是否符合 create-react-app 对这些核心依赖的版本要求。如果不符合依赖版本要求,那么 create-react-app 的构建过程会直接报错并退出

create-react-app 这么做的理由是:需要上述依赖项的某些确定版本,以保障 create-react-app 源码的相关功能稳定

我认为这样做看似强硬且无理由,实则是对前端社区、npm 版本混乱现象的一种妥协。这种妥协确实能保证 create-react-app 的正常构建工作。因此现阶段来看,也不失为一种值得推荐的做法。而作为 create-react-app 的使用者,我们依然可以通过 SKIP_PREFLIGHT_CHECK 这个环境变量,跳过核心依赖版本检查,对应源码:

const verifyPackageTree = require('./utils/verifyPackageTree');
if (process.env.SKIP_PREFLIGHT_CHECK !== 'true') {verifyPackageTree();
}

create-react-app 的锁版本行为无疑彰显了目前前端社区中工程依赖问题的方方面面,从这个细节管中窥豹,希望能引起你更深入的思考。


最佳实操建议

前面我们讲了很多 npm 的原理和设计理念,理解了这些内容,你应该能总结出一个适用于团队的最佳实操建议。对于实操我有以下想法,供你参考。


  1. 优先使用 npm v5.4.2 以上的 npm 版本,以保证 npm 的最基本先进性和稳定性。

  2. 项目的第一次搭建使用 npm install 安装依赖包,并提交 package.json、package-lock.json,而不提交 node_modules 目录。

  3. 其他项目成员首次 checkout/clone 项目代码后,执行一次 npm install 安装依赖包。

  4. 对于升级依赖包的需求:


  • 依靠 npm update 命令升级到新的小版本;

  • 依靠 npm install @ 升级大版本;

  • 也可以手动修改 package.json 中版本号,并执行 npm install 来升级版本;

  • 本地验证升级后新版本无问题,提交新的 package.json、package-lock.json 文件。


  1. 对于降级依赖包的需求:执行 npm install @ 命令,验证没问题后,提交新的 package.json、package-lock.json 文件。

  2. 删除某些依赖:


  • 执行 npm uninstall 命令,验证没问题后,提交新的 package.json、package-lock.json 文件;

  • 或者手动操作 package.json,删除依赖,执行 npm install 命令,验证没问题后,提交新的 package.json、package-lock.json 文件。


  1. 任何团队成员提交 package.json、package-lock.json 更新后,其他成员应该拉取代码后,执行 npm install 更新依赖。

  2. 任何时候都不要修改 package-lock.json。

  3. 如果 package-lock.json 出现冲突或问题,建议将本地的 package-lock.json 文件删除,引入远程的 package-lock.json 文件和 package.json,再执行 npm install 命令。

如果以上建议你都能理解,并能够解释其中缘由,那么这三讲内容,你已经大致掌握了。


总结

通过本讲学习,相信你已经掌握了在 CI 环境中优化包管理器的方法以及更多、更全面的 npm 设计规范。希望不管是在本地开发,还是 CI 环境中,你在面对包管理方面的问题时能够游刃有余,轻松面对。

前端基建 金句.png

随着前端的发展,npm/Yarn 也在互相借鉴,不断改进,比如 npm v7 会带来一流的 Monorepo 支持。历史总是螺旋式前进,其间可能出现困局和曲折,但是对前端从业人员来说,时刻保持对工程化理念的学习,抽丝剥茧、理清概念,必能从中受益。

npm/Yarn 相关的话题不是一个独立的点,它是成体系的一个面,甚至可以算得上是一个完整的生态。这部分知识我们虽没有面面俱到,但是聚焦在依赖管理、安装机制、CI 提效等话题上。更多 npm 的内容,比如 npm scripts、公共库相关设计、npm 发包、npm 安全、package.json 等话题我会在后面章节中也会继续讲解,希望你能坚持学习。

不管是本地开发环境还是 CI 环境,不管是使用 npm 还是 Yarn,都离不开构建工具。下一讲我会带你对比主流构建工具,继续深入工程化和基建的深水区。我们下一讲再见。




精选评论


*仔:

老师,看了你的文章有一个问题想咨询一下,项目仓库里面有 package-lock.json;本地开发环境(node10+,npm6+)npm install 不会没有报错;但是在 Jenkins 打包环境(node8+,npm5+) npm install 偶尔会出现下面这种类型的错误; npm ERR! code EINTEGRITYnpm ERR! sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA== integrity checksum failed when using sha512: wanted sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA== but got sha512-WXI95kpJrxw4Nnx8vVI90PuUhrQjnNgghBl5tn54rUNKZYbxv+4ACxUzPVpJEtWxKmeDwnQrzjc0C2bYmRJVKg==. (65117 bytes) npm ERR! A complete log of this run can be found in:npm ERR! /home/ubuntu/.npm/_logs/2017-11-29T05_33_52_182Z-debug.log想问一下老师,这种情况一般从哪些方向思考:1.网络不稳定导致下载的依赖不完整?2.npm 版本不同导致?3.本地开发是 window10,打包服务器是 ubuntu,系统不同 npm 根据 package-lock.json ji计算依赖包的 hash 不同导致?



    讲师回复:

    首先对齐一下 CI 机器上和本地的各环境版本,包括 node npm 等



**茏:

可以讲讲pnpm吗,感觉挺好用的,下载也比较可观,但还是不太敢在项目开发中使用



    讲师回复:

    pnpm 非常不错,npm7 和 pnpm 其实是我认为设计上目前最好的了,纯设计上。可以大胆在项目中尝试下。



**宇:

如果我们开发的库依赖了一个精确版本号的模块,那么提交 lockfiles 到仓库可能会造成同一个依赖不同版本都被下载的情况。如果作为库开发者,真的有使用某个特定版本依赖的需要,一个更好的方式是定义 peerDependencies。这样做的话如果遇到了不符合规则的 peerDenpendencies,npm 也会正常安装使用,那怎么确保版本号精确



    讲师回复:

    会有 warning,业务方处理



*滨:

摘录:历史总是螺旋式前进,其间可能出现困局和曲折,但是对前端从业人员来说,时刻保持对工程化理念的学习,抽丝剥茧、理清概念,必能从中受益。赞



*鑫:

怎么看待yarn v2的巨大变化😀



    讲师回复:

    正向进化



**的小叶酱:

现在项目里,yarn和npm都在使用,请问更推荐使用哪个呢



    讲师回复:

    目前看区别不大,我个人喜欢 yarn



L:

bundledDependencies 的意义是什么?如果只是需要这个依赖得话,那为什么不直接使用 dependencies 呢?



    讲师回复:

    恰好这里有个人跟你疑问一样:https://stackoverflow.com/questions/11207638/advantages-of-bundleddependencies-over-normal-dependencies-in-npm 简单来说 使用 bundledDependencies,直接把我的依赖打包进来,这是最安全的——不管 npm 机制怎么变,我都是最安全的,直接打包我的依赖



**3335:

我们的CI/CD环境,能缓存上次构建的node_modules目录。那如果换成npm ci 安装,先删除 node_modules。会不会反而减慢了下载依赖的速度?



    讲师回复:

    是存在这种可能的,一般缓存肯定会更快,如果单纯从头安装的话,CI 依赖确定的依赖树,会有速度优势



推荐阅读
  • Android系统移植与调试之如何修改Android设备状态条上音量加减键在横竖屏切换的时候的显示于隐藏
    本文介绍了如何修改Android设备状态条上音量加减键在横竖屏切换时的显示与隐藏。通过修改系统文件system_bar.xml实现了该功能,并分享了解决思路和经验。 ... [详细]
  • 基于PgpoolII的PostgreSQL集群安装与配置教程
    本文介绍了基于PgpoolII的PostgreSQL集群的安装与配置教程。Pgpool-II是一个位于PostgreSQL服务器和PostgreSQL数据库客户端之间的中间件,提供了连接池、复制、负载均衡、缓存、看门狗、限制链接等功能,可以用于搭建高可用的PostgreSQL集群。文章详细介绍了通过yum安装Pgpool-II的步骤,并提供了相关的官方参考地址。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了Perl的测试框架Test::Base,它是一个数据驱动的测试框架,可以自动进行单元测试,省去手工编写测试程序的麻烦。与Test::More完全兼容,使用方法简单。以plural函数为例,展示了Test::Base的使用方法。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • 关键词:Golang, Cookie, 跟踪位置, net/http/cookiejar, package main, golang.org/x/net/publicsuffix, io/ioutil, log, net/http, net/http/cookiejar ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • r2dbc配置多数据源
    R2dbc配置多数据源问题根据官网配置r2dbc连接mysql多数据源所遇到的问题pom配置可以参考官网,不过我这样配置会报错我并没有这样配置将以下内容添加到pom.xml文件d ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
author-avatar
JSHGDF5649
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有