webpack 5

web 1.0 : static website 

web 2.0 : AJAX

web 3.0 : modern front-end | module | framework | sass | es 6 .....

问题: 模块开发支持问题 | 实时监听 热更新 | 打包 压缩 | 新特新使用 |=>webpack 

上手

webpack 5.45.1      |"webpack": "^5.52.1",

webpack cli 4.7.2    |"webpack-cli": "^4.8.0"

live server              |vscode extension

添加 type="module" 字段 => 浏览器(if support )可以识别 ESM 代码, 但 此时CJS => 无效

<script src="./src/index.js" type="module"></script>

使用webpack 打包后则 CJS 和 ESM 都可以正常执行=》被转换,

此时 type="module" 则不需要

默认入口: src/index.js

默认出口: dist/main.js

yarn webpack
<script src="./dist/main.js"></script>

配置 package.json

指定入口: yarn webpack --entry ./src/main.js

指定出口: yarn webpack --output-path ./build

//package.json
"scripts":{
    "build": "yarn webpack --entry ./src/main.js --output-path ./build"
}
// node 
yarn build

配置 webpack.config.js

⚠️  path: path.join(__dirname,'dist') // webpack 4 写法

entry 指定入口文件,相对路径

path { filename:指定输出文件名,path:绝对路径<require> ( require('path') ) }

// 默认: webpack.config.js
const path = require('path')
module.exports = {
    entry:"./src/index.js", // 可以是相对路径
    output:{
        filename:"build.js", // 指定文件名
        path: path.resolve(__dirname,'dist') // 必须是绝对路径
        // path: path.join(__dirname,'dist') // webpack 4 写法
    }
}
// node 
yarn webpack
// package.json
"scripts":{
    "build":"webpack --config lg.config.js" // 修改webpack配置文件路径
}

无法从入口出发找到的文件不会被webpack打包

Loader

1. what is loader:  模块|转换

2. loader 的使用

webpack4 中 loader 有3中使用方式, webpack5 中使用 行内 和配置文件方式

1. 行内loader 

import "css-loader! ../css/login.css" //每一次导入都要写

2. 配置文件中 loader (推荐

module.exports = {
    entry:"",
    output: {
    },
    module:{
        rules:[
            {                     //方式1:较全方式
                test:/\.css$/,
                use:[
                    {
                        loader:"css-loader",
                        // options:"", // 添加配置
                        // query:{} // 在webpack5 中 被合并到了options 里
                    }
                ]
            },
            {                     //方式2 : 简写
                test:/\.css$/,
                loader:"css-loader"
            },
            {                     //方式3: 简写
                test:/\.css$/,
                use:["css-loader"]
            }
        ]
    }
}

3. 命令行中的loader (webpack5 中 CLI 方式被废弃)

css-loader + style-loader | 执行顺序问题

yarn add css-loader --dev  |转换css文件|"css-loader": "^6.2.0",

yarn add style-loader --dev|展示css样式|"style-loader": "^3.2.1",

module.exports = {
    entry:"",
    output: {
    },
    module:{
        rules:[
            {                    
                test:/\.css$/,
                use:["style-loader","css-loader"] //从后往前执行
            }
        ]
    }
}

less-loader

   首先用 less-loader  将 less 文件转化为css文件

   然后用 css-loader   将 css 文件转化为js代码

   最后用 style-loader 将 js    代码 展示到浏览器

    "css-loader": "^6.2.0",
    "less": "^4.1.1",
    "less-loader": "^10.0.1",
    "style-loader": "^3.2.1",

// index.less
@bgColor:seagreen;
.title{
    background-color:@bgColor;
}

//index.js 
import "./css/index.less"
// yarn add less --dev
module.exports = {
    ...
    module:{
        rules:[
            {
                test:/\.less$/,
                use: ["style-loader","css-loader","less-loader"]
            }
        ]
    }
    ...
}

配置 Browserslistrc

工程化:前端开发基于工程化 => 使用了CSS JS新特性

兼容性:浏览器 支持 CSS JS 新特性 问题

哪些浏览器平台需要兼容:项目指明的平台 | 市面主流平台 caniuse.com

常见配置: >1%  | default |last 2 versions|not dead|

配置位置: 1. package.json | 2. .browserslistrc

由此处更新:

postcss 的工作流程

基于browserlistrc 的配置 做兼容

1. postcss : 利用JavaScript转换 样式 的工具

2. less(less-loader) -> css ->css-loader

yarn add postcss --dev       | "postcss": "^8.3.6",         | 工具

yarn add postcss-cli --dev   | "postcss-cli": "^8.3.1",   |命令行工具

yarn add autoprefixer --dev |"autoprefixer": "^10.3.1",|添加前缀包

yarn postcss --use autoprefixer -o ret.css ./src/css/test.css

--use <postcss 使用的包>|-o < 输出文件 + 输入文件地址  > 

Autoprefixer CSS online | 检查浏览器 CSS 前缀 

postcss-loader 

postcss.config.js | webpack.config.js 可以在这两个文件中配置

postcss-loader 调用 postcss ==》postcss 调用工具包 (postcss-preset-env)

yarn add postcss-loader --dev                |  "postcss-loader": "^6.1.1",
yarn add postcss-preset-env --dev         |  "postcss-preset-env": "^6.7.0", |     插件集合

module.exports = {
    ...
    modules: [
        {
            test: /\.less$/,
            use: ['style-loader',
                  'css-loader',
                  // 'postcss-loader', // 添加配置文件后的写法
                  {
                    loader:'postcss-loader',
                    postcssOptions: {
                        plugins:['postcss-preset-env']          // 写法1
                        // plugins:[ require('autoprefixer') ]  // 写法2
                    }
                  },
                  'less-loader'
            ]
        }
    ]
    ...
}
// postcss.config.js // 不可改名
module.exports = {
    plugins:[
        require('postcss-preset-env')
    ]
}

importLoaders 属性

向后推 loaders 处理顺序

问题实例: @import test.css 中有需要 postcss 处理的 代码。

思路流程:当postcss-loader 运行结束后交给css-loader,而css-loader 可以识别@import |media |url 等语句, css-loader 运行后会继续向前。 导致 test.css 里文件并没有被处理。

解决办法:在css-loader中添加 importLoaders 属性 处理未解析的css 文件

test:/\.css$/
use:[
    'style-loader',
    {
        loader:'css-loader',
        options:[
            importLoaders: 1
        ]
    },
    'postcss-loader'
]

file-loader 

yarn add file-loader --dev | "file-loader": "^6.2.0",

处理图片| 将图片当作模块对待

img src|background url|

// 前置准备

function packImg(){
    const ele = document.createElement('div')    // 创建一个容器
    const img = document.createElement('img')    // 创建一个图片
    img.src = require('../img/icon.png').default // 设置图片src
    ole.appendChild(img)                         // 将img 普鹿梗 进 ele
    return ele                                   // 返回容器
}
document.body.appendChild(packImg())             // 将 ele 普鹿梗 进 body

// 关于image 导入 再webpack5 中的问题
//********** 方式1
// img.src = require('../img/icon.png')          // 将图片当作模块使用
img.src = require('../img/icon.png').default     // webpack5中返回的是一个ESModule对象
                                                 // webpack4 中不需要添加
//********** 方式2
// 在 file-loader 里 添加 esModule: false 属性
options:{
    esModule: false
}
//********** 方式3
import image from '../img/icon.png'             // 使用ESModule的方式导入
module.exports = {
    ...
    module:{
        rules:[
            test: /\(png|svg|gif|jpe?g)$/,
            use: [ 'file-loader']
        ]
    }
    ...
}

关于 background url 的问题: 

在css 处理css 文件是遇到 url ('../img/icon.png') 时会转化为 require('../img/icon.png') 语法

而 require 语法 返回的 是一个 ESModule 对象,

解决办法是:css-loader 中 添加 esModule: false

修改产出图片名

占位符:|ext|name|hash|contentHash|hash:8|

module.exports = {
    ...
    module:{
        rules:[
            test: /\(png|svg|gif|jpe?g)$/,
            use: [
                {
                    loader: 'file-loader',
                    options: {
                        name: 'img/[name].[hash:6].[ext]',
                        // output-path: 'img'
                    }
                }
            ]
        ]
    }
    ...
}

url-loader

"url-loader": "^4.1.1",

url-lloader:减少请求次数|适合小文件 | 以base64 uri 方式 加入文件

file-lloader:直接拷贝文件

url-loader 内部可以直接调用 file-loader

通过limit 选择 url-loader 还是 file-loader

module.exports = {
    ...
    module:{
            rules:[
                {
                    test: /\(png|svg|gif|jpe?g)$/,
                    use: [
                        {
                            loader: 'url-loader',
                            options: {
                            name: 'img/[name].[hash:6].[ext]',
                            // output-path: 'img'
                            limit: 25 * 1024
                            }
                        }
                    ]
                }
            ]    
    }
    ...
}

asset 处理图片 - 资源类型模块(webpack 内置 | webpack 5 特有)

配置选项:

asset/resource  = file-loader | 拷贝资源 / 配置文件名 和 输出路径

asset/inline = url-loader 

asset/source  = raw-loader

asset (parser dataurlconditon maxsize) = 设置 limit

module.exports = {
    output:{
        assetModuleFilename: "img/[name].[hash:6][ext]" // 方式1:全局指定导出
    },
    modules:{
        rules:[
            // asset/resource | asset/inline
            {
                test:/\.(png|svg|gif|gpe?g)$/,
                type:'asset/resource',
                // type: 'asset/inline', // 不需要导出文件
                generator:{
                    filename:"img/[name].[hash:6][ext]" //方式2:只将图片导出到img里
                }
            },
            // asset
            {
                test:/\.(png|svg|gif|gpe?g)$/,
                type:'asset',
                generator:{
                    filename:"img/[name].[hash:6][ext]" 
                },
                parser:{
                    dataUrlCondition:{
                        maxSize: 30 * 1024
                    }
                }
            }
        ]
    }
    ...
}

asset 处理图标字体

asset/resource

@font-face {
  font-family: "iconfont"; /* Project id 2250626 */
  src: url('iconfont.woff2?t=1628066777598') format('woff2'),
       url('iconfont.woff?t=1628066777598') format('woff'),
       url('iconfont.ttf?t=1628066777598') format('truetype');
}
.iconfont {
  font-family: "iconfont" !important;
  font-size: 16px;
  font-style: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.icon-linggan:before {
  content: "\e602";
}
//前置工作

import '../font/iconfont.css'
import '../css/index.css'

function packFont() {
  const oEle = document.createElement('div')

  const oSpan = document.createElement('span')
  oSpan.className = 'iconfont icon-linggan lg-icon'
  oEle.appendChild(oSpan)

  return oEle
}

document.body.appendChild(packFont())
module.exports = {
    ...
    module: {
        rules:[
                  {
                    test: /\.css$/,
                    use: [
                      'style-loader',
                      {
                        loader: 'css-loader',
                        options: {
                                  importLoaders: 1,
                                  esModule: false
                        }
                      },
                      'postcss-loader'
                    ]
                  },
                  {
                    test: /\.(ttf|woff2?)$/,
                    type: 'asset/resource', // 直接拷贝
                    generator: {
                          filename: 'font/[name].[hash:3][ext]'
                    }
                  }
        ]
    }
    ...
}

loaders VS plugins

loader:对模块 转换 读取资源时

plugin:更多功能 | 更多工作时间

clean-webpack-plugin

版本: "clean-webpack-plugin": "^4.0.0-alpha.0"  | 解构

const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
    ...
    plugins:[
        new CleanWebpackPlugin()
    ]
    ...
}

html-webpack-plugin

版本: "html-webpack-plugin": "^5.3.2",

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

module.exports = {
    ...
    plugins: [
        new HtmlWebpackPlugin({
            title: 'html-webpack-plugin'
            template:'./public/index.html'
        })
    ]
    ...
}
// 无参数:   建立基础html文件, default title is: webpack app, 通过参数title修改
//          注入 生成的js 文件,添加defer 属性
// 模版参数: 自定义模版html,通过 EJS <%= htmlWebpackPlugin.options.title %> 添加数据
// 添加常量数据: 接下一段 DefinePlugin (webpack 自带的插件)

copy-webpack-plugin

arguments - patterns
版本: "copy-webpack-plugin": "^9.0.1",

const CopyWebpackPlugin = require('copy-webpack-plugin')

module.exports = {
    ...
    plugins: [
        new CopyWebpackPlugin({
            patterns: [
                {
                    from:'public',
                    to:'./dist', // 可省略
                    ignore:[ '**/index.html'] //注意public和dist下的重复文件
                }
            ]
        })
    ]
    ...
}
//**** ignore:['**/index.html']
// webpack 5 需要添加,如果 public下有index.html
// html-webpack-plugins已经在dist下生成了index.html
// **/  意味着从public下找

define-webpack-plugin

webpack 内置 plugin

const DefinePlugin = require('webpack')

module.exports = {
    ...
    plugins:[
        new DefinePlugin({
            BASE_URL: '"./"'    // 注意这里两次引号的使用
        })
    ]
    ...
}

Babel

Babel 是 一个工具| 本身不具备任何功能

    "@babel/cli": "^7.14.8",                                                    |命令行操作
    "@babel/core": "^7.15.0",                                                |转换核心
    "@babel/plugin-transform-arrow-functions": "^7.14.5",   |箭头函数转换
    "@babel/plugin-transform-block-scoping": "^7.14.5",      |作用域转换
    "@babel/preset-env": "^7.15.0",                                      |插件集合

npx babel src --out-dir build --plugins=@babel/plugin-transfrom-arrow-functions,@babel/plugin-transfrom-block-scoping
npx babel 输入 --out-dir 输出 --plugins=插件,插件

Babel-loader

    "babel-loader": "^8.2.2",                                                    | 类似于babel/core:不转换    

   "@babel/plugin-transform-arrow-functions": "^7.14.5",   |箭头函数转换
    "@babel/plugin-transform-block-scoping": "^7.14.5",      |作用域转换
    "@babel/preset-env": "^7.15.0",                                      |插件集合

{
    test:/\.js$/,
    use:[
        {
            loader:'babel-loader',
            options:{
                //plugins:[
                    //'@babel/plugin-transform-arrow-function',
                    //'@babel/plugin-transform-block-scoping',
                //]
                presets: ['@babel/preset-env']
            }
        }
    ]
}

查看 .browserslistrc | 针对需要转换的浏览器支持进行转换 (次)(推荐)(其他也可用)

或者添加配置 来设置转换目标支持的浏览器(主)(仅仅对babel 起效)

presets: [
    ['@babel/preset-env',{targets:"chrome 91"}]
]

babel-loader 配置文件

第一种:babel.config.js(json cjs mjs)|多包管理 (推荐)(示例)

第二种:.babelrc.json(js)|babel 7 之前|babel每个功能对应一个仓库|

// babel.config.js
module.exports = {
  presets: ['@babel/preset-env']
}
//webpack.config.js
{
    test:/\.js$/,
    use:['babel-loader']
}

polyfill

ployfill 是什么: 对于preset-env中不支持的转换做填充,例如:promise

在 webpack 4 中 默认添加,产出较大|webpack5 中 移除 > 优化打包速度, 按需添加

yarn add @babel/polyfill@7.12.1 --dev(开发依赖) | --save(生产依赖)(推荐)

        core-js 3.16.0

        regenerator-runtime 0.13.9

useBuildIns:false | usage

                                 corejs:3

                                                | entry +

                                                  2核心包 index.js

                                                              (import "core/js/stable")

                                                                (import "regenerator-runtime/runtime")

// babel.config.js
module.exports = {
  presets: [
    ['@babel/preset-env',
     {
        // useBuildIns:false // 默认
        // useBuildIns:entry // 依据.browserlistrc中的表填充(浏览器需要的都填充,即使代码中        没有)
        useBuildIns:usage, // 对于preset-env 不能转换 但 源代码中使用的新语法 做填充
        corejs:3 // 默认使用的是corejs 2 // 安装几版本就是几
     }
    ]
  ]
}
// webpack.config.js
{
    test:/\.js$/,
    exclude:/node_modules/,   // 不转换第三方的包  
    use:["babel-loader"]
}

webpack-dev-server 

方式1: 观察模式 + live server |所有源代码重新编译|磁盘交互|

             观察模式:方式1: yarn webpack --watch

                               方式2: module.exports = { watch: true } |默认false

方式2: webpack-dev-server

版本:"webpack-dev-server": "^3.11.2"

yarn webpack serve                | webpack 5
// yarn webpack-dev-server --open | webpack 4

yarn webpack serve --config lg.webpack.js

webpack-dev-middleware

express                                     | 开启服务器

webpack-dev-middleware        |wrapper|拿取webpack打包结果|传递给服务器

yarn add express webpack-dev-middleware --dev
// server.js
const express = require("express")
const webpackDevMiddleware = require("webpack-dev-middleware")
const webpack = require("webpack")


const app = express()    // 开启一个服务器

// webpack 打包 : 获取配置文件
const config = require("webpack.config.js")
const compiler = webpack(config)

app.use(webpackDevMiddleware(compiler))

// 开启端口
app.listen(3000, ()=>{
    console.log("服务器开启运行在3000")
})
node .\server.js

HMR

webpack.config.js

// webpack.config.js
module.exports = {
    mode: "development",    | 在开发阶段 与.browserslistrc 冲突
    target:"web",           | 解决冲突办法|屏蔽.browserlistrc
    ...
    devServer:{             |热更新功能开启
        hot: true           |热更新功能开启
    }
    ...
}

index.js 

在入口文件制定需要HMR的模块

import "./title.js"

if(module.hot){                                       // 源码
    module.hot.accept(                                // 源码
        ["./title.js"],                               // 指定热更新模块
        ()=>{ console.log("title.js is updated") }    // 可以添加回调函数
   )
}

HMR - React

"@babel/core": "^7.15.0",
"@babel/preset-env": "^7.15.0",
"@babel/preset-react": "^7.14.5",
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",

"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-refresh": "^0.10.0",

//react
import React, {Component} from 'react'

class App extends Component{
  constructor(props){
    super(props)
    this.state = {
      title: '前端 33'
    }
  }
  render(){
    return (
      <div>
        <h2>{this.state.title}</h2>
      </div>
    )
  }
}
export default App
import './title'
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App.jsx'            // webpack.config.js 配置

if (module.hot) {
  module.hot.accept(['./title.js'], () => {
    console.log('title.js模块更新')
  })
}

ReactDOM.render(<App />, document.getElementById('app'))
//babel.config.js

module.exports = {
    presets:[
        ['@babel/preset-env'],     // 识别常见es6语法
        ['@babel/preset-react']    // 识别jsx代码
    ],
    plugins:[
        ['react-refresh/babel']    // 搭配ReactRefreshWebpackPlugin使用
    ]
}
const ReactRefreshWebpackPlugin = require(@pmmmwh/react-refresh-webpack-plugin)

target: web,
devServer:{
    hot: true
},

...
{
    test:/\.jsx?$/,
    use:['babel-loader']
}

HMR - VUE

使用vue-loader 

"vue-loader": "^15.9.8", // vue 3 使用 vue-loader@16, vue2 使用vue-loader@15/14

vue@2  + vue-loader@14

// index.js |入口文件
import Vue from 'vue'
import App from './App.vue'

//if(module.hot){
//...            // js HMR
//}

new Vue({
    render: h => h(app)
}).$mount('#root')
const VueLoaderPlugin = require('vue-loader/lib/plugin')

...

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

...
plugins:[
    new VueLoaderPlugin()
]

// vue-loader@15 以后 需要手动添加VueLoaderPlugin
<template>
  <div class="example">{{ msg }}</div>
</template>

<script>
export default {
  data() {
    return {
      msg: 'Hello world!  222',
    }
  },
}
</script>

<style>
.example {
  color: orange;
}
</style>

output - path 

output:{
    filename:'js/main.js'
    path: path.resolve(__dirname,'dist'),    // path:打包输出路径,本地静态资源访问
                                             // publicPath:开启本地服务时访问本地文件
    publicPath:'./'    |1. 默认    ''         
                       |2. 绝对路径 '/'       // 本地浏览无法访问,devServer可以
                       |3. 相对路径 './'      // 本地可以访问, devServer 不可以
}

http://域名 + publicPath + filename 形式访问
    如果 publicPath为空 则: 默认添加 / :http://localhost8080/js.main.js

devServer - publicPath | path | contentBase | watchContentBase

devServer - publicPath 和 output - publicPath 保持一致

devServer - publicPath:如:告知express, 资源存在哪里


contentbase: 打包后的资源 依赖了 未被打包的文件, 因此publicPath 无法找到(绝对路径)

contentBase: path.resolve(__dirname,'public')

       index.html 中   <script src="/utils.js"></script>

watchContentBase: true         // 为contentbase 开启HMR

devServer - hotonly:true

修改报错后, 不刷新页面,只做热更新

devServer - histroyApiFallback:true

当请求404时,则找index.html.single page web application 只有index.html 返回,

因此刷新后的可能出现其他页面404. 所以使用 histroyApiFallback:true

devServer - proxy

本地开启服务 https://localhost:8080/index.html 中 需要其他数据, 而这些数据在其他端口上, 如:api.com/users. 此时可能会出现 跨域问题 CORS.  ==>  通过代理proxy 解决

devServer:{
    proxy:{
        // /api/users |简写了 http://localhost:4000|浏览器会自动补充 | index.html
        // http://localhost:4000/api/usrs  |通过localhost 发送请求
        // 设置 'api' 后,结果为:https://api.github.com/api/users

        '/api':{                               
            target:'https://api.github.com',
            pathRewrite:{
                // "^/api": ""     // 若:请求接口为 https://api.github.com/users
                "^/api": "info"    // 若:请求接口为 https://api.github.com/info/users
            },
            changeOrigin:true      // 这样对于GitHub 来说, 属于同源请求。
        }
    }
}

 configration - resolve 

resolve.extensions |默认文件名

resolve: {
    extensions: ['.js', '.json', '.wasm'],
  },

当 导入文件没有文件名时,从该配置中找。 如:

import Home from "./components/Home"
// 此时 home 在文件结构中为一个文件,但是并没有文件名后缀。 则从提供的extension中找,并添加,如果重名,以第一个为准

resolve.mainFiles

若导入文件夹后为写明文件, 则默认index,然后再从 extensions里找后缀补全。如:

import About from "./components/"
  resolve: {
    mainFiles: ['index'],
  },
// 此时找的是 compoents下的index.js

 resolve.alias | 修改别名

resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src')
    },
import About from "./components/"

import About from "@/components/" 

devtool.source-map 

为什么需要sourcemap, 在开发中遇到报错时,能够定位源码中的错误。

mode:"development"  模式下 默认是 devtool:  "eval" 模式, short for evaluate

devtool:  "source-map"

最佳实践

开发模式下: cheap-module-eval-source-map

发布前打包: none : source map 会暴露源代码

ts-loader vs babel-loader[preset-typescript]

ts-loader@9.2.5

npm i typescript -D

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

ts-loader                                     在编译阶段 可以 校验出 语法错误|不能做polyfill 填充

babel-loader[preset-typescript]   在编译阶段 有时候不可以 校验出 语法错误,运行阶段方可

搭配: yarn tsc --noEmit 检测语法错误

"build":"npm run check && webpack"
"check":"tsc --noEmit"
// babel.config.js
module.exports = {
  presets: [
    ['@babel/preset-env', {
      useBuiltIns: 'usage',
      corejs: 3
    }],
    ['@babel/preset-typescript']
  ]
}

加载 vue 文件

打包环境 区分 合并

"build":"webpack --config ./conifg/webpack.common.js --env production"
"serve":"webpack serve --config ./conifg/webpack.common.js --env development"
// 配置信息可以通过参数获取
module.exports = (env)=>{
    const isProduction = env.production
    return {
        // module.exports里的对象全部替换在此
    }
}

1. 命令行 - 环境配置 

2. 分离配置文件: 运行环境通过module.exports = (env) =>{} 参数获取

3. 文件路径问题: 运行 webpack.common.js 时,__dirname 获取的时conifg文件夹,

path: path.resolve(__dirname, '../dist') // __dirname: config目录,拼接: 退后一层= 根, 接dist

运行 "build":"webpack --config ./conifg/webpack.common.js --env production" 时工作环境时config所在的目录 = 项目目录


yarn add webpack-merge

const { merge } = require("webpack-merge")

webpack-merge

return merge( commonConfig,config) 合并的是对象


根据 模式来判断 对 babel.config.js 文件的处理, 关键是:如何获取 当前模式

在 webpack 文件中 设置process.env.NODE_ENV = .... 来设置环境配置, 

在 babel.config.js 中 获取环境配置的值,从而进行不同的操作

代码拆分打包(依赖)- 3 methods - 主流:optimization.splitChunks/chunks

const TerserPlugin = require('terser-webpack-plugin')    // 不需要安装 直接导入

...
entry:{
    // 方式1: 多入口
    main1: "./src/main1.js",    // main1. main2 在自定义输出文件名时可以用到
    main2: "./src/main2.js"

    // 方式2: 多入口 + 依赖 |共同依赖
    main1: {import:"./src/main1.js", dependOn:"lodash"},
    main2: {import:"./src/main2.js", dependOn:"lodash"},
    lodash:"lodash"
    shared:["lodash, jquery"]

    // 方式3: 常见/单入口 + optimization - splitChunks:{chunks: "all"}
    main3: './src/index.js'

}

output: {
    filename:'js/[name].js'    // 自定义文件名
}

optimization:{
    minimizer:[
        new TerserPlugin({
            extractComments:false,    // 不导出注释文件txt
        })
    ],
    splitChunks:{
        chunks: "all"   // 拆分依赖, 在index.js中导入的jquery会被单独打包出来
                        // default: async
    }
}

optimization.splitChunks 配置(基于同步模式拆分的前提 )

SplitChunksPlugin | webpack 中文文档

bundles vs chunks: |bundle=> 交给 html 使用| chunks => 依赖的包|

动态导入文件默认单独打包

output:{
    chunkFilename:'js/chunk_[name].js'
}

// [name] 可以通过动态导入时的魔法注释获取。
// 如: import(/*webpackChunkName:"title"*/ './title')

// 导出结果为: js/chunk_title.js

optimization:{
    chunkIds: 'natural',  // 问题: 当有一个包不打包了, 所有id都变了, 影响缓存
    ...
}
natural按使用顺序的数字 id。
named对调试更友好的可读的 id。
deterministic被哈希转化成的小位数值模块名。生产模式下 建议使用
chunks: async (默认)|initial | all 

minSize: 20000(默认|如果拆分后的包大小 小于20kb 则 不会单独拆开)

minChunks:1 // 如果一个包需要被拆分 至少要被使用一次

cacheGroups:{        // 第三方包统一打包规则
    syVendors:{
        test: /[\\/]node_modules[\\/]/,
        filename:'js/[id]_vendor.js',
        priority:-10
    }
    default:{        // 打包默认规则: chunks, minSize,minChunks 都可以在这里设置
        priority:-20    // 当 syvendors 和 default 规则同时符合,优先级高的规则执行

    }
}

optimization.runtimeChunk

runtimeChunk: true(默认false),

处理模块加载规则的代码会被 单独提取=> 利于浏览器缓存

optimization: {
    runtimeChunk: true,
}

代码懒加载

首次加载时不需要的文件, 在运行时才需要

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(魔法注释+懒加载+子文件中)

prefetch: 浏览器加载完 当前页面后, 空闲时:加载以后可能会遇到的代码。

preload:与当前chunk 并行下载

const oBtn = document.createElement('button')
oBtn.innerHTML = '点击加载元素'
document.body.appendChild(oBtn)

// 按需加载
oBtn.addEventListener('click', () => {
  import(
    /*webpackChunkName:'utils' */
    /*webpackPreLoad:true */
    './utils').then(({ default: element }) => {
      console.log(element)
      document.body.appendChild(element)
    })
})

第三方扩展设置CDN

1. 不参与webpack打包

module.exports = {
  //...
  externals: {       // 不让lodash参与打包
    lodash: '_'      // 语法: 不希望被打包的包名:"包对外暴露的全局变量名"
  },
};

2. 去官网找src link ,插入需要的html文件

<script src="https://cdn...../lodash.js"></script>

DLL库(动态链接库 dynamic link library)

好处: 不需要频繁打包的不重复打包,跨项目使用

第一部分: 打包

拆分bundles,将重复使用且不常变动的bundle 提取出来单独打包,配合一个json文件。

const path = require('path')
const webpack = require('webpack')
const TerserPlugin = require('terser-webpack-plugin')

module.exports = {
  mode: "production",
  entry: {
    react: ['react', 'react-dom']
  },
  output: {          // 生成.js 文件
    path: path.resolve(__dirname, 'dll'),
    filename: 'dll_[name].js',
    library: 'dll_[name]'
  },
  optimization: {    // 去除txt文件
    minimizer: [
      new TerserPlugin({
        extractComments: false
      }),
    ],
  },
  plugins: [
    new webpack.DllPlugin({    // 生成配套json 文件用于查找源代码
      name: 'dll_[name]',
      path: path.resolve(__dirname, './dll/[name].manifest.json')
    })
  ]
}

第二部分: 使用

new webpack.DllReferencePlugin({context:路径<dll folder>, manifest:路径<manifest.json>})

"add-asset-html-webpack-plugin": "^3.2.0",

// resolveApp 模块 => 绝对路径拼接
const path = require('path')
const appDir = process.cwd()
const resolveApp = (relativePath) => {
  return path.resolve(appDir, relativePath)
}
module.exports = resolveApp
const webpack = require('webpack')
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')

... 
 plugins: [

    new webpack.DllReferencePlugin({
      // 都是绝对路径
      context: resolveApp('./'),    // 相对于manifest如何查找到dll_react.js文件                    
      manifest: resolveApp('./dll/react.manifest.json') // 查找 manifest.json 文件
    }),

    new AddAssetHtmlPlugin({    // 引入静态资源之中
      outputPath: 'js',         // 需要手动更改, html中也是(小问题)
      filepath: resolveApp('./dll/dll_react.js')
    })
  ]
<script src="js/dll_react.js"></script>
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App.jsx'

ReactDOM.render(<App />, document.getElementById('app'))

CSS 抽离 与压缩

在production 模式下进行 css 抽离 和压缩, 开发模式下不需要。

CssMinimizerWebpackPlugin        |    "css-minimizer-webpack-plugin": "^3.0.2",

        CssMinimizerWebpackPlugin.loader 搭配 css-loader 使用

MiniCssExtractPlugin        |    "mini-css-extract-plugin": "^2.2.0",

npm install --save-dev mini-css-extract-plugin
$ npm install css-minimizer-webpack-plugin --save-dev
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin')

module.exports = {
    ...
    plugins:[
        new MiniCssExtractPlugin({
            filename:'css/[name].[hash:6].css'
        })
    ]
    ...
    module:{
        rules:[
            {
                test:/\.css$/i,
                use: [MiniCssExtractPlugin.loader, 'css-loader']
                // 开发模式下用的style-loader, 如果分开模式打包, 可以通过函数来判断时开发还是生产模式, 选择相对的loader。
                use:[
                    isProduction ? MiniCssExtractPlugin.loader : style-loader,
                    'css-loader'
                ]
            }
        ]
    },
    optimization:{
        minimizer:[
            new CssMinimizerWebpackPlugin()
        ]
    }
    
}

TerserPlugin 压缩 JS

GitHub - terser/terser: 🗜 JavaScript parser, mangler and compressor toolkit for ES6+

TerserWebpackPlugin | webpack

基于teser, teser 是一个工具,用于优化JavaScript。

webpack5 内置,不需要安装, webpack4 需要安装|"terser": "^5.7.1",

主要有两个功能 mangler/compressor
 

terser --compress --input.js    // compress options

terser -- mangler --input.js    // minify options
const TerserPlugin = require("terser-webpack-plugin")

module.exports = {  
  mode:"production",
  optimization: {
    minimize: true,    // 需要为true, terser plugin 才生效
    minimizer: [
      new TerserPlugin({
        extractComments: false
      })
    ]
  }
  
}

scope hoisting  作用域提升

ModuleConcatenationPlugin,基于 ESModules 规范

将合适的数据打包入同一个层级,方便查找

production 模式下该功能默认开启。 

//const webpack = require('webpack')

module.exports = {
  mode: 'production',
  plugins: [
   // new webpack.optimize.ModuleConcatenationPlugin()
  ]
}

tree shaking

tree shaking 解决了:去除无用代码

核心原理: 基于ESModules的静态语法分析

webpack 中两种常见的tree shaking 方式:usedExports |sideEffects

usedExports 配置

?production模式下默认产出的就是tree shaking 后的结果吗?

usedExports:true  // 标记未使用的代码

terserPlugin // 去除未使用的代码, 而 terser plugin 要搭配 minimize:true 使用

const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  mode: 'development',
  devtool: false,
  optimization: {
    usedExports: true,    // 标记未使用
    minimize: true,
    minimizer: [
      new TerserPlugin({
        extractComments: false,
      }),
    ]
  }

}

sideEfeects 配置

// package.json

 "sideEffects": [
    "./src/title.js"            // 单独保留副作用
  ],

 "sideEffects": true | false    // 全部保留 | 去除
 rules: [
      {
        test: /\.css$/,
        use: [
          ...
        ],
        sideEffects: true,    // 对于css 的副作用, 推荐在webpack配置文件中设置
      },
]

Css-TreeShaking

purgecss-webpack-plugin  |    "purgecss-webpack-plugin": "^4.0.3",

glob | "glob": "^7.1.7",

const PurgeCSSPlugin = require('purgecss-webpack-plugin')
const resolveApp = require('./paths')
const glob = require('glob')


module.exports = {
  mode: 'development',
  devtool: false,
  plugins: [
    new PurgeCSSPlugin({
      paths: glob.sync(`${resolveApp('./src')}/**/*`, { nodir: true }), //绝对路径|不包括文件夹
      safelist: function () {
        return {
          standard: ['body', 'html', 'ef']    // 不会被摇掉的选择器
        }
      }
    })
  ]
}

资源压缩

   上线前的压缩输出

"compression-webpack-plugin": "^8.0.1",

const CompressionPlugin = require("compression-webpack-plugin")

module.exports = {
  mode: 'production',

  plugins: [
    new CompressionPlugin({
      test: /\.(css|js)$/,// 压缩什么类型的文件
      minRatio: 0.8,      // 压缩比例超过20% 则输出
      threshold: 0,       // 文件大于多少 则 压缩
      algorithm: 'gzip'   // 指定算法
    })
  ]
}

InlineChunkHtmlPlugin

配合htmlWebpackPlugin 使用

    "inline-chunk-html-plugin": "^1.1.1",
    "html-webpack-plugin": "^5.3.2",

将通过<script></script>注入html的文件,通过inline方式注入,从而不需要进行一次请求。 

const InlineChunkHtmlPlugin = require('inline-chunk-html-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    mode: "production",
    plugins:[
        new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime.*\.js/])
    ]
}

webpack 打包 library|库

speed-measure-webpack-plugin

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值