loader使用

  1. loader 是一个导出为函数的 JS 模块,接收上游产出的结果或者资源文件做为参数
  2. compiler 拿到最后一个 loader 产出的结果,这个结果应该是一个 string 或者 buffer(被转为string)
  3. 从入口文件出发,调用所有配置中的 loader 对模块进行编译,找到依赖模块,递归到所有依赖都被处理
  1. module.exports = {
  2. mode: 'development',
  3. devtool: false,
  4. entry: './src/index.js',
  5. output: {
  6. filename: 'build.js',
  7. path: path.resolve(__dirname, 'dist')
  8. },
  9. module: {
  10. rules: [
  11. {
  12. test: /\.js$/,
  13. use: [
  14. {
  15. loader:'babel-loader',
  16. options: {
  17. presets: ['@babel/preset-env']
  18. }
  19. }
  20. ]
  21. }
  22. ]
  23. }
  24. }

配置resolveLoader

  1. module.exports = {
  2. mode: 'development',
  3. devtool: false,
  4. resolveLoader: {
  5. modules: [path.resolve(__dirname, 'loaders'), 'node_modules']
  6. },
  7. entry: './src/index.js',
  8. output: {
  9. filename: 'build.js',
  10. path: path.resolve(__dirname, 'dist')
  11. },
  12. module: {
  13. rules: [
  14. {
  15. test: /\.js$/,
  16. use: [
  17. {
  18. // loader: path.resolve(__dirname, 'loaders/test-loader.js')
  19. loader: 'test-loader'
  20. }
  21. ]
  22. }
  23. ]
  24. }
  25. }

loader组成

一个完整的 loader 有两部分组成 pitchLoader 与 normalLoader

  1. function loader(source) {
  2. console.log('loader2执行了------')
  3. return source + '//loader2'
  4. }
  5. loader.pitch = function (data) {
  6. console.log('loader2-pitch')
  7. return '2222'
  8. }
  9. module.exports = loader

Normal loader

  1. content 源文件的内容
  2. [map] 可以被 https://github.com/mozilla/source-map 使用的 SourceMap 数据
  3. [meta] meta 数据,可以是任何内容

    1. function webpackLoader(content, map, meta) {
    2. // 你的webpack loader代码
    3. }
    4. module.exports = webpackLoader;

    Pitching Loader

  4. remainingRequest 剩余请求

  5. precedingRequest 前置请求
  6. data 数据对象
    1. /**
    2. * @remainingRequest 剩余请求
    3. * @precedingRequest 前置请求
    4. * @data 数据对象
    5. */
    6. function (remainingRequest, precedingRequest, data) {
    7. // some code
    8. };

    参数作用

    有如下三个loader,在b-loader上测试pitch参数 ```typescript // webpack.config.js rules: [ {
    1. test: /\.js$/,
    2. use: ['a-loader'],
    }, {
    1. test: /\.js$/,
    2. use: ['b-loader'],
    }, {
    1. test: /\.js$/,
    2. 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;

  1. <a name="bSDVz"></a>
  2. #### 总结
  3. 1. remainingRequest 剩余请求 (代指下一个loader位置和调用chunk)
  4. 1. precedingRequest 前置请求 (代指上一个loader)
  5. 1. data 数据对象 传递数据,可以再nomal loader 中用 this.data 接受
  6. <a name="pCnYS"></a>
  7. #### 作用
  8. 当某个 Pitching Loader 返回非 undefined 值时,就会实现熔断效果(会跳过本身的normal loader 和后续pitch loader、normal loader)
  9. <a name="849821da"></a>
  10. ## loader分类
  11. > 本质上所有的 loader 都是一样的,但是可以通过 enforce 或者不同的使用方式来区分它们
  12. 1. 普通 loader : 没有任务配置正常使用
  13. 1. 前置 loader : 通过 enforce 配置 pre
  14. 1. 后置 loader : 通过 enforce 配置 post
  15. 1. 行内 loader : 通过 !分割符,直接在行内进行使用
  16. 1. 上述的 loader 如果区分之后在执行上也存在固定的先后顺序:pre normal inline post
  17. ```javascript
  18. module.exports = {
  19. mode: 'development',
  20. devtool: false,
  21. resolveLoader: {
  22. modules: [path.resolve(__dirname, 'loaders'), 'node_modules']
  23. },
  24. entry: './src/index.js',
  25. output: {
  26. filename: 'build.js',
  27. path: path.resolve(__dirname, 'dist')
  28. },
  29. module: {
  30. rules: [
  31. {
  32. test: /\.js$/,
  33. use: ['normal-loader']
  34. },
  35. {
  36. test: /\.js$/,
  37. enforce: 'post',
  38. use: ['post-loader']
  39. },
  40. {
  41. test: /\.js$/,
  42. enforce: 'pre',
  43. use: ['pre-loader']
  44. }
  45. ]
  46. }
  47. }

loader的执行顺序

normal 从后向前、从右向左的, pitch loader 是从左向右,从上到下
image.png
pitch 熔断效果
image.png

如何调试

新建run.js 文件,正常断点调试loader即可

  1. // 引入webpack
  2. const webpack = require('webpack');
  3. // 引入webpack配置文件
  4. const config = require('./webpack.config.js');
  5. // 实例化webpack
  6. const compiler = webpack(config);
  7. // 监听编译事件
  8. compiler.run();

特殊配置符号

在使用 loader 的时候为了操作方便 webpack 允许我们使用不同的符号来使用 loader

  1. ! 跳过 normal loader
  2. -! 跳过 pre 和 normal loader
  3. !! 跳过 pre normal post loader(只有inline)
  1. const title = require('!!inline-loader!./title')

loader获取参数

  1. const { getOptions } = require('loader-utils')
  2. function loader(source) {
  3. console.log('test-loader执行了------')
  4. const options = getOptions(this)
  5. console.log(options, 2222222)
  6. return source + '//test-loader'
  7. }
  8. module.exports = loader

loader校验

安装 npm i schema-utils -D

  1. {
  2. "type": "object",
  3. "properties": {
  4. "name": {
  5. "type": "string",
  6. "description": "输入name"
  7. },
  8. "age": {
  9. "type": "number",
  10. "description": "输入年纪"
  11. }
  12. },
  13. "additionalProperties": true
  14. }
  1. const { getOptions } = require('loader-utils')
  2. const { validate } = require('schema-utils')
  3. const schemaTestLoader = require('../test-loader.schema.json')
  4. function loader(source) {
  5. // 设置为异步
  6. const callback = this.async()
  7. // 获取参数
  8. const options = getOptions(this)
  9. // 参数校验
  10. validate(schemaTestLoader, options)
  11. setTimeout(() => {
  12. console.log('test-loader', source, options)
  13. callback(null, source)
  14. }, 5000)
  15. }
  16. module.exports = loader

同步的Loader

默认创建的Loader就是同步的Loader,

这个Loader必须通过 return 或者 this.callback 来返回结果,交给下一个loader来处理;

p通常在有错误的情况下,我们会使用 this.callback;

this.callback的用法如下:

  1. 第一个参数必须是 Error 或者 null;
  2. 第二个参数是一个 string或者Buffer;
  1. // 第一种
  2. module.exports = function (content) {
  3. return content
  4. }
  5. // 第二种
  6. module.exports = function (content) {
  7. this.callback(null,content)
  8. }

异步的Loader

  1. module.exports = function (content) {
  2. const callback = this.async()
  3. setTimeout(()=>{
  4. callback(null,content)
  5. })
  6. }

file-loader

生成一个新的文件名,让 webpack 将当前文件拷贝至指定的路径

  1. const imgSrc = require('./img/t1.jpg')
  2. const oImg = document.createElement('img')
  3. oImg.src = imgSrc
  4. oImg.width = 180
  5. document.body.appendChild(oImg)
  6. ---------------------------------------------------
  7. const { getOptions, interpolateName } = require('loader-utils')
  8. function loader(source) {
  9. const options = getOptions(this) || {}
  10. // 生成打包后输出的文件名
  11. let filename = interpolateName(this, options.filename, { content: source })
  12. // 利用 webpack 内部实现的方法将上述文件名所对应的文件拷贝至指定的目录
  13. this.emitFile(filename, source)
  14. // 最终返回一个 buffer 或者字符串直接给 compiler 进行使用
  15. return `module.exports = ${JSON.stringify(filename)}`
  16. }
  17. loader.raw = true
  18. module.exports = loader
  1. 通过 loader-utils里的 interpolateName 方法可以配合 options.name 及文件内容生成一个唯一的文件名
  2. 通过 this.emitFile(uri, content) 让 webpack依据参数创建对应的文件,放在指定目录下
  3. 返回 module.exports=$(JSON.stringify(uri)), 这样就把原来的文件路径替换为编译后的路径

url-loader

建立在 file-loader 之上的一个 loader

  1. const mime = require('mime')
  2. const { getOptions } = require('loader-utils')
  3. function loader(content) {
  4. const options = getOptions(this) || {}
  5. let { limit, fallback = 'lg-file-loader' } = options
  6. // 判断是否存在 limit
  7. if (limit) {
  8. limit = parseInt(limit, 10)
  9. }
  10. if (!limit || content.length < limit) {
  11. let mimeType = mime.getType(this.resourcePath) // resourcePath就是需要加载的文件路径
  12. // 按着规则将图片数据处理为 base64
  13. let base64Str = `data:${mimeType};base64,${content.toString('base64')}`
  14. return `module.exports=${JSON.stringify(base64Str)}`
  15. } else {
  16. // 这里的 require 不会自动加载配置文件,需要手动设置
  17. let fileLoader = require(fallback)
  18. return fileLoader.call(this, content)
  19. }
  20. }
  21. loader.raw = true
  22. module.exports = loader
  23. // data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ

less-loader

npm i less postcss css-selector-tokenizer -D

  1. // 将 less 编译成css字符串
  2. let less = require('less')
  3. function loader(content) {
  4. // 通过调用 this.async 可以返回一个函数,它可以将 loader的执行变为异步,不会直接向后执行
  5. // loader 默认情况下是同步操作
  6. let callback = this.async()
  7. less.render(content, { filename: this.resource }, (err, output) => {
  8. console.log(output.css)
  9. callback(err, output.css)
  10. })
  11. }
  12. module.exports = loader

style-loader

  1. // 把 css 变成一个 JS 脚本
  2. // 脚本就是动态创建一个 style 标签,并且把这个 style 标签插入到html的header里
  3. function loader(content) {
  4. console.log(11111)
  5. return `
  6. let style = document.createElement('style')
  7. style.innerHTML = ${JSON.stringify(content)}
  8. document.head.appendChild(style)
  9. `
  10. }
  11. module.exports = loader

css-loader打包分析

  1. 普通 CSS无数组
  1. (function () {
  2. var modules = {
  3. './src/index.css': (module, exports, require) => {
  4. module.exports = `body{\r\n background-color: orange; \r\n}`
  5. }
  6. }
  7. var cache = {}
  8. function require(moduleId) {
  9. if (cache[moduleId]) {
  10. return cache[moduleId].exports
  11. }
  12. var module = (cache[moduleId] = {
  13. id: moduleId,
  14. exports: {}
  15. })
  16. modules[moduleId](module, module.exports, require)
  17. return module.exports
  18. }
  19. const css = require('./src/index.css')
  20. console.log(css)
  21. })()
  1. 添加数组
  1. (function () {
  2. var modules = {
  3. './src/index.css': (module, exports, require) => {
  4. var list = [] // 这里设置为数组就是为了后续处理 @import
  5. list.push([
  6. module.id, 'body{\r\n background-color: red;\r\r}'
  7. ])
  8. // 定义一个映射函数,作用就是把第一个 css 描述对象转为CSS代码
  9. let cssWithMappingToString = item => item[1]
  10. let css = list.map(cssWithMappingToString).join('')
  11. module.exports = css
  12. }
  13. }
  14. var cache = {}
  15. function require(moduleId) {
  16. if (cache[moduleId]) {
  17. return cache[moduleId].exports
  18. }
  19. var module = (cache[moduleId] = {
  20. id: moduleId,
  21. exports: {}
  22. })
  23. modules[moduleId](module, module.exports, require)
  24. return module.exports
  25. }
  26. const css = require('./src/index.css')
  27. console.log(css)
  28. })()
  1. 新增toString方法
  1. (function () {
  2. var modules = {
  3. 'css-loader.js!./src/index.css': (module, exports, require) => {
  4. var api = require('api.js')
  5. let cssWithMappingToString = item => item[1]
  6. let EXPORT = api(cssWithMappingToString)
  7. EXPORT.push([
  8. module.id, 'body{\r\n background-color: blue;\r\r}'
  9. ])
  10. // 定义一个映射函数,作用就是把第一个 css 描述对象转为CSS代码
  11. module.exports = EXPORT
  12. },
  13. 'api.js': (module, exports, require) => {
  14. module.exports = function (cssWithMappingToString) {
  15. var list = [] // 这里设置为数组就是为了后续处理 @import
  16. list.toString = function () {
  17. return this.map(cssWithMappingToString).join('')
  18. }
  19. return list
  20. }
  21. },
  22. './src/index.css': function (module, exports, require) {
  23. var result = require('css-loader.js!./src/index.css')
  24. module.exports = result.toString()
  25. }
  26. }
  27. var cache = {}
  28. function require(moduleId) {
  29. if (cache[moduleId]) {
  30. return cache[moduleId].exports
  31. }
  32. var module = (cache[moduleId] = {
  33. id: moduleId,
  34. exports: {}
  35. })
  36. modules[moduleId](module, module.exports, require)
  37. return module.exports
  38. }
  39. const css = require('./src/index.css')
  40. console.log(css)
  41. })()
  1. 支持import
  1. (function () {
  2. var modules = {
  3. 'css-loader.js!./src/global.css': (module, exports, require) => {
  4. var api = require('api.js')
  5. let cssWithMappingToString = item => item[1]
  6. let EXPORT = api(cssWithMappingToString)
  7. EXPORT.push([
  8. module.id, 'body{\r\n color: blue;\r\r}'
  9. ])
  10. // 定义一个映射函数,作用就是把第一个 css 描述对象转为CSS代码
  11. module.exports = EXPORT
  12. },
  13. 'css-loader.js!./src/index.css': (module, exports, require) => {
  14. var api = require('api.js')
  15. let cssWithMappingToString = item => item[1]
  16. let EXPORT = api(cssWithMappingToString)
  17. // 需要在 index.css 当中导入 global.css
  18. let GLOBAL = require('css-loader.js!./src/global.css')
  19. // 将 global.css 当中的CSS描述信息添加到EXPORT 数组当中
  20. EXPORT.i(GLOBAL)
  21. EXPORT.push([
  22. module.id, 'body{\r\n background-color: blue;\r\r}'
  23. ])
  24. // 定义一个映射函数,作用就是把第一个 css 描述对象转为CSS代码
  25. module.exports = EXPORT
  26. },
  27. 'api.js': (module, exports, require) => {
  28. module.exports = function (cssWithMappingToString) {
  29. var list = [] // 这里设置为数组就是为了后续处理 @import
  30. list.toString = function () {
  31. return this.map(cssWithMappingToString).join('')
  32. }
  33. list.i = function (otherList) {
  34. list.unshift(...otherList)
  35. }
  36. return list
  37. }
  38. },
  39. './src/index.css': (module, exports, require) => {
  40. var result = require('css-loader.js!./src/index.css')
  41. module.exports = result.toString()
  42. }
  43. }
  44. var cache = {}
  45. function require(moduleId) {
  46. if (cache[moduleId]) {
  47. return cache[moduleId].exports
  48. }
  49. var module = (cache[moduleId] = {
  50. id: moduleId,
  51. exports: {}
  52. })
  53. modules[moduleId](module, module.exports, require)
  54. return module.exports
  55. }
  56. const css = require('./src/index.css')
  57. console.log(css)
  58. })()