webpack5 基础打包

一、开始

1. webpack简介

本笔记中Webpack版本为Webpack5。

1.1 官方的解释

webpack is a static module bundler for modern JavaScript applications

webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)

官网图:

image-20220914170417454

webpack在处理应用程序时,它会根据命令或者配置文件找到入口文件,从入口开始,会生成一个 依赖关系图,这个依赖关系图会包含应用程序中所需的所有模块(比如.js文件、css文件、图片、字 体等),然后遍历图结构,打包一个个模块(根据文件的不同使用不同的loader来解析)。

  • 创建Vue项目、React项目时,都会使用到脚手架(cli),他们都是基于Webpack来帮助我们构建模块化、less、TypeScripts、打包优化。
  • webpack官网:https://webpack.js.org/
  • webpack中文文档官网:https://www.webpackjs.com/

1.2 webpack 使用前提

为电脑安装 NodeJS 环境,Node官网

2. webpack的安装

安装:webpackwebpack-cli

# 全局安装-----------意味着在自己电脑上本地安装
npm i webpack webpack-cli -g

# 局部安装(开发依赖)------- 在某个项目上安装
npm i webpack webpack-cli -D

两者间的关系:

  • 在项目根路径下执行 webpack 命令,会执行node_modules下的 .bin 目录下的webpack;
  • webpack在执行时是依赖webpack-cli的,如果没有安装就会报错;
  • 而webpack-cli中代码执行时,才是真正利用webpack进行编译和打包的过程;
  • 所以在安装webpack时,我们需要同时安装webpack-cli(第三方的脚手架事实上是没有使用webpack-cli的,而是类似于自己的vue-service-cli的东西)

3.webpack的默认打包

前提:局部的webpack演示项目

  1. npm init 创建package.json 管理当前项目的信息及依赖等
  2. npm i webpack webpack-cli -D 在当前文件夹(项目)局部安装webpack

当在项目目录下执行webpacknpx webpack命令时,

# 直接执行 webpack,会去寻找全局的webpack依赖来打包,如果全局未安装则会报错
# webpack
# npx webpack,利用当前node_modules中的依赖进行webpack打包
npx webpack

可在package.json中创建scripts脚本:

"scripts": {
    // 设置之后,可直接使用 npm run build即可运行该脚本,等同于 npx webpack。
    // 为何build的值不是npx webpack却等同于npx webpack的效果,是因为通过运行package.json中的scripts脚本,默认使用当前node_modules中的依赖
    "build": "webpack"
 },

3.1 webpack命令后的打包过程

  • 入口问题:webpack会查找当前目录下的 src/index.js 作为入口,从而开始收集项目运行所需的所有依赖,然后进行打包。如果当前项目中没有存在src/index.js文件,那么会报错;
  • 出口问题:打包完成后,会在目录下生成一个dist文件夹,里面存放一个main.js的文件或其他媒体依赖(img/font等),就是我们打包之后的文件。

配置来指定入口和出口:

npx webpack --entry ./src/main.js --output-path ./build

4. Webpack的配置文件

在通常情况下,webpack需要打包的项目是非常复杂的,并且我们需要一系列的配置来满足要求,默认配置必然是不行的,需要一个专门的文件来进行打包配置,即webpack.config.js

根目录下创建一个webpack.config.js文件,来作为webpack的配置文件:

const path = require("path");

// 导出配置信息
module.exports = {
    // 配置入口信息
    entry: "./src/main.js",
    // 出口信息
    output: {
        path: path.resolve(__dirname, "dist")
    }
}
// 以上配置等同于默认打包
// npm run build 进行打包和默认打包效果一致

二、基本案例-loader

1. css-loader

1.1 目录结构

├── dist (打包出口文件夹)
├── mode_modules
├── src
|   ├── css
|   |   ├── style.css
|   ├── js
|   |   ├── element.js
|   ├── main.js
├── index.html
├── webpack.config.js

1.2 代码

  • style.css

    .title{
        /* color: red; */
        /* 最新CSS颜色,后面两位表示透明度 */
        color: #12345678;
        font-size: 30px;
        font-weight: 600;
        user-select: none;
    }
    
  • element.js

    import "../css/style.css";
    
    const title = document.createElement("div");
    title.className = "title";
    title.innerHTML = '你好啊,李大钊'
    
    document.body.append(title);
    
  • main.js

    import './js/element';
    
  • 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="./dist/main.js"></script>
    </body>
    </html>
    

1.3 编译

运行:

npm run build

运行结果:报错,原因是./src/css/style.css模块解析失败,需要一个合适的 loader 来处理这种文件。Webpack默认可以解析处理JS文件,但不能直接处理css文件。

1.4 loader是什么

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

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

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

1.5 css-loader

加载css文件,就需要处理css文件的loader。常用的是:css-loader

# 安装css-loader
npm install css-loader -D
1.5.1 配置信息

通过在webpack.config.js中配置,在module.rules中。

  • module为对象,rules为对象数组,其中每一个对象可设置属性
    • test属性:用于对 resource(资源)进行匹配的,通常会设置成正则表达式
    • use属性:为数组,数组每一项为对象或字符串
      • 数组项为对象时:
        • loader::必须有一个loader属性,对应的值是一个字符串;
        • options:可选的属性,值是一个字符串或者对象,值会被传入到loader中
      • 字符串时,如:use: ['style-loader']是 loader 属性的简写方式(如:use: [{loader:'style-loader'}]
    • loader属性: use: [{loader:'style-loader'}] 的简写

1.5.2 配置代码

  • webpack.config.js
const path = require('path');

module.exports = {
  entry: "./src/main.js",
  output: {
    // 输出出口路径
    path: path.resolve(__dirname, "./build"),
    // 出口JS文件名
    filename: "bundle.js"
  },
  module: {
    rules: [
      {
        test: /\.css$/, //正则表达式
        // loader的写法(语法糖)
        // loader: "css-loader" // 写法1
        // use: ["css-loader"] // 写法2
        // 写法3
        use: [
          { loader: "css-loader" }
         ]
      }
    ]
  }
}

通过css-loader来加载css文件了,但这个css在我们的代码中并没有生效(页面没有效果),这是因为css-loader只是负责将.css文件进行解析,并不会将解析之后的css插入到页面中。如果我们希望再完成插入样式style的操作,那么我们还需要另外一个loader,就是style-loader。

2. style-loader

2.1 安装

npm install style-loader -D

2.2 配置 style-loader

  • 在配置文件中,添加style-loader

  • 注意:因为loader的执行顺序是从右向左(或者说从下到上,或者说从后到前的),所以我们需要将style-loader写到css-loader的前面;

    module: {
        rules: [
          {
            test: /\.css$/, //正则表达式
            //use: [
            //  "style-loader",
            //  "css-loader",
            //],
            use: [
              { loader: "style-loader" },
              { loader: "css-loader" }
           ]
    }
    
  • 重新执行编译npm run build,可以发现打包后的css已经生效了。

问题:

开发中,我们可能会使用less、sass等预处理器来编写css样式,那 Webpack如何解析这些文件呢?

首先我们需要确定:less、sass等编写的css需要通过工具转换成普通的css,然后css被浏览器识别,从而得到相应的样式。

3. less-loader

处理 less预处理器的语言。

3.1 安装

npm install less-loader -D

3.2 配置

{
    test: /\.less$/,
    use: [
        "style-loader",
        "css-loader",
        "less-loader"
    ]
},

执行npm run build,less就会被转换成css,然后css被解析,随后style样式生效。

认识PostCSS工具

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

4. 加载图片

4.1 目录结构

├── dist (打包出口文件夹)
├── mode_modules
├── src
|   ├── css
|   |   ├── img.css
|   ├── img
|   |   ├── bilibili-1.png
|   |   ├── wlop.jpg
|   ├── js
|   |   ├── element.js
|   ├── main.js
├── index.html
├── webpack.config.js

4.2 代码

  • img.css

    .bg-div {
      background-image: url('../img/wlop.jpg');
      width: 200px;
      height: 200px;
      background-size: contain;
    }
    
  • element.js

    import "../css/img.css"
    // 图片src要正确引入,需要将所需图片当模块引入
    import bilibiliImg from '../img/bilibili-1.png'
    
    // 背景图片 div
    const bgDivElement = document.createElement("div");
    bgDivElement.className = "bg-div";
    
    // 图片src
    const imgEve = document.createElement('img');
    /**
     * 直接赋值在HTML中不能正确渲染图片,因为写死的相对路径下找不到对应的图片
     */
    // imgEve.src = "../img/bilibili-1.png";
    imgEve.src = bilibiliImg;
    
    document.body.append(bgDivElement);
    document.body.append(imgEve);
    
  • 其他文件(index.html、main.js),同上

💡🔥 webpack4,使用 file-loaderurl-loader进行处理图片,而Webpack5则不使用这些,使用资源模块类型

4.3 file-loader

要处理jpg、png等格式的图片,我们也需要有对应的loader:file-loader,file-loader的作用就是帮助我们处理import/require()方式引入的一个文件资源,并且会将它放到我们输出的文件夹中。

4.3.1 安装

npm install file-loader -D

4.3.2 配置

文件的命名规则:

  • 有时候我们处理后的文件名称按照一定的规则进行显示:
    • 比如保留原来的文件名、扩展名,同时为了防止重复,包含一个hash值等;
  • 我们可以使用PlaceHolders来完成,webpack给我们提供了大量的PlaceHolders来显示不同的内容
  • 介绍几个最常用的placeholder:
    • [ext]: 处理文件的扩展名;
    • [name]:处理文件的名称;
    • [hash]:文件的内容,使用MD4的散列函数处理,生成的一个128位的hash值(32个十六进制);
    • [contentHash]:在file-loader中和[hash]结果是一致的(在webpack的一些其他地方不一样,后面会讲到);
    • [hash:]:截取hash的长度,默认32个字符太长了;
    • [path]:文件相对于webpack配置文件的路径;
{
    test: /\.(jpe?g|png|gif|svg)$/,
    use: {
        loader: "file-loader",
        options: {
            // 设置文件存储的位置
            // outputPath: "img"
            // 等同于以下
            name: "img/[name]-[hash:6][ext]"
        }
    }
},

4.4 url-loader

url-loader和file-loader的工作方式是相似的,但是可以将较小的文件,转成base64的URI。

4.4.1 安装
npm install url-loader -D
4.4.2 配置

npm run build后,在dist文件夹中,我们会看不到图片文件,默认情况下url-loader会将所有的图片文件转成base64编码。

但是开发中我们往往是小的图片需要转换,但是大的图片直接使用图片即可,这是因为小的图片转换base64之后可以和页面一起被请求,减少不必要的请求过程,而大的图片也进行转换,反而会影响页面的请求速度。

通过limit属性进行限制哪些大小的图片转换和不转换

{
    test: /\.(jpe?g|png|gif|svg)$/,
    use: {
        loader: "url-loader",
        options: {
            // 设置文件存储的位置
            outputPath: "img",
            name: "[name]-[hash:6][ext]",
            // 小于100kb的图片进行base64编码
            limit: 100 * 1024
        }
    }
}

4.5 资源模块类型(asset module type)

webpack5之前,加载这些资源我们需要使用一些loader,比如raw-loader 、url-loader、file-loader;

webpack5开始,我们可以直接使用资源模块类型(asset module type),来替代上面的这些loader;

  • 资源模块类型(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,并且配置资源体 积限制实现;

4.5.1 配置

举例使用 asset

const path = require('path');

module.exports = {
    entry: "./src/main.js",
    output: {
        path: path.resolve(__dirname, "./build"),
        filename: "bundle.js",
        // 通过asset打包的文件名
        // assetModuleFilename: "img/[name]-[hash:6][ext]"
    },
    module: {
        rules: [
            {
                test: /\.(jpe?g|png|gif)$/,
                type: "asset",
                //解析
                parser: {
                    //转base64的条件
                    dataUrlCondition: {
                        maxSize: 100 * 1024, // 约定大小
                    }
                },
                generator: {
                    // outputPath: 'img'
                    // 与outputPath是相同的,这个写法引入的时候也会添加好这个路径
                    // 与在output出口配置assetModuleFilename属性一致 ↑↑↑↑
                    filename: 'img/[name]-[hash:6][ext]',
                },
            }
        ]
    }
}

5. 加载字体文件

字体资源可从阿里巴巴矢量库中下载。前提:Webpack5

5.1 目录结构

├── dist (打包出口文件夹)
├── mode_modules
├── src
|   ├── font
|   |   ├── iconfont.css
|   |   ├── iconfont.eot
|   |   ├── iconfont.ttf
|   |   ├── iconfont.woff
|   |   ├── iconfont.woff2
|   ├── js
|   |   ├── element.js
|   ├── main.js
├── index.html
├── webpack.config.js

5.2 代码

  • element.js

    // 引入字体css
    import "../font/iconfont.css";
    
    // font字体的使用
    const iEl = document.createElement("i");
    iEl.className = "iconfont icon-caps-lock";
    
    document.body.append(iEl);
    
  • webpack.config.js

    {
        test: /\.(eot|ttf|woff2?)$/,
        // webpack4
        // use: {
        //     loader: "file-loader",
        //     options: {
        //         name: "font/[name]-[hash:6].[ext]"
        //     }
        // }
        // webpack5
        type: "asset/resource",
        generator: {
            filename: "font/[name]-[hash:6][ext]"
        }
    }
    
  • 其他文件同上

三、插件Plugin

1.认识Plugin

Webpack官方描述:

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 可以用来执行更广泛的任务,如打包优化、资源管理和环境变量注入。

2. CleanWebpackPlugin

前面的案例中,每npm run build打包前,都需要手动删除之前打包的文件夹(如:默认的dist文件夹)。

我们可以利用一个插件来帮我们自动删除----CleanWebpackPlugin

2.1 安装

npm i clean-webpack-plugin -D

2.2 使用

  • webpack.config.js

    const path = require("path");
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    
    module.exports = {
        entry: "./src/main.js",
        output: {
            path: path.resolve(__dirname, "build"),
            // 设定文件夹名和打包的JS文件名
            filename: "js/build.js"
        },
        module: {
            rules: [
              //{} 匹配规则对象
            ]
        },
        plugins: [
            // 通过new一个新的实例
            new CleanWebpackPlugin()
        ]
    }
    

3.HtmlWebpackPlugin

在之前的案例中,我们的index.html文件在项目根目录下,而最终打包的dist文件夹下则没有index.html文件,当进行项目部署时,必然是需要有对应的入口文件index.html,所以我们需要对index.html进行打包处理。

3.1 安装

npm i html-webpack-plugin -D

3.2 使用

使用 HtmlWebpackPlugin 插件可不在项目根目录下新建index.html文件,插件可根据项目依赖自动创建对应的index.html文件在打包文件夹中。

  • webpack.config.js

    const path = require("path");
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    module.exports = {
        entry: "./src/main.js",
        output: {
            path: path.resolve(__dirname, "build"),
            // 设定文件夹名和打包的JS文件名
            filename: "js/build.js"
        },
        module: {
            rules: [
              //{} 匹配规则对象
            ]
        },
        plugins: [
            // 通过new一个新的实例
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin()
        ]
    }
    
  • 新生成的index.html文件(压缩过后的,js引用的为js文件夹下的build.js)

    <!doctype html><html><head><meta charset="utf-8"><title>Webpack App</title><meta name="viewport" content="width=device-width,initial-scale=1"><script defer="defer" src="js/build.js"></script></head><body></body></html>
    

    这个文件是如何生成的呢?

    • 默认情况下是根据ejs的一个模板来生成的;
    • 在html-webpack-plugin的源码中,有一个default_index.ejs模块;

3.3 自定义HTML模板

如果我们想在自己的模块中加入一些比较特别的内容:

  • 比如添加一个noscript标签,在用户的JavaScript被关闭时,给予响应的提示;
  • 比如在开发vue或者react项目时,我们需要一个可以挂载后续组件的根标签;

这时就需要一个属于自己的index.html模版

  • 目录结构

    ├── dist (打包出口文件夹)
    ├── mode_modules
    ├── public
    |   ├── index.html
    |   ├── favico.hico
    ├── src
    |   ├── main.js
    ├── webpack.config.js
    
  • index.html模板---例子如下:

    <!DOCTYPE html>
    <html lang="">
      <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">
        <!-- 在webpack中的 DefinePlugin 中配置变量 -->
        <link rel="icon" href="<%= BASE_URL %>favicon.ico">
        <!-- 在webpack中的plugin中配置title变量 -->
        <title><%= htmlWebpackPlugin.options.title %></title>
      </head>
      <body>
        <noscript>
          <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
        </noscript>
        <div id="app"></div>
        <!-- built files will be auto injected -->
      </body>
    </html>
    

    上面的代码中,会有一些类似这样的语法<% 变量 %>,这个是EJS模块填充数据的方式。

  • webpack.config.js

    // DefinePlugin允许在编译时创建配置的全局常量,是一个webpack内置的插件(不需要单独安装):
    // HTML模板中 BASE_URL 变量的设置
    const { DefinePlugin } = require("webpack");
    
    plugins: [
        new HtmlWebpackPlugin({
            // 指定HTML模板的位置
            template: "./public/index.html",
            // HTML模板中 htmlWebpackPlugin.options.title 对应的变量
            title: "哈哈哈哈"
        }),
        // 设置变量
        new DefinePlugin({
            BASE_URL: "'./'"
        }),
    ]
    

4.CopyWebpackPlugin

在vue的打包过程中,如果我们将一些文件放到public的目录下,那么这个目录会被复制到dist文件夹中。

这个复制的功能,我们可以使用CopyWebpackPlugin来完成;

4.1 安装

npm install copy-webpack-plugin -D

4.2 配置

  • 复制的规则在patterns中设置:
    • from:设置从哪一个源中开始复制;
    • to:复制到的位置,可以省略,会默认复制到打包的目录下;
    • globOptions:设置一些额外的选项,其中可以编写需要忽略的文件:
      • .DS_Store:mac目录下回自动生成的一个文件;
      • ndex.html:也不需要复制,因为我们已经通过HtmlWebpackPlugin完成了index.html的生成;
plugins: [
    new CopyWebpackPlugin({
      patterns: [
        {
          from: "public",
          to: "./",
          globOptions: {
            ignore: [
              "**/index.html"
            ]
          }
        }
      ]
    })
]

四、其他配置或工具

1. 调试源码配置

默认情况下,我们写完代码然后将其打包,当编写的代码有错误时,在浏览器控制台上查找错误,将会指到打包后的JS文件上,而不知道具体哪个源文件出错。

因此得加以配置,更好的调试项目。

module.exports = {
    // 设置模式
    // development 开发阶段使用
    // production 准备打包上线时使用
    mode: "development",
    // 建立js映射文件,方便调试代码和报错
    devtool: "source-map"
}

2.PostCSS工具

2.1 PostCSS是什么

  • PostCSS是一个通过JavaScript来转换样式的工具;
  • 这个工具可以帮助我们进行一些CSS的转换和适配,比如自动添加浏览器前缀、css样式的重置;
  • 但是实现这些功能,我们需要借助于PostCSS对应的插件;

2.2 使用PostCSS

  1. 查找PostCSS在构建工具中的扩展,比如webpack中的postcss-loader;
  2. 选择可以添加你需要的PostCSS相关的插件;

2.3 命令行使用postcss

不借助webpack也能在终端使用,需要安装工具postcss-cli。

npm install postcss postcss-cli -D

2.3.1 自动实现添加前缀的CSS

初始css样式:

.content {
    user-select: none;
}

**使用autoprefixer插件和postcss工具: **

# 安装autoprefixer插件
npm install autoprefixer -D

# 直接使用使用postcss工具,并且制定使用autoprefixer
npx postcss --use autoprefixer -o end.css ./src/css/style.css

**转化后的css如下: **

.content {
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

2.4 postcss-loader

真实开发中我们必然不会直接使用命令行工具来对css进行处理,而是可以借助于构建工具。在webpack中使用postcss就是使用postcss-loader来处理的;

安装:

npm install postcss-loader -D

使用:

我们修改加载css的loader,因为postcss需要有对应的插件才会起效果,所以我们需要配置它的plugin。(需要下载插件)

module: {
    rules: [
      {
        test: /\.css$/, //正则表达式
        use: [
          "style-loader",
          "css-loader",
          // "postcss-loader"
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  require("autoprefixer")
                ]
              }
            }
          }
        ]
      }
    ]
}

2.5 单独的postcss配置文件

我们也可以将这些配置信息放到一个单独的文件中进行管理:

  • 将webpack.config.js中的postcss-loader的配置信息删掉,只指定使用postcss-loader

    module: {
        rules: [
          {
            test: /\.css$/, //正则表达式
            use: [
              "style-loader",
              "css-loader",
              "postcss-loader"
            ]
          }
        ]
    }
    
  • 在根目录下创建postcss.config.js

  • module.exports = {
      plugins: [
        require("autoprefixer")
      ]
    }
    

2.6 postcss-preset-env

事实上,在配置postcss-loader时,我们配置插件并不需要使用autoprefixer

我们可以使用另外一个插件:postcss-preset-env

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

安装:

npm install postcss-preset-env -D

使用:

  • postcss.config.js文件中修改掉之前的autoprefixer即可

    module.exports = {
      plugins: [
        require("postcss-preset-env")
      ]
    }
    

五、Babel

1. 认识Babel

事实上,在开发中我们很少直接去接触babel,但是babel对于前端开发来说,目前是不可缺少的一部分:

  • 开发中,我们想要使用ES6+的语法,想要使用TypeScript,开发React项目,它们都是离不开Babel的;
  • 所以,学习Babel对于我们理解代码从编写到线上的转变过程至关重要;

1.1 Babel是什么

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

// 例如箭头函数转换
[1, 2, 3].map((n) => n + 1);

[1, 2, 3].map(function(n) {
    return n + 1;
});

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

2. Babel命令行使用

2.1 安装库及基本配置

  • 如果我们希望在命令行尝试使用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
      
      # 指定文件
      npx babel src --out-file dist
      
  • Babel官网

2.2 插件的使用

比如我们需要转换箭头函数,那么我们就可以使用箭头函数转换相关的插件

# 安装插件
npm install @babel/plugin-transform-arrow-functions -D

# 指定使用插件
npx babel src --out-dir dist --plugins=@babel/plugin-transform-arrow-functions

查看转换后的结果:我们会发现其中 const 并没有转成 var

  • 这是因为 plugin-transform-arrow-functions,并没有提供这样的功能;

  • 我们需要使用 plugin-transform-block-scoping 来完成这样的功能;

    # 安装插件
    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
    

2.3 Babel的预设preset

就如上述插件的使用而言,仅仅使用两个插件就需要很长的命令行来执行,当转换的内容过多时,设置颇为麻烦,我们可以使用预设(preset)。

# 安装@babel/preset-env预设:
npm install @babel/preset-env -D

# 执行如下命令:
npx babel src --out-dir dist --presets=@babel/preset-env

3.babel-loader

在实际开发中,我们通常会在构建工具中通过配置babel来对其进行使用的,比如在webpack中。

安装依赖:

npm install babel-loader @babel/core -D

webpack.config.js中配置

module: {
    rules: [
        {
            test: /\.js$/,
            use: {
                loader: "babel-loader"
            }
        }
    ]
}

同样的我们必须指定相应使用得插件才能生效:

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

3.1 babel-preset

如果我们一个个去安装使用插件,那么需要手动来管理大量的babel插件,我们可以直接给webpack提供一个 preset,webpack会根据我们的预设来加载对应的插件列表,并且将其传递给babel。

比较常见的预设有三个:

  • env
  • react
  • Typescript

安装preset-env:

npm install @babel/preset-env -D

使用:

{
    test: /\.js$/,
    use: {
      loader: "babel-loader",
      options: {
        presets: [
          "@babel/preset-env"
        ]
      }
    }
  }

4. Babel的配置文件

像之前的postcss配置一样,我们可以将babel的配置信息放到一个独立的文件中---babel.config.js

// babel.config.js
module.exports = {
  presets: [
    "@babel/preset-env"
  ]
}

webpack.config.js

{
    test: /\.js$/,
    loader: "babel-loader"
 }

随后进行打包npm run build,即可发现代码转换成功。

六、webpack中Vue代码的打包

1. JS代码

首先下载Vue包。(Vue3)

npm i vue
  • src目录下的main.js

    import { createApp } from "vue";
    // Vue代码
    createApp({
      template: `
        <h2>Vue3代码</h2>
        <h4>{{msg}}</h4>
      `,
      data() {
        return {
          msg: '你好,陈平安'
        }
      }
    }).mount('#app');
    
  • index.html

    <!DOCTYPE html>
    <html lang="">
      <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">
        <link rel="icon" href="./favicon.ico">
        <title>哈哈哈哈</title>
      <script defer src="js/bundle.js"></script></head>
      <body>
        <noscript>
          <strong>We're sorry but 哈哈哈哈 doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
        </noscript>
        <div id="app"></div>
        <!-- built files will be auto injected -->
      </body>
    </html>
    
  • webpack.config,js

    const path = require("path");
    module.exports = {
      mode: "development",
      // 设置source-map, 建立js映射文件, 方便调试代码和错误
      devtool: "source-map",
      entry: "./src/main.js",
      output: {
        path: path.resolve(__dirname, "./build"),
        filename: "js/bundle.js",
      }
    }
    

1.1 控制台警告

打包过后运行html文件,发现页面上并无效果。打开控制台会有如下警告:

image-20220920151743521

2. Vue打包后的不同版本

  • vue(.runtime).global(.prod).js:
    • 括号表示可省略,runtime表示运行时,.prod表示生成阶段代码处于压缩状态;
    • 通过浏览器中的script src= "......">直接使用;
    • 通过CDN引入和下载的VueJS的版本就是这个版本;
    • 会暴露一个全局的Vue来使用;
  • vue(.runtime).esm-browser(.prod).js:
    • 用于通过原生 ES 模块导入使用 (在浏览器中通过<script type="module"> 来使用)。
  • vue(.runtime).esm-bundler.js:
    • 用于 webpack,rollup 和 parcel 等构建工具;
    • 构建工具中默认是vue.runtime.esm-bundler.js
    • 如果我们需要解析模板template,那么需要手动指定vue.esm-bundler.js;
  • vue.cjs(.prod).js:
    • 服务器端渲染使用;
    • 通过require()在Node.js中使用;

3. 运行时+编译器 vs 仅运行时

  • 在Vue的开发过程中我们有三种方式来编写DOM元素:
    • template模板的方式(之前经常使用的方式);
    • render函数的方式,使用h函数来编写渲染的内容;
    • 通过.vue文件中的template来编写模板;
  • 它们的模板分别是如何处理的呢?
    • 方式一和方式三的template都需要有特定的代码来对其进行解析:
      • 方式一中的template我们必须要通过源码中一部分代码来进行编译
      • 方式三.vue文件中的template可以通过在vue-loader对其进行编译和处理;
    • 方式二中的h函数可以直接返回一个虚拟节点,也就是Vnode节点;
  • 所以,Vue在让我们选择版本的时候分为 运行时+编译器仅运行时
    • 运行时+编译器 包含了对template模板的编译代码,更加完整,但是也更大一些;
    • 仅运行时没有包含对template版本的编译代码,相对更小一些;

因此解决上面控制台警告就得更改引入的Vue版本:

main.js

// import { createApp } from "vue"; // 无页面效果+控制台警告
// 指定 运行时+编译器 的Vue版本
import { createApp } from "vue/dist/vue.esm-bundler";
// Vue代码
createApp({
template: `
 <h2>Vue3代码</h2>
 <h4>{{msg}}</h4>
`,
data() {
 return {
   msg: '你好,陈平安'
 }
}
}).mount('#app');

⬇⬇⬇⬇⬇⬇⬇

3.1 其实控制台上还有一个警告:

image-20220920154613294

点击链接查看详情,进入到GIthub文档:

image-20220920163149857

上述文档表达的意思:

从Vue3.0.0版本开始,使用esm-bundler打包,需要设置全局特征标志

  • __VUE_OPTIONS_API__,表示是否是否支持Options API,默认为TRUE
  • __VUE_PROD_DEVTOOLS__,表示devtools调试工具是否支持生产环境,默认FALSE

这些虽不用配置vue也能正常打包,但是强烈建议手动进行配置

webpack可以在DefinePlugin中配置

webpack.config.js

const { DefinePlugin } = require("webpack");

module.exports = {
  module: {
    rules: [
      // ... 其它规则
    ]
  },
  plugins: [
    // 添加
    new DefinePlugin({
      __VUE_OPTIONS_API__: true,
      __VUE_PROD_DEVTOOLS__: false
    })
  ]
}

4. .vue文件的打包

4.1 新建App.vue文件

<template>
  <div>
    <h2>我是Vue渲染出来的</h2>
    <h2>{{ title }}</h2>
  </div>
</template>

<script>
export default {
  data() {
    return {
      title: 'Hello World',
    };
  }
};
</script>

<style scoped>
h2 {
  color: red;
}
</style>

main.js

import { createApp } from "vue/dist/vue.esm-bundler";
import App from "./vue/App.vue";

// vue代码
createApp(App).mount('#app');

4.2 打包出错

终端提示我们需要合适的Loader来处理App.vue文件。

  1. 使用vue-loader:
# 安装
# npm install vue-loader -D
npm install -D vue-loader vue-template-compiler
  1. webpack.config.js配置
{
  test: /\.vue$/,
  use: "vue-loader"
}
  1. 打包依然会报错,提示你需要使用VueLoaderPlugin插件
// webpack.config.js 配置:
const { VueLoaderPlugin } = require('vue-loader')

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

🎈🎈🎈

通过使用vue-loader加载Vue文件后,我们在之前main.js中引入的vue版本为:

import { createApp } from "vue/dist/vue.esm-bundler";

现由于Vue文件会交给vue-loader和插件VueLoaderPlugin处理,因此,我们可以直接引入vue而不会出错

import { createApp } from "vue";

七、devServe

目前我们开发的代码,为了运行需要有两个操作:

  1. npm run build,编译相关的代码;
  2. 通过live server或者直接通过浏览器,打开index.html代码,查看效果;

这个过程经常操作会影响我们的开发效率,我们希望可以做到,当文件发生变化时,可以自动的完成编译和展示;

为了完成自动编译,webpack提供了几种可选的方式:

  • webpack watch mode;
  • webpack-dev-server(常用);
  • webpack-dev-middleware;

1. Webpack watch

webpack给我们提供了watch模式

  • 在该模式下,webpack依赖图中的所有文件,只要有一个发生了更新,那么代码将被重新编译
  • 我们不需要手动去运行 npm run build指令了;

开启watch:

  • 方式一:在导出的配置中(webpack.config.js),添加 watch: true

    module.exports = {
      // 监听文件改变
      watch: true,
    }
    
  • 方式二:在启动webpack的命令中,添加 --watch的标识;

    package.json

    {
       "scripts": {
        "build": "webpack --watch"
      },
    }
    

2.webpack-dev-server

上面的方式可以监听到文件的变化,但是事实上它本身是没有自动刷新浏览器的功能的:

  • 目前我们可以在VSCode中使用插件live-server来完成这样的功能;
  • 但是插件的实现并不适用所有编辑器,我们想具备live reloading(实时重新加载)的功能,就需要另外一个工具webpack-dev-server

2.1 基本使用

  1. 安装

    npm install webpack-dev-server -D
    
  2. 基本使用

    package.json

    {
      "scripts": {
        "serve": "webpack serve"
      },
    }
    

    终端运行:

    # 运行
    npm run serve
    

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

事实上webpack-dev-server使用了一个库叫memfs,将文件内容读取到内容存中。

2.2 配置选项

2.2.1 static

该配置项允许配置从目录提供静态文件的选项(默认是 'public' 文件夹)。将其设置为 false 以禁用:

官方文档webpack-devServe-static配置

webpack.config.js

module.exports = {
    devServer: {
      // static: false, // 禁用
      // 告诉dev serve从哪取未被webpack打包的静态资源,
      // 浏览器加载html文件,html文件中引入了经过webpack打包的js文件和未经过打包打包的文件
      // Content not from webpack is served from
      // 而未被webpack打包的内容若引入路径为'./abc.js',则会默认从public目录下寻找abc.js文件
      // 如要指定相应的资源路径,则可以设置static
      static: ["./public", "./asset"],
      static: {
        directory: path.join(__dirname, 'public'),
      }
    }
}

2.2.2 模块热替换(HMR)

2.2.2.1 介绍:

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

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

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

  • 不重新加载整个页面,这样可以保留某些应用程序的状态不丢失
  • 只更新需要变化的内容节省开发的时间
  • 修改了css、js源代码,会立即在浏览器更新,相当于直接在浏览器的devtools中直接修改样式;
2.2.2.2 使用:

webpack.config.js

module.exports = {
    devServer: {
      contentBase: "./public",
      hot: true
    }
}

仅配置hot: true,不能实现热模块替换。还需要指定需要监听的模块是哪个:

  • main.js-------webpack入口文件

    import App from './vue/App.vue';
    
    import "./js/element";
    // 监听element.js模块
    if (module.hot) {
      module.hot.accept("./js/element.js", () => {
        console.log('elementJS模块更新了');
      })
    }
    
    const app = createApp(App);
    app.mount("#app");
    
  • element.js

    console.log("更改前");
    
    // npm run serve,新增一句console.log,热更新后在控制台会在之前输出的内容后
    // 继续打印 “更改后” 和 “elementJS模块更新了”
    console.log("更改后");
    

问题:在开发其他项目时,我们是否需要经常手动去写入 module.hot.accpet相关的API呢?

在Vue、React中,其实已经有成熟的解决方案:

  • vue开发中,我们使用vue-loader,此loader支持vue组件的HMR,提供开箱即用的体验;

    之前已经使用过vue-loader。因此可直接修改Vue文件,保存后前往浏览器查看效果。

  • react开发中,有React Hot Loader,实时调整react组件(目前React官方已经弃用了,改成使用reactrefresh);

2.2.2.3 HMR的原理

webpack-dev-server会创建两个服务:提供静态资源的服务(express)Socket服务(net.Socket)

  • express server负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析);
  • Socket Server,是一个socket的长连接
    • 长连接有一个最好的好处是建立连接后双方可以通信(服务器可以直接发送文件到客户端)
    • 当服务器监听到对应的模块发生变化时,会生成两个文件.json(manifest文件)和.js文件(update chunk)
    • 通过长连接,可以直接将这两个文件主动发送给客户端(浏览器);
    • 浏览器拿到两个新的文件后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新

2.2.3 hotOnly、host配置

  • host设置主机地址:

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

    • localhost:本质上是一个域名,通常情况下会被解析成127.0.0.1;
    • 127.0.0.1:回环地址(Loop Back Address),表达的意思其实是我们主机自己发出去的包,直接被自己接收;
      • 正常的数据库包经常 应用层 - 传输层 - 网络层 - 数据链路层 - 物理层 ;
      • 而回环地址,是在网络层直接就被获取到了,是不会经常数据链路层和物理层的;
      • 比如我们监听 127.0.0.1时,在同一个网段下的主机中,通过ip地址是不能访问的;
    • 0.0.0.0:监听IPV4上所有的地址,再根据端口找到不同的应用程序;
      • 比如我们监听 0.0.0.0时,在同一个网段下的主机中,通过ip地址是可以访问的;
  • webpack.config.js设置

    module.exports = {
        devServer: {
          static: ['./src/js', './public'],
          hot: true,
          // 设置主机地址,默认为localhost即127.0.0.1
          host: "0.0.0.0"
        }
    }
    

2.2.4 port、open、compress

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

  • open是否打开浏览器:

    • 默认值是false,设置为true会打开浏览器;
    • 也可以设置为类似于 Google Chrome等值;
  • compress是否为静态文件开启gzip compression:

    • 默认值是false,可以设置为true;
    • 设置为True时,webpack dev serve会将打包的资源进行gzip压缩
  • webpack.config.js

    module.exports = {
        devServer: {
          static: ['./src/js', './public'],
          hot: true,
          // 设置主机地址,默认为localhost即127.0.0.1
          host: "0.0.0.0",
          port: 9999,	// 默认是8080
          open: true,	// 默认是false
          compress: true	// 默认是false
        }
    }
    

2.2.5 Proxy

proxy是我们开发中非常常用的一个配置选项,它的目的设置代理来解决跨域访问的问题:

  • 比如我们的一个api请求是http://localhost:8888,但是本地启动服务器的域名是 http://localhost:8000,这个时候发送网络请求就会出现跨域的问题;
  • 那么我们可以将请求先发送到一个代理服务器,代理服务器和API服务器没有跨域的问题,就可以解决我们的跨域问题了;
2.2.5.1 配置
  • target表示的是代理到的目标地址,比如 /api/moment会被代理到 http://localhost:8888/api/moment
  • pathRewrite默认情况下,我们的 /api 也会被写入到URL中,如果希望删除,可以使用pathRewrite;
  • secure默认情况下不接收转发到https的服务器上,如果希望支持,可以设置为false;
  • changeOrigin它表示是否更新代理后请求的 headers 中的host地址;
    • 因为我们真实的请求,其实是需要通过 http://localhost:8888来请求的;
    • 但是因为使用了Proxy代理,默认情况下它的值是 http://localhost:8000
    • 如果我们需要修改,那么可以将changeOrigin设置为true即可;

webpack.config.js

module.exports = {
    devServer: {
        proxy: {
          // "/api": "http://localhost:8888",
          "/api": {
              target: 'http://localhost:8888',
              pathRewrite: { '^/api': '' },
              changeOrigin: true,
              secure: false
          }
        }
    }
}

main.js

// 使用 axios 进行网络请求
import axios from 'axios'

// 当前前端启动地址为 'http://localhost:8000'
axios.get('/api/mement').then(res => {
    console.log(res.data)
}).catch(err => {
    console.log(err)
})
// 这里请求地址为 '/api/mement',因为配置了Proxy代理,
// 所以请求地址被改为 http://localhost:8888/mement
// 未设置pathRewrite 则为 http://localhost:8888/api/mement

2.2.6 historyApiFallback

使用 HTML5 History API (历史History路由)时,可能必须提供 index.html 页面来代替任何 404 响应。通过将 devServer.historyApiFallback 设置为 true 来启用它。

historyApiFallback是开发中一个非常常见的属性,它主要的作用是解决SPA页面在路由跳转之后,然后进行页面刷新时,返回404的错误。

  • boolean值:默认是false

    • 如果设置为true,那么在刷新时,返回404错误时,会自动返回 index.html 的内容;

      module.exports = {
        //...
        devServer: {
          historyApiFallback: true,
        },
      };
      
  • object类型的值,可以配置rewrites属性(了解):

    • 可以配置from来匹配路径,决定要跳转到哪一个页面;

      module.exports = {
        //...
        devServer: {
          historyApiFallback: {
            rewrites: [
              { from: /^\/$/, to: '/views/landing.html' },
              { from: /^\/subpage/, to: '/views/subpage.html' },
              { from: /./, to: '/views/404.html' },
            ],
          },
        },
      };
      
  • historyApiFallback配置文档

八、resolve模块解析

1.resolve的介绍

1.1 resolve用于设置模块如何被解析:

  • 在开发中我们会有各种各样的模块依赖,这些模块可能来自于自己编写的代码,也可能来自第三方库;
  • resolve可以帮助webpack从每个 require/import 语句中,找到需要引入到合适的模块代码;
  • webpack 使用 enhanced-resolve 来解析文件路径;

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

  • 绝对路径
    • 由于已经获得文件的绝对路径,因此不需要再做进一步解析。
  • 相对路径
    • 在这种情况下,使用 importrequire 的资源文件所处的目录,被认为是上下文目录;
    • import/require 中给定的相对路径,会拼接此上下文路径,来生成模块的绝对路径;
  • 模块路径
    • resolve.modules 中指定的所有目录检索模块;
      • 默认值是 ['node_modules'],所以默认会从 node_modules 中查找文件;
    • 我们可以通过设置别名的方式来替换初识模块路径,具体后面讲解 alias 的配置;

1.3 确实文件还是文件夹

  • 如果是一个文件:
    • 如果文件具有扩展名,则直接打包文件;
    • 否则,将使用 resolve.extensions选项作为文件扩展名解析;
  • 如果是一个文件夹:
    • 会在文件夹中根据 resolve.mainFiles 配置选项中指定的文件顺序查找;
      • resolve.mainFiles的默认值是 ['index'];
      • 再根据 resolve.extensions来解析扩展名;

2. 配置

2.1 extensions

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

  • 默认值是 ['.wasm', '.mjs', '.js', '.json']
  • 所以如果我们代码中想要添加加载 .vue 或者 jsx 或者 ts 等文件时,我们必须自己写上扩展名;
module.exports = {
  //...
  resolve: {
    extensions: ['.js', '.json', '.mjs', '.vue', '.ts'],
  },
};

请注意,以上这样使用 resolve.extensions覆盖默认数组,这就意味着 webpack 将不再尝试使用默认扩展来解析模块。然而你可以使用 '...' 访问默认拓展名:

module.exports = {
  //...
  resolve: {
    extensions: ['.ts', '...'],
  },
};

2.2 alias

配置别名alias

  • 特别是当我们项目的目录结构比较深的时候,或者一个文件的路径可能需要 ../../../这种路径片段;
  • 我们可以给某些常见的路径起一个别名;
const path = require('path');

module.exports = {
  //...
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      js: path.resolve(__dirname, 'src/js/'),
      utils: path.resolve(__dirname, 'src/utils/'),
    },
  },
};

路径引入:

// 配置别名前:
import utilFormDate from '../../utils/utilFormDate';

// 配置别名后:
import utilFormDate from 'utils/utilFormDate';
import utilFormDate from '@/utils/utilFormDate';

九、区分开发环境的webpack配置

1. 目前状况

目前我们所有的webpack配置信息都是放到一个配置文件中的:webpack.config.js

  • 当配置越来越多时,这个文件会变得越来越不容易维护;
  • 并且某些配置是在开发环境需要使用的,某些配置是在生成环境需要使用的,当然某些配置是在开发和生成环境都会使用的
  • 所以,我们最好对配置进行划分,方便我们维护和管理;

2.解决方案

2.1 编写不同配置文件

在根目录下新建config的目录

├── config (不同配置文件夹)
|   ├── webpack.comm.conf.js
|   ├── webpack.dev.conf.js
|   ├── webpack.prod.conf.js
  • webpack.comm.conf.js

    存放开发和生产环境下共有的配置

  • webpack.dev.conf.js

    存放开发环境下的webpack配置,并通过 merge 函数进行合并配置

  • webpack.prod.conf.js

    存放生产环境下的webpack配置,并通过 merge 函数进行合并配置

  • merge 函数进行合并配置

    1. 下载 webpack-merge 工具
    npm i webpack-merge -D
    
    1. 配置
    // 合并公有webpack配置
    const { merge } = require('webpack-merge');
    const commonConfig = require('./webpack.common.config');
    
    module.exports = merge(commonConfig, {
      // 相应环境下的 webpack 配置
    });
    
    

2.2 为webpack运行指定配置文件

package.json 中进行脚本配置

{
  "scripts": {
    "dev": "webpack serve --config ./config/webpack.dev.config.js",
    "prod": "webpack --config ./config/webpack.prod.config.js"
  },
}
posted @ 2022-09-30 09:37  青柠i  阅读(197)  评论(0编辑  收藏  举报