webpack

166 阅读13分钟

一:在学习webpack之前,我们需要了解一下什么是前端工程化的概念

为什么要进行工程化

以模块化,组件化,规范化,自动化为基础,对前端项目开发的方式叫前端工程化,工程化开发的思想提供了一套标准的开发方案和流程,让前端开发自成体系

前端开发的四个现代化

  • ① 模块化(js 的模块化、css 的模块化、资源的模块化)
  • ② 组件化(复用现有的 UI 结构、样式、行为)
  • ③ 规范化(目录结构的划分、编码规范化、接口规范化、文档规范化、 Git 分支管理)
  • ④ 自动化(自动化构建、自动部署、自动化测试)

二:工程化开发的背景下衍生出了webpack

他的作用就是让我们项目的符合前端工程化,自动帮我们的代码兼容浏览器,让程序员把更多的精力放在业务上。

三:webpack的好处

从官网上的官方文档来看,总结出webpack他包含了一下几个好处

  • 模块打包。可以将不同模块的文件打包整合在一起,并且保证它们之间的引用正确,执行有序。利用打包我们就可以在开发的时候根据我们自己的业务自由划分文件模块,保证项目结构的清晰和可读性。

  • 编译兼容 。通过webpackLoader机制,不仅仅可以帮助我们对代码做polyfill,还可以编译转换诸如.less, .vue, .jsx这类在浏览器无法识别的格式文件 。当我们用到了高级的语法时候,浏览器不兼容的时候会报错,webpack会帮我们做降级,让我们在开发的时候可以使用新特性和新语法做开发,提高开发效率。

  • 能力扩展。通过webpackPlugin机制,我们在实现模块化打包和编译兼容的基础上,可以进一步实现诸如按需加载,代码压缩等一系列功能,帮助我们进一步提高自动化程度,工程效率以及打包输出的质量。


接下来 我们自己手动配置一个简易的webpack文件来学习他的的原理。

例如:我创建一个了项目,比如这个项目就是用来求1-100的和,项目完成后然后我需要配置wepack来进行打包,所以我们在我的项目下新建一个wepack.config.js文件

bundle.js为自动生成,package.json为配置文件,也不用管。 所以只需要关心三个类型的三个文件,即dist,main.js,webpack.config.js,见名知意,第一个是dist文件就是打包后生成打包文件;main.js里面包含你想做的事情;剩下的webpack.config.js就是整个webpack工程的配置文件了。

2222.png

1 webpack的用法(安装 webpack 相关的两个包)

webpack@5.58.2 和 webpack-cli@4.9.0

2:配置wepback中第一个需要知道的配置 mode

在webpack的配置中,其他配置都可以没有!但是mode是必备的,如果不加mode,官方虽然会打包,但同时也会给你一个警告

mode的可选值有三种:

module.exports = {
  // 打包模式
  mode: "development",  //生产  development
  mode: "production",   //开发  production
};

1:mode 节点的可选值有三个,分别是:

① development

  • 开发环境
  • 不会对打包生成的文件进行代码压缩和性能优化
  • 打包速度快,适合在开发阶段使用
  • 保留了很多注释警告信息,main.js大一些

② production

  • 生产环境
  • 会对打包生成的文件进行代码压缩和性能优化
  • 打包速度很慢,仅适合在项目发布阶段使用
  • 去掉了注释警告信息,main.js小一些 ③ none: 不使用任何默认优化选项

配置 webpack 中的第二个需要知道(entry)与出口(output)

module.exports = {
  // 打包模式
  mode: "development", //  production:生产  development:开发

  //默认的打包入口文件为 src -> index.js 
  //默认的输出文件路径为 dist -> main.js 

  //修改入口文件
  entry:join(__dirname,'src','index.js'),
  //修改出口文件
  output:{
      path:join(__dirname,'dist'),
      filename:'js/bundle.js',
  },

配置 webpack 中的第三个需要知道的 loader

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

  • 在实际开发过程中,webpack 默认只能打包处理以 .js 后缀名结尾的模块。其他非 .js 后缀名结尾的模块, webpack 默认处理不了

  • 需要调用 loader 加载器才可以正常打包非 js 文件,否则会报错!,总结:loader 加载器的作用是协助 webpack 打包处理特定的文件模块。比如: 1.jpeg

  • css-loader 可以打包处理 .css 相关的文件
    
  • less-loader 可以打包处理 .less 相关的文件
    
  • babel-loader 可以打包处理 webpack 无法处理的高级 JS 语法
    
module.exports = {
  // 打包模式
  mode: "development", //  production:生产  development:开发

  //默认的打包入口文件为 src -> index.js 
  //默认的输出文件路径为 dist -> main.js 

  //修改入口文件
  entry:join(__dirname,'src','index.js'),
  //修改出口文件
  output:{
      path:join(__dirname,'dist'),
      filename:'js/bundle.js',
  },
  module: {
    rules: [
      // 处理css文件
      { test: /\.css$/, use: ['style-loader', 'css-loader'] },
      // 处理less文件
      { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] },
      // 处理图片文件
      { test: /\.(jpg|png|gif)$/, use: 'url-loader?limit=6055&outputPath=images' },
      // .js 文件使用 babel-loader去处理。但是不要处理 node_moduels文件夹中的第三方模块
      { test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ }
    ]
  },

其中,test 表示匹配的文件类型, use 表示对应要调用的 loader

注意:

  • use 数组中指定的 loader 顺序是固定的
  • 多个 loader 的调用顺序是:从后往前调用

webpack 中的默认约定

在 webpack 4.x 和 5.x 的版本中,有如下的默认约定:

① 默认的打包入口文件为 src -> index.js

② 默认的输出文件路径为 dist -> main.js

注意:可以在 webpack.config.js 中修改打包的默认约定

配置 webpack 中的第四个需要知道的 Plugin

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

配置 webpack 中第五个需要知道的 Plugin插件

最常用的 webpack 插件有如下3个:

npm i clean-webpack-plugin@4.0.0

1:clean-webpack-plugin

  • 每次打包时,自动清理 dist 目录
// 1. 配置自动清理插件(在打包的时候,插件就会自动清理 dist 中没用的文件)
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const cleanPlugin = new CleanWebpackPlugin();

npm i webpack-dev-server@4.3.1

2:webpack-dev-server

  • 类似于 node.js 阶段用到的 nodemon 工具
  • 每当修改了源代码,webpack 会自动进行项目的打包和构建
// 自动打包插件配置
  devServer: {
    port: 8080, // 实时打包所用的端口号
    open: true // 初次打包完成后,自动打开浏览器
  },

npm i html-webpack-plugin@5.3.2

3:html-webpack-plugin

  • webpack 中的 HTML 插件
  • 可以通过此插件自定制 index.html 页面的内容
/ 2.自动帮你在index.html中引入生成的bundle.js文件的插件
const HtmlPlugin = require('html-webpack-plugin');
const htmlPlugin = new HtmlPlugin({
  template: join(__dirname, 'public', 'index.html'), // public中,你的html叫什么
  filename: 'index.html' // 打包后的html叫什么(这个文件会生成到dist文件夹),等下用生产环境来打包才知道这是什么意思

扩展:还有一些常用插件,可以了解一下

  • webpack-dashboard:可以更友好的展示相关打包信息。

  • webpack-merge:提取公共配置,减少重复配置代码

  • speed-measure-webpack-plugin:简称 SMP,分析出 Webpack 打包过程中 Loader 和 Plugin 的耗时,有助于找到构建过程中的性能瓶颈。

  • size-plugin:监控资源体积变化,尽早发现问题

  • HotModuleReplacementPlugin:模块热替换

配置 webpack 中的第六个需要知道的 Source Map

Source Map 是将编译、打包、压缩后的代码映射回源代码的过程。打包压缩后的代码不具备良好的可读性,想要调试源码就需要 soucre map。

或者说Source Map是一个信息文件,里面储存着位置信息。 也就是说,Source Map 文件中存储着压缩混淆后的代码,所对应的转换前的位置。 有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码,能够极大的方便后期的调试。不同的环境中使用,有不同的效果。

开发环境===配置后程序员可以准确的排错

module.exports = {
  // eval-source-map 仅限在开发模式中使用
  //(开发中,程序员需要排错,需要准确的定位错误行号)
  devtool: 'eval-source-map',
}

生产环境===配置别人不能看到我们的源码

module.exports = {
  // nosources-source-map 适合生产环境。
  //(生产环境,我们不希望别人看到我们的源码,这个配置项只显示行号,但不会显示源码)
  devtool: 'nosources-source-map',
}

最后我就成功配置好了一个webpack文件

//这个配置文件发生了改变,那就一定要重新打一下包.

const {join} = require('path');


// 1. 配置自动清理插件(在打包的时候,插件就会自动清理 dist 中没用的文件)
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const cleanPlugin = new CleanWebpackPlugin();

// 2.自动帮你在index.html中引入生成的bundle.js文件的插件
const HtmlPlugin = require('html-webpack-plugin');
const htmlPlugin = new HtmlPlugin({
  template: join(__dirname, 'public', 'index.html'), // public中,你的html叫什么
  filename: 'index.html' // 打包后的html叫什么(这个文件会生成到dist文件夹),等下用生产环境来打包才知道这是什么意思
});

module.exports = {
  // 打包模式
  mode: "development", //  production:生产  development:开发

  //默认的打包入口文件为 src -> index.js 
  //默认的输出文件路径为 dist -> main.js 

  //修改入口文件
  entry:join(__dirname,'src','index.js'),
  //修改出口文件
  output:{
      path:join(__dirname,'dist'),
      filename:'js/bundle.js',
  },
  // 插件配置
  plugins: [ cleanPlugin ,htmlPlugin ],
  // 自动打包插件配置
  devServer: {
    port: 8080, // 实时打包所用的端口号
    open: true // 初次打包完成后,自动打开浏览器
  },
  //
  module: {
    rules: [
      // 处理css文件
      { test: /\.css$/, use: ['style-loader', 'css-loader'] },
      // 处理less文件
      { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] },
      // 处理图片文件
      { test: /\.(jpg|png|gif)$/, use: 'url-loader?limit=6055&outputPath=images' },
      // .js 文件使用 babel-loader去处理。但是不要处理 node_moduels文件夹中的第三方模块
      { test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ }
    ]
  },

  // eval-source-map 仅限在开发模式中使用
  //(开发中,程序员需要排错,需要准确的定位错误行号)
  // devtool: 'eval-source-map',
  
  // nosources-source-map 适合生产环境。
  //(生产环境,我们不希望别人看到我们的源码,这个配置项只显示行号,但不会显示源码)
  devtool: 'nosources-source-map',

};

配置 webpack 中的第七个需要知道的的热更新原理

只用于开发中,所以配置在webpack.dev.js

// webpack.dev.js

//引入webpack
const webpack = require('webpack');
//使用webpack提供的热更新插件
   plugins: [
   new webpack.HotModuleReplacementPlugin()
    ],
    //最后需要在我们的devserver中配置
     devServer: {
+     hot: true
    },

他的原理:Webpack 的热更新又称热替换(Hot Module Replacement),缩写为 HMR。 这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。

HMR的核心就是客户端从服务端拉去更新后的文件,准确的说是 chunk diff (chunk 需要更新的部分),实际上 WDS 与浏览器之间维护了一个 Websocket,当本地资源发生变化时,WDS 会向浏览器推送更新,并带上构建时的 hash,让客户端与上一次资源进行对比。客户端对比出差异后会向 WDS 发起 Ajax 请求来获取更改内容(文件列表、hash),这样客户端就可以再借助这些信息继续向 WDS 发起 jsonp 请求获取该chunk的增量更新。

后续的部分(拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?)由 HotModulePlugin 来完成,提供了相关 API 以供开发者针对自身场景进行处理,像react-hot-loadervue-loader 都是借助这些 API 实现 HMR。

webpack 第八个需要知道的按需加载?什么是神奇注释

1、按需加载

在 Webpack 中,import 不仅仅是 ES6 module 的模块导入方式,还是一个类似 require 的函数,我们可以通过 import('module')的方式引入一个模块,import()返回的是一个 Promise 对象;使用 import()方式就可以实现 Webpack 的按需加载

2、神奇注释

在 import()里可以添加一些注释,如定义该 chunk 的名称,要过滤的文件,指定引 入的文件等等,这类带有特殊功能的注释被称为神器注释

webpack 第九个需要知道的hash、chunkhash 和 contenthash 的区别,怎么利用他们来实现长缓存

webpack.base.js中配置

// webpack.base.js

  output: {
    path: path.resolve(__dirname, '../dist'),
    // 给js文件加上 contenthash
    filename: 'js/chunk-[contenthash].js',
    clean: true,
  },

原理:浏览器是有缓存机制的.如果一个文件的文件名不发生变化,那么浏览器就会有很大的可能去缓存这个文件(cache-control:no-cache除外.)

有时候,浏览器的缓存也是一把双刃剑.比如我们的某个css文件的内容实际上是改了,但是由于文件名没改. 浏览器就认为这个文件没有发生变动,不去再次发生请求从服务器获取,而是从本地缓存里读取.会导致用户看到的文件并不是最新的.

  • hash [范围最大] 是针对整个项目的,如果把整个项目当做是一个文件(为什么非要是单个的1.txt就这么好理解成是文件了?),那么这个项目文件的内容发生改变(文件删除添加,文件内容修改),都会导致整个项目的hash值发生改变.

  • chunkhash [范围其次] 是根据当前入口文件最终打包出来的js文件.output. 当前依赖链中,有任意文件变动,都会改变这个hash值.

  • 而contenthash [范围最小] 就仅仅只是针对当前文件的内容. hash,chunkhash,contenthash,说白了,就是根据不同的范围,给最终生成的文件名里加一串字符串.(hash把整个项目当成一个文件;chunkhash把一个独立的entry个output当前一个文件(多个模块之间的依赖当成一个chunk);contenthash则是把单个文件当成是一个文件)

它们有一个共同的特点就是,最终它们都会是生成一个独立的文件,且在浏览器中,会有一个链接指向它们.

有链接指向它们,它们就能利用所谓的各种hash,来做缓存了.

Webpack第十个需要知道的如何提高webpack的构建速度

在多入口情况下,使用 CommonsChunkPlugin 来提取公共代码

1、通过 externals 配置来提取常用库

2、利用 DllPlugin 和 DllReferencePlugin 预编译资源模块 通过 DllPlugin 来对那些我们

引用但是绝对不会修改的 npm 包来进行预编译,再通过 DllReferencePlugin 将预编译的模块加

载进来。

3、使用 Happypack 实现多线程加速编译

4、使用 Webpack-uglify-parallel 来提升 uglifyPlugin 的压缩速度。 原理上 Webpack

uglify-parallel 采用了多核并行压缩来提升压缩速度

5、使用 Tree-shaking 和 Scope Hoisting 来剔除多余代码

总结: Webpack构建流程

2.jpeg