umi插件开发仿dumi项目加载markdown文件实现详解

引言

前面章节中我们已经顺利将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文件已经正确解析到导航栏中了

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

解决文件加载类型错误

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

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

这里就返回了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

刷新页面可以看到,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

哈哈终于显示出来了

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;
});

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

解决错误

从报错上看,意思大概是.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文件显示

作者:kukiiu

%s 个评论

要回复文章请先登录注册