概述

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

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

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

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社区提供了非常多的资源加载器,基本上开发者能想到的合理的需求都有对应的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在打包时遇到了我们的图片文件,然后根据我们配置文件中的配置,拼配到对应的文件加载器,此时文件加载器就开始工作了,那他先是将我们导入的这个文件拷贝到输出的目录,然后再将我们文件拷贝到输出目录的那个路径作为当前这个模块的返回值返回,这样对于我们的应用来说,所需要的这个资源就被发布出来了,同时我们也可以通过模块的导出成员拿到这个资源的访问路径。