美文网首页
你必须要会的webpack

你必须要会的webpack

作者: 贰玖是只猫 | 来源:发表于2021-04-22 08:43 被阅读0次

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

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

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

  • ES Modules 存在环境兼容问题。虽然大多数浏览器目前都已经支持es module 语法,但是由于我们无法限定用户的浏览器使用方式,因此还需要我们去做此问题的兼容。
  • 模块文件过多导致的网络请求频繁问题
  • 不止js存在模块化,所有的前端资源都需要模块化
    之前我们说过webpack并不是一个自动化构建工具,而是一个模块化打包工具,就是因为这一点。

目的

  • 新特性代码编译
  • 模块化 js 打包
  • 支持不同类型的资源模块

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提供了三种模式,具体使用为:

  • yarn webpack --mode development
  • yarn webpack --mode none
  • yarn webpack --mode production
    node什么都不提供,development会在代码里提供调试辅助
    同样我们也可以直接添加在配置文件中:
const path = require("path")

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

进阶使用

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

  • yarn add css-loader style-loader
    css-loader的作用是将css打包,但是打包后是生成了一个模块,并没有加载到页面上,所以需要我们再引入style-loader这个加载器,使其能够渲染到页面上。
    配置:
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的时候需遵循从后往前执行的逻辑

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

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

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

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

  • loader 专注实现资源模块加载
  • plugin 解决其他自动化工作

常见插件

  • clean-webpack-plugin
    用来打包前清理打包后的目录,避免脏数据
  • html-webpack-plugin
    用来打包html文件,模板渲染html文件并打包
  • copy-webpack-plugin
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
  • eval
    eval 模式下是不生成source-map的,但是他会在打包后的bundle.js里保留一个映射关系
    # sourceURL=webpack://use-webpack/./src/index.js?"
    因此此模式是最快的,但是无法定位具体的代码行数
  • eval-source-map
    生成sourcemap 报错可定位到行、列
  • cheap-eval-source-map
    报错只可定位到行,而且是编译过后的
  • cheap-module-source-map
    报错定位到行,是源代码的行
  • inline-source-map
    采用给文件中以dataurl的格式附加映射的方式,提供sourcemap,会使文件体积变大
  • hidden-source-map
    在开发工具中看不到映射关系,通常用来接第三方包
  • nosources-source-map
    可在开发工具中看到报错的行列信息,但是不可以看到源代码

推荐: 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
}
  • 一个环境对应一个配置文件
    常见是创建三个文件: 公共配置文件、dev配置文件、prod配置文件
// 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 //压缩清理未引用代码
  }
}
  • 代码分割
    为了解决打包过程中可能存在的打包后文件体积过大,或者文件过多过碎的情况,可以利用webpack的代码分割功能,避免这一情况或者说将这种情况作出优化。
  • 多入口打包
    常用于多页面应用
  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模块的形式进行动态导入。我们不需要配置代码,只需要在代码中按照逻辑按需导入即可
    形如
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压缩的需求

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

文件名 Hash

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

  • hash // 所有文件用同一个hash值,一个文件更新,所有hash值都变更
  • chunkhash // 更新同一个chunk的hash,引用的文件对应hash也变更
  • contenthash // 只更新变更的文件和引用的文件的hash

相关文章

  • 你必须要会的webpack

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

  • webpack独立打包与缓存处理

    前言 先前写了一篇webpack入门的文章《webpack入门必知必会》,简单介绍了webpack拆分、打包、压缩...

  • 心不唤物,物不至

    心灵会塑造那个现实,也会驱动那个现实。 所以有些东西该坚持的,那就必须要坚持,你所认为的人生的无奈和屈服,真的就必...

  • css超出两行省略号,超出一行省略号

    超出一行省略: 超出2行省略 注:两行注释必须要!因为 webpack打包后-webkit-box-orient会...

  • webpack eslint loader 所需依赖

    eslint :必不可收 eslint-loader : webpack的eslint loader eslint...

  • 开始使用webpack

    作者:叶茂;标签: webpack webpack配置文件 webpack配置文件是使用webpack的核心,会配...

  • 考研很难,不代表你不可以。

    经历了一年的考研告诉你: 1.关于要不要考研。 答:要,必须要,既然你能提出这样的问题告诉你必须要考,有种考研就必...

  • 七行表单|好计划从今天开始

    大家一听到讲如何作计划,也许会觉得非常枯燥,但是,我要告诉你:计划,就是为了让你把今天过好。战略必须要结合实际,必...

  • 细心里透着你的未来

    细心里透着你的未来 天下难事必作于易,天下大事必作于细。成大事者,必细心为之。天下的大事要做成功,必须要从细处着手...

  • 想要成为富人,做好这个开始,你也能实现梦想

    天下难事,必作于易;天下大事,必作于细。 要想掌握金钱的秘密和规律,你自己必须要有“想要变得富有”这个愿望。 下面...

网友评论

      本文标题:你必须要会的webpack

      本文链接:https://www.haomeiwen.com/subject/vijakltx.html