webpack5

五个概念

  • entry:哪个文件为入口起点开始打包
  • output:输出的资源到哪里去,叫什么名字
  • loader:翻译官,webpack只能处理js代码,其他要交给loader来处理。
  • plugins,插件,执行范围更广的任务,如压缩
  • mode。开发模式(development):本地可以运行就可以,生产模式(production):代码优化上线

执行打包

1.纯命令行

# 安装。不加版本号默认安装最新版本
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

2.package.json中添加配置

"scripts": {
    "build": "webpack --entry ./src/main.js  --output-path ./dist"
}

后续直接命令行直接执行 npm run bulid

3.webpack.config.js中添加配置

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名字改了

loader

  • 顺序:从右往左,从下往上
  • 只有一个loader时,支持字符串写法
{
    test:/\.css$/,
    loader:'css-loader'
}

处理图片

file-loader处理图片

  • 安装:file-loader url-loader。

配置

{
    test: /\.(png|svg|gif|jpe?g)$/,
    use: [
        {
            loader: 'file-loader',
            options: {
                esModule: false//不转为esModule
            }
        }
    ]
             
}

问题

**问题1:**webpack5中,img标签的图片,通过require(’…/img/bg.png’)拿。拿到的是一个对象,{default:xxxx}

处理方法:

  • 配置esModule改为false,让它直接返回资源,不要esModule
  • 通过require(’…/img/bg.png’).default获取到图片链接
  • import oImgSrc from ‘…/img/bg.png’

**问题2:**css中的url图片,都会被自动转为require语法。require语法会默认导出一个esModule。

处理方法:

  • 在css-loader配置参数,esModule:false

url-loader处理图片

说明

/**
* 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]:
*/

webpack5 asset 处理文件

/**
* 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

plugin

和loader区别

  • loader 特定模块类型进行转换 读取文件内容时工作
  • plugin 做更多的事情,任意时期是都可以做。。。每个插件的核心就是一个类

使用

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下面查找,不写会报错
                    }
                }
            ]
        })
    ]
}

browserslistrc

问题

1、如何实现兼容:

  • 兼容处理工具…(需要处理兼容性,有工具不需要手动处理,工具有了,但是具体要兼容哪些平台)

2、到底兼容哪些平台:

  • 浏览器市场占有率网站:caniuse.com

browserslist

  • 有个工具:browserslist,默认安装webpack,就被安装好了,内部有caniuse-lite工具。
  • 兼容条件给完之后,这个工具会返回响应满足条件的浏览器平台
  • **工作原理:**moudele browserslist工作时->调用caniuse-lite,去请求一份数据,基于当前写的条件如兼容市场占有率>1%,返回满足条件的浏览器平台。然后结合加到webpack中的工具,对这些选中的平台进行兼容

配置

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:最新两版本

postcss

为什么需要postcss

是什么?

  • 利用js转换样式的工具,用来兼容选中的平台(见上)。

原理:

  • 首先需要要知道浏览器平台
  • 代码兼容到什么程度,取决于筛选出的浏览器平台。

安装插件

  • postcss:整体的工具,解析器。本质什么都不能做,要加对应的插件
  • postcss-cli:为了可以在命令行终端使用postcss命令
#指定文件进行处理,处理完成后输出ret.css文件
npx postcss -o ret.css ./src/css/test.css    
  • autoprefixer:给样式加前缀的插件,包含在 postcss-preset-env中
#--use autoprefixer 使用了autoprefixe这个插件
npx postcss --use autoprefixer  -o ret.css ./src/css/test.css
  • postcss-loader:像less-loader,postcss在postcss-loader之前工作,先对代码进来转换。只有这个插件不会有变化,需要插件,加参数
  • postcss-preset-env:预设->很多功能插件的集合。

配置

**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
    }
},

babel

为什么需要Babel

  • 处理js兼容
  • JSX、TS、ES6+等非es5 —>转成浏览器可识别的代码

安装插件

  • @babel/core:安装核心,默认情况下什么也转不了,需要特殊语法、安装特定的插件
  • @babel/cli:为了可以在命令行终端使用babel命令
#src下所有进行处理输出到build文件中  
#===>发现用了babel,但是也没有转换,要加载工具包
npx babel src --out-dir build 
  • @babel/plugin-transform-arrow-functions:箭头函数转换
#--plugins使用插件
npx babel src --out-dir build  --plugins=@babel/plugin-transform-arrow-functions
  • @babel/plugin-transform-block-scoping:作用域转换
npx babel src --out-dir build  --plugins=@babel/plugin-transform-arrow-functions,@babel/plugin-transform-block-scoping
  • @babel/preset-env:预设->很多功能插件的集合。就不需要每次专门去安装特定的插件
# --presets 使用预设
npx babel src --out-dir build  --presets=@babel/preset-env

配置

  • 转换不转换,根据
    • 需要兼容的浏览器配置文件中.browserslistrc…
    • 根据targets中指明浏览器.一般不用
{
    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'}
                      ]
                 ]*/
            }
        }
    ]
}

把配置单独拎出来

  • 文件名称可以是:
    • babel.config.js(后缀可以改 json cjs mjs)
    • babelrc.json(js) babel7之前的写法

webpack.config.js

{
    test: /\.js$/,
    exclude: /node_modules/,//当前需要,node_modules插件包里可能也需要。如,为了不相互影响要去掉
    use:['babel-loader']//把配置单独拎出来
}

babel.config.js

module.exports={
    presets:['@babel/preset-env']
}

polyfilly

是什么

  • 填充,babel的preset-env可以转换的代码不是特别多,如promise symbol更新语法,不能帮忙做转换,所以做兼容会遇到问题,所以有polyfilly这个存在。
  • webpack5之前,不用自己处理,默认有polyfilly,webpack5优化打包速度,所以把它去掉了,我们要进行按需配置。

安装

  • @babel/polyfill :–save 生产环境下也需要对它进行转换,包很大,可以去引用两个核心部分
  • core-js:大部分的ecmascript语法
  • regenerator-runtime:generator await等新语法

配置

在babel.config.js中配置

module.exports={
    presets:[
        [
            '@babel/preset-env',
            {  
                useBuiltIns:'entry' ,
                corejs:3//code-js版本是2,下载的是3,需要修改
            }
        ]
    ]
}
/**
 * 属性:useBuiltIns
 * false:不对当前的js处理做polyfill的填充
 * usage:根据用户源代码当中所使用到的新语法进行填。只管源代码。==>比较友好。
 * entry:依据筛选出来的浏览器,来决定填充什么。不管源代码有没有用,浏览器需要什么就填充什么。。。需要引入核心包
 * /

webpack-dev-server

界面自动更新

不需要重新打包,文件内容修改,自动编译打包,界面自动更新:

  • 方式一:package.json文件===>“build”: “webpack --watch”
  • 方式二:webpack.config.js===>watch:true
  • 开发模式下, watch 结合VSCode的live serve插件实现自动更新。

以上方式不足:

  • 某个内容发生改变,所有源代码都会重新编译
  • 每次编译成功之后都需要进行文件读写
  • live serve工具是VSCode下
  • 不能局部更新(不能热更新),一个完整界面是有很多个组件组成的,只修改一个,不希望全部更新

配置

package.json

  • webpack5之前需要写全 webpack-dev-server
"scripts": {
    "serve": "webpack serves",
}
  • 命令行npm run serve运行
  • 没有产生 dist目录,内容都写在内存中。读写速度快。

webpack-dev-middleware

原理: 中间件,订制,开启一个服务,webpack打包后处理的资源交给服务。之后浏览器端才能访问这个服务器

需要做的事:

  • 开启服务 express
  • 打包后的资源交给服务器 webpack-dev-middleware
/**********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端口上')
})
  • 运行 node Server.js

HMR功能

  • 模块热加载功能
  • 一个模块发生变化,只会重新打包这一个模块。 极大提升构建速度。

配置

webpack.config.js开启热加载功能

devServer:{
    //hot:true,//开启热加载功能
    hot: 'only',//如语法报错的地方,重新加载,而不是全部刷新
},

模块中

import "./js/title";
if (module.hot) {
  //哪些模块开启热更新,多个是数组
  module.hot.accept("./js/title", () => {
    console.log("./js/title更新了");
  });
}

处理.vue文件

  • 安装:vue 、vue-template-compiler 、vue-loader、

vue-loader

  • 支持vue组件的热更新
  • 版本不同配置不同,跟vue版本有关
  • vue-loader@16要跟vue3一起用
  • vue-loader15,需要手动加载插件

配置

webpack.config.js,vue-loader14时,可以直接使用

{
    test:/\.vue$/,
    use:['vue-loader']
}

vue-loader15时,需要再手动加载插件

const VueLoaderPlugin = require('vue-loader/lib/plugin')
plugins:[
    new VueLoaderPlugin()
]

路径属性path

output

  • path:绝对路径,打包之后资源要输出到哪目录下
  • publicPath:index.html内部的引用路径( index.html引用的路径去哪找)

devServer

webpack5之前:

  • publicPath:指定本地服务所在的目录,默认’/’,项目所在的目录,最好跟output publicPath设成一样
  • contentBase: 我们打包之后的资源,如果依赖了其他的资源,此时就告知去哪里找(并不希望这个资源被打包,但是要使用)

配置

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不存在了

},

代理

  • 异步请求,api.com/users(跨域)
  • 开发阶段可能存在跨域问题(后端前端再不同的服务器上开发)。生产阶段一般不存在,一般后端解决,或已经部署再同一个地方。
  • 通过代理方式来完成跨域数据请求
  • **怎么做?**服务端和服务端之间不存在跨域。webpack-dev-serve已经开启了一个服务

配置代理:

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);
});

resolve

  • 模块解析
  • 相对路径 绝对路径 模块名称

相对路径解析规则

  • 路径确定了,判断后面是文件夹还是文件
  • 如果是文件夹会根据 mainFiles: ['index'] 去补全。补全完再在根据下面文件查找方式查找。
  • 如果是文件,看文件后缀名是什么。有明显后缀名,直接打包。没有后缀名,去extensions中去补充的操作,extensions中找到补充,没找到报错。

配置

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";

devtool

  • eval:报错得文件是打包之后得文件…没法快速调试。development模式下会把devtool改成eval。
  • source-map:一种映射得技术,再调试时可以定位到源代码中的信息,可以快速调错
  • eval-source-map
  • inline-source-map
  • cheap-source-map
  • cheap-module-source-map
  • hidden-source-map
  • nosource-source-map

source-map组合写法

[inline-|hidden-|eval-]:

  • source-map文件怎么产出。
  • inline-:产出base64,放文件的最后面
  • eval-:每个模块放eavl后面
  • hidden:.map文件存在又不希望默认加载进来,不能直接定位源文件

[nosources]:没有源文件,又有报错信息

[cheep-|[module-]]

  • cheep- 只提供行信息,减少消耗
  • module- 对loader更友好。是源码信息。没有加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"
},

新建配置文件夹config

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' //不论是同步还是异步,都会解析出分包处理
    }
  },

splitchunks配置

https://webpack.docschina.org/plugins/

chunks: 'initial', //async initial all
minSize: 20000,
maxSize: 20000,   //拆分时小于maxSize,大于minSize
minChunks: 1, //包至少引用了几次
cacheGroups

import动态导入

optimization:{
// 当前文件的名称是按自然数进行编号排序,如果某个文件当前次不再被依赖那么重新打包时序号都会变
  chunkIds: 'deterministic', //既易于观察,也对性能影响较小
}
output: {
    chunkFilename: 'js/chunk_[name].js'
  },

其中的name可以通过魔法注释的方式定义

import(/*webpackChunkName: "title"*/'./title')

runtimeChunk

将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"
/******/     }
...

如果采用这种分包策略

  1. 当更改app的时候runtime与(被分出的动态加载的代码)0.01e47fe5.js名称(hash)不会改变,main的名称(hash)会改变。
  2. 当更改component.jsmain名称(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)
  })
})

prefetch和preload

在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

第三方扩展设置CDN

内容分发网络(Content Delivery Network,简称CDN)是建立并覆盖在承载网之上,由分布在不同区域的边缘节点服务器群组成的分布式网络。
CDN应用广泛,支持多种行业、多种场景内容加速,例如:图片小文件、大文件下载、视音频点播、直播流媒体、全站加速、安全加速。

  • 自有服务器
output: {
    filename: 'js/[name].[contenthash:8].bundle.js',
    path: resolveApp('./dist'),
    publicPath: url //将资源部署在cdn服务器上
  },
  • 使用第三方cdn

在webpack中加入配置

externals: {
    lodash: '_'  //键:不希望被打包的报名 值:全局的变量名
  },

在index.html加入cdn地址

 <script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js">script>

打包时会忽略lodash,通过cdn使用lodash

DLL

动态链接库英文为DLL,是Dynamic Link Library的缩写。DLL是一个包含可由多个程序,同时使用的代码和数据的库。

DllPlugin

DllPluginDllReferencePlugin 用某种方法实现了拆分 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,映射资源路径

使用DLL

DllReferencePlugin

此插件配置在 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
    })
  ]

CSS抽离和压缩

  • CssMinimizerPlugin

这个插件使用 cssnano 优化和压缩 CSS。

本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。

  • MiniCssExtractPlugin

本插件基于 webpack v5 的新特性构建,并且需要 webpack 5 才能正常工作。

  • 与 extract-text-webpack-plugin 相比:
    • 异步加载
    • 没有重复的编译(性能)
    • 更容易使用
    • 特别针对 CSS 开发
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'
    })
  ]
}

TeserPlugin压缩JS

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'
    })
  ]
}

Scope hoisting

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'
    })
  ],
};

InlineChunkHtmlPlugin

可以辅助将一些chunk出来的模块,内联到html中:

  • 比如runtime的代码,代码量不大,但是是必须加载的;
  • 那么我们可以直接内联到html中

你可能感兴趣的:(webpack,javascript,前端)