React SPA 应用
SPA,本质就是通过路由的不同来加载不同的组件,然后插入到html页面的根元素中,实现切换页面但不需要刷新页面的现象,其优势有很多,现在也被广泛使用。
以往使用社区的脚手架做搭建目,并不完全知道,SPA应用的路由切换是需要我们进行一些相关的配置的。不知道你是否遇到过这种情况,当使用history的路由模式时,部署到服务器上,一刷新就会出现404的情况,这时就需要后台来配合做一个重定向了。
其出现的原因是,我们刚才说,前端切换url实际上是切换前端的路由这时候,只有你的页面在index.html下,他才会保持路由的切换,不然就会变成接口的请求,就像传统的请求那样,而SPA只需要返回一次html和js就可以了,其他的时间,都是在往html中注入不同页面的js达到页面切换的效果。所以,就需要后端在每次路由匹配下的url都返回一个html,而不是做对应的请求。
实际上,不过是线上有这样的问题,使用webpack搭建的原生项目,也是会遇到类似的问题的,所以要对webpack-dev-server进行historyApiFallback配置。
Loader
Webpack 支持使用 loader 对文件进行编译处理,可以构建包括 JavaScript 在内的任何静态资源,并且可以使用 Node.js 编写自定义 loader。
loader的使用可根据静态资源类型分为:scripts / styles / assets。
About Scripts
TSC or Babel?
项目使用react+typescript,要对jsx | js | ts | tsx进行编译处理,思路一般是将ts/tsx使用ts-loader编译成js/jsx文件,再处理js/jsx,即「TS > TS 编译器 > JS > Babel > JS (再次)」。其中,ts-loader是用于 webpack 的 TypeScript 加载器,其在内部调用了 TypeScript 的官方编译器 -- tsc,编译时ts-loader 和 tsc 均可依赖 tsconfig.json文件的规则。
显然如上的方式使用了两种编译器处理,配置繁琐复杂,所以Babel 7版本增加了对 TypeScript的处理,实现了 Babel 作为唯一的编译器来工作,再也没必要利用一些复杂的 Webpack 方法来管理、配置或者合并两个编译器。
Babel 处理 TypeScript 的方式,是将 TS 全部转换为常规 JS,然后再一如既往的操作,即移除了TS部分。
Why?
- 基于 Babel 的优秀的缓存和单文件散发架构,Babel + TypeScript 的组合套餐会提供了更快的编译。
- 而 类型检查 则需通过安装 Typescript 补全。
npm i typescript -D
tsc --init
如果我们没有使用Babel,则可直接通过tsc/ts-loader来编译ts。但在webpack中使用了babel-loader,则可以直接在loader.options中,添加presets - @babel/preset-typescript并在babelrc,再配合 tsc 做类型检查即可。
综上所述,babel-loader用于处理全部的scripts肯定是很耗时的,所以还可以通过一些方法来提升速度,如使用多线程编译方式。
多线程编译
webpack在node中是单线程的,使用webpack编译项目时,loader、plugin需要处理的文件数量很大,单线程处理会导致效率低、速度慢,所以往往考虑实现多线程工作。
如webpack4常用的happyPack,happyPack将不同的loader、plugin工作分配到不同的子线程,在子线程结束完后再推入到主线程中,多线程同时编译同时减少编译时间。HappyPack同时提供plugin和loader以完成其工作,因此必须同时使用两者来启用它。
因为happyPack作者目前很少从事JavaScript工作,所以不再维护,在webpack5到来之后,更多的使用thread-loader,使用thread-load可以实现将不同编译工作放入不同的工作池中运行。
thread-loader
把这个 loader 放置在其他 loader 之前, 放置在这个 loader 之后的 loader 就会在一个单独的 worker 池(worker pool)中运行
在 worker 池(worker pool)中运行的 loader 是受到限制的。例如:
- 这些 loader 不能产生新的文件。
- 这些 loader 不能使用定制的 loader API(也就是说,通过插件)。
- 这些 loader 无法获取 webpack 的选项设置。
每个 worker 都是一个单独的有 600ms 限制的 node.js 进程。同时跨进程的数据交换也会被限制。
请仅在耗时的 loader 上使用(例如 babel-loader)。
const threadLoader = require('thread-loader');
threadLoader.warmup(
{
// 池选项,例如传递给 loader 选项
// 必须匹配 loader 选项才能启动正确的池
},
[
// 加载模块
// 可以是任意模块,例如
'babel-loader',
'babel-preset-es2015',
'sass-loader',
]
);
可以通过预热 worker 池来防止启动 worker 时的高延时。
这会启动池内最大数量的 worker 并把指定的模块加载到 node.js 的模块缓存中。
babelrc
module.exports = {
presets: [
'@babel/preset-env',
['@babel/preset-react', { runtime: 'automatic' }],
'@babel/preset-typescript',
],
plugins: [
/** 懒加载引入动态文件 */
'@babel/plugin-syntax-dynamic-import',
/** antd提供了按需引入,而babel-plugin-import能够帮助我们在引入组件的时候自动加载相关样式。 */
[
'babel-plugin-import',
{
libraryName: 'antd',
libraryDirectory: 'es',
style: true,
},
],
/** 解决全局对象等编译不足,不会污染全局API
* babel 在转译的过程中,对 syntax 的处理会使用到 helper 函数,对 api 的处理会引入 polyfill。
* 默认情况下,babel 在每个需要使用 helper 的地方都会定义一个 helper,导致最终的产物里有大量重复的 helper;
* 引入 polyfill 时会直接修改全局变量及其原型,造成原型污染,故使用此插件解决。
* api 从之前的直接修改原型改为了从一个统一的模块中引入,避免了对全局变量及其原型的污染
* helpers 从之前的原地定义改为了从一个统一的模块中引入,使得打包的结果中每个 helper 只会存在一个
*/
'@babel/plugin-transform-runtime',
['@babel/plugin-proposal-decorators', { version: 'legacy' }],
],
};
@babel/plugin-transform-runtime
About Styles
Webpack默认只能理解JS和JSON文件,其他类型的资源打包,需要安装相应的插件或loader,loader让webpack能够去处理其他类型的文件,并将它们转换为有效模块,并添加到依赖树中。
处理css内容时,常通过css-loader,处理CSS的模块化(如import)和使用url引入的外部资源;再通过style-loader,把css-loader解析后的样式内联插入到HTML的head中。配置css-loader的 options: { modules: { localIdentName: xxx } },可以自定义生成类名的样式规则。
项目中往往使用Less 和 Sass 等 CSS 扩展语言,除了需要安装编译 CSS 语言相关的loader外,还需要安装它们各自的语言包和loader,以项目中使用的Less为例,则需要对less文件使用less-loader解析。
生产环境中,我们期望将样式内容单独处理成.css文件,以更好的利用CDN,而不是把样式代码放到HTML的style标签中。所以,可以使用mini-css-extract-plugin插件代替style-loader,提取css代码为单独文件,在HTML中通过link标签引入CSS文件,引入需配合html-webpack-plugin使用。mini-css-extract-plugin 同时提供 Loader、Plugin 组件,需要同时使用。
对于样式内容,我们还会关心客户端兼容性问题,可以通过postcss样式转换工具解决。配置 postcss后, 会根据 package.json 中的 browserslis t或者 postcss.config.js 内容,向编译器指明需要兼容的浏览器类型,再通过安装 autoprefixer 会给需要兼容的样式自动添加前缀,便可以不再担心某个功能是否需要前缀,或者以往使用的前缀是否因为过时而不再需要。
在使用antd组件库时,支持通过按需加载引入组件,所以样式也期望是按需引入的,取消在App入口引入全部样式「@import '~antd/dist/antd.less'」,会遇到样式加载不出来的问题,故可以使用babel-plugin-import,帮助我们在引入组件的时候自动加载相关样式。
About Assets
SVG
svg是一种基于XML的可缩放矢量图形,它具有PNG和JPEG格式无法具备的优势:缩放时不会牺牲图像质量,是动态可交互的,一般拥有较小的尺寸。由于SVG 可以与 JavaScript 技术一起运行,所以被大量用在前端项目中。我们在使用svg时也产生了一定需求,如:期望在React中以JSX方式直接使用svg,且不影响其作为url在css中引入。
基于以上产生了react-svgr,一个将 SVG 转换为 React 组件的通用工具。只需分别配置两种svg的资源解析方式,将resourceQuery用于一种,以支持通过 ×.svg?url 引用。
{
test: /.svg$/i,
type: 'asset',
resourceQuery: /url/, // *.svg?url
},
{
test: /.svg$/i,
issuer: /.[jt]sx?$/,
resourceQuery: { not: [/url/] }, // exclude react component if *.svg?url
use: ['@svgr/webpack'],
}
csv | pdf | xlsx | doc | ppt...
网页上需要使用到图片、字体图标、音频、视频等资源。在之前的webpack4版本,默认是不能处理图片资源的,需要额外配置loader才能处理:
- raw-loader 将文件导入为字符串
- url-loader 将文件作为 data URI 内联到 bundle 中
- file-loader 将文件发送到输出目录
在webpack 5中增加了资源模块类型(asset module type),可以支持对图片的处理,不需要引入额外的依赖。如果需要指定图片编码为base64、输出目录等,只需要简单的配置即可。
- asset/resource 发送一个单独的文件并导出 URL,仅拷贝,不能配置转base64。之前通过 file-loader 实现。
- asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现。
- asset/source 导出资源的源代码。之前通过使用 raw-loader 实现。
- asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。
png | jpe?g | gif | ico
对于常用格式的图片资源,可以进一步处理优化。使用url-loader将体积较小的图片,转换成base64的格式,可以减少网络请求数,降低网络带宽消耗。超过limit体积的图片,则使用 file-loader 。
url-loader:url-loader 允许你有条件地将文件转换为内联的 base-64 URL (当文件小于给定的阈值),这会减少小文件的 HTTP 请求数。如果文件大于该阈值,会自动的交给 file-loader 处理。
file-loader:file-loader 可以指定要复制和放置资源文件的位置,以及如何使用版本哈希命名以获得更好的缓存。此外,这意味着 你可以就近管理图片文件,可以使用相对路径而不用担心部署时 URL 的问题。使用正确的配置,webpack 将会在打包输出中自动重写文件路径为正确的 URL。
V5.0内置提升
v4.x 大多使用cache-loader或者DLL
之所以要有dll,是因为cache-loader并不能覆盖所有模块,只能对个别被loader处理的模块进行缓存。而那些通用的库是没法被cache-loader处理的,所以只能通过dll的方式来预编译。
Webpack5开始内置缓存方案,提供了一套持久化抽象,并提供了几个实现:
- 【build】IdleFileCachePlugin:持久化到本地磁盘
- 【dev】MemoryCachePlugin:持久化到内存