一文学会Webpack实用功能|加载器篇

Webpack 资源模块加载

通过探索我们知道可以把css文件作为打包的入口,不过webpack的打包入口一般还是JavaScript, 因为他的打包入口从某种程度来说可以算是我们应用的运行入口。

而就目前而言,前端应用当中的业务是由JavaScript去驱动的,我们只是尝试一下,正确的做法还是把js文件作为打包的入口。然后在js代码当中通过import的方式去引入css文件。

这样的话css-loader仍然可以正常工作,我们再来尝试一下。我们在js中import我们的css文件

import createHeading from './heading.js';

import './style.css';

const heading = createHeading();

document.body.append(heading);

然后在webpack.config.js中配置css的loader 这里注意,css-loader 和 style-loader 要使用yarn安装到项目中。

yarn add css-loader style-loader --dev

webpack中的loader需要配置到config的module中。

const path = require('path');

module.exports = {
    entry: './src/main.js',
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist')
    },
    module: {
        rules: [
            {
                test: /.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            }
        ]
    }
}

完成之后我们打开命令行终端,运行一下打包命令。启动项目之后,我们发现,我们的样式是可以生效的。

传统模式开发我们是将文件单独分开单独引入,可是webpack确要求我们在js当中去引入css这到底是为什么呢。

其实webpack不仅仅是建议我们在webpack当中去引入css,而是建议我们我们在编写代码当中去引入任何你当前代码需要的资源文件。

因为真正需要这个资源的不是应用,而是此时正在编写的代码,是代码想要正常工作就必须要去加载对应的资源,这就是webpack的哲学,可能一开始不太容易理解,可以对比一下,假设我们的样式还是单独的去引入到页面当中,如果我们的代码更新了,不再去需要这个样式资源了,那又会怎么样。

所以说通过JavaScript的代码去引入文件或者建立我们js和文件之间的依赖关系是有一个很明显的优势的。

JavaScript的代码本身是负责完成整个业务的功能,放大来看就是驱动了我们整个前端应用,在实现业务功能的过程当中可能需要用到样式或者图片等等一系列的资源文件。

如果建立了这种依赖关系,一来逻辑上比较合理,因为我们的JS确实需要这些资源文件的配合才能实现对应的功能,二来我们可以保证上线时资源文件不缺失,而且每一个上线的文件都是必要的。

学习一个新事物不是学习用法就能提高,因为这些东西按照文档基本上谁都可以,很多时候这些新事物的思想才是突破点,能够搞明白这些新事物为什么这样设计,基本上才是出道了。

Webpack 文件资源加载器

目前webpack社区提供了非常多的资源加载器,基本上开发者能想到的合理的需求都有对应的loader,接下来我们来尝试一些非常有代表性的loader。

首先是文件资源加载器

大多数文件加载器都类似于css-loader,都是将资源模块转换为js代码的实现方式去工作,但是呢还有一些我们经常用到的资源文件,例如我们项目当中用到的图片或者字体,这些文件是没办法通过js的方式去表示的。

对于这类的资源文件,我们需要用到文件的资源加载器,也就是file-loader。

那文件资源加载器究竟是如何工作的?首先我们在项目中添加一张普通的图片文件,假设这张图片就是我们在实现某个功能时候需要的资源,按照webpack的思想,我们也应该在用到这个资源的地方去通过import 的方式去导入这张图片。让webpack去处理资源的加载。

我们这里需要接收一下模块文件的默认导出,我们导出的内容就是这张文件的资源路径。

我们创建一个img元素,把我们资源的路径也就是src设置成文件,最后再将这个元素append到body中


import createHeading from './heading.js';

import './style.css';

import icon from './icon.png';

const heading = createHeading();

document.body.append(heading);

const img = new Image();

img.src = icon;

document.body.append(img);

因为我们导入了一个webpack不能识别的资源,所以我们需要修改webpack配置,首先需要安装额外的加载器file-loader

yarn add file-loader --dev

安装完成过后我们修改webpack的配置文件,为png文件添加一个单独的加载规则配置,test属性设置以.png结尾,use属性设置为刚安装的file-loader, 这样的话webpack在打包的时候就会以file-loader去处理图片文件啦。

const path = require('path');

module.exports = {
    entry: './src/main.js',
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist')
    },
    module: {
        rules: [
            {
                test: /.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /.png$/,
                use: 'file-loader'
            }
        ]
    }
}

打包过后(yarn webpack) 我们发现dist目录中多了一个图片文件,这个文件就是我们刚刚在代码中导入的图片,不过文件名称发生了改变,这个问题以后再进行介绍。

这张图片在bundle中是如何体现的呢?打开文件之后我们发现,比较简单,只是把我们刚刚生成的文件名称导出了。

/* 6 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = (__webpack_require__.p + "e177e3436b8f0b3cfff0fd836ea3472c.png");

/***/ })

然后我们看下入口木模块, 这里直接使用了导出的文件路径(__webpack_require__(6))
img.src = icon_png__WEBPACK_IMPORTED_MODULE_2_[“default”];

/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _heading_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
/* harmony import */ var _style_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2);
/* harmony import */ var _style_css__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_style_css__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var _icon_png__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6);

const heading = Object(_heading_js__WEBPACK_IMPORTED_MODULE_0__["default"])();

document.body.append(heading);

const img = new Image();

img.src = _icon_png__WEBPACK_IMPORTED_MODULE_2__["default"];

document.body.append(img);

/***/ })

我们启动这个应用,打开浏览器,发现图片并不能正常的加载,打开控制台终端我们可以发现,是直接加载了网站根目录的图片,而我们网站根目录并没有这个图片,所以没有找到。我们的图片应该在dist目录当中。

这个问题是由于我们的index.html并没有生成到dist目录,而是放在了项目的跟目录,所以这里把项目的跟目录作为了网站的跟目录,而webpack会认为所有打包的结果都会放在网站的跟目录下面,所以就造成了这样一个问题。

解决的方法非常简单,我们就是通过配置文件去告诉webpack,打包过后的文件最终在网站当中的位置,具体的做法就是在我们的配置文件当中的output位置,添加一个publicPath。

这个属性的默认值是一个空字符串,表示的就是网站的跟目录,我们这里因为我们生成的文件是放在dist目录下,所以我们设置为dist/。注意这里的斜线不能省略。

const path = require('path');

module.exports = {
    entry: './src/main.js',
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist'),
        publicPath: 'dist/'
    },
    module: {
        rules: [
            {
                test: /.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /.png$/,
                use: 'file-loader'
            }
        ]
    }
}

完成以后我们重新打包,我们打开打包后的文件,找到图片对应的位置,我们发现,这一次在文件名称前面拼接了一个变量。

/* 6 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = (__webpack_require__.p + "e177e3436b8f0b3cfff0fd836ea3472c.png");

/***/ })

这个变量是在webpack内部的代码所提供的。我们可以在运行代码中找到这个变量,可以发现,这个变量就是我们在配置中设置的publicPath(_webpack_require_.p = “dist/”😉

这也就解释了为什么public当中最后的/不能省略的原因。


/******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ 		}
/******/ 	};
/******/
/******/ 	// define __esModule on exports
/******/ 	__webpack_require__.r = function(exports) {
/******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ 		}
/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
/******/ 	};
/******/
/******/ 	// create a fake namespace object
/******/ 	// mode & 1: value is a module id, require it
/******/ 	// mode & 2: merge all properties of value into the ns
/******/ 	// mode & 4: return value when already ns object
/******/ 	// mode & 8|1: behave like require
/******/ 	__webpack_require__.t = function(value, mode) {
/******/ 		if(mode & 1) value = __webpack_require__(value);
/******/ 		if(mode & 8) return value;
/******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ 		var ns = Object.create(null);
/******/ 		__webpack_require__.r(ns);
/******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ 		return ns;
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "dist/";
/******/
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 0);
/******/ })

解决了这个问题之后我们来重新运行这个应用,回到浏览器,刷新一下页面,此时浏览器中的内容就能正常工作了。

此时我们再来总结一下文件加载器的工作过程。

webpack在打包时遇到了我们的图片文件,然后根据我们配置文件中的配置,拼配到对应的文件加载器,此时文件加载器就开始工作了,那他先是将我们导入的这个文件拷贝到输出的目录,然后再将我们文件拷贝到输出目录的那个路径作为当前这个模块的返回值返回,这样对于我们的应用来说,所需要的这个资源就被发布出来了,同时我们也可以通过模块的导出成员拿到这个资源的访问路径。

Webpack url加载器

除了file-loader这种通过copy物理文件的形式去处理文件资源以外还有一种通过date url的形式去表示文件。这种方式也非常常见。

Data URLs是一种特殊的url协议,可以用来直接去表示一个文件,传统的url要求服务器上有一个对应的文件,然后通过请求这个地址,得到服务器上对应的文件。

而Data URLs是一种当前url就可以去表示文件内容的方式,也就是说这种url当中的文本就已经包含了文件的内容。我们在使用这种url 的时候就不会再去发送任何的http请求。

data:[mediatype][;base64],<data>

data: -> 协议
[mediatype][;base64], -> 媒体类型和编码
<data> -> 文件内容

例如我们下面给出的这一段Data URLs浏览器可以根据这个url解析出来这是一个html类型的文件内容,编码是url-8,内容是一段包含h1的html代码。

data:text/html;charset=UTF-8,<h1>html content</h1>

如果是图片或者字体这一类无法通过文本去表示的2进制类型的文件。可以通过将文件的内容进行base64编码,以base64编码过后的结果也就是一个字符串,去表示这个文件内容。例如下面这样一段Data URLs

data:image/png;base64,iVBORw0KGgoAAAANSUhE...SuQmCC

这个url就是表示了一个png类型的文件, 文件的编码是base64,后面就是我们这张图片的base64编码。

当然了一般情况下我们base64的编码会比较长,浏览器同样可以解析出对应的文件。

在webpack打包静态资源模块时,我们同样可以使用这种方式去实现,那通过data urs我们就可以以代码的形式去表示任何类型的文件。具体做法就是我们需要用到一个专门的加载器,叫做 url-loader

yarn add url-loader --dev

在webpack的配置文件中找到之前的file-loader,将其修改为url-loader

const path = require('path');

module.exports = {
    entry: './src/main.js',
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist'),
        publicPath: 'dist/'
    },
    module: {
        rules: [
            {
                test: /.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /.png$/,
                use: 'url-loader'
            }
        ]
    }
}

此时webpack再去打包时,再遇到.png文件就会使用url-loader将其转换为Data URLs的形式。我们重新打包,可以发现,dist目录下已经没有png文件了,打开bundle.js文件查看。可以发现在最后的文件模块中,导出的不再是之前的文件路径,而是一个完整的Data URLs。

/* 6 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ("data:image/png;base64,iVBORw0KGgoAAAANSUh...AAAABJRU5ErkJggg==");

/***/ })

我们刚刚也了解到了,因为这个Data URLs当中就已经包含了文件内容,所以说我们就不需要再有独立的物理文件了。我们可以尝试着把这个完整的Data URLs复制下来,打开浏览器去尝试访问这个地址,这张图片同样可以正常显示出来。

我们启动服务,可以发现,同样可以正常显示。

data:image/png;base64,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

这种方式十分适合项目当中体积比较小的资源,因为如果说体积过大的话就会造成打包结果非常大,从而影响运行速度。

最佳的实践方式应该是对项目中的小文件通过url-loader转换成Data URLs然后在代码当中去嵌入,从而减少应用发送请求次数。对于较大的文件仍然通过传统的file-loader方式以单个文件方式去存放,从而提高应用的加载速度。

url-loader支持通过配置选项的方式去实现我们刚刚说的这种最佳实践的方式,在配置文件中,具体的做法就是将url-loader字符串配置方式修改为一个对象的配置方式,对象中使用loader定义url-loader,然后额外添加一个options属性用来去为他添加一些配置选项,其实每一个需要配置的loader都是通过这种方式去配置的。

这里为url-loader添加一个叫做limit的属性,将其设置为 10kb(10 * 1024), 这里的单位是字节,所以需要乘以1024。

这样我们url-loader就会只将10kb以下的文件转换成Data URLs,将超过10kb的文件,仍然会交给file-loader去处理。

const path = require('path');

module.exports = {
    entry: './src/main.js',
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist'),
        publicPath: 'dist/'
    },
    module: {
        rules: [
            {
                test: /.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /.png$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        limit: 10 * 1024
                    }
                }
            }
        ]
    }
}

重新进行打包,可以测试效果,如果是小于10kb的图片就会转换为Data URLs嵌入代码中去工作,如果是超过10kb的图片就会单独存放。这种方式就是实现我们刚刚所说的最佳实践了。

这里特别需要注意的是,如果按照这种方式去使用url-loader的话就一定要同时安装file-loader因为url-loader对于超出的文件还是会调用file-loader这个模块。例如我们删除掉file-loader重新打包,那么命令行就会报一个找不到file-loader的错误,这里需要注意。

yarn remove file-loader --dev
yarn webpack

Webpack 常用加载器分类

资源加载器有点像我们工厂里面的车间,是用来处理和加工我们打包过程中所遇到的资源文件,那除了以上介绍到的加载器,社区当中还有许多其他的加载器。

对于常用的loader之后我们基本都会用到,但是在这之前呢为了可以更好的了解他们我们先将loader大致分为三类,做一个归纳。

首先就是最常见的编译转换类型的加载器,这种类型的loader会把我们加载到的资源模块转换为JavaScript代码,例如之前用到的css-loader,就是将css代码转换为了bundle当中一个js模块,从而去实现通过JavaScript去运行我们的css。

其次就是文件操作类型的文件加载器,通常文件类型的加载器都会把加载到的资源模块拷贝到输出目录,同时又将这个文件的访问路径向外导出,例如之前用的file-loader就是一个非常典型的文件操作加载器。

最后还有一种针对代码质量检查的加载器,就是对我们所加载到的资源文件,一般是代码,去进行校验的一种加载器(eslint-loader),那这种加载器呢他的目的是为了统一我们的代码风格,从而去提高代码质量,那这种类型加载器一般不会去修改我们生产环境的代码。

以上三种就是我们针对于常用的loader做的一个归纳,后续我们在接触到一个loader过后呢,我们需要先去明确他到底是一个什么样的加载器,那他的特点是什么,使用又需要注意什么。

Webpack 与ES2015

由于webpack默认就可以处理我们代码中的import和export,所以很自然的会有人认为,webpack会自动编译ES6的代码,实则不然,webpack仅仅是对于模块去完成打包工作,所以说他才会对我们代码中的import和export做一些相应的转换,除此之外它并不能转换我们代码中其他的ES6代码。

我们可以将main.js中的var修改为const,执行打包,打开bundle.js可以发现,const并没有被webpack额外处理。

如果我们需要webpack在打包过程中同时处理其他ES6特性的转换,我们需要为js文件配置一个额外的编译型loader,例如最常见的就是babel-loader。

我们先通过yarn去安装babel-loader, 由于babel-loader需要依赖额外的babel核心模块,所以我们需要安装@babel/core模块和用于完成具体特性转换插件的一个集合叫做@babel/preset-env。

yarn add babel-loader @babel/core @babel/preset-env --dev

安装完成过后我们回到我们的配置文件中,这里我们为我们的js文件指定加载器为babel-loader, 这样的话babel-loader就会取代默认的加载器,在打包过程当中呢,就会帮我们处理我们代码当中的一些新特性了。

const path = require('path');

module.exports = {
    entry: './src/main.js',
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist'),
        publicPath: 'dist/'
    },
    module: {
        rules: [
            {
                test: /.js$/,
                use: 'babel-loader'
            }
            {
                test: /.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /.png$/,
                use: 'url-loader'
            }
        ]
    }
}

回到命令行当中,再次运行打包命令,打包过后我们查看bundle.js可以发现,const特性仍然没有被转换。其实这个原因我们前面已经介绍过了,因为babel严格上来说只是转换ES代码的一个平台。我们需要基于babel这样一个平台去通过插件来去转换我们代码当中一些具体的特性。

所以我们需要为babel去配置他需要使用的插件,回到配置文件当中,这里我们给babel-loader传入相应的配置就可以了,这里我们直接使用preset-env这样一个插件集合,因为这个集合当中就已经包含了全部的ES最新特性。

const path = require('path');

module.exports = {
    entry: './src/main.js',
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist'),
        publicPath: 'dist/'
    },
    module: {
        rules: [
            {
                test: /.js$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                }
            }
            {
                test: /.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /.png$/,
                use: 'url-loader'
            }
        ]
    }
}

完成以后我们再次回到命令行终端,重新打包,打包之后我们再去找到我们生成的代码,此时我们代码中的ES2015的一些新特性都被转换了。

总结一下就是,webpack默认只是一个打包工具,他不会去处理我们代码当中一些ES6或者更高版本的一些新特性,如果说你需要去处理这些新特性,我们可以通过为我们js代码单独去配置单独的加载器去实现。

Webpack加载资源的方式

除了代码中的import可以去触发模块的加载,webpack中还提供了其他几种方式,具体就是以下这几种。

首先第一个就是我们一直在用的也是最常用到的一种方式就是遵循ES Module标准的import声明。

import heading from './heading.js';
import icon from './icon.png';

其次就是遵循CommonJS标准的require函数,不过如果你去通过require函数去载入一个ES Module的话,那你需要注意,对于ES Module的默认导出,我们需要通过导入,也就是require这个函数导入的结果的default属性去获取。

const heading = require('./heading.js').default;
const icon = require('./icon.png');

最后呢,遵循AMD标准的define函数和require函数,webpack也同样是支持的。

define(['./heading.js', './icon.png', './style.css'], (createHeading, icon) => {
    const heading = createHeading();
    const img = new Image();
    img.src = icon;
    document.body.append(heading);
    document.body.append(icon);
});

require(['./heading.js', './icon.png', './style.css'], (createHeading, icon) => {
    const heading = createHeading();
    const img = new Image();
    img.src = icon;
    document.body.append(heading);
    document.body.append(icon);
})

那换句话说webpack兼容多种模块化标准,不过以个人经验来说,除非必要的情况,否则一定不要在项目中去混合使用这些标准,如果混合用的话,可能会大大降低我们项目的可维护性,我们每个项目只需要去统一使用一个统一的标准就可以了。

除了JavaScript代码中的这三种方式以外,还有一些加载器他在工作时也会去处理我们所加载到的资源当中一些导入的模块,例如我们css-loader加载的css文件(@import指令和url函数)

在这样的文件当中,我们import指令和部分属性中的url函数,他们也会去触发相应的资源模块加载。

@import '';

还有像html-loader去加载的html文件当中的一些src属性也会去触发相应的模块加载,针对于这两种方式我们需要一起去尝试一下。

首先我们先来尝试一下在样式文件当中导入资源模块, 我们现在入口文件导入样式文件。

main.js

import './main.css';

在main.css中我们通过background-image添加一张背景图片

body {
    min-height: 100vh;
    background-image: url(background.png);
    background-size: cover;
}

webpack在遇到css文件时会使用css-loader进行处理,处理的时候发现css中有引入图片,就会将图片作为一个资源模块加入到打包过程。webpack会根据配置文件当中,针对于遇到的文件去找到相应的loader,此时这张图片是一张png图片,就会将这个图片交给url-loader去处理。

打包过后,启动服务,可以发现图片是可以正常显示的。这就表示在css-loader加载的样式文件当中url函数确实可以触发模块的加载。

样式文件当中除了属性文件使用的url函数还有import指令,他同样支持加载其他的样式资源模块。新建一个reset.css文件,随便写点什么,然后在main.css中引入该文件。

@import url(reset.css);
body {
    min-height: 100vh;
    background-image: url(background.png);
    background-size: cover;
}

重新打包运行,可以发现reset.css已经工作了,这也就意味着,刚刚的reset.css确实被打包进了我们bundle文件。

以上就是css-loader再去加载样式时,样式文件当中会去触发文件加载的两种方式。

接下来我们再来看看html文件当中加载资源的一些方式,因为html文件当中也会有引用其他文件的可能性,例如img标签的src,所以说我们这里需要再去尝试一下。

我们在项目中添加一个src/footer.html文件,在这个文件中我们只是通过简单的img标签去引入一张图片。

<footer>
    <img src="better.png" />
</footer>

完成以后我们再回到打包入口当中。我们需要导入footer.html文件,这样他才能参与我们webpack打包的过程,我们同样使用import去打包这个文件, 不过html文件默认会将html代码作为默认导出,所以我们这里需要接收一下导出的字符串。然后我们将它通过document.write的方式将它输出到页面。

import './main.css';

import footer from './footer.html';

document.write(footer);

那这个html模块准备好了过后,我们还需要为这个html模块配置对应的loader,否则webpack是没办法认识html模块的,我们通过yarn去安装一个html-loader

yarn add html-loader --dev

安装之后打开webpack的配置文件,为扩展名为html的文件去配置使用这个loader, 完成之后打包重启。可以看到html代码中的img标签所对应的图片可以正常显示出来。这就说明html文件当中src属性也可以去触发资源模块的加载。

const path = require('path');

module.exports = {
    entry: './src/main.js',
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist'),
        publicPath: 'dist/'
    },
    module: {
        rules: [
            {
                test: /.js$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                }
            }
            {
                test: /.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /.png$/,
                use: 'url-loader'
            },
            {
                test: /.html$/,
                use: 'html-loader'
            }
        ]
    }
}

但是我们在html文件当中不仅仅只有img的src需要去依赖资源,其他的标签也有可能需要一些资源的依赖,例如a标签的href属性。

比如a标签点击需要去加载应用当中的一个文件,这个地方我们就来尝试一下,我们回到html中去添加一个a标签, 然后我们将这个a标签的href属性指向一个文件资源。

<footer>
    <!-- <img src="better.png" /> -->
    <a href="better.png">download png</a>
</footer>

我们重新打包,启动服务,回到浏览器,刷新过后我们发现去点击这个a标签过后我们确找不到这个a标签所对应的这个文件。针对于这个问题是因为html-loader默认只会去处理img标签的src属性,如果我们需要其他标签的一些属性也能够触发打包的话,我们可以去相应的一些配置

具体的做法就是给html-loader添加一个attrs的一个属性, 也就是我们html加载的时候对页面上的属性做额外的处理,这个属性默认当中只有img:src,也就是表示img标签的src属性。

那我们就根据这样一个使用的规范,我们去添加一个a:href属性,让他去能支持我们a标签的href属性。

const path = require('path');

module.exports = {
    entry: './src/main.js',
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist'),
        publicPath: 'dist/'
    },
    module: {
        rules: [
            {
                test: /.js$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                }
            }
            {
                test: /.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /.png$/,
                use: 'url-loader'
            },
            {
                test: /.html$/,
                use: {
                    loader: 'html-loader',
                    options: {
                        attrs: ['img:src', 'a:href']
                    }
                }
            }
        ]
    }
}

完成以后我们再次回到命令行中运行打包。在打包的结果中我们就可以看到a标签用到的资源已经参与了打包。回到浏览器刷新页面,点击a标签就可以正常找到依赖文件。

通过刚刚的这些尝试我们发现,几乎我们在代码当中所有需要引用到的资源,就是有引用资源的可能性的地方,都会被webpack找出来,然后根据我们的配置,交给不同的loader去处理,最后将处理的结果整体打包到输出目录。webpack就是通过这样一个特点去实现我们整个项目的模块化。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页