webpack5入门

696 阅读6分钟

Webpack

五大模块
  • Entry 入口,webpack打包的入口,默认为src文件夹下的index.js
  • Output 出口,打包完成后输出的位置和命名
  • Loader 翻译官,webpack只能识别js和json文件,所以处理css,less,img等内容需要用到loader将其转换为webpack能识别的内容
  • Plugins 插件,将打包的东西按照自己的想法进行打包,比如说压缩等
  • Mode 模式,分为development开发模式和production生产模式,分别针对的是开发的时候自己打包用的和上线的时候线上优化的
小结:执行顺序为:
1. 先看mode是哪种模式
2. 然后Entry找到入口,然后找到对应的内容
3. 如果有css或其他webpack识别不了的东西就用loader将其转换
4. 然后根据plugins将其进行压缩
5. 最后到将打包好的文件进行output导出
基础配置
  • 前提:node版本10以上
  • npm init -y生成package.json
  • npm install webpack webpack-cli
  • 创建src文件夹,且创建index.js作为入口
  • 创建build文件夹,且创建index.html作为引入打包文件的地方==(因为最初如果不配置的话,打包后的文件是在build里面webpack自己默认生成个main.js需要自己手动引入到index.html中的)==
  • 运行指令 webpack ./src/index.js -o ./build/build.js --mode=development
小结:
webpack ./src/index.js -o ./build/build.js --mode=development
指令意思为
webpack 以./src/index.js作为入口
		-o 是-output的缩写,也就是出口
		以./build/build.js作为出口,默认为main.js
		打包环境为development(开发环境)
		
初始化文件目录
build
	|-build.js 打包后才有这个build.js文件
node.modules
src
	|-index.js
package-lock.json
package.json

作用:
举例: index.js里面import $ from 'jquery'
本来直接使用import浏览器会报错,但是经过webpack打包后,使用main.js就不会报错,而且可以实现我们想要的效果,但是此时还不能打包其他的,只能打包js和json,因为还没有配置

webpack配置文件,webpack.config.js

  • 作用:是webpack的自定义配置文件,可以指定webpack做哪些事情
  • 文件位置:和src同级
  • 所有的构建工具都是基于node.js运行的,所以我们在webpack.config.js里使用commonjs规范导入导出
const path =require('path');
const htmlPlugin=require('html-webpack-plugin');
module.exports={
    entry:'./src/index.js',//入口
    output:{
        filename:'bb.js',//出口名称
        path:path.resolve(__dirname,'build')//出口地址
    },
    module:{},
    plugins:[
	    //自动生成html,并且自动引入打包文件
        new htmlPlugin()
    ],
    mode:'develpoment'
}

loader 翻译官

  • lodaer配置放在module这个对象中的rules数组里面,每一个配置都为一个对象
  • 一个对象包括test和use,
    • test是匹配文件,使用正则
      • test也可以换成exclude,test表示的是处理匹配到的所有,exclude表示的是处理除了匹配到的以外所有
      • test和exclude可以同时存在
    • use是转化规则,是数组,use的执行顺序是从后到前,是有依赖关系的,里面放的是loader,如果只有一项可以写成字符串
  • enforce :true/false true为优先执行
  • options是对象,(独属于图片的)做一些配置项的,默认没有,可以不写
    module:{
        rules:[
//use从后到前面执行,有依赖关系
//针对css配置的loader
前提:npm i css-loader style-loader -D
            {test:/\.css$/,use:['style-loader','css-loader']},
//针对less配置的loader
前提 npm i style-loader less-loader css-loader -D
            {test:/\.css$/,use:['style-loader','css-loader','less-loader']},
//配置针对图片的loader
//虽然需要下载两个loader,但是配置的时候只需要配置url-loader即可,options里面的limit是大于当前8kb的话转换为base64
前提 npm i url-loader file-loader -D
            {test:/\.(jpg|png|gif)$/,loader:'url-loader',options:{
                limit:8*1024
            }},
//处理除了css、js、html以外的文件
可以用于处理icon
            {exclude:/.(css|js|html)$/,loader:'file-loader'}
        ]
    },
图片打包中的options的一些可选配置项
limit : 8*1024   图片小于8kb转为base64
name : hash[10].[ext] 压缩后的图片名字用10个字,ext是他原本的后缀名,默认压缩完的图片名很长
esModule : true/false 开启或关闭es6Module模式
outputPath : 地址 将打包好的文件输出到指定文件夹,是字符串类型的地址,相对于build根目录的
常见问题
  • url-loader和html-loader一起使用有冲突
  • 原因是url-loader默认使用es6模块解析,而html-module引入图片采用的是commonjs,es6Module在
  • 解决方法:关闭url-loader里的es6模块化,让其使用commonjs规范解析
module.exports = {
    module: {
        rules: [{
                test: '/\.(png|jpg|jpeg)$/',
                use: 'url-loader',
                options:{
                    limit:8*1024,
                    esModule:false
                }
            },
            //处理html中的img资源
            {
                test: '/\.html$/',
                use: 'html-loader'
            }
        ]
    }
}
eslint
  • 作用:语法检测
  • 检测范围:只检测自己写的代码,第三方库是不检查的
  • 下载依赖 yarn add eslint-config-airbnb-base eslint-plugin-import eslint eslint-loader -D
  • 设置检查规则:
    • package.json中eslintConfig中设置想要的语法规范
  • 打包的时候,红色的是错误,黄色的是警告
> package.json
extends后写要继承的语法规范
  "eslintConfig": {
    "extends":"airbnb-base"
  }
modules.exports={
rules:[
            {
                test:/\.js$/,
                exclude:/node_modules/,
                loader:'eslint-loader',
                options:{
                //自动修复语法规范
                    fix:true
                }
            }]
}
js兼容性处理
  • 处理js兼容
  • yarn add -D babel-loader @babel/core @babel/preset-env webpack
版本冲突
  • babel-loader8对应babel 7
  • babel-loader7对应babel6
默认只能转换基础语法,没有全部将es6转换为es5或更低版本
  • yarn add @babel/polyfill -D
  • 这个使用就不是在loader里了,直接在index.js中import即可
  • 缺陷,虽然会把所有的都处理,但是有一些我们没有用到,他也将方法一并打包,会很大
//缺陷用法,全部引入,在index.js里导入
import '@babel/polyfill';

//按需引入
首先需要将全局的注释掉
yarn add babel-loader @babel/preset-env @babel/preset-env @babel/polyfill core-js -D 
module.exports={
	module:{
	rules:[
	            {
                test: /\.js$/,
                exclude: /node-modules/,
                loader: 'babel-loader',
                options: {
                    presets: [
                        ['@babel/preset-env', {
                            //按需加载
                            useBuiltIns:'usage',
                            //指定core-js版本
                            corejs:{
                                version:3
                            },
                            //指定兼容性做到哪个版本
                            targets:{
                                chrome:'60',
                                firefox:'60',
                                ie:'9',
                                safari:'10'
                            }
                        }]
                    ]
                },
            }]}
}

plugins

  • 作用:通过一些插件处理东西,不同插件有不同的作用
  • 用法:都是通过先require导入,然后在plugins这个数组内,new的方式执行
html-webpack-plugin
  • 作用: 实现自动生成html,并且引入打包文件
    • 可选配置项,注意,如果使用配置项,new的时候就要在()内传个对象
    • template:'文件地址' 作用是以文件地址中的html为模板生成新的html
前提: npm i html-webpack-plugin -D
const htmlPlugin=require('html-wenpack-plugin');
module.exports={
	plugins:[
	        new htmlPlugin()
	    ],
//	plugins:[
//    new htmlPlugin({
//	       template:'./src/index.html'//配置项
//	       })
//	   ],
}
    
mini-css-extract-plugin插件
  • 作用:实现css的提取,单独把css提取成文件,用link标签引入
  • 好处:正常loader使用的是style-loader打包在js中然后通过js添加style标签实现,会出现白屏效果
  • 注意:特殊之处在于,不仅需要在plugins里new,还要在loader里使用名字.loader,且注意,不能与style-loader同时使用
  • new的时候可以传配置项
//style-loader是放在js中,通过js添加style标签实现样式添加
module.exports = {
    module: {
        rules: [{
            test: /\.css$/,
            use: ['style-loader', 'css-loader']
        }]
    }
}
//使用插件,把css提取出来,使用link标签导入
前提: npm install mini-css-extract-plugin -D
const miniPlugin = require('mini-css-extract-plugin');
module.exports = {
    module: {
        rules: [{
            test: /\.css$/,
            use: [miniPlugin.loader, 'css-loader']
        }]
    },
    plugins: [
        new miniPlugin({
        //将css打包到built文件下的css文件夹中的built.css里
		filename:'css/built.css'
		})
    ]
}
optimize-css-assets-webpack-plugin压缩css
  • 作用:压缩css
  • 正常下载是npm i optimize-css-assets-webpack-plugin -D,但是下载不了,npm报错,可以使用yarn add -D optimize-css-assets-webpack-plugin
const OptimePlugin=require('optimize-css-assets-webpack-plugin');
module.exports={
plugins:[new OptimePlugin()]
}
css兼容性处理
  • 作用:为了适应各个浏览器的兼容,自动给样式加前缀
  • 前提:npm install postcss postcss-loader postcss-preset-env -D
  • 他会根据package.json里面的browserslist进行兼容css处理
//老语法,高版本使用会报错
  // {
      //     loader:'postcss-loader',
      //     options:{
      //         ident:'postcss',
      //         plugins:()=>{
      //              require('postcss-preset-env')()
      //         }  
      //     }
      // }
新语法
    module: {
        rules: [{
                test: /\.css$/,
                use:[
                    miniPlugin.loader,
                    'css-loader',
                    // 'postcss-loader',
                    {
                        loader:'postcss-loader',
                        options:{
                           postcssOptions:{
                               plugins:[
                                   [
                                       "postcss-preset-env",
                                       
                                   ]
                               ]
                           }
                        }
                    }
                ],
            },
            {
                test: /\.(jpg|png|gif)$/,
                loader: 'url-loader',
                options: {
                    limit: 8 * 1024,
                    outputPath: 'image'
                }
            }
        ]
    },

dll 缓存第三方库

  • 第三方包是固定的,不需要每次编译都进行打包,这时候就可以使用dll缓存
  • 根目录下新建webpack.dll.config.js,添加配置,以jquery举例
const path = require("path");
const webpack = require("webpack");
module.exports = {
  mode: "production",
  entry: {
    jquery: ["jquery"],
  },
  output: {
    filename: "[name].js",
    path: path.resolve(__dirname, "dll"),
    library: "[name]_[hash]", //打包后保留的名字
  },
  plugins: [
    //打包生成一个manifest.json文件,提供与jquery的映射
    new webpack.DllPlugin({
      name: "[name]_[hash]", //映射的暴露的内容的名字
      path: path.resolve(__dirname, "dll/manifest.json"),
    }),
  ],
};
  • 配置执行命令,"dll" : "webpack --config webpack.dll.config.js"
  • 在webpack.config.js中使用webpack自带插件
    //webpack打包的时候就会去找manifest.json文件
   new webpack.DllReferencePlugin({
      manifest: path.resolve(__dirname, "dll/manifest.json"),
    }),
  • 执行顺序,先执行npm run dll,将缓存打包出来,后续再执行npm run build命令时,明显加快
  • 注意:因为dll文件是单独打包,所以需要手动引入

devServer 服务,实现自动化

  • 作用:我们每一次更新代码都需要npm run build,显然很麻烦,这个时候就可以使用devServer来帮我们处理
  • npm install webpack-dev-server -D
  • 启动的需要npx webpack-dev-server 或者在package.json里scripts里自己配置一下就可以用npm run 的方式
  • 需要了解的是,他并没有肉眼可见的更新文件,是自己在内存中进行更新的,而npx webpack是输出了文件,这是他们的区别
module.export={
   devServer:{
        //项目路径
        contentBase:path.resolve(__dirname,'build'),
        //启动gzip压缩
        compress:true,
        //端口号
        port:3000,
        //自动打开默认浏览器
        open:true
    }
}
遇到问题
版本信息:
    "webpack": "^5.36.0",
    "webpack-cli": "^4.6.0",
    "webpack-dev-server": "^3.11.2"

报错 Error: Cannot find module 'webpack-cli/bin/config-yargs'
原因:webpack-dev-server版本不支持wepack5和webpack4
解决方法: 在package.json里的scripts里加个 "start": "webpack serve --mode development --env development"
执行成功:npm run start 

webpack性能优化

HMR热更新

  • 场景:只要修改一个文件,webpack会将全部的都进行重新打包,消耗性能,慢
  • 作用:一个模块发生改变,只打包改变的
  • 更新范围:
    1. 样式文件可以使用hmr功能,因为style-loader内部实现了,所以开发时候我们会用style-loader
    2. js文件默认不能使用hmr功能,修改了会全部重新打包,刷新页面,在src/index.js里面配置内容,可以实现单个模块更新,示例在下方
    3. html文件,默认不可以使用hmr功能,修改后会导致页面不可以热更新,页面也不会刷新 + 解决方案:修改entry入口,改成数组,将html文件也放入,一般是用不到
//webpack.config.js
const webpack=require('webpack');
module.exports={
	//entry:'./src/index.js'
	entry:['./src/index.js','./index.html'],
	plugins: [
	//使用webpack自带热更新的插件
	    new webpack.HotModuleReplacementPlugin()
	],
	devServer:{
	//开启热更新,也就是hmr功能
	hot:true
			}
	}

//index.js
解决js更新会全部重新加载页面问题
./aa.js是要更新的js地址
if(module.hot){
    module.hot.accept('./aa.js',function(){
        print();
    })
热更新失效问题
  • 在network里面查看websocket是否加载,不加载的话就讲package.json中的 "browserslist"删除

映射 source-map

  • source-map 是一种提供源代码到构建后代码的映射技术,简单说明就是打包后的代码出错了,在指出在源代码中错误的位置,便于修改,因为打包后的代码是一个js,很难以观察
  • 使用的时候是devtool:'值'
  • 有七个值,特点不同,可以两两组合使用
    devtool 有几个值 
    外联
        source-map  提示错误信息和源代码的错误位置
        hidden-source-map  不能追踪源代码错误,只提示打包后代码错误位置
        nosources-source-map 提示错误信息和源代码文件位置,但是没有提示源代码内错误内容具体行数
        cheap-source-map 提示错误信息和源代码的错误位置,但是他是按照行来提示的,简单来说就是,如果两行代码没有换行,后面的报错,他会把前面的也标红
        cheap-module-map 提示错误信息和源代码的错误位置,module会将loader和source map加入,也就是会将第三方库的错误抛出来
    内联  
        inline-source-map 提示错误信息和源代码的错误位置
        eval-source-map 提示错误信息和源代码的错误位置
    内联和外联的区别:1.外联是在外部生成js文件,内联是在html中书写,2. 内联构建速度更快

    开发环境要求的是:速度快,调试方便
        速度(eval>inline>cheap)
            eval-cheap-souce-map
            eval-source-map
        调试友好
            souce-map
            cheap-module-souce-map
            cheap-souce-map
        可以结合出最优方案
            eval-source-map / eval-cheap-module-source-map
    生产环境:源代码要隐蔽,调试要不要方便
        内联是嵌入在html内,所以体积会变大,生产环境不用
        nosources-source-map 全部隐藏
        hidden-source-map 只隐藏源代码,会提示构建后代码错误信息
        最优方案
        source-map / cheap-module-source-map
oneOf
  • 应用场景:比如说css、less我们都配了loader,但是打包的时候他每个都要匹配一次,可是我们不用每个都loader都匹配一次,只匹配一个就好了
  • 作用:让一些loader在这个范围内只匹配一个
  • 注意:一些后缀相同,且是连带关系的不要这样用,比如说js既要走eslint还要走es6转es5,那只走一个显然有违初衷
module.exports = {
    module: {
        rules: [
            //注意,这里是加了个对象
            {
                oneOf: [
                    {
                        test: /\.css$/,
                        use: ['style-loader', 'css-loader']
                    },
                    {
                        test: /\.less$/,
                        use: ['style-loader', 'css-loader','less-loader']
                    }
                ]
            }
        ]
    }
}
三种hash
  • hash一般用于加在文件名上,阻止走缓存的
hash
  • 每次webpack打包都会生成一个唯一的hash,可以理解为这次打包行动的编号
  • 问题:用这个的话,一旦从新打包,所有缓存都失效,因为hash变了,意味着使用hash的文件名也改变了,会重新请求
chunkhash
  • 根据chunk生成的hash值,如果打包来源于同一个chuank,那hash值就相同
  • 问题:js和css的hash还是相同的,因为正常项目是在js里引入css的,chunk是根据entry产生的(可能不太标准),可以理解为一个入口一个chuank,所以重新打包缓存还是会失效
chuank产生的途径
  • entry入口,
    1. 如果是数组或字符串,那只会产生一个chunk
    2. 如果是对象,那对象内几个键值对,就产生几个chunk
  • 异步加载模块
  • 代码分割
contenthash
  • 根据文件内容产生的hash,也可以理解为每个人独有一份,只有自己改变了,才会生成一个新的唯一标识

缓存

js走缓存
  • 在options内添加cacheDirectory: true
  • 避免js文件重复走babel编译,应该走的只有修改的js
  • 第一次以后才会执行缓存
    module: {
        rules: [{
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            },
            {
                test: /\.js$/,
                exclude: /node-modules/,
                loader: 'babel-loader',
                options: {
                //添加这个
                    cacheDirectory: true
                },
            }
        ]
    },
文件缓存
  • 使用hash命名
    output: {
        filename: 'bb.[contenthash].js',
        path: path.resolve(__dirname, 'build')
    },
    plugins: [
        new miniPlugin({
            filename:'css/build.[contenthash:10].css'
        })
    ],

tree shaking 树摇

  • 作用:英语直译树摇,简单来说就是我们下载的第三方库很多,但是有一些用不到,将整个项目比作一颗树的话,用到的就是绿叶,没用到的就是灰叶,那我们需要摇晃大树将灰色枝叶去掉,达到修建枝叶的效果,也就是去除无用代码
  • 前提:
    1. 必须使用es6模块化
    2. mode为production
css丢失问题
  • package.json 中配置了"sideEffects":false意思是所有代码都可以进行tree shaking
  • 那么他可能会将css等其他没有用到的文件去掉
  • 解决方法:配置"sideEffects"
//树摇的时候排除css
"sideEffects":["*.css"]

多入口/多页面

  • name代表的就是entry的key
  • 会打包成两个js
module.exports={
    entry:{
        index:'./src/js/index.js',
        login:'./src/js/login.js'
    },
    output:{
        filename:'js/[name].[contenthash:10].js',
        path:resolve(__dirname,'build')
    }
}

code split 代码分割

  • 简述:webpack打包的话,会默认将js合并,一个入口合并成一个js,代码分割可以将引入的第三方模块抽离出来,多入口时候,同一第三方库在多处导入,默认会在每个入口js合并一次,因为分属不同模块,但是代码分割后,抽离出来,就只将公有第三方模块抽离一次,然后入口js自动引入
//webpack.confing.js
module.exports = {
    //代码分割
    optimization: {
        splitChunks: {
            chunks: 'all'
        }
    },
}
  • 如果想将自己的某个js单独打包,且命名的话导入的时候就是用import ''
  • /webpackChunkName:'aa'/'中的aa是自定义名字,可以不写
import (/*webpackChunkName:'aa'*/'./aa');

懒加载和预加载

  • 懒加载就是触发条件才加载,懒加载可以将import放在异步函数里面
  • 预加载就是提前把资源加载好,等到触发条件,直接走缓存,实施方法是在异步函数里面的import中添加webpackPrefetch:true
  • 预加载与正常直接导入加载的区别,正常加载是并行加载,预加载是异步加载,可以理解为等其他的全部加载完,预加载的资源才开始加载
btn.onclick=function (){
    import (/*webpackChunkName:'test',webpackPrefetch:true*/'./test')
}

常见问题

babel提示babel-core版本不对,需要安装7却安装了6版本,而且npm i babel@7 安装不了
  • 解决方法: npm i babel-core babel-loader@7.1.5 babel-plugin-transform-runtime -D
配置命令dev为webpack-dev-server后执行报错 Cannot find module 'webpack-cli/bin/config-yargs'
  • 解决方法:将dev的命令改为 webpack server

自定义node服务,不使用devserver

  • npm i express webpack-dev-middleware
  • 配置server.js
const express = require("express");
const webpack = require("webpack");
const webpackDevMiddleware = require("webpack-dev-middleware");
const path = require("path");
const app = express();
const config = require("./build/webpack.config.js");
const compiler = webpack(config);

// 告知 express 使用 webpack-dev-middleware,
// 以及将 webpack.config.js 配置文件作为基础配置。
// app.use(express.static('public'));
// app.use(require("webpack-dev-middleware")(compiler, {
//   noInfo: true, publicPath: config.output.publicPath,
// }));

const UPLOAD_DIR = path.resolve(__dirname, "target");
app.use(
  webpackDevMiddleware(compiler, {
    publicPath: config.output.publicPath,
  })
);
//返回页面,自己定义了一下,page的返回html
app.get("/page/*", (req, res) => {
  res.sendFile(path.resolve("./dist/index.html"));
});
// /Proxy的当做接口
app.get("/Proxy/*", (req, res) => {
  res.send({
    status: 0,
    data: [{ a: 1 }, { a: 2 }],
  });
});
// 将文件 serve 到 port 3000。
app.listen(3000, function () {
  console.log("🚀3000🚀");
});
  • 在package.json中配置命令"server": "nodemon server.js"
  • 执行npm run server即可

开启热更新

  • npm i webpack-hot-middleware
  • 在server.js中添加内容
const webpackHotMiddleware = require("webpack-hot-middleware");
app.use(
  webpackHotMiddleware(compiler, {
    log: false,
    heartbeat: 2000,
  })
);
  • 修改webpack.conf.js
//entry修改为
  entry: [
    "webpack-hot-middleware/client?path=/__webpack_hmr&noInfo=true&reload=true",
    "./src/index.tsx",
  ],
//plugins添加
    new webpack.HotModuleReplacementPlugin(),
  • 在主入口文件添加内容,通常是src下的index.jsx,看自己具体情况
//通知进行热更新,不添加的话还需要手动刷新,挺麻烦
if (module.hot) {
    module.hot.accept();
}