Webpack知识整理———插件&提高开发体验

Webpack知识整理———插件

  • 概述
    • 基本使用
    • Webpack 常用插件
      • 自动清除输出目录
      • 自动生成引用打包结果的HTML
      • 把静态资源目录拷贝到打包后的目录中
      • 插件工作原理
    • 提高开发体验
      • 自动编译
        • webpack-dev-server
        • webpack5之后 webpack-dev-server 执行cli 修改为 webpack serve
        • 使用contentBase 指定静态资源目录
        • 利用devServer 在开发环境下启用代理
        • 使用Source Map来调试代码
          • source map 基本使用
          • 选择不同的模式
        • 自动刷新的问题
          • 在webpack5 中,dev-server 可能不会出现自动刷新的问题,在配置中添加target:"web"属性
        • 开启HMR ( 热更新 )
      • 生产环境优化
        • 1. 配置文件根据环境不同导出不同配置
        • 2. 一个环境对应一个配置文件
      • Webpack DefinePlugin
      • Tree Sharking
      • webpack 合并模块
      • sideEffects
      • 代码分割
        • 多入口打包
        • 动态导入
        • webpack MiniCssExtractPlugin
        • optimize-css-assets-webpack-plugin

概述

在开发中为了增强webpack自动化的能力,我们需要安装一些插件来辅助开发。

Loader 是为了解决资源加载的问题,而Plugin是为了解决其他自动化的问题。

基本使用

在webpack.config.js 文件中添加 plugins属性 该属性是一个数组,在这个数组填入插件的实例即可。

	plugins: [
		插件1,
		插件2,]

Webpack 常用插件

自动清除输出目录

clean-webpack-plugin 通过安装使用该插件 来实现打包时自动清除上一次打包成果的功能

	const {
      CleanWebpackPlugin } = require('clean-webpack-plugin')
	
	module.exports = {
      
		plugins:[
			new CleanWebpackPlugin ()
		]
	 }

自动生成引用打包结果的HTML

如果html文件是由我们自己手动引入打包后的资源,那么会存在硬编码问题,即,如果修改打包配置修改文件名或者文件路径,就需要我们手动再去修改html中的文件引用
我们可以借助 html-webpack-plugin 来实现自动生成html 并自动完成打包后文件的注入,这样再运行打包之后,dist目录中就会出现html文件 并且自动引入对应的js文件了

const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
      
	plugins:[
		new HtmlWebpackPlugin()
	]
 }

我们可以自定义输出的html文件内容,可以在new HtmlWebpackPlugin中添加配置对象,如果需要自定义的内容比较复杂,我们还可以使用template设置模板

	plugins:[
		new HtmlWebpackPlugin({
     
			title:'Hello World!',
			meta: {
     
				viewport: 'width=device-width'
			},
			template:'./src/index'
		})
	]

如果要输出多个页面就继续添加 多个实例即可

	plugins:[
		new HtmlWebpackPlugin({
     
			title:'Hello World!',
			meta: {
     
				viewport: 'width=device-width'
			},
			filename:'home.html',
			template:'./src/index'
		}),
		new HtmlWebpackPlugin({
     
			filename:'about.html'
		}),
	]

把静态资源目录拷贝到打包后的目录中

项目中的静态资源目录,比如 public 目录 想要在打包后也放在打包后的目录中,可以借助于这个 copy-webpack-plugin 插件来完成

const CopyWebpackPlugin = require('copy-webpack-plugin')

plugins:[
	new CopyWebpackPlugin({
      
		patterns:[ {
      from:'目标',to:'生成到哪去' } ]
	 })
]

插件工作原理

webpack的插件工作机制就是我们在软件开发中常见的钩子机制来实现。
webpack要插件必须是一个函数,或者包含apply方法的对象。

我们来实现一个去掉 /******/ 这种注释的插件

	class MyPlugin {
     
	  apply (compiler) {
     
	    console.log('MyPlugin 启动')
	
	    compiler.hooks.emit.tap('MyPlugin', compilation => {
     
	      // compilation => 可以理解为此次打包的上下文
	      for (const name in compilation.assets) {
     
	      	
	        if (name.endsWith('.js')) {
     
	          const contents = compilation.assets[name].source()
	          const withoutComments = contents.replace(/\/\*\*+\*\//g, '')
	          compilation.assets[name] = {
     
	            source: () => withoutComments,
	            size: () => withoutComments.length
	          }
	        }
	      }
	    })
	  }
	}

提高开发体验

总结一下,我们使用webpack进行项目打包的流程:
Webpack知识整理———插件&提高开发体验_第1张图片
四个步骤,依次进行,但是这种手动的方式过于原始,也会让开发的体验很差,我们就需要想办法提高开发的体验,使一些工作能够自动完成,让我们专注于代码编写上。

希望能够有以下几个方向的优化,来提高开发体验:

  1. 以 Http Server 来运行项目,而不是文件的方式打开
  2. 修改完代码保存后,能够自动编译 + 自动刷新页面
  3. 为了方便代码调试,还希望拥有 Source Map 支持 这样就可以进行断点调试

自动编译

使用watch 工作模式,监听文件变化,自动重新打包。

执行 webpack --watch 这样webpack就会开始监听文件变化了

webpack-dev-server

该工具提供一个 HTTP Server,并且这个工具集成了 「自动编译」和「自动刷新页面」 等功能

  • 启动一个HTTP Server 服务器运行项目
  • 以watch的方式运行打包,监听文件变化 自动打包编译
  • 代码更新后会自动刷新页面
  • devServer 打包生成的文件存在内存中,HTTP Server也是从内存中获取文件进行执行
  • 因为生成的文件在内存中 所有减少了 磁盘读写 提高了执行效率

运行方式: webpack-dev-server
可以在 npm script 中添加该执行 并传入cli 参数

  "scripts": {
     
    "dev": "webpack-dev-server --open --host 127.0.0.1 --port 8888"
  }

然后执行 npm run dev 即可

webpack5之后 webpack-dev-server 执行cli 修改为 webpack serve

需要执行 webpack serve 指令来启动

在package.json应做如下配置

"dev": "webpack serve"

再执行npm run start就正常启动。

使用contentBase 指定静态资源目录

我们在开发的时候 CopyWebpackPlugin 一般只在最后添加 所以为了防止dev server访问不到静态资源
为 dev-server 指定要访问的静态资源目录,在 webpack.config.js中添加

  devServer: {
     
    contentBase: './public'
  }

利用devServer 在开发环境下启用代理

前端在发起接口请求时,由于浏览器的同源问题,会出现跨域问题。我们在开发的过程中可以利用devServer配置代理api 来绕开同源策略。当项目上线之后 交给运维人员去配置反向代理

	  devServer: {
     
	    contentBase: './public',
	    proxy: {
     
	      // 匹配以 /api 开头的请求
	      '/api': {
     
	        // http://localhost:8080/api/users -> https://api.github.com/api/users
	        target: 'https://api.github.com',
	        // 重写地址,不希望其中出现/api
	        // http://localhost:8080/api/users -> https://api.github.com/users
	        pathRewrite: {
     
	          '^/api': ''
	        },
	        // 不能使用 localhost:8080 作为请求 GitHub 的主机名
	        // changeOrigin默认是false:请求头中host仍然是浏览器发送过来的host
	        // 如果设置成true:发送请求头中host会设置成target
	        changeOrigin: true
	      }
	    }
	  },

使用Source Map来调试代码

因为运行代码都是经过打包编译之后的,跟编写的不同,不方便我们调试。Source Map就解决了这个问题 ,它利用map文件 映射出原代码,方便开发人员进行调试。

webpack配置SourceMap

source map 基本使用
	devtool:"source-map"
选择不同的模式
  • 开发环境下 可以使用 cheap-module-eval-source-map 模式

注意在webpack 5 之下 该模式更名为 eval-cheap-module-source-map,
也就是要这样设置

	devtool:"eval-cheap-module-source-map"
  • 生成环境下 可以使用 none 这样不会使我的开发原代码暴露

自动刷新的问题

devServer可以在我们修改过代码后,自动刷新页面,但是这样也存在一个问题。如果页面中有一些表单元素,输入内容到一般,修改代码自动刷新就会出现输入的内容丢失。就又需要再次输入比较麻烦。
我们需要解决这个问题,这里可以使用到的就是 Hot Module Replacement (模块热更新)
HMR 指的是在程序运行过程中可以实时的替换掉某个模块,并且程序的运行状态不受影响.

在webpack5 中,dev-server 可能不会出现自动刷新的问题,在配置中添加target:"web"属性

开启HMR ( 热更新 )

热更新集成在了 webpack-dev-server 中 不需要额外安装其他模块。
可以通过cli 参数 运行 webpack serve --hot添加 --hot来开启
也可以通过配置文件开启, 这里注意 修改的样式文件要是js 文件中引入的样式文件 这样才会触发热更新
注意 : 针对于 html js 图片文件 需要手动去设置hmr的逻辑,社区还提供许多其他 loader 和示例,可以使 HMR 与各种框架和库平滑地进行交互。

  "scripts": {
     
    "serve":"webpack serve --hot --open"
  },

生产环境优化

1. 配置文件根据环境不同导出不同配置

webpack的配置文件除了支持导出一个对象,还支持导出一个函数,函数接收两个形参: env 和 argv, 在这个函数中 返回我们需要的配置对象。
env 是我们通过cli(终端指令)传递的环境名参数
argv 是指我们运行cli过程中传递的所有参数

默认约定 生产环境的env的值是 “production” 比如 webpack --env production

module.exports = (env, argv) => {
     
  const config = "具体配置对象"
  if (env === 'production') {
     
    config.mode = 'production'
    config.devtool = false
    config.plugins = [
      ...config.plugins,
      new CleanWebpackPlugin(),
      new CopyWebpackPlugin(['public'])
    ]
  }
  return config
}

2. 一个环境对应一个配置文件

  • 创建一个公共的配置文件 webpack.common.js
  • 创建开发模式下的配置文件 webpack.dev.js
  • 创建生产环境下的配置文件 webpack.pro.js
  • 在开发和生产 环境对应的文件中使用webpack-merge 来合并公共配置
	const webpack = require('webpack')
	const merge = require('webpack-merge')
	const common = require('./webpack.common')
	
	module.exports = merge(common, {
     
	  mode: 'development',
	  devtool: 'cheap-eval-module-source-map',
	  devServer: {
     
	    hot: true,
	    contentBase: 'public'
	  }
	})
  • 在scripts中配置对应的cli
  "scripts": {
     
    "build": "webpack --config webpack.prod.js",
    "serve": "webpack serve --hot"
  },

Webpack DefinePlugin

这是webpack内置的一个插件,它可以为我们的代码注入全局成员
比如 我要注入api的公共域名

	const webpack = require('webpack')
	module.exports = {
     
	  mode: 'none',
	  entry: './src/main.js',
	  output: {
     
	    filename: 'bundle.js'
	  },
	  plugins: [
	    new webpack.DefinePlugin({
     
	      // 值要求的是一个代码片段 
	      // API_BASE_URL 就可以在全局使用了
	      API_BASE_URL: JSON.stringify('https://api.example.com')
	    })
	  ]
	}

Tree Sharking

顾名思义 “摇树” 去掉枯叶,在webpack里它的作用是「摇掉」代码中未引用的部分,未引用代码( dead-code ),生产模式中,webpack会自动检测未引用的代码 然后移除掉它们。

Tree Sharking 不是指某一个配置选项,它是一组功能搭配使用过后的优化效果,在生产模式中webpack会自动启动它

如果需要在开发模式中使用,我们可以通过两个配置来实现

  • usedExports — 负责标记 「枯树叶」
  • minimize — 负责 「摇掉」它们
	optimization: {
     
		usedExports: true,
		minimize: true
	}

tree sharking 要想生效,关键点在于 由webpack 打包的代码必须使用ESM,而我们有时候使用的babel版本比较低的话 就会把ESM 转化为 commonJS 规范 这样就会导致 tree sharking 失效,但是在最新版的babel中已经不存在这个问题了

webpack 合并模块

concatenateModules 合并模块,原本webpack打包会把一个模块生产一个函数,使用了concatenateModules 可以把所有的模块都合并到一个函数中,进一步压缩体积。

sideEffects

开启了sideEffects配置后,webpack在打包时就会先检查当前代码所属的package.json中有没有sideEffects的标识,以此来判断这个模块是不是又副作用。如果这个模块没有副作用,这些没被用到的模块就不会被打包。(这个特性在production模式下会自动开启)

副作用: 模块执行时除了导出成员之外所作的事情。

sideEffects 一般用于 npm 包标记是否有副作用

比如说 一个模块中的代码 除了导出成员之外,还为Object 扩展了原型方法那么这就是副作用,我们需要标记这些有副作用的文件模块,不然它们是不会被webpack打包进来的。

使用sideEffects的前提就是确定你的代码真的没有副作用,否则的话,在webpack打包时,就会误删掉那些有副作用的代码。

代码分割

webpack 可以把模块代码打包成一个文件,这样也存在一些弊端。如果应用非常复杂,模块很多,那么最终生成的文件就会特别的大。然而,在实际情况中,并不是每个模块在启动时都是必要的。

最理想的情况是,把代码分离到多个文件中,分包,根据需要按需加载。

当然这样有人会有疑问,就是说一开始模块就是分离的,既然要分离干脆别打包啊?

理由很简单,单一文件太大这样肯定不行,而如果文件数量太多太碎,也是一样不行的。

我们可以按照不同的业务功能来对模块进行分包,实现的方式有以下两种:

  • 多入口打包
  • 动态导入

多入口打包

一般适用于传统的多页应用程序,最常见的是一个页面对应一个打包入口,公共部分单独提取。

  • entry 的值要修改成对象,每一个属性就对应一个打包入口
  • output 中filename 的 [name] 就是entry里的属性名
  • HtmlWebpackPlugin默认会在生成的html文件中引入所有打包好的js文件,这里我们需要单独引入对应的js文件 所以需要添加chunks属性 里面的值就是要引入的包名
	module.exports = {
     
	  mode: 'none',
	  entry: {
     
	    index: './src/index.js',
	    album: './src/album.js'
	  },
	  output: {
     
	    filename: '[name].bundle.js'
	  },
	  module: {
     
	    rules: [
	      {
     
	        test: /\.css$/,
	        use: [
	          'style-loader',
	          'css-loader'
	        ]
	      }
	    ]
	  },
	  plugins: [
	    new CleanWebpackPlugin(),
	    new HtmlWebpackPlugin({
     
	      title: 'Multi Entry',
	      template: './src/index.html',
	      filename: 'index.html',
	      chunks: ['index']
	    }),
	    new HtmlWebpackPlugin({
     
	      title: 'Multi Entry',
	      template: './src/album.html',
	      filename: 'album.html',
	      chunks: ['album']
	    })
	  ]
	}

公共模块提取

	  optimization: {
     
	    splitChunks: {
     
	      // 自动提取所有公共模块到单独 bundle
	      chunks: 'all'
	    }
	  },

动态导入

动态导入的模块会被自动分包。
而实现动态导入模块的方法,其实就是利用ESM中动态导入模块的写法即可。只要使用动态导入的模块webpack就会自动实现打包后生成多个分包文件,并提取公共部分

    import(./posts/posts').then(({
      default: posts }) => {
     
      
    })

而默认打包后的文件名字只是在前面添加了序号,我们可以在动态导入里添加一行魔法注释,这样生成的分包文件的名字就会使用注释中的名字了,并且添加了相同注释的动态导入会被打包到一起。
这一点在我们开发vue项目时,配置动态路由的时候尤为明显

    import(/* webpackChunkName: 'components' */'./posts/posts').then(({
      default: posts }) => {
     
      
    })

webpack MiniCssExtractPlugin

如果项目中的css文件体积过大,我们可以将css从js中抽离出来 还是以单独文件的形式引入,这就需要使用 MiniCssExtractPlugin这个插件了


	  module: {
     
	    rules: [
	      {
     
	        test: /\.css$/,
	        use: [
	          // 'style-loader', // 将样式通过 style 标签注入
	          MiniCssExtractPlugin.loader,
	          'css-loader'
	        ]
	      }
	    ]
	  }
	  

optimize-css-assets-webpack-plugin

webpack 生产模式下 只能对js文件进行压缩,如果需要对样式文件进行压缩,就需要借助于插件

  optimization: {
     
    minimizer: [
      new OptimizeCssAssetsWebpackPlugin()
    ]
  },

你可能感兴趣的:(前端工程化相关,webpack,前端,es6,node.js)