loader使用
- loader 是一个导出为函数的 JS 模块,接收上游产出的结果或者资源文件做为参数
- compiler 拿到最后一个 loader 产出的结果,这个结果应该是一个 string 或者 buffer(被转为string)
- 从入口文件出发,调用所有配置中的 loader 对模块进行编译,找到依赖模块,递归到所有依赖都被处理
module.exports = {
mode: 'development',
devtool: false,
entry: './src/index.js',
output: {
filename: 'build.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader:'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
]
}
]
}
}
配置resolveLoader
module.exports = {
mode: 'development',
devtool: false,
resolveLoader: {
modules: [path.resolve(__dirname, 'loaders'), 'node_modules']
},
entry: './src/index.js',
output: {
filename: 'build.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
use: [
{
// loader: path.resolve(__dirname, 'loaders/test-loader.js')
loader: 'test-loader'
}
]
}
]
}
}
loader组成
一个完整的 loader 有两部分组成 pitchLoader 与 normalLoader
function loader(source) {
console.log('loader2执行了------')
return source + '//loader2'
}
loader.pitch = function (data) {
console.log('loader2-pitch')
return '2222'
}
module.exports = loader
Normal loader
- content 源文件的内容
- [map] 可以被 https://github.com/mozilla/source-map 使用的 SourceMap 数据
[meta] meta 数据,可以是任何内容
function webpackLoader(content, map, meta) {
// 你的webpack loader代码
}
module.exports = webpackLoader;
Pitching Loader
remainingRequest 剩余请求
- precedingRequest 前置请求
- data 数据对象
/**
* @remainingRequest 剩余请求
* @precedingRequest 前置请求
* @data 数据对象
*/
function (remainingRequest, precedingRequest, data) {
// some code
};
参数作用
有如下三个loader,在b-loader上测试pitch参数 ```typescript // webpack.config.js rules: [ {
}, {test: /\.js$/,
use: ['a-loader'],
}, {test: /\.js$/,
use: ['b-loader'],
}, ]test: /\.js$/,
use: ['c-loader'],
// b-loader function loader(content) { console.log(‘开始执行——-b’); console.log(this.data); // { dnv :’weiyafei’} return content + ‘\nconsole.log(b);’; } loader.pitch = function (remainingRequest, precedingRequest, data) { console.log(‘b-loader————pitch’); console.log(remainingRequest); // H:.….\webpack\loader\loader\c-loader.js!H:.….\webpack\loader\src\index.js 代指下一个loader console.log(precedingRequest); // H:.….\webpack\loader\loader\a-loader.js 代指上一个loader data.env = ‘weiyafei’; }; module.exports = loader;
<a name="bSDVz"></a>
#### 总结
1. remainingRequest 剩余请求 (代指下一个loader位置和调用chunk)
1. precedingRequest 前置请求 (代指上一个loader)
1. data 数据对象 传递数据,可以再nomal loader 中用 this.data 接受
<a name="pCnYS"></a>
#### 作用
当某个 Pitching Loader 返回非 undefined 值时,就会实现熔断效果(会跳过本身的normal loader 和后续pitch loader、normal loader)
<a name="849821da"></a>
## loader分类
> 本质上所有的 loader 都是一样的,但是可以通过 enforce 或者不同的使用方式来区分它们
1. 普通 loader : 没有任务配置正常使用
1. 前置 loader : 通过 enforce 配置 pre
1. 后置 loader : 通过 enforce 配置 post
1. 行内 loader : 通过 !分割符,直接在行内进行使用
1. 上述的 loader 如果区分之后在执行上也存在固定的先后顺序:pre normal inline post
```javascript
module.exports = {
mode: 'development',
devtool: false,
resolveLoader: {
modules: [path.resolve(__dirname, 'loaders'), 'node_modules']
},
entry: './src/index.js',
output: {
filename: 'build.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
use: ['normal-loader']
},
{
test: /\.js$/,
enforce: 'post',
use: ['post-loader']
},
{
test: /\.js$/,
enforce: 'pre',
use: ['pre-loader']
}
]
}
}
loader的执行顺序
normal 从后向前、从右向左的, pitch loader 是从左向右,从上到下
pitch 熔断效果
如何调试
新建run.js 文件,正常断点调试loader即可
// 引入webpack
const webpack = require('webpack');
// 引入webpack配置文件
const config = require('./webpack.config.js');
// 实例化webpack
const compiler = webpack(config);
// 监听编译事件
compiler.run();
特殊配置符号
在使用 loader 的时候为了操作方便 webpack 允许我们使用不同的符号来使用 loader
- ! 跳过 normal loader
- -! 跳过 pre 和 normal loader
- !! 跳过 pre normal post loader(只有inline)
const title = require('!!inline-loader!./title')
loader获取参数
const { getOptions } = require('loader-utils')
function loader(source) {
console.log('test-loader执行了------')
const options = getOptions(this)
console.log(options, 2222222)
return source + '//test-loader'
}
module.exports = loader
loader校验
安装
npm i schema-utils -D
{
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "输入name"
},
"age": {
"type": "number",
"description": "输入年纪"
}
},
"additionalProperties": true
}
const { getOptions } = require('loader-utils')
const { validate } = require('schema-utils')
const schemaTestLoader = require('../test-loader.schema.json')
function loader(source) {
// 设置为异步
const callback = this.async()
// 获取参数
const options = getOptions(this)
// 参数校验
validate(schemaTestLoader, options)
setTimeout(() => {
console.log('test-loader', source, options)
callback(null, source)
}, 5000)
}
module.exports = loader
同步的Loader
默认创建的Loader就是同步的Loader,
这个Loader必须通过 return 或者 this.callback 来返回结果,交给下一个loader来处理;
p通常在有错误的情况下,我们会使用 this.callback;
this.callback的用法如下:
- 第一个参数必须是 Error 或者 null;
- 第二个参数是一个 string或者Buffer;
// 第一种
module.exports = function (content) {
return content
}
// 第二种
module.exports = function (content) {
this.callback(null,content)
}
异步的Loader
module.exports = function (content) {
const callback = this.async()
setTimeout(()=>{
callback(null,content)
})
}
file-loader
生成一个新的文件名,让 webpack 将当前文件拷贝至指定的路径
const imgSrc = require('./img/t1.jpg')
const oImg = document.createElement('img')
oImg.src = imgSrc
oImg.width = 180
document.body.appendChild(oImg)
---------------------------------------------------
const { getOptions, interpolateName } = require('loader-utils')
function loader(source) {
const options = getOptions(this) || {}
// 生成打包后输出的文件名
let filename = interpolateName(this, options.filename, { content: source })
// 利用 webpack 内部实现的方法将上述文件名所对应的文件拷贝至指定的目录
this.emitFile(filename, source)
// 最终返回一个 buffer 或者字符串直接给 compiler 进行使用
return `module.exports = ${JSON.stringify(filename)}`
}
loader.raw = true
module.exports = loader
- 通过 loader-utils里的 interpolateName 方法可以配合 options.name 及文件内容生成一个唯一的文件名
- 通过 this.emitFile(uri, content) 让 webpack依据参数创建对应的文件,放在指定目录下
- 返回 module.exports=$(JSON.stringify(uri)), 这样就把原来的文件路径替换为编译后的路径
url-loader
建立在 file-loader 之上的一个 loader
const mime = require('mime')
const { getOptions } = require('loader-utils')
function loader(content) {
const options = getOptions(this) || {}
let { limit, fallback = 'lg-file-loader' } = options
// 判断是否存在 limit
if (limit) {
limit = parseInt(limit, 10)
}
if (!limit || content.length < limit) {
let mimeType = mime.getType(this.resourcePath) // resourcePath就是需要加载的文件路径
// 按着规则将图片数据处理为 base64
let base64Str = `data:${mimeType};base64,${content.toString('base64')}`
return `module.exports=${JSON.stringify(base64Str)}`
} else {
// 这里的 require 不会自动加载配置文件,需要手动设置
let fileLoader = require(fallback)
return fileLoader.call(this, content)
}
}
loader.raw = true
module.exports = loader
// data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ
less-loader
npm i less postcss css-selector-tokenizer -D
// 将 less 编译成css字符串
let less = require('less')
function loader(content) {
// 通过调用 this.async 可以返回一个函数,它可以将 loader的执行变为异步,不会直接向后执行
// loader 默认情况下是同步操作
let callback = this.async()
less.render(content, { filename: this.resource }, (err, output) => {
console.log(output.css)
callback(err, output.css)
})
}
module.exports = loader
style-loader
// 把 css 变成一个 JS 脚本
// 脚本就是动态创建一个 style 标签,并且把这个 style 标签插入到html的header里
function loader(content) {
console.log(11111)
return `
let style = document.createElement('style')
style.innerHTML = ${JSON.stringify(content)}
document.head.appendChild(style)
`
}
module.exports = loader
css-loader打包分析
- 普通 CSS无数组
(function () {
var modules = {
'./src/index.css': (module, exports, require) => {
module.exports = `body{\r\n background-color: orange; \r\n}`
}
}
var cache = {}
function require(moduleId) {
if (cache[moduleId]) {
return cache[moduleId].exports
}
var module = (cache[moduleId] = {
id: moduleId,
exports: {}
})
modules[moduleId](module, module.exports, require)
return module.exports
}
const css = require('./src/index.css')
console.log(css)
})()
- 添加数组
(function () {
var modules = {
'./src/index.css': (module, exports, require) => {
var list = [] // 这里设置为数组就是为了后续处理 @import
list.push([
module.id, 'body{\r\n background-color: red;\r\r}'
])
// 定义一个映射函数,作用就是把第一个 css 描述对象转为CSS代码
let cssWithMappingToString = item => item[1]
let css = list.map(cssWithMappingToString).join('')
module.exports = css
}
}
var cache = {}
function require(moduleId) {
if (cache[moduleId]) {
return cache[moduleId].exports
}
var module = (cache[moduleId] = {
id: moduleId,
exports: {}
})
modules[moduleId](module, module.exports, require)
return module.exports
}
const css = require('./src/index.css')
console.log(css)
})()
- 新增toString方法
(function () {
var modules = {
'css-loader.js!./src/index.css': (module, exports, require) => {
var api = require('api.js')
let cssWithMappingToString = item => item[1]
let EXPORT = api(cssWithMappingToString)
EXPORT.push([
module.id, 'body{\r\n background-color: blue;\r\r}'
])
// 定义一个映射函数,作用就是把第一个 css 描述对象转为CSS代码
module.exports = EXPORT
},
'api.js': (module, exports, require) => {
module.exports = function (cssWithMappingToString) {
var list = [] // 这里设置为数组就是为了后续处理 @import
list.toString = function () {
return this.map(cssWithMappingToString).join('')
}
return list
}
},
'./src/index.css': function (module, exports, require) {
var result = require('css-loader.js!./src/index.css')
module.exports = result.toString()
}
}
var cache = {}
function require(moduleId) {
if (cache[moduleId]) {
return cache[moduleId].exports
}
var module = (cache[moduleId] = {
id: moduleId,
exports: {}
})
modules[moduleId](module, module.exports, require)
return module.exports
}
const css = require('./src/index.css')
console.log(css)
})()
- 支持import
(function () {
var modules = {
'css-loader.js!./src/global.css': (module, exports, require) => {
var api = require('api.js')
let cssWithMappingToString = item => item[1]
let EXPORT = api(cssWithMappingToString)
EXPORT.push([
module.id, 'body{\r\n color: blue;\r\r}'
])
// 定义一个映射函数,作用就是把第一个 css 描述对象转为CSS代码
module.exports = EXPORT
},
'css-loader.js!./src/index.css': (module, exports, require) => {
var api = require('api.js')
let cssWithMappingToString = item => item[1]
let EXPORT = api(cssWithMappingToString)
// 需要在 index.css 当中导入 global.css
let GLOBAL = require('css-loader.js!./src/global.css')
// 将 global.css 当中的CSS描述信息添加到EXPORT 数组当中
EXPORT.i(GLOBAL)
EXPORT.push([
module.id, 'body{\r\n background-color: blue;\r\r}'
])
// 定义一个映射函数,作用就是把第一个 css 描述对象转为CSS代码
module.exports = EXPORT
},
'api.js': (module, exports, require) => {
module.exports = function (cssWithMappingToString) {
var list = [] // 这里设置为数组就是为了后续处理 @import
list.toString = function () {
return this.map(cssWithMappingToString).join('')
}
list.i = function (otherList) {
list.unshift(...otherList)
}
return list
}
},
'./src/index.css': (module, exports, require) => {
var result = require('css-loader.js!./src/index.css')
module.exports = result.toString()
}
}
var cache = {}
function require(moduleId) {
if (cache[moduleId]) {
return cache[moduleId].exports
}
var module = (cache[moduleId] = {
id: moduleId,
exports: {}
})
modules[moduleId](module, module.exports, require)
return module.exports
}
const css = require('./src/index.css')
console.log(css)
})()