1.Webpack基础打包
认识webpack
事实上随着前端的快速发展,目前前端的开发已经变的越来越复杂了:
比如开发过程中我们需要通过模块化的方式来开发;
比如也会使用一些高级的特性来加快我们的开发效率或者安全性,比如通过ES6+、TypeScript开发脚本逻辑,通过sass、less等方式来编写css样式代码;
比如开发过程中,我们还希望实时的监听文件的变化来并且反映到浏览器上,提高开发的效率;
比如开发完成后我们还需要将代码进行压缩、合并以及其他相关的优化;
等等….
但是对于很多的前端开发者来说,并不需要思考这些问题,日常的开发中根本就没有面临这些问题:
这是因为目前前端开发我们通常都会直接使用三大框架来开发:Vue、React、Angular;
但是事实上,这三大框架的创建过程我们都是借助于脚手架(CLI) 的;
事实上Vue-CLI、create-react-app、Angular-CLI都是基于webpack来帮助我们支持模块化、less、
TypeScript、打包优化等的;
Webpack到底是什么呢?
我们先来看一下官方的解释:
webpack is a static module bundler for modern JavaScript applications.
webpack是一个静态的模块化打包工具,为现代的JavaScript应用程序;
我们来对上面的解释进行拆解:
打包bundler: webpack可以将帮助我们进行打包,所以它是一个打包工具
静态的static: 这样表述的原因是我们最终可以将代码打包成最终的静态资源(部署到静态服务器);
模块化module: webpack默认支持各种模块化开发,ES Module、CommonJS、AMD等;
现代的modern: 我们前端说过,正是因为现代前端开发面临各种各样的问题,才催生了webpack的出现和发展;
Vue项目加载的文件有哪些呢?
JavaScript的打包:
将ES6转换成ES5的语法;
TypeScript的处理,将其转换成JavaScript;
Css的处理:
CSS文件模块的加载、提取;
Less、Sass等预处理器的处理;
资源文件img、font:
图片img文件的加载;
字体font文件的加载;
HTML资源的处理:
打包HTML资源文件;
处理vue项目的SFC文件.vue文件;
Webpack的使用前提
webpack的官方文档是webpack.js.org/
webpack的中文官方文档是webpack.docschina.org/
DOCUMENTATION:文档详情,也是我们最关注的
Webpack的运行是依赖Node环境的,所以我们电脑上必须有Node环境
所以我们需要先安装Node.js,并且同时会安装npm;
我当前电脑上的node版本是v14.15.5,npm版本是6.14.11(你也可以使用nvm或者n来管理Node版本);
pNode官方网站:nodejs.org/
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 # 局部安装
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
创建局部的webpack
前面我们直接执行webpack命令使用的是全局的webpack,如果希望使用局部的可以按照下面的步骤来操作。
第一步:创建package.json文件,用于管理项目的信息、库依赖等
npm init
第二步:安装局部的webpack
npm install webpack webpack-cli -D
第三步:使用局部的webpack
npx webpack
第四步:在package.json中创建scripts脚本,执行脚本打包即可
npm run build
Webpack配置文件
在通常情况下,webpack需要打包的项目是非常复杂的,并且我们需要一系列的配置来满足要求,默认配置必然是不可以的。
我们可以在根目录下创建一个webpack.config.js文件,来作为webpack的配置文件:
继续执行webpack命令,依然可以正常打包
npm run build
- 指定配置文件
但是如果我们的配置文件并不是webpack.config.js的名字,而是其他的名字呢?
比如我们将webpack.config.js修改成了wk.config.js;
这个时候我们可以通过--config 来指定对应的配置文件;
webpack --config wk.config.js
但是每次这样执行命令来对源码进行编译,会非常繁琐,所以我们可以在package.json中增加一个新的脚本:
之后我们执行npm run build来打包即可。
Webpack的依赖图
webpack到底是如何对我们的项目进行打包的呢?
事实上webpack在处理应用程序时,它会根据命令或者配置文件找到入口文件;
从入口开始,会生成一个依赖关系图,这个依赖关系图会包含应用程序中所需的所有模块(比如.js文件、css文件、图片、字体等);
然后遍历图结构,打包一个个模块(根据文件的不同使用不同的loader来解析);
编写案例代码
我们创建一个component.js
通过JavaScript创建了一个元素,并且希望给它设置一些样式;
继续编译命令npm run build,出现下列报错
css-loader的使用
上面的错误信息告诉我们需要一个loader来加载这个css文件,但是loader是什么呢?
loader 可以用于对模块的源代码进行转换;
我们可以将css文件也看成是一个模块,我们是通过import来加载这个模块的;
在加载这个模块时,webpack其实并不知道如何对其进行加载,我们必须制定对应的loader来完成这个功能;
那么我们需要一个什么样的loader呢?
对于加载css文件来说,我们需要一个可以读取css文件的loader;
这个loader最常用的是css-loader;
css-loader的安装: npm install css-loader -D
- css-loader的使用方案
如何使用这个loader来加载css文件呢?有三种方式:
1.内联方式;
2.CLI方式(webpack5中不再使用);
3.配置方式;
内联方式:
内联方式使用较少,因为不方便管理;
在引入的样式前加上使用的loader,并且使用!分割;
CLI方式:
在webpack5的文档中已经没有了 --module-bind;
实际应用中也比较少使用,因为不方便管理;
- 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的配置代码
认识style-loader
我们已经可以通过css-loader来加载css文件了
但是你会发现这个css在我们的代码中并没有生效(页面没有效果)。
这是为什么呢?
因为css-loader只是负责将.css文件进行解析,并不会将解析之后的css插入到页面中;
如果我们希望再完成插入style的操作,那么我们还需要另外一个loader,就是style-loader;
安装style-loader:
npm install style-loader -D
- 配置style-loader
那么我们应该如何使用style-loader:
在配置文件中,添加style-loader;
注意:因为loader的执行顺序是从右向左(或者说从下到上,或者说从后到前的),所以我们需要将styleloader写到css-loader的前面;
重新执行编译npm run build,可以发现打包后的css已经生效了:
当前目前我们的css是通过页内样式的方式添加进来的;
后续我们也会讲如何将css抽取到单独的文件中,并且进行压缩等操作;
如何处理less文件?
在我们开发中,我们可能会使用less、sass、stylus的预处理器来编写css样式,效率会更高。
那么,如何可以让我们的环境支持这些预处理器呢?
首先我们需要确定,less、sass等编写的css需要通过工具转换成普通的css;
比如我们编写如下的less样式:
-
Less工具处理 我们可以使用less工具来完成它的编译转换:
npm install less -D
执行如下命令:npx lessc ./src/css/title.less title.css -
less-loader处理 但是在项目中我们会编写大量的css,它们如何可以自动转换呢?
这个时候我们就可以使用less-loader,来自动使用less工具转换less到css;
npm install less-loader -D
配置webpack.config.js
执行 npm run build less就可以自动转换成css,并且页面也会生效了
认识PostCSS工具
什么是PostCSS呢?
PostCSS是一个通过JavaScript来转换样式的工具;
这个工具可以帮助我们进行一些CSS的转换和适配,比如自动添加浏览器前缀、css样式的重置;
但是实现这些功能,我们需要借助于PostCSS对应的插件;
如何使用PostCSS呢?主要就是两个步骤:
第一步:查找PostCSS在构建工具中的扩展,比如webpack中的postcss-loader;
第二步:选择可以添加你需要的PostCSS相关的插件;
- 命令行使用postcss
当然,我们能不能也直接在终端使用PostCSS呢?
也是可以的,但是我们需要单独安装一个工具postcss-cli;
我们可以安装一下它们:postcss、postcss-cli
npm install postcss postcss-cli -D
我们编写一个需要添加前缀的css:
autoprefixer.github.io/
我们可以在上面的网站中查询一些添加css属性的样式;
- 插件autoprefixer 因为我们需要添加前缀,所以要安装autoprefixer:
npm install autoprefixer -D
直接使用使用postcss工具,并且制定使用autoprefixer
npx postcss --use autoprefixer -o end.css ./src/css/style.css
转化之后的css样式如下:
- postcss-loader 真实开发中我们必然不会直接使用命令行工具来对css进行处理,而是可以借助于构建工具: 在webpack中使用postcss就是使用postcss-loader来处理的;
我们来安装postcss-loader:
npm install postcss-loader -D
我们修改加载css的loader:(配置文件已经过多,给出一部分了)
注意:因为postcss需要有对应的插件才会起效果,所以我们需要配置它的plugin;
单独的postcss配置文件
当然,我们也可以将这些配置信息放到一个单独的文件中进行管理:
在根目录下创建postcss.config.js
- postcss-preset-env 事实上,在配置postcss-loader时,我们配置插件并不需要使用autoprefixer。
我们可以使用另外一个插件:postcss-preset-env
postcss-preset-env也是一个postcss的插件;
它可以帮助我们将一些现代的CSS特性,转成大多数浏览器认识的CSS,并且会根据目标浏览器或者运行时环境
添加所需的polyfill;
也包括会自动帮助我们添加autoprefixer(所以相当于已经内置了autoprefixer);
首先,我们需要安装postcss-preset-env:
npm install postcss-preset-env -D
之后,我们直接修改掉之前的autoprefixer即可:
注意:我们在使用某些postcss插件时,也可以直接传入字符串。
2.Webpack打包其他资源
加载图片案例准备
为了演示我们项目中可以加载图片,我们需要在项目中使用图片,比较常见的使用图片的方式是两种:
img元素,设置src属性;
其他元素(比如div),设置background-image的css属性;
这个时候,打包会报错
file-loader
要处理jpg、png等格式的图片,我们也需要有对应的loader:file-loader
file-loader的作用就是帮助我们处理import/require()方式引入的一个文件资源,并且会将它放到我们输出的文件夹中;
当然我们待会儿可以学习如何修改它的名字和所在文件夹;
安装file-loader:
npm install file-loader -D
配置处理图片的Rule:
文件的命名规则
有时候我们处理后的文件名称按照一定的规则进行显示:
比如保留原来的文件名、扩展名,同时为了防止重复,包含一个hash值等;
这个时候我们可以使用PlaceHolders来完成,webpack给我们提供了大量的PlaceHolders来显示不同的内容:
webpack.js.org/loaders/fil…
我们可以在文档中查阅自己需要的placeholder;
我们这里介绍几个最常用的placeholder:
[ext]: 处理文件的扩展名;
[name]: 处理文件的名称;
[hash]: 文件的内容,使用MD4的散列函数处理,生成的一个128位的hash值(32个十六进制);
[contentHash]: 在file-loader中和[hash]结果是一致的(在webpack的一些其他地方不一样,后面会讲到);
[hash:<length>] 截图hash的长度,默认32个字符太长了;
[path]: 文件相对于webpack配置文件的路径;
- 设置文件的名称
那么我们可以按照如下的格式编写:
这个也是vue的写法;
- 设置文件的存放路径
当然,我们刚才通过img/ 已经设置了文件夹,这个也是vue、react脚手架中常见的设置方式:
其实按照这种设置方式就可以了;
当然我们也可以通过outputPath来设置输出的文件夹;
url-loader
url-loader和file-loader的工作方式是相似的,但是可以将较小的文件,转成base64的URI。
安装url-loader:
npm install url-loader -D
显示结果是一样的,并且图片可以正常显示;
但是在dist文件夹中,我们会看不到图片文件:
这是因为我的两张图片的大小分别是38kb和295kb;
默认情况下url-loader会将所有的图片文件转成base64编码
- url-loader的limit
但是开发中我们往往是小的图片需要转换,但是大的图片直接使用图片即可
这是因为小的图片转换base64之后可以和页面一起被请求,减少不必要的请求过程;
而大的图片也进行转换,反而会影响页面的请求速度;
那么,我们如何可以限制哪些大小的图片转换和不转换呢?
url-loader有一个options属性limit,可以用于设置转换的限制;
下面的代码38kb的图片会进行base64编码,而295kb的不会;
认识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,并且配置资源体积限制实现;
- asset module type的使用 比如加载图片,我们可以使用下面的方式:
但是,如何可以自定义文件的输出路径和文件名呢?
方式一:修改output,添加assetModuleFilename属性;
方式二:在Rule中,添加一个generator属性,并且设置filename;
- url-loader的limit效果
我们需要两个步骤来实现:
步骤一:将type修改为asset;
步骤二:添加一个parser属性,并且制定dataUrl的条件,添加maxSize属性;
加载字体文件
如果我们需要使用某些特殊的字体或者字体图标,那么我们会引入很多字体相关的文件,这些文件的处理也是一样的。
首先,我从阿里图标库中下载了几个字体图标:
在component中引入,并且添加一个i元素用于显示字体图标:
- 字体的打包
这个时候打包会报错,因为无法正确的处理eot、ttf、woff等文件:
我们可以选择使用file-loader来处理,也可以选择直接使用webpack5的资源模块类型来处理;
认识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可以用于执行更加广泛的任务,比如打包优化、资源管理、环境变量注入等;
CleanWebpackPlugin
前面我们演示的过程中,每次修改了一些配置,重新打包时,都需要手动删除dist文件夹:
我们可以借助于一个插件来帮助我们完成,这个插件就是CleanWebpackPlugin;
首先,我们先安装这个插件:
npm install clean-webpack-plugin -D
之后在插件中配置:
HtmlWebpackPlugin
另外还有一个不太规范的地方:
我们的HTML文件是编写在根目录下的,而最终打包的dist文件夹中是没有index.html文件的。
在进行项目部署的时,必然也是需要有对应的入口文件index.html;
所以我们也需要对index.html进行打包处理;
对HTML进行打包处理我们可以使用另外一个插件:HtmlWebpackPlugin;
npm install html-webpack-plugin -D
- 生成index.html分析
我们会发现,现在自动在dist文件夹中,生成了一个index.html的文件:
该文件中也自动添加了我们打包的bundle.js文件;
这个文件是如何生成的呢?
默认情况下是根据ejs的一个模板来生成的;
在html-webpack-plugin的源码中,有一个default_index.ejs模块;
- 自定义HTML模板
如果我们想在自己的模块中加入一些比较特别的内容:
比如添加一个noscript标签,在用户的JavaScript被关闭时,给予响应的提示;
比如在开发vue或者react项目时,我们需要一个可以挂载后续组件的根标签<div id="app"></div>;
这个我们需要一个属于自己的index.html模块:
- 自定义模板数据填充 上面的代码中,会有一些类似这样的语法<% 变量%>,这个是EJS模块填充数据的方式。
在配置HtmlWebpackPlugin时,我们可以添加如下配置:
template: 指定我们要使用的模块所在的路径;
title: 在进行htmlWebpackPlugin.options.title读取时,就会读到该信息;
DefinePlugin的介绍
但是,这个时候编译还是会报错,因为在我们的模块中还使用到一个BASE_URL的常量:
这是因为在编译template模块时,有一个BASE_URL:
<link rel="icon" href="<%= BASE_URL %>favicon.ico">;
但是我们并没有设置过这个常量值,所以会出现没有定义的错误;
这个时候我们可以使用DefinePlugin插件;
DefinePlugin允许在编译时创建配置的全局常量,是一个webpack内置的插件(不需要单独安装):
这个时候,编译template就可以正确的编译了,会读取到BASE_URL的值;
CopyWebpackPlugin
在vue的打包过程中,如果我们将一些文件放到public的目录下,那么这个目录会被复制到dist文件夹中。
这个复制的功能,我们可以使用CopyWebpackPlugin来完成;
安装CopyWebpackPlugin插件:npm install copy-webpack-plugin -D
接下来配置CopyWebpackPlugin即可:
复制的规则在patterns中设置;
from: 设置从哪一个源中开始复制;
to: 复制到的位置,可以省略,会默认复制到打包的目录下;
globOptions: 设置一些额外的选项,其中可以编写需要忽略的文件:
.DS_Store: mac目录下回自动生成的一个文件;
index.html: 也不需要复制,因为我们已经通过HtmlWebpackPlugin完成了index.html的生成;
Mode配置
前面我们一直没有讲mode。
Mode配置选项,可以告知webpack使用响应模式的内置优化:
默认值是production(什么都不设置的情况下);
可选值有:'none' | 'development' | 'production';
这几个选项有什么样的区别呢?
- Mode配置,代表更多的潜在配置
mode设置为:development代表设置了如下配置
mode设置为:production代表设置了如下配置:
3.babel对JS-Vue处理
为什么需要babel?
事实上,在开发中我们很少直接去接触babel,但是babel对于前端开发来说,目前是不可缺少的一部分:
开发中,我们想要使用ES6+的语法,想要使用TypeScript,开发React项目,它们都是离不开Babel的;
所以,学习Babel对于我们理解代码从编写到线上的转变过程至关重要;
那么,Babel到底是什么呢?
Babel是一个工具链,主要用于旧浏览器或者环境中将ECMAScript 2015+代码转换为向后兼容版本的JavaScript;
包括:语法转换、源代码转换等;
Babel命令行使用
babel本身可以作为一个独立的工具(和postcss一样),不和webpack等构建工具配置来单独使用。
如果我们希望在命令行尝试使用babel,需要安装如下库:
@babel/core: babel的核心代码,必须安装;
@babel/cli: 可以让我们在命令行使用babel;
npm install @babel/cli @babel/core -D
使用babel来处理我们的源代码:
src:是源文件的目录;
--out-dir:指定要输出的文件夹dist;
npx babel src --out-dir dist
插件的使用
比如我们需要转换箭头函数,那么我们就可以使用箭头函数转换相关的插件:
npm install @babel/plugin-transform-arrow-functions -D
npx babel src --out-dir dist --plugins=@babel/plugin-transform-arrow-functions
查看转换后的结果:我们会发现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
Babel的预设preset
但是如果要转换的内容过多,一个个设置是比较麻烦的,我们可以使用预设(preset):
后面我们再具体来讲预设代表的含义;
安装@babel/preset-env预设:
npm install @babel/preset-env -D
执行如下命令:
npx babel src --out-dir dist --presets=@babel/preset-env
Babel的底层原理
babel是如何做到将我们的一段代码(ES6、TypeScript、React) 转成另外一段代码(ES5) 的呢?
从一种源代码(原生语言) 转换成另一种源代码(目标语言),这是什么的工作呢?
就是编译器,事实上我们可以将babel看成就是一个编译器。
Babel编译器的作用就是将我们的源代码,转换成浏览器可以直接识别的另外一段源代码;
Babel也拥有编译器的工作流程:
解析阶段(Parsing)
转换阶段(Transformation)
生成阶段(Code Generation)
Babel编译器执行原理
Babel的执行阶段
当然,这只是一个简化版的编译器工具流程,在每个阶段又会有自己具体的工作:
babel-loader
在实际开发中,我们通常会在构建工具中通过配置babel来对其进行使用的,比如在webpack中。
那么我们就需要去安装相关的依赖:
如果之前已经安装了@babel/core,那么这里不需要再次安装;
npm install babel-loader @babel/core
我们可以设置一个规则,在加载js文件时,使用我们的babel:
- 我们必须指定使用的插件才会生效
babel-preset
如果我们一个个去安装使用插件,那么需要手动来管理大量的babel插件,我们可以直接给webpack提供一个preset,webpack会根据我们的预设来加载对应的插件列表,并且将其传递给babel。
比如常见的预设有三个:
env
react
TypeScript
安装preset-env:
npm install @babel/preset-env
Babel的配置文件
像之前一样,我们可以将babel的配置信息放到一个独立的文件中,babel给我们提供了两种配置文件的编写:
babel.config.json(或者.js,.cjs,.mjs)文件;
.babelrc.json(或者.babelrc,.js,.cjs,.mjs)文件;
它们两个有什么区别呢?目前很多的项目都采用了多包管理的方式(babel本身、element-plus、umi等);
.babelrc.json:早期使用较多的配置方式,但是对于配置Monorepos项目是比较麻烦的;
babel.config.json(babel7):可以直接作用于Monorepos项目的子包,更加推荐;
Vue源码的打包
我们课程主要是学习Vue的,那么我们应该包含Vue相关的代码:
界面上是没有效果的:
并且我们查看运行的控制台,会发现如下的警告信息;
Vue打包后不同版本解析
vue(.runtime).global(.prod).js:
通过浏览器中的<script src=“...”> 直接使用;
我们之前通过CDN引入和下载的Vue版本就是这个版本;
会暴露一个全局的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中使用;
运行时+编译器vs 仅运行时
在Vue的开发过程中我们有三种方式来编写DOM元素:
方式一:template模板的方式(之前经常使用的方式);
方式二:render函数的方式,使用h函数来编写渲染的内容;
方式三:通过 .vue文件中的template来编写模板;
它们的模板分别是如何处理的呢?
方式二中的h函数可以直接返回一个虚拟节点,也就是Vnode节点;
方式一和方式三的template都需要有特定的代码来对其进行解析:
√方式三.vue文件中的template可以通过在vue-loader对其进行编译和处理;
√方式一种的template我们必须要通过源码中一部分代码来进行编译;
所以,Vue在让我们选择版本的时候分为运行时+编译器vs 仅运行时
运行时+编译器包含了对template模板的编译代码,更加完整,但是也更大一些;
仅运行时没有包含对template版本的编译代码,相对更小一些;
全局标识的配置
我们会发现控制台还有另外的一个警告:
在GitHub上的文档中我们可以找到说明:
这是两个特性的标识,一个是使用Vue的Options,一个是Production模式下是否支持devtools工具;
虽然他们都有默认值,但是强烈建议我们手动对他们进行配置;
VSCode对SFC文件的支持
在前面我们提到过,真实开发中多数情况下我们都是使用SFC( single-file components (单文件组件) )。
我们先说一下VSCode对SFC的支持:
插件一:Vetur,从Vue2开发就一直在使用的VSCode支持Vue的插件;
插件二:Volar,官方推荐的插件(后续会基于Volar开发官方的VSCode插件);
编写App.vue代码
接下来我们编写自己的App.vue代码:
App.vue的打包过程
我们对代码打包会报错:我们需要合适的Loader来处理文件。
这个时候我们需要使用vue-loader:
npm install vue-loader -D
在webpack的模板规则中进行配置:
- @vue/compiler-sfc
打包依然会报错,这是因为我们必须添加@vue/compiler-sfc来对template进行解析:
npm install @vue/compiler-sfc -D
另外我们需要配置对应的Vue插件:
重新打包即可支持App.vue的写法
另外,我们也可以编写其他的.vue文件来编写自己的组件;
4.webpack服务器和VueCLI
为什么要搭建本地服务器?
目前我们开发的代码,为了运行需要有两个操作:
操作一:npm run build,编译相关的代码;
操作二:通过live server或者直接通过浏览器,打开index.html代码,查看效果;
这个过程经常操作会影响我们的开发效率,我们希望可以做到,当文件发生变化时,可以自动的完成编译和展示;
为了完成自动编译,webpack提供了几种可选的方式:
webpack watch mode;
webpack-dev-server(常用);
webpack-dev-middleware;
- Webpack watch
webpack给我们提供了watch模式:
在该模式下,webpack依赖图中的所有文件,只要有一个发生了更新,那么代码将被重新编译;
我们不需要手动去运行npm run build指令了;
如何开启watch呢? 两种方式:
方式一:在导出的配置中,添加watch: true;
方式二:在启动webpack的命令中,添加--watch的标识;
这里我们选择方式二,在package.json的scripts 中添加一个watch 的脚本:
- webpack-dev-server
上面的方式可以监听到文件的变化,但是事实上它本身是没有自动刷新浏览器的功能的:
当然,目前我们可以在VSCode中使用live-server来完成这样的功能;
但是,我们希望在不使用live-server的情况下,可以具备live reloading(实时重新加载) 的功能;
安装webpack-dev-server
npm install webpack-dev-server -D
修改配置文件,告知dev server,从什么位置查找文件:
webpack-dev-server 在编译之后不会写入到任何输出文件。而是将bundle 文件保留在内存中:
事实上webpack-dev-server使用了一个库叫memfs(memory-fs webpack自己写的)
认识模块热替换(HMR)
什么是HMR呢?
HMR的全称是Hot Module Replacement,翻译为模块热替换;
模块热替换是指在应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个页面;
HMR通过如下几种方式,来提高开发的速度:
不重新加载整个页面,这样可以保留某些应用程序的状态不丢失;
只更新需要变化的内容,节省开发的时间;
修改了css、js源代码,会立即在浏览器更新,相当于直接在浏览器的devtools中直接修改样式;
如何使用HMR呢?
默认情况下,webpack-dev-server已经支持HMR,我们只需要开启即可;
在不开启HMR的情况下,当我们修改了源代码之后,整个页面会自动刷新,使用的是live reloading;
- 开启HMR 修改webpack的配置:
浏览器可以看到如下效果:
但是你会发现,当我们修改了某一个模块的代码时,依然是刷新的整个页面:
这是因为我们需要去指定哪些模块发生更新时,进行HMR;
- 框架的HMR
有一个问题: 在开发其他项目时,我们是否需要经常手动去写入module.hot.accpet相关的API呢?
比如开发Vue、React项目,我们修改了组件,希望进行热更新,这个时候应该如何去操作呢?
事实上社区已经针对这些有很成熟的解决方案了:
比如vue开发中,我们使用vue-loader,此loader支持vue组件的HMR,提供开箱即用的体验;
比如react开发中,有React Hot Loader,实时调整react组件(目前React官方已经弃用了,改成使用reactrefresh);
接下来我们来演示一下Vue实现一下HMR功能。
- HMR的原理
那么HMR的原理是什么呢?如何可以做到只更新一个模块中的内容呢?
webpack-dev-server会创建两个服务:提供静态资源的服务(express)和Socket服务(net.Socket);
express server负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析);
HMR Socket Server,是一个socket的长连接:
长连接有一个最好的好处是建立连接后双方可以通信(服务器可以直接发送文件到客户端);
当服务器监听到对应的模块发生变化时,会生成两个文件.json(manifest文件)和.js文件(update chunk);
通过长连接,可以直接将这两个文件主动发送给客户端(浏览器);
浏览器拿到两个新的文件后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新;
- HMR的原理图
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地址是可以访问的;
- port、open、compress port设置监听的端口,默认情况下是8080
open是否打开浏览器:
默认值是false,设置为true会打开浏览器;
也可以设置为类似于Google Chrome等值;
compress是否为静态文件开启gzip compression:
默认值是false,可以设置为true;
- Proxy
proxy是我们开发中非常常用的一个配置选项,它的目的设置代理来解决跨域访问的问题:
比如我们的一个api请求是http://localhost:8888,但是本地启动服务器的域名是http://localhost:8000,这个时候发送网络请求就会出现跨域的问题;
那么我们可以将请求先发送到一个代理服务器,代理服务器和API服务器没有跨域的问题,就可以解决我们的跨域问题了;
我们可以进行如下的设置:
target: 表示的是代理到的目标地址,比如/api-hy/moment会被代理到http://localhost:8888/apihy/moment;
pathRewrite: 默认情况下,我们的/api-hy 也会被写入到URL中,如果希望删除,可以使用pathRewrite;
secure: 默认情况下不接收转发到https的服务器上,如果希望支持,可以设置为false;
changeOrigin: 它表示是否更新代理后请求的headers中host地址;
- changeOrigin的解析
这个changeOrigin官方说的非常模糊,通过查看源码我发现其实是要修改代理请求中的headers中的host属性:
因为我们真实的请求,其实是需要通过http://localhost:8888来请求的;
但是因为使用了代码,默认情况下它的值时http://localhost:8000;
如果我们需要修改,那么可以将changeOrigin设置为true即可;
historyApiFallback
historyApiFallback是开发中一个非常常见的属性,它主要的作用是解决SPA页面在路由跳转之后,进行页面刷新 时,返回404的错误。
boolean值:默认是false
如果设置为true,那么在刷新时,返回404错误时,会自动返回index.html 的内容;
object类型的值,可以配置rewrites属性:
可以配置from来匹配路径,决定要跳转到哪一个页面;
事实上devServer中实现historyApiFallback功能是通过connect-history-api-fallback库的:
可以查看 connect-history-api-fallback文档
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来解析扩展名;
extensions和alias配置
extensions是解析到文件时自动添加扩展名:
默认值是['.wasm', '.mjs', '.js', '.json'];
所以如果我们代码中想要添加加载.vue 或者jsx 或者ts 等文件时,我们必须自己写上扩展名;
另一个非常好用的功能是配置别名alias:
特别是当我们项目的目录结构比较深的时候,或者一个文件的路径可能需要../../../这种路径片段;
我们可以给某些常见的路径起一个别名;
如何区分开发环境
目前我们所有的webpack配置信息都是放到一个配置文件中的:webpack.config.js
当配置越来越多时,这个文件会变得越来越不容易维护;
并且某些配置是在开发环境需要使用的,某些配置是在生成环境需要使用的,当然某些配置是在开发和生成环境都会使用的;
所以,我们最好对配置进行划分,方便我们维护和管理;
那么,在启动时如何可以区分不同的配置呢?
方案一:编写两个不同的配置文件,开发和生成时,分别加载不同的配置文件即可;
方式二:使用相同的一个入口配置文件,通过设置参数来区分它们;
入口文件解析
我们之前编写入口文件的规则是这样的:./src/index.js,但是如果我们的配置文件所在的位置变成了config 目录,我们是否应该变成../src/index.js呢?
如果我们这样编写,会发现是报错的,依然要写成./src/index.js;
这是因为入口文件其实是和另一个属性时有关的context;
context的作用是用于解析入口(entry point)和加载器(loader):
官方说法:默认是当前路径(但是经过我测试,默认应该是webpack的启动目录)
另外推荐在配置中传入一个值;
区分开发和生成环境配置
这里我们创建三个文件:
webpack.comm.conf.js
webpack.dev.conf.js
webpack.prod.conf.js
具体的分离代码这里不再给出,查看课堂代码;
5.VueCLI和Vite
Vue CLI脚手架
什么是Vue脚手架?
我们前面学习了如何通过webpack配置Vue的开发环境,但是在真实开发中我们不可能每一个项目从头来完成所有的webpack配置,这样显示开发的效率会大大的降低;
所以在真实开发中,我们通常会使用脚手架来创建一个项目,Vue的项目我们使用的就是Vue的脚手架;
脚手架其实是建筑工程中的一个概念,在我们软件工程中也会将一些帮助我们搭建项目的工具称之为脚手架;
Vue的脚手架就是Vue CLI:
CLI是Command-Line Interface, 翻译为命令行界面;
我们可以通过CLI选择项目的配置和创建出我们的项目;
Vue CLI已经内置了webpack相关的配置,我们不需要从零来配置;
Vue CLI 安装和使用
安装Vue CLI(目前最新的版本是v4.5.15 2021/12/7)
我们是进行全局安装,这样在任何时候都可以通过vue的命令来创建项目;
npm install @vue/cli -g
升级Vue CLI:
如果是比较旧的版本,可以通过下面的命令来升级
npm update @vue/cli -g
通过Vue的命令来创建项目
vue create 项目的名称
vue create 项目的过程
项目的目录结构
Vue CLI的运行原理
认识Vite
Webpack是目前整个前端使用最多的构建工具,但是除了webpack之后也有其他的一些构建工具:
比如rollup、parcel、gulp、vite等等
什么是vite呢?
官方的定位:下一代前端开发与构建工具;
如何定义下一代开发和构建工具呢?
我们知道在实际开发中,我们编写的代码往往是不能被浏览器直接识别的,比如ES6、TypeScript、Vue文件等等;
所以我们必须通过构建工具来对代码进行转换、编译,类似的工具有webpack、rollup、parcel;
但是随着项目越来越大,需要处理的JavaScript呈指数级增长,模块越来越多;
构建工具需要很长的时间才能开启服务器,HMR也需要几秒钟才能在浏览器反应出来;
所以也有这样的说法:天下苦webpack久矣;
Vite (法语意为"快速的",发音/vit/) 是一种新型前端构建工具,能够显著提升前端开发体验。
Vite的构造
它主要由两部分组成:
一个开发服务器,它基于原生ES模块提供了丰富的内建功能,HMR的速度非常快速;
一套构建指令,它使用rollup打开我们的代码,并且它是预配置的,可以输出生成环境的优化过的静态资源;
目前是否要大力学习vite?vite的未来是怎么样的?
我个人非常看好vite的未来,也希望它可以有更好的发展;
但是,目前vite虽然已经更新到2.0,依然并不算非常的稳定,并且比较少大型项目(或框架)使用vite来进行构建;
vite的整个社区插件等支持也还不够完善;
包括vue脚手架本身,目前也还没有打算迁移到vite,而依然使用webpack(虽然后期一定是有这个打算的);
所以vite看起来非常的火热,在面试也可能会问到,但是实际项目中应用的还比较少;
浏览器原生支持模块化
但是如果我们不借助于其他工具,直接使用ES Module来开发有什么问题呢?
首先,我们会发现在使用loadash时,加载了上百个模块的js代码,对于浏览器发送请求是巨大的消耗;
其次,我们的代码中如果有TypeScript、less、vue等代码时,浏览器并不能直接识别;
事实上,vite就帮助我们解决了上面的所有问题。
Vite的安装和使用
注意: Vite本身也是依赖Node的,所以也需要安装好Node环境
并且Vite要求Node版本是大于12版本的;
首先,我们安装一下vite工具:
npm install vite –g # 全局安装
npm install vite –D # 局部安装
通过vite来启动项目:
npx vite
Vite对css的支持
vite可以直接支持css的处理
直接导入css即可;
vite可以直接支持css预处理器,比如less
直接导入less;
之后安装less编译器;
npm install less -D
vite直接支持postcss的转换:
只需要安装postcss,并且配置postcss.config.js 的配置文件即可;
npm install postcss postcss-preset-env -D
Vite对TypeScript的支持
vite对TypeScript是原生支持的,它会直接使用ESBuild来完成编译:
只需要直接导入即可;
如果我们查看浏览器中的请求,会发现请求的依然是ts的代码:
这是因为vite中的服务器Connect会对我们的请求进行转发;
获取ts编译后的代码,给浏览器返回,浏览器可以直接进行解析;
注意:在vite2中,已经不再使用Koa了,而是使用Connect来搭建的服务器
Vite对vue的支持
vite对vue提供第一优先级支持:
Vue 3 单文件组件支持:@vitejs/plugin-vue
Vue 3 JSX 支持:@vitejs/plugin-vue-jsx
Vue 2 支持:underfin/vite-plugin-vue2
安装支持vue的插件:
npm install @vitejs/plugin-vue -D
在vite.config.js中配置插件:
Vite打包项目
我们可以直接通过vite build来完成对当前项目的打包工具:
npx vite build
我们可以通过preview的方式,开启一个本地服务来预览打包后的效果:
npx vite preview
ESBuild解析
ESBuild的特点:
超快的构建速度,并且不需要缓存;
支持ES6和CommonJS的模块化;
支持ES6的Tree Shaking;
支持Go、JavaScript的API
支持TypeScript、JSX等语法编译;
支持SourceMap;
支持代码压缩;
支持扩展其他插件;
ESBuild的构建速度
ESBuild的构建速度和其他构建工具速度对比:
ESBuild为什么这么快呢?
使用Go语言编写的,可以直接转换成机器代码,而无需经过字节码;
ESBuild可以充分利用CPU的多内核,尽可能让它们饱和运行;
ESBuild的所有内容都是从零开始编写的,而不是使用第三方,所以从一开始就可以考虑各种性能问题;
等等....
Vite脚手架工具
在开发中,我们不可能所有的项目都使用vite从零去搭建,比如一个react项目、Vue项目;
这个时候vite还给我们提供了对应的脚手架工具;
所以Vite实际上是有两个工具的:
vite:相当于是一个构件工具,类似于webpack、rollup;
@vitejs/create-app:类似vue-cli、create-react-app;
如果使用脚手架工具呢?
npm init @vitejs/app
上面的做法相当于省略了安装脚手架的过程:
npm install @vitejs/create-app -g
create-app