前端工程化

358 阅读18分钟

性能优化方面,Tree Shaking、代码分割、懒加载、CDN加速、Service Workers和Web Vitals指标都是关键点,需要详细说明

性能优化上的工作不仅限于基础技巧,更需要从架构设计、工程化、用户体验深度等多个维度构建高性能系统。以下是进阶方向的优化策略和技术要点:

性能优化体系化建设

  1. 建立性能基线与监控体系

    • 指标埋点:通过 Web Vitals(LCP、FID、CLS、TTI)量化性能表现。

    • 监控工具

      • 前端:Chrome Performance Monitor、Lighthouse CI、WebPageTest。
      • 服务端:Prometheus + Grafana(监控API响应时间、资源加载错误率)。
      • 用户端:Mixpanel/Alice(收集真实用户行为数据,识别慢速页面)。
  2. 构建性能优化SLA(服务等级协议)​

    • 明确业务场景下的性能目标(例如:核心页面首屏时间 ≤ 1.5s,FID ≤ 50ms)。
    • 设计自动化告警规则(如Lighthouse评分低于90分触发通知)

二、代码层面的深度优化

1. Webpack/Vite 优化

动态 Import:按需加载模块(代码分割)

Vue 代码层面的优化

编码阶段

  • 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher

  • 合理使用缓存组件keep-alive

  • 路由懒加载 import引入路由

    • Vue 是单页面应用,可能会有很多的路由引入 ,会使webpcak 打包后的文件很大,当进入首页时,加载的资源过多,页面会出现白屏的情况,不利于用户体验。所以把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件,这样就更加高效了。这样会大大提高首屏显示的速度,但是可能其他的页面的速度就会降下来
  • 图片资源懒加载

    • 对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载
npm install vue-lazyload --save-dev
//在入口文件 man.js 中引入并使用
import VueLazyload from 'vue-lazyload'
Vue.use(VueLazyload)
//在 vue 文件中将 img 标签的 src 属性直接改为 v-lazy ,从而将图片显示方式更改为懒加载显示:
<img v-lazy="/static/img/1.png">
  • 事件的销毁

        created() {
          addEventListener('click', this.click, false)
        },
    beforeDestroy() {
      removeEventListener('click', this.click, false)
    }
    
  • v-for 遍历必须为 item 添加 key( key保证唯一),且避免同时使用 v-if

    • 在列表数据进行遍历渲染时,需要为每一项 item 设置唯一 key 值,方便 Vue.js 内部机制精准找到该条列表数据。当 state 更新时,新的状态值和旧的状态值对比,较快地定位到 diff
    • v-for 比 v-if 优先级高,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候,必要情况下应该替换成 computed 属性
  • 在更多的情况下,使用v-if替代v-show

  • 使用路由懒加载、异步组件

  • 防抖、节流

  • 第三方模块按需导入

  • 长列表性能优化

    • 通过 Object.defineProperty 对数据进行劫持,实现视图响应数据的变化

    • 但有些时候我们的组件就是纯粹的数据展示,不会有任何改变,就不需要 Vue 来劫持我们的数据,在大量数据展示的情况下,这能够很明显的减少组件初始化的时间

    • 可通过 Object.freeze 方法来冻结一个对象,一旦被冻结的对象就再也不能被修改了

      export default {
        data: () => ({
          users: {}
        }),
        async created() {
          const users = await axios.get("/api/users");
          this.users = Object.freeze(users);
        }
      };
      
  • 合理使用computed

  • data层级不要太深:导致响应式在做监听时计算的深度比较多,定位的次数比较多,会导致页面卡顿

打包优化

  • 压缩代码
  • Tree Shaking/Scope Hoisting
  • 使用cdn加载第三方模块
  • 多线程打包happypack
  • splitChunks抽离公共文件
  • sourceMap优化

webpack 配置层面的优化

提取公共代码

如果项目中没有去将每个页面的第三方库和公共模块提取出来,则项目会存在以下问题:

  • 相同的资源被重复加载,浪费用户的流量和服务器的成本。
  • 每个页面需要加载的资源太大,导致网页首屏加载缓慢,影响用户体验

所以需要把公共的代码抽离成单独的文件,来优化以上问题,Webpack 内置了专门用于提取多个Chunk 中的公共部分的插件 CommonsChunkPlugin,项目中 CommonsChunkPlugin 的配置如下

image.png

优化 SourceMap

在项目进行打包后,会将开发中的多个文件代码打包到一个文件中,并且经过压缩、去掉多余的空格、babel编译化后,最终将编译得到的代码会用于线上环境,那么这样处理后的代码和源代码会有很大的差别,当有 bug的时候,我们只能定位到压缩处理后的代码位置,无法定位到开发环境中的代码,对于开发来说不好调式定位问题,因此 sourceMap 出现了,它就是为了解决不好调式代码问题的

  • 开发环境推荐:cheap-module-eval-source-map

  • 生产环境推荐:cheap-module-source-map 原因如下:

  • cheap:源代码中的列信息是没有任何作用,因此我们打包后的文件不希望包含列相关信息,只有行信息能建立打包前后的依赖关系。因此不管是开发环境或生产环境,我们都希望添加 cheap 的基本类型来忽略打包前后的列信息;

  • module :不管是开发环境还是正式环境,我们都希望能定位到bug的源代码具体的位置,比如说某个 Vue 文件报错了,我们希望能定位到具体的 Vue 文件,因此我们也需要 module 配置;

  • soure-map :source-map 会为每一个打包后的模块生成独立的 soucemap 文件 ,因此我们需要增加source-map 属性;

  • eval-source-map:eval 打包代码的速度非常快,因为它不生成 map 文件,但是可以对 eval 组合使用 eval-source-map 使用会将 map 文件以 DataURL 的形式存在打包后的 js 文件中。在正式环境中不要使用 eval-source-map, 因为它会增加文件的大小,但是在开发环境中,可以试用下,因为他们打包的速度很快

构建结果输出分析

Webpack 输出的代码可读性非常差而且文件非常大,让我们非常头疼。为了更简单、直观地分析输出结果,社区中出现了许多可视化分析工具。这些工具以图形的方式将结果更直观地展示出来,让我们快速了解问题所在。接下来讲解我们在 Vue 项目中用到的分析工具:webpack-bundle-analyzer 项目中 webpack.prod.conf.js 进行配置:

if (config.build.bundleAnalyzerReport) {
  var BundleAnalyzerPlugin =   require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
  webpackConfig.plugins.push(new BundleAnalyzerPlugin());
}

执行 $ npm run build --report 后生成分析报告如下

image.png

开启 gzip 压缩

compression-webpack-plugin、在webpack.prod.conf.js中的配置

image.png 配置完后执行build命令,会发现生成的静态文件里面新增了后缀为gz的文件

如果此时将项目部署到已开启了gzip的服务器如nginx里面之后,访问浏览器即可看到浏览器下载的是已压缩的文件

image.png 服务器配置gzip,只是为了可以兼容这种格式,可以读取这种格式,压缩为gzip还是需要前端做的2:服务器把gzip发送给浏览器,浏览器认识这种格式所以能解读他3:用处就是用户获取的文件体积减小了,下载的快了

基础的 Web 技术层面的优化

浏览器缓存

为了提高用户加载页面的速度,对静态资源进行缓存是非常必要的,根据是否需要重新向服务器发起请求来分类,将 HTTP 缓存规则分为两大类(强制缓存,对比缓存)

CDN的使用

浏览器从服务器上下载 CSS、js 和图片等文件时都要和服务器连接,而大部分服务器的带宽有限,如果超过限制,网页就半天反应不过来。而 CDN 可以通过不同的域名来加载文件,从而使下载文件的并发连接数大大增加,且CDN 具有更好的可用性,更低的网络延迟和丢包率

用 Chrome Performance 查找性能瓶颈

webpack

//脚手架结构
├── build                       构建服务和webpack配置
    |—— build.js                webpack打包服务
    |—— webpack.base.conf.js    webpack基本通用配置
    |—— webpack.dev.conf.js     webpack开发环境配置
    |—— webpack.prod.conf.js    webpack生产环境配置
├── config                      构建项目不同环境的配置
├── public                      项目打包文件存放目录
├── index.html                  项目入口文件
├── package.json                项目配置文件
├── static       	            静态资源
├── .babelrc                    babel配置文件
├── .gitignore                  git忽略文件
├── postcss.config.js           postcss配置文件
├── src                         项目目录
    |—— page                    页面组件目录
    |—— router                  vue路由配置
    |—— store                   vuex配置
    |—— App.vue                 vue实例入口
    |—— main.js                 项目构建入口

juejin.cn/post/684490…

webpack的作用

  • 模块打包:将不同模块的文件打包整合在一起,并且保证它们之间的引用正确,执行有序。利用打包可以在开发时根据业务自由划分文件模块,保证项目结构的清晰和可读性
  • 编译兼容:因为浏览器兼容,通过webpack的Loader机制,不仅可以对代码做polyfill,还可以编译转换诸如.less, .vue, .jsx这类在浏览器无法识别的格式文件开发时可使用新特性和新语法做开发,提高开发效率
  • 能力扩展。通过webpack的Plugin机制,我们在实现模块化打包和编译兼容的基础上,可以进一步实现诸如按需加载,代码压缩等一系列功能,帮助我们进一步提高自动化程度,工程效率以及打包输出的质量

webpack流程

  1. 初始化:从配置文件读取与合并参数,然后实例化插件new Plugin()
  2. 开始编译:通过上一步获取的参数,初始化一个Complier对象加载插件,执行Compiler.run开始编译
  3. 确定入口:根据配置中entry找出所有入口文件
  4. 编译模块:从entry出发,调用配置的loader,对模块进行转换,同时找出模块依赖的模块,一直递归,一直到找到所有依赖的模块
  5. 完成模块编译:这一步已经使用loader对所有模块进行了转换,得到了转换后的新内容以及依赖关系
  6. 输出资源:根据入口与模块之间的依赖关系,组装成chunk代码块,生成文件输出列表
  7. 输出成功:根据配置中的输出路径还有文件名,把文件写入系统,完成构建

webpack几种hash的实现原理

  • hash是跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的hash值都会更改,并且全部文件都共用相同的hash值。(粒度整个项目)
  • chunkhash是根据不同的入口进行依赖文件解析,构建对应的chunk(模块),生成对应的hash值。只有被修改的chunk(模块)在重新构建之后才会生成新的hash值,不会影响其它的chunk。(粒度entry的每个入口文件)
  • contenthash是跟每个生成的文件有关,每个文件都有一个唯一的hash值。当要构建的文件内容发生改变时,就会生成新的hash值,且该文件的改变并不会影响和它同一个模块下的其它文件。(粒度每个文件的内容)

webpack如果使用了hash命名,那是每次都会重写生成hash吗 3种情况:

  • 如果是hash的话,是和整个项目有关的,有一处文件发生更改则所有文件的hash值都会发生改变且它们共用一个hash值;
  • 如果是chunkhash的话,只和entry的每个入口文件有关,也就是同一个chunk下的文件有所改动该chunk下的文件的hash值就会发生改变
  • 如果是contenthash的话,和每个生成的文件有关,只有当要构建的文件内容发生改变时才会给该文件生成新的hash值,并不会影响其它文件

webpack中如何处理图片的? 在webpack中有两种处理图片的loader:

  • file-loader:解决CSS等中引入图片的路径问题;(解决通过url,import/require()等引入图片的问题)
  • url-loader:当图片小于设置的limit参数值时,url-loader将图片进行base64编码(当项目中有很多图片,通过url-loader进行base64编码后会减少http请求数量,提高性能),大于limit参数值,则使用file-loader拷贝图片并输出到编译目录中

webpack的热更新

主要依赖webpack, express, websocket

  • 使用express启动本地服务,当浏览器访问的时候做出相应
  • 服务端和客户端使用websocket实现长连接
  • webpack监听源文件的变化
    • 每次编译完成之后会生成hash值,已改动模块的json文件,已改动模块代码的js文件
    • 编译完成后通过socket向客户端推送当前编译的hash值
  • 客户端的websocket监听到有文件改动推送过来的hash值,会和上一次进行对比
    • 一致就走缓存
    • 不一致则通过ajax和jsonp获取最新的资源
  • 使用内存文件系统去替换有修改的内容实现局部更新

webpack的打包机制

打包流程

  1. 读取webpack的配置参数;
  2. 启动webpack,创建Compiler对象并开始解析项目;
  3. 从入口文件(entry)开始解析,并且找到其导入的依赖模块,递归遍历分析,形成依赖关系树;
  4. 对不同文件类型的依赖模块文件使用对应的Loader进行编译,最终转为Javascript文件;
  5. 整个过程中webpack会通过发布订阅模式,向外抛出一些hooks,而webpack的插件即可通过监听这些关键的事件节点,执行插件任务进而达到干预输出结果的目的

其中文件的解析与构建是一个比较复杂的过程,在webpack源码中主要依赖于compiler和compilation两个核心对象实现。 compiler对象是一个全局单例,他负责把控整个webpack打包的构建流程

compilation对象是每一次构建的上下文对象,它包含了当次构建所需要的所有信息,每次热更新和重新构建,compiler都会重新生成一个新的compilation对象,负责此次更新的构建过程

而每个模块间的依赖关系,则依赖于AST语法树。每个模块文件在通过Loader解析完成之后,会通过acorn库生成模块代码的AST语法树,通过语法树就可以分析这个模块是否还有依赖的模块,进而继续循环执行下一个模块的编译解析

最终Webpack打包出来的bundle文件是一个IIFE的执行函数

打包处理流程

1 删除掉 devServer 相关的配置项
2 将图片和字体文件输出到指定的文件夹中
3 自动删除dist目录
4 分离第三方包(将使用的vue等第三方包抽离到 vender.js 中)
5 压缩混淆JS 以及 指定生成环境
6 抽取和压缩CSS文件
7 压缩HTML页面
8 配合vue的异步组件,实现按需加载功能

打包机制

webpack的构建流程

Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:

  • 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数
  • 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译
  • 确定入口:根据配置中的 entry 找出所有的入口文件
  • 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
  • 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系
  • 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
  • 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果

简单说

  • 初始化:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler
  • 编译:从 Entry 出发,针对每个 Module 串行调用对应的 Loader 去翻译文件的内容,再找到该 Module 依赖的 Module,递归地进行编译处理
  • 输出:将编译后的 Module 组合成 Chunk,将 Chunk 转换成文件,输出到文件系统中

构建流程,如何提高构建速度

  • 多进程/多实例构建:thread-loader
  • 压缩代码
    • 多进程并行压缩
      • webpack-paralle-uglify-plugin
      • uglifyjs-webpack-plugin 开启 parallel 参数 (不支持ES6)
      • terser-webpack-plugin 开启 parallel 参数
    • 通过 mini-css-extract-plugin 提取 Chunk 中的 CSS 代码到单独文件,通过 css-loader 的 minimize 选项开启 cssnano 压缩 CSS
  • 图片压缩
    • 使用基于 Node 库的 imagemin (很多定制选项、可以处理多种图片格式)
    • 配置 image-webpack-loader
  • 缩小打包作用域:
    • exclude/include (确定 loader 规则范围)
    • resolve.modules 指明第三方模块的绝对路径 (减少不必要的查找)
    • resolve.mainFields 只采用 main 字段作为入口文件描述字段 (减少搜索步骤,需要考虑到所有运行时依赖的第三方模块的入口文件描述字段)
    • resolve.extensions 尽可能减少后缀尝试的可能性
    • noParse 对完全不需要解析的库进行忽略 (不去解析但仍会打包到 bundle 中,注意被忽略掉的文件里不应该包含 import、require、define 等模块化语句)
    • IgnorePlugin (完全排除模块)
    • 合理使用alias
  • 提取页面公共资源:
    • 基础包分离:将公共的第三方包,抽离为一个单独的包文件,这样防止重复打包!
      • 使用 html-webpack-externals-plugin,将基础包通过 CDN 引入,不打入 bundle 中
      • 使用 SplitChunksPlugin 进行(公共脚本、基础包、页面公共文件)分离(Webpack4内置) ,替代了 CommonsChunkPlugin 插件

如:main.js、router、vuex中都引入了vue,不分离的话,vue会被打包3次

抽离后, vue文件只会被打包一次, 用到的地方仅仅是引用

/* webpack.prod.js */

// 1 入口 -- 打包文件的入口
entry: {
  // 项目代码入口
  app: path.join(__dirname, './src/js/main.js'),
  // 第三方包入口
  vendor: ['vue', 'vue-router', 'axios']
},

output: {
  // 2 修改输出文件路径和命名规则
  filename: 'js/[name].[chunkhash].js',
},

plugins: [
  // 3 抽离第三方包
  new webpack.optimize.CommonsChunkPlugin({
    // 将 entry 中指定的 ['vue', 'vue-router', 'axios'] 打包到名为 vendor 的js文件中
    // 第三方包入口名称,对应 entry 中的 vendor 属性
    name: 'vendor',
  }),
]
  • DLL:
    • 使用 DllPlugin 进行分包,使用 DllReferencePlugin(索引链接) 对 manifest.json 引用,让一些基本不会改动的代码先打包成静态资源,避免反复编译浪费时间
    • HashedModuleIdsPlugin 可以解决模块数字id问题
  • 充分利用缓存提升二次构建速度:
    • babel-loader 开启缓存
    • terser-webpack-plugin 开启缓存
    • 使用 cache-loader 或者 hard-source-webpack-plugin
  • 动态Polyfill
    • 建议采用 polyfill-service 只给用户返回需要的polyfill,社区维护。 (部分国内奇葩浏览器UA可能无法识别,但可以降级返回所需全部polyfill)

压缩HTML页面

new htmlWebpackPlugin({
  // 模板页面
  template: path.join(__dirname, './index.html'),

  // 压缩HTML
  minify: {
    // 移除空白
    collapseWhitespace: true,
    // 移除注释
    removeComments: true,
    // 移除属性中的双引号
    removeAttributeQuotes: true
  }
}),

抽取和压缩CSS文件

  • 安装:抽离 npm i -D extract-text-webpack-plugin
  • 安装:压缩 npm i -D optimize-css-assets-webpack-plugin
压缩和抽离CSS报错的说明:
Error processing file: css/style.css
postcss-svgo: Error in parsing SVG: Unquoted attribute value

原因:压缩和抽离CSS的插件中只允许 SVG 使用双引号
/* webpack.prod.js */

// 分离 css 到独立的文件中
const ExtractTextPlugin = require("extract-text-webpack-plugin");
// 压缩 css 资源文件
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')

// bug描述: 生成后面的css文件中图片路径错误,打开页面找不到图片
// 解决:google搜索 webpack css loader 样式图片路径
output: {
  // ...

  // https://doc.webpack-china.org/configuration/output/#output-publicpath
  // 设置公共路径
  publicPath: '/',
},

module: {
  rules: [
    {
      test: /\.css$/,
      use: ExtractTextPlugin.extract({
        fallback: "style-loader",
        use: "css-loader"
      })
    },
    {
      test: /\.scss$/,
      use: ExtractTextPlugin.extract({
        fallback: "style-loader",
        use: ['css-loader', 'sass-loader']
      })
    },
  ]
},
plugins: [
  // 通过插件抽离 css (参数)
  new ExtractTextPlugin("css/style.css"),
  // 抽离css 的辅助压缩插件
  new OptimizeCssAssetsPlugin()
]

loader

常见的Loader webpack.docschina.org/loaders/

  • raw-loader:加载文件原始内容(utf-8)
  • file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体)
  • url-loader:与 file-loader 类似,区别是用户可以设置一个阈值,大于阈值会交给 file-loader 处理,小于阈值时返回文件 base64 形式编码 (处理图片和字体)
  • image-loader:加载并且压缩图片文件
  • json-loader 加载 JSON 文件(默认包含
  • babel-loader:把 ES6 转换成 ES5
  • sass-loader:将SCSS/SASS代码转换成CSS
  • css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
  • style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS
  • postcss-loader:扩展 CSS 语法,使用下一代 CSS,可以配合 autoprefixer 插件自动补齐 CSS3 前缀
  • eslint-loader:通过 ESLint 检查 JavaScript 代码
  • mocha-loader:加载 Mocha 测试用例的代码
  • coverjs-loader:计算测试的覆盖率
  • vue-loader:加载 Vue.js 单文件组件
  • cache-loader: 可以在一些性能开销较大的 Loader 之前添加,目的是将结果缓存到磁盘里

Plugin

webpack.docschina.org/plugins/

  • html-webpack-plugin:简化 HTML 文件创建 (依赖于 html-loader)
  • web-webpack-plugin:可方便地为单页应用输出 HTML,比 html-webpack-plugin 好用
  • uglifyjs-webpack-plugin:不支持 ES6 压缩 (Webpack4 以前)
  • terser-webpack-plugin: 支持压缩 ES6 (Webpack4)
  • webpack-parallel-uglify-plugin: 多进程执行代码压缩,提升构建速度
  • mini-css-extract-plugin: 分离样式文件,CSS 提取为独立文件,支持按需加载 (替代extract-text-webpack-plugin)
  • clean-webpack-plugin: 目录清理
  • ModuleConcatenationPlugin: 开启 Scope Hoisting
  • speed-measure-webpack-plugin: 可以看到每个 Loader 和 Plugin 执行耗时 (整个打包耗时、每个 Plugin 和 Loader 耗时)
  • webpack-bundle-analyzer: 可视化 Webpack 输出文件的体积 (业务组件、依赖第三方模块)

Loader 本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。

因为 Webpack 只认识 JavaScript,所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作。

Plugin 就是插件,基于事件流框架 Tapable,插件可以扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

Loader 在 module.rules 中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性。 Plugin 在 plugins 中单独配置,类型为数组,每一项是一个 Plugin 的实例,参数都通过构造函数传入

工作流: 本地仓库由git维护的三棵树组成:工作目录working,暂存区域index,head:指向最后一次的改动

管理冲突: 开发者本地提交和中央仓库分叉时,git会拒绝推送修改 发布功能前,需要 fetch 更新的中央提交,在它们之上 rebase 自己的更改, 若本地修改和上游提交冲突时,Git 会暂停 rebase 流程,需手动解决这些冲突

查看仓库

  • git status
  • git log --oneline

保存修改/添加和提交

  • git add :添加到暂存区
  • git commit -m '代码提交信息' //实际提交更新 tips:改动已经提交到了 HEAD,但是还没到你的远端仓库

撤销修改

查看之前的commit

  • git checkout
  • git checkout
  • git checkout 撤销公共修改 git revert 撤销本地修改
  • git reset
  • git clean 重写Git历史记录
  • git commit --amend
  • git rebase
  • git reflog

Git协作开发

分支

  • git branch
  • git checkout
  • git merge 仓库同步
  • git remote
  • git fetch :

fetch远程仓库已更新的commit到本地仓库和rebase到已更新的commit的上面 --- git fetch和git rebase 或 git pull --rebase

  • git pull :
  • git push :push本地主分支(master branch)到远程仓库

查看/切换用户

指令操作

git checkout -b 分支名 创建并切换到该分支

git log -a提交记录

git reset --hard 版本号 回退到某一版本

git log --graph --pretty=oneline --abbrev-commit查看分支的合并情况

git log --graph命令可以看到分支合并图

git diff:查看difference即修改内容

git commit -m '':提交更改,把暂存区的所有修改提交到当前分支

git commit -- file 丢弃工作区的修改

git log --pretty=oneline

git reflog:查看所有分支的所有版本号

git reset --hard 版本号:回退到当前版本号

git checkout -b branchName 创建并切换到新分支

git checkout branchName切换到新分支

git switch -c branchName 创建并切换到新分支

git switch branchName 切换分支

git branch当前所在分支

git branch -a 查看远程分支

git branch -D branchName强制删除没有被合并过的分支

git remote查看远程仓库

git remote -v显示更详细信息:如果没有推送权限,就看不到push的地址

从本地推送分支,使用git push origin branch-name,如果推送失败,先用git pull抓取远程的新提交

在本地创建和远程分支对应的分支,使用git checkout -b branch-name origin/branch-name,本地和远程分支的名称最好一致

建立本地分支和远程分支的关联,使用git branch --set-upstream branch-name origin/branch-name;

tag:git中打标签

git tag查看所有标签:默认标签是打在最新提交的commit上的,默认head

或者要打在某次commit上:找到对应的commit id

git tag v0.9 f52c633

git tag tabName 打新标签

git tag -a -m 'tag info'指定标签信息

git show 查看标签信息

git tag -d v0.1 删除本地标签

git push origin :推送某个标签到远程

git push origin --tags一次性推送全部尚未推送到远程的本地标签

git push origin :refs/tags/删除远程标签

查看配置的别名:vim ~/.gitconfig

拉取远程已有分支到本地:

git branch -a 查看本地分支

git fetch 同步远程分支???

git checkout optimize

然后本地就存在optimize这个分支了

image.png

rebase操作

rebase

rebase操作可以把本地未push的分叉提交历史整理成直线 ; rebase的目的是使得我们在查看历史提交的变化时更容易,因为分叉的提交需要三方对比

暂存操作

在不提交当前分支的情况下切换到其它分支进行操作

  • git stash :储存当前分支的修改
  • git stash list :查看储存的修改信息
  • 恢复stash的修改
//方法1:
git stash apply //但恢复后,stash内容并不删除,
git stash list
git stash drop //删除
tips:若分支上多个 stash,如果需要恢复指定的 stash ,可在命令尾部加id,如git stash apply stash@{0},同样删除指定 stash 项目则执行如 git stash drop stash@{1}
方法2:
git stash pop //恢复的同时把 stash 存储列表的内容也删了。这时候再执行git stash list 命令,id 为xxx 的储藏项目不会在列表中。

学习git分支

CI/CD

ci/cd:持续集成/持续部署、构建成功后,自动把代码部署到服务器

CI是:比如代码写好了,前端CI做什么?前端只需要git push代码,然后机器自动执行,最后生成一个dist文件,

机器自动执行: npm install # 装依赖 npm run lint # 检查代码格式 npm run test # 跑测试 npm run build # 打包构建

CD是:构建成功后,自动把代码部署到服务器,流程如下

自动把 dist 文件夹

上传到服务器

或者上传到CDN

用户就能访问新版本了

构建好后会在前端CI镜像构建群里通知:“我们项目配置了CI/CD,构建成功后会自动在钉钉群通知,方便测试和产品及时验证”

测试环境和预发直接push即可,但是上线时会使用ArgoCD工具,去手动炒作上线,回答如下:

“我们用了ArgoCD实现GitOps,部署状态和Git仓库完全一致,回滚只需要git revert,特别方便”

一个完整的前端CI/CD流程

image.png

总结:

“CI/CD就是自动化的代码构建和部署流程。打个比方,以前我们写完代码要手动打包、手动上传服务器,很麻烦还容易出错。

现在用了CI/CD,我只需要git push代码,后面的事情全是自动的:自动安装依赖、自动打包、自动测试、自动部署到服务器。

我们项目具体是:用GitHub Actions做CI,构建成功后自动打Docker镜像,然后用ArgoCD同步到k8s集群,整个过程在钉钉群里都有通知。这样我就能专注于写代码,不用关心怎么上线。”

怎么实现的:

前端项目的根目录中会有:.gitlab-ci.yml 和Dockerfile 和package.json单三个个文件

1、前端代码提交阶段:

“我写完代码后,只需要git push,后续全是自动的。运维同学提前配好了GitLab仓库和分支保护规则,比如main分支必须PR才能合并。”、、、

2、CI触发阶段(自动化,双方配合):在项目根目录放一个配置文件gitlab-ci.yml文件,定义ci流程,运维同学提前注册好了Runner,配置了Node.js环境,还帮我加了各种环境变量。”

3、构建阶段(前端主导,运维支持)

image.png

面试官问:你们前端项目的CI/CD怎么做的?

“我们用的是GitLab CI + Docker + K8s的方案。

前端这边,我负责在项目里写.gitlab-ci.yml,定义好test、build、deploy这几个阶段。每次git push到main分支,自动执行lint、单元测试、打包,生成dist目录。

运维那边,提前搭好了GitLab Runner,配置了Node.js环境,还用缓存让第二次构建只要1分钟。构建完自动打Docker镜像,推送到私有镜像仓库。

部署环节,运维配了K8s集群和ArgoCD,ArgoCD检测到新镜像就自动滚动更新,用户无感。部署完钉钉群自动通知,我们再用Sentry看线上错误。

整个流程前端只用写代码、配流水线,服务器和环境全是运维管,分工很清楚。”

image.png 我们项目的 CI/CD 流程是这样的:

  1. 我把代码提交到 Git 仓库。
  2. 自动触发 CI 流水线,开始安装依赖、执行代码校验、执行 npm run build 打包构建;
  3. 构建成功或失败,都会自动发镜像群通知(钉钉 / 企业微信);
  4. 构建完成后,生成容器镜像;
  5. 通过 ArgoCD 自动同步到 K8s 集群里。
  6. 最终完成自动化部署,整个过程不需要手动操作。

CI/CD 就是前端项目的自动化构建、自动化部署流水线。CI 是持续集成,主要做代码检查、安装依赖、打包构建,保证代码提交后能正常编译。CD 是持续部署,构建完成后自动把项目发布到测试环境或线上环境。整个流程不用手动打包、手动上传,减少人为失误,提高发布效率。

. CI 主要做哪些事情?

CI 主要做这几件事: 拉取最新代码 安装依赖(npm install) 代码规范检查(ESLint) 执行构建打包(npm run build) 输出可部署的文件 结果通知开发和运维

CD 主要做什么?

CD 就是把 CI 构建好的文件,自动发布到服务器。包括:上传文件、更新环境、启动服务、验证是否可用。实现提交代码 → 自动上线。

Docker / 容器 是什么?

容器就是把项目代码 + 运行环境打包在一起,保证在任何环境运行结果都一致,不会出现 “我本地能跑,线上跑不起来” 的问题。

K8s 是什么?

K8s 是管理容器的平台。主要作用:

  • 服务挂了自动重启
  • 流量大了自动扩容
  • 服务器故障自动迁移
  • 保证项目稳定运行。

ArgoCD 是干什么的?

ArgoCD 是部署在 K8s 里的持续部署工具,它能监听最新镜像,自动把项目更新到 K8s 中,实现一键发布、自动更新。

用的什么 CI/CD 工具?

我们用的是 GitLab CI ,用来做自动构建、自动检查、自动部署,配合 Docker、K8s、ArgoCD 一起使用。

image.png

你作为前端,在 CI/CD 里做过什么?

最适合你(Vue + PC + 小程序)的回答:

我主要负责: 配置项目的构建命令(npm run build) 配合运维调整构建脚本 处理构建报错 查看构建日志、排查发布失败问题 接入自动通知,保证发布流程顺畅

在前端项目中有.gitlab-ci.yml 和Dockerfile 和package.json三个文件

“package.json是项目的清单和说明书。CI/CD流程里,GitLab CI第一个步骤就是npm install,根据这个文件安装所有依赖。后面的npm run build也是从这里读取打包命令。”

“Dockerfile定义了项目怎么打包成容器。GitLab CI构建完生成dist目录后,下一步就是根据Dockerfile打镜像。这个镜像包含了nginx和静态文件,放到哪里都能跑起来”

面试官问:你们项目里这三个文件是怎么配合的?

package.json是基础,它定义了项目需要什么依赖、怎么启动、怎么打包。CI/CD流水线里所有的npm命令都来自这里。

Dockerfile是打包标准,它定义了怎么把打包好的dist文件变成容器镜像。比如我们用的是nginx镜像,把dist放进去,再配上nginx配置。

.gitlab-ci.yml是总指挥,它把上面两个文件串起来。比如它会先执行npm install(用package.json),再执行npm run build(也用package.json),然后用Dockerfile打镜像,最后部署到K8s。

简单说:package.json管‘做什么’、Dockerfile管‘装什么’、.gitlab-ci.yml管‘什么时候做’。”

“这三个文件其实体现了声明式配置的思想:

  • package.json声明了项目需要什么

  • Dockerfile声明了运行环境需要什么

  • .gitlab-ci.yml声明了流程需要什么