Webpage相关

185 阅读9分钟

Webpack相关

1.前端构建(打包)工具有哪些?区别?

gruntgulp前端构建工具

主要讲webpackgulp的区别

  • webpack专注于实现模块化,高效地管理和维护项目中的资源。而gulp强调的是自动化构建流程,自定义配置一系列任务按照顺序执行,后端开发的思维。
  • webpack适合单页面开发,将资源模块化打包,适配各种模块系统,减少请求资源的数量、程序等待时间。gulp适合多页面应用开发,通用,更多适用于轻量化中。
  • webpack使用起来是基于入口文件的,根据入口自动解析需要加载的资源文件,然后使用loader来处理不同的文件,使用plugin来扩展功能;gulp是基于任务流的,一整个链式操作构成一个任务。
  • webpack使用起来相对复杂,但是功能强大,众多的plugins资源可供webpage拓展;gulp相对简单一点,只需要定义一些任务让他们顺序执行就好。

WebpackviteRollupParcelSnowpack前端打包工具

  • webpack适用于大型复杂的前端项目的打包构建,plugins和loader实现强大的功能。
  • rollup适用于基础库的打包,如vuereact,和webpack类似,但是更轻量化;但是加载其他资源时或者实现其他功能需要引入各种模块、插件;不支持HMR,开发效率低;只打包js库时可用他。
  • parcel适用于简单的实验性项目,他可以满足低门槛的快速看到效果
  • Vite是直接开启一个服务器,没有webpack那样的打包过程,所以启动很快;HMR效率高;不够成熟。

2.webpack的loader和plugins区别?常见的loader和plugins?

webpack本身只能打包commonjs规范的js的文件,因此引入Loader,Loader可以加载不同格式的资源文件,并对这些文件进行一些处理包括编译、压缩等,如将scss转换为css,或者typescript转化为js,loader运行在Nodejs中,仅仅为了打包各种资源文件。

如:

file-loader:文件加载

url-loader:文件加载,可以设置阈值,小于时把文件base64编码

image-loader:加载并压缩图片

babel-loader:ES6+转成ES5

ts-loader:将ts转成js

css-loader:处理@importurl这样的外部资源

style-loader:在head创建style标签把样式插入s

eslint-loader:进行代码eslint检查

cache-loader:性能开销大的loader前添加,将结果缓存到磁盘

Plugin目的在于解决loader无法实现的其他事,也是对webpack的功能进行拓展,从打包优化和压缩,到重新定义环境变量,功能强大到可以用来处理构建过程中各种各样的任务。

如:

  1. ignore-plugin:忽略文件
  2. terser-webpack-plugin:支持压缩ES6(webpack4)
  3. webpack-arallel-uglify-plugin: 多进程执行代码压缩,提升构建速度
  4. mini-css-extract-plugin: 分离样式文件,css提取为独立文件,支持按需加载
  5. serviceworker-webpack-plugin: 为网页应用增加离线缓存功能
  6. clean-webpack-plugin: 目录清理
  7. 为什么不用viteProviderPlugin: 自动加载模块,代替requireimport
  8. html-webpack-plugin可以根据模板自动生成html代码,并自动引用cssjs文件
  9. extract-text-webpack-pluginjs文件中引用的样式单独抽离成css文件
  10. compression-webpack-plugin 生产环境可以采用gzip压缩jscss
  11. happypack: 通过多进程模式,来加速代码构建

Loader运行在打包文件之前;

Plugins在整个编译周期都起作用

3.如何自己写一个loader或者plugin?

loader就是对源码进行编译转换,如ES6编译为ES5,一个loader就是一个function,每一个function接收一个源码作为source参数,进行一些处理后返回。

实现一个简单的loader:删除调试代码阶段写的一些console.log()代码

// 目录结构
loader-demo
 ├─index.js
 └package.json
  • npm init之后在package.json同目录下新建一个js文件,内容为:

    // index.js内容
    module.exports = function(source) {
      console.log('----------- loader -----------');
      return source.replace(/console.log(.*?)/, '');
    }
    
  • 然后要使用这个loader的话需要先发布该loader

    • 通过npm publish命令将loader-demo发布到npm仓库;这样就可以在另外一个工程使用了;
    • 为了方便测试,也可以使用npm link命令,进入loader-demo目录,执行npm link
    • 在一个工程项目中执行npm link loader-demo
  • 在配置文件中添加规则使用loader-demo

    // webpack.config.js内容
    const path = require('path');
    module.exports = {
      entry: './src/index',
      output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'app.bundle.js'
      },
      module: {
        rules: [
          {
            test: /.js$/,
            use: 'loader-demo'
          }
        ]
      }
    }
    

实现一个简单的plugin:在打包后的每个js文件头部加入版权信息。

// 目录结构
yo-plugin-demo
 ├─index.js
 └package.json
  • npm init之后在package.json同目录下新建一个js文件,内容为:

    // index.js内容
    module.exports = class YoPloginDemo {
      // 构造器,传入版权信息
      constructor(crInfo) {
        this.crInfo = crInfo;
      }
      // 必须,webpack运行时调用
      apply(compiler) {
        // webpack生命周期,生成资源到output目录之前执行
        // 具体可以查看 https://www.webpackjs.com/api/compiler-hooks
        compiler.hooks.emit.tap('YoPluginDemo', compilation => {
          console.log('----------- plugin -----------');
          // 遍历所有资源,以js结尾的文件,在头部加上版权信息
          for(const fileName in compilation.assets) {
            if(/.js$/.test(fileName)) {
              const asset = compilation.assets[fileName];
              asset.source = () => {
                return `/** Copyright © ${this.crInfo} */\r\n${asset._value}`
              }
            }
          }
        });
      }
    }
    
  • 然后要使用这个plugin的话需要先发布该plugin

    • 通过npm publish命令将yo-plugin-demo发布到npm仓库;这样就可以在另外一个工程使用了;
    • 为了方便测试,也可以使用npm link命令,进入yo-plugin-demo目录,执行npm link
    • 在一个工程项目中执行npm link yo-plugin-demo
  • 在配置文件中

// webpack.config.js内容
const path = require('path');
const YoPluginDemo = require('yo-plugin-demo');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  entry: './src/index',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'app.bundle.js'
  },
  module: {
    rules: [
      {
        test: /.js$/,
        use: 'loader-demo'
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin(),
    new YoPluginDemo('卢本伟牛逼出品'),
  ]
}

4.webpack的构建流程?

随着前端开发越来越复杂,规模越来越大,开始走向了工程化的独立开发,日常写的代码包括.js.html.css.json.png.scss等需要我们进行打包合并到一个文件里面,简单来说就是把这些文件压缩、打包等一系列操作自动化,包括分析项目结构、配合loader处理ES6语法等特殊资源的加载和解析、通过plugin实现自动化操作。

整个过程包括两方面:

  • 通过 Loader 处理特殊类型资源的加载,例如加载样式、图片;
  • 通过 Plugin 实现各种自动化的构建任务,例如自动压缩、自动发布。

具体的流程:

  1. 初始化:从配置文件开始读取和合并参数,对参数进行整合,得到整合后的参数
  2. 开始编译:用上一步整合后的参数,初始化Complier对象,加载所有配置的插件(调用插件中的apply方法),通过complie.run开始执行编译
  3. 确定入口:根据配置文件的entry找到入口文件
  4. 编译模块:从入口文件出发,调用配置的loader,对模块进行转换。具体的操作是根据入口模块开始依次递归找出所有依赖,形成依赖关系树,然后将递归结果交给各自的loader进行编译处理,得到chunk。
  5. 输出:模块转换完成后得到一个个的chunk代码块,并转换成文件输出。

5.用webpack来优化前端性能?

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

  • 压缩代码。删除多余的代码、注释、简化代码的写法等等方式。可以利用webpackUglifyJsPluginParallelUglifyPlugin来压缩JS文件
  • 利用CDN加速。在构建过程中,将引用的静态资源路径修改为CDN上对应的路径。可以利用webpack对于output参数和各loaderpublicPath参数来修改资源路径
  • 删除死代码(Tree Shaking)。将代码中永远不会走到的片段删除掉。可以通过在启动webpack时追加参数optimize-minimize来实现
  • 提取公共代码
  • 分离代码,按需加载或者并行加载分离出来的bundle文件

6.如何提高webpack的构建速度?

npm run serve/build时速度慢,项目越来越复杂。

主要可以从优化搜索时间、缩小文件搜索范围、减少不必要的编译等方面入手

  • 优化 loader 配置

可以通过配置includeexcludetest属性来匹配文件

  • 合理使用 resolve.extensions
module.exports = {
    ...
    extensions:[".warm",".mjs",".js",".json"]
}

配置的时候,则不要随便把所有后缀都写在里面,这会调用多次文件的查找,这样就会减慢打包速度

  • 优化 resolve.modules

resolve.modules 用于配置 webpack 去哪些目录下寻找第三方模块,可以使用绝对路径把第三方模块放在项目根目录。

  • 优化resolve.alias
resolve:{
        alias:{
            "@":path.resolve(__dirname,'./src')
        }
    }

alias给常用路径起一个别名,如根目录用@表示

  • 使用DLLPlugin插件

把那些不常用的代码抽成一个共享的库,再需要的时候直接引入即可。

  • 使用 cache-loader

在一些性能开销较大的 loader之前添加 cache-loader,以将结果缓存到磁盘里,显著提升二次构建速度

保存和读取这些缓存文件会有一些时间开销,所以请只对性能开销较大的 loader 使用此loader

module: {
        rules: [
            {
                test: /.ext$/,
                use: ['cache-loader', ...loaders],
                include: path.resolve('src'),
            },
        ],
    },
  • terser启动多线程
optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true,
      }),
    ],
  },
  • 合理使用 sourceMap

7.webpack的热更新是如何做到的?原理是什么?

HMR全称 Hot Module Replacement,热模块更新/替换,指在应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个应用。

image-20220315160417492

  • Webpack Compile:将 JS 源代码编译成 bundle.js
  • HMR Server:用来将热更新的文件输出给 HMR Runtime
  • Bundle Server:静态资源文件服务器,提供文件访问路径
  • HMR Runtimesocket服务器,会被注入到浏览器,更新文件的变化
  • bundle.js:构建输出的文件
  • HMR RuntimeHMR Server之间建立 websocket,即图上4号线,用于实时更新文件变化

上面图中,可以分成两个阶段:

  • 启动阶段为上图 1 - 2 - A - B

在编写未经过webpack打包的源代码后,Webpack Compile 将源代码和 HMR Runtime 一起编译成 bundle文件,传输给Bundle Server 静态资源服务器

  • 更新阶段为上图 1 - 2 - 3 - 4

    • 当某一个文件或者模块发生变化时,webpack监听到文件变化对文件重新编译打包,编译生成唯一的hash值,这个hash值用来作为下一次热更新的标识 根据变化的内容生成两个补丁文件:manifest(包含了 hashchundId,用来说明变化的内容)和chunk.js 模块

    • 由于socket服务器在HMR RuntimeHMR Server之间建立 websocket链接,当文件发生改动的时候,服务端会向浏览器推送一条消息,消息包含文件改动后生成的hash

    • 在浏览器接受到这条消息之前,浏览器已经在上一次socket 消息中已经记住了此时的hash 标识,这时候我们会创建一个 ajax 去服务端请求获取到变化内容的 manifest 文件

      mainfest文件包含重新build生成的hash值,以及变化的模块

    • 浏览器根据 manifest 文件获取模块变化的内容,从而触发render流程,实现局部模块更新

8.常用的库有哪些?作用?

"cross-env": "^7.0.3",  设置跨操作系统的环境变量
"css-loader": "^6.7.1", 解析css文件
"scss-loader": "^0.0.1",  解析scss文件
"style-loader": "^3.3.1", 将解析的css文件打包到bundle.js文件中
"html-webpack-plugin": "^5.5.0", 输入一个html模板文件,然后生成一个自动引入打包后生成的css文件的html文件
"mini-css-extract-plugin": "^2.6.1", 该插件将单独把css文件提取出来,用于生产环境中
"webpack-merge": "^5.8.0", 将开发环境和生产环境分开后,分别在两个配置文件里用于合并公共配置文件,类似Object.assign({},'a')
"source-map": "^0.7.4", 可以通过开发者工具看到源码,方便调试,主要用于开发环境,生产环境非要用就用hidden-source-map这个类型或者白名单策略

基础:

"webpack": "^5.74.0", 核心包
"webpack-cli": "^4.10.0", 命令行工具包
"webpack-dev-server": "^4.9.3", 开启一个本地服务器访问打包代码,处理网络请求,不用每次都重新打包,用于开发环境中

预处理器loader:

"babel-loader": ES6->ES5
"file-loader": 处理css、js文件的导入语句并替换成访问地址,同时把文件输出到相应位置
"url-loader": file-loader的升级版,除了上述功能还有base64编码能力,可以减少小于8KB的文件的一次网络请求

插件plugin:

"clean-webpack-plugin": 重复打包后,删除本地上次打包的文件
"copy-webpack-plugin": 用来复制文件,如一些图片和音频等资源,打包过程没有用到,但需要输出到输出目录下

开发环境和生产环境的需求区别:

  • 开发环境的需求:

    • 模块热更新(本地开启服务,页面实时更新,不用每次都刷新一下页面,只需要把webpack-dev-server里的hot设置为true)
    • sourceMap(方便打包调试)
    • 接口代理(配置proxyTable解决开发环境中的跨域问题)
    • 代码规范检查
  • 生产环境的需求:

    • 压缩混淆代码,清楚代码空格、注释
    • 文件压缩/图片使用Base64编码
    • 去除无用代码

开发环境配置:

"webpack":  核心包
"webpack-cli": 命令行工具包
"webpack-dev-server": 开启一个本地服务器访问打包代码
"source-map": "^0.7.4", 可以通过开发者工具看到源码,方便调试,主要用于开发环境,生产环境非要用就用hidden-source-map这个类型或者白名单策略
"cross-env": 设置跨操作系统的环境变量
​
"css-loader": 解析css文件
"scss-loader": 解析scss文件
"style-loader": 将解析的css文件打包到bundle.js文件中
"mini-css-extract-plugin": "^2.6.1", 该插件将单独把css文件提取出来,用于生产环境中
"html-webpack-plugin": "^5.5.0", 输入一个html模板文件,然后生成一个自动引入打包后生成的css文件的html文件
​
"file-loader""url-loader": 解析文件导入地址并转换为访问地址,同时把文件输出到相应位置,未来被Assets Modules代替
"webpack-merge": "^5.8.0", 将开发环境和生产环境分开后,分别在两个配置文件里用于合并公共配置文件,类似Object.assign({},'a')
​

生产环境配置:

"webpack":  核心包
"webpack-cli": 命令行工具包
"webpack-dev-server": 开启一个本地服务器访问打包代码
"cross-env": 设置跨操作系统的环境变量
​
"css-loader": 解析css文件
"scss-loader": 解析scss文件
"style-loader": 将解析的css文件打包到bundle.js文件中
"mini-css-extract-plugin": "^2.6.1", 该插件将单独把css文件提取出来,用于生产环境中
"html-webpack-plugin": "^5.5.0", 输入一个html模板文件,然后生成一个自动引入打包后生成的css文件的html文件
​
"webpack-merge": "^5.8.0", 将开发环境和生产环境分开后,分别在两个配置文件里用于合并公共配置文件,类似Object.assign({},'a')

\