webpack基础篇

介绍webpack的基础使用,包含开发模式和生产模式的常用配置。

Posted by jiangvv on May 7, 2025

webpack基础

下载webpack依赖包:

npm i webpack webpack-cli -D

一、配置文件

wbepack.config.js配置文件详解

作用:指示 webpack 干哪些活(当你运行 webpack 指令时, 会加载里面的配置)

注意:

  • 我们创建的src文件是我们写项目的源代码,用的是ES6模块化。
  • ​所有构建工具都是基于node.js平台运行的,所以wbepack.config.js配置文件也是运行在nodejs中的,默认用的是commonjs。
const path = require('path'); //nodejs核心模块,专门用来处理路径问题

module.exports = {
  // 定义入口文件
  entry: './src/index.js', //相对路径

  // 定义输出
  output: {
    // __dirname:__dirname是nodejs的变量,代表当前文件所在的文件夹的绝对路径,__filename: 总是返回当前文件的绝对路径,与__dirname的区别是这个返回的绝对路径包含该文件本身。
    //path:文件输出目录,必须是绝对路径
    //path:文件输出目录,必须是绝对路径
    //path.resolve:返回一个绝对路径
    path: path.resolve(__dirname, 'dist'),
    // 输出文件名为 main.js
    filename: 'main.js',
    clean: true, // 自动将上次打包目录资源清空
  },

  // 定义模式:开发模式或生产模式,开发模式下仅会编译ES6模块化语法,不会编译ES6的箭头函数等,而生产模式下还会做一些压缩和优化。
  mode: 'development',

  // 定义模块处理规则,loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块
  module: {
    rules: [
      //loader的配置
      {
        test: /\.css$/, // 处理 CSS 文件,只检测.css文件
        use: [
          //执行顺序,从右往左,从下往上,类似链式调用,只会输出最终结果
          'style-loader', 
          'css-loader'
        ],
        options: {
          // 可配置的参数,例如是否开启 CSS 模块化等
           modules: true
        }
        //简写模式:
        //loader: "style-loader!css-loader?modules"
        //!感叹号是分割符,表示两个工具都参与处理。
        //?问号,其实跟url的问号一样,就是后面要跟参数的意思。
        //loader和use的区别是loader中只能写一个loader,比如loader:"xxxxx",而use中可以写多个loader

        //完整模式:
        //use: [
        //  { loader: 'style-loader' },
        //  {
        //    loader: 'css-loader',
        //    options: {
        //      modules: true
        //    }
        //  }

      },
      
      {
        test: /\.(png|svg|jpg|gif)$/, // 处理图片文件
        use: [
          'file-loader'
        ]
      }
    ]
  },

  // 定义插件
  plugins: [
    // 自动生成 HTML 文件并注入资源
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ]
};

二、处理样式资源

为什么要处理样式以及其他资源?

因为webpack仅仅能将js中的ES6模块化语法转化为commonjs语法,并将转化后的js文件输出在dist文件夹中,要是不对其他资源进行处理,那么dist文件夹中就只有js文件,没有CSS文件和图片以及其他资源了,因此,webpack要对所有的静态资源进行处理,最起码要将静态资源复制到dist文件夹中并改变js或者css或者html中的url为复制到dist文件夹后的url地址。

1.css-loader

主要负责解析CSS文件内部的依赖结构,并将CSS模块转化为js模块。

(1)解析CSS模块依赖关系

css-loader 的首要任务是深度解析 CSS 文件内部的依赖结构。

在 CSS 规范中,@import 规则允许引入外部 CSS 文件,而 url() 函数则用于引用诸如背景图片、字体文件等外部资源。css-loader 能够遍历 CSS 文件的语法树,精准识别这些依赖声明,并将被引用的外部 CSS 文件内容整合到当前处理的模块中。

例如,考虑一个具有多层嵌套 @import 的 CSS 结构:

@import "base.css";
@import "layout.css";
.container {
  background-image: url('bg-image.png');
  font-family: 'CustomFont', sans-serif;
}

其中 base.css 和 layout.css 可能又各自包含其他的依赖或样式规则。css-loader 会递归地解析这些文件,构建起一个完整的样式依赖图谱,确保所有相关的样式信息都被正确收集和处理。

(2)将 CSS 转换为 JavaScript 模块

在解析完依赖关系后,css-loader 会将整个 CSS 模块(包括自身及其所有依赖的样式)转换为 JavaScript 模块。这一转换过程并非简单的字符串拼接,而是遵循特定的模块规范,使得在 JavaScript 环境中能够像处理普通模块一样对待 CSS。具体而言,css-loader 会将 CSS 样式规则转换为 JavaScript 对象,其中每个样式规则的选择器作为对象的属性,对应的样式声明则作为属性值。例如,对于上述的 .container 规则,可能会被转换为类似以下的 JavaScript 对象形式:

{
  ".container": {
    "background-image": "url('bg-image.png')",
    "font-family": "'CustomFont', sans-serif"
  }
}

当设置 modules: true 时,css-loader 还会为 CSS 类名添加唯一的哈希值,以实现 CSS 模块化,避免全局样式冲突。这在大型项目中,多个组件或模块共享样式时尤为重要。

2.style-loader

主要负责创建<style>标签,并将css-loader转化后的CSS内容设置为标签的内容,之后将<style>标签插入到html页面的<head>标签内部。

(1)动态创建 <style> 标签

style-loader 的核心功能是将 CSS 样式注入到 HTML 页面中,使其能够被浏览器解析和应用。它通过在 JavaScript 运行时动态创建 <style> 标签来实现这一目的。当 Webpack 构建项目并执行到 style-loader 处理的 CSS 模块时,style-loader 会获取由 css-loader 转换后的 CSS 内容(通常是 JavaScript 模块中的字符串形式),然后创建一个新的 <style> 元素,并将 CSS 内容设置为该元素的 textContent

例如:

// 假设 cssContent 是 css-loader 处理后的 CSS 字符串
const styleElement = document.createElement('style');
styleElement.textContent = cssContent;
(2)插入 <style> 标签到 HTML 页面

在创建完 <style> 标签并填充 CSS 内容后,style-loader 会将该标签插入到 HTML 页面的合适位置,通常是 <head> 元素内部。这样,浏览器在渲染页面时就能够识别并应用这些样式。document.head.appendChild(styleElement);通过这种方式,style-loader 确保了 CSS 样式能够及时生效,并且在页面的生命周期内保持可用。

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .container {
      background-image: url('bg-image.png');
      font-family: 'CustomFont', sans-serif;
    }
  </style>
</head>

3.处理css资源

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/, 
        use: [
          'style-loader', 
          'css-loader'
        ]
      },
    ]
  },
};

4.处理less资源

module.exports = {
  module: {
    rules: [
      {
        test: /\.less$/, 
        use: [
          'style-loader', 
          'css-loader',
          'less-loader', //将less文件编译为css文件
        ]
      },
    ]
  },
};

5.处理scss/sass资源

module.exports = {
  module: {
    rules: [
      {
        test: /\.s[ac]ss$/, 
        use: [
          'style-loader', 
          'css-loader',
          'sass-loader',//将sass文件编译为css文件
        ]
      },
    ]
  },
};

三、处理图片资源

文件指纹:指的是打包后输出的文件名和后缀

文件指纹可以由以下占位符组成:

  • ext:原资源后缀名
  • name:原文件名称
  • path:原文件的相对路径
  • folder:原文件所在的文件夹
  • hash:每次webpack构建时生成一个唯一的hash值

1.webpack4的处理方式

(1)file-loader

file-loader 会将图片文件打包到输出目录,并返回图片的 URL,开发者可以直接在 CSS 或 JavaScript 中引用图片。配置如下:

module.exports = {
  output: {
    publicPath'/assets/'
    //会在所有的引用资源前加上该前缀,比如url("img/image1.png")会被修改为url("/assets/img/image1.png"),优先级低于局部配置的publicPath属性。
    //在基于 html-webpack-plugin 进行 webpack build 时,会修改生成的 index.html 文件中的 script、link 等的引用路径,如原来的<script src="main.js"></script> 会被修改为 <script src="/static/main.js"></script>
  },
  module: {
    rules: [
      {
        test: /\.(png|jpg|jpeg|gif|svg)$/,
        use: {
          loader: 'file-loader',
          options: {
            name: '[name].[hash].[ext]', //默认为[hash].[ext],可以省略下面的outputPath,简写为'images/[name].[hash].[ext]'
            outputPath: 'images/' //设置资源放置路径,如不设置则默认直接放在dist文件夹下
            publicPath: '/assets/' //会修改所有使用该资源的url链接,在原url链接前加上/assets/,比如url("img/image1.png")会被修改为url("/assets/img/image1.png")
          }
        }
      }
    ]
  }
};

(2)url-loader

url-loader 的功能类似于 file-loader,但它提供了一个额外的功能:当图片文件较小时,通过设置limit属性url-loader 会将图片转为 Base64 格式的内联数据 URI 直接嵌入到打包后的 JavaScript 文件中,从而减少 HTTP 请求,如果不配置limit属性,则所有图片都会被编码为base64的形式。

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|jpeg|gif|svg)$/,
        use: {
          loader: 'url-loader',
          options: {
            limit: 8192,  // 文件小于 8KB 时转换为 Base64
            name: '[name].[hash].[ext]',
            outputPath: 'images/'
          }
        }
      }
    ]
  }
};

(3)raw-loader

url-loader可以将文件内容导出为字符串,导出方式为js模块化导出,这样在其他文件中导入该字符串进行使用。

比如有一个这样的txt

this is a txt file

假如你把它当作js来用,import或者require进来的时候,执行this is a txt file这句js,肯定会报错。如果想正常使用,那么这个txt文件需要改成:

export default 'this is a txt file'

最后在要使用的地方进行引入就行了

//因为loader的执行优先级是前置loader>普通loader>内联loader>后置loader,同类loader从后往前执行,所以要是webpack.config.js中配置了相关loader,此处使用的内联loader可能就会失效了,所以可以通过!!跳过前置,普通和后置loader的执行,只执行当前导入的内联loader。具体的loader解释可以看原理篇。
//这个命令是使用raw-loader处理/public/test.txt文件,跳过webpack中所有匹配该文件的前置,普通和后置loader的执行(注意是所有,不仅仅是webpack配置文件中的raw-loader)。
import txt from '!!raw-loader!/public/test.txt'

console.log(txt);//this is a txt file

默认不用配置,但如果你使用的是 CommonJS模块语法。需要做如下配置:

module.exports = {
  module: {
    rules: [
      {
        test: /\.txt$/i,
        use: [
          {
            loader: 'raw-loader',
            options: {
              esModule: false, //不使用ES6模块化,使用commonjs模块化
            },
          },
        ],
      },
    ],
  },
};

2.webpack5的处理方式

在 webpack5 之前,可能需要使用 raw-loader、file-loader、url-loader 来加载资源。

  • raw-loader:将文件作为字符串导入
  • file-loader:处理文件的路径并输出文件到输出目录
  • url-loader:有条件将文件转化为 base64 URL,如果文件大于 limit 值,通常交给 file-loader 处理,如果没有设置limit值,默认全部按照base64打包。

在 webpack5+,以上方法已经被webpack内置了,即使不配置相关的loader也能成功加载资源,如果想要配置这些资源,可以使用“资源模块”来代替以上 loader。

资源模块(asset module)是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外 loader。

资源模块的类型:

  • asset/resource: 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。
  • asset/inline: 导出一个资源的 data URI。之前通过使用 url-loader 实现。
  • asset/source: 导出资源的源代码。之前通过使用 raw-loader 实现。
  • asset: 介于asset/resource和asset/inline之间,在导出一个资源data URI和发送一个单独的文件并导出URL之间做选择,之前通过url-loader+limit属性实现。
module.exports = {
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
    // 所有使用type: "asset"处理的静态文件打包后的路径及文件名(默认是走全局的,如果有独立的设置就按照自己独立的设置来。)
    assetModuleFilename: "assets/[name]_[hash][ext]",
    publicPath'/assets/'//使用与webpack4一致,Webpack 会将所有资源的加载路径都从该路径开始,比如为index.html中引入的<script> <link>等标签中的资源路径添加前缀,CSS文件中url路径前添加前缀等
  },
  module: {
    rules: [
      {
        // 用来匹配图片文件。
        test: /\.(png|jpe?g|gif|webp)$/,
        exclude: /node_modules/ //排除node_modules文件夹
        include: /node_modules/ //只会匹配node_modules文件夹的资源,与exclude二选一
        type: "asset", //类似于webpakc4中的url-loader+limit,如果不配置图片大小限制,默认是限制大小是8k
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024, // 小于10kb的图片会被base64处理,好处是可以减少http请求,缺点是会增大图片尺寸。
          },
        },
        generator: {
          filename: "images/[hash:10][ext]", // 指定打包路径和文件名,优先级高于assetModuleFilename
          publicPath'/assets/'//使用与webpack4一致,在所有使用该资源的url地址前新增公共路径
        },
      },
    ]
  },
};

Webpack 5 中的三种 PublicPath 类型

  • output.publicPath :适用于所有输出文件,包括 JavaScript、CSS 和图像。

  • module.rules[].generator.publicPath :覆盖特定加载器或插件生成的输出文件的 URL 前缀。

  • devServer :指定开发服务器上提供文件的 URL 前缀。和output.publicPath的作用和范围一致,只不过这个打包的资源是在内存中的,而output.publicPath的打包资源在硬盘上。

四、处理字体图标以及其他资源

1.处理字体图标

首先下载字体图标,可以去阿里巴巴图标库下载,选择想要的图标加入到购物车,之后将购物车中的图片添加进项目(可以新建项目加入),之后下载到本地,解压后有个iconfont.css文件要加入到项目的css文件夹中并在html文件或者js文件中引入(没有引入的文件是不会被打包的,一般使用font class的引用方式),同时demo_index.html文件可以查看具体的使用方式。

2.处理字体图标和其他资源的相关配置

webpack的配置如下:

module.exports = {
  module: {
    rules: [
      {
        // 用来匹配图片文件。
        test: /\.(woff2?|ttf|map4|map3|avi)$/,
        type: "asset/resource",  //不需要使用base64编码
        generator: {
          filename: "media/[hash:10][ext]", 
        },
      },
    ]
  },
};

五、处理js资源

由于Webpack 对 js 处理是有限的,只能编译 js 中 ES 模块化语法,不能编译其他语法,导致 js 不能在 IE 等浏览器运行,所以我们希望做一些兼容性处理,将ES6语法编译成ES5语法,其次开发中,团队对代码格式是有严格要求的,我们不能由肉眼去检测代码格式,需要使用专业的工具来检测。。

1.Eslint配置文件

配置文件有两种写法:

  • 方法一:新建文件.eslintrc.*,位于项目根目录,区别在于配置格式不一样,新建.eslintrc或者.eslintrc.js或者.eslintrc.json文件

  • 方法二:不需要创建文件,在原有文件基础上写,即package.json 中 eslintConfig配置项

ESLint 会查找和自动读取它们,所以以上配置文件只需要存在一个即可,以上两种配置文件写法仅支持eslint版本8及以下

我们以 .eslintrc.js 配置文件为例:

module.exports = {
  // 解析选项
  parserOptions: {
    ecmaVersion: 6, //使用的是 ES6 语法版本
    sourceType: "module", // 使用的是 ES6 模块化,还可以是commonjs(commonjs模块化)或者script(不使用模块化)
    ecmaFeatures: { // ES 其他特性
      jsx: true // 如果是 React 项目,就需要开启 jsx 语法
  }
  },
  // 具体检查规则,会覆盖extends继承的规则,规则很多,用到的时候可以网上查eslint的规则文档。
  //"off" 或 0 - 关闭规则
  //"warn" 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出,正常打包,控制台会打印警告)
  //"error" 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出,不会进行打包,控制台打印错误)
  rules: {
    semi: "error", // 禁止使用分号,配置中semi上的引号可写可不写
    'array-callback-return': 'warn', // 强制数组方法的回调函数中有 return 语句,否则警告
    'default-case': [ //可以有多个参数的情况下,使用数组,第一个参数永远是级别,后面的参数是对规则的补充
      'warn', // 要求 switch 语句中有 default 分支,否则警告
      { commentPattern: '^no default$' } // 如果在最后一个case最后有//no default注释, 就不会有警告
    ],
    eqeqeq: [
      'warn', // 强制使用 === 和 !==,否则警告
      'smart' // 少数情况下不会有警告(比如说比较两个具体的值,使用typeof的比较,与null进行比较)
    ],
    "no-var": 2, // 不能使用 var 定义变量
  },
  // 继承其他规则,开发中一点点写 rules 规则太费劲了,所以有更好的办法,继承现有的规则。
  //Eslint 官方的规则:eslint:recommended
  //Vue Cli 官方的规则:plugin:vue/essential
  //React Cli 官方的规则:react-app
  extends: ['eslint:recommended'],
  //是否允许使用环境变量
  env: {
    node: true, // 启用node中全局变量global等
    browser: true, // 启用浏览器中全局变量window,document等
  },

};

注意: 如果已经在根目录新建了.eslintrc.js还是报错显示找不到配置文件,可能是因为eslint版本太高了,eslint版本9及其以上配置文件改为了eslint.config.js,所以找不到.eslintrc.*配置文件,即使将配置名称改为了eslint.config.js,还是会发生报错,因为相关配置项也发生了改变比如说parserOptions被废弃,使用languageOptions进行代替,比如extends的使用方法发生了改变,使用的时候注意eslint版本的差异。

2.webpack中使用eslint

下载eslint插件和eslint:

npm i eslint-webpack-plugin eslint -D

在webpack.config.js文件中进行配置

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

module.exports = {
  plugins: [
    new ESLintWebpackPlugin({
      // 指定检查文件的根目录
      context: path.resolve(__dirname, "src"),
    }),
  ],
};

注意: 由于eslint版本9以后配置文件的名称和配置项发生了改变,于是我将eslint的版本降到了8,这样就可以使用.eslintrc.js了,但是还是报错了。

ERROR in [eslint] Couldn't find FlatESLint, you might need to set eslintPath to 'eslint/use-at-your-own-risk'

ERROR in   Error: Child compilation failed:
  [eslint] Couldn't find FlatESLint, you might need to set eslintPath to 'eslint/use-at-your-own-risk'

这时候需要修改eslint-webpack-plugin的配置项,

new ESLintWebpackPlugin({
      context: path.resolve(__dirname, 'src'), // eslint 检测的目录
      configType: "eslintrc", //指定配置文件类型,flat表示eslint9新格式,eslintrc表示原来的格式,如果使用老版本的eslint和eslint-webpack-plugin插件就不需要配置,比如eslint@8.14.0和eslint-webpack-plugin@3.1.1
    })

一般我们会在 VSCode 中使用 Eslint 插件,即可不用编译就能看到错误,可以提前解决

但是此时就会对项目所有文件默认进行 Eslint 检查了,我们 dist 目录下的打包后文件就会报错。但是我们只需要检查 src 下面的文件,不需要检查 dist 下面的文件。

所以可以使用 Eslint 忽略文件解决。在项目根目录新建下面文件:.eslintignore

# 忽略dist目录下所有文件
dist

查看效果的话可以通过直接运行打包命令或者使用以下命令:

npx eslint ./src  //检测src目录下的代码

3.babel配置文件

处理react,vue,es6等,将其转化为es5,使其能在低版本浏览器中运行。

  • 方法一:新建文件babel.config.*或者.babelrc.*,位于项目根目录,区别在于配置格式不一样,新建babel.config.js或者babel.config.json或者.babelrc或者.babelrc.js或者.babelrc.json文件

  • 方法二:不需要创建文件,在原有文件基础上写,即package.json 中 babel 配置项

Babel 会查找和自动读取它们,所以以上配置文件只需要存在一个即可

我们以 babel.config.js 配置文件为例:

module.exports = {
  // 预设,就是一组 Babel 插件, 扩展 Babel 功能
  //@babel/preset-env: 一个智能预设,可以把ES6语法编译为ES5语法。
  //@babel/preset-react:一个用来编译 React jsx 语法的预设
  //@babel/preset-typescript:一个用来编译 TypeScript 语法的预设
  presets: ["@babel/preset-env"],
};

4.在webpack中使用babel

下载相关包:

npm i babel-loader @babel/core @babel/preset-env -D

在webpack.config.js中进行配置:

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/, // 排除node_modules代码不编译
        loader: "babel-loader",
        //options: { //可以在这里配置,也可以在babel.config.js中进行配置
        //  presets: ["@babel/preset-env"],
        //}
      },
    ]
  },
};

六、处理html资源(html-webpack-plugin)

在之前我们一直是将打包的资源手动添加到html文件中的,有没有插件可以将html资源进行打包,然后将打包的其他资源自动通过link或者script引入到html中呢?

可以使用html-webpack-plugin插件,它可以将打包好的js问通过script标签导入到指定的html模板,如果使用了mini-css-extract-plugin插件,则会将css文件通过link标签导入。

同时html-webpack-plugin中内置了html-minifier,这个插件可以在打包的过程中对html进行压缩,经过测试发现默认生产模式下会对html进行压缩(个人认为应该是插件内部设置了生产模式下对html进行压缩),开发模式下不会压缩。

注意:

1.html-webpack-plugin只会自动导入js和css文件,图片以及其他资源不会自动导入到html中。

2.当有多个入口文件时,需要创建多个html-webpack-plugin插件,给每个html模板指定分别要导入的js文件,如果css文件是在js文件中导入的,则不需要指定css文件,因为只要指定了js文件,那么css文件会根据依赖自动导入。

下载相关包:

npm i html-webpack-plugin -D

在webpack.config.js中进行配置:

const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
  entry: {
    index: './src/js/index.js',
    index2: './src/js/info.js',
  },
  plugins: [
    new HtmlWebpackPlugin({
      // 以 public/index.html 为模板创建文件
      // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
      template: path.resolve(__dirname, "public/index.html"), //html模板的路径,也可以写成相对路径template:  "./public/index.html"
      filename: 'static/index.html', //打包后的文件名称和路径
      chunks: ['index'] //当多入口文件的时候,需要给每个html模板指定要导入的js文件数组,文件名称是entry属性中定义的名称,不需要指定css文件,因为css文件是js中导入,根据js文件可以找到相应的css文件并导入到html模板中。
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "public/index2.html"),
      filename: 'static/index2.html', 
      chunks: ['index2'] 
    }),
  ],
};

七、搭建开发服务器

我们使用webpack打包时,每次修改代码后,都需要重新打包,然后刷新浏览器才能看到效果,这样的开发体验是非常不友好的。所以我们需要一个工具webpack-dev-server,能够实现代码修改后自动打包,自动刷新浏览器,从而提高我们的开发效率。

webpack-dev-server的实现原理,实际就使用 webpack-dev-middleware 中间件来处理 webpack 的打包资源,并且和 express 服务器进行关联。同时提供一个 socket 服务,将 webpack 编译打包的各个阶段的状态信息告知给客户端,通知网页调用reload接口刷新页面,从而实现实时重新加载(live reloading)和模块热替换(hot module replacement)功能。

个人理解:相当于安装了一台web服务器,当开启服务器后,会自动将src目录或者指定的目录下的资源进行打包,打包后的资源存放在内存中(不需要磁盘io,可以加速打包和更新),当检测到src中的内容发生改变时会自动重新打包,打包好的资源在内存中该如何访问呢?这就是服务器的事了,可以通过浏览器访问配置的服务器网址(localhost)和端口号来查看打包后的效果,方便开发,不仅可以查看html资源,还可以查看图片等静态资源(前提是配置好静态资源路径)

由于使用该插件后webpack打包的资源会放在内存中,无法在硬盘上直接查看,因此如果想要访问服务器内存中打包好的资源可以通过http://localhost:8080/webpack-dev-server进行访问,也可以通过配置static来设置访问路径,这两种方式访问的都是内存中的目录结构,不是硬盘上的。

下载包

npm i webpack-dev-server -D

主要配置如下:

module.exports = {
  // ...
  devServer: {
      // 设置静态资源目录,这里的静态资源指的不是打包后的资源,而是指开发项目时package.json所在的根目录下的所有资源,包括src文件夹,dist文件夹,node_modules文件夹等等,webpack-dev-server 默认会将构建结果和输出文件全部作为开发服务器的资源文件,也就是说,只要通过 Webpack 打包能够输出的文件都可以直接被访问到。但是如果你还有一些 没有参与打包的静态文件 也需要作为开发服务器的资源被访问,那你就需要额外通过配置告诉 webpack-dev-server,这就是static的作用了。

      static: {
        //这里配置的就是可以通过访问的文件夹,比如说dist文件夹里面有static文件夹,那么通过localhost:3000/static就可以显示dist/static文件夹中的内容,再比如说设置为src,src文件夹中有css文件夹,那么通过localhost:3000/css就可以显示src/css文件夹中的内容。如果不设置,默认是public文件夹(注意不是打包后的dist文件夹中的public,是跟package.json同一级的根目录下的public文件夹),注意:正常情况下localhost:3000默认代表的是打包好的dist目录,比如说要默认打开的是dist目录下的index.html文件,也就是localhost:3000/index.html,如果配置了directory项,则localhost:3000也同时代表directory中配置的目录,比如src目录,那么localhost:3000后既可以跟打包后的dist目录中的文件,也可以跟src目录中的文件,即可以同时访问dist和src两个文件夹中的文件,但是dist文件中的优先级比较高,如果dist目录和src目录下都有index.js文件,那么通过localhost:3000/index.js访问到的是dist中的index.js文件,建议如果配置directory,则必须配置publicPath,不然容易造成歧义,无法确定文件属于哪个文件夹。
        directory: path.join(__dirname, 'dist'),
        //这个publicPath跟output.publicPath区别很大,这个相当于给上面directory配置的路径起别名,因此只配置directory的话有一个很致命的缺陷,就是没法直接访问配置的文件夹,只能访问该文件夹下指定的下级文件夹,比如说我想访问src文件夹就没办法访问,只能通过localhost:3000/css访问src目录下的css文件夹(别想着不夹css,只访问localhost:3000不就行了,这个默认访问的是打包后dist文件夹下的index.html文件,主要通过下面的open来进行配置浏览器默认打开的文件),因此需要配置publicPath,比如说我将publicPath配置为下面的/test,那么就可以通过localhost:3000/test访问的就是src文件夹。如果想访问package.json文件,只需要将directory中的目录设置为./就可以了。如果配置了该选项,则通过localhost:3000/css这种方法访问就失效了,只能通过localhost:3000/test/css的方法访问。
        publicPath: './test'
      },
      //  服务器host,默认为localhost,
      host: 'localhost',
      // 开启服务器时,浏览器自动打开页面,默认打开根目录的index.html文件
      // open: true,
      //自动打开指定目录的文件,配置多项可以打开多个页面,文件路径的配置不受static中的配置影响,localhost:8080和./代表的就是dist文件夹
      //open: ['/static/index.html','http://localhost:8080/second.html']
      //指定打开的浏览器,默认是google
      open: {
        target: ['first.html', 'http://localhost:8080/second.html'],
        //不同系统使用的不同。不要在可重用模块中硬编码它。例如,'Chrome' 在 macOS 是 'Google Chrome',在 Linux 是 'google-chrome',在 Windows 是 'chrome'。
        app: {
          name: 'chrome',
        },
      }
      // 自定义端口号,也可以设置为 auto
      port: 9000,
      // 开启热模块替换,默认是true开启。
      hot: true,
      // 配置代理,当前端服务器遇到以下开头的请求,则会进行拦截,之后将请求发送到target配置的目标服务器,这个过程中请求头中的host,origin等是不会变化的,如果要修改请求头需要进行配置,比如说changeOrigin配置可以将请求头中的host 修改为 target
      proxy: {
        "/bili": { // 以 /bili 开头的请求,会转发到下面的 target 配置
          target: "https://api.bilibili.com", // 目标服务器
          pathRewrite: {"^/bili" : ""}, // 重写路径,如果不配置则会将路径加在target后面,比如说访问localhost:3000/bili/user,代理后为https://api.bilibili.com/user。
          secure: false, // https接口,需要配置这个参数
        },
        '/api': {
          //如果不配置重新路径,则会将路径加在target后面,比如说访问localhost:3000/api/user,代理后为https://api.bilibili.com/api/user。
          target: 'http://82.25.101.11:8000',
          changeOrigin: true, // 将请求头中的host 配置修改为 target,如果不配置的话,host的值为前端服务器的域名加端口号
        },
      }
  }
}

启动devServer运行指令

npx webpack server

在 package.json 中增加启动服务器的命令,即可使用npm [run] start简化启动命令

{
  "scripts": {
    "start": "webpack serve"
  }
}

遇到两个问题:

  • 如果设置static.directory的文件夹没有包含html模板所在的文件夹,当修改html模板时,webpack打包正常,但浏览器不会自动更新,当js和css文件修改时,浏览器自动更新正常。这是为啥,难道webpack只会检测static.directory设置的静态目录吗?也不对啊,保存html修改时,webpack确实重新打包了,但是浏览器没有自动更新。

  • 这个与上个问题类似,如果使用了MiniCssExtractPlugin插件单独打包css文件,如果设置static.directory的文件夹没有包含css所在的文件夹,当修改css文件时,webpack打包正常,但浏览器不会自动更新。

解答:当我将hot设置为false时,不会出现上述问题,有可能是因为hot默认是true,所有修改文件的时候webpack会重新打包,进行了热更新,所以浏览器没刷新。

八、开发模式和生产模式

1.区分开发配置和生产配置

在真实的开发中,webpack配置分为开发配置和生产配置,这两种配置的内容是不同的,上面介绍的主要是开发模式下的配置,特别是devserver,接下来讲一下如何划分开发配置和生产配置以及两者的具体区别。

原来webpack.config.js配置文件在项目根目录,现在为了区分开发与生产的配置文件,创建了文件夹config,把开发和生产的文件分开了放进去。

├── webpack-test (项目根目录)
    ├── config (Webpack配置文件目录)
    │    ├── webpack.dev.js(开发模式配置文件)
    │    └── webpack.prod.js(生产模式配置文件)
    ├── node_modules (下载包存放目录)
    ├── src (项目源码目录,除了html其他都在src里面)
    │    └── 略
    ├── public (项目html文件)
    │    └── index.html
    ├── .eslintrc.js(Eslint配置文件)
    ├── babel.config.js(Babel配置文件)
    └── package.json (包的依赖管理配置文件)

2.修改配置文件的绝对路径

修改webpack.dev.js和webpack.prod.js配置文件中的路径,相对路径不变,所有的绝对路径要添加../返回到根目录。

问题:为啥配置里面的相对路径不变,绝对路径要加个`../`呢?

个人认为应该是因为这是配置文件,项目打包时我们一般在根目录运行打包命令,通过在命令行中使用`webpakc --config ./config/webpakc.config.js` 打包项目,打包的时候读取config文件夹下的配置文件,`相当于在命令行中将配置文件中的每个配置项拿出来放到命令行中`,比如说配置文件中的`mode: 'development',entry: './src/main.js'`配置项就可能变成`webpakc --entry ./src/main.js --mode development`的类似形式,所以此时配置项中的相对路径就是相对于执行打包命令的目录来说的,而不是配置文件所在的目录。而绝对路径就不同了,它是根据`webpack.config.js`文件所在的位置决定的,如果不加上`../`返回到根目录,那么得到的配置的绝对路径中会包含`/config/`。

3.开发模式webpack.dev.js配置文件

const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/main.js",
  output: {
    path: undefined, // 开发模式没有输出,不需要指定输出目录
    filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
    // clean: true, // 开发模式没有输出,不需要清空输出结果
  },
  module: {
    rules: [
      {
        // 用来匹配 .css 结尾的文件
        test: /\.css$/,
        // use 数组里面 Loader 执行顺序是从右到左
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
      {
        test: /\.s[ac]ss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
      {
        test: /\.styl$/,
        use: ["style-loader", "css-loader", "stylus-loader"],
      },
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
          },
        },
        generator: {
          // 将图片文件输出到 static/imgs 目录中
          // 将图片文件命名 [hash:8][ext][query]
          // [hash:8]: hash值取8位
          // [ext]: 使用之前的文件扩展名
          // [query]: 添加之前的query参数
          filename: "static/imgs/[hash:8][ext][query]",
        },
      },
      {
        test: /\.(ttf|woff2?)$/,
        type: "asset/resource",
        generator: {
          filename: "static/media/[hash:8][ext][query]",
        },
      },
      {
        test: /\.js$/,
        exclude: /node_modules/, // 排除node_modules代码不编译
        loader: "babel-loader",
      },
    ],
  },
  plugins: [
    new ESLintWebpackPlugin({
      // 指定检查文件的根目录
      context: path.resolve(__dirname, "../src"),
      configType: "eslintrc",//指定配置文件类型,flat表示eslint9新格式,eslintrc表示原来的.eslintrc.js格式
    }),
    new HtmlWebpackPlugin({
      // 以 public/index.html 为模板创建文件
      // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
      template: path.resolve(__dirname, "../public/index.html"),
    }),
  ],
  // 其他省略
  devServer: {
    host: "localhost", // 启动服务器域名
    port: "3000", // 启动服务器端口号
    open: true, // 是否自动打开浏览器
  },
  mode: "development",
};

启动开发模式的运行指令

npx webpack server --config ./config/webpack.dev.js

4.生产模式webpack.prod.js配置文件

const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "../dist"), // 生产模式需要输出
    filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
    clean: true,
  },
  module: {
    rules: [
      {
        // 用来匹配 .css 结尾的文件
        test: /\.css$/,
        // use 数组里面 Loader 执行顺序是从右到左
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
      {
        test: /\.s[ac]ss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
      {
        test: /\.styl$/,
        use: ["style-loader", "css-loader", "stylus-loader"],
      },
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
          },
        },
        generator: {
          // 将图片文件输出到 static/imgs 目录中
          // 将图片文件命名 [hash:8][ext][query]
          // [hash:8]: hash值取8位
          // [ext]: 使用之前的文件扩展名
          // [query]: 添加之前的query参数
          filename: "static/imgs/[hash:8][ext][query]",
        },
      },
      {
        test: /\.(ttf|woff2?)$/,
        type: "asset/resource",
        generator: {
          filename: "static/media/[hash:8][ext][query]",
        },
      },
      {
        test: /\.js$/,
        exclude: /node_modules/, // 排除node_modules代码不编译
        loader: "babel-loader",
      },
    ],
  },
  plugins: [
    new ESLintWebpackPlugin({
      // 指定检查文件的根目录
      context: path.resolve(__dirname, "../src"),
      configType: "eslintrc",//指定配置文件类型,flat表示eslint9新格式,eslintrc表示原来的.eslintrc.js格式
    }),
    new HtmlWebpackPlugin({
      // 以 public/index.html 为模板创建文件
      // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
      template: path.resolve(__dirname, "../public/index.html"),
    }),
  ],
  // devServer: {
  //   host: "localhost", // 启动服务器域名
  //   port: "3000", // 启动服务器端口号
  //   open: true, // 是否自动打开浏览器
  // },
  mode: "production",
};

启动生产模式的运行指令

npx webpack --config ./config/webpack.prod.js

5.配置npm启动脚本

为了简化运行指令,可以在package.json中配置npm启动脚本

// package.json
{
  // 其他省略
  "scripts": {
    "start": "npm run dev",
    "dev": "npx webpack serve --config ./config/webpack.dev.js",
    "build": "npx webpack --config ./config/webpack.prod.js"
  }
}

九、提取单独CSS文件

通过css-loader和style-loader的处理会将Css 文件被打包到 js 文件中,当 js 文件加载时,会创建一个 style 标签来生成样式,这样对于网站来说,会出现闪屏现象,用户体验不好。我们应该是单独的 Css 文件,通过 link 标签加载性能才好。

1.下载mini-css-extract-plugin插件包

注意:

  • 该插件会将所有的css样式输出到一个.css文件中,只会产生一个css文件。
  • 产生的css文件在生产模式下不会被压缩,生产模式下仅会压缩js和html文件,需要使用额外插件对css文件进行压缩。
npm i mini-css-extract-plugin -D

2.在webpack.prod.js中进行配置

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  module: {
    rules: [
      {
        // 用来匹配 .css 结尾的文件
        test: /\.css$/,
        // use 数组里面 Loader 执行顺序是从右到左
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
      {
        test: /\.less$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"],
      },
      {
        test: /\.s[ac]ss$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
      },
      {
        test: /\.styl$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "stylus-loader"],
      }
    ],
  },
  plugins: [
    // 提取css成单独文件
    new MiniCssExtractPlugin({
      // 定义输出文件名和目录
      filename: "static/css/main.css",
    }),
  ],
};

如何解决闪屏现象:

出现闪屏现象的原因的css样式是通过js语言在html中生成style标签使用的,而浏览器在解析html标签时遇到script标签会先下载js代码再运行js代码,阻塞html解析,在这个过程中浏览器只会显示已经解析的html标签,没有样式,当js代码下载运行完了才会在html中新增style标签,之后浏览器中的元素才会有样式,这就是闪屏现象,如果单独提取css文件在html中通过link引入,就会一边解析html标签(构建DOM树),一边解析css样式(构建CSSOM树),一边渲染元素(通过DOM树和CSSOM树构建渲染树)。这样出现由于js阻塞html解析导致的闪屏现象了。

白屏现象出现的原因是与上面类似,html中的结构都是通过js生成的,索引刚打开网页的时候浏览器要下载解析js,要是js代码很多,就会出现白屏现象

十、处理CSS兼容性

在前端开发中,我们通常使用 CSS 来美化我们的网页,但是在不同的浏览器下,同一份 CSS 样式的表现也可能有所不同,这就是样式兼容性问题。为了解决这个问题,我们可以使用 postcss-loader 对 CSS 进行预处理,使得 CSS 样式可以更好的兼容不同的浏览器。

postcss-loader 是一个 webpack 的 loader,它可以自动地对 CSS 样式进行预处理。它可以支持许多插件,比如 autoprefixer(自动添加浏览器前缀),cssnano(压缩 CSS 代码),cssnext 等等,通过这些插件,我们可以更简单地解决样式兼容性问题,同时还可以自动压缩代码。

/* 处理前css代码 */
display: flex;

/* 处理后css代码 */
display: -webkit-box;
display: -ms-flexbox;
display: flex;

1.下载插件包

postcss-preset-env插件中已经包含了autoprefixer插件。

npm i postcss-loader postcss postcss-preset-env -D

2.在webpack.prod.js中进行配置

module.exports = {
  module: {
    rules: [
      {
        // 用来匹配 .css 结尾的文件
        test: /\.css$/,
        // use 数组里面 Loader 执行顺序是从右到左
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  "postcss-preset-env", // 能解决大多数样式兼容性问题
                ],
              },
            },
          },
        ],
      },
      {
        test: /\.less$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  "postcss-preset-env", // 能解决大多数样式兼容性问题
                ],
              },
            },
          },
          "less-loader",
        ],
      },
      {
        test: /\.s[ac]ss$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  "postcss-preset-env", // 能解决大多数样式兼容性问题
                ],
              },
            },
          },
          "sass-loader",
        ],
      },
      {
        test: /\.styl$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  "postcss-preset-env", // 能解决大多数样式兼容性问题
                ],
              },
            },
          },
          "stylus-loader",
        ],
      },
    ],
  }
};

注意postcss-loader的位置,在css-loader和less-loader之间。

3.控制兼容性

我们可以在 package.json 文件中添加 browserslist 来控制样式的兼容性做到什么程度。实际开发中我们一般不考虑旧版本浏览器了,所以我们可以这样设置:

{
  // 其他省略
  "browserslist": ["last 2 version", "> 1%", "not dead"]
}

浏览器的兼容性可以在这里查找

4.结合js函数

对于.css文件,.less文件和.sass文件来说,大部分的loader配置类似,可以使用js对loader配置进行函数封装。

修改webpack.prod.js配置如下:

// 获取处理样式的Loaders
const getStyleLoaders = (preProcessor) => {
  return [
    MiniCssExtractPlugin.loader,
    "css-loader",
    {
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: [
            "postcss-preset-env", // 能解决大多数样式兼容性问题
          ],
        },
      },
    },
    preProcessor,
  ].filter(Boolean);
};

module.exports = {
  module: {
    rules: [
      {
        // 用来匹配 .css 结尾的文件
        test: /\.css$/,
        // use 数组里面 Loader 执行顺序是从右到左
        use: getStyleLoaders(),
      },
      {
        test: /\.less$/,
        use: getStyleLoaders(less-loader),
      },
      {
        test: /\.s[ac]ss$/,
        use: getStyleLoaders(sass-loader),
      },
      {
        test: /\.styl$/,
        use: getStyleLoaders(stylus-loader),
      },
    ],
  }
};

十一、进行CSS压缩

由于生产模式下webpack默认只会对html和js文件进行压缩,不会对css进行压缩,所以需要单独使用插件压缩css文件。

1.下载相关依赖包

npm i css-minimizer-webpack-plugin -D

2.在webpack.prod.js进行配置

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  plugins: [
    // css压缩
    new CssMinimizerPlugin(),
  ],
};

接下来还有高级篇,主要介绍webpack优化相关的内容,包括优化构建速度,减少打包体积等~