因为觉得hexo框架搭的博客功能一般,就有了想要自己写一个博客的想法。然后就想着如果把markdown文件也放到项目里的话,会比较方便,发现需要编写一个加载markdown的loader,于是就有了这篇博客,来记录一下踩过的坑和收获
loader的作用
我们都知道,直接在代码里面通过require或者import去引入markdown文件是会报错的,因为webpack在打包的时候不知道如何去处理markdown文件。webpack是负责打包js模块的,非js模块的文件,如果要被打包,那么就需要有loader去处理。
比如你要import一个txt文件或者markdown文件,这种就相当于是纯文本字符串。你让webpack去打包这种东西它是不认识的。你需要把它改成一个js模块,模块的导出值是原本文件的内容。
所以,我们需要给markdown文件专门编写一个loader。
简单来讲,可以是这样
1 | ## ./md-loader.js |
这个loader接收原本文件的内容,然后返回一个新的字符串,字符串的内容就是一个js模块。
这样webpack导入这个文件的时候,看到的就是这个loader返回的内容,也就是一个js模块了。
动态加载模块
引入文章的内容,我们肯定是希望懒加载,动态import进来的。这样可以让文章的内容被放在一个单独的js包里。我们可以首屏渲染出页面的框架,然后呈现一个加载的动画,再通过一个单独的请求去拿到文章内容。
我们可以通过js原生的import()语法去动态加载,也可以通过next/dynamic提供的dynamic方法去加载(next的dynamic只适用于加载组件,貌似还只支持函数组件)
1 | import("../../public/test.md").then( |
这样做的好处是,减小首屏资源的包体积,让首屏资源能更快的拿到,从而缩短白屏时间。
区分场景,让loader有不同的导出值
更进一步地说,我们未来的博客肯定是有一个文章列表页,和文章详情页的。文章列表页的文章可能会非常的多。如果对于每一个文章,我们都去请求文章的完整内容,是有点多余的,试想每篇文章几百上千字,都在一个页面去请求,也会比较影响性能。在列表页,外显的信息可能只需要文章的标题,tag,或者第一段之类的部分信息。所以我们其实希望,列表页import markdown文件的时候,只拿到一些文章摘要信息。
但是呢,我们在详情页import markdown文件的时候,有需要文章完整的内容。或许也可以通过,每写一篇博客,就自动生成一个文件来存文章摘要来实现,但是这样总显得有点捞。我还是希望能做到,对同一个文件,可以手动控制import的时候,需要摘要信息还是完整信息。
webpack的loader是可以接收参数的。我们可以在webpack的配置文件里去写,比如这样(以next.js的配置文件next.config.js中,配置webpack为例)
1 | ## ./next.config.js |
我们通过options传参数进去,然后再loader里面可以通过loader-utils去获取options。
1 | ## ./md-loader.js |
特别注意,loader-utils要装^2.0.0版本的,不能装默认的^3.0.0版本,因为3.x版本里面没有getOptions这个api了。或者不借助loader-utils,也可以用this.query去获取传入的参数。还要特别注意,由于箭头函数的this指向问题,loader非常不适合用箭头函数去写。
上面的loader文件里面,我们获取options里面的参数,然后根据options里面content的值,去决定loader是返回md文件的内容,还是只是返回一个title字符串(title内容仅作为demo,实际可以通过正则,从md文件匹配出标题,tag,分类,第一段内容等信息)。
不过问题来了,把参数写在webpack配置文件里,好像每次import的时候,拿到的参数都是一样的。。。也做不到列表页和文章详情页,从loader拿到不同的内容。
经历了一番文档的阅读和代码的尝试,我终于找到了方法,能够在每次import的时候传入参数。我们要在import的时候,用inline的语法,为这次import指定loader。
1 | ## ./src/app/page.tsx |
通过感叹号,将各个loader与最后的文件隔开,然后在每个loader后面用问号分隔,将参数以类似url的query string的格式写上去。
如果我在文章列表页import markdown文件,那么content就传false。如果我在文章详情页import markdown文件,content就传true。
最后来看一下实际效果
能看出来,这里有个单独的js请求,里面就是导出的文章内容。
而当我只需要文章的标题时,我可以这么写
1 | ## ./src/app/page.tsx |
这样就做到了,对于同一份文件,借助loader来实现不同场景下,拿到的内容不同。
一些感想
其实一开始是想,我这个博客到底如何设计架构,想要不要把项目代码和文章放到一块。如果不放到一块的话,好处就是每次发文章,项目完全不用重新部署。但是坏处就是,每次访问,都需要项目向另一个服务器请求文章内容,感觉这样会导致加载速度变慢。不如将文章直接和项目代码放到一起,这样如果是纯静态页面,可以全部依靠服务端渲染或者next13支持很好的服务端组件来实现。
一开始只是单纯的查如何用react渲染markdown,然后就动手尝试,发现需要编写loader来import markdown。后面又想到了,需要动态加载,通过单独的js文件来请求文章内容。再面又想到了,文章列表页有好多文章,如果每个文章都请求全部内容,又是很庞大的网络请求开销,于是就有了想法,想试试能不能通过loader去实现导出不同的内容,最终有了此文章,收获颇丰