webpack的学习

webpack的学习

虽然以前接触过webpack,但是一开始webpack学习后考虑到当时很菜的我并没有什么运用的地方

因此一开始就学习的很草率,什么也没有记住,现在重新再学一次

想要使用webpacj,首先得安装webpack和webpackcli

但是不建议安装在全局可以在某个文件夹下单独npm init 建立package.json文件以后再npm安装在该目录下

npm install webpack -S

npm install webpack-cli -S

在src文件下需要新建一个index.js作为js文件的入口

被外部的index.html引入

npm run build && npx webpack

会在目录下生成一个dist文件夹,内部包含一个main.js 包含打包后的代码

webpack内置指令很多,可以通过npx webpack –help查看

自定义文件的打包路径和打包文件名 path需要引入path模块拼接形成绝对路径,否则会报错

因为打包完后的路径是dist/main.js,因此需要修改index.html的js文件路径

插件

每次手动去index.html修改dist下的路径太麻烦,因此需要插件

想要使用插件需要require引入,然后放入plugins中

HtmlWebpackPlugin

npm install -S html-webpack-plugin

new实例化 在dist文件夹下单独生成一个引入了js文件的html

如何使用原来的index.html入口

npx webpack ! 冲啊!

清理dist

每次重复生成dist内部文件,需要在每次构建前清除dist文件 再重新生成,需要使用output.clean

搭建开发环境

设置mode = ‘development’

设置source map 因为webpack打包源码时很难追踪到错误和警告所处位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry:'./src/index.js',
output:{
filename:'haoge.js',
path:path.resolve(__dirname,'./dist'),
clean:true
},
mode:'development',
devtool:'inline-source-map', //在开发模式下追踪代码
plugins:[
new HtmlWebpackPlugin({
template:'./index.html', //打包生成的文件模板
filename:'app.html', //打包生成的文件名称 默认是index.html
// 资源文件注入模板的位置
inject:'body'
})
]
}

由于每次都需要重新打包启动浏览器观看 很麻烦 可以使用 watch mode观察模式

1
npx webpack --watch

能够自动检测代码变化,不需要重新打包,但是再浏览器中想要查看最新的代码依旧需要刷新

因此需要使用webpack-dev-server

1
2
3
4
5
6
7
npm install webpack-dev-server -S
module.exports = {
devServer:{
static:'./dist' //将dist下的文件设置为web服务的目录
},
}
npx webpack serve --open

资源模块

webpack内置模块asset modules引入其他类型文件

1
2
3
4
asset/resource 发送一个单独的文件并导出URL
asset/inline 导出一个资源的data URI
asset/source 导出资源源代码
asset 在导出一个data URI 和发送一个单独的文件之间自动选择

引入资源匹配

修改src内容引入资源创建dom渲染

注:不能再npx webpack serve –open下打开 必须先npx webpack打包后 再npx webpack serve –open启动

自定义输出文件名

1
2
3
output:{
assetModuleFilename:'image/[contenthash][ext][query]'
}

会将文件导出带dist下的image目录 默认命名为image/[contenthash][ext][query]

也可将某些指定资源打包到特定目录

1
2
3
4
5
6
7
8
rules: [{
 test: /\.png/,
 type: 'asset/resource',
 // 优先级高于 assetModuleFilename
 generator: {
filename: 'images/[contenthash][ext][query]'
}
}]

inline资源行内

1
2
3
4
5
6
module:{
rules[{
test:/\.svg/,
type:'asset/inline'
}]
}

会将文件作为URL注入dom的src

自定义webpack的URL生成器

webpack自带的inline编译的url是base64,如果想自定义url,可以下载一个自定义函数来编码文件内容

npm install mini-svg-data-uri -D

source资源

1
2
3
4
5
6
7
8
9
10
mocule:{
rules:[{
test:/\.txt/,
type:'asset/source'
}]
}
// 遇到一个以前没接触过的写法
dom.style.cssText = `width:100px;height:200px;background:skyblue` // 定义样式
dom.textContent = 'cabbage' //
//最后生成一个200px宽高,内容是文本cabbage的盒子

通用资源

设置type为asset以后,webpack会自动识别,小于8k被视为inline,反之视为resource

可以在webpack中设置限制的大小

1
2
3
4
5
6
7
8
9
{
test:/\.jpg/,
type:'asset',
parser:{
dataUrlCondition:{
maxSize: 4 * 1024 // 限制4kb
}
}
}

Loader

l除开资源模块,可以使用loader引入其他类型文件

webpack只能识别js和json,loader能够让其他类型文件转换为有效模块,供应用程序使用

loader具有两个属性,test:匹配对应文件,use:定义应该使用哪种loader

加载CSS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//为了import css文件,需要安装style-loader与css-loader
npm install --save-d style-loader css-loader
{
test:/\.css$/i,
use:['style-loader','css-loader'] //顺序固定,不然可能会抛出错误
}

// 也可以支持less,sass等预处理器
// npm install less less-loader --save-dev
// 匹配less文件
{
test:/\.less$/i,
use:['style-loader','css-loader','less-loader']
}

@color:yellow;
.world {
color: @color;
}

抽离和压缩CSS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
npm install mini-css-extract-plugin --save-dev
// 该插件会将包含css的js文件创建一个css文件,并支持按需加载和sourcemap,必须要webpack5才能运行
const miniCssExtractPlugin = require('mini-css-extract-plugin')
plugins:[
new MiniCssExtractPlugin({
filename:'styles/[contenthash].css'
})
]
{
test:/\.css$/i,
use:[miniCssExtractPlugin.loader,'css-loader']
}

npx webpack //将css文件打包在dist/styles/xxxx.css
打开文件后发现样式代码并没有压缩 需要安装css-minimizer-webpack-plugin
npm install css-minimizer-webpack-plugin --sace-dev
optimization:{
minimizer:[
new CssMinimizerPlugin()
]
},
// 有一个地方不能忽视,需要将mode改为production,否则压缩会失败

加载images图像

我们可以借助资源模块将图片混入我们的系统,也可以使用css直接引用文件

1
2
3
4
5
......
backImg {
background-image: url('')
// style.css 我在玩的时候引入的背景图片大小是400多k,会直接遮罩警告,但是可以关闭,所以丝毫不慌
}

加载font

使用资源模块

1
2
3
4
5
{
test:/\.(woff|woff2|eot|ttf|otf)$/i,
type:'asset/resource'
}
// 引入阿里巴巴字体图标库......

加载数据

webpack还可以加载xml,json,csv,tsv等文件,需要使用csv-loader和xml-loader

1
2
3
4
5
6
7
8
9
10
11
npm install --save-dev csv-loader xml-loader

{
test:/\.(csv|tsv)$/i,
use:['csv-loader']
},{
test:/\.xml$/i,
use:['xml-loader']
}

// csv会被转化成数组 xml会被转化为对象

自定义JSON模块parser

通过自定义parser替代特定的webpack loader,可以将任何toml,yaml,或者json5作为JSON文件导入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
npm install toml yamljs json5 --save-dev
在webpack.config.js中配置
const toml = require('toml')
const yaml = require('yamljs')
const json5 = require('json5')

{
test: /\.toml$/i,
type: 'json',
parser: {
parse: toml.parse,
},
},{
test: /\.yaml$/i,
type: 'json',
parser: {
parse: yaml.parse,
},
},{
test: /\.json5$/i,
type: 'json',
parser: {
parse: json5.parse,
},
}

babel-loader

js文件需要编译吗?

webpack可以加载打包js文件,但是无法对js文件做出转换,会保持原样输出,可以使用babel-loader将es6转换成es5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
npm install -D babel-loader @babel/core @babel/preset-env
babel-loader : 在webpack里应用 babel 解析ES6的桥梁
@babel/core : babel核心模块
@babel/preset-env : babel预设,一组 babel 插件的集
{
   test: /\.js$/,
   exclude: /node_modules/,
   use: {
    loader: 'babel-loader',
    options: {
     presets: ['@babel/preset-env']
   }
  }
 }
 
// npx webpack
// 按照文档来说应该会报regeneratorRuntime is not defined 但是我没报 挺神奇的
// 如果报错了那么就是babel未配置正确
# 这个包中包含了regeneratorRuntime,运行时需要
npm install --save @babel/runtime
# 这个插件会在需要regeneratorRuntime的地方自动require导包,编译时需要
npm install --save-dev @babel/plugin-transform-runtime
# 更多参考这里
https://babeljs.io/docs/en/babel-plugin-transform-runtime
// 接着修改一下webpack.config.js的配置
{
   test: /\.js$/,
   exclude: /node_modules/,
   use: {
    loader: 'babel-loader',
    options: {
     presets: ['@babel/preset-env'],
     plugins: [
     [
       '@babel/plugin-transform-runtime'
     ]
    ]
   }
  }
 }

代码分离

代码分离是webpack的特性,可以将不同的代码分离到不同的bundle,可以影响加载时间

常用的代码分离方法:

1:入口起点,手动配置entry分离代码

2:防止重复,使用Entry dependencies 或者 SplitChunksPlugin 去重和分离chunk

3:动态导入,通过模块的内联函数调用来分离代码

1:入口起点

1
2
3
4
5
6
7
8
9
10
11
12
13
// 修改配置文件
entry:{
index:'./src/index.js',
another:'./src/another-module.js'
},
output:{
filename:'[name].bundle.js',
path:path.resolve(__dirname,'./dist'),
clean:true,
assetModuleFilename:'images/[contenthash][ext][query]'
},

// 在 another-bundle.js 和 index.js 中如果都引用了lodash 那么打包的体积就会增大 造成重复问题

2:防止重复

配置dependOn,可以在多个chunk共享模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 设置lodash为共享模块 会在dist下生成shared.bundle.js
entry:{
index:{
import:'./src/index.js',
dependOn:'shared'
},
another:{
import:'./src/another-module.js',
dependOn:'shared'
},
shared: 'lodash',
// index:'./src/index.js',
// another:'./src/another-module.js'
},

// 但是这种方式需要手动配置共享文件 可以利用SplitChunksPlugin插件将公共依赖模块提取到已有或者是新生成的chunk

3:动态导入

当涉及到动态拆分代码时,需要使用es的import或者webpack的遗留功能require.ensure

1
2
3
// 1:import
import './async-module' // 内部包含js代码
// 2:require.ensure

4:懒加载

在一开始不加载,在完成了某些操作之后再加载,优化响应速度

1
2
3
4
5
6
7
8
9
const btn = document.createElement('button')
btn.textContent = '点击执行加法'
btn.addEventListener('click',()=>{
import(/* webpackChunkName: 'math' */ './math.js').then(({add})=>{
console.log(add(10,30))
})
})
// webpack 魔法注释: webpackChunkName: 'math' ,告诉webpack打包生成的文件名为 math
// npx webpack打包完以后,在浏览器点击这个按钮才会引入并运行math.bundle.js

5:预获取/预加载模块

webpack4.6增加了对预获取和预加载的支持

使用prefetch和preload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// prefetch 添加该魔法注释以后 浏览器回在闲置时间自动下载该math.js文件
btn.addEventListener('click',()=>{
import(/* webpackChunkName: 'math', webpackPrefetch: true */ './math.js').then(({add})=>{
console.log(add(10,30))
})
})
// preload
preload是当下时刻,prefetch是未来的时刻
preload立即下载,prefetch是闲置时下载
preload加载方式是并行的,prefetch是主chunk结束后执行
import(/* webpackChunkName: 'print', webpackPreload: true */ './print.js').then(({print})=>{
print()
})

// prefetch 闲置加载

// preload 并行加载 是和index.bundle.js一起加载的

缓存

输出文件文件名

1
2
3
4
5
6
// 可以通过修改替换outpur中的filename的substitutions设置,定义输出名称
// webpack提供了一个substitution(可替换模板字符串的方式)
// 通过括号字符串来模板化文件名,[contenthash] ,substitution会根据资源内容创建出唯一hash,当资源内容变化时,[conetenthash]会自动发生变化
output:{
filename:'[name].[contenthash].js',
},

缓存第三方库

在webpack中比较推荐将lodash等第三方库提取到一个单独的chunk中,利用client的长效缓存机制,减少不做修改的文件的请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 在optimization.splitChunks 添加如下 cacheGroups 参数并构建
optimization:{
// minimizer:[
// new CssMinimizerPlugin()
// ],
splitChunks:{
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
}
},

将js文件放置到一个文件夹中

1
2
3
output: {
filename: 'scripts/[name].[contenthash].js',
},

拆分开发环境和生产环境配置

目前只能手动调整mode切换开发环境和生产环境,考虑到很多配置在开发与生产环境不一样,因此需要区分开发和生产环境以便更灵活的打包

公共路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 在IE情况下// publicPath 可以用来指定应用程序中所有资源的基础路径
// 基于环境配置,如果在开发环境将assets目录下的文件夹托管至CDN,那么如何使用
// environment variable(环境变量)
// webpack.config.js中
import { webpack } from 'webpack'
const ASSET_PATH = process.env.ASSET_PATH || '/'
output:{
filename:'scripts/[name].[contenthash].js',
publicPath:ASSET_PATH
},
plugins: [
  // 这可以帮助我们在代码中安全地使用环境变量
  new webpack.DefinePlugin({
   'process.env.ASSET_PATH': JSON.stringify(ASSET_PATH),
 }),
],
// Automatic publicPath
webpack 会自动根据import.meta.url 、 document.currentScript 、 script.src 或者self.location 变量设置 publicPath。我们所需要做的是将 output.publicPath设为 'auto'
// 在IE情况下,不支持document.currentScript,这时需要引入polyfill,例如currnetScript Polifill

环境变量

想要消除webpack.config.js在开发环境和生产环境之间的差异,需要环境变量environment variable

1
2
3
4
5
6
7
8
9
10
// npx webpack --env goal=local --env production --progress
// 可以通过该方式给webpack配置环境变量
// 通常module.exports 指向一个对象,想要使用env变量,那么就需要将module.exports转换成一个函数
module.exports = () => {
return {
......
mode:env.production ? 'production' : 'development'
......
}
}

拆分配置文件

目前不管是开发环境还是生产环境,使用的都是同一个配置文件,我们需要创建新的文件区分

webpack.config.dev.js

webpack.config.prod.js

1
2
3
4
5
6
// ...文件忽略 在生产环境可以配置如下信息,隐藏webpack性能提示信息
performance: {
hints:false
},
npx webpack serve -c ./config/webpack.config.dev.js // 执行开发环境配置
npx webpack serve -c ./config/webpack.config.prod.js // 执行生产环境配置

npm脚本

1
2
3
4
5
6
7
// 每次打包时都要npx xxxxxx
// 在package.json中配置命令
"scripts": {
"start": "webpack serve -c ./config/webpack.config.dev.js",
"build": "webpack -c ./config/webpack.config.prod.js"
}
// npm run build 完美!

提取公共配置

在webpack.config.dev.js 和 webpack.config.prod.js 中存在大量相同配置webpack.config.common.js

1
2
3
4
5
6
// 新建webpack.config.common.js 抽取dev和prod中相同的部分
......此处省略一万字
//改写webpack.config.dev.js
......此处省略一万字
//改写webpack.config.prod.js
......此处省略一万字

合并配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 在代码拆分完毕后 如何保证合并没有问题呢?利用webpack-merge
// npm install webpack-merge
// 创建webpack.config.js
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.config.common.js')
const productionConfig = require('./webpack.config.prod.js')
const developmentConfig = require('./webpack.config.dev')
module.exports = (env) => {
switch(true) {
case env.development:
return merge(commonConfig, developmentConfig)
case env.production:
return merge(commonConfig, productionConfig)
default:
throw new Error('No matching configuration was found!');
}
}

内卷无出路,躺平才是真……