今天分享的内容是如何开发一个简单的WebpackLoader。希望通过这个过程,让大家了解WebpackLoader的工作原理和机制。Loader是Webpack的核心机制,但其内部工作原理非常简单。下面我们一起来开发自己的Loader,通过这个开发过程,我们可以深入了解Loader的工作原理。我这里的需求是开发一个可以加载markdown文件的loader,这样代码中就可以直接导入md文件了。大家应该都知道markdown一般都需要转成html然后呈现在页面上,所以我希望在导入md文件后能直接得到markdown转成的html字符串,如下图:这里需要直观的演示,我没有单独创建一个npm模块,而是直接在项目根目录下创建一个markdown-loader.js文件。完成后,你可以将这个模块发布到npm,作为一个独立的模块使用。项目结构及核心代码如下:└─03-webpack-loader························································································································································································································································································sourcedir│├──about.md···························Markdown模块│└─Main.js................................................................................................json·····················packagefile├──markdown-loader.js····················Markdownloader└-webpack.config.js..../src/main.jsimportaboutfrom'./about.md'console.log(about)//希望about=>'
About
thisisamarkdownfile.
'每个Webpack的Loader都需要导出一个函数。这个函数就是我们Loader处理资源的过程。它的输入是加载的资源文件的内容,它的输出是我们处理的结果。我们通过source参数接收输入,通过返回值输出。这里我们尝试先打印source,然后在函数内部直接返回一个字符串'helloloader~',具体代码如下://./markdown-loader.jsmodule.exports=source=>{//Loadtomodulecontent=>'#About\n\nthisisamarkdownfile.'console.log(source)//返回值为最终打包后的内容return'helloloader~'}完成后,我们回到webpack配置文件添加一个loader规则,这里匹配的扩展名是.md,使用的loader是我们刚刚写的markdown-loader.js模块。具体代码如下://./webpack.config.jsmodule.exports={entry:'./src/main.js',output:{filename:'bundle.js'},module:{rules:[{test:/\.md$/,//直接使用相对路径use:'./markdown-loader'}]}}TIPS:这里的use属性不仅可以使用模块名,还可以使用模块文件路径,这与Node.js中的require函数相同。配置完成后,我们再次打开命令行终端运行打包命令,如下图所示:打包过程中,命令行确实打印出了我们导入的Markdown文件的内容,也就是说Loader函数的参数确实是文件的内容。但同时也报了解析错误,说:你可能需要一个额外的加载器来处理这些加载器的结果。(我们可能还需要一个额外的加载器来处理当前加载器的结果)。所以为什么?实际上,Webpack加载资源文件的过程类似于一个工作流水线。这个过程中可以依次使用多个Loader,但是这个pipeline的最终结果必须是一个标准的JS代码串。所以这里才会出现上面提到的错误信息,解决方法很明显:直接在这个Loader的末尾返回一个JS代码串;再找一个合适的loader,稍后再处理我们得到的结果。我们先试试第一种方法。回到markdown-loader,我们将返回的字符串内容修改为console.log('helloloader~'),然后再次运行打包。这时候Webpack就不会再报错了。代码如下://./markdown-loader.jsmodule.exports=source=>{//loadedmodulecontent=>'#About\n\nthisisamarkdownfile.'console.log(source)//返回值为最终打包的内容//return'helloloader~'return'console.log("helloloader~")'}此时打包的结果是什么?我们打开输出的bundle.js,找到最后一个模块(因为后面导入了md文件),如下图:这个模块很简单,就是把我们刚才返回的字符串直接拼接成这个模块。这也就解释了为什么刚才的Loader管道最后一定要返回JS代码,因为随便返回什么内容,语法都不会通过这里。#实现Loader的逻辑了解了Loader的大致工作机制,我们回到markdown-loader.js,完成我的需求。这里需要安装一个可以将Markdown解析成HTML的模块,叫做marked。安装完成后,我们在markdown-loader.js中引入这个模块,然后使用这个模块来解析我们的源码。这里解析的结果是一个HTML字符串。如果我们直接返回,也会面临Webpack无法解析模块的问题。正确的做法是将这个HTML字符串拼接成一段JS代码。这个时候我们希望返回的代码就是通过module.exports把这个HTML字符串导出来,这样外界在导入模块的时候就可以接收到这个HTML字符串了。如果只是单纯的拼接,HTML中的换行符和引号可能会造成语法错误,所以我这里用了个小技巧,具体操作如下://./markdown-loader.jsconstmarked=require('marked')module.exports=source=>{/1。将markdown转换为html字符串consthtml=marked(source)//html=>'关于
thisisamarkdownfile.
'//2.将html字符串拼接成导出字符串的JS代码constcode=`module.exports=${JSON.stringify(html)}`returncode//code=>'exportdefault"关于
thisisamarkdownfile.
"'}先把字段字符串通过JSON.stringify()转换成标准的JSON字符串,再参与拼接,这样就不会出问题了。我们回到命令行再次运行打包,打包后的结果就是我们需要的。除了module.exports方法,Webpack还允许我们在返回的代码中使用ESModules进行导出。比如这里我们修改module.exports为exportdefault,然后运行package。结果也是可以的。Webpack会在内部自动转换ESModules代码。//./markdown-loader.jsconstmarked=require('marked')module.exports=source=>{consthtml=marked(source)//constcode=`module.exports=${JSON.stringify(html)}`constcode=`exportdefault${JSON.stringify(html)}`returncode}#在多个Loader的配合下,我们也可以尝试刚才说的第二种思路,就是在我们的markdown-loader中直接返回html字符串,然后提交交给下一个Loader处理。这涉及多个Loader一起工作。回到代码,这里直接返回标记解析后的HTML,代码如下://./markdown-loader.jsconstmarked=require('marked')module.exports=source=>{//1。markdown转成html字符串consthtml=marked(source)returnhtml}然后我们安装一个处理HTML的Loader,叫做html-loader,代码如下://./webpack.config.jsmodule.exports={entry:'./src/main.js',output:{filename:'bundle.js'},module:{rules:[{test:/\.md$/,use:['html-loader','./markdown-loader']}]}}安装完成后,返回配置文件。这里,同样将use属性修改为一个数组,可以依次使用多个Loader。不过还要注意,这里的执行顺序是从后往前的,也就是说我们要把最先执行的markdown-loader放在后面,html-loader放在前面。完成后我们回到命令行终端再次打包,这里的打包结果还是ok的。至此,我们完成了markdown-loader模块。其实整个过程的重点是Loader的工作原理和实现。