# webpack-code **Repository Path**: yan_guoping/webpack-code ## Basic Information - **Project Name**: webpack-code - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-03-21 - **Last Updated**: 2024-03-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## webpack加载器和插件开发 ### 加载器概述 为什么要使用loader,因为使用import和require进行模块加载的时候,这两种语法都是JavaScript进行模块化开发的语法,而我们导入的图片和css并不是JavaScript,在导入的时候解析器(v8引擎)无法识别这种语法就会报错。如果想正常执行内容,就需要对一个内容转换,转换成JavaScript所识别的语法。 * ##### 基本概念 > loader 用于对模块的源代码进行转换。 * ##### 使用方法 * 安装 * 在项目中通过npm或yarn命令进行安装,之后在webpack.config.js进行配置,当我们执行webpack命令的时候,一旦遇到了import和require的时候,就会把它进行处理,把它处理成JavaScript认识的语法格式。 ```sh yarn init -y yarn add webpack webpack-cli mkdir src touch src/index.js || type nul>src/index.js touch src/index.css || type nul>src/index.css touch webpack.config.js || type nul>webpack.config.js ``` index.css ```css body{ background-color: red; } ``` index.js ```js import "./index.css"; ``` webpack.config.js ```js const path = require("path"); module.exports = { entry: "./src/index.js", //entry可以是相对路径,也可以是绝对路径 //output只能是绝对路径 output: { path: path.resolve(__dirname, "dist"), //__dirname是指项目所在的路径,指定路径 filename: "bundle.js", //指定文件名 }, }; ``` 直接使用webpack打包,会报错,因为解析不了,建议我们安装loader ```sh yarn add css-loader ``` webpack.config.js ```js { module:{ //test要处理的文件类型,use 指定loader rules: [{ test: /.css$/, use: "css-loader" }], } } ``` 可以从上面看到,webpack已经将css输出成一个对象模块了,此时index.html肯定是不认识的 style-loader将css对象注入到html中,请使用style-loader吧 ``` yarn add style-loader ``` 因为要使用两个loader,use要改成数组的形式,注意:loader的执行顺序是先后到前 ```js { module: { //test要处理的文件类型,use 指定loader rules: [{ test: /.css$/, use: ["style-loader", "css-loader"] }], }, } ``` 然后我们可以测试一下,在 ```html Document

使用webpack

``` * 配置 * 只需要一个loader * 多个loader配合 * 执行时机 * ##### loader分类 * **同步loader** 文件的读写,文件比较小的话可以使用同步的方式来进行内容的读取 * **异步loader** 事件的监听、发送http请求、以及文件的异步读写,可以使用异步的方式来执行 * **"Raw"loader** 后面这两个可以和上面的同步、异步loader同时使用,raw loader 的含义是原本的loader,loader在读取到文件的时候,webpack默认转换成utf-8的形式,但有时候我们并不希望它是一个字符串,比如说图片,但我们更希望它是原始的数据,是一个二进制的数据,放到一个buffer中,这时候使用raw loader,它就会是一个它原始的数据,是一个buffer数据。这种对于我们处理二进制数据还是很有用的 * **Pitching Loader** 如果我们多个loader一起使用的时候,默认是从数组的后面,从后到前来执行,后面一个loader处理完之后再传给前面的一个loader。但我们有时候希望,前一个loader在后一个loader处理之前,就拿到前面文件的原始数据,这个时候就可以使用pitching loader。pitching loader 在每个loader中会有一个pitching的阶段,在这个pitching阶段,它的执行顺序是从数组前面的pitching开始执行,逐个往数组后面的loader的pitching执行,等pitching执行完之后再返过来从数组loader的最后再向前执行。 # webpack加载器和插件开发 ### 基础开发 * ##### Hello world loader是一个JS模块,其模块导出的内容为一个函数。 ​ loader本质就是一个转换器,以函数的形式呈现,输入文件内容,输出转换之后的JavaScript语法支持的内容。输入的文件内容可以是一个utf-8的字符串,也可以是二进制的buffer数据。 ```sh mkdir loaders type nul>loaders/hello-world.js type nul>src/index.txt ``` webpack.config.js ```js const path = require("path"); module.exports = { entry: "./src/index.js", //entry可以是相对路径,也可以是绝对路径 //output只能是绝对路径 output: { path: path.resolve(__dirname, "dist"), //__dirname是指项目所在的路径,指定路径 filename: "bundle.js", //指定文件名 }, module: { //test要处理的文件类型,use 指定loader,use的查找方式是以当前webpack.config.js的路径为基准,去找js的文件 rules: [{ test: /.txt$/, use: "./loaders/hello-loader.js" }], }, }; ``` hello-loader.js ```js module.exports = function (content) { console.log(content, "content"); return content; }; ``` 直接执行webpack 内容是输出来了,但还是报错,报错我们导出的内容不对,原因是我们在进行模块的import或者require的时候,会要求我们读取的内容必须是一个js模块形式的文件(比如说必须是commonjs或者es模块的形式)。 hello-loader.js ```js module.exports = function (content) { //要求模块导出的内容,要求是一个字符串,而且格式为ESM或者是commonjs的格式 console.log(content, "content"); //ESM的格式 xxx可以字符串,也可以是对象或函数 如果是字符串,记得用引号包裹起来 //export default xxx //commonjs的格式 //module.exports = xxx // const result = ` // export default '${content}' // `; // 当然,上面我们export 字符串的时候,不建议手动加上引号,还是使用JSON.stringify const result = ` export default ${JSON.stringify(content)} `; return result; }; ``` 当然,上面我们export 字符串的时候,不建议手动加上引号,还是使用JSON.stringify * ##### 使用[loader runner](https://www.npmjs.com/package/loader-runner)进行调试 loader runner允许你在不安装webpack的情况下运行loaders * 作用: * 作为webpack的依赖包 * 进行loader的开发和调试 * 使用: webpack内部也是使用loader runner ```sh yarn add loader-runner ``` 之后可以不用安装webpack,执行webpack来运行调试你自己写的loader了 ```sh mkdir src mkdir loaders touch src/index.js touch loaders/my-loader.js touch run-loaders.js ``` run-loaders.js ```js const { runLoaders } = require("loader-runner"); const path = require("path"); const fs = require("fs"); //接收两个参数,第一个参数是配置项,第二个参数是一个回调函数, //回调函数的第一个参数是err,当我们loader内部发生错误的时候,可以查看到 //回调函数的第一个参数是result,是如果没有错误输出的结果 runLoaders( { //导入的资源路径,绝对路径 resource: path.resolve(__dirname, "./src/index.js"), //loaders所在的位置,绝对路径,数组 loaders: [path.resolve(__dirname, "./loaders/my-loader.js")], //context:loader执行时的上下文 this context: { minimize: true, }, //readResource:在读取被转换文件的时候,需要以什么函数来进行读取 readResource: fs.readFile.bind(fs), }, (err, result) => { if (err) { console.log(err, "err in loader"); return; } console.log(result, "result"); } ); ``` cacheable:true 被缓存,当我们这个函数输入没有变化的时候,下次就不会再处理,直接将内容返回 ```js module.exports = function (content) { console.log("=====[my-loader]this", this); return content; }; ``` * ##### loader上下文(this上的属性和方法) * this.context * this.data * this.cacheable * this.callback * this.async * this.emitFile my-context-loader.js ```js module.exports = function (content) { //this.context:属性, 被处理的文件所在的路径 console.log("======this.context======", this.context); //this.data:属性, loader有两个执行的阶段,一个是normal阶段,一个是pitching阶段 //pitching阶段在normal阶段之前执行,这两个阶段如何共享数据呢,就是通过这个this.data , //在pitching阶段可以给data属性传一个值,在normal阶段就可以拿到这个值 //如果loader没有定义pitching阶段的处理,该值为null console.log("======this.data======", this.data); //this.cacheable:函数,设置当前loader是否可以被缓存, //被缓存的loader,当文件没有发生变化时,直接返回之前的处理结果 //默认情况下,设置为true this.cacheable(false); //this.callback:函数,返回loader处理的结果,可能是成功的结果,也可能是失败的结果 //失败的结果通过this.callback的第一个参数传递,成果的结果就是通过第二个参数传递 //如果第一参数不为null,则第二个参数会被无视 //之前我们是通过return来返回loader处理完的结果,当然我们可以通过callback来返回处理完成的结果 // this.callback(new Error("loader出错了,亲~")); this.callback(null, `export default ${JSON.stringify(content)}`); // this.emitFile:函数,生成一个文件,到output指定的路径.(文件的复制,file-loader就是通过这个函数来实现的) //函数的第一个参数为路径,第二个参数为文件内容 this.emitFile("index-out.js", content); //this.async:函数,声明当前的loader为异步loader,并返回一个callback函数。 //通过调用callback函数,向webpack传递loader的处理结果 // return `export default ${JSON.stringify(content)}`; }; ``` * ##### 同步loader ```sh mv webpack-config.js webapck.config.js 重命名文件名 ``` 需求分析: 1.loader要处理的文件扩展名xxx.guoping 2.文件的内容,引用到两个去全局变量 guopingName,guopingUrl(不需要自己定义,由webpack为我们定义) 3.这两个变量的值,从json文件中提取出来的 4.导出JS语法正确的转换结果 5.在页面中执行导出的JS文件 index.js ```js //my-sync-loader.js import "./main.guoping"; ``` ```sh touch loader/var.json touch src/main.guoping ``` main.guoping ```guoping const h3 =document.createElement('h3') h3.innerHTML = guopingName const a = document.createElement('a') a.innerHTML = guopingUrl document.body.appendChild(h3) document.body.appendChild(a) ``` var.json ```json { "guopingName": "国萍", "guopingUrl": "国萍的地址" } ``` my-sync-loader.js ```js const path = require("path"); const fs = require("fs"); module.exports = function (content) { //同步loader:我们在loader函数的处理,都是以同步的方式进行的调用 //此时,我们就可以使用同步loader。默认情况下,我们的loader都是同步loader //注意:return和this.callback不可以同时使用 const jsonPath = path.resolve(__dirname, "var.json"); //readFileSync(同步) 文件比较小的时候,可以同步的方式读取,文件比较大的时候,可以采用异步读取的方式 const varResult = fs.readFileSync(jsonPath, "utf-8"); const varJson = JSON.parse(varResult); //1。读取json文件 //2、拼装定义的变量 //3.拼装后的结果导出 //4.同步的loader,直接使return就可以返回loader转换的结果,异步loader不能使用return,当然同步loader可以使用this.callback const result = ` var guopingName =${JSON.stringify(varJson.guopingName)}; var guopingUrl = ${JSON.stringify(varJson.guopingUrl)}; ${content} `; return result; }; ``` 同步loader要注意的几个点: 1、默认就是同步loader 2、同步loader里面不要有异步处理,比如说ajax请求和事件监听 * ##### 异步loader my-async-loader.js ```js const path = require("path"); const fs = require("fs"); module.exports = function (content) { //异步loader,必须通过this.callback来返回文件处理的结果,不能使用return //在异步loader中,使用异步方式调用this.callback之前,必须调用this.async // 这样webpack就不会把loader函数返回undefined作为loader的处理结果,而是等待callback被调用 this.async(); //关键第一步 let result = ""; setTimeout(() => { result = `export default "hello world"`; this.callback(null, result); }, 1000); //函数return的时候,setTimeout的回调还没有被调用,此时result并不是想要的结果 //所以loader中包含异步处理的时候,不能用return返回处理结果,只能使用callback //return result }; ``` my-async-loader2.js ```js const path = require("path"); const fs = require("fs"); module.exports = function (content) { //声明该loader是一个异步loader this.async(); //1。读取json文件 const jsonPath = path.resolve(__dirname, "var.json"); //readFileSync(同步) 文件比较小的时候,可以同步的方式读取,文件比较大的时候,可以采用异步读取的方式 const varResult = fs.readFile(jsonPath, "utf-8", (err, data) => { if (err) { console.log("===读取json文件失败===", err); this.callback(new Error("===读取json文件失败===")); return; } console.log(data); //2、拼装定义的变量 //3.拼装后的结果导出 const varJson = JSON.parse(data); const result = ` var guopingName =${JSON.stringify(varJson.guopingName)}; var guopingUrl = ${JSON.stringify(varJson.guopingUrl)}; ${content} `; this.callback(null, result); }); }; ``` * ##### 多个loader的执行顺序 * ##### raw loader my-raw-loader.js ```js module.exports = function (content) { //默认的webpack会将要处理的文件utf-8字符串的形式传递给loader //但有些时候,我们处理的是二进制流文件, //如果将其转换成字符串就会出错,此时我们需要得到的是这个文件的二进制流的buffer, //此时我们可以使用raw loader //使用方法:module.exports.exports.raw = true console.log(content); //如果是图片,就可以对这个buffer值进行文件的存储和拷贝 this.emitFile("summary.png", content); return ""; }; module.exports.raw = true; ``` * ##### pitching loader loader有两个阶段,一个是normal阶段,一个是pitching阶段。 先pitching阶段的有pitching阶段的loader的数组的先后顺序 后normal阶段的loader数组的后先顺序 ===loader-a的pitching阶段=== ===loader-b的pitching阶段=== ===loader-c的pitching阶段=== ===loader-c的normal阶段=== ===loader-b的normal阶段=== ===loader-a的normal阶段=== ```js const path = require("path"); module.exports = { entry: "./src/index.js", mode: "development", output: { path: path.resolve(__dirname, "dist"), filename: "bundle.js", }, module: { rules: [ { test: /.txt$/, use: [ "./loaders/loader-a.js", "./loaders/loader-b.js", "./loaders/loader-c.js", ], }, ], }, }; ``` loader-b.js ```js module.exports = function (content) { //loader的normal阶段 console.log("===loader-b的normal阶段==="); return content; }; //增加pitching阶段 // 函数:三个参数 //remainingReq 还剩下的请求 //previousReq 之前的请求 //data module.exports.pitch = function (remainingReq, previousReq, data) { console.log("===loader-b的pitching阶段==="); console.log("===loader-b的remainingReq===", remainingReq); console.log("===loader-b的previousReq===", previousReq); console.log("===loader-b的data===", data); }; ``` loader的配置,除了在webpack使用数组,还有一种使用内联的方式导入 webpack在解析完配置文件后,会将loader转换为下面格式 ===loader-b的remainingReq=== D:\ziliao\webpack-code2\loaders\loader-b.js!D:\ziliao\webpack-code2\loaders\loader-c.js!D:\ziliao\webpack-code2\src\index.txt ==loader-b的previousReq=== D:\ziliao\webpack-code2\loaders\loader-a.js ===loader-c的remainingReq=== D:\ziliao\webpack-code2\loaders\loader-c.js!D:\ziliao\webpack-code2\src\index.txt ==loader-c的previousReq=== D:\ziliao\webpack-code2\loaders\loader-a.js!D:\ziliao\webpack-code2\loaders\loader-b.js loader-a.js ```js module.exports = function (content) { //loader的normal阶段 console.log("===loader-a的normal阶段==="); console.log("===loader-a的normal[this.data]===", this.data); console.log("===loader-a的normal阶段的content===", content); return content; }; //增加pitching阶段 // 函数:三个参数 module.exports.pitch = function (remainingReq, previousReq, data) { console.log("===loader-a的pitching阶段==="); console.log("===loader-a的remainingReq===", remainingReq); console.log("===loader-a的previousReq===", previousReq); console.log("===loader-a的data===", data); }; ``` loader-b.js ```js module.exports = function (content) { //loader的normal阶段 console.log("===loader-b的normal阶段==="); return content; }; //增加pitching阶段 // 函数:三个参数 //remainingReq 当前loader还剩下的请求 //previousReq 当前loader之前的请求 //data 要跟loader normal阶段进行数据传递的对象。在data上追加的属性,在normal阶段进行访问 module.exports.pitch = function (remainingReq, previousReq, data) { console.log("===loader-b的pitching阶段==="); console.log("===loader-b的remainingReq===", remainingReq); console.log("===loader-b的previousReq===", previousReq); console.log("===loader-b的data===", data); data.guoping = "国萍"; //当一个pitching阶段函数有返回值,此时,正常loader的执行阶段就会被打破。 //当b的pitching阶段函数有返回值,就不会执行c的pitching、c的normal、b的normal, //直接到最后执行a的normal(只会执行它之前loader的normal),a的normal的content就是b的pitching阶段函数的返回值 return "loader b pitching result"; }; ``` loader-c.js ```js module.exports = function (content) { //loader的normal阶段 console.log("===loader-c的normal阶段==="); return content; }; //增加pitching阶段 // 函数:三个参数 module.exports.pitch = function (remainingReq, previousReq, data) { console.log("===loader-c的pitching阶段==="); console.log("===loader-c的remainingReq===", remainingReq); console.log("===loader-c的previousReq===", previousReq); console.log("===loader-c的data===", data); }; ``` 使用pitching的目的,想获取被请求数据的原始数据,在pitching阶段来通过data属性把处理的结果给normal 然后normal再去使用 * ##### 错误处理 ### Loader源码解读 * ##### [file-loader](https://www.npmjs.com/package/file-loader) 1. 概述 在require或者import文件时,得到该文件的url,并将文件拷贝到output指定的文件夹 hash值.文件。在拷贝的过程中进行了重命名,这个hash值是根据文件的内容生成的,如果文件的内容不发生变化, 这个hash值就不会变化,好处就是浏览器会把请求过的这个文件进行缓存,这样就不会重复请求。 2. 使用 ```sh $ npm install file-loader --save-dev ``` ```js import img from './file.png'; const pic = new Image(); pic.src = img; document.body.appendChild(pic) ``` ```json module.exports = { module: { rules: [ { test: /\.(png|jpe?g|gif)$/i, use: [ { loader: 'file-loader', //加上这个就代表你想要生成的文件名,而不是hash文件名 options:{ name:'20.png' } }, ], }, ], }, }; ``` 3. 下载源码 [github](https://github.com/webpack-contrib/file-loader) 4. 开始阅读 5. debug源码 6. 自己实现 * ##### [style-loader](https://www.npmjs.com/package/style-loader) 1. 概述 将css注入到DOM中(接受的是css-loader的处理结果) 2. 使用 ```sh $ npm install style-loader ``` ```css body { background: red; } ``` ```js import './index.css' ``` ```js module.exports = { ... module: { rules: [ { test: /\.css$/, loader: ['style-loader', 'css-loader'] } ] } } ``` 3. 下载源码 4. 开始阅读 5. debug源码 6. 自己实现 ```js //下面的js代码是在./src/index.js导入./src/index.css时,执行的 //也就是说当bundle.js被导入到html时,下面代码会执行 import API from "!../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js"; import domAPI from "!../node_modules/style-loader/dist/runtime/styleDomAPI.js"; import insertFn from "!../node_modules/style-loader/dist/runtime/insertBySelector.js"; import setAttributes from "!../node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js"; import insertStyleElement from "!../node_modules/style-loader/dist/runtime/insertStyleElement.js"; import styleTagTransformFn from "!../node_modules/style-loader/dist/runtime/styleTagTransform.js"; import content, * as namedExport from "!!../node_modules/css-loader/dist/cjs.js!./index.css"; var options = {}; //转换css-loader的结果,到style options.styleTagTransform = styleTagTransformFn; //设置标签 options.setAttributes = setAttributes; //将style节点插入到head options.insert = insertFn.bind(null, "head"); options.domAPI = domAPI; //插入style元素 options.insertStyleElement = insertStyleElement; //所有的处理入口为这个API函数 var update = API(content, options); export * from "!!../node_modules/css-loader/dist/cjs.js!./index.css"; export default content && content.locals ? content.locals : undefined; ``` style-loader源码解读总结: 1、在pitching阶段进行的处理(关于style-loader为什么要把loader的处理放在pitching阶段呢,是因为 1)style-loader在css-loader之后运行,在pitching阶段可以避免使用出css-loader 函数导出的字符串 ) 2、在pitching阶段首先读取了用户传递进来的options 3、然后根据options对导出内容(模板字符串)进行拼装 4、拼装完成后,导出(runtime的内容) 5、bundle.js被导入到html中时,会执行上面的运行时内容 ['my-style-loader','css-loader'] pitching阶段没有返回值: my-style-loader pitching —>css loader pitching —>css loader normal —>my-style-loader normal pitching阶段有返回值: //如果在第一个loader的pitching阶段返回来内容,默认情况下,后续的loader的所有处理都不会执行 //所以要在该函数中,处理所有的转换流程 my-style-loader pitching —>webpack 处理 为什么官方的style-loader在pitching阶段处理,它直接通过remainRequest,直接把css-loader处理的结果拿过来了 实现一个在pitching阶段的my-style-loader my-style-loader.js ```js const getImportHandleCssCode = require("./utils"); module.exports = function (content) {}; module.exports.pitch = function (remainingReq, previousReq, data) { //如果在第一个loader的pitching阶段返回来内容,默认情况下,后续的loader的所有处理都不会执行 //所以要在该函数中,处理所有的转换流程 console.log(remainingReq); return ` // 将css-loader处理css文件的导入,使用内联的方式(在导入文件时,直接指定loader) // 在webpack.config.js中配置的css处理,与下面内联方式处理css相冲突 // 解决冲突的办法:在请求的最前面加上!,忽略webpack.config.js中的配置 import cssList from '!../node_modules/css-loader/dist/cjs.js!D:/ziliao/webpack-code/src/index.css'; // import handleCss from '../loaders/runtime/handle-css.js' // 这个css是一个二维数组,css:[id,cssText] // import handleCss from '../loaders/runtime/handle-css.js' ${getImportHandleCssCode(this)} //将下面的循环提取到一个loader的一个函数中 // for (let css of cssList){ // const style =document.createElement('style') // document.head.appendChild(style) // style.appendChild(document.createTextNode(css[1])) // } handleCss(cssList) export default cssList `; }; ``` utils.js ```js const path = require("path"); function getImportHandleCssCode(loaderContext) { //返回handle-css.js所在的路径,与被处理css文件所在的路径之间的相对路径 //找到handle-css.js的路径 const handleCssPath = path.resolve(__dirname, "runtime/handle-css.js"); console.log(handleCssPath, "handleCssPath"); //找到css的路径:loaderContext.context //使用loaderContext.utils.contextify来计算相对路径 const relativePath = loaderContext.utils.contextify( loaderContext.context, handleCssPath ); console.log( relativePath, "relativePath", `import handleCss from ${JSON.stringify(relativePath)}` ); return `import handleCss from ${JSON.stringify(relativePath)}`; } module.exports = getImportHandleCssCode; ``` runtime/handle-css.js ```js function handleCss(cssList) { for (let css of cssList) { const style = document.createElement("style"); document.head.appendChild(style); style.appendChild(document.createTextNode(css[1])); } } module.exports = handleCss; ``` webpack.config.js ```js const path = require("path"); module.exports = { entry: "./src/index.js", //entry可以是相对路径,也可以是绝对路径 //output只能是绝对路径 output: { path: path.resolve(__dirname, "dist"), //__dirname是指项目所在的路径,指定路径 filename: "bundle.js", //指定文件名 }, mode: "development", module: { //test要处理的文件类型,use 指定loader,use的查找方式是以当前webpack.config.js的路径为基准,去找js的文件 rules: [ { test: /\.css$/, use: ["./loaders/my-style-loader.js", "css-loader"], }, ], }, }; ``` 在normal阶段实现一个style-loader my-style-loader2.js ```js const getImportHandleCssCode = require("./utils"); module.exports = function (content) { console.log(content); content = content.replace("export default ___CSS_LOADER_EXPORT___;", ""); return ` //将css-loader的导出内容,原封不动的返回了 ${content} //中间插入将css-loader的导出结果,注入到html的过程 const cssList = ___CSS_LOADER_EXPORT___; ${getImportHandleCssCode(this)} handleCss(cssList) export default ___CSS_LOADER_EXPORT___ `; }; ``` 1、在pitching阶段,直接请求了css文件,通过内联的方式,指定了css-loader,直接拿到css-loader处理后的结果 2、将处理的结果(list)遍历,放在dom树上 3、将放置dom树的过程,封装到函数中 4、处理import时的路径问题,并将import的生成代码,封装到函数中 5、在normal阶段,来处理了style-loader的转换过程 pitching阶段的处理相对复杂且抽象,好处是:不依赖于css-loader内部的导出代码。 normal阶段,严重依赖css-loader的导出代码,这种强耦合的方式,不适用于在第三方node包中使用 需求: 导入一个pdf文件, 1、生成对应的图片(每一页对应一张图片) 2、返回生成图片所对应的路径 ```sh npm i loader-utils -D npm i pdf2pic -D ``` 遇到一个问题,坑死我了。解决了半天,原来是在使用 Ghostscript **9.52**和GraphicsMagick,用vscode要以管理员权限运行!!! pdf2img-loader.js ```js const { interpolateName } = require("loader-utils"); const schema = require("./options.json"); const path = require("path"); const { fromBuffer } = require("pdf2pic"); const createFolderIfNoExist = require("./utils-pdf2pic"); module.exports = async function (content) { //声明为异步loader,因为转换pdf到img的处理是异步函数 this.async(); const options = this.getOptions(schema); // console.log(options, "options"); //生成被转换后图片的名字 let name = options.name || "[contenthash]-[name].pdf"; name = interpolateName(this, name, { content, }); //决定被转换图片的目标路径 //webpack的输出路径是this._compiler.outputPath let outputPath = this._compiler.outputPath; if (options.outputPath) { outputPath = path.resolve(outputPath, options.outputPath); } //使用第三方的库 将pdf转换为图片 pdf2pic const pdf2imgOptions = { density: 100, saveFilename: name, savePath: outputPath, format: "png", width: 2550, height: 3000, }; //check outputPath是否存在,如果不存在,则创建该文件夹 createFolderIfNoExist(outputPath); const result = await fromBuffer(content, pdf2imgOptions).bulk(-1); console.log(result); const paths = result.map((img) => { return this.utils.contextify(this._compiler.outputPath, img.path); }); console.log(paths, "paths"); this.callback(null, `export default ${JSON.stringify(paths)}`); }; module.exports.raw = true; ``` webpack.config.js ```js const path = require("path"); module.exports = { entry: "./src/index.js", //entry可以是相对路径,也可以是绝对路径 //output只能是绝对路径 output: { path: path.resolve(__dirname, "dist"), //__dirname是指项目所在的路径,指定路径 filename: "bundle.js", //指定文件名 }, mode: "development", module: { //test要处理的文件类型,use 指定loader,use的查找方式是以当前webpack.config.js的路径为基准,去找js的文件 rules: [ { test: /\.pdf$/, use: { loader: "./loaders/pdf2img-loader.js", options: { outputPath: "pdfImages", }, }, }, ], }, }; ``` webpack的 file-loader和style-loader都有一个固定的模板,这个是webpack-default脚手架生成的 ```sh yarn init -y //package.json "script":{ "defaults":"webpack-defaults" } yarn defaults ```