2023前端年末备战面试总结——构建工具与git

构建工具与设计模式

本文是本人整理归纳出来的一篇构建工具方面的面试题及知识点,其中不免有许多漏掉的问题或是答案,发现错误的或者是有什么问题需要补充的朋友们,可以在评论区留言,大家虚心交流,一起进步。

1. 构建工具的作用

构建工具前端工程化中重要的一环,有了构建工具的存在,可以简化我们的编程过程,在编程的过程中能够使用最新语法简单的编写方式等,然后通过构建工具的处理,使我们最终打包出来的产物变成体积小兼容性强能够在浏览器运行的标准语法。

常见的构建工具有webpackviterollupesbuild等,目前比较流行的还有SnowpackTurbopackBun等。

2. 说一下webpack的优点

  1. 模块化:webpack支持将项目拆分为多个模块,最终将多个模块进行打包,编译出最终产物,能够让开发者在开发过程中更便捷的维护复用代码。
  2. 代码拆分和按需加载:webpack支持将代码拆分为多个chunk,并且实现按需加载,降低应用程序的首次加载时间,以及用户在使用过程中的页面加载时间,对spa应用极为友好。
  3. 资源优化:webpack支持代码压缩tree-shaking静态资源压缩等功能,能够缩小最终打包产物的体积,去除无用代码。
  4. 丰富的生态:webpack经过这么多年的发展,拥有庞大的社区完整的生态,有许多loader以及plugin供开发者使用,这也是为什么webpack久经不衰的原因之一。
  5. 热更新:webpack支持热更新,修改代码时无需再次编译整个文件,只会对改动内容进行重新编译,在开发环境带来了很大的便利。

3. loader是什么,说一下常用的loader以及它们的作用

loader分为normalLoaderpitchLoader,每个loader就是一个函数,该函数可以对源代码进行转换,返回值可以被下一个loader所使用,loader可以是同步,也可以是异步的

(1)loader的配置方式

js 复制代码
module.exports = {
  module: {
    rules: [
      // 基础使用loader
      {
        // 匹配需要使用该loader的文件,通常使用正则表达式进行匹配
        test: /\.css$/,
        use: 'css-loader'
      },
      {
        test: /\.css$/,
        // 通过数组可以使用多个loader
        use: ['css-loader']
      },
      {
        test: /\.css$/,
        use: {
          loader: 'css-loader',
          // 给loader传递参数
          options: {
            name: 'lee'
          }
        }
      }
    ]
  }
},

(2)normalLoader

一般的loader都是normalLoader,也就是我们最终通过module.exports导出的函数

js 复制代码
const myLoader = function (content, map, meta) {
  // webpack5内置了getOptions方法
  const options = this.getOptions()
  console.log(options, '获取webpack配置loder时传入的参数')
  console.log('我是normalLoader1')
  console.log(this.data, 'pitch阶段传入的参数') 
  console.log(content, '源文件或上个loader传递的文件')
  console.log(map, 'source-map内容')
  console.log(meta, '上个loader传递的数据')
  return content
}

module.exports = myLoader

(3)pitchLoader

pitchLoader只是loader的一个属性,只需要在loader中添加pitch方法,就可以当作pitchLoader,可以在该阶段给normalLoader传递一些数据

js 复制代码
// 这个函数就是pitchLoader
// PitchLoader有三个默认参数,
// remainingRequest: 代表未执行pitch阶段的loader数组
// previousRequest: 代表已经执行过pitch阶段的loader数组
// data: 可以传入任意参数, 能够在normal阶段被当前loader通过this.data接收

// tip: currentRequest代表当前正在执行和未执行过pitch阶段的loader数组
myLoader.pitch = function (
  remainingRequest,
  previousRequest,
  data
) {
  console.log('我是pitchLoader1')
  console.log(remainingRequest)
  console.log(previousRequest)
  data.a = 123
}

module.exports = myLoader

(4)loader的返回值

loader的返回值可以被下一个loader接收,可以通过return的方式进行返回,也可以通过内置的callback方法进行返回。

js 复制代码
// 方式一
const myLoader = function (content) {
  return content
}

// 方式二
/**
 * @param {*} err 代表是否有错误 null表示没有错误
 * @param {*} content 源代码, 也可以是上一个loader处理后的代码
 * @param {*} map source-map
 * @param {*} meta 给下一个loader传递参数
 */
const myLoader = function (content, map, meta) {
  this.callback(null, content, map, meta)
}

module.exports = myLoader

相比直接return的方式,使用callback函数进行返回更加的灵活,因为通过这种方式除了返回content之外还可以返回其它的一些信息,并且可以传递参数。

(5)异步loader

loader正常情况下是通过同步的方式进行返回的,但是某些场景下需要异步返回,这时需要创建一个异步loader

js 复制代码
// 异步loader
const myLoader2 = function (content, map, meta) {
  const callback = this.async()
  setTimeout(() => 
      callback(null, content, map, meta)
  }, 1000)
}

module.exports = myLoader2

(6)常用的loader以及作用

  1. babel-loader:通过babel-loader将代码转换为兼容性更强的代码。
  2. css-loader:可以识别css中的@import以及url()语句,将多个css文件合并成一个css段落,还可以进行一些压缩处理;
  3. style-loader:可以将css插入到html文件的style标签中
  4. less-loader:对less文件进行处理,处理为正常的css。
  5. sass-loader:对sass文件进行处理,处理为正常的css。
  6. postcss-loader:可以对css属性进行补全浏览器前缀,使其兼容更多内核的浏览器。

其它常用的loader可以去webpack官网查看,或者有特殊需求的loader可以去npm查找。

4. plugin是什么,说一下常用的plugin以及它们的作用

(1)plugin是什么

plugin是webpack中的插件。它的目的在于解决一些loader无法完成的其他事情,loader一般只工作于模块的加载环节,而plugin则可以工作于webpack整个生命周期,而这一切基于Tapable这个库,该库是webpack官方维护的一个库,里面提供了多种类型的hook,而webpack又在此基础上提供了许多生命周期hook,在plugin中,可以访问到这些hook,从而在不同的生命周期执行不同的方法,使得plugin可以贯穿整个webpack的生命周期。

plugin可以是一个函数,也可以是一个包含apply方法的对象。apply 方法会被webpack compiler调用,并且在整个编译生命周期都可以访问 compiler 对象。

(2)Tapable中的hooks

js 复制代码
const {
	SyncHook,
	SyncBailHook,
	SyncWaterfallHook,
	SyncLoopHook,
	AsyncParallelHook,
	AsyncParallelBailHook,
	AsyncSeriesHook,
	AsyncSeriesBailHook,
	AsyncSeriesWaterfallHook
 } = require("tapable");
钩子名称 执行方式 使用要点
SyncHook(同步钩子) 同步串行 回调函数会按照注册的顺序依次执行
SyncBailHook(同步熔断钩子) 同步串行 只要监听函数中有一个函数的返回值不为undefined,则跳过剩下所有的回调函数
SyncWaterfallHook(同步瀑布钩子) 同步串行 可以将上一个监听函数的返回值作为下一个监听函数的参数
SyncLoopHook(同步循环钩子) 同步循环 当监听函数被触发的时候,如果该监听函数返回true,则会反复执行该函数,如果返回undefined 则退出循环
AsyncParallelHook(异步并行钩子) 异步并行 钩子支持异步操作,并且并行执行
AsyncParallelBailHook(异步并行熔断钩子) 异步并行 异步并行钩子的基础上增加了熔断机制,只要监听函数的返回值不为undefined,则跳过剩下所有的回调函数
AsyncSeriesHook(异步串行钩子) 异步串行 钩子支持异步操作,并且串行执行
AsyncSeriesBailHook(异步串行熔断钩子) 异步串行 异步串行钩子的基础上增加了熔断机制,只要监听函数的返回值不为undefined,则跳过剩下所有的回调函数
AsyncSeriesWaterfallHook(异步串行瀑布钩子) 异步串行 上一个监听函数的中的callback(err, data)的第二个参数,可以作为下一个监听函数的参数

(3)webpack的生命周期hook

webpack一共暴露出了5大类型的hooks

  1. Compiler Hooks:这类hook一般作用于主流程
  2. Compilation Hooks:这类hook一般作用于编译流程
  3. NormalModuleFactory Hooks:这类hook一般作用于模块创建、解析流程
  4. ContextModuleFactory Hooks:这类hook类似于NormalModuleFactory Hooks,但是它主要用来对使用了webpack独有API require.context模块方法的文件进行处理。
  5. JavascriptParser Hooks:这类hook一般作用于将模块解析为AST语法树时。

每种类型的Hooks所包含的具体Hook

(4)compiler和compilation的区别

  • compiler贯穿了webpack整个生命周期compilation只会在编译模块时产生。
  • compiler只会在启动webpack时创建一次,而compilation则是在每次进行编译时都会创建一次。
  • 之所以需要compilation是因为在开发环境下,有了热更新的存在,当我们频繁改动模块时,无需重新创建compiler了,可以将其复用,只需要重新创建对应模块的compilation对象

(5)常见的plugin

  1. HtmlWebpackPlugin:在单页面开发中,最终只会有一个HTML文件,该插件可以帮助我们在打包环节生成一个HTML文件,并且支持自定义模板
  2. DefinePlugin:该插件能够在编译时将代码中的变量替换为其它值或表达式
  3. TerserWebpackPlugin:该插件使用terser来压缩 JavaScript。

其它常用的plugin可以去webpack官网查看

5. loader的执行顺序是什么

配置每一个loader时,可以通过enforce去设置loader的执行顺序,该属性一共有4个值,分别为pre(前置)、normal(默认)、inline(行内)、post(后置)。具体会按照以下顺序执行:

  1. 先执行pitchLoader,顺序为post、inline、normal、pre
  2. 然后执行normalLoader,顺序为pre、normal、inline、post
  3. 默认情况下,loader的执行顺序为从右往左或者从下往上

normalLoader的执行顺序正好是和pitchLoader相反的,这是因为默认情况下(所有loader都为normal时),会先去执行每一个loader的pitchLoader,通过一个变量记录当前loader的索引,每执行完一个pitchLoader都会对索引进行++,然后再进行--,执行normalLoader,这也是为什么我们的loader执行顺序和我们书写顺序是相反的原因。

如果换成代码可以这么理解:

js 复制代码
const loaders = [loader1, loader2, loader3]
let index = 0

for(let i = 0; i < loaders.length; i++) {
  // 执行loaders[i]的pitchLoader
  index = i
}

for(let j = index; j >= 0; j--) {
  // 执行loaders[j]的normalLoader
}

6. loader和plugin的区别是什么

  1. 作用不同:webpack将所有文件视为模块,但是无法完成对js以外资源的解析和打包,loader的作用就是帮助webpack进行加载、解析其它类型的文件。而plugin则是完成那些loader无法完成的任务,它可以使用webpack提供的不同hook,在不同的生命周期进行一些处理,比如资源优化代码压缩分离,还可以帮助生成HTML文件等。
  2. 用法不同:配置方法不同。
  3. 影响范围loader是作用于文件,而plugin则是作用于整个项目

7. 是否手写过loader和plugin

8. webpack如何进行分包

webpack可以通过optimization.splitChunks进行分包。

(1)splitChunks.chunks

splitChunks.chunks属性一共有三个值asyncinitialall,默认值为async

async

当设置为async时,只会对异步导入的模块进行分包,同步导入的模块内容依然会保留在主包内。

js 复制代码
// webpack配置
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
  mode: 'development',
  entry: {
    a: './src/indexA.js',
    b: './src/indexB.js'
  },
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: '[name].index.js',
    chunkFilename: '[name].chunk.js'
  },
  optimization: {
    splitChunks: {
      chunks: 'async'
    }
  },
  plugins: [new CleanWebpackPlugin()]
}

// src/indexA.js
// 异步导入utils
import('./js/utils').then(res => {
  console.log(res)
})

// 同步导入utils2
import myFun2 from './js/utils2'

myFun2()

// src/indexB.js
// 异步导入utils
import('./js/utils').then(res => {
  console.log(res)
})

// 同步导入utils2
import myFun2 from './js/utils2'

myFun2()

因为utils文件为异步导入,因此进行了单独分包,而utils2文件为同步导入,因此在a.index.jsb.index.js中各存在一份utils2文件的代码,这无疑是冗余的。

initial

当设置为initial时,会将同步导入并且满足条件的文件也进行分包,但是如果同一个文件,被不同的文件分别异步和同步两种方式导入,异步导入的会进行拆分,而同步导入的依旧会保留在主包内。

js 复制代码
// webpack配置
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
  mode: 'development',
  entry: {
    a: './src/indexA.js',
    b: './src/indexB.js'
  },
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: '[name].index.js',
    chunkFilename: '[name].chunk.js'
  },
  optimization: {
    splitChunks: {
      chunks: 'async'
    }
  },
  plugins: [new CleanWebpackPlugin()]
}

// src/indexA.js
// 异步导入utils
import('./js/utils').then(res => {
  console.log(res)
})

// 同步导入utils2
import myFun2 from './js/utils2'

// 同步导入utils3
import myFun3 from './js/utils3'

myFun2()
myFun3()

// src/indexB.js
// 异步导入utils
import('./js/utils').then(res => {
  console.log(res)
})

// 同步导入utils2
import myFun2 from './js/utils2'

// 异步导入utils3
import('./js/utils3').then(res => {
  console.log(res)
})
myFun2()

此时虽然utils2同步导入,但是也会被进行分包,但是utils3即被同步导入又被异步导入,虽然进行了分包,但是a.index.js文件中依然保留了utils3的代码。

all

使用all时,会结合asyncinitial的优点,会对满足条件的同步模块和异步模块都进行分包,并且不会产生冗余代码。因此,一般在开发中,都会将chunks设置为all

限制条件

当配置chunks时,可以给splitChunks配置一些其它属性配合使用,比如配置splitChunks.minSize(模块体积达到最小值时会分包)、splitChunks.maxSize(模块体积小于最大值时会分包)、splitChunks.minChunks(模块被引用多少次才会进行分包)等等,还有一些其它配置可参考官方文档

(2)第三方库的分包(splitChunks.cacheGroups)

有时我们会使用一些第三方库,比如dayjslodash-es等,当我们多个文件使用这些库时,我们可以把这些库单独打包出来,这时会用到splitChunks.cacheGroups属性。

js 复制代码
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
  mode: 'development',
  entry: {
    a: './src/indexA.js',
    b: './src/indexB.js'
  },
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: '[name].index.js',
    chunkFilename: '[name].chunk.js'
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
      // 分包的最小体积单位字节
      minSize: 1,
      // 将大于maxSize的包拆分于不小于minSize的包
      maxSize: 600,
      // // 表示最少被使用了几次的包才会分包
      minChunks: 1,
      cacheGroups: {
        // vendor: 包的名字,可以随意命名, test: 匹配规则
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          filename: '[name]_vendor.js' // 包里文件的名字
        }
      }
    }
  },
  plugins: [new CleanWebpackPlugin()]
}

(3)运行时代码分包(optimization.runtimeChunk)

比如import('xxx').then()这种异步导入的代码就是一种运行时代码

当我们配置了splitChunks.chunksall的时候,异步导入不是可以直接被分包吗,为什么还需要optimization.runtimeChunk这种方式进行分包呢?

js 复制代码
// webpack.config.js
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
  mode: 'development',
  entry: './src/indexA.js',
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: '[name].[chunkhash].index.js',
    chunkFilename: '[name].[chunkhash].chunk.js'
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
      // 分包的最小体积单位字节
      minSize: 1,
      // 将大于maxSize的包拆分于不小于minSize的包
      maxSize: 600,
      // // 表示最少被使用了几次的包才会分包
      minChunks: 1,
    }
    // runtimeChunk: true
  },
  plugins: [new CleanWebpackPlugin()]
}

// src/indexA.js
import('./js/utils').then(res => {
  console.log(res)
})

// js/utils.js
function myFun() {
  console.log('i am 1')
}

export default myFun

没有配置runtimeChunk时,默认为false,打包出来的产物如下:

当我们修改js/utils.js文件的内容,重新进行打包时:

js 复制代码
// js/utils.js
function myFun() {
  console.log('i am 2')
}

export default myFun

打包出来的两个文件,hash值都发生了改变,这是因为,chunkhash是根据文件内容生成的,当内容改动时,hash值也会变动。如果开启runtimeChunk之后呢?

js 复制代码
// 修改js/utils.js
function myFun() {
  console.log('i am 3')
}

export default myFun

发现主入口文件的hash值并未改变。这样有什么好处呢?假如说每次生成的主文件hash值都会改变,我们在浏览器上运行时,每次发布文件都会变化,浏览器每次都要请求新的资源,那么缓存的作用就几乎没有了,如果说我们开启了runtimeChunk,即使引入的文件发生了变化,主文件的hash值也不会发生变化,就可以很好的利用浏览器的缓存

9. 说一下webpack中热更新的原理

(1)什么是热更新

我们平时在HTML文件中编写代码,想要在浏览器运行时,需要通过浏览器打开我们的HTML文件,如果此时对css、js一些内容进行了修改,我们需要重新刷新页面才能够看到最新的效果,如果在开发中也这样的话,是非常影响开发效率的。

热更新(Hot Module Replacement)指的就是我们在对一个模块进行修改删除更新操作时,不会重启整个项目,只会对修改的内容进行替换,并不影响当前应用程序的状态,能够提高开发效率。

(2)热更新的原理

webpack的热更新是基于webpack-dev-server这个库。

  1. 当我们以watch模式启动webpack,对项目进行编译时,webpack会监听我们文件的改动;
  2. webpack-dev-server进行启动,这时会帮我们开启两个本地服务,一个是express服务,一个是websocket长链接
  3. 每一次编译文件之后,会将编译好的文件放在内存中,这样是为了减少文件的写入和读取,性能会更好(之前通过memory-fs库进行实现,现在改为了memfs),第一次开启服务时,浏览器会访问express服务,拿到打包后的bundle.js资源进行加载,在ws中会有当前的hash值,浏览器会记录当前的hash值;
  4. 如果某个模块发生了改动,webpack监听到之后会再次对该模块进行编译,然后生成一个json文件和一个js文件,json文件中存放了哪些模块进行了改动,js文件中存放了最新变更的代码。然后通过ws告知浏览器新的hash值,浏览器发现hash值有变动,于是请求,获取最新资源;
  5. 浏览器使用上一次的hash值向服务端请求json文件,通过json文件得知了哪些模块有改动;
  6. 浏览器再次使用上一次的hash值向服务端请求最新的js文件,然后把相应模块的代码进行替换,从而达到了热更新的效果

10. 说一下你对webpack5中模块联邦的理解

模块联邦(Module Federation)webpack5中的一个新特性,它主要是用于解决公共模块在多个项目重复引用的问题。

在之前的开发中,如果我们多个项目需要使用同一份配置文件或模块,要么把它复制一份放到另一个项目,要么上传npm包,然后两个项目进行下载使用。但是无论使用哪种方式,当我们想对公共模块进行更新时,使用该模块的项目都需要进行更新,万一漏掉了一个可能会产生许多问题。而模块联邦就可以解决这种问题,当多个项目引用同一个模块时,只需要更新该模块,所有引用该模块的项目都会更新为最新的模块。这种情况有利有弊,比如设计有问题时,更新模块可能不兼容旧的使用方式,这时可能会带来更大的麻烦了。

11. webpack性能优化的手段

  1. 优化loader:可以通过loader的include/exclude配置来让loader跳过或精准匹配某些文件,跳过对那些不必要文件的处理。
  2. 多进程处理loader:通过happyPack插件,可以开启多进程处理,优化loader处理文件的时间。
  3. 按需优化:通过webpack-bundle-analyzer插件,可以分析打包后文件的依赖关系以及图层结构,能够让我们更好的分析还有哪些文件资源过大需要优化。
  4. tree-shaking:利用tree-shaking,移除项目中没有使用到的代码,减少打包体积。
  5. 按需加载:利用动态导入进行分包,然后实现对不同文件的按需加载。
  6. gzip压缩:利用compression-webpack-plugin插件实现gzip压缩,对压缩后的文件进行部署(需要服务端配合)。

12. Babel是如何实现编译的

Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。

babel一共有三个阶段,分别为parse(解析)transform(转换)generator(生成)

  • parse:首先babel会将我们的代码解析为AST抽象语法树
  • transform:然后babel会对AST的内容进行遍历,然后对需要修改的内容进行修改或补全,让我们的代码能够满足目标环境的运行条件;
  • generator:最终生成字符串形式的代码,同时还会创建source-map

13. tree-shaking的是怎么实现的

tree-shaking在前端领域被首先使用是rollup,因为想要实现tree-shaking需要依赖ES Module的静态分析,而rollup正是基于ES Module的一款打包工具。

webpack目前也支持tree-shaking,并且除了ES Module模块文件以外,还支持了部分CommonJs模块的tree-shaking。文档地址

使用tree-shaking的方式

webpack中,tree-shaking依赖于terser,terser是一个插件,全称为terser-webpack-plugin,在webpack5中已经被内置。同时我们还需要配置webpakc中的optimization.usedExports以及optimization.minimizer以及optimization.minimize属性。

js 复制代码
// webpack配置
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
  mode: 'development',
  devtool: 'source-map',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: '[name].index.js',
    chunkFilename: '[name].chunk.js'
  },
  optimization: {
    usedExports: true,
    // minimize: true,
    minimizer: [new TerserPlugin()]
  },
  module: {},
  plugins: [new CleanWebpackPlugin()]
}

// ------------------------------------------------
// utils文件
export function myFun() {
  console.log('i am utils')
}

export const a = 1
// ------------------------------------------------
// index文件
import { myFun } from './js/utils'

myFun()

utils文件exportmyFun、a两个变量,但是在index文件中只import了myFun,此时变量a并没有用到,因此是可以被删掉的,这时打包看一下:

在打包的文件中,webpack会标注出来export的a是未使用的,这时我们就可以通过将optimization.minimize属性设置为true,从而让terser帮助我们移除掉这些无用代码

tree-shaking的副作用

如果说utils文件中的代码变成了以下情况:

js 复制代码
import { myFun2 } from './utils2'

export function myFun() {
  console.log('i am utils')
}

export const a = 1

虽然myFun2并没有被utils文件所使用,但是因为import影响了utils文件,这种影响其它代码的情况称之为副作用,当有副作用时,terser不会帮我们消除这部分代码,即使它没有被使用。

消除副作用

为了让最终tree-shaking的代码更加的简洁、准确,我们自身在编写代码时要保证尽量不产生副作用,同时我们可以通过package.json文件中的sideEffects属性,来告知terser哪些文件有副作用,哪些文件没有副作用,从而让terser更好的进行tree-shaking

sideEffects可以为truefalse数组

  • true:代表所有文件都有副作用
  • false:代表所有文件都没有副作用,如果未使用,可以被安全删除;
  • 数组:可以配置哪些文件或模块没有副作用

修改package.json配置,再次进行打包:

json 复制代码
{
  "name": "webpack-demo",
  "version": "1.0.0",
  "sideEffects": false
}

再次编译的代码,已经没有utils2文件的内容了。

总结

webpack的tree-shaking是基于ES Module的静态分析的,但是在webpack5中也支持了部分CommonJS语法,webpack会通过静态分析,记录export内容被使用的情况,并进行注释记录,最终由terser插件完成对这些无用代码的tree-shaking。

14. webpack的构建流程

15. webpack和vite的区别

  • 开发环境下vite不需要打包,因为它基于ES Module,能够按需加载,而webpack则需要对所有文件进行打包,分析依赖,流程较为繁琐,大型项目构建较慢;
  • 生产环境下vite是由rollup完成的打包,而webpack不是;
  • webpack发展时间较长,社区生态也比较完善,而vite起步较晚,在生态方面略逊于webpack

16. vite为什么比webpack快

  1. Vite在开发环境下使用esbuild进行构建,而生产环境下使用rollup,esbuild是使用GO语言编写的属于编译型语言,而webpack是使用js编写的,属于解释性语言,在这一点上天生就落了下风;
  2. Vite利用了最新浏览器支持ESModule的特性,不会对项目进行打包,而是遇到import语句时,通过网络请求,请求需要加载的模块,做到了按需请求加载,而webpack则不同,webpack通过入口文件逐层分析各个文件的依赖,然后建立依赖关系,打包成最终文件让浏览器直接运行,这就导致了依赖过多或层级过深时,webpack就会打包越慢。

17. git fetch和git pull的区别

git fetch只会拉取远程的内容分支到本地,但是不会自动合并,是否合并取决于用户的操作。

git pull会拉取远程对应分支的内容到本地,并且与本地分支进行合并,git pull相当于git fetch + git merge

18. git rebase和git merge的区别

git rebase也可以合并代码,如果把提交记录当作一条线时,当在a分支上使用git rebase b时,会以b分支的最后一个commit为基点,然后将a分支上与b分支有差异的commit向上延伸。也可以理解为在b分支最后一个commit之后又提交了n个commit

此时有test和test1两个分支,在test分支上使用git rebase feature/test1

get merge则不同,get merge会将两个分支进行合并,两条分支线在合并处会产生一个交点并新增一条commit记录

因此,如果我们想表明某个分支是通过合并进来的,就使用git merge,如果我们想让两个分支进行合并,但是又希望分支线保持一条直线,可以使用git rebase或git cherry-pick的方式进行合并。

git pull --rebase

git pull --rebase一般用于多人协作时拉取远程代码,如果直接使用git pull,可能远程和本地都有提交,此时代码就会冲突。如果使用git pull --rebase,就会先将远程的代码拉取到暂存区,然后以本地最新代码为基准,如果远程有改动的代码,就会在当前基准上创建新的commit。

19. 如何回退提交

reset

可以通过git reset [ --soft | --mixed | --hard ] [< commitid >]的方式进行代码git回退。

  • 当参数为soft时,会保留回退位置的commit之后的代码,并且为暂存状态;
  • 当参数为mixed时,会保留回退位置的commit之后的代码,但是没有暂存;
  • 当参数为hard时,不会保留回退位置的commit之后的代码;

revert

git revert不会像git reset一样,将某个commit之后的commit全都清除,它的作用为修改某一次commit,然后产生一个新的commit

比如有3个commit,分别为a、b、c,当执行git revert b时,让用户选择是否保留commit c以及commit c的代码,最终会生成一个Revert commit

20. 谈谈git flow

git flow是git官方推崇的一种工作流,在大型团队合作项目中,如果能落实git flow的话,能够给团队合作带来很大提升。

git flow中规定了有五种分支,分别为masterdevelophotfixreleasefeature

  • master:该分支是主分支,作为发布分支,只接收hotfix和release的合并;
  • release:该分支为预发布分支,作为提测分支,由develop分支拉取,最终合并进master;
  • hotfix:该分支为bug修复分支,当有线上bug时,由master分支拉取hotfix进行修改,最终合并给master;
  • develop:该分支作为开发分支,开发新需求时,以该分支为基准,拉取feature分支,feature合并之后,由develop拉取release进行提测;
  • feature:该分支作为需求分支,每一个独立的需求单独拉取feature,开发完成之后合并进develop。

在实际工作中应该视情况而定,如果项目较繁杂,人员较多,可以使用五分支模型,如果较少时,可以变成三分支等。

相关推荐
崔庆才丨静觅14 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606114 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了14 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅15 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅15 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅15 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment15 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅16 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊16 小时前
jwt介绍
前端
爱敲代码的小鱼16 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax