webpack学习记录02

194 阅读4分钟

loader模块的学习

这篇文档主要记录loader一些基础的知识点

loader文档:webpack.docschina.org/concepts/lo…

loader列表:webpack.docschina.org/loaders/

如何处理静态资源模块

1.处理图片或字体等

  • file-loader:处理静态资源模块

所以我们什么时候⽤file-loader呢?

场景:就是当我们需要模块,仅仅是从源代码挪移到打包⽬录,就可以使⽤file-loader来处理, txt,svg,csv,excel,图⽚资源等等

npm install file-loader -D //安装

案例:处理图片格式文件

module: {
 rules: [
     {
     test: /\.(png|jpe?g|gif)$/,
     //use使⽤⼀个loader可以⽤对象,字符串,两个loader需要⽤数组
     use: {
     	loader: "file-loader",
    	// options额外的配置,⽐如资源名称
         options: {
         // placeholder 占位符 [name]原本资源模块的名称
         // [hash]随机生成的hash字符串
         // [ext]原本资源模块的后缀
         // https://webpack.js.org/loaders/file-loader#placeholders
             name: "[name]_[hash].[ext]",
             //打包后的存放位置
 			 outputPath: "images/"
 			}
 		}
 	}
  ]
 },

有关file-loader占位符的说明文档:webpack.docschina.org/loaders/fil…

处理字体

//css
@font-face {
 font-family: "webfont";
 font-display: swap;
 src: url("webfont.woff2") format("woff2");
}
body {
 background: blue;
 font-family: "webfont" !important;
}
//webpack.config.js
{
 test: /\.(eot|ttf|woff|woff2|svg)$/,
 use: "file-loader"
}
  • url-loader

url-loader 功能类似于 file-loader,但是在文件大小(单位 byte)低于指定的限制时,可以返回一个 DataURL

npm install --save-dev url-loader //安装

案例:

module: {
        rules: [
            {
                test: /\.(png|jpe?g|gif)$/,
                use: {
                    loader: "url-loader",
                    options: {
                        name: "[name]_[hash].[ext]",
                        outputPath: "images/",
                        //⼩于2048,才转换成base64
                        limit: 2048
                    }
                }
            }
        ]
    },

2.处理样式

处理样式有很多个loader

  • less-loader --处理less样式文件
  • sass-loader --处理sass样式文件
  • stylus-loader --处理stylus样式文件
  • css-loader --处理css样式文件
  • style-loader --将处理好的样式使用style标签插入文档
  • postcss-loader --添加样式浏览器前缀,处理兼容性

案例:

//css样式处理
{
 test: /\.css$/,
 use: ["style-loader", "css-loader"]
}
//less样式处理
{
 test: /\.less$/,
 use: ["style-loader", "css-loader", "less-loader"]
}

loader有顺序,从右到左,从下到上

样式自动添加前缀:

Postcss-loader

npm i postcss-loader autoprefixer -D //安装

新建一个postcss.config.js

//webpack.config.js
{
 test: /\.css$/,
 use: ["style-loader", "css-loader", "postcss-loader"]
},
//postcss.config.js
//postcss 的配置
const autoprefixer = require("autoprefixer");
module.exports = {
  plugins: [
    autoprefixer({
      //autoprefixer 根据我们的配置 自动的帮助我们做兼容
      overrideBrowserslist: ["last 2 versions", ">1%"],
    }),
  ],
};

loader的要点

  • loader 处理webpack不⽀持的格式⽂件,模块
  • ⼀个loader只处理⼀件事情
  • loader有执⾏顺序

手写一个loader

⾃⼰编写⼀个Loader的过程是⽐较简单的,

Loader就是⼀个函数,声明式函数,不能⽤箭头函数

拿到源代码,作进⼀步的修饰处理,再返回处理后的源码就可以了

官⽅⽂档:webpack.js.org/contribute/…

接⼝⽂档:webpack.js.org/api/loaders…

实现一个简单案例

1.创建⼀个替换源码中字符串的loader

//index.js
console.log("hello kkb");

//replaceLoader.js
module.exports = function (source) {
    const result = source.replace("hello", "你好")
    return result
}

需要⽤声明式函数,因为要上到上下⽂的this,⽤到this的数据,该函数接受⼀个参数,是源码

  • 在配置⽂件中使⽤loader
//需要使⽤node核⼼模块path来处理路径
const path = require('path')
module: {
    rules: [
        {
            test: /\.js$/,
            use: path.resolve(__dirname, "./loader/replaceLoader.js")
        }
    ]
},

2.如何给loader配置参数,loader如何接受参数?

  • this.query
  • loader-utils -- loader-utils是一个webpack工具类,通过一些方法配合loader处理文件。
//webpack.config.js
module: {
    rules: [
        {
            test: /\.js$/,
            use: [
                {
                    loader: path.resolve(__dirname, "./myLoader/replaceLoader.js"),
                    options: {
                    	name: "webpack"
                	}
            	}
            ]
        }
    ]
},


// replaceLoader.js
// loader本质上是一个函数。但是不可以是箭头函数
// 拿到原内容,做进一步加工处理,处理后把加工后的内容返回
const loaderUtils = require("loader-utils");//官⽅推荐处理loader,query的⼯具
// source 来源
module.exports = function (source) {
    const options = loaderUtils.getOptions(this);
    // const result = source.replace("kkb", options.name);
    return source.replace("哈哈哈哈", options.name);
}
// index.js
console.log("hello 哈哈哈哈!!!!");

打包完成后:

打包完成

成功替换!

3.如何返回多个信息,不⽌是处理好的源码呢,可以使⽤this.callback来处理

// callback有四个参数
this.callback(
    err: Error | null, 
    content: string | Buffer, 
    sourceMap ?: SourceMap,
    meta ?: any);
    
  • 第一个参数必须为 Errornull
  • 第二个参数是 string Buffer
  • 可选:第三个参数必须是可由此模块解析的源映射。
  • 可选:webpack忽略的第四个选项可以是任何内容(例如某些元数据)。
//replaceLoader.js
// 使用this.callback返回最后的字符串
const loaderUtils = require("loader-utils");//官⽅推荐处理loader,query的⼯具
module.exports = function (source) {
    const options = loaderUtils.getOptions(this);
    const result = source.replace("哈哈哈哈", options.name);
    this.callback(null, result);
};

4.如果loader⾥⾯有异步的事情要怎么处理呢

const loaderUtils = require("loader-utils");
module.exports = function(source) {
    const options = loaderUtils.getOptions(this);
    setTimeout(() => {
        const result = source.replace("哈哈哈哈", options.name);
        return result;
    }, 1000);
};
//先⽤setTimeout处理下试试,发现会报错

出现报错

报错说明

报错说明 无法获取到返回的buffer和字符串

再用this.async来处理,它会返回this.callback

const loaderUtils = require("loader-utils");
module.exports = function (source) {
    const options = loaderUtils.getOptions(this);
    //定义⼀个异步处理,告诉webpack,这个loader⾥有异步事件,在⾥⾯调⽤下这个异步
    //callback 就是 this.callback 注意参数的使⽤
    const callback = this.async();
    setTimeout(() => {
        const result = source.replace("哈哈哈哈", options.name);
        callback(null, result);
    }, 3000);
};

异步loader编译结果

异步loader结果

5.多个自定义loader的组合使用

//replaceLoader.js
module.exports = function (source) {
    return source.replace("哈哈哈哈", "webpack");
};
//replaceLoaderAsync.js
const loaderUtils = require("loader-utils");
module.exports = function (source) {
    const options = loaderUtils.getOptions(this);
    //定义⼀个异步处理,告诉webpack,这个loader⾥有异步事件,在⾥⾯调⽤下这个异步
    const callback = this.async();
    setTimeout(() => {
        const result = source.replace("哈哈哈哈", options.name);
        callback(null, result);
    }, 3000);
};


//webpack.config.js
module: {
    rules: [
        {
            test: /\.js$/,
            use: [
                path.resolve(__dirname, "./myLoader/replaceLoader.js"),
                {
                    loader: path.resolve(__dirname, "./myLoader/replaceLoaderAsync.js"),
                    options: {
                        name: "webpack"
                    }
                }
            ]
        }
    ]
},

实现一套简版的css处理loader

我们可以来根据自定义loader的原理来实现一套简版的style-loader,css-loader,less-loader

//hless-loader.js
//引入less模块
const less = require("less");
module.exports = function (source) {
    // 使用less render函数对less文件转换成css文件
    less.render(source, (err, output) => {
        this.callback(err, output.css);
    });
};
//hcss-loader.js
//拿到返回的css 文件
module.exports = function (source) {
  return source;
};

// htyle-loader.js
//动态生成style 把css插入

module.exports = function (source) {
  console.log(source);
  // 将拿到的字符串进行转义 插入到style标签里面 并添加到head头部
  return `const tag = document.createElement('style');
    tag.innerHTML = ${JSON.stringify(source)};
    document.head.appendChild(tag)`;
};

//最后在配置中使用
 {
     test: /\.less$/,
     use: [
         "./myLoaders/hstyle-loader",
         "./myLoaders/hcss-loader",
         "./myLoaders/hless-loader",
     ],
 },
处理loader的路径问题

resolveLoader字段可以告诉webpack去哪里找loader解析

  //到哪去找loader
    resolveLoader: {
        modules: ["./node_modules", "./myLoaders"],
    },
     module: {
        rules: [
            {
                test: /\.less$/,
                use: [
                    "hstyle-loader",
                    "hcss-loader",
                    "hless-loader",
                ],
            },
          ]
     }

这样的话就不用加上长串路径前缀了

实现一个新的需求:将css文件模块单独抽离出来

mini-css-extract-plugin

作用:打包的时候将css文件单独抽离出来放在一个文件中

npm install mini-css-extract-plugin -D //安装
如何使用:
//webpack-config.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
 module: {
    rules: [
      {
        test: /\.less$/,
        use: [
          {
            loader: miniCssExtractPlugin.loader,
            options: {
              publicPath: "../",
            },
          },
          "css-loader"
        ]
      }
    ]
  }
  plugins: [
      new miniCssExtractPlugin({
     	 filename: "css/[name]-[contenthash].css",
      }),
  ]

打包后的结果:

打包后文件夹

浏览器文件呈现

最后整理一道面试题

盘一盘下面几个hash的区别

  • hash
  • Chunkhash
  • Contenthash

hash是跟整个项目的构建相关,构建生成的文件hash值都是一样的,所以hash计算是跟整个项目的构建相关,同一次构建过程中生成的hash都是一样的,只要项目里有任意文件更改,整个项目构建的hash值都会更改。

chunkhash和hash不一样,它根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的hash值。我们在生产环境里把一些公共库和程序入口文件区分开,单独打包构建,接着我们采用chunkhash的方式生成hash值,那么只要我们不改动公共库的代码,就可以保证其hash值不会受影响。

contenthash表示由文件内容产生的hash值,内容不同产生的contenthash值也不一样。在项目中,通常做法是把项目中css都抽离出对应的css文件来加以引用。

js推荐使用Chunkhash

css推荐使用Contenthash