# WebPack

基本任务

  快速搭建一个 webpack 环境

  查看 ruanyf 的 webpack-demos (opens new window)

官方简介:

webpack (opens new window) 是一个现代 JavaScript 应用程序的静态资源模块化 打包器(module bundler)。

打包工具解析
  • JavaScript

将 ES6+语法转换成 ES5(某些浏览器不支持)

将 CJS、ESM 模块化代码转换为浏览器可以执行代码

其他 vue、CSS 预处理器代码处理

安装:

npm i webpack webpack-cli -D
webpack 打包方式
# 使用局部的 webpack ✨
./node_modules/.bin/webpack
npx webpack
# 附带参数
npx webpack --entry ./src/main.js
npx webpack --output-path ./build
npx webpack --output-filenme bundle.js
npx webpack --mode production

# 使用全局的 webpack
webpack

或者:

显然 webpack 的使用是需要 node 环境的

{
  "scripts": {
    "build": "webpack"
  }
}

webpack 项目默认结构

WebpackProject
├─ dist
|   └─ main.js
├─ node_modules
├─ src
|   └─ index.js
├─ index.html
├─ package-lock.json
├─ package.json
└─ webpack.config.js

  下面介绍的是关于 webpack 手动配置的相关内容

webpack 环境搭建
  • webpack.config.js
const path = require('path')
const HtmlPlugin = require('html-webpack-plugin')

module.exports = {
  // 基本配置
  mode: 'development',
  entry: './src/index.js',
  output: {
    path: path.join(__dirname, './dist'),
    filename: 'main.js'
  },

  // 辅助插件
  // webpack-dev-server
  devServer: {
    open: true,
    port: 80,
    host: '127.0.0.1'
  },
  // html-webpack-plugin
  plugins: [
    new HtmlPlugin({
      template: './index.html' // html模板
    })
  ],
  // ts-loader
  module: {
    rules: [
      {
        test: /\.ts$/,
        loader: 'ts-loader'
      }
    ]
  }
}

# 一、基本配置

# 1、mode 模式

运行模式(mode):

默认为 production,所以开发阶段必须配置。

module.exports = {
  mode: 'development' // 这个设置会修改 process.env.NODE_ENV 的值 和 其他配置
}
  • development:打包速度快
  • production:会对生成的文件进行代码压缩性能优化
环境区分

① 方式 1

WebpackProject
├─ config
| ├─ webpack.dev.config.js # 开发配置
| └─ webpack.prod.config.js # 生产配置
├─ package.json
└─ README.md
  • package.json
{
  "scripts": {
    "build": "webpack --config ./config/webpack.prod.config.js",
    "serve": "webpack serve --config ./config/webpack.dev.config.js"
  }
}

② 方式 2

WebpackProject
├─ config
| ├─ webpack.comm.config.js # 公共配置
| ├─ webpack.dev.config.js # 开发配置
| └─ webpack.prod.config.js # 生产配置
├─ package.json
└─ README.md
npm install webpack-merge -D
  • webpack.dev.config.js
const { merge } = require("webpack-merge")
const commonConfig = require("./webpack.comm.config")

module.exports = merge(commonConfig, {
  mode: "development"devServer: {
    port: 8888
  }
})
  • webpack.prod.config.js
const { merge } = require("webpack-merge")
const commonConfig = require("./webpack.comm.config")

module.exports = merge(commonConfig, {
  mode: "production"output: {
    clean: true
  }
})

③ 方式 3

  上面的两种方式实际上和 vue-cli 脚手架 2.0 时构建项目的方式基本类似,不过后来使用了 vue-service-cli 这个库来实现全部过程

# 2、entry/output

项目打包的入口(entry)、出口(output):

在 webpack 4.x 和 5.x 的版本中,默认的打包路径如下

const path = require('path')

module.exports = {
  //下面是默认的配置
  content: "" // 默认为根路径
  entry: path.join(__dirname, './src/index.js'), // 相对于content
  output: {
    path: path.join(__dirname, './dist'),
    filename: 'bundle.js'
  }
}

# 3、本地服务

提示

  webpack 虽然作为一个打包工具,但是它还可以利用插件实现实时更新,而不用反复使用 npm run build 来查看结果

先安装包 webpack-dev-server

此插件类似于 node.js 中的 nodemon 工具,可以自动帮我们完成编译。

npm i webpack-dev-server -D

然后配置 :

  • webpack.config.js
module.exports = {
  devServer: {
    port: 80,
    host: '127.0.0.1',
    open: true, //首次打包成功时,自动打开浏览器
    compress: true // 静态文件gzip压缩
  }
}
关于 模块热更新 HMR

  webpack 中是有相关配置的

module.exports = {
  devServer: {
    hot: true // 模块热更新HMR(默认开启)
  }
}

  但是一般情况下,是不需要我们手动配置 HMR 的,大多都是默认开启或者默认支持 HMR 的。

比如 vue 开发中,vue-loader 就支持 vue 组件的 HMR,并提供了开箱即用的解决方案

devServer 重要配置
  • historyApiFallback

https://www.cnblogs.com/Lencamo/p/16860823.html

  • proxy

http://localhost:8080/notes-library/frontend/node_Note/Node%E5%AD%A6%E4%B9%A0/%E8%B7%A8%E5%9F%9F.html#_3%E3%80%81vue-cli-%E7%9A%84%E4%BD%BF%E7%94%A8

  • package.json
{
  "scripts": {
    "serve": "webpack serve" // 编译文件不会输出到output目录,而是保留在内存中
  }
}
注意事项:实时预览 ✍

  webpack-dev-server 插件除了可以自动打包外,还可以通过 http://localhost:8080 查看打包效果。

  但是我们已经知道的是实际上 webpack-dev-server 使用的是一个库叫做 memfs,它是将编译代码放在内存中,然后提供一个本地服务供我们使用。

  但是由于内存中和本地项目中的 bundle.js 文件并不在同一位置,所以 bundle.js 文件不是实时的

<!-- 原来:使用本地项目中的bundle.js -->
<script src="../dist/bundle.js"></script>

<!-- 现在: 直接使用内存中的bundle.js(保持bundle.js实时更新)-->
<script src="/bundle.js"></script>

# 二、打包辅助插件

plugins 和 loader 各自的作用

loader 可以先解析.css 文件,而 plugins 可以将所有的.css 文件融合在 style.css 中,而不是直接塞入 html 中

# 1、plugins 插件

官方地址:Plugin (opens new window)

  上面手动解决通过 http://localhost:8080 直接实时预览项目效果显然不是长久之策。除了之外,我们还可以通过插件的方式:

① html-webpack-plugin 插件

先安装包 html-webpack-plugin

npm i html-webpack-plugin -D

然后配置:

  • webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      title: "xxxx" // 可以设置一个标题
      template: './index.html' // 使用自定义模板(相对于项目根目录)
      filename: './index.html' // 设置该模板打包后文件路径和文件名(相对于output目录)
    })
  ]
}

  html-webpack-plugin 插件会根据配置自动在 output 目录和项目根目录中生成 index.html 文件,并自动导入 bundle.js 文件

npm run build
<script src="bundle.js"></script>

② clean-webpack-plugin

  每次打包前,原有的 dist 文件并不会自动删除,所有下次打包时可能会有残留文件。这个问题我们可以用下面这个插件解决:

提示

在最新的 webpack 官网 plugin (opens new window) 介绍中并没有 clean-webpack-plugin,转而可以直接使用 output.clean (opens new window) 进行配置

module.exports = {
  //...
  output: {
    clean: true // 在生成文件之前清空 output 目录
  }
}

安装

npm install clean-webpack-plugin -D

配置

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

module.exports = {
  plugins: [new CleanWebpackPlugin()]
}
webpack 内置插件

  默认情况下,在 webpack 中我们可以使用下面这个全局变量

console.log(process.env.NODE_ENV)

  除此之外,我们还可以自定义全局变量(以 vue-cli 中的某个效果为例:)

<!-- ejs模板 -->
<link rel="ico" href="<%= BASE_URL %>favicon.ico" />
const { DefinePlugin } = require('webpack')

module.exports = {
  plugins: [
    new DefinePlugin({
      BASE_URL: "'./'"
      // 其他自定义全局变量
    })
  ]
}

# 2、loader 插件

官方地址:Loader (opens new window)

  实际开发中,webpack 默认只能打包处理.js 文件,而无法处理.css、.less 等文件。

先安装相关解析插件:

# css文件
npm i style-loader@3.0.0 css-loader@5.2.6 -D

# less文件
npm i less@4.1.1 less-loader@10.0.1 -D

# 图片文件(webpack5之前)
npm i file-loader@6.2.0 url-loader@4.1.1 -D

# js高级语法🎈(ES6+、TS、JSX)
npm i babel-loader @babel/core @babel/preset-env -D

然后配置:

  • webpack.config.js

limit 限制的是图片大小小于等于多少 B 字节,limit=1024 表示图片小于等于 1kB 才会转换为 base64 字符串

module.exports = {
  module: {
    rules: [
      { test: /\.css$/, use: ['style-loader', 'css-loader'] },
      { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] },
      // { test: /\.(jpe?g|png|gif|svg)$/, use: 'url-loader?limit=8192' },
      {
        test: /\.(jpe?g|png|gif|svg)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192
            }
          }
        ]
      },
      {
        test: /\.m?js$/,
        exclude: /(node_modules|bower_components)/, // 排除npm包文件(注意与 webpack-node-externals包的区别)
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }
}
postcss 配置说明

  use 中的多个 loader 的使用顺序是从后往前

module.exports = {
  module: {
    rules: [
      {
        test: /\.sass$/,
        use: [
          { loader: 'style-loader' },
          {
            loader: 'css-loader',
            options: {
              modules: true
            }
          },
          { loader: 'sass-loader' },
          {
            loader: 'postcss-loader'
            options: {
              postcssOptions: {
                plugins: [
                  require('autoprefixer') // 可以自动为css添加浏览器前缀
                ]
              }
            }
          }
        ]
      }
    ]
  }
}

  除此此外,我们还可以将 postcss 配置单独抽离出来:

  • postcss 插件
# 1、CSS新特性
npm install autoprefixer -D
# 或者
npm install postcss-preset-env -D

# 2、px -> rem/vm

  • postcss.config.js
module.exports = {
  plugins: [
    // 'autoprefixer'
    'postcss-preset-env' // 这个插件也可以,而且根据强大
  ]
}
资源模型类型

  在 webpack5 之后,webpack 对于图片资源的处理,已经不需要手动下载 loader 了,转而使用资源模块类型(Asset Modules types)。

Asset Modules (opens new window)

module.exports = {
  module: {
    rules: [
      {
        test: /\.(jpe?g|png|gif|svg)$/,
        type: 'asset'
      }
    ]
  }
}

  资源模块类型通过添加 4 种新模块类型来替换原先对应的 loader,下面简单介绍两个:

  • asset/resource:

作用:和 file-loader 一样,webpack 打包图片,并放在 dist 文件夹根目录(图片名改变了)

缺点:大量图片会增加网络请求

  • asset/inline:

作用:和 url-loader 一样,将图片转换为 base64 行内编码使用,并将其放在打包的 js 文件中

缺点:大量图片会造成 js 代码下载和解析/执行时间过长

① 打包优化 1:是否使用 url-loader

module.exports = {
  module: {
    rules: [
      {
        test: /\.(jpe?g|png|gif|svg)$/,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 4 * 1024 // 4kb(默认8kb) // 使用asset/inline时,文件大小限制
          }
        }
      }
    ]
  }
}

② 打包优化 2:自定义输出文件名

module.exports = {
  module: {
    rules: [
      {
        test: /\.(jpe?g|png|gif|svg)$/,
        type: 'asset',
        generator: {
          filename: 'static/[name]_[hash:8][ext]' // 使用asset/resource时,文件名设置
        }
      }
    ]
  }
}
babel 配置说明

  现在我们已经知道了 babel 可以将复杂的、特殊的 js 文件转换为浏览器可识别的 js 文件。

module.exports = {
  module: {
    rules: [
      {
        test: /\.m?js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'] // 预设
            // plugins: [
            //   '@babel/plugin-transform-arrow-functions',
            //   '@babel/plugin-transform-block-scoping'
            // ] // 手动添加插件
          }
        }
      }
    ]
  }
}

  和 postcss 一样,我们也可以将 babel 配置单独抽离出来:

  • babel.config.js
module.exports = {
  presets: ['@babel/preset-env'] // 预设
}

常见的预设有 env、react、typescript,我们这里仅仅使用了 env 预设 @babel/preset-env 而已

拓展:webpack 处理 vue 代码

① 代码编写

npm install vue
  • /src/components/Hello.vue
<template>
  <p>{{ title }}</p>
</template>

<script>
export default {
  data() {
    return {
      title: '你好呀!'
    }
  }
}
</script>

<style>
p {
  color: red;
}
</style>
  • /src/main.js
import { createApp } from 'vue'
import Hello from './components/Hello.vue'

createApp(Hello).mount('#app')
  • index.html
<body>
  <div id="app"></div>
</body>

② webpack 配置

npm install vue-loader -D
const { VueLoaderPlugin } = require('vue-loader/dist/index')
module.exports = {
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },
  plugins: [new VueLoaderPlugin()]
}

# 三、其他

# 1、dist 优化

# ① 模块化

  普通方式打包生成的文件全部直接放在了 dist 文件夹下,没有进行分类,显得格外混乱。

  • webpack.config.js 配置
module.exports = {
  // 1、js文件处理
  output: {
    path: path.join(__dirname, './dist'),
    filename: 'js/bundle.js'
  },

  // 2、图片文件处理
  module: {
    // &outputPath指定存放位置
    rules: [{ test: /\.jpg|png|gif$/, use: 'url-loader?limit=1024&outputPath=images' }]
  }
}

# ② 自动化

  防止打包时,旧的 dist 内文件对新生成的 dist 内文件造成影响。所以自动删除旧的 dist 文件夹是非常有必要的:

安装包 clean-webpack-plugin

npm i clean-webpack-plugin@3.0.0 -D

然后配置

  • webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

const cleanPlugin = new CleanWebpackPlugin()

module.exports = {
  plugins: [cleanPlugin]
}

# 2、source-map 优化

  默认情况下,运行项目报错时,控制台报错的提示行号(bundle.js)与源代码的对应行号是不一致的。

原因:

在浏览器中运行的代码是打包压缩后的代码(代码经过转换)

  那如果我们在开发阶段想要浏览器正确显示报错的位置该怎么做?

开启 source-map

  source-map 可以将已转换的代码,映射到原始的源文件。使浏览器可以重构原始源,并在调试器中显示重建的原始源

  • webpack.config.js

Devtool (opens new window)

module.exports = {
  devtool: 'false' // 不使用source-map

  // 1、production模式
  devtool: 'none' // 默认值(不生成source-map文件)

  devtool: 'source-map' // 会生成完整的 source_map 文件(浏览器会根据其中的 // # sourceMappingURL = bundle.js.map 找到并解析source-map文件)
  devtool: 'nosources-source-map' // 只显示行号不暴露源码

  // 2、development模式
  devtool: 'eval' // 默认值(不生成source-map文件)

  devtool: 'eval-source-map' // source-map是以DataUrl添加到bundle.js内eval函数的后面
}

TIP

  开发阶段为了方便调试,可以手动关闭 Source Map。但项目发布时,出于安全角度(防止源码暴露),建议使用默认的none或者使用nosources-source-map(只显示行号不暴露源码)。

# 3、webpack 分包

  import 函数可以让 webpack 对导入文件进行分包处理(首屏渲染速度 🤔)

问题描述

  默认情况下,webpack 在打包时会将组件模块打包到一起(比如 app.js 文件中)。随着项目的不断庞大,app.js 文件的内容过大,会造成首屏的渲染速度变慢。

  我们可以把一些不需要立即使用的组件从 app.js 文件中拆分出来,形成单独的 chunk.js 代码块,这些 chunk.js 会在需要时从服务器加载并运行。

常见的 vue-cli 打包时就默认包项目核心代码 app.js 和第三方库 chunk.js 进行了分离。

// 路由组件分包
const Home = () => import('@/components/Home.vue')

const Home = () => import(/* webpackChunkName: "home" */ '@/components/Home.vue')

# 4、路径优化

  除了上面的细节,我们还可以对路径进行优化:

官方描述:resolve (opens new window)

配置如下:

  • webpack-config.js

@表示正处于项目根目录的 src 文件夹下

module.exports = {
  // 内部实际是通过enhanced-resollve包实现的
  resolve: {
    extensions: ['.js', '.json', '.jsx', '.ts', '.vue'], // 让模块化导入可以省略后缀名(默认值为:['.js', '.json', '.wasm'])
    alias: {
      '@': path.join(__dirname, './src/')
    }
  }
}

使用示例:

// 原来:
import './css/index.css'
import './css/index.less'

// 现在:
import '@/css/index.css'
import '@/css/index.less'
更新于 : 8/7/2024, 2:16:31 PM