前面的话
基于webpack5创建自定义同步、异步loader,在此基础上实现一个简易的渲染markdown的loader和合成雪碧图的loader。
代码地址:自定义loader
准备工作
我们先创建一个webpack项目。
初始化项目
新建一个文件夹create-loader,在该目录执行以下命令来完成初始化工作。
npminit-y
安装依赖
安装webpack、webpack-cli以及webpack-dev-server。前两者打包必备;后者是一个提供热更新的开发服务器,对开发阶段友好。
pnpmaddwebpackwebpack-cliwebpack-dev-server--save-dev
创建入口文件
创建src目录,并创建index.js入口文件。
//index.jsdocument.write('hellowp')
创建webpack配置文件webpack.config.js
虽然webpackv4开箱即用,可以无需配置文件就可以打包,但webpack会默认打包入口文件为src/index.js文件,打包产物为dist/main.js,并且开启生产环境的压缩和优化。
但大多数的项目还是需要一些复杂的配置,配置文件webpack.config.js文件还是很有必要的。webpack在打包时会自动识别这个文件,根据里面的配置来进行打包。
constpath=require('path')module.exports={mode:'development',//以什么模式进行打包entry:'./src/index.js',output:{path:path.resolve(__dirname,'dist'),//打包后的代码放在dist目录下filename:'bundle.js',//文件名为bundle.js},devServer:{static:'./dist'}}
Tips:如果我们想更改为指定的配置文件prod.config.js来打包,可以使用--config
标志来修改。
"scripts":{"build":"webpack--configprod.config.js"}
添加npmscript
我们可以在package.json文件中创建快捷方试来启动开发和打包。
{//...省略"main":"src/index.js",//修改入口文件"scripts":{"dev":"webpackserve--open","build":"webpack"},//...省略}
这样可以通过npmrundev来启动项目:
使用npmrunbuild来打包项目:
可以看到生成了一个dist目录,里面有一个bundle.js文件。
创建index.html文件
上面打包的产物只有一个js文件,我们想要使用浏览器访问里面的内容就要手动创建一个html文件,并引入bundle.js脚本文件,使用浏览器打开即可。
<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metaname="viewport"content="width=device-width,initial-scale=1.0"><title>Document</title></head><body><scriptsrc="./bundle.js"></script></body></html>
以上的步骤简单的完成了一个基于webpack打包的最简单例子。
实现简单自定义的loader
为什么需要loader
webpack本身只能识别js和json文件,对于css、ts等其他文件就需要loader对齐进行处理,转换为webpack能识别的模板。
loader是什么
loader是一个导出为函数的JavaScript模块,用于对模块的源码进行转换。形如:
moudle.exports=(source)=>{//按照自己的转换要求进行处理returnsource}
loader原则
- 单一:一个loader只做一件事。
- 链式调用:从右往左依次调用。
- 模块化:保证输出的是模块化。
- 无状态:每一个loader的运行相对独立,不与其他loader
- loader-utils工具库:提供了很多工具
- …
更多原则参考官方文档loader原则。
实现一个简单的同步loader
在上面例子的基础上我们来编写一个简单的字符串替换loader、
创建同步loader
在src下创建xq-loader目录,创建my-loader.js文件。
//my-loader.jsmodule.exports=function(source){//在这里按照你的需求处理source//可以通过this.getOptions或者this.query来获取参数constoptions=this.getOptions()console.log(options)returnsource.replace('wp','xiaoqi')}//或者使用this.callbackmodule.exports=function(source){//在这里按照你的需求处理source//可以通过this.getOptions或者this.query来获取参数constoptions=this.getOptions()constresult=source.replace('wp','xiaoqi')this.callback(null,result)}
这里编写的是一个简单的同步loader,我们可以通过return一个表示已转换模块的单一值。如果情况比较复杂,也可以通过this.callback(err,values…)这个回调函数来返回转换后的结果。
this.callback(//转换异常时,抛出错误err:Error||null,//转换后的结果content:string|Buffer,//用于把转换后的内容得出原内容的SourceMap,方便调试sourceMap?:SourceMap,)
使用loader
在webpack.config.js中配置loader有两种方式。一种是path.resolve,另一种是ResolveLoader。
使用path.resolve指定loader的文件路径
//webpack.config.js{//...省略module:{rules:[{test:/\.js$/,use:[{loader:path.resolve(__dirname,'./src/xq-loader/my-loader.js'),options:{name:'xiaoqi',},},],},],}}
使用ResolveLoader
{module:{rules:[{test:/\.js$/,use:['my-loader'],},],},resolveLoader:{//webpack将会从这些目录中依次搜索loader,modules:['node_modules','./src/xq-loader'],},}
运行npmrundev可以看到wp被替换为xiaoqi。
实现一个异步loader
在某些耗时久的场景下,比如处理网络请求的结果,我们可以使用异步loader,这样不会阻塞整个构建。
创建async.txt文件
在src目录下创建async.txt文件,随便写点内容。
创建异步loader
在src下创建xq-loader目录,创建my-async-loader.js文件。该loader的功能为读取async.txt中的内容并返回。
//my-async-loader.jsconstfs=require('fs')constpath=require('path')module.exports=function(source){//通过this.async来返回一个异步函数,第一个参数Error,第二个参数是处理的结果。constcallback=this.async()fs.readFile(path.join(__dirname,'../async.txt'),'utf-8',(err,data)=>{consthtml=`module.exports=${JSON.stringify(data)}`callback(null,html)})}
添加loader配置
在webpack.config.js文件中的module.rules下添加my-async-loader。
//webpack.config.js//...{test:/\.txt$/,use:{loader:'my-async-loader',},},//...
引入txt文件
在index.js入口文件中引入。
//index.jsimporttxtfrom'./async.txt'document.write('hellowp')document.write(`</br>异步loader:${txt}`)
运行||打包
npmrundev可以看到async.txt文件下的内容被打印出来。
实现一个渲染markdown的loader
简易版mark-loader,借助markdown-it库的能力。
创建md文件
首先我们在src下创建一个md文件。
编写mark-loader
在src下创建xq-loader目录,创建mark-loader.js文件。
//mark-loader.jsconstMarkdownIt=require('markdown-it')module.exports=function(source){constoptions=this.getOptions()constmd=newMarkdownIt({html:true,...options,})lethtml=md.render(source)//webpack无法直接去解析html模板,所以要返回一段js代码html=`module.exports=${JSON.stringify(html)}`this.callback(null,html)}
添加配置loader
//webpack.config.js{test:/\.md$/,use:[{loader:'mark-loader',},],},
引入md文件
创建一个div,将生成的html插入其中。
importtxtfrom'./async.txt'importmdfrom'./mk.md'document.write('hellowp')document.write(`</br>异步loader:${txt}`)constdiv=document.createElement('div')div.innerHTML=`${md}`document.body.appendChild(div)
运行||打包
npmrundev运行,可以看到md文件以html的形式渲染出来。
实现一个生成雪碧图的loader
简易版sprite-loader,借助spritesmith库的能力。
创建css文件
首先我们在src下创建一个css文件,以?__sprite
来标志是需要合成的图片。
创建sprite-loader
找到需要合成的图片,组合生成一个数组,再使用spritesmith的能力将多张图合并成一张图。
constpath=require('path')constfse=require('fse')constSpritesmith=require('spritesmith')module.exports=function(source){constcallback=this.async()//匹配url(开头?__sprite结尾的constimgs=source.match(/url\((\S*)\?__sprite/g)constmatchImgs=[]for(leti=0;i<imgs.length;i){//解析出图片路径constimg=imgs[i].match(/url\((\S*)\?__sprite/)matchImgs.push(path.join('./src',img[1]))}Spritesmith.run({src:matchImgs},(err,result)=>{//将合成的图片写入到src/images/sprite.jpg中fse.writeFileSync(path.join(process.cwd(),'src/images/sprite.jpg'),result.image)//替换原引入为合成图片的路径source=source.replace(/url\((\S*)\?__sprite/g,()=>{return`url('./images/sprite.jpg'`})callback(null,source)})}
添加loader配置
由于webpack没法识别css文件,这里先用sprite-loader合成图片;再调用css-loader,它会帮我们对@import
和url()
进行处理,就像js解析import/require()
一样;最后调用style-loader帮我们将css代码以<style>
的形式插入到DOM中。
//webpack.config.js{test:/\.css$/,use:['style-loader','css-loader','sprite-loader'],},
引入css文件
在index.js中引入css文件,给上述的div加上类名img2。
import'./index.css'importtxtfrom'./async.txt'importmdfrom'./mk.md'//...constdiv=document.createElement('div')div.className='img2'div.innerHTML=`${md}`document.body.appendChild(div)
运行||打包
可以看到背景图生效了,并且在dist下会生成一个图片文件。