如何在gitlab上发布npm包

活着,见天地,见众生,见自己

大家好,我是柒八九 。一个专注于前端开发技术/RustAI应用知识分享Coder

前言

在上一篇文章(环境变量:熟悉的陌生人)中我们就提到过,最近在做gitlab上发布私有npm的事情。

大家都很清楚,为了提高开发效率,我们会利用各种千奇百怪的方式将一些公共的工具方法或者API进行封装,然后发布的团队成员可以探查到的地方。其中,最常用的方式就是将其构建成一个npm包然后发布到npm公共仓库 (我们之前写的f_cli就是如此)。但是呢,有一些工具库可能会涉及公司内部信息,我们将其发布到公共仓库就不合适了。此时,我们就需要将npm发布到内网环境。

今天呢,我们就来讲讲如何在gitlab上发布npm包

好了,天不早了,干点正事哇。

我们能所学到的知识点

  1. 初始化项目
  2. 创建gitlab仓库
  3. 手动发布
  4. Semantic-release自动发布
  5. 本地项目使用私有包

1. 初始化项目

这里我们用一个比较简单的项目来做演示。如果想了解一个功能全备的前端项目都有啥,可以参考之前的文章前端项目里都有啥?

npm init

选择你认为合适的目录(这里我们直接使用demo目录)。执行下面命令

shell 复制代码
mkdir demo
cd tool
npm init

下面是我们习以为常的初始化项目的流程,其中有些值需要按照自己的项目而定,或者一路回车也可以。

安装依赖

我们应该安装一些必需和可选的开发依赖项,这将帮助我们轻松构建包。

  • webpack,这是一个模块打包程序,webpack-cli是一个使用webpack的命令行工具。 通过使用webpack,我们使用babel-loader在打包之前将我们的 ES6 代码转译为 ES5。(在这个项目中我们采用webpack做为打包构建工具,当然你也可以选择使用vite。这都是看个人喜好。)

    shell 复制代码
    npm i --save-dev webpack webpack-cli @babel/core babel-loader
  • jest用于编写 jest 测试用例(可选)。

    shell 复制代码
    npm i --save-dev jest
  • prettiereslint-plugin-prettiereslint-config-prettier用于规范和格式化我们的代码(可选)。

    shell 复制代码
    npm i --save-dev prettier eslint-plugin-prettier eslint-config-prettier
  • documentation用于自动生成文档(可选)。

    shell 复制代码
    npm i --save-dev documentation

初始化git 仓库

通过git init初始化git仓库并且通过配置.gitignore来忽略一下文件。像环境变量:熟悉的陌生人介绍过的环境变量的配置文件.env就应该被忽略掉。

bash 复制代码
.DS_Store 
# node模块
node_modules/  

# 日志文件  
npm-debug.log*

# 编辑器设置
.vscode

# 生产环境/发布
/dist 

# 测试覆盖率
/coverage

# 环境变量
.env.*

.DS_StoreMac OS 系统自动生成的隐藏文件,用于存储文件夹的自定义属性,如文件夹的图标位置或背景颜色等设置。

  1. 它是 Mac 独有的,其他系统如 Windows 不会自动生成此文件。

  2. 每个文件夹下都会生成一个 .DS_Store 文件,用于存储该文件夹的设置。

  3. 对系统和其他程序没有影响,可以安全删除,但会丢失文件夹的自定义设置。

  4. 该文件不参与版本控制,通常会在 .gitignore 文件中忽略。

  5. 在打包分发程序或共享文件夹时,应该删除 .DS_Store 文件,避免泄露隐私或造成兼容性问题。

    所以简单来说,.DS_Store 就是一个 Mac 系统使用的设置文件,对开发和分发代码没有实际作用,应该添加到忽略文件中去。

配置项目

正如我们在图片中看到的,我们的项目包含了很多文件和文件夹。现在让我们解释每一个的内容以及它们的用途。(更具体的可以参考之前的前端项目里都有啥?)

配置Prettier + ESLint

  • Prettier 用于自动格式化我们的代码
  • ESLint 确保我们的代码风格保持良好的形式

我们可以通过配置.eslintigonre /.eslintrc.json / .prettierogonre / .prettierrc.js等文件来设置Prettier + ESLint

.eslintigonre

bash 复制代码
# 忽略第三方依赖
node_modules 

# 忽略配置文件
.eslintrc.js
.prettierrc.js

# 忽略构建输出
dist
build
lib

# 忽略检查单元测试的覆盖率报告
coverage

# 忽略文档输出
docs

.eslintrc.json

json 复制代码
{
    // 配置 ESLint 解析器的选项,指定了语法为 ES6,源代码类型为 ES module
   "parserOptions": {
     "ecmaVersion": 6, 
     "sourceType": "module"
   },
   "plugins": [
     "prettier" // 使用 eslint-plugin-prettier 插件
   ],
   "rules": {
     "prettier/prettier": [
       "error", 
       { 
         "endOfLine": "off"  
       }
     ] // 调用 prettier/prettier 规则来格式化代码
   } 
}

.prettierogonre

bash 复制代码
# 忽略:
node_modules 

# 忽略构建输出
dist
build
lib

# 忽略测试覆盖率
coverage

# 忽略文档
docs

.prettierrc.js

js 复制代码
module.exports = {
   // 使用单引号
   singleQuote: true,  
   // 对象和数组的末尾添加逗号
   trailingComma: 'all',  
   // 每行最大长度
   printWidth: 120,   
   // 使用4个空格作为缩进
   tabWidth: 4,    
};

配置 webpack.config.js

webpack.config.jsWebpack 配置文件,用于定义如何打包 JavaScript 代码并指定如何输出打包后的文件。

js 复制代码
const path = require("path");

module.exports = {

 // 模式 
 mode: "production",
 
 // 入口文件
 entry: "./index.js",

 // 输出配置
 output: {
   path: path.resolve("dist"),
   filename: "index.js",
   libraryTarget: "commonjs2", 
 },

 // 模块规则
 module: {
   rules: [
     {
       // 对 .js 文件使用 babel-loader 处理
       test: /\.js?$/,
       exclude: /(node_modules)/,
       use: "babel-loader",
     },
   ]
 },

 // 解析模块请求的选项
 resolve: { 
   extensions: [".js"],  
 },
};

项目主逻辑(src/methods)

包含 3 个文件,每个文件导出一个特定几何形状面积的计算公式。当然,在实际场景中我们需要放置我们的业务逻辑,下面只是做一个demo级别的演示。

circleArea.js

js 复制代码
/**
* 计算圆面的面积
* @param {*} raduis 圆Radius
* @returns {number} 面积
*/
function getCircleArea(radius) {
 return Math.PI * radius * radius;
}

module.exports = {
 getCircleArea  
};

rectangleArea.js

js 复制代码
/**
* 计算矩形的面积  
* @param {*} length 长度
* @param {*} width 宽度
* @returns {number} 面积
*/
function getRectangleArea(length, width) {
 return length * width;
}

module.exports = {
 getRectangleArea
};

triangleArea.js

js 复制代码
/** 
* 计算三角形的面积
* @param {*} base 底边长度
* @param {*} perpendicularHight 高
* @returns 面积
*/
function getTriangleArea(base, perpendicularHeight) {
 return base * perpendicularHeight * 0.5;
}

module.exports = {
 getTriangleArea  
};

项目主入口(index.js)

一般而言,我们的包都是以index.js作为主入口。这个可以在package.jsonmain字段中指定。

js 复制代码
const { getCircleArea } = require('./src/methods/circleArea');
const { getRectangleArea } = require('./src/methods/rectangleArea');
const { getTriangleArea } = require('./src/methods/triangleArea');

module.exports = {
    getCircleArea,
    getRectangleArea,
    getTriangleArea,
};

设置单元测试

一个功能完备的项目,单元测试是必不可少的。但是呢,这个也是因人而异的,我们也可以选择不做这步,毕竟有些项目只是一个资源或者工具的封装。因为,我们在平时开发中已经对这些工具方法都做了验证了。

我们将使用 Jest 框架来编写 3 个方法的单元测试。为此,让我们创建/tests 文件夹并开始编写测试用例:

circleArea.test.js

js 复制代码
const { getCircleArea } = require('../index');

test('测试 getCircleArea 是否返回一个真值', () => {
 expect(getCircleArea(1)).toBeTruthy();
});

test('计算半径为 1 的圆的面积,预期结果:1*1*π = π', () => {
 expect(getCircleArea(1)).toBe(Math.PI);  
});

rectangleArea.test.js

js 复制代码
const { getRectangleArea } = require('../index');

test('测试 getRectangleArea 是否返回一个真值', () => {
 expect(getRectangleArea(1, 1)).toBeTruthy();
});

test('计算一个长 2 宽 2 的矩形的面积,预期结果:2*2 = 4', () => {
 expect(getRectangleArea(2, 2)).toBe(4); 
});

triangle.test.js

js 复制代码
const { getTriangleArea } = require('../index');

test('测试 getTriangleArea 是否返回一个真值', () => {
 expect(getTriangleArea(1, 1)).toBeTruthy();
});

test('计算底边长度为 1,高为 2 的三角形的面积,预期结果:1*2*0.5 = 1', () => {
 expect(getTriangleArea(1, 2)).toBe(1);
});

我们可以是在package.jsonscripts字段中新增一段专门用于单元测试的命令,并且在jest中配置关于jest的配置信息。

json 复制代码
{
  "scripts" : {
      "test": "jest --coverage --passWithNoTests" 
  },
  "jest": { 
       "verbose": true,
       "testEnvironment": "node"
  }
}

然后我们通过npm run test执行单元测试


项目文档生成

通过使用文档工具,我们可以根据代码中包含的 jsDoc 注释自动生成代码文档。

json 复制代码
{
  "scripts": {

   // 删除 /docs 文件夹
   "docs:clean": "rimraf docs",  

   // 构建文档
   "docs:build": "npm run docs:clean && documentation build src/** -f html -o docs",   

   // 删除 /dist 文件夹
   "clean": "rimraf dist",

   // 生产模式构建项目
   "build": "npm run clean && webpack --mode production",

   // 准备发布,构建项目和文档
   "prepare": "npm run build && npm run docs:build",

   // 运行测试和覆盖率
   "test": "jest --coverage --passWithNoTests" 

  },
}

现在,我们只需运行相应的脚本,就能轻松地测试、构建和生成项目文档。例如,在构建软件包并准备将其投入生产时,我们只需运行 :

arduino 复制代码
npm run prepare

这将生成两个文件夹 :

  • /dist : 代码的发布版本
  • /docs :包含代码文档

2. 创建gitlab仓库

这一步其实很简单,就是在gitlab中创建存放我们私有包的仓库。

随后,我们将我们本地仓库和gitlab仓库做一下关联。

shell 复制代码
git remote add origin https://gitlab.com/xxx/demo.git
git push --set-upstream origin master

然后我们将本地代码推人到远程仓库中。

shell 复制代码
git add.
git ci -m 'feat: 项目初始化'
git push

这样我们本地代码就和远程代码有了联系。

这一步简单的不能简单了。


3. 手动发布

其实,针对在gitlab中发布npm包有两种方式,

  1. 一种是手动推送,这个每次在本地通过一些命令执行发布操作。
  2. 另外一种是利用Semantic-releaseCI/CD执行发布

下面我们先从简单的来。毕竟,不是所有项目都需要走CI/CD或者有些工具包本身逻辑简单只需要做一次发布,终身不变。

生成令牌

项目创建完成之后,需要生成项目私有的认证令牌 ,我们把demo这个库作为我们要发布的npm包,先生成它的Deploy tokens

token作用:最后发布npm包的时候需要用来认证

我们在Demo项目的主页面的Settings->Repository->Deploy tokens中设置token

在新增界面中Name可以随意起。然后比较重要的是,我们需要勾选read_package_registry/write_package_registry的选项。这样我们这个token就拥有了对package registry的读写权限。

点击Create deploy token后,gitlab就会为为们生成key-value格式的值。我们只关心value。并且,我们需要将value保存起来,因为离开这个页面,这个值就不会显示了。

本地项目新增.npmrc

要从私有注册表(在我们的情况下是Gitlab)安装一个软件包,我们需要告诉npm从哪里安装我们的软件包。为了实现这一点,我们在项目的根目录中创建一个名为.npmrc的配置文件。

.npmrc文件是NPM项目中的配置文件,用于定义NPM在运行命令时的行为设置。通过.npmrc文件,我们可以配置NPM的各种行为,例如设置日志级别、定义包的注册表、配置代理等。这个文件可以帮助你在项目级别或全局级别上自定义NPM的行为,使得NPM命令在执行时按照你的配置进行操作。.npmrc文件采用INI格式,其中包含了一系列的键值对,用于配置NPM的各种选项。

我们需要两个比较重要的信息

  1. @<your-scoop>:registry=https://gitlab.com/api/v4/npm/
  2. //gitlab.com/api/v4/projects/<your_project_id>/packages/npm/:_authToken=${AUTH_TOKEN}

上面有几个参数我们需要变更

  1. <your-scoop>:这里设置我们的想要的名称,这里我们设置为front789
  2. <your_project_id>:这是我们demo项目在gitlab的id,这个我们可以在Settings->General->Project ID获取
  3. AUTH_TOKEN我们使用环境变量来处理,这个在之前的文章中有过介绍。并且该值就是刚刚我们创建并单独保存的deploy tokens
less 复制代码
@front789:registry=https://gitlab.com/api/v4/npm/
//gitlab.com/api/v4/projects/55073819/packages/npm/:_authToken=${AUTH_TOKEN}

package.json 新增publishConfig

json 复制代码
{
  "publishConfig": {
    "@front789:registry": "https://gitlab.com/api/v4/projects/55073819/packages/npm/"
  }
}

针对这块的解释,我们在你真的了解package.json吗?有过介绍,这里不在过多解释。

手动发布npm

其实这步和我们将一个包发布到npm一样。都是通过npm publish进行发布。但是呢,由于我们使用环境变量AUTH_TOKEN)所以我们需要将AUTH_TOKEN放置到命令行参数。

shell 复制代码
AUTH_TOKEN=gldt-xxxx npm publish

如果有如下的结果,就表明我们这个包已经发布成功了。

随后,我们就可以在demo项目中的Deploy->Package Registry中看到发布成功的包

弊端

但是,采用这种方式进行发布包时,有一些弊端。

手动更新版本号

我们都知道在更新包时,我们需要更新版本信息。例如从1.0.0更新到1.1.0等。

但是,采用手动发布时,我们需要手动将项目的版本号进行更改。如果不更改还是用上面的命令(AUTH_TOKEN=gldt-xxxx npm publish)发布时。就会报错。

下图的报错原因就是因为,本地版本号和远程仓库有冲突,然后发生了报错。

手动编译

由于我们这个项目不需要多次进行本地编译,但是有些包的更新,可能涉及到本地打包的流程,但是通过上述操作,我们就需要本地打包,然后再进行上传处理。这就很不智能。

针对上述的种种弊端,我们急需一种更加智能的方式。这就是我们接下来要讲的利用Semantic-release自动发布


4. Semantic-release自动发布

相比之前的手动发布,我们本节中的自动发布是利用了GitlabCI/CD功能,但凡和CI/CD有关,那势必.gitlab-ci.yml肯定是绕不过的坎。(这又是一篇可能大写特写的内容,我们下面就不过多解释)

.gitlab-ci.yml

yml 复制代码
image: node:latest

stages:
    - build
    - test
    - document
    - publish

build:
    stage: build
    script:
        - npm install
        - CI=false npm run prepare
    cache:
        paths:
            - node_modules/
            - dist/
            - src/
            - docs/
    artifacts:
        expire_in: 1 days
        when: on_success
        paths:
            - node_modules/
            - dist/
            - src/

test:
    stage: test
    script:
        - npm run test
    dependencies:
        - build
    cache:
        paths:
            - coverage/
    artifacts:
        expire_in: 1 days
        when: on_success
        paths:
            - coverage/

pages:
    stage: document
    dependencies:
        - build
    script:
        - mkdir .public
        - cp -r docs/* .public
        - mv .public public
    artifacts:
        paths:
            - public
    only:
        - master
        - tags

publish:
    stage: publish
    variables:
        NPM_TOKEN: ${AUTH_TOKEN}

    script:
        - git config --global http.emptyAuth true
        - npm run semantic-release
        - echo "-- publish completed succesfully"
    dependencies:
        - build
        - test
    only:
        - master
        - tags

我们简单解释一下上面代码。它定义了一系列的阶段(stages)和对应的任务(jobs),以及这些任务之间的依赖关系和执行条件。

  1. image: node:latest:指定了使用的Docker镜像,这里使用了最新版本的Node.js镜像。

  2. stages:定义了多个阶段,包括构建(build)、测试(test)、文档生成(document)和发布(publish)。

  3. build:构建阶段的任务,包括安装依赖和运行构建脚本,并且定义了缓存和构件。构建成功后,将node_modules/dist/src/目录作为构件保存,并且设置构件的过期时间为1天。

  4. test:测试阶段的任务,依赖于构建阶段。在构建成功后,运行测试脚本,并且定义了测试覆盖率的缓存和构件。

  5. pages:文档生成阶段的任务,依赖于构建阶段。在构建成功后,将docs/目录下的文件复制到.public目录,并将.public目录重命名为public,然后将public目录作为构件保存。这个任务只在master分支和标签上执行。

  6. publish:发布阶段的任务,依赖于构建和测试阶段。在构建和测试成功后,设置了NPM令牌,并运行语义化版本发布脚本。这个任务只在master分支和标签上执行。

总之,这个配置文件定义了一个完整的CI/CD流程,包括构建、测试、文档生成和发布。它使用了缓存和构件来优化任务的执行效率,并且设置了任务的依赖关系和执行条件,以确保任务按照正确的顺序执行。

我们的流水线包含4个阶段,每个阶段负责执行一个任务。

此时,当我们通过

csharp 复制代码
git add .
git ci -m 'feat: xx'

进行代码提交时,由于设置了.gitlab-ci.yml所以他会自动触发gitlabCI/CD。但是呢,上面的配置有问题。

我们看到publish stage失败了,我们回头看我们的.gitlab-ci-yml配置,发现在publish阶段有一个环境变量 (AUTH_TOKEN),这个AUTH_TOKEN其实就和我们上一节讲的token是一样的。这里就不在过多说明。

其实,在publishscript有一个很明显的命令:

shell 复制代码
npm run semantic-release

这是我们这节的主角。它可以帮助我们实现在gitlab中自动发布包。

semantic-release相关操作

semantic-release:帮助我们根据Git提交来管理何时发布新版本,并且还支持语义化版本。

安装相关依赖

sql 复制代码
npm install semantic-release @semantic-release/git @semantic-release/gitlab @semantic-release/npm --save-dev

配置semantic-release

我们通过.releaserc.json来配置semantic-release的动作。

json 复制代码
{
  "branches": ["master"], // 定义了只有在master分支上的提交才会触发发布流程

  "plugins": [ // 定义了语义化版本发布所使用的插件
    "@semantic-release/commit-analyzer", // 使用commit-analyzer插件来分析提交信息
    "@semantic-release/release-notes-generator", // 使用release-notes-generator插件来生成发布日志
    [
      "@semantic-release/gitlab", // 使用gitlab插件来发布到GitLab
      {
        "assets": [ // 定义了发布时需要包含的文件
          { "path": "index.js", "label": "Module" }, // 发布时包含index.js文件,并标注为Module
          { "path": "README.md", "label": "Documentation" } // 发布时包含README.md文件,并标注为Documentation
        ]
      }
    ],
    "@semantic-release/npm", // 使用npm插件来发布到npm
    [
      "@semantic-release/git", // 使用git插件来提交发布的变更
      {
        "assets": ["package.json"], // 定义了发布时需要提交的文件
        "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" // 提交的提交信息模板,包括版本号和发布日志
      }
    ]
  ]
}

更新package.json

修改main

由于我们最终想要的代码是打包后的代码,所以我们需要修改main,将其指向dist/index.js

这样做是为了在使用semantic-release发布npm包时,确保发布的包中包含了经过构建后的代码而不是源代码。通常,源代码位于项目的根目录,而经过构建后的代码(通常是位于dist/目录下)才是用于实际部署和使用的代码。因此,通过将main字段指向经过构建后的代码文件,可以确保发布的npm包包含了正确的可执行代码,而不是源代码文件。这样做可以提高包的可用性和可靠性,同时也符合npm包的最佳实践。

新增scripts命令

json 复制代码
{
  "scripts"{
    //...
    "semantic-release": "semantic-release"
    //...
  }
}

新增publishConfig

这里的操作和之前的手动发布的情况是一样的,就是设置一套发布规则。

json 复制代码
{
  "publishConfig": {
    "@front789:registry": "https://gitlab.com/api/v4/projects/55073819/packages/npm/"
  }
}

配置.npmrc

此处的配置和手动处理也是一样的。

less 复制代码
@front789:registry=https://gitlab.com/api/v4/npm/
//gitlab.com/api/v4/projects/55073819/packages/npm/:_authToken=${AUTH_TOKEN}

在我们提交代码到gitlab后,在publish阶段还是会报错。

上面提示我们需要在CI中配置GITLAB_TOKEN

配置gitlab 环境变量

GITLAB_TOKEN

作为发布软件包的一部分,semantic-releasepackage.json 中增加版本号。为了让semantic-release能够提交这个更改并推送回 GitLab,流水线(pipeline )需要一个名为 GITLAB_TOKEN 的自定义 CI/CD 变量。

下面是详细的配置过程。这里不在多聊。

NPM_TOKEN

我们可以在Settings->CI/CD->Variables中设置相关的环境变量。

此时我们将变量的key设置为NPM_TOKEN,值的话就是我们之前保存的Deploy Token

AUTH_TOKEN

和配置NPM_TOKEN同样的操作流程。

经过上述的操作,我们就配置了,我们发布npm包需要的各种环境变量。


发布包

由于我们配置了semantic-release,只要我们git push本地代码到gitlab,然后后续所有的流程就交由gitlab负责。

此时,在Build->Pipelines中可以看到部署过程。

经过短时间的等待,就会出现如下结果。

也就是说,我们CI/CD成功了。

那么,如何验证我们的npm包是否发布成功呢。

我们可以在Deploy->Package Registry中进行查看。

每当我们本地push代码到gitlab就会触发一次发布流程。也就是说在Package Registry中就会出现多个版本的npm包


5. 本地项目使用私有包

既然,我们向gitlab发布完私包了,在对应的位置也看到了有包的信息。是不是意味我们可以通过npm/yarn进行安装了呢。

让我们随意在一个新项目(demo_test)中执行安装命令npm i @front789/demo

从错误中看到在执行npm i @front789/demo命令时候,命令行提示在https://registry.npmjs.org不存在@front789/demo

这下是不是恍然大悟了,我们虽然在gitlab上发布了我们的私包,但是在npm i xx的时候,如果额外指定,它是会像我们指定的仓库寻找对应的包。

这里多说几句,我们可以通过nrm来切换和查看我们的npm的源。

使用nrm ls探测到我们项目所用的是npm的源。

那么,我们就需要在我们项目中指定当遇到@front789/demo时候,我们需要从哪里去寻找。这就需要用到.npmrc了。

其实在gitlabPackage Registry中已经给我们提示了。

上面分了两种安装方式

  1. Instance-level
  2. Project-level

其实这两种方式都一样,我们就挑一种来解释。

我们在demo_test项目中新增一个.npmrc。然后配置如下代码。

less 复制代码
@front789:registry=https://gitlab.com/api/v4/projects/55073819/packages/npm/

当我们再次执行npm i @front789/demo时,我们以为我们完事大吉,但是控制台就有报错。

当我们看到401 Unauthorized的错误是不是感觉到似曾相识。我们在利用CI/CD发布包时也遇到过。因为我们在新建项目的时候,就是选择了私有。

相同的处理方式,我们可以利用环境变量来为我们的npm新增权限信息。

我们新增另外一条命令,并且用AUTH_TOKEN作为参数,要求我们在cli中提供必要的授权信息。

less 复制代码
@front789:registry=https://gitlab.com/api/v4/projects/55073819/packages/npm
//gitlab.com/api/v4/projects/55073819/packages/npm/:_authToken=${AUTH_TOKEN}

那么我们就可以在cli中执行AUTH_TOKEN=gldt-xxx npm i @front789/demo

然后在package.json中看到我们发布在gitlab上的私包。

项目验证

既然,我们已经在本地安装了发布在gitlab的私包。虽然在node_modules中能看到包信息,但是我们还是不放心。

所以,我们在demo_test中新增了以index.js,内容如下。

js 复制代码
import pkg from '@front789/demo';
const { getCircleArea } = pkg;

console.log(getCircleArea(10))

然后,我们在使用node index.js进行验证。

完美🎉🎉🎉🎉🎉,这样我们就拥有了一个发布在gitlab上的私包了。


后记

分享是一种态度

全文完,既然看到这里了,如果觉得不错,随手点个赞和"在看"吧。

相关推荐
开发者小天9 小时前
react中useReducer的使用
前端·javascript·react.js
小虎牙0079 小时前
关于Android Compose架构的思考
android·前端·mvvm
Calm55010 小时前
ele表单未输入值提示为英文
前端
爪洼守门员10 小时前
前端性能优化
开发语言·前端·javascript·笔记·性能优化
TOYOAUTOMATON10 小时前
GTH系列模组介绍
前端·目标检测·自动化
2022.11.7始学前端10 小时前
n8n第十节 把Markdown格式的会议纪要发到企微
前端·chrome·n8n
fruge11 小时前
Lodash 源码精读:防抖节流的实现细节与边界场景
前端
yuzhiboyouye11 小时前
怎么熟悉一个web前端项目的业务呢?
前端
GISer_Jing11 小时前
AI在前端营销和用户增长领域应用(待补充)
前端·人工智能