webpack

122 阅读7分钟

一、认识webpack(静态的模块化打包工具)

1.【webpack解决的问题】:

  • 模块化的方式来开发 (ESModule、common.js)
  • 通过各种loader处理文件(less-loader,sass-loader,file-loader,vue-loader等)
  • 热更新,提高开发的效率;
  • 将代码进行压缩、合并以及其他相关的优化

2.【webpack的构建流程】:

1、初始化流程:初始化需要使用的插件和配置插件等执行环境所需要的参数;

2、编译构建流程:从entery出发,针对每个module 串行调用对应的Loader去翻译文件内容,再找到该Module依赖的Module,递归地进行编译处理。

3、输出流程:对编译后的module组合成chunk(大块代码),把chunk转为文件,输出到文件系统。

二、webpack安装

  • 1.先安装Node.js,并且同时会安装npm;
  • 2.创建全局的webpack;
npm install webpack webpack-cli -g # 全局安装
  • 3.创建项目并使用webpack
//第一步:创建package.json文件,用于管理项目的信息、库依赖等
npm init
//第二步:安装局部的webpack
npm install webpack webpack-cli -D
//第三步:在package.json中创建scripts脚本
"scripts":{
"build":"webpack"
},
//第四步:创建文件夹src然后在src中创建index.js和index.html
//第五步:创建 webpack.config.js(配置入口和出口)
//第六步:开始打包
npx webpack / npm run build
会生成dist/bundle.js

三、配置文件webpack.config.js

1、基本配置

// 设置模式
  // development 开发阶段, 会将DefinePlugin中process.env.NODE_ENV设置为development
  // production 准备打包上线的时候, 设置production
  mode: "development",
  // 设置source-map, 建立js映射文件, 方便调试代码和错误
  devtool: "source-map",

  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "./build"),
    filename: "js/bundle.js",
    assetModuleFilename: "img/[name]_[hash:6][ext]"
  },

2、Loader

  • loader用于对模块的“源代码”进行转换,在import或“加载”模块时预处理文件
  • webapck做的事情,仅仅是分析出各种模块的依赖关系,然后形成资源列表,最终打包生成到指定的文件中,
  • webpack只支持对js和json文件打包,像css、sass、png等这些类型的文件的时候,webpack则无能为力,
  • 这时候就需要配置对应的loader进行文件内容的解析。

1. 常见的loader

 // loader是用于特定的模块类型进行转换;
  module: {
    rules: [{
        test: /\.css$/,
        use: ["style-loader", "css-loader", "postcss-loader"],
      },
      {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
      // 一、理图片和字体等资源
      // 1.webpack5之前:
      // 1.1 file-loader/url-loader共有属性:
      // [ext]: 处理文件的扩展名;
      // [name]:处理文件的名称;
      // [hash]:文件的内容,使用MD4的散列函数处理,生成的一个128位的hash值(32个十六进制);
      // [contentHash]:在file-loader中和[hash]结果是一致的(在webpack的一些其他地方不一样,后面会讲到);
      // [hash:<length>]:截图hash的长度,默认32个字符太长了;
      // [path]:文件相对于webpack配置文件的路径;
      // 1.2 url-loader的特有属性:
      // [limit]: 1.url-loader对图片是否转base64的限制;小于20kb会转换base64之后可以和页面一起被请求’
      {
        test: /\.(jpe?g|png|gif|svg)$/, //图片
        test: /\.(eot|ttf|woff2?)$/, //字体文件
        loader: "file-loader" | "url-loader",
        options: {
          limit: 20 * 1024,
          name: "img/[name].[hash:8]/[ext]"
        }
      },
      // 2.webpack5之后:
      // 资源模块类型(asset module type),来替代上面的这些loader
      // 2.1.file-loader的name
      // 方式一:修改output,添加assetModuleFilename属性;
      // 方式二:在Rule中,添加一个generator属性,并且设置filename;
      // 2.2.url-loader的limit效果
      // 步骤一:将type修改为asset;
      // 步骤二:添加一个parser属性,并且制定dataUrl的条件,添加maxSize属性;
      {
        test: /\.(jpe?g|png|gif|svg)$/,
        type: "asset",
        generator: {
          filename: "img/[name]_[hash:6][ext]",
        },
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024,
          },
        },
      },
      {
        test: /\.(eot|ttf|woff2?)$/,
        type: "asset/resource",
        generator: {
          filename: "font/[name]_[hash:6][ext]",
        },
      },
    ],
  },

2. babel-loader

用法一:通过命令行使用

运行多个babel插件

1.安装@babel/core(babel的核心代码)和@babel/cli
npm install @babel/cli @babel/core -D
2.转换箭头函数
npm install @babel/plugin-transform-arrow-functions -D
npx babel src --out-dir dist --plugins=@babel/plugin-transform-arrow-functions
3.const 转成 var
npm install @babel/plugin-transform-block-scoping -D
npx babel src --out-dir dist --plugins=@babel/plugin-transform-block-scoping

运行一个babel插件(预设)

1.安装@babel/preset-env预设:
npm install @babel/preset-env -D
2.执行:npx babel src --out-dir dist --presets=@babel/preset-env

用法二:通过babel-loader设置

      非预设
     {
        test: /\.js$/,
        loader: "babel-loader",
        options: {
          plugins: [
            "@babel/plugin-transform-block-scoping",
            "@babel/plugin-transform-arrow-functions"
          ]
        }
      },
      
      预设
       {
        test: /\.js$/,
        loader: "babel-loader",
        options:   {
           presets: [
              "@babel/preset-env"
             ]
          } 
      },
     

执行原理:

babel原理.png 原生的代码会先被词法分析器进行分割然后生成tokens数组,生成的数组会经过语法分析,分析出哪些是关键字、运算符,并生成抽象语法树,对树进行遍历,对遍历时访问的不同关键字/运算符使用不同的插件转化,转化完生成新的树,最后生成目标源代码。

3.vue.loader

在Vue的开发过程中我们有三种方式来编写DOM元素:

方式一:template模板的方式,只有vue.esm-bundler.js的vue版本才能编译

import {createApp} from "vue/dist/vue.sem-bundler"
const App = createApp({
  template: "#my-app",
  components: {},
  data() {
    return {
      title: "Hello World",
      message: "哈哈哈"
    }
  }
});
const app = createApp(App);
app.mount("#app");

方式二:h函数直接就可以返回Vnode节点,然后通过render函数就可以编写DOM元素

方式三:通过.vue文件中的template来编写模板;则需要用vue-loader编译template

.vue的打包过程:
1.安装vue-loader
npm install vue-loader -D
2.在webpack.config.js中进行配置loader:
{
test:/\.vue$/,
loader:"vue-loader"
}
3.在webpack.config.js中进行配置对应的Vue插件
{ 
const {VUeLoaderPlugin} = require('vue-loader/dist/index');
new VueLoaderPlugin()
}

3、Plugin

  • Plugin是一种计算机应用程序,它和主应用程序互相交互,以提供特定的功能。

  • webpack中的plugin赋予其各种灵活的功能,例如打包优化,资源管理,环境变量注入等,它们会运行在webpack的不同阶段(钩子/生命周期),贯穿了webpack整个编译周期。目的在于解决loader无法实现的其他事。

  // plugins是用于打包优化、资源管理、环境变量注入等;
  plugins: [
    //开启热更新HMR
    new webpack.HotModuleReplacementPlugin(),
  
    //CleanWebpackPlugin:重新打包时,会自动删除dist文件夹并重新生成新的
    new CleanWebpackPlugin(),

    //HtmlWebpackPlugin:自定义HTML模板,
    //在打包结束后,自动生成一个html文件,并把打包生成的js模块引入到该html中
    //[template]:指定我们要使用的模块所在的路径
    new HtmlWebpackPlugin({
      template: "./public/index.html",
      title: "哈哈哈哈"
    }),

    //DefinePlugin允许在编译时创建配置的全局常量
    new DefinePlugin({
      BASE_URL: "'./'",
      __VUE_OPTIONS_API__: true,//是否支持vue2的options
      __VUE_PROD_DEVTOOLS__: false//是否在生产环境开启source-map
    }),
    
    //CopyWebpackPlugin在vue的打包过程中,public的目录下的文件会被复制到dist文件夹中
    // from:设置从哪一个源中开始复制
    // to:复制到的位置,可以省略,会默认复制到打包的目录下;
    // globOptions:设置一些额外的选项,其中可以编写需要忽略的文件:
    new CopyWebpackPlugin({
      patterns: [{
        from: "public",
        to: "./",
        globOptions: {
          ignore: [
            "**/index.html"
          ]
        }
      }]
    }),
    new VueLoaderPlugin()
  ],

4、dev-server(配置webpack-dev-server)

1、为什么要搭建本地服务器并开启热更新?

因为在浏览器运行代码有三种方式

  • 1.通过npm run build编译的代码通过live server打开index.html。

不会监听文件变化 ;通过live server监听后自动刷新浏览器;不可实现部分更新

  • 2.通过webpack watch mode;

会监听文件变化 ;通过live server监听后自动刷新浏览器;不可实现部分更新

配置中添加 watch: true;或者scripts:{ "watch":"webpack -- watch"}

  • 3.通过webpack-dev-server

会监听文件变化 ;通过开启HMR不会刷新浏览器;可实现部分更新

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

2、webpack的[热更新]是如何做到的?原理是什么?

开启HMR模块热替换? HMR全称Hot Module Replacement,可以理解为模块热替换,指在应用程序运行过程中,替换,添加,删除模块,而无需重新刷新整个应用。例如,我们在应用运行过程中修改了某个模块,通过自动刷新会导致整个应用的整体刷新,那页面中的状态信息都会丢失,如果使用的是HMR,就可以实现只将修改的模块实时替换至应用中,不必完全刷新整个应用。

如何配置HRM:

1.修改webpack.config.js配置

devServer:{
hot:true
}

2.指定哪些模块发生更新时,进行HMR

  • module.hot.accept("./js/element.js");指定模块
  • vue.loader会在js文件中自动添加上面代码

原理:

1.通过webpack-dev-server创建两个服务器:提供静态资源的服务Express和Socket服务;

2.express server负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析)

3.socket server 是一个websocket的长连接,双方可以通信

4.当 socket server 监听到对应的模块发生变化时,会生成两个文件.json(manifest文件)和.js文件(update chunk)

5.通过长连接,socket server 可以直接将这两个文件主动发送给客户端(浏览器)

6.浏览器拿到两个新的文件后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新

QQ截图20230402022755.png

5、全部配置代码:

const path = require("path");
const {
  CleanWebpackPlugin
} = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const {
  DefinePlugin
} = require("webpack");
const CopyWebpackPlugin = require('copy-webpack-plugin');
const {
  VueLoaderPlugin
} = require('vue-loader/dist/index');

module.exports = {
  // 设置模式
  // development 开发阶段, 会将DefinePlugin中process.env.NODE_ENV设置为development
  // production 准备打包上线的时候, 设置production
  mode: "development",
  // 设置source-map, 建立js映射文件, 方便调试代码和错误
  devtool: "source-map",
  lintOnSave,
  //lintOnSave是否每次保存时开启eslint编译;
  //1.false:关闭 2.true / warning:错误将显示到控制台命令行,而且编译并不会失败。
  //3.default/error:错误将显示到浏览器页面上,且编译失败。
  transpileDependencies: ["vux"], //兼容ie浏览器,指定node-module里面哪些依赖需要编译
  entry: "./src/main.js",
  assetsDir: 'static', // 放置静态资源
  output: {
    path: path.resolve(__dirname, "./build"),
    filename: "js/bundle.js",
    assetModuleFilename: "img/[name]_[hash:6][ext]"
  },
  //host:主机地址,默认是localhost(127.0.0.1)回环地址,不能通过ip被访问;
  //设置为0.0.0.0时在同一个网段下的主机中,通过ip地址可以访问。
  //port:端口号,默认是8080
  //open:是否打开浏览器,默认是true
  //compress:是否为静态文件开启gzip程序压缩 ,成功开启后将大大优化vue首页加载时长
  //proxy:用于开发阶段解决跨域问题的方法,生产阶段一般用代理服务器以解决跨域访问的问题:
  devServer: {
    contentBase: "./public",
    hot: true,
    host: "0.0.0.0",
    port: 7777,
    open: true,
    compress: true,
    proxy: {
      "/api": {
        target: "http://localhost:8888", //要代理的目标地址 
        pathRewrite: {
          "^/api": "" //默认情况下,/api会被写入到URL中,需要删除
        },
        secure: false, //默认不接受转发https且无证书的服务器上
        //http和https的区别:https://blog.csdn.net/weixin_43975162/article/details/126885163 
        changeOrigin: true, //是否更新代理后请求的header中host地址,防止抓包
        historyApiFallback: {
          rewrites: {
            from: "index.html"
          }
        }
      }
    }
  },
  //resolve用于设置模块如何被解析 
  resolve: {
    modules: ["node_modules"], //设置模块的目录
    mainFiles: ["index"],//引入文件时只需要引入对应文件夹,会默认拿index加下面后缀
    extensions: [".js", ".json", ".mjs", ".vue", ".ts", ".jsx", ".tsx"], //按照顺序去加后缀名去加载无后缀的引入
    alias: {
      "@": path.resolve(__dirname, "./src"),
      "js": path.resolve(__dirname, "./src/js")
    }
  },
  // loader是用于特定的模块类型进行转换;
  module: {
    rules: [{
        test: /\.css$/,
        use: ["style-loader", "css-loader", "postcss-loader"],
      },
      {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
      // 一、理图片和字体等资源
      // 1.webpack5之前:
      // 1.1 file-loader/url-loader共有属性:
      // [ext]: 处理文件的扩展名;
      // [name]:处理文件的名称;
      // [hash]:文件的内容,使用MD4的散列函数处理,生成的一个128位的hash值(32个十六进制);
      // [contentHash]:在file-loader中和[hash]结果是一致的(在webpack的一些其他地方不一样,后面会讲到);
      // [hash:<length>]:截图hash的长度,默认32个字符太长了;
      // [path]:文件相对于webpack配置文件的路径;
      // 1.2 url-loader的特有属性:
      // [limit]: 1.url-loader对图片是否转base64的限制;小于20kb会转换base64之后可以和页面一起被请求’
      {
        test: /\.(jpe?g|png|gif|svg)$/, //图片
        test: /\.(eot|ttf|woff2?)$/, //字体文件
        loader: "file-loader" | "url-loader",
        options: {
          limit: 20 * 1024,
          name: "img/[name].[hash:8]/[ext]"
        }
      },
      // 2.webpack5之后:
      // 资源模块类型(asset module type),来替代上面的这些loader
      // 2.1.file-loader的name
      // 方式一:修改output,添加assetModuleFilename属性;
      // 方式二:在Rule中,添加一个generator属性,并且设置filename;
      // 2.2.url-loader的limit效果
      // 步骤一:将type修改为asset;
      // 步骤二:添加一个parser属性,并且制定dataUrl的条件,添加maxSize属性;
      {
        test: /\.(jpe?g|png|gif|svg)$/,
        type: "asset",
        generator: {
          filename: "img/[name]_[hash:6][ext]",
        },
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024,
          },
        },
      },
      {
        test: /\.(eot|ttf|woff2?)$/,
        type: "asset/resource",
        generator: {
          filename: "font/[name]_[hash:6][ext]",
        },
      },

      //Babel是一个工具链,主要用于旧浏览器或者环境中将ECMAScript 2015+代码转换为向后兼容版本的 JavaScript;
      // 包括:语法转换、源代码转换等;(将箭头函数转换为普通函数,)

      {
        test: /\.js$/,
        loader: "babel-loader",
        options: {
          plugins: [
            "@babel/plugin-transform-block-scoping",
            "@babel/plugin-transform-arrow-functions"
          ]
        }
      },
      {
        test: /\.vue$/,
        loader: "vue-loader"
      }
    ],
  },
  // plugins是用于打包优化、资源管理、环境变量注入等;
  plugins: [
    //开启热更新HMR
    new webpack.HotModuleReplacementPlugin(),

    //CleanWebpackPlugin:重新打包时,会自动删除dist文件夹并重新生成新的
    new CleanWebpackPlugin(),

    //HtmlWebpackPlugin:自定义HTML模板
    //[template]:指定我们要使用的模块所在的路径
    new HtmlWebpackPlugin({
      template: "./public/index.html",
      title: "哈哈哈哈"
    }),

    //DefinePlugin允许在编译时创建配置的全局常量
    new DefinePlugin({
      BASE_URL: "'./'",
      __VUE_OPTIONS_API__: true,
      __VUE_PROD_DEVTOOLS__: false
    }),

    //CopyWebpackPlugin在vue的打包过程中,public的目录下的文件会被复制到dist文件夹中
    // from:设置从哪一个源中开始复制
    // to:复制到的位置,可以省略,会默认复制到打包的目录下;
    // globOptions:设置一些额外的选项,其中可以编写需要忽略的文件:
    new CopyWebpackPlugin({
      patterns: [{
        from: "public",
        to: "./",
        globOptions: {
          ignore: [
            "**/index.html"
          ]
        }
      }]
    }),
    new VueLoaderPlugin()
  ],
};

四、基于webpack的脚手架vue-cli

1.安装和使用(内置了webpack相关的配置)

   1.安装Vue CLI
   npm install @vue/cli -g
   2.升级Vue CLI:
   npm update @vue/cli -g
   3.通过Vue的命令来创建项目
   vue create 项目的名称
   

QQ截图20230402190201.png

QQ截图20230402190211.png

QQ截图20230402190358.png

五、Vite(比webpack快的构建工具)

1.介绍

1734761-20210917181616590-1171327741.png

2.解析文件

  • vite可以直接支持css的处理
  • vite可以直接支持css预处理器,比如less
  • vite直接支持postcss的转换:
  • vite对TypeScript是原生支持的,它会直接使用ESBuild来完成编译: 这些都不需要想webpack一样还有下载很多loader了,vite直接就支持;
  • vite不可以直接解析.vue文件,需要插件支持:@vitejs/plugin-vue

3.vite和webpack区别是什么?为什么vite更快?

1.区别是什么?

webpack无论是开发(热加载)还是构建(打包代码),都是一个流程——生成依赖图,打包代码到内存memfs或者生成dist文件;

而vite有两套系统,开发时用第一个保证快,生产时用第二个保证稳;

  • 一个开发服务器,它基于 原生 ES 模块 提供了 丰富的内建功能,如速度快到惊人的 模块热更新(HMR)

  • 一套构建指令,它使用 Rollup 打包你的代码,并且它是预配置的,可以输出用于生产环境的优化过的静态资源。

    vite在生产环境时仍需要打包成es5,毕竟有些浏览器可能还是不支持esm

  1. vite在开发时快的原因:
  • 第一,vite基于的浏览器必须支持原生es-module;所以vite不会在开发时将你的es6等高阶语法转化为es5;

  • 第二,使用基于go语言的esbuild进行依赖的预构建;

  • 第三,源码按需加载

六、基于vite的脚手架@vitejs/create-app

  • 这是个vue脚手架,快速创建vue项目的;npm安装好之后,使用create-app命令来初始化项目;

  • 和基于webpack的vuecli差不多;

1734761-20210917181141315-104998098.png