Open maicFir opened 2 years ago
webpack的loader本质上是一个导出的函数,loader runner会调用该函数,在loader函数内部,this的上下文指向是webpack,通常loader内部返回的是一个string或者Buffer。当前loader返回的结果,会传递给下一个执行的loader
webpack
loader
this
string
Buffer
今天一起学习一下webpack5中的loader,让我们进一步加深对webpack的理解
webpack5
正文开始...
首先我们看下,通常情况下loader是怎么使用的
module.exports = { ... module: { rules: [ { test: /\.js$/, use: [ { loader: 'babel-loader', options: { presets: ['@babel/env'] } }, ] } ] }, }
在module.rules下,use是一个数组,数组中是可以有多个loader 默认情况loader:'babel-loader'会从node_modules中的lib/index.js中执行内部的_loader函数,然后通过内部@babel/core这个核心库对源代码进行ast转换,最终编译成es5的代码
module.rules
use
loader:'babel-loader'
node_modules
lib/index.js
_loader
@babel/core
ast
es5
现在需要自己写个loader,参考官方文档writing loader
我们在新建一个loader目录,然后新建test-loader
test-loader
module.exports = function (source) { console.log('hello world'); return source; };
在rules中我们修改下
rules
const path = require('path'); module.exports = { module: { rules: [ { test: /\.js$/, use: [ { loader: path.resolve(__dirname, 'loader/test-loader.js') } ] } ] } };
当我运行npm run start时,我们会发现loader中加载的自定义test-loader已经触发了。
npm run start
但是官方提供另外一种方式
在resolveLoader中可以给加载loader快捷的注册路径,这样就可以像官方一样直接写test-loader了,这个是文件名,文件后缀名默认可以省略。
resolveLoader
module.exports = { module: { rules: [ { test: /\.js$/, use: [ { loader: 'test-loader' } ] } ] }, resolveLoader: { modules: ['node_modules', './loader'] } };
我们知道loader中可以设置options,而在自定义loader是如何获取options的参数呢?
options
官方提供了loader的一些接口api-loader
获取 loader 传过来的options
// loader/test-loader.js module.exports = function (source) { const options = this.getOptions(); console.log(options); console.log('hello world'); return source; };
我们可以看到以下options传入的参数
... use: [ { loader: 'test-loader', options: { name: 'Maic', age: 18 } } ]
在官方提供了一个简单的例子,主要是用schema-utils验证options传入的数据格式是否正确
schema-utils
安装schema-utils
npm i schema-utils --save-dev
在test-loader中引入schema-utils
// 定义schema字段数据类型 const schema = { type: 'object', properties: { name: { type: 'string', description: 'name is require string' }, age: { type: 'number', description: 'age is require number' } } }; // 引入validate const { validate } = require('schema-utils'); module.exports = function (source) { // 获取loader传入的options const options = this.getOptions(); validate(schema, options); console.log(options); console.log('hello world'); return source; };
当我把rules中options修改类型时
{ use: [ { loader: 'test-loader', options: { name: 'Maic', age: '18' } } ]; }
运行npm run start 直接提示报错了,相当于validate这个方法帮我们验证了loader传过来的options,如果传入的options类型不对,那么直接报错了,我们可以用此来检验参数的类型。
validate
babel-loader
在之前的所有项目中,我们都会使用这个babel-loader,那我们能不能自己实现一个自定义的babel-loader呢?
首先我们要确定,babel转换es6,我们需要安装依赖两个插件,一个是@babel/core核心插件,另一个是@babel/preset-env预设插件
babel
es6
@babel/preset-env
修改rules,我们现在使用一个test-babel-loader插件
test-babel-loader
... { module: { rules: [ { test: /\.js$/, use: [ { loader: 'test-babel-loader', options: { presets: ['@babel/preset-env'] // 预设 } }, { loader: 'test-loader', options: { name: 'Maic', age: 18 } } ] } ] }, resolveLoader: { modules: ['node_modules', './loader'] }, }
修改test-babel-loader
// 引入@babel/core核心库 const babelCore = require('@babel/core'); module.exports = function (content) { // 获取options const options = this.getOptions(); // 必须异步方式 const callback = this.async(); // 转换es6 babelCore.transform(content, options, (err, res) => { if (err) { callback(err); } else { callback(null, res.code); } })
在index.js中写入一些 es6 代码
index.js
const sayhello = () => { const str = 'hello world'; console.log(str); }; sayhello();
然后在package.json写入打包命令
package.json
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "webpack server --port=8081", "build": "webpack" },
我们执行npm run build
npm run build
test-loader与test-babel-loader都会执行,而且生成的main.js源代码的es6已经被转换成es5了。
main.js
markdown-loader
首先我们在loader目录下新建一个markdown-loader.js
markdown-loader.js
// markdown-loader.js module.exports = function (content) { console.log(content); return content; };
然后在rules中加入自定义loader
{ test: /\.md$/, loader: 'markdown-loader' } ...
我们需要在src/index.js中引入md文件
src/index.js
md
import md from '../doc/index.md'; const sayhello = () => { const str = 'hello world'; console.log(str); }; sayhello();
我们运行npm run build
已经获取到了doc/index.md的内容了
doc/index.md
在 loader 中我需要解析md的内容,此时我们需要借助一个第三方的md解析器marked
npm i marked --save-dev
详细使用文档参考markedjs
const { marked } = require('marked'); module.exports = function (content) { // 解析md const ret = marked.parse(content); console.log(ret); return ret; };
此时依然报错,错误提示You may need an additional loader to handle the result of these loaders.
You may need an additional loader to handle the result of these loaders.
所以需要解析html,那么此时需要另外一个loader来解决,html-loader
html
html-loader
npm i html-loader --save-dev
然后添加html-loader
{ test: /\.md$/, use: ['html-loader', 'markdown-loader'] }
我们在看下index.js
import md from '../doc/index.md'; console.log(md); const sayhello = () => { const str = 'hello world'; console.log(str); }; sayhello();
我们在index.js打印引入的md就一段html-loader转换过的最终代码
import md from '../doc/index.md'; const sayhello = () => { const str = 'hello world'; console.log(str); }; sayhello(); const renderMd = () => { const app = document.getElementById('app'); const div = document.createElement('div'); div.innerHTML = md; app.appendChild(div); }; renderMd();
我么最终就看到md文件就成功通过我们自己写的 loader 给转换了
本质上就是将md转换成html标签,然后再渲染到页面上了
了解loader的本质,实际上就是一个导出的函数,该函数只能返回字符串或者Buffer,内部提供了很多钩子,比如getOptions可以获取loader中的options
字符串
getOptions
loader的执行顺序是从下往上或者从右往左,在后一个 loader 中的content是前一个loader返回的结果
content
loader 有两种类型,一种是同步this.callback,另一种是异步this.async
this.callback
this.async
了解自定义babel转换,通过@bable/core,@babel/preset-env实现 es6 转换
@bable/core
实现了一个自定义markdown转换器,主要是利用marked.js这个对md文件转换成 html,但是html标签进一步需要html-loader
markdown
marked.js
本文示例code-example
今天一起学习一下
webpack5
中的loader
,让我们进一步加深对webpack
的理解正文开始...
开始一个
loader
首先我们看下,通常情况下
loader
是怎么使用的在
module.rules
下,use
是一个数组,数组中是可以有多个loader
默认情况loader:'babel-loader'
会从node_modules
中的lib/index.js
中执行内部的_loader
函数,然后通过内部@babel/core
这个核心库对源代码进行ast
转换,最终编译成es5
的代码现在需要自己写个
loader
,参考官方文档writing loader我们在新建一个
loader
目录,然后新建test-loader
在
rules
中我们修改下当我运行
npm run start
时,我们会发现loader
中加载的自定义test-loader
已经触发了。但是官方提供另外一种方式
在
resolveLoader
中可以给加载loader
快捷的注册路径,这样就可以像官方一样直接写test-loader
了,这个是文件名,文件后缀名默认可以省略。我们知道
loader
中可以设置options
,而在自定义loader
是如何获取options
的参数呢?官方提供了
loader
的一些接口api-loadergetOptions
获取 loader 传过来的
options
我们可以看到以下
options
传入的参数在官方提供了一个简单的例子,主要是用
schema-utils
验证options
传入的数据格式是否正确安装
schema-utils
在
test-loader
中引入schema-utils
当我把
rules
中options
修改类型时运行
npm run start
直接提示报错了,相当于validate
这个方法帮我们验证了loader
传过来的options
,如果传入的options
类型不对,那么直接报错了,我们可以用此来检验参数的类型。自定义
babel-loader
在之前的所有项目中,我们都会使用这个
babel-loader
,那我们能不能自己实现一个自定义的babel-loader
呢?首先我们要确定,
babel
转换es6
,我们需要安装依赖两个插件,一个是@babel/core
核心插件,另一个是@babel/preset-env
预设插件修改
rules
,我们现在使用一个test-babel-loader
插件修改
test-babel-loader
在
index.js
中写入一些 es6 代码然后在
package.json
写入打包命令我们执行
npm run build
test-loader
与test-babel-loader
都会执行,而且生成的main.js
源代码的es6
已经被转换成es5
了。写一个自定义
markdown-loader
首先我们在
loader
目录下新建一个markdown-loader.js
然后在
rules
中加入自定义loader
我们需要在
src/index.js
中引入md
文件我们运行
npm run build
已经获取到了
doc/index.md
的内容了在 loader 中我需要解析
md
的内容,此时我们需要借助一个第三方的md
解析器marked详细使用文档参考markedjs
我们运行
npm run build
此时依然报错,错误提示
You may need an additional loader to handle the result of these loaders.
所以需要解析
html
,那么此时需要另外一个loader
来解决,html-loader
然后添加
html-loader
我们在看下
index.js
我们在
index.js
打印引入的md
就一段html-loader
转换过的最终代码我么最终就看到
md
文件就成功通过我们自己写的 loader 给转换了本质上就是将
md
转换成html
标签,然后再渲染到页面上了总结
了解
loader
的本质,实际上就是一个导出的函数,该函数只能返回字符串
或者Buffer
,内部提供了很多钩子,比如getOptions
可以获取loader
中的options
loader
的执行顺序是从下往上或者从右往左,在后一个 loader 中的content
是前一个loader
返回的结果loader 有两种类型,一种是同步
this.callback
,另一种是异步this.async
了解自定义
babel
转换,通过@bable/core
,@babel/preset-env
实现 es6 转换实现了一个自定义
markdown
转换器,主要是利用marked.js
这个对md
文件转换成 html,但是html
标签进一步需要html-loader
本文示例code-example