webpack打包

模块化工具的由来

  • ES Modules存在环境兼容问题
  • 模块文件过多,网络请求频繁
  • 所有的前端资源都需要模块化

在这里插入图片描述

yarn init -y
yarn add webpack-cli --dev
yarn webpack // 在4.0以后支持0配置打包;`scr/index.js`->`dist/main.js`

配置文件

新建webpack.cinfig.js的配置文件

const path = require('path')
module.exprots = {
	entry:'./src/main.js', // 入口文件
	output:{
		filename: 'bundle.js', // 生成文件名
		path:path.join(__dirname,'output') // 生成文件路径(必须是绝对路径)
	}
}

工作模式

可以理解为针对不同的环境的几组预设的配置,简化了配置模式的复杂程度
// mode 在webpack5中不用写

yarn webpack --mode production // 默认,优化打包结果(压缩)
yarn webpack --mode development // 开发模式,优化打包速度(添加调试时的辅助)
yarn webpack --mode none // 原始打包,不做任何额外处理

在配置文件中添加mode:'development'属性就会根据配置当中的模式去工作了

原理

依赖关系树(递归)
在这里插入图片描述

生成代码是一个立即执行函数,这个函数是webpack的工作入口,他接收一个modules的参数,调用时传入一个数组(该数组中的每个元素都是一个参数列表相同的函数,这里的函数就对应着我们源代码中的模块,我们的每一个模块都会被包裹到这样的一个函数当中,从而实现模块的私有作用域)
在这里插入图片描述

在这里插入图片描述

模块id就是模块数组中的元素下标(这里才开始加载我们在源代码中的入口模块)在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

资源模块加载(loader)

webpack在所有模块打包之前,将模块根据配置交给不同的loader去处理,最后将处理的结果打包到一起

相当于工厂里的生产车间,处理和加工打包中的资源文件

  • 编译转换类
  • 文件操作类
  • 代码检查类

通过不同的Loader就可以加载任何类型的资源

yarn add css-loader style-loader --dev

作用就是将css文件转换为一个js模块;创建style标签追加到页面上

module:{
	rules:[
		{
			test:/.css$/, // 正则匹配
			use: [ // 执行顺序是从后往前
				'style-loader'
				'css-loader'
			]
		}
	]
}

导入资源模块

根据代码的需要动态导入资源;需要资源的不是应用,而是代码

js驱动整个前端应用
逻辑合理,js确实需要这些资源文件
确保上线资源不缺失,都是必要的

import './heading.css'
export default () => {
	const element = document.createElement('h2')
	element.textContent = 'Hello world'
	element.classList.add('heading')
	element.addEventListener('click',()=>{
		alert('Hello world')
	})
	return element
}

文件资源加载器

大多数的加载器都是类似于css-loader,将css模板转换为js代码的方式去工作。还有些文件,例如图片和字体,这些文件是没有办法去用js文件的方式来表示的,这类文件需要用到file-loader

yarn add file-loader --dev
{ // webpack-dev-server使用的是内存中的打包文件,并不是webpack命令打包后的路径;
 // webpack-dev-server在不设置publicPath的情况下,将默认输出bundle.js到根目录
	publicPath:'dist/' // 该配置能帮助你为项目中的所有资源指定一个基础路径(虚拟打包路径,就是说文件夹不会真正生产,而且在8080端口虚拟生成)
},
{
	test:'/.png$/',
	use:'file-loader'
}

处理ES6

因为模块打包需要,所以处理import和export

  • webpack只是打包工具
  • 加载器可以用来编译转换代码
yarn add babel-loader @babel/core @babel/preset-env --dev
{
	test:/.js$/,
	use:{
		loader:'babel-loader', // 是一个平台,需要通过不同的插件来转换
		options:{
			presets:['@babel/preset-env'] // 插件的集合
		}
	}
}

模块加载方式

  • EsModules
  • CommonJs

require函数载入EsModules,对于默认导出。引入时需要用到default

const createHeading = require('./heading.js').default

Loader加载的非javaScript也会触发资源加载,例如css-lodaer加载的css文件样式代码中的@import指令和url函数,html代码中图片标签的src属性

// css文件中
@import url(reset.css);
{background-image: url(background.png);}
// js文件中
import footerHtml from './footer.html'
document.write(footerHtml )
{
	test:/.png$/,
	use:{
		loader: 'url-loader',
		options:{
			limit: 10*1024 // 10kb
		}
	}
},
{
	test:/.html$/,
	use:{
		loader: 'html-loader',
		options:{
			attrs: ['img:src','a:href']
		}
	}
}

开发一个自己的loader

在这里插入图片描述

// markdown-loader.js
const marked = require('marked')
const html = marked(source)
// return `module.exports = ${JOSN.stringfiy(html)}` // return的必须是js代码
return html
module:{
	rules:[
		{
			test:/.md$/,
			use:[
				'html-loader',
				'./markdown-loader'
			]
		}
	]
}

插件机制

plugin解决其他自动化工作

  1. 清除dist目录
yarn add clean-webpack-plugin --dev
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
plugins:[
	new CleanWebpackPlugin()
]
  1. 自动生成使用bundle.js的HTML
yarn add html-webpack-plugin --dev
const HtmlWebpackPlugin = require('html-webpack-plugin')
plugins:[
	new HtmlWebpackPlugin({
		title:'Webpack Plugin Sample',
		meta:{
			viewport: 'width=device-width'
		}, // 原数据标签
		template:'./src/index.html' // 使用模板
	}), // 自定义配置选项
	// 同时输出多个页面文件
	new HtmlWebpackPlugin({
		filename: 'about.html'
	})
]

// 在src下新建index.html模板页面

<h1><%= htmlWebpackPlugin.options.title %></h1>
  1. 复制静态文件至输出目录
yarn add copy-webpack-plugin --dev
const CopyWebpackPlugin = require('copy-webpack-plugin')
// 开发阶段最好不要使用这个插件
plugins:[
	new CopyWebpackPlugin([
	// 'public/**'
	'public'
	])
]

开发一个自己的插件

plugin通过钩子机制实现
要求是一个函数或者是一个包含apply方法的对象

// 清除.js注释
class MyPlugin {
	apply(compiler){ // 核心对象,compiler包含所有的构建配置信息
		console.log('MyPlugin启动时自动调用')
		compiler.hooks.emit.tap('MyPlugin', compilation => { // 插件的名称,挂载的函数
		 // compilation => 可以理解为此次打包的上下文
		 for (const name in compiler.assets){ // compiler.assets写入资源信息
			// conole.log(name) // 文件名
			console.log(compilation.assets[name].source()) // 拿到内容
			if(name.endsWith('.js')){
				const contents = compilation.assets[name].source()
				const withoutComments = contents.replace(/\/\*\*+\*\//g,'')
				compilation.assets[name] = {
					source:() => withoutComments, // 返回新的内容
					size:() => withoutComments.length // webpack内部要求的方法,大小
					}
				}
			}
		}) // .tap方法去注册一个函数
	})
	}
}
plugins:[
	new MyPlugin()
]

webpack增强开发体验

自动编译

yarn webpack --watch
serve dist

自动刷新

browser-sync dist --files "**/*"

Dev Server

他提供了一个http Server, 集成了 [自动编译] 和 [自动刷新] 等功能

yarn add webpack-dev-server --dev
yarn webpack-dev-server --open

默认只会serve打包输出文件,只要是webpack输出的文件都可以直接被访问到,其他静态资源文件也需要serve

在最新版的contentBase使用报错问题解决方案

devServer: {
	contentBase: './public' // 额外为开发服务器指定查找资源目录
}

代理api
在这里插入图片描述

devServer: {
	poxy:{
		'/api':{
		// http://localhost:8080/api/users -> https://api.github.com/api/users
			target: 'https://api.github.com',
			// http://localhost:8080/api/users -> https://api.github.com/users
			pathRewrite:{ // 代理路径规则的重写
				'^/api':''	
			},
			// 不能使用localhost:8080 (浏览器请求)作为请求github的主机名,以代理主机名去请求
			changeOrigin: true
		}
	}
}

resolve(解析)

resolve 用于设置模块如何解析,常用配置如下:

alias:配置别名,简化模块引入;
extensions:在引入模块时可不带后缀;
symlinks:用于配置 npm link是否生效,禁用可提升编译速度。

module.exports = {
    resolve: {
        extensions: ['.js', '.jsx', '.ts', '.tsx', '.json', '.d.ts'],
        alias: {
          '@': paths.appSrc,
        },
        symlinks: false,
      }
}

Source Map

源代码地图
// 因开发环境与生成环境的代码不一致,需要调试代码

// # SourceMappingURL = jquery-3.4.1.min.map

加上这行注释,请求Source Map文件根据Source Map文件映射的关系,逆向解析出源代码找到问题所在

devtool: 'none'  // eval-cheap-module-source-map

首次打包速度慢无所谓,因为有dev server重写打包速度快

HMR

模块热替换(热拔插)

热替换只将修改的模块实时替换至应用中,不必完全刷新应用
我们需要手动处理js模块更新后的热替换

const webpack = require('webpack')
devServer: {
	hot: true // hotOnly 如果出现问题都不会自动刷新
}
plugins:[
new webpack.HotModuleReplacementPlugin()
]

在打包的入口文件中

module.hot.accept('./editor',() => { // 注册
	console.log('editor 模块更新了')
})

处理图片模块热替换

module.hot.accept('./better.png',() => {
	img.src = background
	console.log(background)
})

不同的环境创建不同的配置

  1. 配置文件根据环境不同导出不同配置(中小项目)
module.exports = (env, argv) => { // 环境名,运行cli的所有参数
	const config = {}, // 公用配置
	if(env === 'production'){
		config.mode = 'production'
		config.devtool = false
		config.plugins = [
			...config.plugins,
			new CleanWebpackPlugin(),
			new CopyWebpackPlugin(['public'])
		]
	}
	return config
}
yarn webpack --env production
  1. 一个环境对应一个配置文件(大型项目)

创建 webpack.common.js
webpack.dev.js
webpack.prod.js

yarn add webpack-merge --dev
const common = require('./webpack.common')
const merge = require('webpack-merge') // 合并配置
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = merge(common, {
	mode:'production',
	plugins: [
		new CleanWebpackPlugin(),
		new CopyWebpackPlugin(['public'])
	]
})

DefinePlugin

在webpack4下的production模式中,自动开启了一些很通用的优化功能, 开箱即用,但是忽略了需要了解的东西

为代码注入全局成员

process.env.NODE_ENV // production模式中默认注入

如何设置process.env.NODE_ENV

const webpack = require('webpack')
plugins:[
	new webpack.DefinePlugin({
		API_BASE_URL: JSON.stringify('https://api.example.com') 
	})
]

optimization(优化)

optimization 用于自定义 webpack 的内置优化配置,一般用于生产模式提升性能,常用配置项如下:

minimize:是否需要压缩 bundle;
minimizer:配置压缩工具,如
TerserPlugin、OptimizeCSSAssetsPlugin; splitChunks:拆分 bundle;
runtimeChunk:是否需要将所有生成 chunk 之间共享的运行时文件拆分出来。

module.exports = {
  optimization: {
    minimizer: [
      // 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
      // `...`,
      new CssMinimizerPlugin(),
    ],
    splitChunks: {
      // include all types of chunks
      chunks: 'all',
      // 重复打包问题
      cacheGroups:{
        vendors:{ //node_modules里的代码
          test: /[\\/]node_modules[\\/]/,
          chunks: "all",
          name: 'vendors', //chunks name
          priority: 10, //优先级
          enforce: true 
        }
      }
    },
  },
}

Tree-shaking在这里插入图片描述

未引用代码 (dead-code) 移除掉 ,production模式自动开启
在这里插入图片描述

optimization: {
	usedExports: true, // (标记树叶)只导出外部使用了的成员
	minimize: true // (摇掉未引用的代码) 压缩
}

Tree-shaking前提是ES Modules,就是说交给webpack打包的代码必须使用ESM;但是在babel转换中可能将es转com(但是在最新的babel-loader中,自动关闭了es转换com的插件)

options:{ presets: [ [ '@babel/preset-env', { modules: 'commonjs' } ] ] } // 强制开启es转换com的插件,可设置为false

Scope Hoisting

尽可能的将所有模块合并输出到一个函数中(在这之前是一个模块对应一个函数)
在这里插入图片描述

即提升了运行效率,又减少了代码的体积

optimization:{
	concatenateModules: true
}

sideEffects

在webpack4中新增的新特性,通过配置的方式去标识代码是否有副作用,为Tree-shaking提供更大的压缩空间

副作用:模块执行时除了导出成员之外所作的事情
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

打包后,所有组件模块都被打包(这里就需要用到sideEffects)

optimization:{
	sideEffects: true, // production模式自动开启
}

在package.json中

"sideEffects: false" // 标识package.json影响的项目当中的所有代码都没有没有副作用,一旦没有副作用就会被移除掉

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注意:确保你的代码真的没有副作用

"sideEffects" :  [
	"./src/extend.js",
	"*.css"
]

Code Splitting

代码分包/代码分割

因为所有的代码最终都被打包到一起,体积会特别大 并不是每个模块在启动时都是必要的。分包,按需加载

多入口打包

使用于多页面应用
一个页面对应一个打包入口,公共部分单独提取

const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
	mode:'none',
	entry:{ // 添加2个不同的打包入口
		index: './src/index.js',
		album: './src/album.js'
	}
	output:{ // 输出2个不同的js文件
		filename: '[name].bundle.js' // 入口文件名字
	},
	plugins:[
		new CleanWebpackPlugin(),
		new HtmlWebpackPlugin({
			title: 'Multi Entry',
			template: './src/index.html',
			filename: 'index.html',
			template: '['index']',
		}),
		new HtmlWebpackPlugin({
			title: 'Multi Entry',
			template: './src/album.html',
			filename: 'album.html',
			template: '['album']',
		})
	]
}

提取公共模块

optimization: {
	splitChunks: {
		chunks: 'all' // 所有的公共模板提取
	}
}

动态导入

魔法注释

动态导入名称是随机生成的序号,我们想让每个模块的名字对应到文件名上就需要用魔法注释

import(/*webpackChunkName: 'posts' */'./posts/posts').then() // 如果2个模板名称一致,则打包到一起

MinCssExtractPlug

提取css到单个文件(css的按需加载,文件小于150kb没必要这样,因为会多一次请求)

yarn add mini-css-extract-plugin --dev
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module:{
	rules:[
		{
			use:[
				MiniCssExtractPlugin.loader, // link标签的形式注入
				'css-loader'
			]
		}
	]
}
plugins:[
	new MiniCssExtractPlugin({
		filename: '[name].bundle.css'
	})
]

OptimizeCssAssetsWebpackPlugin

压缩输出css文件

yarn add optimize-css-assets-webpack-plugin --dev
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
plugins:[
	new OptimizeCssAssetsWebpackPlugin() // 任何环境下都会工作
]
// 应该在生产环境下配置,但是有个问题他会覆盖掉js压缩器,
yarn webpack --mode production
optimization:{
	minimizer:[ 
		new OptimizeCssAssetsWebpackPlugin()
		new TerserWebpackPlugin() 
 ]
}
yarn add terset-webpack-plugin --dev // 下载js压缩插件
const TerserWebpackPlugin = require('terset-webpack-plugin')

Hash

生产模式下,文件名使用Hash

filename: '[name]-[hash].bundle.css' // 当文件修改时,打包hash会发生变化
filename: '[name]-[chunkhash].bundle.css' // 同步打包,有效的解决了文件缓存的文件,精确的找到了文件位置
filename: '[name]-[contenthash].bundle.css' // 不同文件有不同文件的hash名 
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值