下一代前端工程化的思考与探索

简介: vite的出现极大地提升了前端开发的效率,没有哪个程序员能拒绝毫秒级响应,可见vite的发展潜力是巨大的。正如其官网所说,vite为下一代的前端工具链提供极速响应,它充分利用了现代浏览器提供的能力,但也因此无法在老旧版本的浏览器中得到支持。但随着社会环境的发展,老旧版本浏览器即将退出历史舞台,前端技术也将迎来一场新的风暴。

webpack统治的江湖

在当下的前端工程化工具中,webpack是当之无愧的老大哥,经过多年的发展和大量的生产实践,webpack已然成为最主流的前端模块打包工具且社区全面。

webpack在编译时会从一个入口文件(entry.js)开始,将其依赖的所有js或者其他配置loader的资源全部打包到一个文件(bundle.js)。

webpack-build.png

当修改文件时,webpack会重新构建js文件。因此随着工程体量的增加,webpack构建的时间也会增加,因此可能导致一次更改需要长达十几秒才能完成编译。

后起之秀vite

vite(法语意为 "快速的")是一种新型前端构建工具,能够显著提升前端开发体验。vite主要由一个开箱即用的开发服务器和构建工具rollup的组成,类似webpack + webpack-dev-server,但是vite的开发服务器基于 原生 ES 模块和esbuild,模块热更新(HMR)速度快到惊人。相比之下,vite更轻更快。

esbuild是一个JavaScript的打包和和压缩工具,最主要的一个特征就是 有极致的性能,那么它到底有多快,参考esbuild官方提供的一张图! esbuild-time.png

vite工作的方式则不一样,vite只会将当前正在使用的文件或模块转换成原声ES模块,而且这个过程由esbuild完成,其执行速度比webpack快10-100倍,并且由于vite的工作机制,热更新时间不会随工程体量增加儿增加。

vite-build.png

基于vite的react工程脚手架

一直以来,我司依赖umijs构建工程模板,umi3内部依赖webpack4。经过几个中大型项目的开发之后,缓慢的应用构建和HMR终于让我们开始寻求新的方案。在体验过vite秒启动之后,我非常想将之用于实践,因此基于vite,我设计了一个脚手架工具neeco(内测中),内部采用许多比较新的依赖,以积累下一代的前端工具链在生产实践中的经验。

尽管现在umi4已经发布,并提供 mfsu以及基于vite或esbuild构建,但经过实测(umi@4.0.11),发现其配置和使用并没有符合预期。另一个方面,相比于umi捆绑的数据流dva,我更倾向基于react hooks的数据流 zustandconstate

这并非是想表达umi不好,相反,在目前的项目开发中,umi依然是首选的。我想寻求的是在开发阶段能带来更优体验的工具,但在生产环境下,稳定性和兼容性才是首选,这并不冲突。neeco内的很多设计思路都来自于umi,在后续的发展中,会考虑使用umi构建生产包。

设计定位

  1. 充分的开发约定,开箱即用;
  2. 全面拥抱hooks,逻辑独立且易移植。

开发约定

在基础设施的构建上,neeco采用了以约定为主。

目录结构和约定式路由

├── src
│   ├── layouts
│   │   ├── BasicLayout.tsx
│   │   ├── index.tsx
│   ├── pages
│   │   ├── index.less
│   │   └── index.tsx
│   │   └── useMeta.ts
│   ├── utils // 推荐目录
│   │   └── index.ts
│   ├── useInitialState.ts
│   ├── global.(css|less|sass|scss)
这与umi非常相似,以便在不修改代码的情况下,umi同样可以进行打包。

src目录下主要是layouts目录和pages目录。

layouts/index.tsx

约定式路由时的全局布局文件,实际上是在路由外面套了一层。

pages目录

所有路由组件存放在这里。使用约定式路由时,约定 pages 下所有的 (j|t)sx? 文件即路由。使用约定式路由,意味着不需要维护可怕的路由配置文件。最常用的有基础路由和动态路由(用于详情页等,需要从 url 取参数的情况)。

基础路由

假设 pages 目录结构如下:

+ pages/
  + users/
    - index.tsx
  - index.tsx

那么,会自动生成路由配置如下:

[
    { path: '/', component: './pages/index.tsx' }, 
    { path: '/users/', component: './pages/users/index.tsx' }
]
动态路由

约定,带 $ 前缀的目录或文件为动态路由。若 $ 后不指定参数名,则代表 * 通配,比如以下目录结构:

+ pages/
  + foo/
    - $slug.tsx
  + $bar/
    - $.tsx
  - index.tsx

会生成路由配置如下:

[
    { path: '/', component: './pages/index.tsx' },
    { path: '/foo/:slug', component: './pages/foo/$slug.tsx' },
    { path: '/:bar/*', component: './pages/$bar/$.tsx' }
]
pages/404.tsx

当访问的路由地址不存在时,会自动显示 404 页面,并生成路由 /404 。

useMeta

useMeta是关于菜单信息的react hook。见约定式菜单

约定式菜单

相比于繁琐的路由配置,菜单的配置要少许多,因为并非所有的路由都需要在菜单中展示出来。从实际项目经验中可以得出,菜单与路由之间有着较为紧密的联系,在约定式路由的基础上,可以同时生成约定式菜单。

export type MenuItem = Required<MenuProps>['items'][number] & {
  children?: MenuItem[];
  label?: ReactNode;
  title: string;
  key: string;
  notInMenu?: boolean;
  /**
   * 菜单顺序,从小到大
   */
  index?: number;
};

菜单的层级结构和路径可以从约定式路由中获取,但菜单名称、顺序等需要额外定义。因此在约定式路由的基础上,增加了useMeta.ts文件,用于定义菜单的相关信息。

type Meta = Omit<MenuItem, 'key' | 'children'>;

例如在/home/useMeta.ts中设置菜单名称、菜单顺序等:

defineMeta可以提供typescript代码提示,实际效果相当于(params) => ({...params})
import { defineMeta } from 'neeco';
export default defineMeta({
  title: '首页',
  index: 1
});

notInMenu可以表明该路由不在菜单中显示,并且其子路径也都不会出现在菜单中。

例如在/login/useMeta.ts中表明/login路由不出现在菜单中:

import { defineMeta } from 'neeco';
export default defineMeta({
  notInMenu: true
});

useMeta会以自定义react hooks的方式调用,因此,你可以在此使用编写一些hooks逻辑,比如动态更改菜单名称:

import type { Meta } from 'neeco';
import { useState, useEffect } from 'react';
export default () => {
    const [title, setTitle] = useState('首页');
    useEffect(() => {
        setTimeout(() => {
            setTitle('导航页')
        }, 1000)
    }, []);
    return {
        title
    } as Meta;
}

不建议在useMeta.ts中编写过于复杂的逻辑,因为useMeta并非是按需加载的,neeco会一次执行完所有的useMeta来生成菜单。

useInitialState

src/useInitialState.ts是一个在所有路由逻辑之前执行的hook,可以在这里进行用户鉴权,初始化数据等。

const useInitialState = () => ({
  name: 'demo-project',
  loaded: true,
});

export default useInitialState;

loaded是一个默认返回为true参数,当它为false时,会展示一个全局的loading状态,并且不会渲染任何页面。

页面文件中可以通过useInitialStateStore获取useInitialState返回的数据:

import { useInitialStateStore } from 'neeco';

const App = () => {
    const { initialState } = useInitialStateStore()
    return <div>{initialState.name}</div>
}

export default App;

内置hooks

neeco的约定基本都是基于react hooks,除此之外,还有其他的内置hooks可用。

useFetch

useFetch是一个用于发送http请求的hook,核心使用use-http,使用方式基本同use-http

import { useFetch } from 'neeco';

const App = () => {
    const { data, get } = useFetch('/api/userInfo')
    return <div>
         <button onClick={get}>点击获取用户名</button>
         <p>{data.name}</p>
     </div>
}

export default App;

拦截器Interceptor

Interceptor可以为请求和响应增加一些通用的额外内容,比如为请求添加token

<Interceptor options={{ header: { Authorization: token } }}>
    <App />
</Interceptor>

统一处理响应数据。例如接口的响应体结构为:

interface Response {
    status: string; // 200为成功响应
    data: any;
    errMsg: string;
}

可以在响应拦截器中统一处理异常数据提示和响应成功判定:

const onResponse = (response, { onSuccess }) => {
    const { status, data, errMsg } = response;
    if(status === "200") {
        onSuccess(data)
    } else {
        Message.error(errMsg);
    }
}

<Interceptor 
    options={{ header: { Authorization: token } }} 
    onResponse={onResponse}
>
    <App />
</Interceptor>

当响应失败时,在页面上弹出错误消息提示;当响应成功时,调用onSuccess方法,这在处理一些局部数据更新时非常有用。

import { useFetch } from 'neeco';

const App = () => {
    // 第二个参数是配置项;第三个参数是调用依赖,有这个参数时,会自动调用此接口。考详情can
    const { data, get } = useFetch('/api/user/detail', {
        // interceptor: {} // 也可以单独给这个接口添加拦截器
    }, []);
    const { post } = useFetch('/api/user/edit', {
        onSuccess: get
    })
    
    return <div>
         <button onClick={() => {
             post({ name: 'jack' })
         }}>修改用户名为jack</button>
         <p>{data.name}</p>
     </div>
}

export default App;

在上面的代码中,初始化时会自动调用/api/user/detail接口获取用户信息,点击按钮则会通过接口/api/user/edit修改用户名为jack,修改成功后(接口响应为200),触发onSuccess回调,再调用/api/user/detail更新用户信息。

国际化

neeco基于zuoy提供了自动国际化配置,进行国际化开发时无需额外配置,直接编写代码即可:

import { useZuoyIntl } from 'neeco'

const App = () => {
    const z = useZuoyIntl();
    return <div>{z('你好,妮蔻')}</div>
}

可以通过命令行执行npx zy buildzuoy会按照配置文件(参加zuoy-使用配置文件)自动生成如下文件:

├── src
│   ├── locales
│   │   ├── zh-CN.json
│   │   ├── en-US.json

国际化配置完成。

开源

一片文章的篇幅有限,neeco还有很多其他的功能就不一一描述了。按照计划,neeco将在2023年春节前后发布1.0版本,并遵循MIT协议在github上开源,欢迎关注。

neeco@1.0的主要目标是提效,下一个主要版本的目标是安全,计划有如下内容:

  • 取消页面请求
  • 停止State更新
  • 检查内存泄露
  • 监控告罄
  • 自动测试

如果在这些方面你有独到的见解,请在评论区留下宝贵的建议,非常感谢。

后语

vite的出现极大地提升了前端开发的效率,没有哪个程序员能拒绝毫秒级响应,可见vite的发展潜力是巨大的。正如其官网所说,vite为下一代的前端工具链提供极速响应,它充分利用了现代浏览器提供的能力,但也因此无法在老旧版本的浏览器中得到支持。但随着社会环境的发展,老旧版本浏览器即将退出历史舞台,前端技术也将迎来一场新的风暴。

相关文章
|
6月前
|
资源调度 前端开发 测试技术
前端工程化实践:从零搭建现代化项目构建流程
【4月更文挑战第6天】本文介绍了前端工程化的概念和重要性,包括模块化、自动化、规范化和CI/CD。接着,讨论了选择合适的工具链,如包管理器、构建工具和测试框架。然后,详细阐述了如何从零开始搭建一个基于React的现代化项目构建流程,涉及初始化、代码规范、测试、CSS处理、代码分割和CI/CD配置。最后,提到了持续优化与迭代的方向,如性能优化、类型检查和微前端。通过这样的实践,开发者可以提升开发效率和代码质量,为项目长远发展奠定基础。
311 0
|
6月前
|
前端开发 测试技术 持续交付
版本控制和团队协作:前端工程化的关键要素
版本控制和团队协作:前端工程化的关键要素
|
6月前
|
自然语言处理 前端开发 测试技术
前端工程化最佳实践:项目结构、代码规范和文档管理
前端工程化最佳实践:项目结构、代码规范和文档管理
|
4月前
|
JSON 前端开发 JavaScript
前端工程化:Webpack配置全攻略
【7月更文挑战第14天】
74 6
|
4月前
|
JSON 缓存 前端开发
前端工程化:Webpack配置全攻略
【7月更文挑战第18天】
53 1
|
5月前
|
缓存 前端开发 JavaScript
前端性能优化实践与工程化
前端性能优化实践与工程化
|
6月前
|
资源调度 JavaScript 前端开发
【前端开发---Vue2】史上最详细的Vue入门教程(六) --- 工程化开发和脚手架、组件注册
【前端开发---Vue2】史上最详细的Vue入门教程(六) --- 工程化开发和脚手架、组件注册
【前端开发---Vue2】史上最详细的Vue入门教程(六) --- 工程化开发和脚手架、组件注册
|
5月前
|
前端开发 安全 JavaScript
前端工程化实战 - 日程管理
前端工程化实战 - 日程管理
45 0
|
5月前
|
存储 JavaScript 前端开发
前端工程化
前端工程化
48 0
|
5月前
|
运维 前端开发 JavaScript
什么是前端工程化❓
什么是前端工程化❓
69 0
下一篇
无影云桌面