# 安装。不加版本号默认安装最新版本
npm install webpack webpack-cli --save-dev
#webpack4
# 打包之前需要npm init初始化
#webpack会以 ./src/main.js 为入口文件开始打包,打包输出到 ./dist/bundle.js
#整体打包环境是开发环境
webpack ./src/main.js -o ./dist/bundle.js --mode=development
#整体打包环境是开发环境
webpack ./src/main.js -o ./dist/bundle.js --mode=production
#webpack5
#默认入口文件./src/index/js 默认输出文件.dist/main.js
#没有进行配置,src中没有index.js执行打包会报错,
webpack
#可以在命令行中配置 --output-path 输出文件夹的目录,文件还是mian.js。。。需要后续去配置文件中修改
webpack --entry ./src/main.js --output-path ./dist
"scripts": {
"build": "webpack --entry ./src/main.js --output-path ./dist"
}
后续直接命令行直接执行 npm run bulid
const path=require('path')
module.exports={
entry:'./src/main.js'//入口
output:{//出口
/*
是要一个绝对路径,要动态获取路径
resolve:可以拼接
__dirname:node上下文中的全局变量,当前webpack.config.js所在的路径
*/
path:'./dist',
filename:'bundle.js' //输出的文件名
}
}
修改package.json中配置
"scripts": {
"build": "webpack"
}
后续直接命令行直接执行 npm run bulid
修改配置文件名称
"scripts": {
"build": "webpack --config lg.webpack.js"
}
//lg.webpack.js:webpack.config.js名字改了
{
test:/\.css$/,
loader:'css-loader'
}
{
test: /\.(png|svg|gif|jpe?g)$/,
use: [
{
loader: 'file-loader',
options: {
esModule: false//不转为esModule
}
}
]
}
**问题1:**webpack5中,img标签的图片,通过require(’…/img/bg.png’)拿。拿到的是一个对象,{default:xxxx}
处理方法:
**问题2:**css中的url图片,都会被自动转为require语法。require语法会默认导出一个esModule。
处理方法:
esModule:false
/**
* 01 url-loader 以base64,加载到文件中中。好处,减少请求次数。风险,如果文件很大。一次性请求的数据量就很大
* 02 file-loader 将资源拷贝至指定目录中,分开请求
* 03 url-loader内部其实也可以调用file-loader
* 04 limit
*/
{
test: /\.(png|svg|gif|jpe?g)$/,
use:[
{
loader:'url-loader',
options:{
name:'img/[name].[hash:6].[ext]',
// outputPath:'img'//输出路径,可以直接再name上添加img/
limit:70*1024//超过70kb拷贝。没有超过转base64
}
}
]
}
/**
* [ext]:扩展名
* [name]:文件名
* [hash]:文件内容
* [contentHash]:和[hash]差不多
* [hash:]
* [path]:
*/
/**
* 01 asset/resource -->file-loader (输出路径)
* 02 asset/inline --->url-loader (输出base64)
* 03 asset/source --->raw-loader
* 04 asset(parser)
*/
asset/resource 和 asset/inline
{
test: /\.(png|svg|gif|jpe?g)$/,
type:'asset/resource',//替换成'asset/inline'
generator:{
filename:'img/[name].[hash:4][ext]'
}
}
asset
{
test: /\.(png|svg|gif|jpe?g)$/,
type:'asset',
generator:{
filename:'img/[name].[hash:4][ext]'
},
parser:{//解析
dataUrlCondition:{
maxSize:70*1024
}
}
}
{
test: /\.(ttf|woff2?)$/,
type:'asset/resource',
generator:{
filename:'iconfont/[name].[hash:3][ext]'
}
}
1、全局配置,不好,不同的类型需要输出不同的文件夹,如图片和字体
output: {
assetModuleFilename:'img/[name].[hash:4][ext]'
}
2、见上generator
和loader区别
使用
const {CleanWebpackPlugin}=require('clean-webpack-plugin')//自动清除
const HtmlWebpackPlugin=require('html-webpack-plugin')//模板
const {DefinePlugin}=require('webpack')//定义常量插件
const CopyWebpackPlugin=require('copy-webpack-plugin')//拷贝插件,静态资源目录,只是拷贝过去
module.exports ={
plugins:[
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title:'学习webpack',
template:'./public/index.html'
}),
new DefinePlugin({
BASE_URL:'"./"' // ====>'"./"'==>'./' './'==>./
}),
new CopyWebpackPlugin({
patterns:[
{
from:'public',//to省略,会自己去找output配置中的path
globOptions:{//忽略不拷贝的内容
ignore:['**/index.html']//加'**/',表示从from下面查找,不写会报错
}
}
]
})
]
}
1、package.json
"browserslist":[
">1%",
"last 2 version",
"not dead"
]
2、直接新建.browserslistr文件
>1%
last 4 version
not dead
命令行测试,输出满足条件的浏览器:
npx browserslist
条件筛选含义:
default :>0.5% last 2 version ,firefox no dead
dead :废弃 24个月之内,没有官方支持,没有更新
last 2 version:最新两版本
#指定文件进行处理,处理完成后输出ret.css文件
npx postcss -o ret.css ./src/css/test.css
#--use autoprefixer 使用了autoprefixe这个插件
npx postcss --use autoprefixer -o ret.css ./src/css/test.css
**1、webpack.config.js配置module.rules中加一条 **
{
test: /\.css$/,
use: [
"style-loader",//会生成style标签,内容添加到页面中
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
//require('autoprefixer'),
//require('postcss-preset-env')//支持直接写字符串
"postcss-preset-env",
],
},
},
},
];
}
postcss-loader不仅在处理.css文件需要,处理.less、.scss都需要,所以可以把postcss配置抽取出来。
2、抽取出来postcss.config.js
webpack.config.js中
{
test: /\.css$/,
use: [
"style-loader",
"css-loader",
"postcss-loader"
]
}
postcss.config.js
module.exports={
plugins:[
'postcss-preset-env'
]
}
**问题1:**css-loader,解析的过程会,里面可能会有又嵌套css文件,这个时候已经过了postcss-loader解析,里面的内容没有经过postcss-loader处理====>importLoaders,往前执行
{
loader: 'css-loader',
options: {
importLoaders: 1
}
},
#src下所有进行处理输出到build文件中
#===>发现用了babel,但是也没有转换,要加载工具包
npx babel src --out-dir build
#--plugins使用插件
npx babel src --out-dir build --plugins=@babel/plugin-transform-arrow-functions
npx babel src --out-dir build --plugins=@babel/plugin-transform-arrow-functions,@babel/plugin-transform-block-scoping
# --presets 使用预设
npx babel src --out-dir build --presets=@babel/preset-env
{
test: /\.js$/,
use:[
{
loader:'babel-loader',
options:{
presets:[
//'@babel/plugin-transform-arrow-functions',
//'@babel/plugin-transform-block-scoping'
'@babel/preset-env'//有了预设就不用特定插件
]
/* presets:[
[
'@babel/preset-env',
{targets:'chrome 91'}
]
]*/
}
}
]
}
把配置单独拎出来
webpack.config.js
{
test: /\.js$/,
exclude: /node_modules/,//当前需要,node_modules插件包里可能也需要。如,为了不相互影响要去掉
use:['babel-loader']//把配置单独拎出来
}
babel.config.js
module.exports={
presets:['@babel/preset-env']
}
在babel.config.js中配置
module.exports={
presets:[
[
'@babel/preset-env',
{
useBuiltIns:'entry' ,
corejs:3//code-js版本是2,下载的是3,需要修改
}
]
]
}
/**
* 属性:useBuiltIns
* false:不对当前的js处理做polyfill的填充
* usage:根据用户源代码当中所使用到的新语法进行填。只管源代码。==>比较友好。
* entry:依据筛选出来的浏览器,来决定填充什么。不管源代码有没有用,浏览器需要什么就填充什么。。。需要引入核心包
* /
不需要重新打包,文件内容修改,自动编译打包,界面自动更新:
以上方式不足:
package.json
"scripts": {
"serve": "webpack serves",
}
原理: 中间件,订制,开启一个服务,webpack打包后处理的资源交给服务。之后浏览器端才能访问这个服务器
需要做的事:
/**********Server.js 开启一个服务**********/
const express =require('express')
const webpackDevMiddleware=require('webpack-dev-middleware')
const webpack =require('webpack')
const app=express()
//获取配置文件
const config=require('./lg.webpack')
const compiler=webpack(config)//可以控制webpack打包的流程。webpack包拿过来,去加载配置文件
//把打包好的交给服务器
app.use(webpackDevMiddleware(compiler))
//开启端口上的服务
app.listen(3000,()=>{
console.log('服务运行在3000端口上')
})
配置
webpack.config.js开启热加载功能
devServer:{
//hot:true,//开启热加载功能
hot: 'only',//如语法报错的地方,重新加载,而不是全部刷新
},
模块中
import "./js/title";
if (module.hot) {
//哪些模块开启热更新,多个是数组
module.hot.accept("./js/title", () => {
console.log("./js/title更新了");
});
}
webpack.config.js,vue-loader14时,可以直接使用
{
test:/\.vue$/,
use:['vue-loader']
}
vue-loader15时,需要再手动加载插件
const VueLoaderPlugin = require('vue-loader/lib/plugin')
plugins:[
new VueLoaderPlugin()
]
webpack5之前:
webpack.config.js
const path = require('path')
output: {//出口
/**path:
* 是要一个绝对路径,要动态获取路径
* resolve:可以拼接
* __dirname:node上下文中的全局变量,当前webpack.config.js所在的路径
*/
path: path.resolve(__dirname, 'dist'),//输出到哪个目录
publicPath:'/',
filename: 'js/bundle.js', //输出的文件名
},
devServer:{
hot: 'only',
port:4000,//哪个端口开启服务
open:false,//true每次自动打开浏览器
compress:true,//开启服务gzip压缩
historyApiFallback:true,//前端路由刷新页面为404。true,再刷新不会有这个问题
static: {//bug,不知道是不是用错了,没反应,
directory: path.resolve(__dirname,'asset'),
publicPath: '/lg',
watch: true,
},
// publicPath:'/lg',//webpack5不存在了,变成了static
// contentBase:path.resolve(__dirname,'asset')//webpack5不存在了
},
配置代理:
webpack.config.js
devServer:{
proxy: {
/**分析:
* 所有以 /api开头都走代理
* https://api.github.com/users
* /api/users
* http://localhost:4000/api/users
* 被转发到:https://api.github.com/api/users===>报错。没有/api/,所以要重写
*/
'/api':{
target:'https://api.github.com',
pathRewrite:{
'^/api':''
},
changeOrigin:true//修改host值
} ,
},
}
页面请求
import axios from "axios";
axios.get("/api/users").then((res) => {
console.log(res.data);
});
相对路径解析规则
mainFiles: ['index']
去补全。补全完再在根据下面文件查找方式查找。webpack.config.js
resolve: {
extensions: ['.js', '.json', '.wasm','.vue'],//配置省略后缀名
alias:{//起别名
'@':path.resolve(__dirname,'src')
}
},
起别名之后
//import Home from "./components/Home.vue"
import Home from "@/components/Home";
source-map组合写法
[inline-|hidden-|eval-]
:
[nosources]
:没有源文件,又有报错信息
[cheep-|[module-]]
:
source-map
package.json
"scripts": {
"build2": "webpack --config config/webpack.common.js --env production",
"serve2": "webpack serve --config config/webpack.common.js --env development"
},
webpack.common.js
const {merge}=require('webpack-merge')//合并插件
//导入其他的配置
const prodConfig=require('./webpack.prod')
const devConfig=require('./webpack.dev')
// 定义对象保存 base 配置信息
const commonConfig={
//配置的内容
}
module.exports = (env) => {
//env 获取环境关键字
const isProduction = env.production;
//依据当前的打包模式来合并配置
const config= isProduction? prodConfig:devConfig
const mergeConfig=merge(commonConfig,config)
return mergeConfig
};
webpack.dev.js
module.exports={
// watch:true,
mode:'development',
devtool:'source-map',
target:'web',
devServer:{
hot: 'only',
port:8000,//哪个端口开启服务
open:false,//true每次自动打开浏览器
compress:true,//开启服务gzip压缩
historyApiFallback:true,
proxy: {
'/api':{
target:'https://api.github.com',
pathRewrite:{
'^/api':''
},
changeOrigin:true//修改host值
} ,
},
}
}
webpack.prod.js
const CopyWebpackPlugin=require('copy-webpack-plugin')
const {CleanWebpackPlugin}=require('clean-webpack-plugin')
module.exports={
mode:'production',
plugins:[
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns:[
{
from:'public',
globOptions:{
ignore:['**/index.html']
}
}
]
})
]
}
paths.js
const path=require('path')
//应用所在的根
const appDir=process.cwd()
console.log('----------------------->'+appDir)
const resolveApp=(relativePath)=>{
//应用所在的根和你想找的地方进行结合
return path.resolve(appDir,relativePath)
}
module.exports=resolveApp
webpack.common.js
// const path = require("path");
const resolveApp=require('./paths');
entry: "./src/main.js",//反而没有报错(相对路径context).默认是去找package.json中的 build2
// context:path.resolve(__dirname, "./"),//打包的上下文,path.resolve(__dirname, "./")指向config里面,一加路径全错
output: {
//path: path.resolve(__dirname, "../dist"),//__dirname是config文件位置
path:resolveApp('./dist'),
filename: "js/bundle.js",
},
resolve: {
alias: {
// "@": path.resolve(__dirname, "../src"),//如果嵌套多,比较麻烦,,所以把它拎出去
"@":resolveApp('./src')
},
},
对代码统一打包体积过大,可以使用代码拆分的方式对代码进行打包
引入
const TerserPlugin = require("terser-webpack-plugin")
在web.common.js中加入
optimization: {
minimizer: [
new TerserPlugin({
extractComments: false,
}),
],
splitChunks: {
chunks: 'all' //不论是同步还是异步,都会解析出分包处理
}
},
https://webpack.docschina.org/plugins/
chunks: 'initial', //async initial all
minSize: 20000,
maxSize: 20000, //拆分时小于maxSize,大于minSize
minChunks: 1, //包至少引用了几次
cacheGroups
optimization:{
// 当前文件的名称是按自然数进行编号排序,如果某个文件当前次不再被依赖那么重新打包时序号都会变
chunkIds: 'deterministic', //既易于观察,也对性能影响较小
}
output: {
chunkFilename: 'js/chunk_[name].js'
},
其中的name可以通过魔法注释的方式定义
import(/*webpackChunkName: "title"*/'./title')
将webpack 的runtime信息单独打包出来,这样就不会因为某个模块的变更导致包含模块信息的模块(通常会被包含在最后一个 bundle 中)缓存失效.
假设一个使用动态导入的情况(使用import()),在app.js
动态导入component.js
const app = () =>import('./component').then();
build之后,产生3个包。
0.01e47fe5.js
main.xxx.js
runtime.xxx.js
其中runtime
,用于管理被分出来的包。下面就是一个runtimeChunk
的截图,可以看到chunkId这些东西。
...
function jsonpScriptSrc(chunkId) {
/******/ return __webpack_require__.p + "" + ({}[chunkId]||chunkId) + "." + {"0":"01e47fe5"}[chunkId] + ".bundle.js"
/******/ }
...
如果采用这种分包策略
app
的时候runtime
与(被分出的动态加载的代码)0.01e47fe5.js
的名称(hash)
不会改变,main的名称(hash)
会改变。component.js
,main
的名称(hash)
不会改变,runtime
与 (动态加载的代码) 0.01e47fe5.js
的名称(hash)会改变。const oBtn = document.createElement('button')
oBtn.innerHTML = '点击加载元素'
document.body.appendChild(oBtn)
// 按需加载
oBtn.addEventListener('click', () => {
import('./utils').then(({ default: element }) => {
console.log(element)
document.body.appendChild(element)
})
})
在import内加入prefetch preload配置即可对所要引入的内容得到对应的引入模式
// 按需加载
oBtn.addEventListener('click', () => {
import(
/*webpackChunkName:'utils' */
/*webpackPreLoad:true */
'./utils').then(({ default: element }) => {
console.log(element)
document.body.appendChild(element)
})
})
preload
是告诉浏览器页面必定需要的资源,浏览器一定会加载这些资源。prefetch
是告诉浏览器页面可能需要的资源,浏览器不一定会加载这些资源。VUE SSR
生成的页面中,首页的资源均使用preload
,而路由对应的资源,则使用prefetch
。preload
,对于可能在将来的页面中使用的资源使用 prefetch
。内容分发网络(Content Delivery Network,简称CDN)是建立并覆盖在承载网之上,由分布在不同区域的边缘节点服务器群组成的分布式网络。
CDN应用广泛,支持多种行业、多种场景内容加速,例如:图片小文件、大文件下载、视音频点播、直播流媒体、全站加速、安全加速。
output: {
filename: 'js/[name].[contenthash:8].bundle.js',
path: resolveApp('./dist'),
publicPath: url //将资源部署在cdn服务器上
},
在webpack中加入配置
externals: {
lodash: '_' //键:不希望被打包的报名 值:全局的变量名
},
在index.html加入cdn地址
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js">script>
打包时会忽略lodash,通过cdn使用lodash
动态链接库英文为DLL,是Dynamic Link Library的缩写。DLL是一个包含可由多个程序,同时使用的代码和数据的库。
DllPlugin
DllPlugin
和 DllReferencePlugin
用某种方法实现了拆分 bundles,同时还大幅度提升了构建的速度。“DLL” 一词代表微软最初引入的动态链接库。
webpack.config.js
const path = require('path')
const webpack = require('webpack')
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
mode: "production",
entry: {
react: ['react', 'react-dom']
},
output: {
path: path.resolve(__dirname, 'dll'),
filename: 'dll_[name].js',
library: 'dll_[name]'
},
optimization: {
minimizer: [
new TerserPlugin({
extractComments: false
}),
],
},
plugins: [
new webpack.DllPlugin({
name: 'dll_[name]',
path: path.resolve(__dirname, './dll/[name].manifest.json')
})
]
}
打包会生成xxx.manifest.json,映射资源路径
此插件配置在 webpack 的主配置文件中,此插件会把 dll-only-bundles 引用到需要的预编译的依赖中。
plugins: [
new HtmlWebpackPlugin({
title: 'copyWebpackPlugin',
template: './public/index.html'
}),
new webpack.DllReferencePlugin({
context: resolveApp('./'),
manifest: resolveApp('./dll/react.manifest.json')
}),
new AddAssetHtmlPlugin({
outputPath: 'js',
filepath: resolveApp('./dll/dll_react.js') //加入此插件可以让html使用dll
})
]
这个插件使用 cssnano 优化和压缩 CSS。
本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。
本插件基于 webpack v5 的新特性构建,并且需要 webpack 5 才能正常工作。
const CopyWebpackPlugin = require('copy-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
module.exports = {
mode: 'production',
optimization: {
minimizer: [
new CssMinimizerPlugin()
]
},
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns: [
{
from: 'public',
globOptions: {
ignore: ['**/index.html']
}
}
]
}),
new MiniCssExtractPlugin({
filename: 'css/[name].[hash:8].css'
})
]
}
TerserWebpackPlugin
该插件使用 terser 来压缩 JavaScript。(github.com/terser/terser)
const CopyWebpackPlugin = require('copy-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
const TerserPlugin = require("terser-webpack-plugin")
module.exports = {
mode: 'production',
optimization: {
minimize: true, //设置为true,使用terserplugin
minimizer: [
new CssMinimizerPlugin(),
new TerserPlugin({
extractComments: false
})
]
},
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns: [
{
from: 'public',
globOptions: {
ignore: ['**/index.html']
}
}
]
}),
new MiniCssExtractPlugin({
filename: 'css/[name].[hash:8].css'
})
]
}
new webpack.optimize.ModuleConcatenationPlugin()
正常来说 webpack 的引入都是把各个模块分开,通过 __webpack_require__
导入导出模块,但是使用 scope hoisting 后会把需要导入的文件直接移入导入者顶部,这就是所谓的 hoisting。
可以看出,这么优化后:
__webpack_require__
调用模块,运行速度也会得到提升当然几时你开启了 scope hoisting,webpack 也不会一股脑地把所有东西都堆砌到一个模块。官网对这个问题也清楚地说明了,这里举个例子,在你使用非 ES6 模块或使用异步 import() 时,不会应用作用域提升,模块依然会拆分开,不过具体代码会跟正常的引入有一点差异。
webpack的treeshking是基于 es module的静态分析,能够在编译期间就确定哪些模块用到了哪些模块没用到,并且配合解构赋值还能确定哪些export用到了,哪些export没用到。然后对用到的部分和没用到的部分进行标记,在压缩阶段就可以删除标记出的没有用到的部分,从而达到treeshking的目的。
usedExports
编译时可以分析出解构写法引入的esm模块,哪些export用到了,哪些模块没有用到。然后就需要分别进行标记,开启标记的配置项就是 optimization.usedExports
sideEffects
treeshking只是建立在某个es module的某一些export有没有被用到的基础上的,但是有一些代码会有副作用,比如在window上挂一个变量、写本地文件等,这种代码虽然没有export一些内容,但也是不能被treeshking掉的。对于这些文件需要过滤掉,配置的方式就是在package.json中添加sideEffects字段,因为webpack的模块包括图片、字体文件、css文件等,这些模块都是需要配置的。
Css-TreeShaking
purifycss-webpack purify-css
const path = require('path');
const glob = require('glob');
const PurifyCSSPlugin = require('purifycss-webpack');
//插件配置
plugins:[
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: '[name].css',
// chunkFilename: '[id].css',
}),
new PurifyCSSPlugin({
// 配置这个css文件作用的html文件路径--我的测试项目中只有一个html文件,直接给了根目录下的所有html文件
paths: glob.sync(path.join(__dirname, './*.html')),
})
]
CompressionWebpackPlugin
const CompressionPlugin = require("compression-webpack-plugin");
module.exports = {
plugins: [
new CompressionPlugin({
test: /\.(css|js)$/, //匹配文件
minRatio: 0.8, //压缩比例达到多少开始压缩
threshold: 0, //体积超过多少开始压缩
algorithm: 'gzip'
})
],
};
可以辅助将一些chunk出来的模块,内联到html中: