Webpack自定义Loader
1. 认识自定义Loader
-
Loader是用于对模块的源代码进行转换(处理),之前我们已经使用过很多Loader,比如css-loader、style-loader、babel-loader等
-
自定义自己的Loader:
- Loader本质上是一个导出为函数的JavaScript模块
- loader runner库会调用这个函数,然后将上一个loader产生的结果或者资源文件传入进去
-
编写一个
my-loader01.js
模块这个函数会接收三个参数:- content:资源文件的内容
- map:sourcemap相关的数据
- meta:一些元数据
module.exports = function(content) { console.log(content) return content }
-
配置resolveLoader属性
resolveLoader: { modules: ['node_modules', './my-loaders'] },
2. Loader的执行顺序
-
从后向前、从右向左的
-
为什么loader的执行顺序是相反的:
- run-loader先优先执行PitchLoader,在执行PitchLoader时进行loaderIndex++
- run-loader之后会执行NormalLoader,在执行NormalLoader时进行loaderIndex--
module.exports = function(content) { console.log('1', content) return content } module.exports.pitch = function() { console.log('loader pitch 01') }
-
那么,能不能改变它们的执行顺序呢?
- 可以拆分成多个Rule对象,通过enforce来改变它们的顺序
-
enforce一共有四种方式:
- 默认所有的loader都是normal
- 在行内设置的loader是inline(
import 'loader1!loader2!./test.js'
) - 也可以通过enforce设置 pre 和 post
-
在Pitching和Normal它们的执行顺序分别是:
- post, inline, normal, pre
- pre, normal, inline, post
const path = require('path') module.exports = { mode: 'development', entry: './src/main.js', output: { clean: true, path: path.resolve(__dirname, './build'), filename: '[name]_bundle.js' }, resolveLoader: { modules: ['node_modules', './my-loaders'] }, module: { rules: [ // 执行顺序:3 -> 1 -> 2 { test: /\.js$/, use: 'my_loader01' }, { test: /\.js$/, use: 'my_loader02', enforce: 'post' }, { test: /\.js$/, use: 'my_loader03' } ] } }
3. Loader的同步和异步
3.1 同步的Loader
-
什么是同步的Loader呢?
- 默认创建的Loader就是同步的Loader
- 这个Loader必须通过 return 或者 this.callback 来返回结果,交给下一个loader来处理
- 通常在有错误的情况下,也会使用 this.callback
-
this.callback的用法如下:
- 第一个参数必须是
Error
或者null
- 第二个参数是一个
string
或者Buffer
module.exports = function(content) { // this 绑定一个对象 const callback = this.callback setTimeout(() => { console.log('3', content) return content + 'aaa' }, 2000); // 参数一:错误信息 // 参数二:传递给下一个loader的内容 callback(null, '哈哈哈哈') }
- 第一个参数必须是
3.2 异步的Loader
-
什么是异步的Loader呢?
- 有时候使用Loader时会进行一些异步的操作
- 希望在异步操作完成后,再返回这个loader处理的结果
- 这个时候就要使用异步的Loader了
module.exports = function(content) { // this 绑定一个对象 const callback = this.async() // 执行异步操作 setTimeout(() => { console.log('3', content) callback(null, content + 'aaa') }, 2000); }
4. Loader参数获取和校验
-
在使用loader时,传入参数
module.exports = { module: { rules: [ { test: /\.js$/, use: [ // 给loader传递参数 { loader: 'my_loader04', options: { name: 'lucy', age: 17 } } ] }, ] } }
-
获取参数
- 早期时,需要单独使用loader-utils(webpack开发)的库来获取参数
npm install loader-utils -D
- 目前可以通过this.getOptions()直接获取参数
- 早期时,需要单独使用loader-utils(webpack开发)的库来获取参数
-
校验参数
- 通过一个webpack官方提供的校验库 schema-utils
npm install schema-utils -D
- 编写校验json
{ "type": "object", "properties": { "name": { "type": "string", "description": "请输入名称, 并且是string类型" }, "age": { "type": "number", "description": "请输入年龄, 并且是number类型" } } }
-
loader
const { validate } = require('schema-utils') const loader04Schema = require('./schema/loader04_schema.json') module.exports = function(content) { // 1.获取使用loader时,传递进来的参数 const options = this.getOptions() console.log('options: ', options) // 2.校验参数是否符合规则 validate(loader04Schema, options) console.log('4', content) return content }
5. 自定义babel-loader
-
使用babel核心功能
npm install @babel/core -D
-
配置options
const path = require('path') module.exports = { mode: 'development', devtool: false, entry: './src/main.js', output: { clean: true, path: path.resolve(__dirname, './build'), filename: '[name]_bundle.js' }, resolveLoader: { modules: ['node_modules', './my-loaders'] }, module: { rules: [ { test: /\.jsx?$/, use: { loader: 'my-babel-loader', options: { // plugins: [ // '@babel/plugin-transform-arrow-functions' // ], presets: [ '@babel/preset-env' ] } } } ] } }
-
校验
-
my-babel-loader
const babel = require('@babel/core') module.exports = function(content) { // 使用异步loader const callback = this.async() // 获取options let options = this.getOptions() // 入股webpack没有配置,使用babel.config.js配置文件 if(!Object.keys(options).length) { options = require('../babel.config') } // console.log('options: ', options) // 使用babel转换代码 babel.transform(content, options, (err, res) => { if(err) { callback(err) } else { callback(null, res.code) } }) }
6. 自定义md-loader
-
安装需要的工具
- marked:识别md文件并解析
npm install marked -D
- hightlight.js:代码关键字高亮
npm install highlight.js -D
- marked:识别md文件并解析
-
my-md-loader.js
const { marked } = require('marked') const hljs = require('highlight.js') module.exports = function(content) { // 让marked库解析语法的时候将代码高亮 marked.setOptions({ highlight: function(code, lang) { return hljs.highlight(lang, code).value } }) // 将md语法转换成html结构 const htmlContent = marked(content) // console.log('htmlContent: ', htmlContent) // 返回的结果必须是模块化的内容 const innerContent = '`' + htmlContent + '`' const moduleContent = `var code = ${innerContent}; export default code` return moduleContent }
-
webpack配置
const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { mode: 'development', devtool: false, entry: './src/main.js', output: { clean: true, path: path.resolve(__dirname, './build'), filename: '[name]_bundle.js' }, resolveLoader: { modules: ['node_modules', './my-loaders'] }, module: { rules: [ { test: /\.jsx?$/, use: { loader: 'my-babel-loader', } }, { test: /\.md$/, use: 'my-md-loader' }, { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] } ] }, plugins: [ new HtmlWebpackPlugin() ] }
转载自:https://juejin.cn/post/7179535301264539707