前言
本文是对某开源的项目webpack5 + react + typescript
项目地址逐行代码做分析,解剖一个成熟的环境所有配置的意义,理清一些常见的问题,比如
-
文件中的
import
转es5
被webpack
编译成了什么?webpack的分包配置splitChunks咋用? 你真的理解其选项值chunks
的值async
或者initial
或者all
是什么意思吗,这个可对分包优化至关重要啊!为什么package.json
没有依赖的包,在node_modules
下面会出现,npm install
包是以什么结构安装npm包呢? -
babel/core
有什么用,它跟babel-loader
的区别,babelrc
文件中配置项presets
和plugin
的区别是什么,babelrc
常用设置项知道多少,这个不清楚?那项目代码校验和格式化用到editorConfig、prettier、eslint,stylelint
他们的关系和区别是什么?如何配置防止它们冲突,比如eslint
也有css
校验,怎么让stylelint
跟它不起冲突,这些你要晋升为前端主管怎么能心里没数? -
如果你用的
vscode
,如何在工作区配置ctrl+s
自动保存,让你的js和css文件自动格式化,并配置为prettier
格式化,webpack5
和4
的配置中的变化、等等。。。
以上提到的知识点对我们深入了解项目环境搭建
非常重要, 你的项目你来时一般环境都是搭建好的,试过从0自己搭建不?是不是抄别人的配置,都一头雾水,完全不知道这些配置项时啥意思呢?
现在!本篇本章专解这个问题!废话少说,
我们先从package.json
说起,里面的每一行代码是什么意思。
package.json
package.json
里面有很多有趣的内容,我们先从依赖包说起,解释这个项目中,下面的依赖包分别有什么用。
@babel/core有啥用?
babel
的功能在于「代码转译」
,具体一点,即将目标代码转译为能够符合期望语法规范的代码。在转译的
过程中,babel
内部经历了「解析 - 转换 - 生成」
三个步骤。而 @babel/core
这个库则负责「解析」
,具体的「转换」
和「生成」
步骤则交给各种插件(plugin)和预设(preset)来完成。
你可以从@babel/core
自己的依赖里看到其中有三个包,叫@babel/generator
(将ast生成代码)、 @babel/parser
(将源代码转换为AST)、@babel/traverse
(转换AST),有这三个包,就能转换你的代码,案例如下:
这应该非常清楚的了解babel/core
有什么用了吧,至于说怎么在traverse
阶段改变代码,就要用到其他的插件了,我们马上说一下babel-loader
,让你明白它跟babel/core
的区别
babel-loader
我们知道webpack
需要各种loader
,这些loader
的作用就是把文件做转化,比如babel-loader
是用来转化js
,jsx
,ts
,tsx
文件的。
比如我们写的js
代码是es6
,import xx模块 from ‘xx模块’
,为了浏览器兼容性,我们需要转化为es5
的写法,转译import
,那么这个时候就需要babel-loader
来帮忙了。
比如说一个简单的loader
怎么写呢,我们就知道babel-loader
大概是个什么东西了,
上面我们把任何加载到的文件内容转化为一个字符串,也就是loader
无非是加工读到的文件,所以babel-loader
就是读取对应的jsx?|tsx?
文件,然后加工后返回而已
prest家族:@babel/preset-env、@babel/preset-react、@babel/preset-typescript、
- @babel/preset-typescript: 主要是用来编译
ts
文件的。
目前 TypeScript
的编译有两种方式。一种是使用 TypeScript
自家的编译器 typescript
编译(简称 TS 编译器),一种就是使用 Babel + @babel/preset-typescript
编译。
其中最好的选择就是使用Babel + @babel/preset-typescript
,主要原因是:
Babel
能够指定需要编译的浏览器环境。这一点 TS 编译器是不支持的。在babelrc
文件里可以设置编译的target
属性(在preset-env
插件上设置)为比如
TS
编译器在编译过程中进行类型检查,类型检查是需要时间的,而babel
不做类型检查,编译速度就更快
@babel/preset-react: 主要是编译jsx
文件的,也就是解析jsx语
法的,比如说react
生成div,我们举一个例子,在jsx里面是这样的,转换成什么了呢?
转化后的react
的api
- @babel/preset-env:
@babel/preset-env
将基于你的实际浏览器及运行环境,自动的确定babel
插件及polyfill
,在不进行任何配置的情况下,@babel/preset-env
所包含的插件将支持所有最新的JS特性(ES2015
,ES2016
等,不包含 stage
阶段),将其转换成ES5
代码。例,那么只配置 @babel/preset-env
,转换时会抛出错误,需要另外安装相应的插件。
注意:@babel/preset-env
会根据你配置的目标环境,生成插件列表来编译。Babel
官方建议我们把 targets
的内容保存到 .browserslistrc
文件中 或者 package.json
里增加一个browserslit
节点,不然除了babel
外,其他的工具,例如browserslist
、post-css
等无法从 babel
配置文件里读取配置
如果你不是要兼容所有的浏览器和环境,推荐你指定目标环境,这样你的编译代码能够保持最小。
具体用法我们会在将babelrc
文件配置(babel
的配置文件)的时候详细说明。
@babel/plugin-transform-runtime、@babel/runtime-corejs
为什么我们需要它,我们来看看@babel/prest-env
编译完js文件后,会有哪些问题
-
比如我们使用字符串的inclues语法(es5中并不支持它,需要转译), 例如
Array.from
等静态方法,直接在global.Array
上添加;对于例如 includes 等实例方法,直接在global.Array.prototype
上添加。这样直接修改了全局变量的原型。 -
babel
转译 syntax 时,有时候会使用一些辅助的函数来帮忙转,比如:
class 语法中,babel 自定义了 _classCallCheck
这个函数来辅助;typeof
则是直接重写了一遍,自定义了 _typeof 这个函数来辅助
。这些函数叫做 helpers。每个项目文件都写无意是不合理的。
作用是将 helper
(辅助函数) 和 polyfill
(不修改全局变量原型的静态方法等) 都改为从一个统一的地方引入,并且引入的对象和全局变量是完全隔离的。
具体配置不详细说明了,到后面讲babelrc
文件的的时候说。
- @babel/runtime-corejs:
上面我们看到了@babel/prest-env
带来的问题,这两个问题@babel/plugin-transform-runtime可以解决,那@babel/runtime-corejs
又是个什么东西呢?
其中 @babel/plugin-transform-runtime
的作用是转译代码,转译后的代码中可能会引入 @babel/runtime-corejs
里面的模块,也就是说具体转译代码的函数是单独在另一个包里,就是@babel/runtime-corejs
里面
types家族:@types/react @types/react-dom @types/webpack-env
-
@types/react、@types/react-dom
这两个是react的typescript类型定义 -
@types/webpack-env
是webpack的typescript类型定义
eslint家族:eslint、eslint-config-airbnb、eslint-config-prettier...
-
eslint:是一个插件化并且可配置的
JavaScript
语法规则和代码风格的检查工具。这个就不多说了,大家都知道吧,不用eslint
的前端项目应该很少。 -
eslint-config-airbnb:
Airbnb
的eslint规则的标准,它依赖eslint, eslint-plugin-import, eslint-plugin-react, and eslint-plugin-jsx-a11y
等插件,并且对各个插件的版本有所要求。 -
eslint-config-prettier:
prettier
是一个代码格式化工具,比如说规范项目都使用单引号,还是双引号。而且,Prettier
还给予了一部分配置项,可以通过.prettierrc
文件修改。 -
所以相当于
Prettier
接管代码格式的问题,而使用Prettier + ESLint
就完完全全解决了代码格式和代码语法规则校验的问题。
但实际上使用起来配置有些小麻烦,但也不是什么大问题。因为 Prettier
和 ESLint
一起使用的时候会有冲突,我们需要使用 eslint-config-prettier
来关掉 (disable) 所有和 Prettier
冲突的 ESLint 的配置,
eslint-plugin-prettier
将 prettier 的 rules 以插件的形式加入到 ESLint 里面方法就是在 .eslintrc 里面将 prettier 设为最后一个 extends
将上面两个步骤和在一起就是下面的配置,也是官方的推荐配置
- eslint-plugin-import:用于校验
es6
的import
规则,如果增加import plugin
,在我们使用webpack
的时候,如果你配置了resolve.config.js
的alias
,那么我们希望import plugin的校验规则会从这里取模块的路径,此时需要配置,注意,此时同时要下载eslint-import-resolver-webpack
插件才能像下面一样设置
eslint-import-resolver-typescript:它也是「eslint-import-resolver-」
家族的一员,它的作用是
import/require
扩展名为 .ts/.tsx 的文件- 使用
tsconfig.json
中定义的paths路径 - 优先解析
@types/*
定义而不是普通的 .js
eslint-plugin-jsx-a11y: 该插件为你的 JSX
中的无障碍问题提供了 AST
的语法检测反馈。
eslint-plugin-react: 一些 react
的 eslint
的 rules
规范
eslint-plugin-react-hooks:检测react hooks的一些语法规范,并提供相应的rules
postcss家族:postcss、postcss-flexbugs-fixes、postcss-loaderpostcss-preset-env,autoprefixer
postcss: 是一个使用JavaScript插件来转换CSS
的工具。
PostCSS
本身很小,其只包含CSS
解析器,操作CSS
节点树的API
,source map
,以及一个节点树字符串化工具,其它功能都是通过插件来实现的,比如说插件有
1、添加浏览器内核前缀的
2、有检测css
代码的工具等等
postcss-flexbugs-fixes: 修复在一些浏览器上flex布局的bug,比如说
- 在ie10和标准的区别
----|标准| flex: 1 flex: 1 1 0% flex: 1 0 0px flex: auto flex: 1 1 auto flex: 1 0 auto
缩写声明 | 标准转义 | IE10转义 | ||||||
---|---|---|---|---|---|---|---|---|
(no flex declaration) | flex: 0 1 auto | flex: 0 0 auto | flex: 1 | flex: 1 1 0% | flex: 1 0 0px | flex: auto | flex: 1 1 auto | flex: 1 0 auto |
postcss-loader:loader
的功能在上面已经说明,这个loader
是postcss
用来改变css
代码的loader
postcss-preset-env:这个插件主要是集成了(有了它不用下载autoprefixer插件)
autoprefixer:用于解析 CSS
并使用 Can I Use
中的值向 CSS
规则添加供应商前缀
style-resoures-loader:这个插件比较重要,即使这个项目没有用,我也建议大家项目用上。它的作用就是避免重复在每个样式文件中@import
导入,在各个css
文件中能够直接使用变量和公共的样式。
webpack家族:webpack、webpack-bundle-analyzer、webpack-cli、webpack-dev-server、webpack-merge、webpackbar
webpack:这个不用描述了吧。。。
webpack-cli:
- 是使用
webpack
的命令行工具,在4.x
版本之后不再作为webpack
的依赖了,我们使用时需要单独安装这个工具。
webpack-bundle-analyzer: webpack
打包体积分析工具,会让我们知道打包后的文件分别是由哪些文件组成,并且体积是多少,是一款优化分析打包文件的工具
webpack-dev-server:是一个小型的Node.js Express
服务器,它使用webpack-dev-middleware
来服务于webpack的包,除此自外,它还有一个通过Sock.js
来连接到服务器的微型运行时
webpack-merge:
- 一般情况,我们会把webpack文件分为,
webpack.common.js
(后面两个js文件共同的内容抽离出来),webpack.pro.js
(生产环境独有的内容),webpack.dev.js
(开发环境独有的内容)。 - 此时,我们需要一个方法来合并
webpack.common.js
和webpack.pro.js
变为生产环境的内容,同理common
和dev
也是如此。我们就需要webpack-merge
方法了。它的作用如下
从上面的案例,我们可以看出来, 数组内容会合并,基础类型的值会被覆盖,这比较符合我们webpack.common.js
有一些plugins:[],webpack.pro.js
也有一些plugins是合并的需要,而不是覆盖。
stylelint 家族: stylelint、、stylelint-config-rational-order...
stylelint:stylelint
用于样式规范检查与修复,支持 .css .scss .less .sss
stylelint-config-prettier:关闭所有不必要的或可能与 Prettier
冲突的规则。
stylelint-config-rational-order:它对你的css
样式排序会有要求,具体为
你不按上面的顺序写css
的话,会警告或者报错。
stylelint-order:这个实现的功能也是排序,不过它跟上面的插件的区别是,它按照字母(英文是alpha sort
)排序,所以两个插件要配合使用。
stylelint-config-standard:该风格是 Stylelint
的维护者汲取了 GitHub、Google、Airbnb
多家之长生成的一套css风格规则。
stylelint-declaration-block-no-ignored-properties:这个插件的作用是警告那些不起作用的属性。比如说你设置了display:inline,width: 200px
,其实这里的width
是不起作用的,此时这个插件就会发出警告
chalk
打印有颜色文字的插件:用法比如说
clean-webpack-plugin
webpack使用的插件,一般用在production环境,用来清除文件夹用的,就是类似rm -rf ./dist
conventional-changelog-cli、@commitlint/cli、@commitlint/config-conventional
commitlint
可以帮助我们进行 git commit
时的 message 格式是否符合规范,conventional-changelog
可以帮助我们快速生成 changelog
@commitlint/config-conventional
类似 eslint 配置文件中的 extends ,它是官方推荐的 angular
风格的 commitlint 配置
copy-webpack-plugin
在webpack中拷贝文件和文件夹
cross-env
它是运行跨平台设置和使用环境变量(Node中的环境变量)的脚本。因为在windows和linux|mac里设置环境变量的方法不一致,比如说
mini-css-extract-plugin、css-minimizer-webpack-plugin
webpack 4.0
以后,官方推荐使用mini-css-extract-plugin
插件来打包css文件(从css文件中提取css代码到单独的文件中,对css
代码进行代码压缩等)
相对的,如果你不想提取css
,可以使用style-loader
,将css
内嵌到html
文件里。
使用方法和效果如下:(后面会在webpack配置文件分析里看到),
先举一个基础配置的例子。 webpack.config.js
:
- 实战案例
基于以上配置
- 如果入口
app.js
中引用了Root,js
Root
引入了Topics.js
- 而
Root.js
中引用样式main.css
Topics.js
中引用了topics.css
。
这种情况下,Topics
会和 Root
同属一个 chunk
,所以会一起都打包到 app.js
中, 结果就是 main.less 和 topics.less 会被提取到一个文件中:app.css
。而不是生成两个 css
文件。
- 代码情景二
但是,如果 Root.js
中并没有直接引入 Topics
组件,而是配置了代码分割 ,比如模块的动态引入(也就是说你的topics模块,是impot()动态引入的),那么结果就不一样了:
因为这个时候有两个 chunk,对应了两个 JS 文件,所以会提取这两个 JS 文件中的 CSS 生成对应的文件。这才是“为每个包含 CSS 的 JS 文件创建一个单独的 CSS 文件”
的真正含义。
- 情景三
但是,如果分割了 chunk
,还是只希望只生成一个 CSS
文件怎么办呢?也是可以做到的。但需要借助 Webpack 的配置 optimization.splitChunks.cacheGroups
。
先来看看配置怎么写的:
打包结果:
继续加强上面的配置,压缩上面分理处的代码,
css-minimizer-webpack-plugin
是用来压缩分离出来的css的。使用方法如下:
detect-port-alt
这个包用来检测对应端口是否被占用,比如项目里发现启动3000端口被占用的话就+1,直到选择一个不被占用的端口(端口上限是65535)。
error-overlay-webpack-plugin
它提供了和 create-react-app 一样的错误遮罩:
用法如下:
@typescript-eslint/eslint-plugin、@typescript-eslint/parser
@typescript-eslint/parser:ESLint的解析器,用于解析typescript,从而检查和规范Typescript代码
@typescript-eslint/eslint-plugin:这是一个ESLint插件,包含了各类定义好的检测Typescript代码的规范
配置如下所示:
fork-ts-checker-webpack-plugin
它在一个单独的进程上运行类型检查器,该插件在编译之间重用抽象语法树,并与TSLint共享这些树。可以通过多进程模式进行扩展,以利用最大的CPU能力。
html-webpack-plugin
这个插件非常常用,几乎是必备的。
它的作用是:当使用 webpack
打包时,创建一个 html
文件,并把 webpack
打包后的静态文件自动插入到这个 html 文件当中。简单实用如下(讲webpack文件时会更详细介绍api):
husky、lint-staged
husky是一个npm包,安装后,可以很方便的在package.json配置git hook 脚本 。
比如,在 package.json 内配置如
那么,在后续的每一次git commit
之前,都会执行一次对应的 hook 脚本npm run lint
。其他hook同理.
- lint-staged
如果我们 想对git 缓存区最新改动过的文件进行以上的格式化和 lint
规则校验,这就需要 lint-staged
了 。
如下:
这里没有添加 --fix
来自动修复不符合规则的代码,因为自动修复的内容对我们不透明,这样不太好。
terser-webpack-plugin
是一个使用 terser
压缩js
的webpack
插件。
如果你使用的是 webpack v5
或以上版本,你不需要安装这个插件。webpack v5
自带最新的 terser-webpack-plugin
。如果使用 webpack v4
,则必须安装 terser-webpack-plugin v4
的版本。
简易用法如下,详细介绍留到后面webpack配置文件详解
package.json里的其它比较重要的字段
这里的main
需要跟一些其它字段来一起比较。比如browser,module,main
三个字段都可以出现在package.json
中,它们有什么区别呢?
我们直接说结论,具体详细分析,详情参考这篇文章开发插件package.json在webpack构建中的表现
结论
- webpack 选择 web 浏览器环境
- 插件的 package.json 是否配置了 browser 字段
- 存在:选择 browser 作为入口
- 不存在:
- 插件的 package.json 是否配置了 module 字段
- 存在:选择 module 作为入口
- 不存在:以 main 作为入口
- webapack 选择 node环境
- 插件的 package.json 是否配置了 module 字段
- 存在:选择 module 作为入口
- 不存在:以 main 作为入口
- 插件的 package.json 是否配置了 module 字段
根据上面的行为总结,我们在开发插件的时候,需要考虑插件是提供给web
环境还是node
环境,如果都涉及到且存在区别,就要明确指出 browser、module
字段。如果没有任何区别的话,使用 main
入口足以
.vscode中settings文件
这个文件对于使用vscode的用户比较重要,有一些设置非常棒,比如点击ctrl+s自动格式化你的文件,设置如下:
下图是具体的settings文件,逐一注释其中的作用
上面的保存时自动格式化eslint的规则和stylint的规则,需要注意的是,有些规则是必须手动修改的,不会自动保存格式化。
babelrc文件解析
下面的presets和plugins的区别是,
-
presets是一些预设,插件的对应名字是
babel-preset-xxx
。Babel
插件一般尽可能拆成小的力度,开发者可以按需引进。但是一个一个引进有时候很麻烦,能不能把一些常用的插件打成一个包给我们用呢,这就是presets
的作用和。 -
plugins
就是一个一个的插件集合,你要配特定的功能就可以加入到plugins中
以下的所有插件之前都介绍过,可以试着回忆一下哦
这里详细解释一下@babel/preset-env
这个插件的详细的常见使用参数,因为它很重要,是babel
转义我们代码的关键插件:
- targets属性,最常见的是
- targets.node : 它可以指定编译当前node版本,或者 "node": true 或者 "node": "current", 它与 "node": process.versions.node 相同。
- targets.browsers:可以利用 browserslist 查询选择的浏览器 (例如: last 2 versions, > 5%)
但是这里不建议把browsers
信息写在eslinttc
里面,因为可能其他的插件也需要浏览器信息,最好写在package.json
中。
例如:
-
modules属性,如果是false,就是说导出方式是按es6 module,默认是commonjs规范
-
useBuiltIns:规定如何引入polyfill,比如说有些浏览器不支持promise,我们需要引入polyfill去兼容这些不支持promise的浏览器环境
- 值为usage 会根据配置的浏览器兼容,以及你代码中用到的 API 来进行 polyfill,实现了按需添加,并且使用了
useBuiltIns: 'usage'之后,就不必手动在入口文件中
import '@babel/polyfill'` - 值为
entry
配置项时, 根据target
中浏览器版本的支持,将polyfills
拆分引入,仅引入有浏览器不支持的polyfill
- corejs选项, 这个选项只会在与
useBuiltIns: usage
或者useBuiltIns: entry
一起使用时才会生效, 确保@babel/preset-env
为你的core-js
版本注入了正确的引入
- 值为usage 会根据配置的浏览器兼容,以及你代码中用到的 API 来进行 polyfill,实现了按需添加,并且使用了
接着,我们解释一下在'@babel/plugin-transform-runtime'插件的配置:
- corejs: 比如
['@babel/plugin-transform-runtime', { corejs: 2 }]
,指定一个数字将引入corejs
来重写需要polyfillAPI
的helpers
,这需要使用@babel/runtime-corejs2
作为依赖
技术细节:transform-runtime
转换器插件会做三件事:
- 当你使用
generators/async
函数时,自动引入@babel/runtime/regenerator
(可通过regenerator选项切换) - 若是需要,将使用
core-js
作为helpers
,而不是假定用户已经使用了polyfill
(可通过corejs选项切换) - 自动移除内联的 Babel helpers并取而代之使用
@babel/runtime/helpers
模块(可通过helpers选项切换)
最后,我们要提一个问题,就是import 通过webpack转义之后,变成了什么样子,我们用案例来说。
如下是一个非常简单的webpack编译的模块。
会被webpack编译为以路径为key,以函数为value的对象。
我们接着看一下__webpack_require__.r,webpack_require.d,__webpack_require__分别是什么:
好了,我们接着回头接续分析那个src/sum.js的模块
经过上面分析,发现最关键的一句就是__webpack_require__.r(webpack_exports),把导出对象标记为esModule,如果你没有用import,而是用commonjs的require,那么就不会有这一句
那问题又来了,如果是表示esmodule了,有啥用啊!这部分我就不写了,要不就是一篇专门讲import和require区别的文章了。
我直接说结论了
- 如果是import Header form './Header',在webpack里会转译为类似
- 如果是import * as Header form './Header',在webpack里会转译为类似
- 如果是import { A } form './Header',在webpack里会转译为类似
export default Header
会被挂在exports的default属性上export const A=1
,会被挂在exports的A属性上
意思是es6模块实际上被webpack
的一套规则还是变味了commonjs
规范而已。
上面没看懂?没关系的,更具体更清晰的推论,在tsconfig.js
文件的esModuleInterop参数讲解中会有更清晰的解释(这个是站在webpack编译的角度,下面esModuleInterop参数是在ts编译的角度,其实原理都是一样的)。
以下比较简单的文件,我就在文件注释中解释参数了
.commitlintrc文件解析
如下的rules的规范如下:rule
由name
和配置数组组成,如:'name:[0, 'always', 72]'
,数组中第一位为level
,可选0,1,2
,0
为disable
,1
为warning
,2
为error
,第二位为应用与否,可选always|never
,第三位该rule的值,下面的值代表你的commit开头必须是这些字段
.editorconfig文件分析
.eslintrc文件分析
上面提到一个重要的点,就是eslint-import-resolver-node
,我们并没有在package.json
声明,咋node_modules里面就有它了呢?入下
这就涉及到npm安装依赖包的规则了,因为eslint-import-plugin
依赖eslint-import-resolver-node
,所以,node_modules
里面就会有,我们就简单讲一下npm
包安装(install)规则。
这问题曾经我面试的时候也遇到过,接下来我们简单了解一下:
嵌套结构
我们都知道,执行 npm install
后,依赖包被安装到了 node_modules
,在 npm
的早期版本, npm 处理依赖的方式简单粗暴,以递归的形式,严格按照 package.json
结构以及子依赖包的 package.json
结构将依赖安装到他们各自的 node_modules
中。直到有子依赖包不在依赖其他模块。也就是说,假如你的package.json如下
然后B模块有依赖C模块,B模块的package.json如下
那么整个项目依赖就是嵌套的,如下:
在 Windows 系统中,文件路径最大长度为260个字符,嵌套层级过深可能导致不可预知的问题。
扁平结构
为了解决以上问题,NPM 在 3.x 版本做了一次较大更新。其将早期的嵌套结构改为扁平结构:
安装模块时,不管其是直接依赖还是子依赖的依赖,优先将其安装在 node_modules
根目录。
还是上面的依赖结构,我们在执行 npm install
后将得到下面的目录结构:
当安装到相同模块时,判断已安装的模块版本是否符合新模块的版本范围,如果符合则跳过,不符合则在当前模块的 node_modules 下安装该模块。
就是说假如C模块依赖A模块的2.0.0版本,依赖图如下:
其实铺平的结构也会有问题,我们这里就不详述了,上面提到的那篇文章真的不错,推荐详细看,里面设置npm相关知识的点比这里谈到的多得多。
.npmrc文件分析
.prettierrc文件分析
.stylelintrc文件分析
tsconfig.json文件分析
为什么使用 tsconfig.json?
通常我们可以使用 tsc 命令来编译少量 TypeScript 文件, 但如果实际开发的项目,很少是只有单个文件,当我们需要编译整个项目时,就可以使用 tsconfig.json 文件,将需要使用到的配置都写进 tsconfig.json 文件
上面的jsx选项可以有三个值选择,我们详细解释一下:
jsx可选项包括:preserve, react 和 react-native。
这些模式仅影响编译阶段 - 类型检查不受影响。
preserve
模式将保持JSX
作为输出的一部分,又后面的编译器继续编译(例如Babel)。 此外,输出将具有.jsx文件扩展名。- react模式将编译
React.createElement
,在使用之前不需要经过JSX
转换,输出将具有.js文件扩展名。 - react-native模式相当于保留,因为它保留了所有
JSX
,但输出将具有.js文件扩展名
isolatedModules,这个选项有点复杂,查阅了不少资料。。。下面详细讲一下:
- 导出非值标识符
在 TypeScript 中,你可以引入一个类型,然后再将其导出:
由于 someType
并没有值,所以生成的 export
将不会导出它(否则将导致 JavaScript 运行时的错误):
单文件转译器并不知道 someType
是否会产生一个值,所以导出一个只指向类型的名称会是一个错误。
- 非模块文件
如果设置了 isolatedModules
,则所有的实现文件必须是模块 (也就是它有某种形式的 import
/export
)。如果任意文件不是模块就会发生错误:
此限制不适用于 .d.ts
文件
- 指向
const enum
成员
在 TypeScript 中,当你引用一个 const enum
的成员时,该引用在生成的 JavaScript 中将会被其实际值所代替。这会将这样的 TypeScript 代码:
转换为这样的 JavaScript:
在不知道这些成员值的情况下,其他转译器不能替换对 Numbers
的引用。如果无视的话则会导致运行时错误(运行时没有 Numbers
) 对象。 正因如此,当启用 isolatedModules
时,引用环境中的 const enum
成员将会是一个错误
- moduleResolution (参考《tsconfig详细配资》,详见文章底部)
可选值: classic | node
我们举一个例子,看看两种模式的工作机制,假设用户主目录下有一个ts-test的项目,里面有一个src目录,src目录下有一个a.ts文件,即/Users/**/ts-test/src/a.ts
- classic模块解析规则:
-
对于相对路径模块: 只会在当前相对路径下查找是否存在该文件(.ts文件),不会作进一步的解析,如"./src/a.ts"文件中,有一行
import { b } from "./b"
,那么其只会检测是否存在"./src/b.ts"
,没有就算找不到。 -
对于非相对路径模块: 编译器则会从包含导入文件的目录开始依次向上级目录遍历,尝试定位匹配的ts文件或者d.ts类型声明文件,如果
/Users/**/ts-test/src/a.ts
文件中有一行import { b } from "b"
,那么其查找过程如下:
-
- node模块解析规则:
- 对于相对路径模块:除了会在当前相对路径下查找是否存在该文件(.ts文件)外,还会作进一步的解析,如果在相对目录下没有找到对应的.ts文件,那么就会看一下是否存在同名的目录
- 如果有,那么再看一下里面是否有package.json文件,然后看里面有没有配置,main属性
- 如果配置了,则加载main所指向的文件(.ts或者.d.ts),如果没有配置main属性,那么就会看一下目录里有没有index.ts或者index.d.ts,有则加载。
- 对于非相对路径模块: 对于非相对路径模块,那么会直接到a.ts所在目录下的node_modules目录下去查找,也是遵循逐层遍历的规则,查找规则同上,同上node模块解析规则查找如下(一般情况下是找):
以上需要注意一点的是,还有一个typeRoots属性,默认是node_modules/@types,并且不管是classic解析还是node解析,都会到node_modules/@types目录下查找类型声明文件,即typeRoots和模块的解析规则无关
- baseUrl
这个是用于拓宽引入非相对模块时的查找路径的。其默认值就是"./" ,比如当moduleResolution
属性值为node的时候,如果我们引入了一个非相对模块,那么编译器只会到node_modules目录下去查找,但是如果配置了baseUrl
,那么编译器在node_modules
中没有找到的情况下,还会到baseUrl中指定的目录下查找;
同样moduleResolution
属性值为classic
的时候也是一样,除了到当前目录下找之外(逐层),如果没有找到还会到baseUrl
中指定的目录下查找;就是相当于拓宽了非相对模块的查找路径范围
- allowSyntheticDefaultImports
当设置为 true, 并且模块没有显式指定默认导出时,allowSyntheticDefaultImports
可以让你这样写导入:
而不是:
例如:allowSyntheticDefaultImports
不为 true 时:
这段代码会引发一个错误,因为没有“default”对象可以导入,即使你认为应该有。 为了使用方便,Babel 这样的转译器会在没有默认导出时自动为其创建,使模块看起来更像:
本选项不会影响 TypeScript 生成的 JavaScript,它仅对类型检查起作用。
- esModuleInterop
这个参数涉及到es6模块和commonjs模块互相转换知识点了。具体参考这篇文章(这一参数就是一篇文章 esModuleInterop 到底做了什么?, 我这里简引用一下这篇文章的关键点。
首先我们看一下import
语法在ts中是如何被转译的!
- TS 默认编译规则
TS 对于 import 变量的转译规则为:
结论,可以看到:
- 对于
import
导入默认导出的模块,TS
在读这个模块的时候会去读取上面的default
属性 - 对于
import
导入非默认导出的变量,TS
会去读这个模块上面对应的属性 - 对于
import *,TS
会直接读该模块
TS、babel
对 export` 变量的转译规则为:(代码经过简化)
可以看到:
- 对于
export default
的变量,TS
会将其放在module.exports
的 default 属性上 - 对于
export
的变量,TS
会将其放在module.exports
对应变量名的属性上 - 额外给
module.exports
增加一个__esModule: true 的属性,用来告诉编译器,这本来是一个 esm 模块
TS 开启 esModuleInterop 后的编译规则
回到标题上,esModuleInterop
这个属性默认为 false。改成 true 之后,TS 对于 import 的转译规则会发生一些变化(export 的规则不会变):
可以看到,对于默认导入和 namespace(*)导入,TS 使用了两个 helper 函数来帮忙
其实这个参数对于我们项目而言没有用,因为@babel/preset-typescript
会把类型清除掉,webpack
不会调用 tsc
,tsconfig.json
也会被忽略掉。
但是可以帮助我们拓宽视野,这样面试官让你聊es6
模块和commonjs
模块转换的话题(cjs 导入 esm (一般不会这样使用,除开这种情况),就会游刃有余
webpack相关配置
首先是工具文件:
env.js
path.js
webpack.common.js
这是webpack
生产环境和开发环境共同的配置文件
以下需要特别注意的参数是'css-loader'里有个importLoaders的参数,它的意思是需要举一个例子就明白了,
如下图:importLoader是1
-
我们在写
sass
或者less
的时候可以@import
去引入其他的sass
或less
文件,此时引用的文件如何被loader处理就跟这个参数有关了。 -
当
css-loader
处理index.scss
文件,读取到@import
语句的时候, 因为将importLoaders
设置为1
,那么a.scss
和b.scss
会被postcss-loader
给处理 -
如果将
importLoaders
设置为2
,那么a.scss
和b.scss
就会被postcss-loader
和sass-loader
给处理
下面的externals
属性是一个常见webpack
优化点,比如你会把react,react-dom
放入cdn
,这样就不用打包他们
这里还有一些webpack5
和webpack4
相同功能但配置有些区别的点:
- 之前使用
file-loader
,但是webpack5
现在已默认内置资源模块,根据官方配置,现在可以改为以下配置方式,不再需要安装额外插件:
缓存
这里提一个醒dll
在webpack
里已经过时了!过时了!以后谁给你推荐这个webpack优化就别理他就行了!因为配置hard-source-webpack-plugin都比配置dll容易的多,这还是webpack4的配置。都过时了
之前可以使用插件 hard-source-webpack-plugin
实现缓存,大大加快二次编译速度,现在webpack5
现在默认支持缓存,我们只需要以下配置即可:
cache.buildDependencies
,它可以指定构建过程中的代码依赖。它的值类型有两种:文件和目录。
- 目录类型必须以斜杠(/)结尾。其他所有内容都解析为文件类型。
- 对于目录类型来说,会解析其最近的 package.json 中的 dependencies。
- 对于文件类型来说,我们将查看 node.js 模块缓存以寻找其依赖。
如下示例的意思是:
__filename
变量指向 node.js
中的当前文件。
注意:当设置 cache.type: "filesystem"
时,webpack
会在内部以分层方式启用文件系统缓存和内存缓存。 从缓存读取时,会先查看内存缓存,如果内存缓存未找到,则降级到文件系统缓存。 写入缓存将同时写入内存缓存和文件系统缓存。也就是说它比memory
模式更好
-
HtmlWebpackPlugin
- title
生成html文件的标题
- filename
就是html文件的文件名,默认是index.html
- template
指定你生成的文件所依赖哪一个html文件模板,模板类型可以是html、ejs
如果你设置的
title
和filename
于模板中发生了冲突,那么以你的title
和filename
的配置值为准。-
inject**
inject有四个值:
true
body
head
false
true
默认值,script标签位于html文件的 body 底部body
script标签位于html文件的 body 底部head
script标签位于html文件的 head中false
不插入生成的js文件,这个几乎不会用到的
-
favicon
给你生成的html文件生成一个
favicon
,值是一个路径然后再生成的html中就有了一个
link
标签- minify
使用minify会对生成的html文件进行压缩。注意,不能直接这样写:
minify: true
, 使用时候必须给定一个{ }
对象 )- chunks
chunks主要用于多入口文件,当你有多个入口文件,那就回编译后生成多个打包后的文件,那么
chunks
就能选择你要使用那些js文件那么编译后:
-
如果你没有设置chunks选项,那么默认是全部显示
-
chunksSortMode
script的顺序,默认四个选项: none
auto
dependency
{function}
'dependency' 不用说,按照不同文件的依赖关系来排序。
这里重点讲解一下function
的用法
如何配置出我们想要的顺序
以上配置的顺序就是['common', 'public', 'index'],为什么呢,因为chunksSortMode这个函数就是数组的sort方法里的自定义函数,这里说白了就是数组[0, 1, 2]按升序排列。
接下来还有webpack.dev.js和webpack.prod.js两个文件(有点写不下去了,这篇文章查了n多资料,搞得现在脑袋有点昏啊)
我就快速写重点内容了,不贴代码了
-
webpack.dev.js里面的重点是devServer属性的配置
-
devServer配置详解:
- webpack.prod.js的重点是配置TerserPlugin,和optimization配置其中splitChunks是重点中的重点)
-
TerserPlugin
- test
默认值:
/.m?js(?.*)?$/i
, 用来匹配需要压缩的文件。- include
默认值:
undefined
, 匹配参与压缩的文件- exclude
默认值:
undefined
, 匹配参与压缩的文件parallel
类型:
Boolean|Number
默认值:true
这个参数很重要,启用多进程构建,可以大大提高打包速度,强烈建议开启
-
mode
mode有三个参数production
,development
,none
,前两个参数会默认安装一堆插件,用来区分是开发环境还是生产环境。而none
的话,webpack就是最初的样子,无任何预设,需要从无到有开始配置。
所以我们了解是哪些插件,有啥用是理解webpack进化到现在的比较重要的知识点。
development模式下,webpack做了那些打包工作
在此mode下,就做了以下插件的事(还有其它配置,重点介绍下面的),其他都没做,所以这些插件可以省略,webpack默认就给你加上了,而且会将 DefinePlugin
中 process.env.NODE_ENV
的值设置为 development
我们看看moduleIds
和chunkIds
这两个配置都做了啥,简而言之,就是帮助缓存生效的插件。
我们知道webpack最开始的版本并不会给模块加上名字,模块名都是数字,0,1,2,3
,但是对于我们人来说数字不好认,要是名字多好,便于开发的时候查找。
而且,你想想,如果我们在0
和1
模块之间,再加一个模块,那么顺序就是0
、新模块(现在是1
)、老的1模块(现在是2
),老的2
模块(现在是3
),这时候新模块就是1
,其它老模块数字依次+1
,这个时候缓存就失效了,虽然老的模块代码没变,但是这种缓存下标的方式,让缓存很容易失效,这就是为啥加上这个配置的原因
有了moduleIds
,模块都拥有了姓名,而且都是独一无二的key,不管新增减多少模块,模块的key都是固定的。
除了moduleIds
,还有一个chunkIds
,这个是给配置的每个chunks命名,原本的chunks也是数组,没有姓名。
production
在正式版本中,所省略的插件们,如下所示,我们会一个个分析。
terser-webpack-plugin
用于js代码压缩。在以前版本中,我们需要引入npm包terser-webpack-plugin
来进行压缩,现在我们可以在optimize
中进行配置达到同样的效果
配置之前已讲
ModuleConcatenationPlugin
这个是用来帮助作用域提升的,我们之前看了webpack打包出来的是类似
这样每个模块都在自己的function里面,都有自己的作用域,我们知道作用域链访问是有性能代价的,如果大家都提到一个作用域,对性能提升是有帮助的,这个插件就做这样的事。
NoEmitOnErrorsPlugin
这个就是用于防止程序报错,就算有错误也给我继续编译。
others
还有一些默认的插件配置,也就是可以不在plugins中引用的配置:
SideEffectsFlagPlugin
webpack.optimization.sideEffects
用于实现treeshaking
形式的死码删除。而为了实现treeshaking
,需要满足几个条件:
- 导入的模块已经标记了sideEffect,即package.json中的sideEffects这个属性为false。
- 当前模块引用了无副作用的模块,且没有被使用
这样,经过SideEffectsFlagPlugin
处理后,没有副作用且没有被使用的模块都会被打上sideEffectFree
标记。 在ModuleConcatenationPlugin
中,带着sideEffectFree
标记的模块将不会被打包。
FlagIncludedChunksPlugin
即配置optimization.flagIncludedChunks
。该配置项会使webpack确认,若当前标记的chunk
a是另外一个chunk
A的子集并且已经A加载完成,则a将不会再次加载(包含关系)。
FlagDependencyUsagePlugin
标记没有用到的依赖。
splitChunks
最后1个知识点来了哦!
这个配置对象中,其它都好说,最令人困惑的是chunks属性,我们来看看是个什么东西。
chunks
选项,决定要提取那些模块。-
默认是
async
:只提取异步加载的模块出来打包到一个文件中。- 异步加载的模块:通过
import('xxx')
或require(['xxx'],() =>{})
加载的模块。
- 异步加载的模块:通过
-
initial
:提取同步加载和异步加载模块,如果xxx在项目中异步加载了,也同步加载了,那么xxx这个模块会被提取两次,分别打包到不同的文件中。- 同步加载的模块:通过
import xxx
或require('xxx')
加载的模块。
- 同步加载的模块:通过
-
all
:不管异步加载还是同步加载的模块都提取出来,打包到一个文件中。
-
兄弟们,但是我遇到了问题,就是上面说的这些根本不管用,下面的案例摘自stockOverFolw
的高票回答,但是我用webpack5
同样的配置,根本得不到跟这个回答一致的答案,百思不得其解,后面我改进了一下,就可以了,后面再介绍,大家先看案例
app.js 如下,有一个静态模块导入叫my-static-module
,还有一个动态模块导入叫my-dynamic-module
``
来看看chunks参数不一样,得到的结果会是多么不一样(配置如下)
- async (default)
会生成以下两个文件
bundle.js
(包括 app.js + my-static-module)chunk.js
(仅仅包括 my-dynamic-module)
- initial
会生成以下两个文件
app.js
(仅仅包括 app.js)bundle.js
(仅仅包括 my-static-module)chunk.js
(仅仅包括 my-dynamic-module)
- all
会生成以下两个文件
app.js
(仅仅包括 app.js only)bundle.js
(仅仅包括 my-static-module + my-dynamic-module)
可以看出,all
是比较极限的压缩
我无论怎么尝试,得出来的结果都是默认的async
导出的结果,可能是我配错了吧,希望有熟悉这项配置的大哥评论区留个言。
我后来是怎么改,就可以符合上面的答案了呢,我把chunks配置在cacheGroups
参数里,如下:
这里顺便介绍一下minChunks是什么意思,意思是至少引用多少次才分离公共代码,我这里是1次,只要引用过模块都分离出去。
minSize
是规定被提取的模块在压缩前的大小最小值,单位为字节,默认为30000,只有超过了30000字节才会被提取,我们这里设置为0,是为了自己做实验,保证能被分离就分离出去。
接下来,介绍一下其他参数:
maxSize
选项:把提取出来的模块打包生成的文件大小不能超过maxSize值,如果超过了,要对其进行分割并打包生成新的文件。单位为字节,默认为0,表示不限制大小。maxAsyncRequests
选项:最大的按需(异步)加载次数,默认为 6。maxInitialRequests
选项:打包后的入口文件加载时,还能同时加载js文件的数量(包括入口文件),默认为4。- 先说一下优先级
maxInitialRequests
/maxAsyncRequests
<maxSize
<minSize
。 automaticNameDelimiter
选项:打包生成的js文件名的分割符,默认为~
。name
选项:打包生成js文件的名称。cacheGroups
选项,核心重点,配置提取模块的方案。里面每一项代表一个提取模块的方案。下面是cacheGroups
每项中特有的选项,其余选项和外面一致,若cacheGroups
每项中有,就按配置的,没有就使用外面配置的。-
test
选项:用来匹配要提取的模块的资源路径或名称。值是正则或函数。priority
选项:方案的优先级,值越大表示提取模块时优先采用此方案。默认值为0。reuseExistingChunk
选项:true
/false
。为true
时,如果当前要提取的模块,在已经在打包生成的js文件中存在,则将重用该模块,而不是把当前要提取的模块打包生成新的js文件。enforce
选项:true
/false
。为true
时,忽略minSize
,minChunks
,maxAsyncRequests
和maxInitialRequests
外面选项
能看到最后一定很不容易,欢迎点赞,后面会接着出文章,目前3篇正在写,也是自己最近学习完的知识
- form表单低代码平台之渲染器实现(渲染器就是schema => 表单)
- jest单元测试教程
- leetcode官方面试最常见150题之简单题
参考:
mini-css-extract-plugin插件快速入门
在Typescript项目中,如何优雅的使用ESLint和Prettier
实用husky介绍
我是这样搭建typescript+react
webpack官网
webpack import和export
tsconfig常用配置
前端工程化 - 剖析npm的包管理机制
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!