webpack5 | css 的各种 loader 是怎么工作的

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情

简介

本文包含的 loader 如下

  • sass-loader(scss 转 css) git 仓库
  • postcss-loader (css 加各种兼容前缀)git 仓库
  • css-loader(将 css 文件转化成 webpack module ,支持 import 等) git 仓库
  • style-loader (将 css 文件以 style 标签方式放到 html head 中) git 仓库

包含的 plugin 如下

  • mini-css-extract-plugin(提取 css 成单独文件) git 仓库
  • html-webpack-plugin(将输出的 bundle.js、style.css 等自动放入模板 html 中) git 仓库

npm install

sh 复制代码
# 基础三件套,sass 需要 额外安装 node-sass
npm i style-loader css-loader sass-loader node-sass -D

# postcss ,额外增加 autoprefixer 插件自动补全
npm i postcss postcss-loader postcss-preset-env autoprefixer -D

# plugin
npm i mini-css-extract-plugin -D
npm i html-webpack-plugin -D

loader 流程图

原始 .scss 样式

我们来看一个 scss 文件通过各种哪个 loader 转换后各个输出是什么样子的。

下面是开发人员写的 scss 样式。

css 复制代码
.box {
    background-color: blue;
    padding: 20px;
    display: flex;

    .title {
        color: #00000066;
        flex: 1;
        transform: rotate(90deg);
    }
}

sass-loader 编译后

嵌套的样式等等 sass 高阶样式都转换成平铺的 css 样式

css 复制代码
.box {
  background-color: blue;
  padding: 20px;
  display: flex;
}
.box .title {
  color: rgba(0, 0, 0, 0.4);
  flex: 1;
  transform: rotate(90deg);
}

postcss-loader 编译后

需要额外配置 postcss.config.js 文件 ,包含需要使用的插件

ini 复制代码
const config = {
  plugins: [require("autoprefixer")], // 自动根据 caniuse 里面的兼容性自动补全各种浏览器前后缀
};

module.exports = config;

同时,额外配置 package.json 中的 browserslist 指定索要支持的浏览器类型。

经过 postcss-loader 编译后文件如下

css 复制代码
.box {
  background-color: blue;
  padding: 20px;
  display: -webkit-box;
  display: -webkit-flex;
  display: -moz-box;
  display: flex;
}
.box .title {
  color: rgba(0, 0, 0, 0.4);
  -webkit-box-flex: 1;
  -webkit-flex: 1;
     -moz-box-flex: 1;
          flex: 1;
  -webkit-transform: rotate(90deg);
     -moz-transform: rotate(90deg);
      -ms-transform: rotate(90deg);
          transform: rotate(90deg);
}

css-loader 编译后

  • 将上述代码转换成 js 代码,
  • 支持 import 语法导入到 js 文件中,
  • 开启 module 支持模式
  • 变量重命名,避免重复等问题,比如下面的 U0owno48RksV_sCqH1K_,也可以额外配置命名规则 localidentname, 让 classname 更语义化,方便阅读
js 复制代码
import ___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___ from "../node_modules/css-loader/dist/runtime/noSourceMaps.js";
import ___CSS_LOADER_API_IMPORT___ from "../node_modules/css-loader/dist/runtime/api.js";
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___);
// Module
___CSS_LOADER_EXPORT___.push([module.id, ".U0owno48RksV_sCqH1K_ {\n  background-color: blue;\n  padding: 20px;\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -moz-box;\n  display: flex;\n}\n.U0owno48RksV_sCqH1K_ ._10LGwUfHrljqaYJfVKI {\n  color: rgba(0, 0, 0, 0.4);\n  -webkit-box-flex: 1;\n  -webkit-flex: 1;\n     -moz-box-flex: 1;\n          flex: 1;\n  -webkit-transform: rotate(90deg);\n     -moz-transform: rotate(90deg);\n      -ms-transform: rotate(90deg);\n          transform: rotate(90deg);\n}", ""]);
// Exports
export var box = "U0owno48RksV_sCqH1K_";
export var title = "_10LGwUfHrljqaYJfVKI";
export default ___CSS_LOADER_EXPORT___;

页面可以使用 import * as styles from "./index.module.scss"; 方式引入使用。

这里相当于默认用了 CSS Modules 的方式

ts 复制代码
import React, { useEffect, useRef } from "react";
import * as styles from "./index.module.scss";

interface Props {
  text?: string; // 要绘制的文本
}

const ShowHello = ({ text = "" }: Props) => {
  return (
    <div className={styles.box}>
      <div style={{ background: "pink" }}>Hello word,{text}</div>
    </div>
  );
};

export default ShowHello;

style-loader 编译后

style-loader 编译后,将 css 文件以 <style>.xxx</style> 方式放到 html 中。

html 复制代码
<html>
...
<head>
<style>
.U0owno48RksV_sCqH1K_ {
  background-color: blue;
  padding: 20px;
  display: -webkit-box;
  display: -webkit-flex;
  display: -moz-box;
  display: flex;
}
.U0owno48RksV_sCqH1K_ ._10LGwUfHrljqaYJfVKI {
  color: rgba(0, 0, 0, 0.4);
  -webkit-box-flex: 1;
  -webkit-flex: 1;
     -moz-box-flex: 1;
          flex: 1;
  -webkit-transform: rotate(90deg);
     -moz-transform: rotate(90deg);
      -ms-transform: rotate(90deg);
          transform: rotate(90deg);
}</style>
</head>
<body>
  <div id="root">
      <div class="U0owno48RksV_sCqH1K_"> 
          <div style="background: pink;">Hello word,Nice day!</div>
      </div>
  </div>
  <script src="bundle.js"></script>

</body></html>

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第54天,点击查看活动详情

一般会在 dev 环境下使用。

生产环境一般后面的插件 mini-css-extract-plugin,单独提取 css 文件。

mini-css-extract-plugin 提取 css

引入插件,提取生成 style.css 文件,存在内存中,和 bundle.js 一样,

js 复制代码
// webpack plugin
 plugins: [
    new MiniCssExtractPlugin({
      filename: "style.css", // 定义的 CSS 文件名
    }),
  ],

配置 loader

js 复制代码
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

// webpack module 配置  
module:{
    {
        test: /\.scss$/,
        use:[..., MiniCssExtractPlugin.loader,"css-loader",...] // 引入 loader
    }
}

因为 style.css 文件在内存中,需要手动引入到 html 文件中,以 <link href ... /> 方式。

html 复制代码
// head 中手动下面一行内容
<link href="style.css" rel="stylesheet" />

或者自动引入,增加 html-webpack-plugin 插件,自动会引入打包后的 js 和 css 文件到 head 中

js 复制代码
const HtmlWebpackPlugin = require("html-webpack-plugin");
// webpack plugin 内容
plugins: [
 new HtmlWebpackPlugin({
      template: path.join(__dirname, "../example/public/index.html"), // 使用 xxx 模板
      inject: "body", // 将 js 注入到 body 中(不写这个会默认放到 head 中)
 }),
]

webpack 最终配置

js 复制代码
const path = require("path");
const { merge } = require("webpack-merge");
const baseConfig = require("./webpack.base.js"); // 公共配置
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

const devConfig = {
  mode: "development", // 开发模式
  entry: path.join(__dirname, "../example/src/index.tsx"), // 入口,处理资源文件的依赖关系
  output: {
    path: path.join(__dirname, "../example/public/"),
    filename: "bundle.js",
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.scss$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          "postcss-loader",
          "sass-loader",
        ],
      },
    ],
  },
  devServer: {
    static: path.join(__dirname, "../example/public/"),
    compress: true,
    host: "127.0.0.1",
    port: 4000, // 启动端口
    open: false, // 打开浏览器
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "style.css", // 提取的 CSS 文件名
    }),
    new HtmlWebpackPlugin({
      template: path.join(__dirname, "../example/public/index.html"),
      inject: "body",
    }),
  ],
};
module.exports = merge(devConfig, baseConfig); // 合并配置

参考

Webpack Loader 的输入输出是什么?
postcss 官网

相关推荐
范文杰3 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪3 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪3 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy4 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom5 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom5 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom5 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom5 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom5 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试
LaoZhangAI6 小时前
2025最全GPT-4o图像生成API指南:官方接口配置+15个实用提示词【保姆级教程】
前端