获取中...

-

Just a minute...

因为觉得hexo框架搭的博客功能一般,就有了想要自己写一个博客的想法。然后就想着如果把markdown文件也放到项目里的话,会比较方便,发现需要编写一个加载markdown的loader,于是就有了这篇博客,来记录一下踩过的坑和收获

loader的作用

我们都知道,直接在代码里面通过require或者import去引入markdown文件是会报错的,因为webpack在打包的时候不知道如何去处理markdown文件。webpack是负责打包js模块的,非js模块的文件,如果要被打包,那么就需要有loader去处理。

比如你要import一个txt文件或者markdown文件,这种就相当于是纯文本字符串。你让webpack去打包这种东西它是不认识的。你需要把它改成一个js模块,模块的导出值是原本文件的内容。

所以,我们需要给markdown文件专门编写一个loader。

简单来讲,可以是这样

1
2
3
4
5
6
## ./md-loader.js

module.exports = function (source) {
return `export const content = ${JSON.stringify(source)}`;
};

这个loader接收原本文件的内容,然后返回一个新的字符串,字符串的内容就是一个js模块。

这样webpack导入这个文件的时候,看到的就是这个loader返回的内容,也就是一个js模块了。

动态加载模块

引入文章的内容,我们肯定是希望懒加载,动态import进来的。这样可以让文章的内容被放在一个单独的js包里。我们可以首屏渲染出页面的框架,然后呈现一个加载的动画,再通过一个单独的请求去拿到文章内容。

我们可以通过js原生的import()语法去动态加载,也可以通过next/dynamic提供的dynamic方法去加载(next的dynamic只适用于加载组件,貌似还只支持函数组件)

1
2
3
4
5
import("../../public/test.md").then(
({ content }) => {
setMd2(content);
}
);

动态加载

这样做的好处是,减小首屏资源的包体积,让首屏资源能更快的拿到,从而缩短白屏时间。

区分场景,让loader有不同的导出值

更进一步地说,我们未来的博客肯定是有一个文章列表页,和文章详情页的。文章列表页的文章可能会非常的多。如果对于每一个文章,我们都去请求文章的完整内容,是有点多余的,试想每篇文章几百上千字,都在一个页面去请求,也会比较影响性能。在列表页,外显的信息可能只需要文章的标题,tag,或者第一段之类的部分信息。所以我们其实希望,列表页import markdown文件的时候,只拿到一些文章摘要信息。

但是呢,我们在详情页import markdown文件的时候,有需要文章完整的内容。或许也可以通过,每写一篇博客,就自动生成一个文件来存文章摘要来实现,但是这样总显得有点捞。我还是希望能做到,对同一个文件,可以手动控制import的时候,需要摘要信息还是完整信息。

webpack的loader是可以接收参数的。我们可以在webpack的配置文件里去写,比如这样(以next.js的配置文件next.config.js中,配置webpack为例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
## ./next.config.js 

/** @type {import('next').NextConfig} */
const nextConfig = {
webpack: (config) => {
config.module.rules.push({
test: /\.md$/,
use: [
{
loader: "./md-loader",
options: { content: true },
},
],
});
return config;
},
};

module.exports = nextConfig;

我们通过options传参数进去,然后再loader里面可以通过loader-utils去获取options。

1
2
3
4
5
6
7
8
9
10
11
## ./md-loader.js

const { getOptions } = require("loader-utils");
module.exports = function (source) {
const options = getOptions(this) || {};
console.log(options.content);
if (options.content)
return `export const content = ${JSON.stringify(source)}`;
else return `export const title = 'test'`;
};

特别注意,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
2
3
4
5
6
7
## ./src/app/page.tsx

import("!../../md-loader?content=true!../../public/test.md").then(
({ content }) => {
setMd2(content);
}
);

通过感叹号,将各个loader与最后的文件隔开,然后在每个loader后面用问号分隔,将参数以类似url的query string的格式写上去。

如果我在文章列表页import markdown文件,那么content就传false。如果我在文章详情页import markdown文件,content就传true。

最后来看一下实际效果

请求文章内容

能看出来,这里有个单独的js请求,里面就是导出的文章内容。

而当我只需要文章的标题时,我可以这么写

1
2
3
4
5
6
7
## ./src/app/page.tsx

import("!../../md-loader?content=false!../../public/test.md").then(
({ title }) => {
setMd2(title);
}
);

请求文章标题

这样就做到了,对于同一份文件,借助loader来实现不同场景下,拿到的内容不同。

一些感想

其实一开始是想,我这个博客到底如何设计架构,想要不要把项目代码和文章放到一块。如果不放到一块的话,好处就是每次发文章,项目完全不用重新部署。但是坏处就是,每次访问,都需要项目向另一个服务器请求文章内容,感觉这样会导致加载速度变慢。不如将文章直接和项目代码放到一起,这样如果是纯静态页面,可以全部依靠服务端渲染或者next13支持很好的服务端组件来实现。

一开始只是单纯的查如何用react渲染markdown,然后就动手尝试,发现需要编写loader来import markdown。后面又想到了,需要动态加载,通过单独的js文件来请求文章内容。再面又想到了,文章列表页有好多文章,如果每个文章都请求全部内容,又是很庞大的网络请求开销,于是就有了想法,想试试能不能通过loader去实现导出不同的内容,最终有了此文章,收获颇丰

相关文章
评论
分享
  • 基于tapable来模拟webpack插件机制

    前两天看到ByteFE公众号发了篇文章,从源码去讲webpack。我之前也从很多方面去学习webpack,只是源码一直没太看得进去,这一次我想仔细地读一读。 简要介绍tapable说实话,之前看webpack源码一直没看的太懂,...

    基于tapable来模拟webpack插件机制
  • webpack运行时代码简要分析

    开个坑,主要也是怕自己后面忘了 简单写写,有空了补上 __webpack_modules是一个数组,每个元素都是一个函数,函数接受三个参数:第一个参数module好像没用到,第二个是一个对象(会把本模块要导出的属性,赋值给这个对象),...

    webpack运行时代码简要分析
  • webpack动态加载原理简述

    为什么需要动态加载上一篇讲我写webpack针对markdown的loader的时候,提到了希望能动态加载以优化性能。篇幅原因没有展开说,打算单独写一篇文章讲一下。 当我们访问网站的时候,浏览器会向服务器请求页面资源。首先请求的当然是...

    webpack动态加载原理简述
  • 动手实现react-ssr 2

    仓库链接:本篇代码 运行方法参考readme,请使用pnpm,因为本次使用的rspack尚未推出1.0版本,预计后续可能会有很多breakchange,所以lock文件至关重要,而我只上传了pnpm的lock,非常抱歉。 环境:no...

    动手实现react-ssr 2
  • 动手实现react ssr

    关于ssr相关的介绍,可以移步之前的一篇博文,本文不再赘述 日常工作中,我们可能都接触过ssr,不过我们可能通常是借助于框架(例如Next.js)的支持来完成ssr。今天我想尝试,摆脱高度封装的前端框架,只依赖react相关的库,来实...

    动手实现react ssr
  • 深入js——作用域,作用域链,执行栈(一)

    最近在补js基础。当初自学js的时候,总是感觉学的不扎实。只能说不去上手,再怎么死读书,也理解不了。积累了经验,然后自己去思考,这个时候再去看理论,就容易理解了。 感觉工作一年以来,确实学到了很多,但是过于专注于应用方面的知识,反倒...

    深入js——作用域,作用域链,执行栈(一)
  • 简单通俗解释HTTP缓存

    开始前的碎碎念 最近加班真的很严重,组里同事们都已经苦不堪言。业务交付压力非常大,根本没时间去磨练本领,学习技术。真的很讨厌这种没有成长的日子,每天被业务需求赶着跑。每天下班之后,本来应该用于提升自我的时间也被压榨用来写业务代码。 ...

    简单通俗解释HTTP缓存
  • 从invalid hook call报错说到npm install相关

    遇到了invalid hook call报错上周,业务下游的同事反馈了一个生产问题,说我们传的值有问题,领导让我帮忙排查一下。我最近一直没做那个项目相关的需求,打开那个项目切到release分支,拉代码,npm i然后启动,结果就白屏...

    从invalid hook call报错说到npm install相关
  • useState为什么获取不到更新后的值,如何解决

    令人困扰的问题是因为异步吗大家都知道,react的useState和useReducer,在set和dispatch之后,马上去获取值,是拿不到最新的值的。这个原因之一是useState和useReducer是异步更新的,也就是说我们...

    useState为什么获取不到更新后的值,如何解决
  • 发布一个npm包的踩坑

    试着自己发布了一个npm包,记录一下自己踩的坑 package.json首先npm init,然后添加type:module字段,将其定义为一个es6模块。然后在files字段设置要发布上去的内容,比如dist文件夹。 接下来将开发用...

    发布一个npm包的踩坑
Please check the parameter of comment in config.yml of hexo-theme-Annie!