重学webpack系列(三) -- webpack的loader机制的解读

753 阅读6分钟

上一章重学webpack系列(二) -- webpack解决的问题与实现模块化的具体实践,我们讲解了webpack解决了前端项目在实施落地的过程中遇到的一些问题,也初步去配置了webpackentryoutputmode,实现了文件的打包,很明显在项目中我们不仅仅只有js文件,还有一些非js文件webpack也需要去做处理,那么处理非js文件的工具就是loaders了,那么这一章我们就来了解一下loaders究竟是何方神圣。

webpack怎么去加载非js文件

按照我们以往的经验,我们先尝试在文件目录中添加一个.css文件,在入口entry中配置css: "./src/css/index.css"

// src/css/index.css
.index {
  display: flex;
  justify-content: center;
  align-items: center;
  color: wheat;
  background-color: red;
}

// webpack.config.json
 entry: {
    index: "./src/index.js",
    add: "./src/add.js",
    css: "./src/css/index.css"
},

我们打包的时候,控制台会出现报错:

image.png 出现这样的原因是因为,css的代码语法与js的完全不同,webpack并不认识这样的语法,所以webpack提示应该使用loaders来解决这样的问题。那我们便可以去安装css-loader并在webpack.config.json里面配置css-loader来解决这个问题了。

css-loader

// 安装css-loader
npm i css-loader -S

// webpack.config.json
module.exports = {
    ...
    module:{
        rules:[
            {
                test:/\.css$/, // 用正则来匹配适用的文件
                use:'css-loader' // 用loader来处理此文件
            }
        ]
    }
    ...
}    

样式效果

image.png 在这里我们就可以解决打包报错的问题了,如果你想使用这个index.css文件,你通过script标签引入到页面中,你会发现,此css文件的代码,并不能给节点加上样式。这是为什么呢?我们得去看一看打包过后的文件。在文件里我们可以看到这样的一段代码

var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));
// Module
___CSS_LOADER_EXPORT___.push([module.id, ".index {\n  color: wheat;\n  background-color: red;\n}", "",{"version":3,"sources":["webpack://./src/css/index.css"],"names":[],"mappings":"AAAA;EACE,YAAY;EACZ,qBAAqB;AACvB","sourcesContent":[".index {\n  color: wheat;\n  background-color: red;\n}"],"sourceRoot":""}]);
// Exports
const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);

上面这段代码,出现了我们css中写的样式,表现为拆分了css结构成一个字符串,然后通过__WEBPACK_DEFAULT_EXPORT__暴露到当前的js文件中,换句话说就是css-loader只是加载css模块,但是并没有使用css模块。为了解决这个问题,我们还需要依赖另外一个loader

style-loader

// 安装css-loader
npm i style-loader -S

// webpack.config.json
module.exports = {
    ...
    module:{
        rules:[
            {
                test:/\.css$/, // 用正则来匹配适用的文件
                use:[
                    style-loader,
                    css-loader
                ] 
                // 用loader来处理此文件,如果是多个loader则必须要把style放在最前面,
                // 因为style是使用,而其他的是加载、处理。
            }
        ]
    }
    ...
}    

样式效果

image.png

扩展

为什么style-loader可以使用css样式呢?我们发现在style-loader打包之后,生成了这样的一串代码。

function insertStyleElement(options) {
  // 创建style标签
  var element = document.createElement("style");
  // 设置标签以及标签属性
  options.setAttributes(element, options.attributes);
  // 插入到html中
  options.insert(element, options.options);
  return element;
}

原来style-loader的原理就是在页面创建了一个style标签去使用那些属性。

在js文件中引入css文件

如果我们单独打包css文件的话,那么我们就需要在使用层面,单独对js(源文件是css文件)文件去引入使用,我们也可以在js文件中使用import './css/index.css'来进行加载css文件到js文件中,实现统一打包,其实webpack也建议我们这样做。

// add.js
import './css/index.css'

// webpack.config.json
module.exports = {
    mode: "development",
    entry: {
      add: "./src/add.js"
    }
    ...
}

loader 特性

  • loader支持链式传递,一组链式的loader将按照相反的顺序执行。loader链中的第一个loader返回值给下一个loader。在最后一个loader,返回webpack所预期的JavaScript
  • loader 可以是同步的,也可以是异步的。
  • loader 运行在Node.js中,并且能够执行任何可能的操作。
  • loader 接收查询参数。用于对loader传递配置。
  • loader 也能够使用 options 对象进行配置。
  • 除了使用 package.json 常见的 main 属性,还可以将普通的 npm 模块导出为 loader,做法是在 package.json 里定义一个 loader 字段。
  • 插件(plugin)可以为loader带来更多特性。
  • loader能够产生额外的任意文件。

loader通过(loader)预处理函数,为JavaScript生态系统提供了更多能力。 用户现在可以更加灵活地引入细粒度逻辑,例如压缩、打包等。

常见loader的基础实践

  • babel-loader

    • 作用:将Es6+ 语法转换为Es5语法。
    • 配置
    // 安装
    npm i babel-loader @babel/core @babel/preset-env -S
    
    // webpack.config.json
    module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                use: {
                    loader: "babel-loader",
                    options: {
                        presets: [
                            ['@babel/preset-env', { targets: "defaults"}]
                        ]
                    }
                }
            },
        ]
    }
    
  • less-loader / scss-loader、style-loader、css-loader

    • 作用:处理css/scss/less文件。
    • 配置
    // 安装
    npm i less-loader / scss-loader css-loader style-loader -S
    
    // webpack.config.json
    module.exports = {
    module: {
        rules: [
            {
                test: /\.less$/,
                // test: /\.scss$/,
                use: [
                    'style-loader',
                    'css-loader',
                    ’less-loader‘
                    // 'scss-loader'
                ]
            },
        ]
    }
    
  • file-loader

    • 作用:用于处理文件类型资源,如jpgpng等图片。返回值为publicPath为准。
    • 配置
    // 安装
    npm i file-loader -S
    
    // webpack.config.json
    module: {
        rules: [
            {
                test: /\.(png|jpg|jpeg)$/,
                use: [
                    {
                        loader: "file-loader",
                        options: {
                            name: "[name]_[hash:8].[ext]", // .ext为文件扩展名eg: .png
                            publicPath: "https://www.baidu.com" 
                        }
                    }
                ]
            }
        ]
    }
    
  • url-loader

    • 作用:处理图片资源,根据大小选择打包方式。
    • 配置
    // 安装
    npm i url-loader -S
    
    // webpack.config.json
    module: {
        rules: [
            {
                test: /\.(png|jpg|jpeg)$/,
                use: [
                    {
                        loader: "url-loader",
                        options: {
                            name: "[name]_[hash:8].[ext]",
                            limit: 10240, // 这里单位为(b) 10240 => 10kb
                            // 这里如果小于10kb则转换为base64打包进js文件,如果大于10kb则打包到dist目录
                        }
                    }
                ]
            }
        ]
    }
    
  • eslint-loader

    • 作用:用于检查代码是否符合规范,是否存在语法错误,需要配合.eslintrc.js使用。
    • 配置
    // 安装
    npm i npm i eslint-loader eslint -S
    
    // webpack.config.json
    module: {
        rules: [
            {
                test: /\.ts$/,
                use: ["eslint-loader", "ts-loader"],
                enforce: "pre",
                exclude: /node_modules/ // 除此之外的文件夹
            }
        ]
    }
    
  • postcss-loader

    • 作用:用于补充css样式各种浏览器内核前缀。
    • 配置
    // 安装
    npm i postcss-loader -S
    
    // webpack.config.json
    module: {
        rules: [
            {
                test: /\.scss$/,
                use: [
                    "style-loader",
                    "css-loader",
                    "sass-loader",
            		"postcss-loader"
                ],
                include: /src/, 
            },
        ]
    }
    
  • ts-loader

    • 作用:编译ts文件,需要配合tsconfig.json使用。
    • 配置
    // 安装
    npm i ts-loader -S
    
    // webpack.config.json
    module: {
        rules: [
            {
                {
                	test: /\.ts$/,
                	use: "ts-loader",
            	}
            }
        ]
    }
    

为什么webpack建议我们在js文件中引入其他资源

webpack不仅仅建议我们在js中引入css资源,只要是是当前项目依赖所有资源,webpack都建议我们在js文件中引入。为什么webpack要这样设计呢?

  • 便于统一资源管理

    • 如果我们单独去打包某个与js产生依赖的css图片字体等,那么我们将会有额外的对html页面的操作。
    • 如果这一部分功能废弃掉了,那么我们有必须要去操作html页面,把不需要的模块资源移除掉,增加了额外操作。
  • 使逻辑更合理化

    • js代码肯定是依赖这些非js资源,才能实现整体的功能的。
  • 防止必要文件丢失

    • 通过依赖关系,可以最大程度上保证资源文件不会丢失,在生产环境下可以避免重要经济损失

实现一个自己的loader

步骤

  • 创建一个非js文件。
  • 引入到打包入口js文件中。
  • 创建一个自己命名的loader
  • webpack.config.json中配置自己的loader

loader的基本要求

loader机制告诉我们在函数处理结果返回的时候,必须是一段JavaScript可执行的代码。

具体代码

// mkdown-loader.js
const {marked} = require('marked'); // 借用marked库,让md文件内容转化为html内容

module.exports = (moduleId) => {
  const html = marked(moduleId)

  // const  htmlString = `module.exports = ${JSON.stringify(html)}`

  // const  htmlString = `export default ${JSON.stringify(html)}`

  return html
}

// webpack.confih.json
module.exports = {
    module:{
        rules:[
            {
                test:/\.md$/,
                use:[
                    'html-loader',
                    './src/mkdown-loader.js'
                ]
            }
        ]
    },
}

使用html-webpack-plugin插件

我们先定义一个template模板当做插件模板。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div class="index"></div>
</body>
</html>

配置插件之后的webpack.config.json

// webpack.confih.json
module.exports = {
    module:{
        rules:[
            {
                test:/\.md$/,
                use:[
                    'html-loader',
                    './src/mkdown-loader.js'
                ]
            }
        ]
    },
    plugins:[
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            titie: 'title',
            template: './index.html'
        }),
    ],
}

之后在控制台就会输出一段html文本

image.png

我们再去看看打包后的代码的样子

image.png

获取loader的配置参数

如果我们在使用loader的时候,想给它传递一些配置项,比如

// webpack.config.json
module.exports = {
    ...
    module: {
        rules: [
            {
                test:/\.md$/,
                use:[
                    {
                        loader: 'html-loader'
                    },
                    {
                        loader: './src/mkdown-loader.js',
                        options: {
                                name: '一溪之石',
                                age: 22,
                                sex: '男'
                        }
                    }
                ]
            }
        ]
    }
}

// mkdown-loader.js
module.exports = function(moduleId){
    // 可以通过this.getOptions来获取options里面的参数
    const options = this.getOptions();
    
    const { name = 'null', age = 'null', sex='null' } = options;
    const html = marked(moduleId);
    console.log(html);
    // .... 在loader里面处理其他逻辑
    return html; // 返回给下一个loader处理
}

开发一个loader常用的Api

  • this.async :获取一个 callback 函数,处理异步。
  • this.callback :同步 loader 中,返回的方法。
  • this.emitFile :产生一个文件。
  • this.getOptions :根据传入的 schema 获取对应参数,在webpack5中,schema已经不用再写了,getOptions可以获取到上下文中的配置参数。
  • this.importModule :用于子编译器在构建时编译和执行请求。
  • this.resourcePath :当前资源文件的路径。

总结

好了关于webpackloader的机制,介绍到这里也就结束了,各位同学也看到了本文中使用到了html-webpack-plugin这个插件,插件确实能够让我们的项目啊,loader啊具有更丰富的功能,下一章我们就会探索一下webpack的插件机制,直通车 >>> 重学webpack系列(四) -- webpack的plugins机制的解读