你必须要会的webpack

2021-04-22  本文已影响0人  贰玖是只猫

webpack是我们目前最流行的模块化打包工具,即使vue3.0推出的vite,也很难动摇webpack本身的大市场份额。

模块化打包工具存在的意义

模块化打包工具为了解决模块化过程中存在的问题而出现的。

目的

Webpack 初识

webpack 作为一个模块打包器(Module bundler)可以解决上述的问题。它利用模块加载器(Loader)对模块化的代码编译,并且有代码拆分的能力避免打包后的文件过大的问题产生。

快速上手

首先我们初始化一个示例项目

//目录
├── index.html
├── package.json
├── src
│   ├── index.js
│   └── plugins.js
└── yarn.lock

// plugins.js
export const createP = (text) => {
    let eleP = document.createElement("p")
    let temNode = document.createTextNode(text)
    eleP.appendChild(temNode)
    return eleP;
}
// index.js
import {createP} from "./plugins.js"
const crp = createP("hello world")
document.body.appendChild(crp)
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="src/index.js" type="module"></script>
</body>

</html>

安装 webpack & webpack-cli
执行 yarn webpack
会生成一个

├── dist
│   └── main.js

我们引入的时候只需要将目录修改为此目录并且删掉type = module即可
<script src="dist/main.js"></script>
在webpack 4.0之后,会给用户提供一些默认配置,我们刚才执行的时候可以看出入口文件以及输出路径都已经默认配置好了,如果我们需要调整或者需要更多的自定义配置需要我们再根目录创建一个 webpack.config.js

const path = require("path")

module.exports = {
    entry: "./src/index.js", //使用相对路径,输入路径
    output: { 
        filename: "bundle.js",  //输出文件名
        path: path.join(__dirname, 'dist') //输出路径
    }
}

倘若使用过以上代码打包过,会发现执行过程中会有一个警告

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

提示我们的是缺号mode属性,默认使用了production属性,他的作用是在生产打包的时候默认使用一部分优化的方式。比如文件压缩等。但是在我们开发的时候很显然是不需要的,反而是需要一部分帮我们排查开发过程中bug的工具。
在这里webpack提供了三种模式,具体使用为:

const path = require("path")

module.exports = {
    mode: "development",
    entry: "./src/index.js", //使用相对路径,输入路径
    output: { 
        filename: "bundle.js",  //输出文件名
        path: path.join(__dirname, 'dist') //输出路径
    }
}

进阶使用

之前我们提到过webpack可以打包所有的前端资源,那么我们从打包css模块入手

const path = require("path")

module.exports = {
    mode: "none",
    entry: "./src/index.js", //使用相对路径,输入路径
    output: { 
        filename: "bundle.js",  //输出文件名
        path: path.join(__dirname, 'dist') //输出路径
    },
    module: {
        rules: [
            {
                test: /.css$/,
                use: [
                    "style-loader", //从后往前执行
                    "css-loader"
                ]
            }
        ]
    }
} 

注意:使用多个loader的时候需遵循从后往前执行的逻辑

rules: [
            {
                test: /.png$/,
                use: {
                    loader: "url-loader",
                    options: {
                        limit: 10 * 1024 //10KB
                    }
                }
            }
        ]

10KB以内会使用url-loader,将图片转成data64。此用法需要安装file-loader,超过10KB后会使用file-loader

      rules: [
            {
                test: /.js$/,
                use: {
                    loader: "babel-loader",
                    options: {
                        presets: ["@babel/preset-env"]
                    }
                }
            },
]

插件执行完之后会返回一个js形式的字符串,如果自行开发loader的话需要return这么一个js形式的字符串

常见插件

const {CleanWebpackPlugin} = require("clean-webpack-plugin")
const HtmlWebpackPlugin = require("html-webpack-plugin")
const CopyWebpackPlugin = require("copy-webpack-plugin")
...
plugins: [
       new CleanWebpackPlugin(), 
       new HtmlWebpackPlugin({  //可以new多个分别指向不同的template文件
           title: "your html title",  //对象里的属性可在页面中模板渲染
           meta: {
               viewport: "width=device-width"
           }
           template: './index.html'
       }),
       new CopyWebpackPlugin({
           //"public/**"
           "public"
       })
   ] 

自定义插件

class MyPlugin {
   apply(complier) { // 插件要求 apply 方法
       
       complier.hooks.emit.tap("MyPlugin", compilation => {
           // compilation 是需要执行插件的上下文
           for (const name in compilation.assets) { //遍历所有文件
               // name ===> 文件名
               // compilation.assets[name].source() ===> 文件内容
               if (name.endsWith(".js")) {
                    const contents = compilation.assets[name].source()
                    const withoutComment = contents.replace(/\/\*[a-z 0-9*]+\*\//g, "")
                    compilation.assets[name] = {
                        source: () => withoutComment,
                        size: () => withoutComment.length
                    }
               }
           }
       })
   }
}

Webpack-dev-server

webpack-dev-server为我们开发的时候提供服务器支持,并且还有强大的api代理功能,处理跨域问题。

    devServer: {
        contentBase: "./public", //静态资源
        proxy: {
            "/api": {
                target: "http://xxx.xx.xx", //http://localhost:8080/api ==> http://xxx.xx.xx/api
                pathRewrite: {
                    "^/api": "" //http://localhost:8080/api ==> http://xxx.xx.xx
                },
                changeOrigin: true //不能使用localhost:8080 作为主机名请求 xxx.xx.xx
            }
        }
    },

Source Map

source map 是对你编译过后的代码保留映射关系,使得我们开发过程中debug的时候,能够按照我们想象的逐行调试。
而webpack中提供了12种不同的sourcemap模式,每种方式生成sourcemap的效率和效果都不同。


image.png

推荐: cheap-module-source-map(开发) none(生产)

模块热更新(HMR)

//开头引入
const webpack = require("webpack")

...

devServer: {
      hot: true,
     // hotOnly: true  可避免因热更新导致报错被刷新
 },

...

plugins: [
        new webpack.HotModuleReplacementPlugin(),
]

对于css模块,由于有style-loader的存在会默认热更新。
由于对于js不同的模块有不同的业务逻辑,因此就需要去主动调用webpack提供的HMR api去处理因js模块变化导致的页面刷新。

module.hot.accept("./plugins", () => {
//处理逻辑
})

Mode

我们开发和生产环境下对webpack的配置是不同的,因此需要我们去在不同的阶段执行不同的配置。通常有如下两种配置方法:

module.exports = (env, argv) => {
    const config = {
        ...
        // 这里用来存放两个环境的公用配置,可以默认为dev环境的配置
    }

    if (env === "production") {
        // 这里用来存放prod配置
        config.mode = "production"
        config.devtool = false
        config.plugins = [
            ...config.plugins, // 插件叠加 新增通常包含打包常用插件
            new CleanWebpackPlugin(),
            new CopyWebpackPlugin(["public"])
        ]
    }

    return config
}
// webpack.common.js
公共配置文件

// webpack.prod.js
const common = require("./webpack.commom")
module.exports = merge(common, {
mode: "production",
plugins: [
  // production 配置
]
})

// webpack.dev.js
与prod类似,只需要merge dev环境的配置即可

使用 yarn webpack --mode production 生产环境打包就会注入环境变量

我们还可以通过 definePath 这个插件去区分不同环境下的环境变量对象

        new webpack.DefinePlugin({
            BASE_URL: JSON.stringify("https://api.xx.xx")
        })

直接在插件中使用 BASE_URL即可,这里需要强调的一点是value值是一个js代码块。

Tree Shaking

tree shaking 是webpack 的一种清理冗余代码、无效代码的功能。它会在生产模式下自动开启。

module.exports = {
  optimization: {
    userExports: true, //对于未引用的不导出
    minimize: true //压缩清理未引用代码
  }
}
  const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'none',
  entry: {
    index: './src/entry1.js',
    album: './src/entry2.js'
  },
optimization: {
  splitChunks: {
    chunks: "all"   //将多入口的所有公共模块分离打包
  }
},
  output: {
    filename: '[name].bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'Multi Entry',
      template: './src/index1.html',
      filename: 'index1.html',
      chunks: ['index1']
    }),
    new HtmlWebpackPlugin({
      title: 'Multi Entry',
      template: './src/index2.html',
      filename: 'index2.html',
      chunks: ['index2']
    })
  ]
}
import("./posts/posts").then(module => {
 
})

MiniCssExtractPlugin

我们以上说的都是对js模块的打包优化,我们也可以通过MiniCssExtractPlugin这个插件对css进行打包优化处理。
我们之前使用的”style-loader“是将样式通过style 标签的形式导入。
yarn add mini-css-extract-plugin

const MiniCssExtractPlugin = require("mini-css-extract-plugin")
...
module: {
  rules: [
    {  
       test: /\.css$/,
       use: [
        MiniCssExtractPlugin.loader,
        "css-loader"
       ]
    }
  ]
}
...
plugins: [
   new MiniCssExtractPlugin()
]

然而我们这个时候打包的话会发现只是将所有的css文件打包到了一个文件下,并没有压缩,那么这个时候就用到了我们的下面这个插件 - OptimizeCssAssetsWebpack

OptimizeCssAssetsWebpack

yarn add optimize-css-assets-webpack
插件有两种使用方式:
第一种:

const OptimizeCssAssetsWebpack = require("optimize-css-assets-webpack")
...
plugins: [
   new MiniCssExtractPlugin(),
   new OptimizeCssAssetsWebpack()
]

第二种:

  optimization: {
    minimizer: [
      new OptimizeCssAssetsWebpackPlugin()
    ]
  },

使用第二种方式的时候要注意,因为本身有默认的js压缩配置,但我们使用这种方式的时候,css可以正常压缩,反而js不能正常压缩了。是因为我们的当前配置覆盖了默认配置,需要我们去使用一个新的插件去满足js压缩的需求

const TerserWebpackPlugin = require('terser-webpack-plugin')
...
  optimization: {
    minimizer: [
      new TerserWebpackPlugin(),
      new OptimizeCssAssetsWebpackPlugin()
    ]

文件名 Hash

由于当我们将项目部署到线上后,无论是我们再远端服务器还是浏览器都经常存在缓存的情况。当我们二次部署后,由于缓存尚未达到过期时间,相同的文件我们优先读缓存,在我们不去强制刷新的情况下,往往读到的不是最新的文件,所以就需要我们去使用 Hash 标记我们的文件

上一篇 下一篇

猜你喜欢

热点阅读