前端面试题(四、webpack和vite)

构建工具

        因为浏览器只认识html、css和js,而我们写的jsx,vue、ts、less、js语法降级都需要处理后,再交给浏览器去运行。所以出现了构建工具帮我们做这些事情,开发者只关心怎么写代码就行。比如webpack、vite。

一、webpack

1. 基础功能

        开发模式下:编译es6的模块化语法
        生产模式下:编译es6的模块化语法、压缩代码。

2. 集成功能

        通过集成一系列的第三方库,比如一些loader编译css、ts、less、sass、vue、jsx文件

3. 使用

//5个基本配置项
module.exports = {
    entry: '',   //入口文件
    output: {},  //输出文件位置
    module: {rules: []}, //设置加载器
    plugin: {},  //插件
    mode: ''    //设置生产模式还是开发模式
}

(1)mode=development

这一模式下,主要做两件事情:
        1. 编译自己写的代码

        2. 规范自己写的代码

        1. 我们写的代码有样式,html,js,vue等。浏览器能够识别html、css、js。别的文件需要通过设置一系列的loader,将其编译成浏览器可以识别的代码。

        (1)处理css文件的加载器:css-loader、style-loader
        (2)处理sass文件的加载器:sass-loader
        (3)处理less文件的加载器:less-loader
        (4)处理js文件的加载器:babel-loader。
        (5)图片 type=asset
        (6)音视频资源:type=asset/resource
        (7)处理html:借助html-webpack-plugin来以规定的模板生成html,这个html自动引入打包后的文件

        loader需要在module的rules中配备,一个规则使用的loader执行顺序是
                1. 不同优先级:pre>normal>inline>post
                2. 相同优先级:从下到上从右到左
        loader是一个函数,参数是要处理的源码,需要返回处理后的内容content

插件就是在不同的生命周期做不同的事情。一个插件是一个类,需要new调用。
        2. 规范自己写的代码,借助eslint检查语法。
                首先,一般会新建一个文件.eslintrc.js。在它里面配置需要检查的语法;
                其次,在webpack.config.js中借助一个Eslint插件,指定需要检查的js文件路径

开发模式下可以做的优化(主要是优化开发体验):

1. (找错)借助source-map将打包后的js与之前的js形成映射关系,报错报的是之前js对应的位置。通过设置devtool:source-map
2. (自动监测修改然后运行)当我修改一个文件时,每次都需要重新调用打包命令——借助webpack-dev-server。在config中配置devServer设置open=true自动打开浏览器。使用了这个后,所有代码会被编译在内存中。
3. (当修改文件时)修改文件,尽管使用了自动打开浏览器功能,但是每一次都需要从入口文件开始,从头扫描构建依赖,对js文件的处理,可以从eslint和babel优化,使用include或者exclude将不需要进行检查的给排除在外,还有可以对这二者的处理结果使用缓存;因为处理就需要去module中找对应的loader,所以可以使用OneOf将loader包裹,保证识别到一个之后就不再识别了;以及修改一个还是想只编译替换这一个,所以可以使用HMR热模块替换(只能用于开发环境),它hash值发生变化,别的没有;

(2)mode=production

主要就是优化,从两个角度出发
        1. 优化代码的运行性能
        2. 优化代码体积大小

(性能)

1. 因为css之前会被一起打包到js文件下,当js文件加载时,创建一个style标签生成样式——但是由于js是异步加载,所以css比较慢,网站会出现闪屏现象,所以将css抽离成一个文件,然后通过link标签引入是有必要的。借助css-extract插件

2. 将小图片使用dataURL编码方式,,减少发起的请求数量。需要在loader中设置。

3. webpack它会从头开始扫描我们的入口文件,替换掉导入都出语句。并将需要导入的js代码集成在一起,形成一个大的最终的js文件,这样这个js文件体积就很大,获取到之后还要对他进行解析,耗费时间,影响用户体验。所以可以使用code split对代码进行分割,生成多个js文件(代码分割——通过在optimization.splitChunks配置)。需要哪个就加载哪个(按需加载——通过动态导入的方式)。

4. 但是如果代码分割后有的文件还是比较大,可以使用Preload Prefetch利用浏览器的空闲时间加载后续的资源

5. 静态资源使用缓存,这样二次请求的资源直接读取缓存,速度变快。使用hash值来标记文件是否进行了修改。为了防止我们修改一个文件导致入口文件全部失效,所以可以将hash值单独保存到一个runtime文件中,该文件保存文件的hash值及其关系。

6. 对于es6+的语法使用core-js(在babel中配置)

7. 离线访问PWA

(体积)

1. 压缩代码:借助css-minimizer插件压缩css、使用html-webpack插件默认压缩html、生产环境默认压缩js

2. 图片的压缩:插件image-minimizer

3. treeShaking:将没有使用上的代码移除掉

4. js再压缩:UglifyJsPlugin 和 TerserWebpackPlugin

Vite

1. vite所做的工作

(1)补全路径

        vite支持es module。因为es6导入要么是绝对路径,要么是相对路径。但项目中如果导入第三方库,我们可以直接使用名字,因为vite帮我们去补全了路径,从而自动取node_modules下面去找文件。这个问题可以通过依赖预构建来解决。

(2)依赖预构建 

        首先vite发现import了一个包,然后从当前目录向上查找到对应的依赖,而这些依赖的导出方式是不确定的,所以需要调用esbuild(对js语法处理的一个库),把其他规范的代码转换成es module规范(相当于对第三方库重新进行编译),放到当前目录下的node_modules/.vite/deps,同时对第三方库用到的别的依赖也进行了统一集成到一个或者多个文件中。

       解决的问题:
                1. 不同的第三方库有不同的导出模式
                2. 对路径的处理上可以直接使用.vite/deps,方便路径重写
                3. 网络多包传输的性能问题,因为vite尽可能进行了集成。

(3)区分开发环境还是生产环境

const envResolver = {
  "serve": () => {
    console.log('server')
    return ({ ...viteDevConfig, ...viteBaseConfig })
  },
  "build": () => {
    console.log('build')
    return ({ ...viteProdConfig, ...viteBaseConfig })
  }
}

export default defineConfig(({ command }) => {
  return envResolver[command]()
})

(4)vite环境变量处理

        vite内置了dotenv库,这个库会自动读取.env文件里面的内容,并进行解析,把它挂载到process上面。在vite.config.js里面可以通过loadEnv函数读取到环境变量,主要用法如下:

        如果是开发环境,他会把.env和.env.development下环境变量合并注入到process下,如果是生产环境,把.env和.env.production下环境变量合并注入到process下。

export default defineConfig(({ command, mode }) => {
  const env = loadEnv(mode, process.cwd(), "")
  return envResolver[command]()
})

        在客户端需要用到环境变量的话,他在import.meta.env中存储着。要注意vite做了一层拦截,只有以VITE_开头的环境变量才会注入到meta里面

(5)vite处理css

        vite本身就支持css的处理,不需要安装loader。具体步骤如下:
                1. vite扫描入口main.js,发现引用了index.css
                2. 使用fs内置模块读取css文件内容
                3. 创建style标签,将css文件内容插入标签中。
                4. 将style标签插入到html的head中
                5. 将css文件中的内容替换成JS,同时设置content-type告知浏览器以js的形式解析文件

        当类名冲突,比如有两个.footer,可以使用css模块化,主要步骤如下:
                1. vite扫描入口main.js,发现引用了index.module.css
                2. 他会读取文件内容,并将文件内容的类名进行一定规则的替换,同时创建一个对象{footer: "_footer_i22st_1"}
                3. 创建style标签,将替换后的内容放进去,然后插入到head中
                4. 将module.css文件中的内容替换成JS,并将创建的对象在脚本中默认导出。

可以在vite.config.js中配置css.module选项来控制这个对象的行为。如果使用less这种预处理器的话,也可以通过配置preprocessorOptions来配置一些命令参数,将其转换成css语法;使用postcss选项进行语法降级与前缀自动补全

(6)vite加载静态资源(自动处理)

可以配置一些别名resolve.alias:最终做的就是字符串替换,将@替换成绝对路径

(7)生产环境配置

配置build.rollupOptions配置打包后文件的操作

(8)插件

vite在不同的生命周期调用不同的插件以达到不同的目的

例如:

~  config(config, {mode, command}) 在执行配置文件前执行的钩子,它需要返回一个配置对象

//viteAlias
const fs = require('fs')
const path = require('path')

// 区分文件与目录
function diffDirAndFile(dirArr = [], basePath = '') {
  const result = {
    dirs: [],
    files: []
  }
  dirArr.forEach(item => {
    const currentFileSta = fs.statSync(path.resolve(__dirname, basePath + "/" + item))
    currentFileSta.isDirectory() ? result.dirs.push(item) : result.files.push(item)
  })
  return result
}

// 读取src下面的文件夹,配置对应的键值对
function getTotalSrcDir(keyName) {
  const result = fs.readdirSync(path.resolve(__dirname, '../src'))
  const diffRes = diffDirAndFile(result, "../src")
  // /存放别名配置
  const resolveObj = {}
  diffRes.dirs.forEach(dirName => {
    const key = `${keyName}${dirName}`
    resolveObj[key] = path.resolve(__dirname, '../src' + "/" + dirName)
  })
  return resolveObj
}

module.exports = ({keyName="@"} = {}) => {
  return {
    config: (config, env) => { //env={mode, command}
      // 读src下面的目录配置对应的键值对
      const resolveObj = getTotalSrcDir(keyName)
      return {
        resolve: {
          alias: resolveObj
        }
      }
    }
  }
}

~  transformIndexHtml(html, ctx)转换html钩子是返回html的。
~  configureServer(server)配置服务端处理的:比如说vite-mock-plugin。在插件内部,拦截到发给服务端的请求,然后找到mock文件夹。具体操作是,在中间件server.middlewares.use((req,res,next)=>{})中,需要找到mock文件夹下的index.js,看看里面的请求有没有res对应的url,有的话,把数据返回出去。
~  configResolved(options)当整个配置文件的解析流程完毕后走的钩子
~ configurepreviewServer(server)
~ 还有一些与rollup一一对应的钩子

2. 性能优化

开发时优化:因为vite按需加载,不太注重这方面

打包rollup/webpack/vite优化:优化体积

(1)分包:把一些不会常规更新的文件进行打包处理.build.rollupOptions.output.manualChunks
(2)gzip压缩:服务端读取对应的gz文件,并设置响应头content-type=gz返回给客户端,然后客户端收到消息发现是压缩后的,就赶紧解压
(3)动态导入:路由懒加载
(4)cdn加速:把我们依赖的第三方模块全部写成cdn形式,保证我们自己代码的小体积(体积小的话,客户端和服务端之间的传输压力没那么大)。借助cdn的插件

vite和webpack区别

0. vite优势

(1)速度方面
        webpack更注重兼容性。它支持多种模块化开发,所以需要从入口文件开始扫描全部js,从头构建依赖,统一模块化,分析出项目的所有依赖关系,进行打包后,才能启动开发服务器。项目越大消耗时间越长。
        vite更注重浏览器的开发体验。vite它支持es6的模块化,使用esbuild对依赖进行预构建,将所有模块化转换成es module。不需要进行打包,当浏览器发现需要加载某个模块时,再按需加载。再项目部署时再使用rollup打包

(2)热更新方面
        虽然webpack支持缓存,但是修改文件还是需要从头打包,无法从根本上解决问题。
        当修改文件时,vite通过websocket通知浏览器,重新发起请求,只对该模块进行重新编译替换。并且vite对第三方模块做了强缓存处理

(3)vite对常用功能都做了配置,而webpack需要自己配置,所以出现了脚手架。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值