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 配置 pre1. 后置 loader : 通过 enforce 配置 post1. 行内 loader : 通过 !分割符,直接在行内进行使用1. 上述的 loader 如果区分之后在执行上也存在固定的先后顺序:pre normal inline post```javascriptmodule.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即可
// 引入webpackconst webpack = require('webpack');// 引入webpack配置文件const config = require('./webpack.config.js');// 实例化webpackconst 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 = imgSrcoImg.width = 180document.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 = truemodule.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// 判断是否存在 limitif (limit) {limit = parseInt(limit, 10)}if (!limit || content.length < limit) {let mimeType = mime.getType(this.resourcePath) // resourcePath就是需要加载的文件路径// 按着规则将图片数据处理为 base64let 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 = truemodule.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 = [] // 这里设置为数组就是为了后续处理 @importlist.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 = [] // 这里设置为数组就是为了后续处理 @importlist.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.csslet 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 = [] // 这里设置为数组就是为了后续处理 @importlist.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)})()
