webpack基础

257 阅读9分钟

一、webpack简介

webpack is a static module bundler for modern JavaScript applications.

1.1 介绍

  • webpack是一个静态的模块化打包工具,为现代的JavaScript应用程序
    • 打包bundler:webpack可以进行打包,所以它是一个打包工具

    • 静态的static:可以将代码打包成最终的静态资源(部署到静态服务器)

    • 模块化module:webpack默认支持各种模块化开发,ES Module、CommonJS、AMD等

    • 现代的modern:因为现代前端开发面临各种各样的问题,才催生了webpack的出现和发展

  • 加载vue项目为例:
    • JavaScript的打包:

      • 将ES6转换成ES5的语法
      • TypeScript的处理,将其转换成JavaScript
    • CSS的处理:

      • CSS文件模块的加载、提取
      • Less、Sass等预处理器的处理
    • 资源文件img、font:

      • 图片img文件的加载
      • 字体font文件的加载
    • HTML资源的处理:

      • 打包HTML资源文件;
    • 处理vue项目的SFC文件.vue文件

1.2 使用前提

二、安装及简单使用

2.1 安装

  • webpack的安装目前分为两个:webpackwebpack-cli

  • 两者之间的关系:

    • 执行webpack命令,会执行node_modules下的.bin目录下的webpack;

    • webpack在执行时是依赖webpack-cli的,如果没有安装就会报错;

    • 而webpack-cli中代码执行时,才是真正利用webpack进行编译和打包的过程;

    • 所以在安装webpack时,需要同时安装webpack-cli(第三方的脚手架事实上是没有使用webpack-cli的,而是类似于自己的vue-service-cli的东西)

  • 安装命令

    npm install webpack webpack-cli –g # 全局安装
    
    npm install webpack webpack-cli –D # 局部安装
    
  • 创建局部的webpack

    1. 创建package.json文件,用于管理项目的信息、库依赖等:npm init

    2. 安装局部的webpack: npm install webpack webpack-cli –D

    3. 使用局部的webpack: npx webpack

    4. 在package.json中创建scripts脚本,执行脚本打包即可:npm run build

      image.png

2.2 默认打包

  • 在目录下直接执行 webpack 命令 可以对项目代码进行打包

  • 生成一个dist文件夹,里面存放一个main.js的文件,就是打包之后的文件;

  • webpack是如何确定打包的入口?

    • 事实上,当运行webpack时,webpack会查找当前目录下的 src/index.js作为入口

    • 所以,如果当前项目中没有存在src/index.js文件,打包会报错

  • 当然,可以通过配置来指定入口和出口:npx webpack --entry ./src/main.js --output-path ./build

2.3 配置文件

  • 在根目录下创建一个webpack.config.js文件,来作为webpack的配置文件
    const path = require('path')
    
    module.exports = {
      entry: './src/main.js', // 指定入口文件
      output: {
        filename: 'bundle.js', // 出口文件名称
        path: path.resolve(__dirname, './build') // 出口文件路径
      }
    }
    
  • 配置文件一般为根目录下的 webpack.config.js,若修改名称需要在打包时指定配置
    • npx webpack --entry ./src/main.js --output-path ./build
    • 可将上述命令配置在package.json的脚本中

2.4 Webpack的依赖图

  • webpack是如何对项目进行打包

    • 上webpack在处理应用程序时,它会根据命令或者配置文件找到入口文件

    • 从入口开始,会生成一个 依赖关系图,这个依赖关系图会包含应用程序中所需的所有模块(比如.js文件、css文件、图片、字体等)

    • 然后遍历图结构,打包一个个模块(根据文件的不同使用不同的loader来解析)

    image.png

三、webpack打包css

3.1 loader的认识

webpack默认只能理解JavaScriptJSON文件,但实际工作中各种需求层出不穷,文件类型也多种多样.比如.vue.ts图片.css等,这就需要loader增强webpack处理文件的能力

  • loader 的作用

    • loader 可以用于对模块的源代码进行转换

    • 可以将css文件也看成是一个模块,我们是通过import来加载这个模块的

    • 在加载这个模块时,webpack其实并不知道如何对其进行加载,必须制定对应的loader来完成这个功能

    • 也就是说loader能够帮助webpack正确识别并处理它处理不了的文件

  • loader配置方式

    • rules属性对应的值是一个数组:[Rule]

    • 数组中存放的是一个个的Rule,Rule是一个对象,对象中可以设置多个属性:

    • test属性:用于对 resource(资源)进行匹配的,通常会设置成正则表达式;

    • use属性:对应的值时一个数组:[UseEntry]

      • UseEntry是一个对象,可以通过对象的属性来设置一些其他属性

        • loader:必须有一个 loader属性,对应的值是一个字符串;

        • options:可选的属性,值是一个字符串或者对象,值会被传入到loader中;

        • query:目前已经使用options来替代;

      • 传递字符串(如:use: [ 'style-loader' ])是 loader 属性的简写方式(如:use: [ { loader: 'style-loader'} ]);

    • loader属性: Rule.use: [ { loader } ] 的简写。

    module: {
        rules: [
          {
            // 匹配的文件
            test: /\.css$/,
            // use: [
            //   // use中多个loader的使用顺序是从后往前
            //   { loader: "style-loader" },
            //   { loader: "css-loader" },
            // ],
            // 简写一: 如果loader只有一个
            // loader: "css-loader"
            // 简写二: 多个loader不需要其他属性时, 可以直接写loader字符串形式
            use: [
              "style-loader",
              "css-loader",
              "postcss-loader",
              // {
              //   loader: "postcss-loader",
              //   options: {
              //     postcssOptions: {
              //       plugins: ["autoprefixer"]
              //     }
              //   }
              // },
            ],
          },
          {
            test: /\.less$/,
            use: ["style-loader", "css-loader", "less-loader", "postcss-loader"],
          },
        ],
      },
    

3.2 css-loader

  • 为css-loader只是负责将.css文件进行解析,并不会将解析之后的css插入到页面中
  • 安装css-loader:npm install css-loader -D

3.3 style-loader

  • 如果要完成插入style的操作,那么还需要另外一个loader,就是style-loader

  • 安装style-loader:npm install style-loader -D

3.4 less-loader

  • 使用less-loader,自动使less工具转换less到css
  • less-loader会依赖less,所以安装时需要有less
    • npm install less -D
    • npm install less-loader -D

3.5 postcss-loader

  • PostCSS是一个通过JavaScript来转换样式的工具

    • 这个工具可以帮助进行一些CSS的转换和适配,比如自动添加浏览器前缀、css样式的重置

    • 实现这些功能,需要借助于PostCSS对应的插件

  • 如何使用PostCSS呢?主要就是两个步骤:

    1. 查找PostCSS在构建工具中的扩展,比如webpack中的postcss-loader

    2. 选择可以添加需要的PostCSS相关的插件

      • postcss-preset-env是一个postcss的插件:将一些现代的CSS特性,转成大多数浏览器认识的CSS,并且会根据目标浏览器或者运行时环境添加所需的polyfill;也包括会自动帮助我们添加autoprefixer(所以相当于已经内置了autoprefixer)
      • 安装:npm install postcss-preset-env -D
  • postcss-loader 安装

    • npm install postcss-loader -D
    • postcss需要有对应的插件才会起效果,所以需要配置它的plugin,可以在单独的配置文件 postcss.config.js
    module.exports = {
      plugins: [
        "postcss-preset-env"
      ]
    }
    

四、webpack打包其他资源

4.1 asset module type

  • 资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:

    • asset/resource 发送一个单独的文件并导出 URL:之前通过使用 file-loader 实现;

    • asset/inline 导出一个资源的 data URI:之前通过使用 url-loader 实现;

    • asset/source 导出资源的源代码:之前通过使用 raw-loader 实现;

    • asset 在导出一个 data URI 和发送一个单独的文件之间自动选择:之前通过使用 url-loader,并且配置资源体积限制实现

  • asset/resource
    • 打包两张图片, 并且这两张图片有自己的地址, 将地址设置到img或者bgi中
    • 缺点: 多图片加载多次网络请求
  • asset/inline
    • 将图片进行base64的编码, 并且直接编码后的源码放到打包的js文件中
    • 缺点: 造成js文件非常大, 下载js文件本身消耗时间非常长, 造成js代码的解析/执行时间过长
  • asset
    • 对于小一点的图片, 可以进行base64编码
    • 对于大一点的图片, 单独的图片打包, 形成url地址, 单独的请求这个url图片
  • module中的rule配置
    {
        test: /\.(png|jpe?g|svg|gif)$/i,
        // type: "asset/resource",
        // type: "asset/inline",
        // 3.合理的规范:
        // 3.1对于小图片,进行base64
        // 3.2对于大图片,单独打包,形成url地址,单独请求
        type: "asset",
        parser: {
          // 限制大小
          dataUrlCondition: {
            maxSize: 28 * 1024
          }
        },
        generator: {
          // 占位符
          // name: 原来的图片名称
          // ext: 扩展名
          // hash: webpack生成的hash
          filename: "img/[name]_[hash:8][ext]" 
        }
      },
      // 打包字体
      {
        test: /\.(eot|ttf|woff2?)$/,
        type: "asset/resource", // 字体一般不做base64编码
        generator: {
          filename: "font/[name]_[hash:8][ext]",
        },
      },
    

4.2 打包js文件 - babel

  • Babel到底是什么呢?

    • Babel是一个工具链,主要用于旧浏览器或者环境中将ECMAScript 2015+代码转换为向后兼容版本的JavaScript
    • 包括:语法转换、源代码转换等
  • Babel命令行使用

    • babel本身可以作为一个独立的工具(和postcss一样),不和webpack等构建工具配置来单独使用。

    • 如果希望在命令行尝试使用babel,需要安装如下库:

      • @babel/core:babel的核心代码,必须安装

      • @babel/cli:可以让我们在命令行使用babel

      • npm install @babel/cli @babel/core -D

    • 使用babel来处理源代码:

      • src:是源文件的目录

      • --out-dir:指定要输出的文件夹dist

      • npx babel src --out-dir dist

  • 插件的使用

    • 要转换箭头函数,那么我们就可以使用箭头函数转换相关的插件:
    npm install @babel/plugin-transform-arrow-functions -D
    
    npx babel src --out-dir dist --plugins=@babel/plugin-transform-arrow-functions
    
    • 同时需要使用 plugin-transform-block-scoping 来完成const 转换成 var
    npm install @babel/plugin-transform-block-scoping -D
    
    npx babel src --out-dir dist --plugins=@babel/plugin-transform-block-scoping,@babel/plugin-transform-arrow-functions
    
  • Babel的预设preset

    • 如果要转换的内容过多,一个个设置是比较麻烦的,我们可以使用预设(preset):
      • 安装@babel/preset-env预设:

        • npm install @babel/preset-env -D
      • 单独配置 babel.config.js

        module.exports = {
          presets: ["@babel/preset-env"],
        };
        
  • 安装babel-loader:npm install babel-loader -D

4.3 打包vue文件

  • 安装vue:npm install vue
  • vue-loader

    • npm install vue-loader -D
  • 在webpack配置中设置Plugin

    const { VueLoaderPlugin } = require("vue-loader")
    module.exports = {
      module: {
        rules: [
          {
            test: /\.vue$/,
            loader: "vue-loader",
          },
        ]
      },
      plugins: [
        new VueLoaderPlugin()
      ]
    }
    

4.4 resolve配置

  • webpack能解析三种文件路径:

    • 绝对路径

      • 由于已经获得文件的绝对路径,因此不需要再做进一步解析。
    • 相对路径

      • 在这种情况下,使用 import 或 require 的资源文件所处的目录,被认为是上下文目录

      • 在 import/require 中给定的相对路径,会拼接此上下文路径,来生成模块的绝对路径

    • 模块路径

      • 在 resolve.modules中指定的所有目录检索模块

      • 默认值是 ['node_modules'],所以默认会从node_modules中查找文件

      • 可以通过设置别名的方式来替换初识模块路径

  • 确定文件还是文件夹

    • 如果是一个文件:

      • 如果文件具有扩展名,则直接打包文件

      • 否则,将使用 resolve.extensions选项作为文件扩展名解析

    • 如果是一个文件夹:

      • 会在文件夹中根据 resolve.mainFiles配置选项中指定的文件顺序查找;

        • resolve.mainFiles的默认值是 ['index']

        • 再根据 resolve.extensions来解析扩展名

  • extensions:解析到文件时自动添加扩展名:

    • 默认值是 ['.wasm', '.mjs', '.js', '.json']

    • 如果代码中想要添加加载 .vue 或者 jsx 或者 ts 等文件时,必须自己写上扩展名

  • alias:给文件路径起别名

    • 当项目的目录结构比较深的时候,或者一个文件的路径可能需要 ../../../这种路径片段,可以给某些常见的路径起一个别名
    module.exports = {
      resolve: {
        extensions: [".js", ".json", ".jsx", ".vue", ".ts", ".tsx"],
        alias: {
          utils: path.resolve(__dirname, "./src/utils")
        }
      },
    }
    

五、Webpack的插件

5.1 认识Plugin

While loaders are used to transform certain types of modules, plugins can be leveraged to perform a wider range of tasks like bundle optimization, asset management and injection of environment variables

  • Loader是用于特定的模块类型进行转换

  • Plugin可以用于执行更加广泛的任务,比如打包优化、资源管理、环境变量注入

    image.png

5.2 CleanWebpackPlugin

  • 帮助删除已存在的打包文件夹
  • 安装和配置
    • npm install clean-webpack-plugin -D
    • 配置:
    const { CleanWebpackPlugin } = require("clean-webpack-plugin")
    module.exports = {
      output: {
        filename: "bundle.js",
        path: path.resolve(__dirname, "./build"),
        clean: true
        // assetModuleFilename: "abc.png"
      },
      plugins: [
        new CleanWebpackPlugin()
      ]
    }
    

5.3 HtmlWebpackPlugin

  • 对HTML进行打包处理可以使用另外一个插件:HtmlWebpackPlugin

  • npm install html-webpack-plugin -D

  • 使用指定模板

    const HtmlWebpackPlugin = require("html-webpack-plugin");
    
    module.exports = { 
      plugins: [ 
        new HtmlWebpackPlugin({
          title: "webpack basic",
          template: "./index.html"
        }), 
      ] 
    }
    

5.4 DefinePlugin

  • DefinePlugin允许在编译时创建配置的全局常量,是一个webpack内置的插件(不需要单独安装)

  • 例如:在vue的模板中

    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    
    • 如果没有设置过这个常量值,就会会出现没有定义的错误
  • 使用

    plugins: [
      new VueLoaderPlugin(),
      // new CleanWebpackPlugin(),
      new HtmlWebpackPlugin({
        title: "电商项目",
        template: "./index.html"
      }),
      new DefinePlugin({
        // 这里 "" 中的内容会被当成 js 代码来运行,用''包裹才是字符串
        BASE_URL: "'./'",
        author: "'coder'",
        counter: "999"
      })
    ]
    
  • main.js 可以使用

    // 使用通过DefinePlugin注入的变量
    console.log(author) // coder
    console.log(counter) // 999
    
    console.log(process.env.NODE_ENV)
    

5.5 CopyWebpackPlugin

  • 作用:在webpack打包完成后会把静态资源复制到打包的文件夹下

    • npm install copy-webpack-plugin@9 -D
    • 在部署时,通过此插件将静态资源进行复制到打包后的资源中
  • 配置如下:

    const CopyWebpackPlugin = require("copy-webpack-plugin")
    
    module.exports = { 
      plugins: [ 
       new CopyWebpackPlugin({
      patterns: [
        {
          // 从哪个文件
          from: "public",
          // 到哪个文件,当前打包的文件夹下
          to: "./",
          globOptions: {
            // 忽略的文件
            ignore: [
              "**/index.html"
            ]
          }
        }
      ]
    })
      ] 
    }
    

5.6 mode配置

  • Mode配置选项,可以告知webpack使用相应模式的内置优化:

    • 默认值是production(什么都不设置的情况下)
    • 可选值有:'none' | 'development' | 'production'
  • 区别

    image.png

六、webpack-dev-server

6.1 使用webpack-dev-server

  • 安装webpack-dev-server:npm install webpack-dev-server -D

  • 修改配置文件,启动时加上serve参数:"serve": "webpack serve"

  • webpack-dev-server 在编译之后不会写入到任何输出文件,而是将 bundle 文件保留在内存中:

    • 事实上webpack-dev-server使用了一个库叫memfs(memory-fs webpack自己写的)

6.2 HMR热模块替换

  • 什么是HMR呢?

    • HMR的全称是Hot Module Replacement,翻译为模块热替换

    • 模块热替换是指在 应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个页面

  • HMR通过如下几种方式,来提高开发的速度:

    • 不重新加载整个页面,这样可以保留某些应用程序的状态不丢失

    • 只更新需要变化的内容,节省开发的时间

    • 修改了css、js源代码,会立即在浏览器更新,相当于直接在浏览器的devtools中直接修改样式

  • 如何使用HMR呢?

    • 默认情况下,webpack-dev-server已经支持HMR,只需要开启即可(默认已经开启)

    • 在不开启HMR的情况下,当修改了源代码之后,整个页面会自动刷新,使用的是live reloading

    if (module.hot) {
      module.hot.accept("./utils/demo.js", () => {
        console.log("demo发生了更新");
      });
    }
    

6.3 devServer配置

  • static

    • 该配置项允许配置从目录提供静态文件的选项(默认是 public 文件夹)
    • devServer将所有静态资源进行打包放入内存(memfs)中,开启express服务器,访问的时候会下载index.html(会依赖静态资源),此处static可以指定一个文件夹浏览器请求资源从打包的资源请求不到的时候会让express服务器从该文件夹中将静态资源读到内存中供浏览器访问
    • 注意: 只有在开发阶段希望提供静态文件时才需要这样做,告诉服务器从哪里提供内容;打包阶段通过CopyWebpackPlugin进行复制到打包的文件夹下
      • 比如:单独引入 assets 下的某张图片时,需要配置
    const path = require('path');
    
    module.exports = {
      //...
      devServer: {
        static: {
          directory: path.join(__dirname, 'public'),
        },
      },
    };
    
  • host

    • 默认值是localhost;
    • 如果希望其他地方也可以访问,可以设置为 0.0.0.0;
  • port

    • 设置监听的端口,默认情况下是8080
  • open

    • 是否打开浏览器:默认值是false,设置为true会打开浏览器
  • compress

    • 是否为静态文件开启gzip compression:默认值是false,可以设置为true
  • proxy

    • target:表示的是代理到的目标地址,比如请求到 /api/users 在会被代理到请求 http://localhost:3000/api/users

    • pathRewrite:默认情况下,/api 也会被写入到URL中,如果希望删除,可以使用pathRewrite

    • secure:默认情况下不接收转发到https的服务器上,如果希望支持,可以设置为false

    • changeOrigin:表示是否更新代理后请求的headers中host地址

  • historyApiFallback
    • 当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html
    • 将该属性设为true即可
    const path = require("path")
    
    module.exports = {
      devServer: {
        static: path.resolve(__dirname, "./public"),
        hot: true, // 开启HMR
        host: "0.0.0.0",
        port: 8888,
        open: true, // 自动打开浏览器
        // compress: true,
        proxy: {
          "/api": {
            target: "http://localhost:3000",
            pathRewrite: {
              "^/api": ""
            },
            secure: false,
            changeOrigin: true
          }
        }
      },
    }
    

七、区分环境

  • webpack.com.conf.js
const path = require("path");
const { VueLoaderPlugin } = require("vue-loader/dist/index");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { DefinePlugin } = require("webpack");

module.exports = {
  entry: "./src/main.js",
  output: {
    filename: "js/bundle.js",
    path: path.resolve(__dirname, "../build"),
  },
  resolve: {
    extensions: [".js", ".json", ".vue", ".jsx", ".ts", ".tsx"],
    alias: {
      utils: path.resolve(__dirname, "../src/utils"),
    },
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader", "postcss-loader"],
      },
      {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "less-loader", "postcss-loader"],
      },
      {
        test: /\.(png|jpe?g|svg|gif)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 28 * 1024,
          },
        },
        generator: {
          filename: "img/[name]_[hash:8][ext]",
        },
      },
      {
        test: /\.(eot|ttf|woff2?)$/,
        type: "asset/resource",
        generator: {
          filename: "font/[name]_[hash:8][ext]",
        },
      },
      {
        test: /\.js$/,
        use: [
          {
            loader: "babel-loader",
          },
        ],
      },
      {
        test: /\.vue$/,
        loader: "vue-loader",
      },
    ],
  },
  plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      title: "电商项目",
      template: "./public/index.html",
    }),
    new DefinePlugin({
      BASE_URL: "'./'",
      // 对vue2做适配,是否支持optionsApi
      __VUE_OPTIONS_API__: true,
      // 生产环境不需要支持调试
      __VUE_PROD_DEVTOOLS__: false,
      author: "'coder'",
      counter: "999",
    }),
  ],
};

  • webpack.dev.conf.js
const path = require("path")

const { merge } = require("webpack-merge")
const commonConfig = require("./webpack.com.config")

module.exports = merge(commonConfig, {
  mode: "development",
  // 设置source-map, 建立js映射文件, 方便调试代码和错误
  devtool: "source-map",
  devServer: {
    static: path.resolve(__dirname, "../public"),
    hot: true,
    host: "0.0.0.0",
    port: 8888,
    // open: true,
    proxy: {
      api: {
        target: "http://localhost:7777",
        pathRewrite: {
          "^/api": "",
        },
        // secure: false,
        changeOrigin: true
      },
    },
  },
});

  • webpack.prod.conf.js
const { merge } = require("webpack-merge");
const CopyWebpackPlugin = require("copy-webpack-plugin")
const commonConfig = require("./webpack.com.config");

module.exports = merge(commonConfig, {
  mode: "production",
  output: {
    // 在生成文件之前清空 output 目录
    clean: true,
  },
  plugins: [
    new CopyWebpackPlugin({
      patterns: [
        {
          // 从哪个文件
          from: "public",
          // 到哪个文件
          to: "./",
          globOptions: {
            // 忽略的文件
            ignore: [
              "**/index.html"
            ]
          }
        }
      ]
    })
  ],
});

  • 安装 merge:npm install webpack-merge -D
const { merge } = require("webpack-merge");
const commonConfig = require("./webpack.com.config");

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