前端工程化

99 阅读28分钟

模块化知识

模块化

JavaScript 模块化是将代码分解为独⽴的、可重⽤的模块,以便于管理和维护。

模块化的核⼼思想是将代码组织成较⼩的、具有特定功能的部分,提供清晰的接⼝和隔离的作⽤域。

能说说模块化的好处吗?

避免命名冲突(减少命名空间污染)

更好的分离, 按需加载

更⾼复⽤性

⾼可维护性

模块化的产物

打包后的 JavaScript ⽂件:将多个 JavaScript 模块合并成⼀个或多个⽂件,通常是bundle.js

CSS ⽂件:将多个 CSS ⽂件合并,或⽣成内联样式,优化加载性能。

资源⽂件:包括图⽚、字体等,经过处理和优化后,可以在应⽤中使⽤。

代码分割⽂件:根据需求⽣成的多个代码块,⽀持懒加载,以提⾼性能。

HTML ⽂件:通过插件⽣成的 HTML ⽂件,⾃动引⼊打包后的资源。

模块化打包⽅案

CommonJS 同步加载,主要用于服务器端 采⽤同步加载模块的⽅式,也就是说只有加载完成,才能执⾏后⾯的操作。

CommonJS 模块就是对象;即在输⼊时是先加载整个模块,⽣成⼀个对象,然后再从这个对象上⾯读取 ⽅法,这种加载称为“运⾏时加载”。

AMD(Asynchronous Module Definition)

  • 概述:AMD 是一种异步加载模块的规范,常用于浏览器端。
  • AMD规范采⽤异步⽅式加载模块,模块的加载不影响它后⾯语句的运⾏。所有依赖这个模块的语句,都定义在⼀个回调函数中,等到加载完成之后,这个回调函数才会运⾏。
  • 它主要有两个接⼝:define 和 require。define 是模块开发者关注的⽅法,⽽ require 则是模块使⽤者关注的⽅法。

UMD

UMD 是 AMD 和 CommonJS 的⼀个糅合。

AMD 是浏览器优先,异步加载; CommonJS 是服务器优先,同步加载。

先判断是否⽀持 node 的模块,⽀持就使⽤ node ;

再判断是否⽀持 AMD ,⽀持则使⽤ AMD 的⽅式加载。这就是所谓的 UMD

ES6 Module

ES6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输⼊和输出的变 量。

所以说ES6是编译时加载,不同于 CommonJS 的运⾏时加载(实际加载的是⼀整个对象),

ES6模块不是对象,⽽是通过export命令显式指定输出的代码,输⼊时也采⽤静态命令的形式。

模块功能主要由两个命令构成:export和import。

export命令⽤于规定模块的对外接⼝,import命令⽤于输⼊其他模块提供的功能。

在 ESM 中 export 和 export default 导出⽅式的区别?

export⽤于导出⼀个或多个具名的变量、函数或类。 在其他模块中,我们可以使⽤import语句导⼊这些具名的变量、函数或类: export default 则⽤于导出⼀个默认的变量、函数或类。⼀个模块只能有⼀个默认导出,⽽且在导⼊时不需要使⽤⼤括号

构建工具的作用

  • 编译浏览器无法理解的东西 es6 ts vue
  • 代替人工操作:文件的合并和拆分,图片压缩,资源处理
  • 帮助开发:开发模式

webpack

webpack核心概念

image.png

  • Entry   对象   入口文件名   入口文件地址
  • Output:包后的资源输出位置和命名规则
  1. path  将打包后的文件要输出到哪里  给一个绝对路径
  2. Filename  输出的文件名字叫什么  入口文件名字+哈希+bundle.js
  • Mode:开发模式development  生产环境 production  什么都不用 none
  • Loader :对webpack不认识的资源进行处理
  1. 配置方式:modules:{rules【{},{}】}数组里面的对象,就是一个个的loader
  2. 需要对loader进行配置  就需要用use
  3. Use 【】 如果是数组,就代表这个文件用 数组里面的loader进行处理 可能是多个 从右往左  
  4.  Use { }  如果是进行配置  就用对象   loader    options 里面写配置
  • Plugins  (做一些事情)          Plugins 数组 配置
  • 开发模式 devserver {} 对象
  • Resolve  对象 {}
  • optimization 和代码压缩相关的知识

webpack处理js

  1. 将es6转化为 es5
  2. 安装babel-loader @babel/core "presets"
  3. 写具体的配置项 要给bable一套配置规范,要不然bable不知道要做什么,把什么编译成什么。具体的配置  需要自己编写 也有定义好的一套。
  • 大多数用的是 "@babel/preset-env",
  • 编译的目标是:"targets": {"browsers": [">1%", "last 2 versions"]}
  • 大多数浏览器都支持的版本
  1. 由于babel需要配置的内容非常多,我们需要在项目根目录下创建一个 .babelrc 文件。 就不需要在 webpack.config.js 中写 babel 的配置了。

webpack处理eslint

根据一些规范对代码文件进行检查

  • 1. 通过插件:下载插件,引入插件,定义配置,定义规范 (每个团队的习惯不一样,但是也有定义好的们可以直接拿来用)  需要继承  一些 vue规范
  • 2. 环境规范 继承 插件 解析配置
  • 流程:先继承一些规范,再自己写一些具体的配置,可以覆盖掉之前的规范;
  • plugins:提供额外的rules ,比如vue的模版(必须是闭合的标签)这在js中是没有的,所以可以下载一个插件,去配置。

webpack处理css

webpack只认识js,别的都不认识。所以需要一个css-loader 让webpack识别css。 Css-loader  让webpack识别css (只认识了,不知道下一步怎么做)

  • 下面两种处理方式
  • Style-loader  把css写入js  执行后 作为style 标签插入html
  • Mini-css-plugin  提取css为单独的文件(mini.css插件+mini.css.loader)
  • less-loader 先将less编译成css 再后续处理 css
  • 对css 压缩 css-minimizer-webpack-plugin(用于 Webpack)

image.png

webpack处理资源文件

  • file-loader (只能识别资源文件)和 url-loader (在file loder的基础上,添加额外的功能,推荐使用)
  • Webpack 5 内置了 asset module,无需额外安装依赖。(推荐使用原生webapck)
  • 一些优化操作 hash  转base64   需要自己进行配置 -比如给图片加上配置,小于5kb的文件,转为base64 减少网络请求

image.png

{
  test:/\.(jpg|png|gif)$/,
type:'asset/resource', //使用asset/resource
 parser:{
 dataUrlCondition:{          //设置图片大小小于10k时,使用base64编码
  maxSize:1024*10
 } },
module.exports={
    env:{                 //环境
        browser:true,
        node:true,
        es2021:true
    },
    extends:["standard" ,
        "plugin:vue/vue3-recommended"
        
    ],  //继承一些规范   ,需要安装这些规范
    plugins:[ "vue" ],//插件 特殊的规范  + 提供一套现成的规则
    parserOptions:{     //解析配置
        ecmaVersion:6   ,       //es版本
        sourceType:'module' ,    //资源引入类型
        ecmaFeatures:{          //es特性 配置一些特性
             jsx:true
         }
    },
    rules:{              //编写规则 ,检查细节      会覆盖继承的规则
        'no-console':'off',        //检查输出   off关闭  warn警告  error错误
        
    }
}

loader的本质

是一个方法,接收到要处理的资源的内容,处理完成之后给出内容,作为打包结果

webpack处理html

需要html做什么

  • 提供一个html模版,复用固定内容
  • 打包生成一个html
  • 打包出来的html 自动引入js
  • 对 HTML 文件的处理: html-webpack-plugin 插件该插件能自动生成 HTML 文件,还可将打包后的 JavaScript 和 CSS 文件自动注入到 HTML 中

给一个打包模版 生成打包完成之后的html文件 image.png

关于这个插件的扩展
  1. -多入口文件处理,多个html 多个js,意味着不同的html对应不同的js,不能把所有的js都引入到同一个html中

image.png

  • 2. Html的压缩  的开启 与关闭

image.png

  • 3. 指定js文件在html的插入位置 inject

image.png

webpack代码分割

单入口文件

image.png

如何处理

单入口文件意味着所有代码在一个文件里,这样会导致代码过大,所以需要把一些不是马上用到的代码拆分出来,这样加快首屏速度。

实现方式

1.动态导入(按需加载)

  • 借助 ES6 的动态导入语法(import()),可以在运行时按需加载模块。
  • 哪些适合异步加载:又大 目前又 用不到   (不是所有的都适合异步引入,异步引入会让请求变多)

image.png

多入口文件的问题:重复加载同一段逻辑代码

image.png

如何处理

将相同的文件单独打包出来。实现的原理是:浏览器的缓存机制,第一次请求的时候会缓存在浏览器中,第二次就直接从缓存中读取。 在optimization进行配置。代码分割 第三方库需要单独打包

  • 第三方库  打包到 vendor .js
  • 公共的模块  打包到 common.js  
  • Webpack的 运行代码 runtime.js
  • 默认的分割规则,会让这些文件都打包到一起 image.png 使用cacheGroups分组打包

image.png

总结

单入口文件   runtime+vendor+核心业务+异步模块 多入口文件   runtime+vendor+每个入口的核心业务+common模块

webapck技巧性配置

文件哈希值

文件哈希值的意义:只要文件内容变了,哈希值就会改变; image.png

  • 造成的问题:只要一个文件的内容改变了。所有文件的哈希值都会改变。这很不合理
  • 解决:使用chunkhash

webpack开发模式

  • 工作流程:安装webpack-dev-server ,配置devServer字段,用webpack-dev-server运行
  • 原理:

image.png

配置 source map

出现错误,打包后的代码不好阅读。 source map 可以将打包后的代码映射回原始源代码,便于调试。在开发模式下,推荐使用 eval-cheap-module-source-map。

热模块替换(HMR)

热模块替换是 webpack-dev-server 的一个重要特性,它允许在运行时更新模块而不需要刷新整个页面。

  • 热更新:在不刷新浏览器的情况下更新页面,可以保持页面的当前状态 (改变css代码,资源文件)
  • 强制更新:自动刷新页面来更新界面,会重置页面状态 (js代码)

在开发阶段解决跨域问题

路径匹配,路径代理-路径重写

Proxy:由webpack-dev-server开启的node服务来代替我们请求接口,因为如果后端没有开启cors,我们直接从前端请求会跨域。利用proxy,请求从node发。

  • 原理:开发服务器(如 Webpack Dev Server、Vite 开发服务器)可以作为一个中间层,接收前端应用的请求,然后将这些请求转发到实际的后端 API 服务器。由于服务器之间的请求不受同源策略的限制,这样就可以绕过浏览器的同源策略,解决跨域问题。
  • 路径重写功能

image.png

dev server 常⻅配置

contentBase: 告诉服务器从哪⾥提供内容。默认情况下,将使⽤当前⼯作⽬录作为提供内容的位置。

host: 设置开发服务器的主机地址,默认是 localhost。

port: 设置开发服务器的端⼝号,默认是 8080。

open: 设置为 true 时,开发服务器启动后将⾃动打开浏览器。

hot: 开启热模块替换功能,即如果模块更新了,只替换更新了的部分,⽹⻚不会全部刷新。

historyApiFallback: 任意的404响应都可能需要被替换为 index.html。对于单⻚应⽤来说⾮常有⽤。

headers: 允许开发者⾃定义服务器响应的 HTTP 头。

proxy: 代理配置,可以将特定的 API 请求代理到另⼀台服务器上。

compress: 为所有服务启⽤ gzip 压缩

* devServer 配置:
 * - contentBase: 指定静态文件的目录。
 * - compress: 启用 gzip 压缩。
 * - port: 设置开发服务器的端口号。
 * - open: 自动打开浏览器。
 * - hot: 启用模块热替换(HMR)。
 devServer: {
        contentBase: path.join(__dirname, 'dist'),
        compress: true,
        port: 9000,
        open: true,
        hot: true,
    },

webpack使用过哪些loader和plugin,对应的使用场景

  • babel-loader: 将 ES6+ 代码转译为 ES5。
  • css-loader: 解析 CSS 中的 @import 和 url()。
  • sass-loader: 将 Sass/SCSS 编译为 CSS。
  • file-loader: 处理文件(如图片)的导入路径。
  • url-loader :与 file-loader 类似,区别是⽤户可以设置⼀个阈值,⼤于阈值会交给 file-loader 处理,⼩于阈值时返回⽂件 base64 形式编码 (处理图⽚和字体)
  1. HtmlWebpackPlugin: 生成 HTML 文件并自动引入打包后的资源。
  2. MiniCssExtractPlugin: 将 CSS 提取为独立文件(替代 style-loader)。
  3. CleanWebpackPlugin: 清理构建目录(Webpack 5 中可用 output.clean: true 替代)。
  4. DefinePlugin: 注入全局常量(如 process.env.NODE_ENV)

Loader和Plugin的区别?

Loader 本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。 因为 Webpack 只认识 JavaScript,所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作。

Webpack 本身主要识别 JavaScript 文件,然而在现代 Web 开发中,项目里会包含多种类型的文件,像 CSS、图片、字体、JSON 等。Loader 能让 Webpack 处理这些非 JavaScript 文件,将它们转化成 Webpack 可以理解和打包的模块。

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

Loader 在 module.rules 中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性。

Plugin 在 plugins 中单独配置,类型为数组,每一项是一个 Plugin 的实例,参数都通过构造函数传入。

  1. 职责不同:
  • Loader负责⽂件的转换,将不同类型的⽂件转换成Webpack可识别的模块;
  • Plugin则更侧重于扩展Webpack的功能,可以对Webpack输出的结果进⾏优化、处理和修改。
  1. 使⽤⽅式不同:
  • Loader通过module.rules配置使⽤,可以⽤于⽂件类型转换,例如将LESS、SCSS、ES6、JSX,等⽂件转换并打包成最终的静态资源⽂件;
  • Plugin则通过插件机制绑定到Webpack实例上,在Webpack构建⽣命周期的不同节点执⾏特定的操作。

Webpack的作用

处理兼容,对代码打包压缩。 核心能力:

  1. 打包压缩:会对所有的js代码和css代码 进行压缩。对于js,除了压缩之外,还会混淆名称
  2. 文件指纹:除了页面之外,其他的资源在打包之后,文件名多了一些字符。文件哈希值 的 某几位。会随着文件内容的变化而变化。源码内容不变,hash不变。源码变了,hash值也会改变。为什么这么做:生产环境中,浏览器会对除页面之外的静态资源进行缓存,如果不设置hash值,一旦代码更改,浏览器还用之前的缓存结果,无法使用最新的代码。
  3. 开发服务器:一边写代码,一边看效果。正常:修改完代码--打包 --运行,很麻烦的。在开发阶段,启动一个开发服务器,在这个阶段,webpack并不会形成打包结果文件,而是把打包的内容放在内存中,当我们请求服务器时,服务器会从内存中给我们打包结果。与此同时:当源码发生变化,webpack会自动打包更新,同时刷新页面访问到最新的打包结果。

image.png

babel原理

Babel 是一个 JavaScript 编译器,主要用于将 ECMAScript 2015+(ES6+)版本的代码转换为向后兼容的 JavaScript 代码,以便在旧版本的浏览器或环境中运行。其工作原理主要分为三个阶段:解析(Parsing)、转换(Transformation)和生成(Code Generation)。

解析(Parsing)

解析阶段会将输入的 JavaScript 代码字符串转换为抽象语法树(Abstract Syntax Tree,简称 AST)。这个过程分为两个子阶段:词法分析和语法分析。

转换(Transformation)

转换阶段会对解析得到的 AST 进行修改和转换。Babel 通过插件(Plugins)来实现具体的转换逻辑。每个插件都可以对 AST 进行特定的修改,比如将 ES6 的箭头函数转换为 ES5 的普通函数,将 const 和 let 声明的变量转换为 var 声明的变量等。
插件会遍历 AST 树,找到需要转换的节点,并对其进行修改。例如,对于一个箭头函数 const add = (a, b) => a + b;,Babel 的箭头函数转换插件会将其转换为一个普通函数:

生成(Code Generation)

生成阶段会将转换后的 AST 重新转换为 JavaScript 代码字符串。代码生成器会遍历转换后的 AST,根据节点的类型和属性,生成对应的代码。在生成代码的过程中,还会处理代码的格式化和缩进等问题。

总结

Babel 的工作原理可以概括为:先将输入的 JavaScript 代码解析为 AST,然后通过插件对 AST 进行转换,最后将转换后的 AST 重新生成 JavaScript 代码。

Webpack 的工作流程

  1. 入口(Entry) :从指定文件(如 index.js)开始分析依赖。
  2. 依赖图(Dependency Graph) :递归构建模块间的依赖关系。
  3. 加载器(Loaders) :转换非 JS 资源(如编译 Sass、处理图片)。
  4. 插件(Plugins) :在构建生命周期中执行优化任务。
  5. 输出(Output) :生成优化后的静态文件(如 bundle.js

Vite 特点

  1. 特点:
  • 利用esm ,让代码不像传统的构建工具一样去分析引入,打包构建,而是直接保持模块化,节省大量的编译时间,让代码更改后的响应速度大量提升
  • 生产方式 构建方面:用得是roolup
  • 流程:安装vite --指定一个html为入口(wp,rollup指定的是js为入口文件)--vite build(打包) vite(开发模式的运行)
  • 处理其他资源:都支持:天生css以及预处理语言,能处理各种资源,支持ts
  • 分割代码:vite会自动分割异步引入代码,第三方库vendor或者特殊的拆分需要借助rollup的manulChunks
  • 自带模版仓库:npm create vite@latest my -vue -app ---template
  • vite 的配置:在build里面配置,就是打包的配置,主要就是使用rollup进行配置。
  • vue的识别: @vitejs/plugin-vue 插件,它是 Vite 支持 Vue 3 的关键。该插件能让 Vite 解析 .vue 文件,处理其中的模板、脚本和样式部分。
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
// 导出 Vite 配置
export default defineConfig({
    server: {
        host: 'localhost', // 开发服务器主机地址
        port: 3000, // 开发服务器端口
        open: true, // 自动打开浏览器
        proxy: { // 配置代理
            '/api': {
                target: 'http://localhost:5000', // 代理目标地址
                changeOrigin: true, // 是否改变源
                rewrite: (path) => path.replace(/^\/api/, ''), // 重写路径
            },
        },
    },
    build: {
        outDir: 'dist', // 构建输出目录
        sourcemap: true, // 生成 source map 文件,方便调试
    },
    resolve: {
        alias: {
            '@': '/src', // 设置路径别名,将 '@' 指向 'src' 目录
        },
    },
    css: {
        preprocessorOptions: {
            scss: {
                additionalData: `@import "@/styles/variables.scss";`, // 全局引入 SCSS 变量文件
            },
        },
    },
     plugins: [vue()]
});

vite好用的插件

  • 框架支持@vitejs/plugin-vue 用于 Vue 3,@vitejs/plugin-vue2 用于 Vue 2。
  • 代码检查vite-plugin-eslint 检查 JS 代码规范,vite-plugin-stylelint 检查样式规范。
  • 打包优化vite-plugin-compression 压缩文件,rollup-plugin-visualizer 分析打包结果。
  • 开发体验unplugin-vue-components 自动导入组件,vite-plugin-vue-inspector 方便定位组件代码。

Rollup

特点

  • 不会生成过多的运行代码
  • 可以多模块化规范打包

rollup适⽤于基础库的打包,如vue、d3等: Rollup 就是将各个模块打包进⼀个⽂件中,并且通过 Tree-shaking 来删除⽆⽤的代码,可以最⼤程度上降低代码体积,但是rollup没有webpack如此多的的如代码分割、按需加载等⾼级功能,其更聚焦于库的打包,因此更适合库的开发

image.png

Vite为什么更快

Vite 相比传统构建工具(如 Webpack)更快🚀,主要得益于以下几个核心特性:

  • 基于原生 ES 模块(ESM):Vite 利用浏览器原生的 ES 模块,在开发模式下按需加载模块,避免了整体打包,从而减少了启动时间。它通过只编译实际修改的文件,提升了开发过程中的反馈速度。
  • 高效的热模块替换(HMR):Vite 在开发模式下利用原生 ES 模块实现模块级的热更新。当文件发生变化时,Vite 只会重新加载发生变化的模块,而不是重新打包整个应用,极大提高了热更新的速度。
  • 使用 esbuild 进行快速编译:Vite 默认使用 esbuild 作为编译工具,相比传统的 JavaScript 编译工具(如 Babel、Terser),esbuild 提供显著的性能提升,能够快速完成代码转换和压缩,从而加速开发和构建过程。
  • 现代 JavaScript 特性支持:Vite 在生产环境中使用 Rollup 构建,支持优秀的树摇和代码拆分,有效减小构建体积。同时,Vite 利用现代浏览器特性(如动态导入、ES2015+ 模块),减少了 polyfill 的使用,提升了加载速度。
  • 预构建和缓存:Vite 在开发时会预构建常用依赖(如 Vue、React),并将其转换为浏览器可执行的格式,避免每次启动时重新编译。同时,Vite 会缓存这些预构建的依赖,并在启动时复用缓存,从而加快启动速度。

vite和webpack的区别

开发理念:从“打包一切”到“按需加载”

Webpack 的核心思想是“先打包后运行”。它会在启动开发服务器之前,分析项目的所有依赖关系,生成一个完整的依赖图,并将所有模块打包成一个或多个文件。这种方式类似于“先把所有材料准备好,再开始做菜”虽然稳妥,但启动速度较慢。

Vite充分利用了现代浏览器 原生ES Module转移ESM)的支持,采用“按需加载”的方式。开发服务器无需提前打包所有模块,而是根据浏览器的请求动态编译所需的文件。这种方式更像是“现做现卖,需要什么拿什么”,极大地提升了开发效率。

冷启动速度:即开即用vs.全量打包

冷启动速度是开发者最直观的感受之一。 Webpack的启动过程包括依赖分析、模块打包和开发服务器启动。对于大型项目,这个过程可能需要几十秒甚至几分钟,尤其是首次启动时。

而 Vite 的冷启动速度几乎可以做到“即开即用”。它直接启动开发服务器,只有在浏览器请求某个模块时才会动态编译相关文件。得益于这种按需加载的机制,Vite的启动速度通常比Webpack快 10-100倍。

热更新

热更新(HMR)是开发过程中频繁使用的功能,它直接影响开发体验。

Webpack:在检测到代码修改时,需要重新打包受影响的模块及其相关依赖。随着项目规模的增大,模块依赖链变长,热更新速度会逐渐变慢。

Vite:只会精确编译修改的模块,并立即将更新推送到浏览器。由于它充分利用了浏览器缓存机制,热更新速度几乎与项目大小无关,开发体验更加流畅。

配置复杂度

Webpack的配置灵活性极高,但也因此显得复杂。初学者需要花费大量时间学习如何配置loader 和plugin,才能充分发挥Webpack的能力。

Vite 则以“开箱即用”为设计理念,提供了简单直观的默认配置。对于大多数项目来说,Vite 的默认设置已经足够使用,开发者只需进行少量调整即可。

5生产环境构建:成熟度与速度的权衡

Webpack 统一打包流程//复杂配置//插件生态成熟

Vite 开发: 原生 ESM //生产: Rollup 打包 //预设优化配置

在生产环境中,Webpack经过多年验证,支持多种优化手段(如代码分割、TreeShaking等),适合处理复杂的企业级项目。

Vite 在生产环境中使用 Rollup 进行打包,同样支持各种优化功能。虽然在超大型项目中,Vite 的表现可能不如 Webpack 稳定,但它的构建速度和输出结果依然令人满意。

生态兼容性:老牌工具 vs.新兴力量

Webpack 作为前端构建工具的“老大哥”,拥有庞大的生态系统和社区支持。几乎所有的前端框架和库都提供了针对 Webpack 的插件和 loader。对旧浏览器的兼容性更好,适合需要支持广泛浏览器的项目。

Vite 虽然起步较晚,但发展迅速。得益于现代前端框架(如Vue和 React)的支持,Vite 的生态系统正在快速扩展,逐渐成为开发者的新宠。由于Vite基于ES Modules,需要 @vitejs/plugin-legacy 等插件来支持一些旧的浏览器(如IE11)。

7适用场景

Webpack更适合: 需要支持低版本浏览器的项目复杂的企业级项目; 需要高度定制化配置的场景

Vite 更适合: 现代浏览器环境 中小型项目 对开发体验要求高的场景

image.png

webpack打包流程

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

  1. 初始化参数:从配置⽂件和 Shell 语句中读取与合并参数,得出最终的参数;

  2. 开始编译:⽤上⼀步得到的参数初始化 Compiler 对象,加载所有配置的插件,执⾏对象的 run ⽅法开始执⾏编译;

  3. 确定⼊⼝:根据配置中的 entry 找出所有的⼊⼝⽂件;

  4. 编译模块:从⼊⼝⽂件出发,调⽤所有配置的 Loader 对模块进⾏翻译,再找出该模块依赖的模块,再递归本步骤直到所有⼊⼝依赖的⽂件都经过了本步骤的处理;

  5. 完成模块编译:在经过第4步使⽤ Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;

  6. 输出资源:根据⼊⼝和模块之间的依赖关系,组装成⼀个个包含多个模块的 Chunk,再把每个 Chunk 转换成⼀个单独的⽂件加⼊到输出列表,这步是可以修改输出内容的最后机会;

  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和⽂件名,把⽂件内容写⼊到⽂件系统。

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

Webpack 的热更新原理

HMR全称 Hot Module Replacement,可以理解为模块热替换,指在应⽤程序运⾏过程中,替换、添加、删除模块,⽽⽆需重新刷新整个应⽤。

例如,我们在应⽤运⾏过程中修改了某个模块,通过⾃动刷新会导致整个应⽤的整体刷新,那⻚⾯中的状态信息都会丢失。

如果使⽤的是 HMR,就可以实现只将修改的模块实时替换⾄应⽤中,不必完全刷新整个应⽤

  • 整个流程分为客户端和服务端,通过 websocket 建⽴起 浏览器端 和 服务器端 之间的通信。
  • webpack 会创建 webserver 静态服务器,让浏览器可以请求编译⽣成的静态资源,还会创建 websocket 服务,建⽴本地服务和浏览器的双向通信。
  • 然后以 watch 模式启动编译,监听到⽂件变化后,根据配置⽂件对模块重新编译打包,通过 websocket 告知浏览器执⾏热更新逻辑。
  • 浏览器接收服务器端推送的消息,如果需要热更新,浏览器发起 http 请求去服务器端获取新的模块资源解析并局部刷新⻚⾯。

首先是建立起浏览器端和服务器端之间的通信,浏览器会接收服务器端推送的消息,如果需要热更新,浏览器发起http请求去服务器端获取打包好的资源解析并局部刷新页面。

 webpack打包后输入url第一个访问到的是什么

 Webpack 打包后,输入 URL 第一个访问到的通常是项目的入口文件,一般是 index.html

如何⽤webpack来优化前端性能?

⽤webpack优化前端性能是指优化webpack的输出结果,让打包的最终结果在浏览器运⾏快速⾼效。

  • 压缩代码:删除多余的代码、注释、简化代码的写法等等⽅式。可以利⽤webpack的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩JS⽂件, 利⽤ cssnano (css-loader?minimize)来压缩css
  • 利⽤ CDN 加速: 在构建过程中,将引⽤的静态资源路径修改为CDN上对应的路径。可以利⽤webpack对于 output 参数和各loader的 publicPath 参数来修改资源路径
  • Tree Shaking: 将代码中永远不会⾛到的⽚段删除掉。可以通过在启动webpack时追加参数 --optimize-minimize 来实现
  • Code Splitting: 将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利⽤浏览器缓存
  • 提取公共第三⽅库: SplitChunksPlugin插件来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码

提⾼ Webpack 打包速度

  1. 优化loader配置

  2. 优化resolve.alias 起别名

  3. 使⽤cache-loader 进⾏缓存

  4. HappyPack 启动多线程

  5. 合理使⽤ sourceMap

优化loader配置,精准匹配⽂件,缩⼩⽂件范围

在使⽤ loader 时,可以通过配置 include 、 exclude 、 test 属性来精准的匹配哪些⽂件应⽤ loader ,使⽤include来指定编译⽂件夹,exclude排除指定⽂件夹。

优化 resolve.alias 起别名

alias给⼀些常⽤的路径起⼀个别名,特别当我们的项⽬⽬录结构⽐较深的时候,⼀个⽂件的路径可能 是./../../的形式,通过配置alias以减少查找过程

使⽤ cache-loader 缓存 在⼀些性能开销较⼤的 loader 之前添加 cache-loader ,以将结果缓存到磁盘⾥,显著提升⼆次构建的速度。 注意:保存和读取这些缓存⽂件也会有⼀些时间开销,所以只对性能开销较⼤的 loader 使⽤此 loader

合理使⽤Source map 在开发环境下,因为需要调试源代码,⼀般会开启Source Map。⽽在⽣产环境下,为了保护代码的安全且获得更好的性能,我们⼀般不开启Source Map。

Tree-Shaking

通过静态代码分析的⽅式,识别出未使⽤的模块、函数、变量等,并将其从最终的构建输出中删除。

在 Webpack 中,启动 Tree Shaking 功能必须同时满⾜三个条件:

  • 使⽤ ESM 规范编写模块代码,因为 Tree-Shaking 强依赖于 ESM 模块化⽅案的静态分析能⼒,所以你应该尽量坚持使⽤ ESM 编写模块代码。对⽐⽽⾔,在过往的 CommonJS、AMD、CMD 旧版本模块化⽅案中,导⼊导出⾏为是⾼度动态,难以预测的
  • 配置 optimization.usedExports 为 true,启动标记功能;
  • 启动代码优化功能,可以通过如下⽅式实现: 配置 mode = production; 配置 optimization.minimize = true ; 提供 optimization.minimizer 数组。

原理:

静态分析:Tree Shaking 依赖于静态代码分析,它在不执⾏实际代码的情况下,通过解析JavaScript 源代码来了解模块之间的依赖关系、函数的调⽤关系和变量的引⽤关系。这种静态分析是在构建过程中进⾏的

标记未使⽤代码:在静态分析过程中,Tree Shaking 标记未使⽤的代码。通过分析导⼊和导出之间的依赖关系,判断哪些代码是被使⽤的,哪些是未使⽤的。

删除未使⽤代码:⼀旦标记了未使⽤的代码,Tree Shaking 就会将这些代码从最终的打包结果中删除。

依赖关系追踪:Tree Shaking 还需要追踪模块之间的依赖关系,以确定哪些模块是被使⽤的。通过分析导⼊语句,它可以构建⼀个依赖图,记录模块之间的关系。这样,在标记未使⽤代码时,TreeShaking 可以确定哪些模块是被使⽤的,哪些是没有被使⽤的。

优化输出:⼀旦完成 Tree Shaking 过程,构建⼯具会⽣成优化的输出⽂件。未使⽤的模块和代码块被删除,最终的打包⽂件只包含被使⽤的部分,从⽽减⼩了⽂件的⼤⼩。

参考: 【前端面试】webpack 和 vite 区别知多少?_哔哩哔哩_bilibili

vite详细配置_哔哩哔哩_bilibili
「吐血整理」再来一打Webpack面试题本文已收录在Github github.com/Geekhyt,欢迎Star。 - 掘金 (juejin.cn)

前端工程化 | 前端面试派 (mianshipai.com)