2024-06-10 webpack和打包过程

目录

1、Node内置模块path

  • path模块用于对路径和文件进行处理,提供了很多好用的方法;
  • 我们知道在Mac OS、Linux和Window上的路径是不一样的
  • window上会使用\或者\来作为文件路径的分隔符,当然目前也支持/;
  • 在Mac OS、Linux和Unix操作系统上使用/来作为路径的分隔符;
  • 那么如果我们在window上使用\来作为分隔符开发了一个应用程序,要部署到Linux上面应该怎么办呢?
  • 显示路径会出现一些问题;
  • 所以为了屏蔽他们之间的差异,在开发中对于路径的操作我们可以使用path模块;

1.1、path常见的API

  • 从路径中获取信息
  • dirname:获取文件的文件夹;
  • basename:获取文件名;
  • extname:获取文件扩展名;
const path = require('path')

const filePath = "C://abc/cba/nba.txt"

console.log(path.dirname(filePath)); // C://abc/cba
console.log(path.basename(filePath)); /// nba.txt
console.log(path.extname(filePath)); // .txt
  • 路径的拼接:path.join
  • 如果我们希望将多个路径进行拼接,但是不同的操作系统可能使用的是不同的分隔符;
  • 这个时候我们可以使用path.join函数
const path1 = "/abc/cba"
const path2 = "../why/kobe/james.txt"
console.log(path.join(path1, path2)); // \abc\why\kobe\james.txt
  • 拼接绝对路径:path.resolve
  • path.resolve()方法会把一个路径或者路径片段解析为一个绝对路径;
  • 给定的路径的序列是从右往左被处理的,后面每个path被依次解析,直到构造完成一个绝对路径;
  • 如果在处理完所有给定path的段之后,还没有生成绝对路径,则使用当前工作目录;
  • 生成的路径被规范化并删除尾部斜杠,零长度path段被忽略;
  • 如果没有path传递段,path.resolve()将返回当前工作目录的绝对路径;
console.log(path.resolve("./abc/cba", "C://aaa/bbb", "./abc.txt")); // C:\aaa\bbb\abc.txt
console.log(path.resolve("./abc/cba", "../why/kobe", "./abc.txt")); // C:\Users\liu\Desktop\学习\NodeJs学习\02_Node模块化\11_path模块的使用\abc\why\kobe\abc.txt
console.log(path.resolve("./abc/cba", "", "./abc.txt/")); // C:\Users\liu\Desktop\学习\NodeJs学习\02_Node模块化\11_path模块的使用\abc\cba\abc.txt
console.log(path.resolve()); // C:\Users\liu\Desktop\学习\NodeJs学习\02_Node模块化\11_path模块的使用

1.2、在webpack中的使用

  • 在webpack中获取路径或者起别名的地方也可以使用
  • __dirname:可以用来动态获取当前文件所属目录的绝对路径
    在这里插入图片描述

2、认识webpack

  • 随着前端的快速发展,目前前端的开发已经变的越来越复杂了:
  • 比如开发过程中我们需要通过模块化的方式来开发;
  • 比如也会使用一些高级的特性来加快我们的开发效率或者安全性,比如通过ES6+、TypeScript开发脚本逻辑,通过sass、less等方式来编写css样式代码;
  • 比如开发过程中,我们还希望实时的监听文件的变化并且反映到浏览器上,提高开发的效率;
  • 比如开发完成后我们还需要将代码进行压缩、合并以及其他相关的优化等等;
  • 但是对于很多的前端开发者来说,并不需要思考这些问题,日常的开发中根本就没有面临这些问题:
  • 这是因为目前前端开发我们通常都会直接使用三大框架开发:Vue、React、Angular;
  • 但是事实上,这三大框架的创建过程我们都是借助于脚手架(CLI)的;
  • 事实上Vue-CLI、create-react-app、Angular-CLI都是基于webpack来帮助我们支持模块化、less、TypeScript、打包优化等的;

2.1、webpack的概念

  • 我们先来看一下官方的解释:
webpack is a static module bundler for modern JavaScript applications.
  • webpack是一个静态的模块化打包工具,为现代的JavaScript应用程序;
  • 我么来对上面的解释进行拆解:
  • 打包bundler:webpack可以帮助我们进行打包,所以它是一个打包工具;
  • 静态的static:这样表述的原因是我们最终可以将代码打包成最终的静态资源(部署到静态服务器);
  • 模块化module:webpack默认支持各种模块化开发,ES Module、CommonJS、AMD等;
  • 现代的modern:我们前面说过,正是因为现代前端开发面临各种各样的问题,才催生了webpack的出现和发展;

在这里插入图片描述

2.2、webpack的使用前提

  • webpack的官方文档是webpack的官方文档
  • webpack的运行是依赖Node环境的,所以我们电脑上必须有Node环境

2.3、webpack的安装

  • webpack的安装目前分为两个:webpack、webpack-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

在这里插入图片描述

2.4、webpack的默认打包

  • 我们可以通过webpack进行打包,之后运行打包之后的代码。在目录下直接执行 webpack 命令:
webpack
  • 生成一个dist文件夹,里面存放一个main.js的文件,就是我们打包之后的文件:
  • 这个文件中的代码被压缩和丑化了;
  • 另外我们发现代码中依然存在ES6的语法,比如箭头函数、const等,这是因为默认情况下webpack并不清楚我们打包后的文件是否需要转成ES5之前的语法,后续我们需要通过babel来进行转换和设置;
  • 我们发现是可以正常进行打包的,但是有一个问题,webpack是如何确定我们的入口的呢?
  • 事实上,当我们运行webpack时,webpack会查找当前目录下的src/index.js作为入口;
  • 所以,如果当前项目中没有存在src/index.js文件,那么会报错;
  • 当然,我们也可以通过配置来指定入口和出口
npx webpack --entry ./src/main.js --output-path ./build

2.5、创建局部的webpack

  • 前面我们直接执行webpack命令使用的是全局的webpack,如果希望使用局部的可以按照下面的步骤来操作。
  • 第一步:创建package.json文件,用于管理项目的信息、库依赖等
npm init
  • 第二步:安装局部的webpack
npm install webpack webpack-cli -D
  • 第三步:使用局部的webpack
npx webpack
  • 第四部:在package.json中创建scripts脚本,执行脚本打包即可
"scripts": {
    "build": "webpack"
  },
npm run build

2.6、webpack配置文件

  • 在通常情况下,webpack需要打包的项目是非常复杂的,并且我们需要一系列的配置来满足要求,默认配置必然是不可以的。
  • 我们可以在根目录下创建一个webpack.config,json文件,来作为webpack的配置文件:
const path = require('path')

module.exports = {
    entry: './src/main.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, './dist')
    }
}
npm run build

2.7、指定配置文件

  • 但是如果我们的配置文件并不是webpack.config.js的名字,而是其他的名字呢?
  • 比如我们将webpack.config.js改成了wk.config.js;
  • 这个时候我们可以通过 --config 来指定对应的配置文件;
webpack --config wk.config.js
  • 但是每次这样执行命令来对源码进行编译,会非常繁琐,所以我们可以在package.json中增加一个新的脚本:
 "scripts": {
    "build": "webpack --config wk.config.js"
  },
npm run build

2.8、webpack的依赖图

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

3、打包css文件

3.1、案例代码

  • 我们创建一个component.js,通过JavaScript创建了一个元素,并且希望给它设置一些样式;
import '../css/style.css'

function component() {
    const element = document.createElement('div')

    element.innerHTML = ["hello", "webpack"].join(' ')
    element.className = 'content'

    return element
}

document.body.appendChild(component())
.content {
    color: red;
}

执行npm run build命令后,控制台报错:
在这里插入图片描述

3.2、css-loader的使用

  • 上面的错误信息告诉我们需要一个loader来加载这个css文件,但是loader是什么呢?
  • loader可以用于对模块的源代码进行转换;
  • 我们可以将css文件也看成是一个模块,我们是通过inport来加载这个模块的;
  • 在加载这个模块时,webpack其实并不知道如何对其进行加载,我们必须制定对应的loader来完成这个功能;
  • 那么我们需要一个什么样的loader呢?
  • 对于加载css文件来说,我们需要一个可以读取css文件的loader;
  • 这个loader最常用的是css-loader;
  • css-loader的安装:
npm install css-loader -D

3.3、css-loader的使用方案

  • 如何使用这个loader来加载css文件呢?有三种方式:
  • 内联样式;
  • CLI方式(webpack5中不再使用);
  • 配置方式;
  • 内联方式:内联方式使用较少,因为不方便管理;
  • 在引入的样式前加上使用的loader,并且使用!分割;
import 'css-loader!../css/style.css'
  • CLI方式
  • 在webpack5的文档中已经没有了--module-bind
  • 实际应用中也比较少使用,因为不方便管理;

3.4、loader配置方式

  • 配置方式表示的意思是在我们的webpack.config.js文件中写明配置信息:
  • module.rules中允许我们配置多个loader(因为我们也会继续使用其他的loader,来完成其他文件的加载);
  • 这个方式可以更好的表示loader的配置,也方便后期的维护,同时也让你对各个loader有一个全局的概览;
  • module.rules配置如下:
  • 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 } ]的简写。
  • loader的配置代码:
const path = require('path')

module.exports = {
    entry: './src/main.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, './build')
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                // loader: "css-loader", // 写法一
                // use: ["css-loader"], // 写法二
                // 写法三
                use: [
                    { loader: "css-loader" }
                ]
            }
        ]
    }
}

3.5、认识style-loader

  • 我们已经可以通过css-loader来加载css文件了
  • 但是你会发现这个css在我们的代码中并没有生效(页面没有效果)。
  • 这是为什么呢?
  • 因为css-loader只是负责将.css文件进行解析,并不会将解析之后的css插入到页面中;
  • 如果我们希望再完成插入style的操作,那么我们还需要另外一个loader,就是style-loader;
  • 安装style-loader:
npm install style-loader -D

3.6、配置style-loader

  • 那么我们应该如何使用style-loader:
  • 在配置文件中,添加style-loader;
  • 注意:因为loader的执行顺序是从右往左(或者说从下到上,或者说从前到后的),所以我们需要将style-loader写到css-loader的前面;
const path = require('path')

module.exports = {
    entry: './src/main.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, './build')
    },
    module: {
        rules: [
            {
                // 告诉webpack匹配什么文件,这里是匹配以.css结尾的文件
                test: /\.css$/,
                // use: [ // use中多个loader的使用顺序是从后往前的
                //     { loader: "style-loader" },
                //     { loader: "css-loader" }
                // ]
                // 简写一:如果loader只有一个
                // loader: "css-loader", 
                // 简写二:多个loader不需要其他属性时,可以直接写loader字符串形式
                use: ["style-loader", "css-loader"]

            }
        ]
    }
}
  • 重新执行编译npm run build,可以发现打包后的css已经生效了:
  • 目前我们的css是通过页内样式的方式添加进来的;
  • 后续我们也会将如何将css抽取到单独的文件中,并且进行压缩等操作;

4、打包less文件

4.1、如何处理less文件?

  • 在我们开发中,我们可能会使用less、sass、stylus的预处理器来编写css样式,效率会更高。
  • 那么,如何可以让我们的环境支持这些预处理器呢?
  • 首先我们需要确定,less、sass等编写的css需要通过工具转换成普通的css;
  • 比如我们编写如下的less样式:
@fontSize: 30px;
@fontWeight: 700;

.content {
    font-size: @fontSize;
    font-weight: @fontWeight;
}

4.2、Less工具处理

  • 我们可以使用less工具来完成它的编译转换:
npm install less -D
  • 执行如下命令:
npx lessc ./src/css/title.less title.css
  • 然后引用转换后的title.css文件

4.3、less-loader处理

  • 但是在项目中我们会编写大量的less,它们如何可以自动转换呢?
  • 这个时候我们就可以使用less-loader,来自动使用less工具转换less到css;
npm install less-loader -D
  • 配置webpack.config.js
{
    test: /\.less$/,
    use: [
        { loader: "style-loader" },
        { loader: "css-loader" },
        { loader: "less-loader" }
    ]
}
  • 执行npm run build,less就可以自动转成成css,并且页面也会生效了

5、PostCSS工具

5.1、认识PostCSS工具

  • 什么是PostCSS呢?
  • PostCSS是一个通过JavaScript来转换样式的工具;
  • 这个工具可以帮助我们进行一些CSS的转换和适配,比如自动添加浏览器前缀、css样式的重置;
  • 但是实现这些功能,我们需要借助于PostCSS对应的插件;
  • 如何使用PostCSS呢?主要就是两个步骤:
  • 第一步:查找PostCSS在构建工具中的扩展,比如webpack中的postcss-loader;
  • 第二步:选择可以添加你需要的PostCSS相关的插件;

5.2、postcss-loader

  • 我们可以借助于构建工具:
  • 在webpack中使用PostCSS,也就是使用postcss-loader
  • 我们来安装postcss-loader:
npm install postcss-loader -D
  • 修改加载css的loader:(配置文件已经过多,给出一部分了)
  • 注意:因为postcss需要有对应的插件才会起效果,所以我们需要配置它的plugin;
  • 因为我们需要添加前缀,所以要安装autoprefixer:
npm install autoprefixer -D
use: [
   "style-loader", 
   "css-loader",
   {
       loader: "postcss-loader",
       options: {
           postcssOptions: {
               plugins: [
                   "autoprefixer"
               ]
           }
       }
   }
]

5.3、单独的postcss配置文件

  • 我们可以将这些配置信息放到一个单独的文件中进行管理,这样也防止在webpack.config.js中写多次相同的配置代码(如css、less各写一次):
  • 在根目录下创建postcss.config.js文件
module.exports = {
    plugins: [
        "autoprefixer"
    ]
}

5.4、postcss-preset-env

require可以省掉,直接写字符串

  • 事实上,在配置postcss-loader时,我们配置插件并不需要使用autoprefixer。
  • 我们可以使用另外一个插件:postcss-preset-env
  • postcss-preset-env也是一个postcss插件;
  • 它可以帮助我们将一些现代的CSS特性,转成大多数浏览器认识的CSS,并且会根据目标浏览器或者运行环境添加所需的polyfill(polyfill(polyfiller),指的是一个代码块。这个代码块向开发者提供了一种技术, 这种技术可以让浏览器提供原生支持,抹平不同浏览器对API兼容性的差异。);
  • 也包括会自动帮助我们添加autoprefixer(所以相当于已经内置了autoprefixer);
  • 首先,我们需要安装postcss-preset-env:
npm install postcss-preset-env -D
  • 之后,我们直接改掉之前的autoprefixer即可:
module.exports = {
    plugins: [
        "postcss-preset-env"
    ]
}

6、打包图片资源

6.1、加载图片案例准备

  • 为了演示我们项目中可以加载图片,我们需要在项目中使用图片,比较常见的使用图片的方式是两种:
  • img元素,设置src属性;
  • 其他元素(比如div),设置background-inage的css属性;
// 2、image元素
const zsImage = new Image()
zsImage.src = zs01Img
document.body.appendChild(zsImage)

// 3、增加一个div,用于存放图片
const bgDiv = document.createElement('div')
bgDiv.style.width = '200px'
bgDiv.style.height = '200px'
bgDiv.className = 'bg-image'
document.body.appendChild(bgDiv)
.bg-image {
    background-image: url('../img/zs02.jpeg');
    background-size: contain;
}
  • 这个时候打包会报错
    在这里插入图片描述

6.2、认识asset module type

  • 我们当前使用的webpack版本是webpack5:
  • 在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,并且配置资源体积限制实现;

6.3、asset module type的使用

  • 比如加载图片,我们可以使用下面的方式:
{
    test: /\.(png|svg|jpg|jpeg|gif)$/i,
    // 1.打包两张图片,并且这两张图片有自己的地址,将地址设置到img/bgi中
    type: "asset/resource"
}
  • 但是,怎么自定义文件输出路径和文件名呢?
  • 方式一:修改output,添加assetModuleFilename属性;
output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, './build'),
    // 自定义文件的输出路径和名称,方式一
    assetModuleFilename: "img/[name][hash:6][ext]"
},
  • 方式二:在Rule中,添加一个generator属性,并设置filename;
{
    test: /\.(png|svg|jpg|jpeg|gif)$/i,
    type: "asset/resource",
    // 自定义文件的输出路径和名称,方式二
    generator: {
        filename: "img/[name][hash:6][ext]"
    }
}
  • 这里介绍几个最常用的placeholder:
  • [ext]:处理文件的扩展名;
  • [name]:处理文件的名称;
  • [hash]:文件的内容,使用MD4的散列函数处理,生成一个128位的hash值(32个十六进制);

6.4、url-loader的limit效果

  • 开发中我们往往是小的图片需要进行转换,但是大的图片直接使用图片即可:
  • 这是因为小的图片转换base64之后可以和页面一起被请求,减少不必要的请求过程;
  • 而大的图片也进行转换的话,反而会影响页面的请求速度;
  • 我们需要两个步骤来实现:
  • 步骤一:将type修改为asset;
  • 步骤二:添加一个parser属性,并且制定dataUrl的条件,添加maxSize属性;
{
    test: /\.(png|svg|jpg|jpeg|gif)$/i,
    // 1.打包两张图片,并且这两张图片有自己的地址,将地址设置到img/bgi中
    // type: "asset/resource",
    // 2.将图片进行base64的编码,并且直接将编码后的源码放到打包的js文件中
    // 注意:这种写法,不能配置generator属性,会报错(可自己测试)
    // type: "asset/inline",
    // 3.合理个规范:
    // 3.1.对应小一点的图片,可以进行base64编码
    // 3.2.对于大一点的图片,单独的图片打包,形成url地址,单独的请求这个url图片
    type: "asset/source",
    // 第3种方式配合parser配置
    parser: {
        dataUrlCondition: {
        	// 单位为byte,超过maxSize直接导出url,否则转换为base64
            maxSize: 70 * 1024
        }
    },
    // 自定义文件的输出路径和名称,方式二
    generator: {
        filename: "img/[name][hash:6][ext]"
    }
}

7、babel

7.1、为什么需要babel

  • 事实上,在开发中我们很少直接去接触babel,但是babel对于前端开发来说,目前是不可缺少的一部分:
  • 开发中,我们想要使用ES6+的语法,想要使用TypeScript,开发React项目,它们都是离不开Babel的;
  • 所以,学习babel对于我们理解代码从编写到线上的转变过程至关重要;
  • 那么,Babel到底是什么呢?
  • Babel是一个工具链,主要用于旧浏览器或者环境中奖ECMAScript 2015+代码转换为向后兼容版本的JavaScript;
  • 包括:语法转换、源代码转换等;
// 使用babel把es6语法转换成es5语法
// 转换前
[1, 2, 3].map(n => n + 1)
// 转换后
[1, 2, 3].map(function(n) {
    return n + 1
})

7.2、babel-loader

  • 在实际开发中,我们通常会在构建工具中通过配置babel来对其进行使用,比如在webpack中。
  • 那么我们就需要去安装相关的依赖:
  • 如果我们之前已经安装了@babel/core,那么这里不需要再次安装;
npm install babel-loader  -D
  • 我们可以设置一个规则,在加载js文件时,使用我们的babel:
 {
    test:/\.m?js$/,
    use: {
        loader: "babel-loader"
    }
 }

7.3、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

7.4、babel插件的使用

  • 比如我们需要转换箭头函数,那么我们就可以使用箭头函数转换相关的插件:
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
  • 通过配置webpack.config.js实现上面的功能:
{
    test: /\.m?js$/,
    use: {
        loader: "babel-loader",
        options: {
            plugins: [
                "@babel/plugin-transform-arrow-functions",
                "@babel/plugin-transform-block-scoping"
            ]
        }
    }
}

7.5、babel的预设preset

  • 但是如果要转换的内容过多,一个个设置是比较麻烦的,我们可以使用预设(preset);
  • 安装@babel/preset-env预设:
npm install @babel/preset-env -D
  • 执行如下命令:
npx babel src --out-dir dist --presets=@babel/preset-env
  • 等价于:
{
    test: /\.m?js$/,
    use: {
        loader: "babel-loader",
        options: {
            presets: [
                ["@babel/preset-env"]
            ]
        }
    }
}
  • 比较常见的预设有三个:
  • env
  • react
  • TypeScript
  • 安装preset-typecsript:
npm install @babel/preset-typescript -D

8、打包.vue文件

8.1、Vue项目加载的文件有哪些呢?

  • JavaScript的打包:
  • 将ES6转换成ES5的语法;
  • TypeScript的处理,将其转换成JavaScript;
  • Css的处理:
  • CSS文件模块的加载、提取;
  • Less、Sass等预处理器的处理;
  • 资源文件img、font:
  • 图片img文件的加载;
  • 字体font文件的加载;
  • HTML资源的处理:
  • 打包HTML资源文件;
  • 处理vue项目的SFC文件即.vue文件;

8.2、编写App.vue代码

  • 在开发中我们会编写Vue相关的代码,webpack可以对Vue代码进行解析;
  • 第一步,安装vue:
npm install vue
  • 第二步,为了便于开发,可以安装vscode扩展插件:
  • 如果你是vue2,建议按钮vetur插件;
  • 如果你是vue3,建议安装Volar插件(插件已更名为Vue-Official)
  • .vue文件:
<template>
    <h2>{{title}}</h2>
    <p>{{content}}</p>
</template>

<script>
export default {
    data() {
        return {
            title: "我是App标题",
            content: "我是App内容,哈哈哈"
        }
    }
}
</script>

<style>
    h2 {
        color: red;
    }
    p {
        color: blue;
    }
</style>
  • 引用.vue文件
import {createApp} from "vue";
import App from './vue/App.vue';

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

8.3、App.vue的打包过程

  • 我们对代码打包会报错:我们需要合适的Loader来处理文件。
    在这里插入图片描述
  • 这个时候我们需要使用vue-loader:
npm install vue-loader -D
  • 在webpack的模板规则中进行配置:
{
   test: /\.vue$/,
   loader: "vue-loader"
}
  • 打包依然会报错,这是因为我们必须添加@vue/compiler-sfc来对template进行解析:
npm install @vue/compiler-sfc -D
  • 另外我们需要配置对应的Vue插件:
const { VueLoaderPlugin } = require('vue-loader')
...
plugins: [
   new VueLoaderPlugin()
]
  • 重新打包即可支持App.vue的写法

9、webpack解析文件规则

9.1、resolve模块解析

  • resolve用于设置模块如何被解析:
  • 在开发中我们会有各种各样的模块依赖,这些模块可能来自于自己编写的代码,也可能来着第三方库;
  • resolve可以帮助webpack从每个require/import语句中,找到需要引入的合适的模块代码;
  • webpack使用enhanced-resolve来解析文件路径
  • webpack能解析三种文件路径:
  • 绝对路径
  • 由于已经获得文件的绝对路径,因此不需要再做进一步解析。
  • 相对路径
  • 在这种情况下,使用import或require的资源文件所处的目录,被认为是上下文目录;
  • 在import/require中给定的相对路径,会拼接此上下文路径,来生成模块的绝对路径;
  • 模块路径
  • 在resolve.modules中指定的所有目录检索模块;
    √ 默认值是[‘node_modules’],所以默认会从node_modules中查找文件;
  • 我们可以通过设置别名的方式来替换初始模块路径,具体后面讲解alias的配置;
  • 确定是文件还是文件夹
  • 如果是一个文件:
  • 如果文件具有扩展名,则直接打包文件;
  • 否则,将使用resolve.extensions选项作为文件扩展名解析;
  • 如果是一个文件夹:
  • 会在文件夹中根据resolve.mainFiles配置选项中指定的文件顺序查找;
    √ resolve.mainFiles的默认值是[‘index’];
    √ 再根据resolve.extensions来解析扩展名;

9.2、extensions和alias配置

  • extensions是解析到文件时自动添加扩展名:
  • 默认值是[‘.wasm’, ‘.mjs’, ‘.js’, ‘.json’];
  • 所以如果我们代码中想要添加加载.vue或者jsx或者ts等文件时,我们必须自己写上扩展名;
  • 另一个非常好用的功能是配置别名alias:
  • 特别是当我们项目的目录结构比较深的时候,或者一个文件的路径可能需要…/…/…/这种路径片段;
  • 我们可以给某些常见的路径起一个别名;
resolve: {
    extensions: [".js", ".json", ".vue", ".jsx", ".ts", ".tsx"],
    alias: {
        "@": path.resolve(__dirname, "./src"),
        vuePage: path.resolve(__dirname, "./src/vue")
    }
},

10、webpack常见的插件和模式

在这里插入图片描述

10.1、认识Plugin

  • webpack的另一个核心是Plugin,官方有这样一段对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可以用于执行更加广泛的任务,比如打包优化、资源管理、环境变量注入等;

在这里插入图片描述

10.2、CleanWebpackPlugin

  • 前面我们演示的过程中,每次修改了一些配置,重新打包时,都需要手动删除dist文件夹:
  • 我们可以借助于一个插件来帮助我们完成,这个插件就是CleanWebpackPlugin;
  • 首先,我们安装这个插件:
npm install clean-webpack-plugin -D
  • 之后在插件中配置:
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
  // 省略其他配置
  plugins: [
      new CleanWebpackPlugin()
  ]
}
  • 注意,现在官网删除了CleanWebpackPlugin插件相关内容,通过在output下面设置clean属性为true实现等同于CleanWebpackPlugin插件的功能;
module.exports = {
  //...
  output: {
    clean: true, // Clean the output directory before emit.
  },
};

10.3、HtmlWebpackPlugin

10.3.1、安装使用

  • 之前我们的写法有一个不太规范的地方:
  • 我们的HTML文件是编写在根目录下的,而最终打包的dist文件夹中是没有index.html文件的。
  • 在进行项目部署时,必然也是需要有对应的入口文件index.html的;
  • 所以我们也需要对index.html进行打包的处理;
  • 对HTML进行打包处理我们可以使用另外一个插件:HtmlWebpackPlugin;
npm install html-webpack-plugin -D
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
  // 省略其他配置
  plugins: [
      new HtmlWebpackPlugin({
          title: 'webpack案例'
      })
  ]
}

10.3.2、生成index.html分析

  • 我们会发现,现在自动在dist文件夹中,生成了一个index.html的文件:
  • 该文件中也自动添加了我们打包的bundle.js文件;
<!doctype html>
<html>

<head>
    <meta charset="utf-8">
    <title>webpack案例</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script defer="defer" src="bundle.js"></script>
</head>

<body></body>

</html>
  • 这个文件是如何生成的呢?
  • 默认情况下是根据ejs的一个模版来生成的;
  • 在html-webpack-plugin的源代码中,有一个default_index.ejs模块;

10.3.3、自定义HTML模板

  • 如果我们想在自己的模块中加入一些比较特别的内容:
  • 比如添加一个noscript标签,在用户的JavaScript被关闭时,给予相应的提示;
  • 比如在开发vue或者react项目时,我们需要一个可以挂载后续组件的根标签
  • 这个时候我们需要一个属于自己的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">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <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>
</body>

</html>

10.3.4、自定义模板数据填充

  • 在上面的代码中,会有一些类似这样的语法<% 变量 %>,这个是EJS模块填充数据的方式。
  • 在配置HtmlWebpackPlugin时,我们可以添加如下配置:
  • template:指定我们要使用的模块所在的路径;
  • title:在进行htmlWebpackPlugin.options.title读取时,就会读到该信息;
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
  // 省略其他配置
  plugins: [
      new HtmlWebpackPlugin({
          title: 'webpack案例',
          template: './pubilc/index.html'
      })
  ]
}

10.4、DefinePlugin

10.4.1、DefinePlugin的介绍

  • 但是,这个时候编译还是会报错,因为我们的模块中还使用到一个BASE_URL的常量:
    在这里插入图片描述
  • 这是因为在编译template模块时,有一个BASE_URL:
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
  • 但是我们并没有设置过这个常量值,所以会出现没有定义的错误;
  • 这个时候我们就可以使用DefinePlugin插件;

10.4.2、DefinePlugin的使用

  • DefinePlugin允许在编译时创建配置的全局常量,是一个webpack内置的插件(不需要单独安装):
const { DefinePlugin } = require('webpack')
module.exports = {
  // 省略其他配置
  plugins: [
      new DefinePlugin({
          BASE_URL: '"./"'
      })
  ]
}
  • 这个时候,编译template就可以正确的编译了,会读取到BASE_URL的值;

10.5、Mode配置

  • 前面我们一直没有讲mode
  • Mode配置选项,可以告知webpack使用相应模式的内置优化:
  • 默认值是production(什么都不设置的情况下);
  • 可选值有:‘none’ | ‘development’ | ‘production’;
  • 这几个选项有什么样的区别呢?
    在这里插入图片描述

  • Mode配置代表更多:
    在这里插入图片描述

11、webpack搭建本地服务器

11.1、为什么要搭建本地服务器

  • 目前我们开发的代码,为了运行需要有两个操作:
  • 操作一:npm run build。编译相关的代码;
  • 操作二:通过live server或者直接通过浏览器,打开index.html代码,查看效果;
  • 这个过程经常操作会影响我们的开发效率,我们希望可以做到,当文件发生变化时,可以自动的完成编译和展示;
  • 为了完成自动编译,webpack提供了几种可选的方式:
  • webpack watch mode;
  • webpack-dev-server(常用);
  • webpack-dev-middleware;

11.2、webpack-dev-server

  • 上面的方式可以监听到文件的变化,但是事实上它本身是没有自动刷新浏览器的功能的:
  • 当然,目前我们可以在VSCode中使用live-server来完成这样的功能;
  • 但是,我们希望在不适用live-server的情况下,可以具备live reloading(实时重新加载)的功能;
  • 安装webpack-dev-server
npm install webpack-dev-server -D
  • 修改配置文件,启动时加上serve参数:
module.exports = {
  // 省略其他配置
  devServer: {
        
  }
}
"scripts": {
  // 省略其他配置
  "serve": "webpack serve --config wk.config.js"
},
  • webpack-dev-server在编译之后不会写入到任何输出文件,而是将bundle文件保留在内存中:
  • 事实上webpack-dev-server使用了一个库叫memfs(memory-fs-webpack自己写的)

11.3、认识模块热替换(HMR)

  • 什么是HMR呢?
  • HMR的全称是Hot Module Replacement,翻译为模块热替换;
  • 模块热替换是指在应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个页面;
  • HMR通过如下几种方式,来提高开发的速度:
  • 不重新加载整个页面,这样可以保留某些应用程序的状态不丢失;
  • 只更新需要变化的内容,节省开发的时间;
  • 修改了css、js源代码,会立即在浏览器更新,相当于直接在浏览器的devtools中直接修改样式;
  • 如何使用HMR呢?
  • 默认情况下,webpack-dev-server已经支持HMR,我们只需要开启即可(默认已经开启);
  • 在不开启HMR的情况下,当我们修改了源代码之后,整个页面都会自动刷新,使用的是live reloading;

11.4、开启HMR

  • 修改webpack的配置:
devServer: {
   hot: true
}
  • 浏览器可以看到如下效果:
    在这里插入图片描述
  • 但是你会发现,当我们修改了某一个模块的代码时,依然是刷新的整个页面:
  • 这是因为我们需要去指定哪些模块发生更新时,进行HMR;
if (module.hot) {
    // 需要注意main.js文件不能使用./utils/foo.js文件相关方法,不然foo.js更新了还是会全局刷新
    module.hot.accept("./utils/foo.js", () => {
        console.log('foo更新了');
    })
}

在这里插入图片描述

11.5、框架的HMR

  • 有一个问题:在开发其他项目时,我们是否需要经常手动去写入module.hot.accpet相关的API呢?
  • 比如开发vue、react项目,我们修改了组件,希望进行热更新,这个时候应该如何去操作呢?
  • 事实上社区已经针对这些有很成熟的解决方案了:
  • 比如vue开发中,我们使用vue-loader,此loader支持vue组件的HMR,提供了开箱即用的体验;
  • 比如react开发中,有React Hot Loader,实时调整react组件(目前react官方已经弃用了,改成使用react-refresh);

11.6、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地址是可以访问的;

11.7、port、open、compress

  • port设置监听的端口,献认情况下是8080
  • open是否打开浏览器:
  • 默认值是false,设置为true会打开浏览器;
  • 也可以设置为类似于Google Chrome等值;
  • compress是否为静态文件开启gzip compression:
  • 默认值是false,设置为true;
devServer: {
    hot:true,
    // host: '0.0.0.0',
    // port: 8888,
    // open: true,
    compress: true
}

在这里插入图片描述

11.8、Proxy( Vue 项目学习)

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

11.9、changeOrigin的解析( Vue 项目学习)

  • 这个 changeOrigin官方说的非常模糊,通过查看源码我发现其实是要修改代理请求中的headers中的host属性:
  • 因为我们真实的请求,其实是需要通过http://localhost:8888来请求的;
  • 但是因为使用了代码,默认情况下它的值是 http://localhost:8000;
  • 如果我们需要修改,那么可以将changeOrigin设置为true即可;

11.10、historyApiFallback(Vue项目学习)

  • historyApiFallback是开发中一个非常常见的属性,它主要的作用是解决SPA页面在路由跳转之后,进行页面刷新时,返回404的错误。
  • boolean值:默认是false
  • 如果设置为true,那么在刷新时,返回404错误时,会自动返回index.html的内容;
  • object类型的值,可以配置rewrites属性:
  • 可以配置from来匹配路径,决定要跳转到哪一个页面;
  • 事实上devServer中实现historyApiFallback功能是通过connect-history-api-fallback库的:

12、如何区分开发环境

  • 目前我们所有的webpack配置信息都是放到一个配置文件中的:webpack.config.js
  • 当配置越来越多时,这个文件会变得越来越不容易维护;
  • 并且某些配置是在开发环境需要使用的,某些配置是在生成环境需要使用的,当然某些配置是在开发和生成环境都会使用的;
  • 所以,我们最好对配置进行划分,方便我们维护和管理;
  • 那么,在启动时如何可以区分不同的配置呢?
  • 编写两个不同的配置文件,开发和生产时,分别加载不同的配置文件即可;
  • 方式二:使用相同的一个入口配置文件,通过设置参数来区分它们;
"scripts":{
	"build": "webpack --config./config/common.config --env production",
	"serve":-"webpack serve --config./config/common.config"
}

12.1、区分开发和生产环境配置

  • 这里我们创建三个文件:
  • webpack.common.config.js
  • webpack.dev.config.js
  • webpack.prod.config.js
  • 这里需要安装一个插件:
npm install webpack-merge -D
  • webpack.common.config.js
const path = require('path')

const { VueLoaderPlugin } = require('vue-loader')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { DefinePlugin } = require('webpack')


module.exports = {
    entry: './src/main.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, '../build'),
        // 自定义文件的输出路径和名称,方式一
        // assetModuleFilename: "img/[name][hash:6][ext]",
    },
    resolve: {
        extensions: [".js", ".json", ".vue", ".jsx", ".ts", ".tsx"],
        alias: {
            "@": path.resolve(__dirname, "../src"),
            vuePage: path.resolve(__dirname, "../src/vue")
        }
    },
    module: {
        rules: [
            {
                // 告诉webpack匹配什么文件,这里是匹配以.css结尾的文件
                test: /\.css$/,
                // use: [ // use中多个loader的使用顺序是从后往前的
                //     { loader: "style-loader" },
                //     { loader: "css-loader" }
                // ]
                // 简写一:如果loader只有一个
                // loader: "css-loader", 
                // 简写二:多个loader不需要其他属性时,可以直接写loader字符串形式
                use: [
                    "style-loader", 
                    "css-loader",
                    // {
                    //     loader: "postcss-loader",
                    //     options: {
                    //         postcssOptions: {
                    //             plugins: [
                    //                 "autoprefixer"
                    //             ]
                    //         }
                    //     }
                    // }
                ]

            },
            {
                test: /\.less$/,
                use: [
                    { loader: "style-loader" },
                    { loader: "css-loader" },
                    { loader: "less-loader" },
                    // {
                    //     loader: "postcss-loader",
                    //     options: {
                    //         postcssOptions: {
                    //             plugins: [
                    //                 "autoprefixer"
                    //             ]
                    //         }
                    //     }
                    // }
                ]
            },
            {
                test: /\.(png|svg|jpg|jpeg|gif)$/i,
                // 1.打包两张图片,并且这两张图片有自己的地址,将地址设置到img/bgi中
                // type: "asset/resource",
                // 2.将图片进行base64的编码,并且直接将编码后的源码放到打包的js文件中
                // 注意:这种写法,不能配置generator属性,会报错(可自己测试)
                // type: "asset/inline",
                // 3.合理个规范:
                // 3.1.对应小一点的图片,可以进行base64编码
                // 3.2.对于大一点的图片,单独的图片打包,形成url地址,单独的请求这个url图片
                type: "asset",
                // 第3种方式配合parser配置
                parser: {
                    dataUrlCondition: {
                        maxSize: 60 * 1024
                    }
                },
                // 自定义文件的输出路径和名称,方式二
                generator: {
                    filename: "img/[name][hash:6][ext]"
                }
            },
            {
                test: /\.m?js$/,
                use: {
                    loader: "babel-loader",
                    options: {
                        // plugins: [
                        //     "@babel/plugin-transform-arrow-functions",
                        //     "@babel/plugin-transform-block-scoping"
                        // ]
                        presets: [
                            ["@babel/preset-env"]
                        ]
                    }
                }
            },
            {
                test: /\.vue$/,
                loader: "vue-loader"
            }
        ]
    },
    plugins: [
        new VueLoaderPlugin(),
        new HtmlWebpackPlugin({
            title: 'webpack案例',
            template: './pubilc/index.html'
        }),
        new DefinePlugin({
            BASE_URL: '"./"'
        })
    ],
}
  • webpack.dev.config.js
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.common.config')


module.exports = merge(commonConfig, {
    mode: 'development',
    devServer: {
        hot: true,
        // host: '0.0.0.0',
        // port: 8888,
        // open: true,
        compress: true
    }
})
  • webpack.prod.config.js
// const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const {merge} = require('webpack-merge')
const commonConfig = require('./webpack.common.config')


module.exports = merge(commonConfig, {
    mode: 'production',
    output: {
        clean: true
    },
    plugins: [
        // new CleanWebpackPlugin(),
    ]
})
  • package.json里面的脚本配置更改:
"scripts": {
  "build": "webpack --config ./config/webpack.prod.config.js",
  "serve": "webpack serve --config ./config/webpack.dev.config.js"
},

12.2、入口文件解析

  • 我们之前编写入口文件的规则是这样的:./src/index.js,但是如果我们的配置文件所在的位置变成了 config 目录,我们是否应该变成: ../src/index.js呢?
  • 如果我们这样编写,会发现是报错的,依然要写成: ./src/index.js;
  • 这是因为入口文件其实是和另一个属性有关的:context;
  • context的作用是用于解析入口 (entry point) 和加载器(loader):
  • 官方说法:默认是当前路径(但是经过我测试,默认应该是webpack的启动目录)
  • 另外推荐在配置中传入一个值:
// context是配置文件所在目录
module.exports = {
	context: path.resolve(__dirname, "./"),
	entry: "../src/index.js"
}
  • 8
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值