目录
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"
}