S09-02 Webpack5-跨域、source-map、Babel、devServer
[TOC]
跨域
概述
同源策略: 是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。
同源: 如果两个 URL 的 protocol、port(如果有指定的话) 和 host都相同的话,则这两个 URL 是同源。这个方案也被称为“协议/主机/端口元组”,或者直接是“元组”。
下表给出了与 URL http://store.company.com/dir/page.html
的源进行对比的示例:
URL | 结果 | 原因 |
---|---|---|
http://store.company.com/dir2/other.html | 同源 | 只有路径不同 |
http://store.company.com/dir/inner/another.html | 同源 | 只有路径不同 |
https://store.company.com/secure.html | 失败 | 协议不同 |
http://store.company.com:81/dir/etc.html | 失败 | 端口不同(http:// 默认端口是 80) |
http://news.company.com/dir/other.html | 失败 | 主机不同 |
跨域: 事实上跨域的产生和前端分离的发展有很大的关系:
早期的服务器端渲染的时候,是没有跨域的问题的;
但是随着前后端的分离,目前前端开发的代码和服务器开发的API接口往往是分离的,甚至部署在不同的服务器上的;
这个时候我们就会发现,访问 静态资源服务器 和 API接口服务器 很有可能不是同一个服务器或者不是同一个端口。
- 浏览器发现静态资源和API接口(XHR、Fetch)请求不是来自同一个地方时(同源策略),就产生了跨域。
所以,在静态资源服务器和API服务器(其他资源类同)是同一台服务器时,是没有跨域问题的。
前端我们学习了很多服务器开发的知识,接下来,我们就可以演示一下跨域产生和不产生的项目部署区别了。
跨域演练
搭建服务器
1、生成package.json
文件
pnpm init
2、安装 koa 、@koa/router
pnpm i koa @koa/router
3、创建src/index.js
文件
4、添加脚本
5、运行服务器
pnpm run start
添加接口
1、添加/list
接口
2、在浏览器中发送请求,获取数据
前端代码
实际开发中,并不是直接在浏览器中通过发送url地址直接获取接口数据的。而是在前端代码中发起请求,获取的数据
1、编写前端代码
2、在前端代码中通过xhr
、fetch
、axios
发起请求,获取接口数据
xhr请求
fetch请求
3、如果通过live server
打开index.html
,会开启一个服务器http://localhost:5500
运行文件。此时就会出现在http://localhost:5500
服务器上向http://localhost:8000
的服务器中发送请求。由于它们的端口不同,所以会形成跨域请求。
注意: 在Chrome125版本中居然没有出现跨域问题!其他浏览器依然有跨域问题
解决: 这是由于在chrome上安装了 Allow CORS: Access-Control-Allow-Origin 插件
同服务器部署
如果想解决以上跨域问题,可以将前端静态资源和接口代码部署到同一个服务器上,可以通过 koa-static实现该想法。
1、安装 koa-static
pnpm i koa-static
2、在接口服务器中部署前端静态资源
3、启动服务器
pnpm run start
4、通过http://localhost:8000
访问静态资源,此时静态资源和接口就都在http://localhost:8000
服务器上,不存在跨域问题
解决方案
那么跨域问题如何解决呢?
所以跨域的解决方案几乎都和服务器有关系,单独的前端基本解决不了跨域(虽然网上也能看到各种方案,都是实际开发基本不会使用)。
你说:老师,不对丫,我明明和配置前端的webpack就可以解决跨域问题了。
webpack配置的本质也是在webpack-server的服务器中配置了代理。
跨域常见的解决方案:
方案一:同服务器部署,静态资源和API服务器部署在同一个服务器中;
方案二:CORS, 即是指跨域资源共享;
方案三:Node代理服务器(webpack中就是它);
方案四:Nginx反向代理;
不常见的方案:
jsonp:现在很少使用了(曾经流行过一段时间);
postMessage:有兴趣了解一下吧;
websocket:为了解决跨域,所有的接口都变成socket通信?
…..
方案-同服务器部署
见:同服务器部署
方案-CORS
跨源资源共享(Cross-Origin Resource Sharing,CORS): 它是一种基于http header的机制。该机制通过允许服务器标示除了它自己以外的其它源(域、协议和端口),使得浏览器允许这些 origin 访问加载自己的资源。
浏览器将 CORS 请求分成两类:
简单请求
非简单请求。
简单请求: 只要同时满足以下两大条件,就属于简单请求(不满足就属于非简单请求)(了解即可)。
请求方法是以下是三种方法之一:
HEAD
GET
POST
HTTP 的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain
简单请求设置CORS
非简单请求设置CORS
方案-Node代理服务器(开发)
Node代理服务器是平时开发中前端配置最多的一种方案。
方案1: 同服务器部署Node代理服务器
1、创建Node代理服务器(9000端口)
2、修改前端服务器请求的地址为Node代理服务器的地址
3、安装 http-proxy-middleware
pnpm i http-proxy-middleware
4、在Node代理服务器中,通过插件转发请求地址到目标资源所在的服务器
5、将Node代理服务器和前端服务器部署到同一个服务器中
6、在浏览器通过locahost:9000
打开前端项目
注意: 在webpack中也是使用的 http-proxy-middleware
插件,配置如下:
module.exports = {
//...
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8000',
pathRewrite: { '^/api': '' }
}
}
}
}
方案2: Node代理服务器开启CORS
前4步同【方案1: 同服务器部署Node代理服务器】
5、在Node代理服务器中设置CORS
5、通过live server
打开前端代码,发起跨域请求
方案-Nginx反向代理(部署)
1、安装Nginx
下载Nginx地址:https://nginx.org/en/download.html
- Linux: http://note.mrer.top/doc/summary/Z03 部署:服务器部署.html#nginx
- Windows: 正常的软件安装流程
2、配置Nginx反向代理
3、重启Nginx
systemctl restart nginx
nginx
4、修改前端请求代码
前端服务器地址:http://locahost:5500
nginx代理服务器地址:http://localhost
目标资源服务器地址:http://locahost:8000
source-map
概述
source-map: 是一种描述原始源代码与转换后代码之间位置对应关系的映射关系表。通过使用 source-map,开发者可以在浏览器的开发者工具中直接查看源代码,并且能够正确地调试源代码,而不需要去查看压缩后的代码。
我们的代码通常运行在浏览器上时,是通过打包压缩的。也就是真实跑在浏览器上的代码,和我们编写的代码其实是有差异的;
比如 ES6 的代码可能被转换成 ES5;
比如对应的代码行号、列号在经过编译后肯定会不一致;
比如代码进行丑化压缩时,会将变量名称等修改;
比如我们使用了 TypeScript 等方式编写的代码,最终转换成 JavaScript;
但是,当代码报错需要调试时(debug),调试转换后的代码是很困难的
但是我们能保证代码不出错吗?不可能。
那么如何可以调试这种转换后不一致的代码呢?答案就是source-map
开启 source-map
生成 source-map:
1、配置 webpack 生成 source-map:devtool: 'source-map'
根据源文件,生成 source-map 文件,webpack 在打包时,可以通过配置生成 source-map
2、在转换后的代码,最后添加一个注释,它指向 source-map
浏览器会根据我们的注释,查找相应的 source-map,并且根据 source-map 还原我们的代码,方便进行调试。
//# sourceMappingURL=bundle.js.map
打开浏览器 source-map:
在 Chrome 中,我们可以按照如下的方式开启 source-map:
source-map 文件
最初 source-map 生成的文件大小是原始文件的 10 倍,第二版减少了约 50%,第三版又减少了 50%,所以目前一个 133kb 的文件,最终的 source-map 的大小大概在 300kb。
目前的 source-map 长什么样子呢?
- version:当前使用的版本,也就是最新的第三版;
- sources:从哪些文件转换过来的 source-map 和打包的代码(最初始的文件);
- names:转换前的变量和属性名称(因为我目前使用的是 development 模式,所以不需要保留转换前的名称);
- mappings:source-map 用来和源文件映射的信息(比如位置信息等),一串 base64 VLQ(variable-length quantity 可变长度值)编码;
- file:打包后的文件(浏览器加载的文件);
- sourcesContent:转换前的具体代码信息(和 sources 是对应的关系);
- sourceRoot:所有的 sources 相对的根目录;
devtool
作用: 控制是否生成,以及如何生成 source-map
文档: https://webpack.docschina.org/configuration/devtool
常见 devtool 选项:
不同的值会明显影响到构建(build)和重新构建(rebuild)的速度。
不生成 source-map 文件:
false:``,不使用 source-map,也就是没有任何和 source-map 相关的内容。
none:``,
eval:``,
development 环境:
eval:``,(development 模式下的默认值),不生成 source-map 文件。但是可以映射出 source-map
- 但是它会在 eval 执行的代码中,添加
//# sourceURL=
- 它会被浏览器在执行时解析,并且在调试面板中生成对应的一些文件目录,方便我们调试代码。但不是很准确
- 但是它会在 eval 执行的代码中,添加
cheap-source-map: ``,会生成 sourcemap,但是会更加高效一些(cheap 低开销),因为它没有生成列映射
cheap-module-source-map: ``,会生成 sourcemap,类似于 cheap-source-map,但是对源自 loader(如 babel-loader)的 sourcemap 处理会更好。
production 环境:
none:``,(production 模式下的默认值),什么值都不要写,不生成 source-map。
source-map: ``,生成独立的 source-map 文件,最完整的映射关系,但会增加打包后文件的大小,不适合生产环境使用。主要用于打包上线后发现错误时临时调试用
eval-source-map: ``,会生成 source-map,但是 source-map 是以 DataUrl 添加到 eval 函数的后面
inline-source-map: ``,会生成 source-map,但是 source-map 是以 DataUrl 添加到 bundle 文件的后面
hidden-source-map: ,会生成 source-map,但是不会对 source-map 文件进行引用。
- 相当于删除了打包文件中对 source-map 的引用注释。
- 需要手动添加:
//# sourceMappingURL=bundle.js.map
nosources-source-map: ``,会生成 source-map,但是生成的 source-map 只有错误信息的提示,不会生成源代码文件
eval
cheap-source-map
cheap-source-map:
会生成 sourcemap,但是会更加高效一些(cheap 低开销),因为它没有生成列映射(Column Mapping)
因为在开发中,我们只需要行信息通常就可以定位到错误了
cheap-module-source-map
cheap-module-source-map:
- 会生成 sourcemap,类似于 cheap-source-map,但是对源自 loader 的 sourcemap 处理会更好。
**这里有一个很模糊的概念:**对源自 loader 的 sourcemap 处理会更好,官方也没有给出很好的解释
- 其实是如果 loader 对我们的源码进行了特殊的处理,比如 babel;
如果我这里使用了 babel-loader(注意:目前还没有详细讲 babel)
- 可以先按照我的 babel 配置演练;
cheap-source-map
和cheap-module-source-map
的区别:
cheap-module-source-map 对经过 loader 处理后的 source-map 处理的更好,不会额外删除一些空白行
source-map
source-map:
- 生成一个独立的 source-map 文件,并且在 bundle 文件中有一个注释,指向 source-map 文件;
bundle 文件中有如下的注释:
- 开发工具会根据这个注释找到 source-map 文件,并且解析;
//# sourceMappingURL=bundle.js.map
eval-source-map
eval-source-map:会生成 sourcemap,但是 source-map 是以 DataUrl 添加到 eval 函数的后面
inline-source-map
inline-source-map:会生成 sourcemap,但是 source-map 是以 DataUrl 添加到 bundle 文件的后面
hidden-source-map
hidden-source-map:
会生成 sourcemap,但是不会对 source-map 文件进行引用;
相当于删除了打包文件中对 sourcemap 的引用注释;
// 被删除掉的
//# sourceMappingURL=bundle.js.map
如果我们手动添加进来,那么 sourcemap 就会生效了
nosources-source-map
nosources-source-map:
- 会生成 sourcemap,但是生成的 sourcemap 只有错误信息的提示,不会生成源代码文件;
正确的错误提示:
点击错误提示,无法查看源码:
多个值的组合
事实上,webpack 提供给我们的26个值,是可以进行多组合的。
组合的规则如下:
*inline-|hidden-|eval:*三个值时三选一;
*nosources:*可选值;
cheap可选值,并且可以跟随module的值;
[inline-|hidden-|eval-] [nosources-] [cheap-[module-]] source-map
那么在开发中,最佳的实践是什么呢?
开发阶段:推荐使用 source-map或者cheap-module-source-map
- 这分别是 vue 和 react 使用的值,可以获取调试信息,方便快速开发;
测试阶段:推荐使用 source-map或者cheap-module-source-map
- 测试阶段我们也希望在浏览器下看到正确的错误提示;
发布阶段:false、缺省值(不写)
Babel7
概述
事实上,在开发中我们很少直接去接触 babel,但是 babel 对于前端开发来说,目前是不可缺少的一部分:
开发中,我们想要使用 ES6+ 的语法,想要使用 TypeScript,开发 React 项目,它们都是离不开 Babel 的;
所以,学习 Babel 对于我们理解代码从编写到线上的转变过程至关重要;
Babel: 是一个工具链,主要用于旧浏览器或者环境中将 ECMAScript 2015+代码转换为向后兼容版本的 JavaScript,包括:语法转换、源代码转换、Polyfill 实现目标环境缺少的功能等
命令行执行
命令行语法
babel 本身可以作为一个独立的工具(和 postcss 一样),不和 webpack 等构建工具配合来单独使用。
依赖包:
- @babel/core:babel 的核心代码,必须安装
- 安装:
npm i @babel/core -D
- 安装:
- @babel/cli:可以让我们在命令行使用 babel
- 安装:
npm i @babel/cli -D
- 安装:
语法:
npx babel <src> --out-dir <dist> --plugins?=<插件> --presets?=@babel/preset-env
# 注意:此处使用 pnpx 不行
命令参数:
- --out-dir:指定要输出的文件夹 <dist>
- --plugins?=:添加插件,多个插件用
,
分隔。和插件之间使用=
连接 - --presets?=:添加预设插件
@babel/preset-env
注意: 默认情况下,babel 不会转化代码,需要配置相关插件
plugins
依赖插件:
@babel/plugin-transform-arrow-functions:(已废弃)转化箭头函数
- 安装:
npm install @babel/plugin-transform-arrow-functions -D
- 安装:
@babel/plugin-transform-block-scoping:(已废弃)转化变量声明
- 安装:
npm install @babel/plugin-transform-block-scoping -D
- 安装:
1、比如我们需要转换箭头函数,那么我们就可以使用箭头函数转换相关的插件:
npx babel src --out-dir dist --plugins=@babel/plugin-transform-arrow-functions
2、查看转换后的结果:我们会发现 const 并没有转成 var
这是因为 plugin-transform-arrow-functions,并没有提供这样的功能;
我们需要使用 plugin-transform-block-scoping 来完成这样的功能;
npx babel src --out-dir dist --plugins=@babel/plugin-transform-block-scoping,@babel/plugin-transform-arrow-functions
presets
但是如果要转换的内容过多,一个个设置是比较麻烦的,我们可以使用预设(presets):后面我们再具体来讲预设代表的含义;
注意: 预设是根据浏览器环境browerlist
来设置配置的
依赖包:
- @babel/preset-env:可以根据浏览器环境或运行时环境来编译 JS 代码,从而更方便地使用最新的 JS 特性。
- 安装:
npm i @babel/preset-env -D
- 功能:
- 自动插件选择:根据目标环境自动选择需要的 Babel 插件,无需手动配置每个插件。
- 目标环境自定义:可以通过
browserslist
配置文件或直接在 Babel 配置中指定目标环境。 - Polyfill 管理:可以与
@babel/polyfill
或core-js
等工具配合使用,根据需要自动引入 polyfill。 - 模块转换:支持按需转换 ES 模块为 CommonJS、AMD 等模块格式。
- 安装:
执行如下命令:
npx babel src --out-dir dist --presets=@babel/preset-env
底层原理
Babel 的底层原理
babel 是如何做到将我们的一段代码(ES6、TypeScript、React)转成另外一段代码(ES5)的呢?
从一种源代码(原生语言)转换成另一种源代码(目标语言),这是什么的工作呢?
就是编译器,事实上我们可以将 babel 看成就是一个编译器。
Babel 编译器的作用就是将我们的源代码,转换成浏览器可以直接识别的另外一段源代码;
Babel 也拥有编译器的工作流程:
解析阶段(Parsing)
转换阶段(Transformation)
生成阶段(Code Generation)
手写编译器: https://github.com/jamiebuilds/
babel 编译器执行原理
Babel 的执行阶段
当然,这只是一个简化版的编译器工具流程,在每个阶段又会有自己具体的工作:
babel-loader
在实际开发中,我们通常会在构建工具中通过配置 babel 来对其进行使用的,比如在 webpack 中。
依赖包:
- babel-loader
- 安装:
npm i babel-loader -D
- 安装:
配置: 在加载 js 文件时,使用我们的 babel
指定使用的插件:
我们必须指定使用的插件才会生效
babel-preset
如果我们一个个去安装使用插件,那么需要手动来管理大量的 babel 插件,我们可以直接给 webpack 提供一个 preset,webpack 会根据我们的预设来加载对应的插件列表,并且将其传递给 babel。
比如常见的预设有三个:
@babel/preset-env
@babel/preset-react
@babel/preset-TypeScript
安装 preset-env:
npm install @babel/preset-env
注意: presets 还有另外一种写法。二者的不同在于:使用数组时,可以添加其他的配置选项(如 target,...)
浏览器兼容
浏览器兼容性
我们来思考一个问题:开发中,浏览器的兼容性问题,我们应该如何去解决和处理?
当然这个问题很笼统,这里我说的兼容性问题不是指屏幕大小的变化适配;
我这里指的兼容性是针对不同的浏览器支持的特性:比如 css 特性、js 语法之间的兼容性;
我们知道市面上有大量的浏览器:
有 Chrome、Safari、IE、Edge、Chrome for Android、UC Browser、QQ Browser 等等;
它们的市场占有率是多少?我们要不要兼容它们呢?
其实在很多的脚手架配置中,都能看到类似于这样的配置信息:
- 这里的百分之一,就是指市场占有率
> 1%
last 2 versions
not dead
浏览器市场占有率:
但是在哪里可以查询到浏览器的市场占有率呢?
这个最好用的网站,也是我们工具通常会查询的一个网站就是 caniuse;
设置目标浏览器方法:
我们最终打包的 JavaScript 代码,是需要跑在目标浏览器上的,那么如何告知 babel 我们的目标浏览器呢?
browserslist 工具
targets 属性
Stage-X
browserslist
概述
但是有一个问题,我们如何可以在 css 兼容性和 js 兼容性下共享我们配置的兼容性条件呢?
就是当我们设置了一个条件: > 1%;
我们表达的意思是 css 要兼容市场占有率大于 1%的浏览器,js 也要兼容市场占有率大于 1%的浏览器;
如果我们是通过工具来达到这种兼容性的,比如我们讲到的 postcss-preset-env、babel、autoprefixer 等
如何可以让他们共享我们的配置呢?
- 这个问题的答案就是browserslist;
browserslist: 是一个在不同的前端工具之间,共享目标浏览器和 Node.js 版本的配置:
浏览器查询过程
我们可以编写类似于这样的配置:
> 1%
last 2 versions
not dead
那么之后,这些工具会根据我们的配置来获取相关的浏览器信息,以方便决定是否需要进行兼容性的支持:
条件查询使用的是 caniuse-lite 的工具,这个工具的数据来自于 caniuse 的网站上;
编写规则
那么在开发中,我们可以编写的条件都有哪些呢?(加红部分是最常用的)
defaults:Browserslist 的默认浏览器(
> 0.5%, last 2 versions, Firefox ESR, not dead
)。5%:通过全局使用情况统计信息选择的浏览器版本。 >=,<和<=工作过。
5% in US
:使用美国使用情况统计信息。它接受两个字母的国家/地区代码。> 5% in alt-AS
:使用亚洲地区使用情况统计信息。有关所有区域代码的列表,请参见 caniuse-lite/data/regions> 5% in my stats
:使用自定义用法数据。>5% in browserslist-config-mycompany stats
:使用 来自的自定义使用情况数据 browserslist-config-mycompany/browserslist-stats.json。cover 99.5%
:提供覆盖率的最受欢迎的浏览器。cover 99.5% in US
:与上述相同,但国家/地区代码由两个字母组成。cover 99.5% in my stats
:使用自定义用法数据。
dead:24 个月内没有官方支持或更新的浏览器。现在是 IE 10,IE_Mob 11,BlackBerry 10,BlackBerry 7, Samsung 4 和 OperaMobile 12.1。
last 2 versions:每个浏览器的最后 2 个版本。
last 2 Chrome versions
:最近 2 个版本的 Chrome 浏览器。last 2 major versions
或last 2 iOS major versions
:最近 2 个主要版本的所有次要/补丁版本。
node 10 和 node 10.4:选择最新的 Node.js10.x.x 或 10.4.x 版本。
current node
:Browserslist 现在使用的 Node.js 版本。maintained node versions
:所有 Node.js 版本,仍由 Node.js Foundation 维护。
iOS 7:直接使用 iOS 浏览器版本 7。
Firefox > 20
:Firefox 的版本高于 20 >=,<并且<=也可以使用。它也可以与 Node.js 一起使用。ie 6-8
:选择一个包含范围的版本。Firefox ESR
:最新的[Firefox ESR]版本。PhantomJS 2.1
和PhantomJS 1.9
:选择类似于 PhantomJS 运行时的 Safari 版本。
extends browserslist-config-mycompany:从 browserslist-config-mycompany npm 包中查询 。
supports es6-module:支持特定功能的浏览器。
- es6-module 这是“我可以使用” 页面 feat 的 URL 上的参数。有关所有可用功能的列表,请参见 。caniuse-lite/data/features
browserslist config:在 Browserslist 配置中定义的浏览器。在差异服务中很有用,可用于修改用户的配置,例如 browserslist config and supports es6-module。
since 2015 或 last 2 years:自 2015 年以来发布的所有版本(since 2015-03 以及 since 2015-03-10)。
unreleased versions 或 unreleased Chrome versions:Alpha 和 Beta 版本。
not ie <= 8:排除先前查询选择的浏览器。
命令行使用
我们可以直接通过命令来查询某些条件所匹配到的浏览器:
npx browserslist ">1%, last 2 versions, not dead"
配置文件使用
配置方案:
我们如何可以配置 browserslist 呢?两种方案:
方案一:在 package.json 中配置;
方案二:单独的一个配置文件.browserslistrc 文件;
方案一: package.json 配置:
方案二: .browserslistrc 文件
默认配置:
如果没有配置,那么也会有一个默认配置:
条件关系:
我们编写了多个条件之后,多个条件之间是什么关系呢?
targets(弃)
之前我们已经使用了 browserslist 工具,我们可以对比一下不同的配置,打包的区别:
配置方法:
我们也可以通过 targets 来进行配置:
那么,如果两个同时配置了,哪一个会生效呢?
配置的 targets 属性会覆盖 browserslist;
但是在开发中,更推荐通过 browserslist 来配置,因为 browserslist 可以在多个前端工具(postcss、babel...)之间共享浏览器兼容性配置
Stage-X(弃)
要了解 Stage-X,我们需要先了解一下 TC39 的组织:
TC39 是指技术委员会(Technical Committee)第 39 号;
它是 ECMA 的一部分,ECMA 是 “ECMAScript” 规范下的 JavaScript 语言标准化的机构;
ECMAScript 规范定义了 JavaScript 如何一步一步的进化、发展;
TC39 遵循的原则是:分阶段加入不同的语言特性,新流程涉及四个不同的 Stage
stage-0: strawman(稻草人),任何尚未提交作为正式提案的讨论、想法变更或者补充都被认为是第 0 阶段的"稻草人";
stage-1: proposal(提议),提案已经被正式化,并期望解决此问题,还需要观察与其他提案的相互影响;
stage-2: draft(草稿),Stage 2 的提案应提供规范初稿、草稿。此时,语言的实现者开始观察 runtime 的具体实现是否合理;
stage-3: candidate(候补),Stage 3 提案是建议的候选提案。在这个高级阶段,规范的编辑人员和评审人员必须在最终规范上签字。Stage 3 的提案不会有太大的改变,在对外发布之前只是修正一些问题;
stage-4: finished(完成),进入 Stage 4 的提案将包含在 ECMAScript 的下一个修订版中;
配置方法:
在 babel7 之前(比如 babel6 中),我们会经常看到这种设置方式:
它表达的含义是使用对应的 babel-preset-stage-x 预设;
但是从 babel7 开始,已经不建议使用了,建议使用 preset-env 来设置;
配置文件
像之前一样,我们可以将 babel 的配置信息放到一个独立的文件中,babel 给我们提供了两种配置文件的编写:
babel.config.json(或者.js,.cjs,.mjs)文件,(推荐);
.babelrc.json(或者.babelrc,.js,.cjs,.mjs)文件;
它们两个有什么区别呢?目前很多的项目都采用了多包管理的方式(babel 本身、element-plus、umi 等);
.babelrc.json:早期使用较多的配置方式,但是对于配置 Monorepo 项目是比较麻烦的;
babel.config.json(babel7):(推荐),可以直接作用于 Monorepo 项目的子包;
js 写法:
json 写法:
polyfill
概述
Polyfill 是什么呢?
翻译:一种用于衣物、床具等的聚酯填充材料, 使这些物品更加温暖舒适;
理解:更像是应该填充物(垫片),一个补丁,可以帮助我们更好的使用 JavaScript;
什么时候会用到 polyfill 呢?
- 比如我们使用了一些语法特性(例如:Promise, Generator, Symbol 等以及实例方法例如 Array.prototype.includes 等)
- 但是某些浏览器压根不认识这些特性,必然会报错;我们可以使用 polyfill 来填充或者说打一个补丁,那么就会包含该特性了;
使用 polyfill
1、依赖包:
babel7.4.0 之前,可以使用 @babel/polyfill 的包,但是该包现在已经不推荐使用了:
shnpm i @babel/polyfill
babel7.4.0 之后,可以通过单独引入 core-js 和 regenerator-runtime 来完成 polyfill 的使用:
shnpm i core-js regenerator-runtime
2、配置 babel.config.js:
我们需要在 babel.config.js
文件中进行配置,给 preset-env 配置一些属性:
useBuiltIns:设置以什么样的方式来使用 polyfill;
false
:(默认),不自动导入任何 polyfill,此时无需设置 corejs 属性。usage
:(推荐),会根据源代码中出现的语言特性,自动检测所需要的 polyfill。entry
:引入所有需要的 Polyfill,但只在代码中指定的位置(入口文件)进行一次性引入。适用于当使用的第三方库依赖于 babel 的情况。
corejs:设置 corejs 的版本,目前使用较多的是 3.x 的版本,比如我使用的是 3.26.1 的版本;
2
:使用 2.x 版本的 corejs。3
:(推荐),使用 3.x 版本的 corejs。注意: 另外 corejs 可以设置是否对提议阶段的特性进行支持;设置 proposals 属性为 true 即可;
useBuiltIns 属性有三个常见的值
第一个值:false
打包后的文件不使用 polyfill 来进行适配;
并且这个时候是不需要设置 corejs 属性的;
第二个值:usage
会根据源代码中出现的语言特性,自动检测所需要的 polyfill;
这样可以确保最终包里的 polyfill 数量的最小化,打包的包相对会小一些;
可以设置 corejs 属性来确定使用的 corejs 的版本;
第三个值:entry
如果我们依赖的某一个库本身使用了某些 polyfill 的特性,但是因为我们使用的是 usage,所以之后用户浏览器可能会报错;
所以,如果你担心出现这种情况,可以使用 entry;
并且需要在入口文件中添加
import 'core-js/stable'
和import 'regenerator-runtime/runtime'
;这样做会根据 browserslist 目标导入所有的 polyfill,但是对应的包也会变大;
1、设置useBuiltIns: "entry"
2、在入口文件index.js
中引入以下包
注意: 可以通过设置exclude
,告诉babel
不要处理/node_modules/
下的代码
编译 JSX
在我们编写 react 代码时,react 使用的语法是 jsx,jsx 是可以直接使用 babel 来转换的。
对 react jsx 代码进行处理需要如下的插件:
但是开发中,我们并不需要一个个去安装这些插件,我们依然可以使用 presets 来配置 @babel/preset-react:
依赖包:
- babel-loader
- 安装:
npm i babel-loader -D
- 安装:
- @babel/preset-react
- 安装:
npm install @babel/preset-react -D
- 安装:
配置:
在webpack.config.js
中如下配置:
打包过程:
1、安装 react 和 react-dom依赖包
pnpm i react react-dom
2、编写 jsx 代码
3、在index.js
中挂载 App 组件
4、编写模板文件index.html
5、通过插件 html-webpack-plugin 使用模板
6、为了让 babel 认识 jsx 语法,需要配置 babel 的 preset
7、效果
编译 TS
在项目开发中,我们会使用 TypeScript 来开发,那么 TypeScript 代码是需要转换成 JavaScript 代码。
我们可以通过以下三种方式编译 TypeScript:
- 命令行编译
- typescript
- webpack 中编译
- ts-loader:使用的是
tsc
作为内核,缺点:不包含 polyfill - babel-loader:(推荐),缺点:不检测类型错误
typescript
可以通过 TypeScript 的 compiler typescript 来转换成 JavaScript:
依赖包:
- typescript
- 安装:
npm i typescript -D
- 安装:
配置:
1、生成 TypeScript 的编译配置信息的配置文件tsconfig.json
npx tsc --init
生成配置文件如下:
2、运行 npx tsc
来编译自己的 ts 代码
npx tsc
ts-loader
如果我们希望在 webpack 中使用 TypeScript,那么我们可以使用 ts-loader 来处理 ts 文件:
依赖包:
- ts-loader
- 安装:
npm i ts-loader -D
- 安装:
配置:
1、在webpack.config.js
中添加ts-loader
配置
2、生成 TypeScript 的编译配置信息的配置文件tsconfig.json
tsc --init
生成配置文件如下:
3、我们通过 npm run build
打包即可。
补充: tsconfig.json 配置
babel-loader
除了可以使用 TypeScript Compiler 来编译 TypeScript 之外,我们也可以使用 Babel:
Babel 是有对 TypeScript 进行支持;
我们可以使用插件: @babel/tranform-typescript;
但是更推荐直接使用 preset:@babel/preset-typescript;
依赖包:
- babel-loader
- 安装:
npm i babel-loader -D
- 安装:
- @babel/preset-typescript
- 安装:
npm i @babel/preset-typescript -D
- 安装:
配置:
1、配置babel-loader
2、配置babel-loader
的预设@babel/preset-typescript
和 polyfill
ts-loader、babel-loader 选择
那么我们在开发中应该选择 ts-loader 还是 babel-loader 呢?
使用 ts-loader(TypeScript Compiler)
来直接编译 TypeScript,那么只能将 ts 转换成 js;
如果我们还希望在这个过程中添加对应的 polyfill,那么 ts-loader 是无能为力的;
我们需要借助于 babel 来完成 polyfill 的填充功能;
使用 babel-loader(Babel)
来直接编译 TypeScript,也可以将 ts 转换成 js,并且可以实现 polyfill 的功能;
但是 babel-loader 在编译的过程中,不会对类型错误进行检测;
那么在开发中,我们如何可以同时保证两个情况都没有问题呢?
编译 TS 最佳实践: 使用 Babel 来完成代码的转换,使用 tsc 来进行类型的检查
事实上 TypeScript 官方文档有对其进行说明:
也就是说我们使用 Babel 来完成代码的转换,使用 tsc 来进行类型的检查。
但是,如何可以使用 tsc 来进行类型的检查呢?
在这里,我在 scripts 中添加了两个脚本,用于类型检查;
我们执行
npm run type-check
可以对 ts 代码的类型进行检测;(推荐),我们执行
npm run type-check-watch
可以实时的检测类型错误;
webpack 服务器
概述
目前我们开发的代码,为了运行需要有两个操作:
操作一:npm run build,编译相关的代码;
操作二:通过 live server 或者直接通过浏览器,打开 index.html 代码,查看效果;
这个过程经常操作会影响我们的开发效率,我们希望可以做到,当文件发生变化时,可以自动的完成 编译 和 展示;
为了完成自动编译,webpack 提供了几种可选的方式:
webpack watch mode
webpack-dev-server(常用)
webpack-dev-middleware
webpack-dev-server
上面的方式可以监听到文件的变化,但是事实上它本身是没有自动刷新浏览器的功能的:
当然,目前我们可以在 VSCode 中使用 live-server 来完成这样的功能;
但是,我们希望在不适用 live-server 的情况下,可以具备 live reloading(实时重新加载)的功能;
依赖包:
- webpack-dev-server
- 安装:
npm install webpack-dev-server -D
- 安装:
开启本地服务器:
1、设置 scripts 脚本
"serve": "webpack serve --config wk.config.js"
2、运行命令
npm run serve
注意:
- webpack-dev-server 在编译之后不会写入到任何输出文件,而是将 bundle 文件保留在内存中
- 事实上 webpack-dev-server 使用了一个库叫 memfs(memory-fs webpack 自己写的)
缺点:
- 局部修改后,整个页面都会被重新刷新一遍,浪费性能
- 可以通过 HMR 解决该问题
devServer 配置
修改配置文件,启动时加上 serve 参数:
static:
boolean | string | array
,(默认:'public'),允许配置从目录提供静态文件的选项。将其设置为false
以禁用proxy:
{'/api': {target, pathRewrite, changeOrigin}}
,设置代理来解决跨域访问的问题- target:
string
,实际储存资源的服务器地址 - pathRewrite:
{'^/api': ''}
,去除原始路径中的/api
- changeOrigin:
boolean
,代理时是否保留主机头的来源
- target:
historyApiFallback:
boolean | object
,解决 SPA 页面在路由跳转之后,进行页面刷新时,返回 404 的错误false
:(默认),true
:在刷新时如果返回 404 错误,会自动返回 index.html 的内容object
:{rewrites: [{from, to},...]}
,可以配置rewrites
属性。通过配置 from 来匹配路径,决定要跳转到哪一个页面
hot:
true | 'only'
,启用 webpack 的 模块热替换 特性true
:(默认),启用 webpack 的模块热替换特性'only'
:启用模块热替换功能,在构建失败时不刷新页面作为回退
host:``,设置主机地址
localhost
:(默认),本质上是一个域名,通常情况下会被解析成 127.0.0.1127.0.0.1
:回环地址(Loop Back Address),表达的意思其实是我们主机自己发出去的包,直接被自己接收正常的数据库包经常 应用层 - 传输层 - 网络层 - 数据链路层 - 物理层
而回环地址,是在网络层直接就被获取到了,是不会经常数据链路层和物理层的
比如我们监听 127.0.0.1 时,在同一个网段下的主机中,通过 ip 地址是不能访问的
0.0.0.0
:监听 IPV4 上所有的地址,再根据端口找到不同的应用程序比如我们监听 0.0.0.0 时,在同一个网段下的主机中,通过 ip 地址是可以访问的
如果希望其他地方也可以访问,可以设置为 0.0.0.0
port:``,(默认:8080),设置监听的端口
open:``,(默认:false),是否打开浏览器。也可以设置为类似于 Google Chrome 等值
false
:(默认),不打开true
:打开默认浏览器['/my-page', '/another-page']
:在浏览器中打开多个指定页面,也可以打开一个页面app{name: 'google-chrome'}
:打开指定的浏览器,chrome 在不同平台名称也不同,此处为 linux 平台名
compress:``,(默认:false),是否为打包文件开启 gzip compression 压缩
static
static:boolean | string | array
,(默认:'public'),允许配置从目录提供静态文件的选项。将其设置为 false
以禁用
devServer 中 static 对于我们直接访问打包后的资源其实并没有太大的作用,它的主要作用是如果我们打包后的资源,又依赖于其他的一些资源,那么就需要指定从哪里来查找这个内容:
比如在 index.html 中,我们需要依赖一个 abc.js 文件,这个文件我们存放在 public 文件 中;
在 index.html 中,我们应该如何去引入这个文件呢?
比如代码是这样的:
<script src="./public/abc.js"></script>
;但是这样打包后浏览器是无法通过相对路径去找到这个文件夹的;
所以代码是这样的:
<script src="/abc.js"></script>
;但是我们如何让它去查找到这个文件的存在呢? 设置 static 即可;
proxy
proxy:{'/api': {target, pathRewrite, changeOrigin}}
,设置代理来解决跨域访问的问题
- target:
string
,实际储存资源的服务器地址 - pathRewrite:
{'^/api': ''}
,去除原始路径中的/api
- changeOrigin:
boolean
,代理时是否保留主机头的来源
proxy 是我们开发中非常常用的一个配置选项,它的目的是设置代理来解决跨域访问的问题:
比如我们的一个 api 请求是
http://localhost:9000
,但是本地启动服务器的域名是http://localhost:8888
,这个时候发送网络请求就会出现跨域的问题;那么我们可以将请求先发送到一个代理服务器,代理服务器和 API 服务器没有跨域的问题,就可以解决我们的跨域问题了;
示例:
API 资源服务器: http://localhost:9000
本地请求服务器: http://localhost:8888
1、本地 webpack.config.js 文件配置
2、本地请求代码
3、API 服务器代码
4、请求结果
changeOrigin
这个 changeOrigin 官方说的非常模糊,通过查看源码我发现其实是要修改代理请求中的 headers 中的 host 属性:
因为我们真实的请求,其实是需要通过
http://localhost:9000
来请求的;但是因为使用了代理,默认情况下它的值时
http://localhost:8888
;如果我们需要修改,那么可以将 changeOrigin 设置为 true 即可;
changeOrigin 设为默认值false时 API 服务器的 ctx.headers:
修改 changeOrigin 为 true 后API 服务器的 ctx.headers:
内部源码:
historyApiFallback
historyApiFallback:boolean | object
,解决 SPA 页面在路由跳转之后,进行页面刷新时,返回 404 的错误
false
:(默认),进行页面刷新时,会返回 404 的错误true
:(推荐),在刷新时如果返回 404 错误,会自动返回 index.html 的内容object
:{rewrites: [{from, to},...]}
,可以配置rewrites
属性。通过配置 from 来匹配路径,决定要跳转到哪一个页面
historyApiFallback 是开发中一个非常常见的属性,它主要的作用是解决 SPA 页面在路由跳转之后,进行页面刷新时,返回 404 的错误。
boolean 值:默认是 false
- 如果设置为 true,那么在刷新时,返回 404 错误时,会自动返回 index.html 的内容;
object 类型的值,可以配置 rewrites 属性:
- 可以配置 from 来匹配路径,决定要跳转到哪一个页面;
事实上 devServer 中实现 historyApiFallback 功能是通过 connect-history-api-fallback 库的