webpack学习笔记

学习视频
webpack是前端资源构建工具,是一个静态模块打包器(构建工具就是将前端需要进行的操作整合到一起)。简而言之就是打包工具

打包过程

webpack.config.js配置文件分为5个部分

  • entry:入口文件
  • output:输出文件
  • loader:翻译
  • plugins:执行功能更强大的事,优化,压缩…
  • mode:模式

loader:webpack本身只能处理js,json资源。如果需要处理css,jpg等其他类型的资源就需要不同的loader对资源进行处理,比如css资源通过css-loader翻译为js代码等
plugin:配置一些功能更为强大的插件
mode分为2种模式:
mode:'development'//开发模式,在本地能开发运行就行
mode:'production' //生产模式,让代码优化上线运行的环境
不同模式下的终端命令打包方法:

//开发环境:
webpack ./src/index.js -o ./build/built --mode=development
//生产环境:
webpack ./src/index.js -o ./build/built --mode=production

整个打包过程就是:
从entry入口文件开始,根据入口文件中引入的其他文件形成关系依赖图(并对资源进行翻译),最后形成一个chunk代码块。
在这里插入图片描述
如:上图中入口文件index.js中引入了index.css/index.less/iconfont.js/print.js四个文件,webpack会先引入这四个文件,然后对index.css/index.less/这两个文件使用相应的loader进行翻译,而iconfont.js/print.js文件里面如果还引入了其他文件,则会继续引入相关的文件并使用loader翻译形成关系依赖图,最终形成chunk代码块输出。

其他知识点:

  1. 浏览器并不能认识ES6模块化。webpack的开发模式打包,生产模式打包都能将】将ES6模块化编译成浏览器能识别的模块化
  2. 生产环境打包 比 开发模式打包 多一个压缩代码的功能
  3. webpack不会重复打包同一个文件,只会打包一次

打包css,less文件(开发模式)

打包css文件

注:loader的加载顺序是从右到左

  module:{
    rules:[
      // 会通过script标签添加到页面上
      {
        test:/\.css$/,
        use:[
          'style-loader', //创建style标签,将js中的样式资源添加到head中生效
          'css-loader' //将css文件变成commonjs模块加载到js中,里面内容是样式字符串
        ]
      }
    ]
  },
打包less文件
  module:{
    rules:[
      // 会通过script标签添加到页面上
      {
        test:/\.css$/,
        use:[
          'style-loader', //创建style标签,将js中的样式资源添加到head中生效
          'css-loader' //将css文件变成commonjs模块加载到js中,里面内容是样式字符串
        ]
      },
      {
        test:/\.less$/,
        use:[
          'style-loader', //创建style标签,将js中的样式资源添加到head中生效
          'css-loader', //将css文件变成commonjs模块加载到js中,里面内容是样式字符串
          'less-loader'  //将less文件翻译为css文件
        ]
      }
    ]
  },
自动将输出资源引入html文件中

指定html的模板为'./src/index.html',将打包后的资源引入到html模板中,并打包输出相应的html文件。

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

plugins:[
  // 默认会创建一个空的html,自动引入打包输出的所有资源
  new HtmlWebpackPlugin({
    // 复制'./src/index.html'文件,自动引入打包输出的所有资源
    template:'./src/index.html'
  })
],

打包图片(开发模式)

打包css文件中引入的图片
  //需要下载url-loader和file-loader,因为url-loader是依赖于file-loader的
  loader:'url-loader',
  options:{
    //图片大小小于8kb,就会被base64字符串处理
    // 优点:减少请求数量,减轻服务器压力
    // 缺点:图片体积更大,文件请求速度慢 
    limit:8 * 1024,
    // 4.给图片重命名
    // [hash:10]:取图片的hash的前10位
    // [ext]取文件原来的扩展名
    name:'[hash:10].[ext]'
  }
}
打包html文件中引入的图片
  //需要下载url-loader和file-loader,因为url-loader是依赖于file-loader的
  loader:'url-loader',
  options:{
    //图片大小小于8kb,就会被base64字符串处理
    // 优点:减少请求数量,减轻服务器压力
    // 缺点:图片体积更大,文件请求速度慢 
    limit:8 * 1024,
    esModule:false,
    // 4.给图片重命名
    // [hash:10]:取图片的hash的前10位
    // [ext]取文件原来的扩展名
    name:'[hash:10].[ext]'
  }
},
{
  // 2.处理html文件的img图片(负责引入img,从而能被url-loader处理)
  test:/\.html$/,
  loader:'html-loader'
}
问题:图片不显示
[object Module]问题

url-loader默认使用es6模块化解析,而html-loader引入图片是commonjs,解析时会出现[object Module]的问题,通过esModule:false来关闭url-loader中的es6模块解析

  //需要下载url-loader和file-loader,因为url-loader是依赖于file-loader的
  loader:'url-loader',
  options:{
    //图片大小小于8kb,就会被base64字符串处理
    // 优点:减少请求数量,减轻服务器压力
    // 缺点:图片体积更大,文件请求速度慢 
    limit:8 * 1024,
    //3.存在问题:
    // 因为url-loader默认使用es6模块化解析,而html-loader引入图片是commonjs
    // 解析时会出现问题[object Module]
    // 解决:
    // 关闭url-loader中的es6模块
    esModule:false,
    // 4.给图片重命名
    // [hash:10]:取图片的hash的前10位
    // [ext]取文件原来的扩展名
    name:'[hash:10].[ext]'
  }
},
{
  // 2.处理html文件的img图片(负责引入img,从而能被url-loader处理)
  test:/\.html$/,
  loader:'html-loader'
}
webpack版本问题
  1. 查看webpack版本,我使用的是webpack5,所以webpack4的配置不生效= =
webpack -v //查看

webpack 5.55.0
webpack-cli 4.8.0
webpack-dev-server 4.3.0
  1. 解决方法1:
    任然使用webpack4的配置方法,需要添加type:'javascript/auto'实现功能
{
  test:/\.(jpg|png|gif)$/,
  use:[
    {
      loader:'url-loader',
      options:{    
        limit:8 * 1024,
        name:'[hash:10].[ext]',   
        esModule:false,    
        outputPath:'imgs'         
      },
    }
  ],
  type:'javascript/auto',
},
  1. 解决方法2:
    直接使用webpack5的配置方法
{
  test:/\.(jpg|png|gif)$/,
  type: 'asset/resource',
  generator:{ 
    filename:'img/[hash:10][ext]',
  },
},

使用devServe(开发模式)

devServe属于开发配置,修改代码不需要重新打包(只在内存中打包),界面会自动更新。

  1. webpack.config.js中添加如下配置:
  //只会在内存中编译打包,不会有任何输出
  devServer:{
    contentBase:resolve(__dirname,'build'),
    compress:true, //启动压缩
    port:3000, //指定端口号
    open:true //自动打开浏览器
  }
  1. package.json中配置指令
"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "dev": "webpack serve"
},
  1. 通过npm run dev启动

生产模式的打包

生产模式的打包需要让代码优化上线,让代码运行速度更快,解决兼容性问题等。

  1. css的代码存放在js文件中,会导致js文件特别大
  2. 对代码进行压缩

注:如果一个文件要被多个loader处理,那么一定要指定loader的执行顺序,比如对js的处理:既使用了eslint-loader,又使用了babel-loader,那么可以通过设置enforce:'pre'来规定该loader先执行。

{
    test:/\.js$/,
    exclude:/node_modules/,
    enforce:'pre', //优先执行
    loader:'eslint-loader',
    options:{
        fix:true
    }
}

css文件打包优化(生产模式)

提取css为单独文件

使用插件mini-css-extract-plugin

第一步:引入

const miniCssExtractPlugin = require('mini-css-extract-plugin')

第二步:配置

//2.1先在plugin中配置插件
plugins:[
  new miniCssExtractPlugin({
    filename:'css/built.css'
  })
],
//2.2然后使用miniCssExtractPlugin.loader取代style-loader
{
  test:/\.css$/,
  use:[
    // 'style-loader', //创建style标签,将js中的样式资源添加到head中生效
    // 使用这个loader取代style-loader,作用是提取js中的css为单独的文件
    miniCssExtractPlugin.loader,
    'css-loader' //将css文件变成commonjs模块加载到js中,里面内容是样式字符串
  ]
},
css兼容性处理

使用插件postcss-loader,postcss-preset-env
postcss会找到package.json文件中browerslist里面的配置(这个配置可以根据需求自行修改),加载配置指定的css兼容性样式.
postcss-loader
第一步:在package.json文件中配置browerslist

"browerslist":{
  //开发环境--->设置node环境变量:
  //在webpack.config.js文件中添加语句process.env.NODE_ENV = "development"
  "development":[
    "last 1 chrom version",
    "last 1 firefox version",
    "last 1 safari version"
  ],
  //生产环境:默认是看生产环境
  "production":[
    ">0.2%",
    "not dead",
    "not op_mini all"
  ]
}

第二步:在webpack.config.js中配置插件

{
  test:/\.css$/,
  use:[
    miniCssExtractPlugin.loader,
    'css-loader', 
    {
      loader:'postcss-loader',
      options:{
        postcssOptions:{
          plugins:[
            'postcss-preset-env'
          ]
        }
      }
    }
  ]
},

实现效果:
打包前css文件:

#boc{
  display:flex;
  backface-visibility: hidden;
}

打包后css文件:

#boc{
  display:flex;
  -webkit-backface-visibility: hidden;
          backface-visibility: hidden;
}
css代码压缩

使用插件css-minimizer-webpack-plugin

//1.引入
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
//2.配置
plugins:[
  new CssMinimizerPlugin()
],

js文件打包优化(生产模式)

js兼容性处理

把es6的语法转化为es5的语法。比如源文件中使用了箭头函数,打包之后箭头函数会变为正常的函数。

打包前:

var add = (x, y) => { return x + y }

打包后:

var add = function add(x, y) {\n  return x + y;\n};\n\nconsole.log(add(1, 2));
  1. 兼容配置方法1

使用插件babel-loader,@babel/core, @babel/preset-env

rules:[
  {
    test:/\.js$/,
    loader:'babel-loader',
    exclude:/node_modules/,
    options:{
      //预设,指示babel做怎么样的兼容性处理
      presets:['@babel/preset-env']
    }
  },
]

方法1存在的问题是:只能转化简单的语法,像Promise这样的语法不能转化。

  1. 兼容配置方法2

使用插件@babel/polyfill,在需要兼容的文件中引入即可,如:

import '@babel/polyfill'

const promise = new Promise((resolve) => {
  setTimeout(() => {
    console.log('定时器执行了!')
    resolve()
  }, 1000)
})

console.log(promise)

方法2存在的问题是:将所有兼容性代码全部引入,代码体积太大

  1. 兼容配置方法3

使用插件cnpm i core-js -D

{
  test:/\.js$/,
  loader:'babel-loader',
  exclude:/node_modules/,
  options:{
    //预设,指示babel做怎么样的兼容性处理
    presets:[[
      '@babel/preset-env',
      {
        useBuiltIns:'usage',
        corejs:{
          version:3
        },
        targets:{
          chrome:'60',
          firefox:'60',
          ie:'9',
          safari:'10',
          edge:'17'
        }
      }
    ]]
  }
},

压缩js和html(生产模式)

  • js

生产环境下会自动压缩js代码

  • html
//在plugins中进行配置:压缩html代码
new HtmlWebpackPlugin({
  // 复制'./src/index.html'文件,自动引入打包输出的所有资源
  template:'./src/index.html',
  minify:{
    collapseWhitespace:true,  //移除空格
    removeComments:true, //移除注释
  }
}),

优化配置

开发环境性能优化

  1. 优化打包构建速度
  2. 优化代码调试

生产环境性能优化

  1. 优化打包构建速度
  2. 优化代码性能
HMR(开发模式)

前面配置过的devServe存在的问题是:修改一个文件中的代码,其他文件都会重新执行
解决:开启HMR功能(热模块替换),这样一个模块发生变化,就只会打包这一个模块,而不是打包所有模块,极大提升打包速度。开启的方式是添加hot:true配置

devServer:{
  contentBase:resolve(__dirname,'build'),
  compress:true, //启动压缩
  port:3000, //指定端口号
  open:true, //自动打开浏览器
  hot:true //开启HMR功能
}

注:修改ebpack.configf.js中的配置,一定要重启服务

  • 样式文件可以使用HMR,因为style-loader内部实现了(即修改css文件中的内容,其他文件不会重新打包)

  • js文件默认没有HMR(即修改js文件,其他文件也会重新打包)

    • 解决:修改index.js入口文件,添加以下代码
    if(module.hot){
      //一旦module.hot为true,说明开启了HMR功能。 ----> 让HMR功能代码生效
      module.hot.accept('./print.js', function(){
        //方法会监听 print.js 文件的变化,一旦发生变化,其他默认不会重新打包构建
        //会执行后面的回调函数
      })
    }
    
  • html文件默认没有HMR,同时会导致问题:html文件没有热更新了

    • 解决:修改entry,添加html文件
    entry:['./src/js/index.js','./src/index.html'],
    

    备注:入口文件index.js是不能做HMR功能的,修改index.js文件就会重新打包其他资源。

source-map

一种提供源代码到构建后代码 映射 的技术(如果构建后代码出错了,通过映射可以追踪源代码错误)

在webpack.config.js中添加下面的语句

devtool:'source-map'

重新打包webpack,在build/js文件夹下会新增一个文件built.js.map文件。
有以下写法:
[inline-|hidden-|eval-|nosources-|cheap-]source-map

//内联:构建速度更快,不会新增文件,map文件中的内容内联在built.js文件中
devtool:'inline-source-map'
//生成外部文件
devtool:'hidden-source-map' 
//内联,每个文件都生成source-map,都在eval
devtool:'eval-source-map' 
oneOf:[]

oneof内配置loader,一个文件只会匹配一个loader

不能有两个配置处理同一类型文件,可以把处理同一类型文件的配置提到oneof外面去。

缓存 (生产模式)
babel缓存

babel对代码进行编译,比如编译了100个js文件,修改其中一个文件,只需要编译这一个文件,其他文件应该不需要编译。开启babel缓存只需要配置cacheDirectory:true

      {
        test:/\.js$/,
        loader:'babel-loader',
        exclude:/node_modules/,
        options:{
          //预设,指示babel做怎么样的兼容性处理
          presets:[[
            '@babel/preset-env', //只能做简单的兼容性处理
            {
              useBuiltIns:'usage',
              corejs:{
                version:3
              },
              targets:{
                chrome:'60',
                firefox:'60',
                ie:'9',
                safari:'10',
                edge:'17'
              }
            }
          ]],
          //开启babel缓存,第二次构建时会读取之前的缓存
          cacheDirectory:true
        }
      },
文件资源缓存

先写一个服务器来测试代码的实际运行情况,在文件根目录下新建文件夹:server.js

// 服务器代码
// 启动:
// node server.js

const express = require('express')

const app = express()

app.use(express.static('build', {maxAge: 1000 * 3600}))

app.listen(3000)

可以在浏览器中输入http://localhost:3000/进行访问,可以发现存在问题:强缓存,资源文件发生改变,页面显示不会更新

解决:使用hash来获取最新的数据,即对输出的文件进行hash命名。webpack每次打包都会生成一个此次打包的hash值,每次打包hash值都会发生变化,即文件名发生变化,所以虽然会有缓存,但是因为文件名变了,所以页面的内容会更新。

  output:{
    filename:'js/built.[hash:10].js',
    path:resolve(__dirname, 'build'), //__dirname代表当前文件的目录绝对路径
  },
    new miniCssExtractPlugin({
      filename:'css/built.[hash:10].css'
    }),

但是上面的做法由于js和css同时使用一个hash值,重新打包会导致所有缓存失效(改动一个文件,导致所有缓存失效),即改动一个文件,其他文件名也会变化,所以不会从缓存中获取数据,而是重新获取所有的文件。

  • chunkhash:根据chunk生成hash值,如果打包来源于同一个chunk,那么hash值就一样。但是css是在js中被引进来的,所以同属于一个chunk,不能解决上面的问题。
    -contenthash:根据文件的内容生成hash值,不同文件的hash值不一样。即通过维护hash值的变化,控制缓存是否失效。
  output:{
    filename:'js/built.[contenthash:10].js',
    path:resolve(__dirname, 'build'), //__dirname代表当前文件的目录绝对路径
  },
    new miniCssExtractPlugin({
      filename:'css/built.[contenthash:10].css'
    }),
tree-shaking

去除无用代码

前提:1.必须使用ES6模块; 2.开启production模式

好处:减少代码体积,请求速度更快

测试:

  1. 在src文件夹下新建.js
export function mul(x, y){
  return x * y
}

export function count(x, y){
  return x - y
}
  1. 在index.js中引入其中的一个方法
//tree-shaking
import {mul} from './test.js'
console.log(mul(3,4))
  1. 可以看到打包后的结果中只打包了mul方法,并没有打包count方法。即tree-shaking。

注意:

在package.json文件中配置

"sideEffects":false

代表所有代码都没有副作用,都可以进行tree-shaking,如果这样配置会导致index.js中引入的css文件失效,被tree-shaking剔除出去了

code split

配置多路口:下面这样配置index.js文件中就不需要引入test.js了,把index.js文件中test.js的内容删除。之前的写法是单入口。

  entry:{
    //每个入口都输出一个bundle
    main:'./src/js/index.js',
    test:'./src/js/test.js'
  },
  output:{
    //输出文件名也需要修改相应的配置,防止文件名重复报错
    // [name]对应entry中的文件名
    filename:'js/[name].built.[contenthash:10].js',
    path:resolve(__dirname, 'build'), //__dirname代表当前文件的目录绝对路径
  },

上面这种做法麻烦,可以通过下面的配置

  optimization:{
      splitChunks:{
        chunks:'all'
      }
  }
  1. 多入口, 单入口都会将node_modules中代码单独打包一个chunk输出
  2. 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk
    如果向让某个文件单独打包成一个chunk,可以通过js代码来写
//import动态导入语法,能将某个文件单独打包
import(/* webpackChunkName:'test' */ './test.js')
  .then(({mul, count}) => {
    console.log(mul(3, 8))
  })
  .catch(() => {
    console.log('文件加载失败')
  })
懒加载和预加载
懒加载

js文件的懒加载。触发了某些条件才加载

在index.js文件中写下面的代码

console.log('index.js文件被加载了')
import {mul} from './test.js'
document.getElementById('btn').onclick = function(){
  console.log(mul(4, 5))
}

可以看到index.js文件和test.js文件都被加载了,但是test.js文件初始应该不需要使用,点击按钮才会加载(懒加载)

修改如下:

console.log('index.js文件被加载了')
document.getElementById('btn').onclick = function(){
  //分割成了一个名为test的单独js文件,并且实现了懒加载
  import(/* webpackChunkName: 'test' */'./test').then(({mul}) => {
    console.log(mul(4, 5))
  })
}

这样初始test.js文化不会被加载,只有点击了按钮,才会加载test.js文件

预加载

设置webpackPrefetch: true

console.log('index.js文件被加载了')
document.getElementById('btn').onclick = function(){
  import(/* webpackChunkName: 'test', webpackPrefetch: true*/'./test').then(({mul}) => {
    console.log(mul(4, 5))
  })
}

刷新页面,f12查看网络
在这里插入图片描述

可以看到test文件已经被加载了,在点击按钮时,其实加载的是之前的缓存。

总结:

懒加载:当文件需要使用时才加载

预加载:会在使用前,提前加载js文件

正常加载:并行加载(同一时间加载多个文件),预加载是等其他资源加载完毕,浏览器空闲了,在偷偷加载资源。

PWA

离线也能访问,渐进式网络开发应用程序

workbox --> workbox-webpack-plugin

//1.引入
const WorkboxWebpackPlugin = require('workbox-webpack-plugin')
//2.在 plugins 中配置
    new WorkboxWebpackPlugin.GenerateSW({
      // 1.帮助servicework快速启动
      // 2.删除旧的servicework
      // 生成一个servicework配置文件
      clientsClaim:true,
      skipWaiting:true
    })
//3.在index.js文件中注册 servicework
// 注册servicework
// 处理兼容性问题
if('serviceWorker' in navigator){
  window.addEventListener('load', () => {
    navigator.serviceWorker
    .register('./service-work.js')
    .then(() => {
      console.log('sw注册成功')
    })
    .catch(() => {
      console.log('sw注册失败')
    })
  })
}

servicework必须运行在服务器上

—> node.js

—> npm i serve -g

serve -s build 启动服务器,将build目录下所有资源作为静态资源暴露出去

在这里插入图片描述
可以在应用程序的服务工作进程中查看注册的serviceworker信息,在缓存存储中查看缓存的资源。

在这里插入图片描述
此时将网络置为offline,并刷新页面,可以看到下面的资源任能被访问,是从serviceworker中获取的。

多进程打包

安装 thread-loader

      {
        test:/\.js$/,
        exclude:/node_modules/,
        use:[
          //开启多进程打包
          // 进程开启大概为600ms,进程通信也有开销
          // 只有工作消耗时间比较长,才需要多进程打包
          'thread-loader', 
          {
            loader:'babel-loader',     
            options:{
              //预设,指示babel做怎么样的兼容性处理
              presets:[[
                '@babel/preset-env', //只能做简单的兼容性处理
                {
                  useBuiltIns:'usage',
                  corejs:{
                    version:3
                  },
                  targets:{
                    chrome:'60',
                    firefox:'60',
                    ie:'9',
                    safari:'10',
                    edge:'17'
                  }
                }
              ]],
              //开启babel缓存,第二次构建时会读取之前的缓存
              cacheDirectory:true
            }
          }
        ]
      },

可以自己配置thread-loader

{
  loader:'thread-loader', 
  options:{
    workers:2
  }
},
externals
externals:{
  // 忽略库名 --- npm包名
  // 拒绝jQuery被打包进来
  jquery:'jQuery'
}

https://www.bootcdn.cn/中找jquery的cdn地址
在这里插入图片描述
添加到index.html文件中

DLL

对代码进行单独打包

// 使用dll技术,对某些库(第三方库:jquery,react, vue...)单独打包
// 当使用webpack时,默认查找的是webpack.config.js配置文件
// 现在需要运行webpack.dll.js配置文件
// 所以运行指令需要改为webpack --config webpack.dll.js

const {resolve} = require('path')
const webpack = require('webpack')

module.exports = {
  entry:{
    //要打包的库是第二个jquery
    jquery:['jquery']
  },
  output:{
    //打包生成文件的[name]为上面的jquery
    filename:'[name].js',
    path:resolve(__dirname,'dll'),
    library:'[name]_[hash]', //打包的库里面向外暴露出去的内容叫什么名字
  },
  plugins:[
    //打包生成一个manifest.json ---> 提供jquery映射
    new webpack.DllPlugin({
      name:'[name]_[hash]', //映射库的暴露的内容名称
      path:resolve(__dirname, 'dll/manifest.json') //输出文件路径
    })
  ]
}

在webpack.config.js中进行配置,不需要再打包jquery了

const webpack = require('webpack')

在plugins中添加

//告诉webpack哪些库不参与打包,同时使用时的名称也得变
new webpack.DllReferencePlugin({
  manifest:resolve(__dirname,'dll/manifest.json')
})

插件 add-asset-html-webpack-plugin

const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')

//将某个文件打包输出出去,并在html中自动引入该资源
new AddAssetHtmlWebpackPlugin({
  filepath:resolve(__dirname,'dll/jquery.js')
})

正在学习中= =,有错误的地方欢迎批评指出~谢谢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值