通过css-loader讲透webpack loader机制(上)

340 阅读3分钟

webpack 中loader的作用是把不是 js 编译成 js 文件。比如,在开发 vue 项目的时候,写的全部是.vue结尾的文件,这种文件是无法运行在浏览器中的,因此需要vue-loader转换成 js 文件。下面我们来演示如果通过 css-loader 打包 css 文件的。

创建项目

  1. npm init 初始化项目
  2. 创建 src/index.js
  3. 创建 public/index.html
  4. 创建 webpack.config.js,并写入配置
  5. 执行 npm install -D webpack webpack-cli
  6. 配置 build 命令为 webpack
  7. 执行 npm run build

主要代码如下:

webpack.config.js

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'bundle.js'
  }
}

index.html 和 index.js

// index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>webpack</title>
    <script src="../dist/bundle.js"></script>
  </head>
  <body></body>
</html>

// index.js
console.log('hello webpack')

packge.json

"scripts": {
  "build": "webpack"
},

执行npm run build,生成 bundle.js 文件,代码精简后如下:

;(() => {
  // 所有的模块都放在__webpack_modules__对象中
  var __webpack_modules__ = {
    './src/index.js': () => {
        console.log('hello webpack')
      )
    }
  }
  // 定义了一个输出,现在入口文件是没有输出的
  var __webpack_exports__ = {}
  // 执行index.js,打印 hello webpack
  __webpack_modules__['./src/index.js']()
})()

利用 css-loader 解析 css 文件

当在入口文件引入css之后,代码如下:

// index.css
.test {
  width: 100px;
  height: 100px;
  background-color: rebeccapurple;
}

// index.js
import './index.css'
console.log('hello webpack')

// index.html
<body>
  <div class="test"></div>
</body>

如果这个时候直接执行打包操作,会报错。

image.png

这是因为 webpack 不能识别 css 文件,所以需要安装css-loader,配置文件如下:

module.exports = {
    mode: 'development',
    entry: './src/index.js',
    devtool: 'source-map',
    output: {
        path: path.resolve(__dirname, './dist'),
        filename: 'bundle.js'
    },
    module: {
      rules: [
        {
          test: /\.css$/,
          use: ['css-loader']
        }
      ]
    }
}

执行npm run build,打包后运行发现浏览器没有显示div的样式,这是什么原因呢?接下来,分析下打包后的文件 bundle.js。

通过对 bundle.js 代码精简后如下:

;(() => {
  'use strict'
  var __webpack_modules__ = {
    './src/index.css': (module, __webpack_exports__, __webpack_require__) => {
      __webpack_require__.d(__webpack_exports__, {
        default: () => __WEBPACK_DEFAULT_EXPORT__
      })
      
      var ___CSS_LOADER_EXPORT___ = []
      // Module
      ___CSS_LOADER_EXPORT___.push([
        module.id,
        '.test {\n  width: 100px;\n  height: 100px;\n  background-color: rebeccapurple;\n}\n'
      ])
      const __WEBPACK_DEFAULT_EXPORT__ =
        ___CSS_LOADER_EXPORT___
    }
  }
  var __webpack_module_cache__ = {}

  function __webpack_require__(moduleId) {
    // Check if module is in cache
    var cachedModule = __webpack_module_cache__[moduleId]
    if (cachedModule !== undefined) {
      return cachedModule.exports
    }
    // Create a new module (and put it into the cache)
    var module = (__webpack_module_cache__[moduleId] = {
      id: moduleId,
      // no module.loaded needed
      exports: {}
    })

    // Execute the module function
    __webpack_modules__[moduleId](module, module.exports, __webpack_require__)

    // Return the exports of the module
    return module.exports
  }

  var __webpack_exports__ = {}
  
  ;(() => {
    // 入口文件
    var _index_css__WEBPACK_IMPORTED_MODULE_0__ =
      __webpack_require__('./src/index.css')
    console.log('hello webpack')
  })()
})()

入口文件 index.js 经过 webpack 编译后变成了如下代码:

__webpack_require__.r(__webpack_exports__)
var _index_css__WEBPACK_IMPORTED_MODULE_0__ =
  __webpack_require__('./src/index.css')
console.log('hello webpack')

可以发现 import './index.css' 变成了 var _index_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__('./src/index.css'),其中 __webpack_require__就类似于nodejs 中的 require 方法,在这里 webpack 实现了一个require方法。

function __webpack_require__(moduleId) {
    // moduleId就是文件路径,也就是 './src/index.css'
    // 首先查找缓存
    var cachedModule = __webpack_module_cache__[moduleId]
    if (cachedModule !== undefined) {
      return cachedModule.exports
    }
    // 如果没有缓存,那么生成一个module对象,这个对象包含一个id和exports对象
    var module = (__webpack_module_cache__[moduleId] = {
      id: moduleId,
      exports: {}
    })

    // 执行 __webpack_modules__对象里面 key 为 './src/index.css'的值,即一个函数,在这个函数里面,把module和__webpack_require__传入
    __webpack_modules__[moduleId](module, module.exports, __webpack_require__)

    // 最后返回module
    return module.exports
}

那现在看看 index.css 编译后的代码:

(module, __webpack_exports__, __webpack_require__) => {
  __webpack_require__.d(__webpack_exports__, {
    default: () => __WEBPACK_DEFAULT_EXPORT__
  })
  var ___CSS_LOADER_EXPORT___ = []
  // 把css文件放到___CSS_LOADER_EXPORT___数组中,如果引入了多个css文件,就都放里面
  ___CSS_LOADER_EXPORT___.push([
    module.id, // id为文件路径
    // css代码变为字符串
    '.test {\n  width: 100px;\n  height: 100px;\n  background-color: rebeccapurple;\n}\n' 
  ])
  const __WEBPACK_DEFAULT_EXPORT__ =
    ___CSS_LOADER_EXPORT___
}

我们可以把___CSS_LOADER_EXPORT___打印出来:

image.png

经过上面的分析,index.css文件最终转化字符串,然后放到_index_css__WEBPACK_IMPORTED_MODULE_0__数组中,但是在index.js中并没有使用这个数组,所以我们就看不到css样式效果,那怎么办呢?

那我们需要就需要安装style-loader,这个loader的作用是把上面生成的_index_css__WEBPACK_IMPORTED_MODULE_0__,转化为<style>标签插入到<head>中,这样样式就生效了。

下一节,我们将介绍style-loader如何生成<style>标签并插入到<head>中的。