loader 是导出为一个函数的 node 模块。该函数在 loader 转换资源的时候调用。给定的函数将调用 loader API,并经过 this 上下文访问。
Webpack的配置离不来 loader
,官方也有关于如何编写一个loader的文档介绍,这篇文章会经过手写一些常见的loader
,加深对loader的认识,提升工做中的开发效率。css
loader
是一个函数,接受匹配到的文件资源字符串和SourceMap
,咱们能够经过修改文件内容的字符串返回给下个一loader
处理:node
module.exports = function(source,map){ return source; }
为了方便咱们编写loader
,咱们先准备好webpack
环境:webpack
生成一份 package.json
:web
npm init -y
安装webpack
:npm
npm install webpack webpack -D
建立webpack.config.js
文件,并输入如下内容:json
// webpack.config.js const path = require('path') module.exports = { mode: 'development', entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') } }
package.json
加入scripts
命令:babel
"scripts": { "build": "webpack", "dev": "webpack --watch" },
咱们知道,webpack
默认会到 node_modules
里面找对应的loader
,这样不方便咱们调试,咱们能够经过给webpack.config.js
添加resolveLoader
属性,将loader
指向咱们建立的loaders
文件夹app
... resolveLoader: { modules: [ path.resolve(__dirname, "node_modules"), path.resolve(__dirname, "./loaders"), ], } ...
建立loaders
文件夹,里面将存放咱们本身编写的loader
:less
mkdir loaders
另外经过resolveLoader.alias
也能配置loader
别名:异步
resolveLoader: { alias: { loader1: resolve(__dirname, "./loaders/loader1.js"), loader2: resolve(__dirname, "./loaders/loader2.js"), loader3: resolve(__dirname, "./loaders/loader3.js"), }, }, module:{ rules:[ { test:/\.(js)$/, use:["loader1.js","loader2.js","loader3.js] } ] }
先写一个简单的loader
练练手,前面说到loader
能够替换目标资源文件的内容,这里我要把项目里面的console.log
都干掉,毕竟项目上线的时候控制台出现调试内容不合适。
在loaders
文件夹下新建cleanlog-loader.js
,输入下面内容:
module.exports = function(source){ return source.replace(/console\.log\(.*\);?\n/g, ''); }
到webpack.config.js
添加配置:
module:{ rules:[{ test: /\.(js)$/, use:"cleanlog-loader" }] }
src
文件夹下新建index.js
写点console
内容,而后控制台执行npm run build
,能够发现编译后的文件dist/bundle.js
已经没有console
信息了。
banner-loader
能够在脚本文件添加注释信息,配置方式以下:
module:{ rules:[{ test: /\.(js)$/, use:{ loader:"banner-loader", options:{ text:"/**** build from chenwl ****/", } } }] }
这里有两个地方须要考虑:
loader-utils
schema-utils
在 loaders
文件夹下建立banner-loader.js
,输入下面内容:
const fs = require("fs"); const {resolve} = require("path"); const loaderUtils = require("loader-utils"); const { validate } = require("schema-utils"); module.exports = function (source) { // 获取配置参数 let options= loaderUtils.getOptions(this); let schema = { type: "object", properties: { text: {type: "string"} } } // 校验参数是否正确 validate(schema, options,"banner-loader") return `${options.text} ${source}`; }
固然也能够经过读取文件内容写入,新增配置参数 filename
,对模板文件进行读取:
if(options.filename){ // 依赖某个文件变化,作到实时更新; this.addDependency(path.resolve(__dirname, `../${options.filename}`)) return fs.readFileSync(options.filename, "utf-8") + source; }
利用addDependency
,若是目标文件发生变化,能够在观察模式(watch mode)下重编译,用npm run dev
启动并修改filename
对应的文件试试
babel-loader
依赖@babel-core
和@babel/preset-env
,经过npm先安装:
npm install @babel-core @babel/preset-env -D
webpack-config.js
添加rules:
{ test: /\.(js)$/, use: [ { loader: "babel-loader", options: { presets: ["@babel/preset-env"], }, }, ] }
能够经过this.async
方法在loader中编写异步代码:
// babel-loader.js const loaderUtils = require("loader-utils"); const babel = require("@babel/core"); module.exports = function(source){ const options = loaderUtils.getOptions(this); const cb = this.async(); // 异步函数 babel.transform(source,{ ...options, sourceMaps:true },function(err,result){ cb(err, result.code) }) }
// less-loader let less = require("less"); module.exports = function (source) { let cssStr = ""; // 用less转成css less.render(source,function(error,result) { if(!error){ cssStr = result.css } }); return cssStr }
// style-loader module.exports = styleLoader(source) { // js 字符串,生成style标签插入到模板文件中 let code = ` let styleEl = document.createElement("style"); styleEl.innerHTML = ${JSON.stringify(source)}; document.head.appendChild(styleEl); `; return code.replace(/\/n/,""); }
咱们知道 webpack
是识别不了js之外的其它文件的,因此file-loader
须要设置loader.raw = true
,让 loader 知道如今处理的是二进制的内容:
const loaderUtils = require("loader-utils"); function fileLoader(source) { // interpolate 插值 let fileUrl = loaderUtils.interpolateName(this, "[hash].[ext]", { content: source, }); this.emitFile(fileUrl,source); // 转换后的Buffer最终是要被插入到页面中,返回类型只能是 buffer 或 string // fileUrl 记得加引号,否则会报错哦 return `module.exports = '${fileUrl}'` } // loader 处理的是二进制的内容 fileLoader.raw = true; module.exports = fileLoader;
url-loader
目的是将小图转成base64编码,不然就用file-loader
处理:
// url-loader.js const loaderUtils = require("loader-utils"); const mime = require("mime"); function urlLoader(source) { let {limit} = loaderUtils.getOptions(this); if(limit > source.length){ let code = `data:${mime.getType(this.resourcePath)};base64,${source.toString('base64')}` return `module.exports = "${code}"` }else{ return require("./file-loader").call(this, source) } } urlLoader.raw = true; module.exports = urlLoader;