挑战21天手写前端框架 day13 解析用户配置文件,将框架行为交到用户手里

525 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第15天,点击查看活动详情

阅读本文需要 5 分钟,编写本文耗时 1 小时

到目前为止,我们的框架行为都是由我们的设计开发时制定的,但是实际项目中肯定会涉及到用户需要定制化的一些数据和定制化需求,由于我们并不是要写一个真正的前端框架,因此我们还是以管控能力为主,尽量将最少的配置能力给到用户。尽量只让用户配置一些框架无关的信息和数据,这将意味着用户需要知道的前端工程化的东西非常的少。借此来提升本次框架的用户开发体验。即整个项目技术栈的选择,都有框架设计统一制定。其实这个思路非常适合用在公司内部团队中构建的框架方案中。

首先依旧随便约定一个配置文件的名字 malita.config.ts 然后将它放到根目录下,然后我们还是用昨天构建“生命周期函数”的方式,增加一个生命周期,getUserConfig

比如就按住我们昨天留下的两个配置需求将真正的 keepalive 需求,传递到 <KeepAliveLayout keepalive={[/./]}> 中,使用用户配置 title 作为页面 dom 的title。

export default {
    title: 'Hello',
    keepalive: [/./]
}

然后我们尝试着将它引入到我们的框架中,

import { existsSync } from 'fs';
import path from 'path';
import type { AppData } from './appData';
import { DEFAULT_CONFIG_FILE } from './constants';

export const getUserConfig = ({ appData }: { appData: AppData; }) => {
    return new Promise((resolve, rejects) => {
        let config = {};
        const configFile = path.resolve(appData.paths.absSrcPath, DEFAULT_CONFIG_FILE);

        if (existsSync(configFile)) {
            config = require(configFile).default;
        }
        resolve(config);
    })
}

结果并不像我们设想的那样被成功引入,我们在控制台上看到有如下错误:

/Users/congxiaochen/Documents/malita/examples/app/malita.config.ts:1
export default {
^^^^^^

SyntaxError: Unexpected token 'export'

这是因为我们的 malita dev 是运行在 es5 的环境下的。但是我们的配置文件使用的却是 es6 的语法。 我们可以尝试着修改配置文件``,将它改成 es5 的语法

module.exports = {
    title: 'Hello',
    keepalive: [/./]
}

这是便可以正确获取到配置信息了

> malita dev

App listening at http://127.0.0.1:8888
{ title: 'Hello', keepalive: [ /./ ] }

配置构建

所有当你在框架开发中遇到类似的错误时,你就可以往语法未正确转换,即文件未编译这个思路上去分析和定位,做同样的尝试,如果修改后文件可用,那么你就可以确定下来问题所在。在提出针对性的解法,比如这里文件未转译,我们就使用 esbuild 对文件进行构建。

构建方法与我们构建主入口文件一直,需要注意的是我们需要导出使用,因此需将 format 改为 cjs:

import { existsSync } from 'fs';
import path from 'path';
import { build } from 'esbuild';
import type { AppData } from './appData';
import { DEFAULT_CONFIG_FILE } from './constants';

export const getUserConfig = ({ appData, sendMessage }: { appData: AppData; sendMessage: (type: string, data?: any) => void; }) => {
    return new Promise(async (resolve, rejects) => {
        let config = {};
        const configFile = path.resolve(appData.paths.cwd, DEFAULT_CONFIG_FILE);

        if (existsSync(configFile)) {
            await build({
                format: 'cjs',
                logLevel: 'error',
                outdir: appData.paths.absOutputPath,
                bundle: true,
                watch: {
                    onRebuild: (err, res) => {
                        if (err) {
                            console.error(JSON.stringify(err));
                            return;
                        }
                        sendMessage('reload');
                    }
                },
                define: {
                    'process.env.NODE_ENV': JSON.stringify('development'),
                },
                external: ['esbuild'],
                entryPoints: [configFile],
            });
            try {
                config = require(path.resolve(appData.paths.absOutputPath, 'malita.config.js')).default;
            } catch (error) {
                console.error('getUserConfig error', error);
                rejects(error);
            }
        }
        resolve(config);
    })
}

配置使用

keepalive

keepalive 配置传递到 generateEntry 函数中 <KeepAliveLayout keepalive={${JSON.stringify(userConfig?.keepalive ?? [])}}>,修改 malita.config.ts 中的配置,验证配置是否有效。我们将状态保持配置修改为只有 users 页面能够被保持。

export default {
    title: 'Hello',
    keepalive: ['/users']
}

值得注意的是,我们的 keepalive 配置是支撑正则表达式的,但是正则表达式在 JSON.stringify 会被转换成 {},因此我们需要编写一个工具类,来对这个配置做特殊的处理,

const configStringify = (config: (string | RegExp)[]) => {
    return config.map((item) => {
        if (item instanceof RegExp) {
            return item;
        }
        return `'${item}'`;
    });
};

然后修改 KeepAliveLayout 配置的获取方式

`
const App = () => {
    return (
        <KeepAliveLayout keepalive={[${configStringify(
            (userConfig.keepalive) || [],
        )}]}>
            <HashRouter>
                <Routes>
                    ${routesStr}
                </Routes>
            </HashRouter>
        </KeepAliveLayout>
    );
}
`

执行 malita dev 验证效果。

title

title 配置传递到 generateHtml 函数中

 const title = userConfig?.title ?? appData.pkg.name ?? 'Malita';
 <title>${title}</title>

打开页面重控制台查看页面的 html

<head>
    <meta charset="UTF-8">
    <title>Hello</title>
</head>

至此我们就简单的完成了用户配置的功能,不知道有没有细心的小伙伴发现了,在 malita dev 启动之后,修改配置之后,虽然页面有重新加载,但是配置数据的变更并没有被真正的覆盖到原来的逻辑,原因是我们仅仅重启了我们的浏览器端,但是并没有重启我们的服务端,这个问题我们将会在明天解决。

感谢阅读,明天我们将会解决上面提到的问题,并且添加框架内置的 http 请求方法,用于在项目中请求服务端数据。其中将会涉及到本地 mock 文件的解析和代理中间件的编写。如果你对这些内容感兴趣,请持续 follow 这个专题。

源码归档