umi插件开发【仿dumi项目】 - 加载markdown文件

lxf2023-05-22 01:35:27

前面章节中我们已经顺利将tsx组件转换为页面展示,但是目前提供的功能和umi的约定式路由功能差不多,接下来我们将实现将markdown文件转换为页面展示。

为什么不能直接展示markdown

我们前面所使用的页面写法都是react组件式写法,umi通过webpack将react组件打包,这是react项目通用的模式。由于webpack不认识markdown文件,所以我们直接引入markdown文件会报错。所以我们只需要让webpack认识markdown,通过自定义loader来加载markdown文件即可。

chainWebpack

umi提供chainWebpack插件api,通过 webpack-chain 的方式修改 webpack 配置

webpack loader

loader是用于webpack解析文件的工具,不同的loader可以解析不同类型的文件,使其解析的内容可被其他模块使用。

我们需要解析markdown文件,那么就需要写一个能认识markdown文件的loader,它的功能就是识别.md文件并将文件内容解析成对象返回给import这个文件的代码使用。

实现过程

新建插件

跟前面一样,我们新建一个插件来处理文件解析:

// /src/features/compile.ts
import type { IApi } from 'umi';

export default (api: IApi) => {
  api.describe({ key: 'domi:compile' });

  api.chainWebpack(async (memo) => {
    const loaderPath = require.resolve('../loaders/markdown/loader.js');
    memo.module
      // 通过链式处理,向`webpack`添加了一条名为`domi-md`的处理规则
      .rule('domi-md')
        // 该规则用于处理`.md`文件
        .test(/\.md$/)
        // 给这个loader取个名字
        .use('md-loader')
        // loader的路径
        .loader(loaderPath)

    return memo;
  });
};

新建loader

接下来创建loader文件,注意这里loader要使用js文件,因为webpack无法直接解析ts类型的loader,第一个入参是文件内容的字符串形式,我们先直接返回。

// /src/loaders/markdown/loader.js
function mdLoader(context) {
    return context
}  
module.exports = mdLoader
为什么`dumi`的loader是用`ts`写的?
因为在`dumi`开发环境下,先将`ts`文件转成了`js``webpack`在运行时其实还是加载的`js`形式的loader。
dumi: 编译 => 启动umi(webpack) => 开发环境
domi: 启动umi(webpack) => 开发环境

新建测试文档

// /docs/markdown.md
# 我是markdown

运行项目

启动项目可以看到markdown文件已经正确解析到导航栏中了

umi插件开发【仿dumi项目】 - 加载markdown文件

点开链接一看,啥也没有,报错了

umi插件开发【仿dumi项目】 - 加载markdown文件

解决文件加载类型错误

看上面的报错信息,意思好像是懒加载的组件元素类型错误,打开请求列表看看加载了什么东西

umi插件开发【仿dumi项目】 - 加载markdown文件

应该就是这里在加载markdown文件时,只导出了个url链接,我们打开链接看看

umi插件开发【仿dumi项目】 - 加载markdown文件

这里就返回了markdown内容,看来目前不能直接从页面打开。

我们换一种方式,在jsx中直接导入这个文件看看:

// /docs/index.tsx
import react from 'react'
import md from './markdown.md'
const Home = () => {
    return (<div>hello domi! {md}</div>)
}
export default Home

umi插件开发【仿dumi项目】 - 加载markdown文件

刷新页面可以看到,import进来的对象确实只是一个地址,那我们直接放个iframe来显示:

// /docs/index.tsx
import react from 'react'
import md from './markdown.md'
const Home = () => {
    return (<>
        <div>hello domi!</div>
        <iframe src={md} />
    </>)
}
export default Home

umi插件开发【仿dumi项目】 - 加载markdown文件

哈哈终于显示出来了

webpack ruletype

当然上面并不是我们想要的效果,从前面的尝试大概能判断出来是webpack在打包时并没有想我们想象那样能直接导出我们想要的对象。这时候我们就要使用webpack一个配置ruletype,告诉他我们想要将markdown文件import成一个包含正文内容的对象,而不是一个资源地址。

这里webpack将文件视为Resource资源,其将所有 .md 文件都发送到输出目录,并且其路径将被注入到 bundle中,与我们常使用的在jsx中导入图片等一样,具体可参考资源模块 | webpack 中文文档 (docschina.org)

要改变这一默认行为,只需要配置时改变资源类型即可

// /src/features/compile.js
api.chainWebpack(async (memo) => {
    const loaderPath = require.resolve('../loaders/markdown/loader.js');
    memo.module
        .rule('domi-md')
            .test(/\.md$/)
            // 表示文件经过这个loader处理后转换为可导入的js模块
            .type('javascript/auto')
            .use('md-loader')
            .loader(loaderPath)
    return memo;
});

重启后运行,发现又报了另一个错误

umi插件开发【仿dumi项目】 - 加载markdown文件

解决错误

从报错上看,意思大概是.md文件在经过loader解析后,解析返回值失败,还告诉我们可能需要其他loader来处理返回值。

这个就比较好理解了,因为上面我们指定了经过loader处理后应该返回一个可导出的js模块,而我们目前loader只返回了markdown的正文内容,并不是js数据,所以我们只需要改动以下loader的返回值即可:

// /src/loaders/markdown/loader.js
function mdLoader(content) {
  return `
      const content = '${JSON.stringify(content)}'
      export default { content };
  `
}
module.exports = mdLoader

此时经过loader处理后,将会导出一个带有content属性的对象,再改变一下导入展示的组件:

import react from 'react'
import md from './markdown.md'
const Home = () => {
    return (<div>hello domi! {md.content}</div>)
}
export default Home

重启后可以看到如下所示,此时我们已经成功通过loader加载到markdown文件显示

umi插件开发【仿dumi项目】 - 加载markdown文件

本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!