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

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

简介

  • 基于 webpack@5.82.0

本文包含的 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 官网

相关推荐
惜.己7 分钟前
Jmeter中的配置原件(四)
java·前端·功能测试·jmeter·1024程序员节
EasyNTS9 分钟前
无插件H5播放器EasyPlayer.js网页web无插件播放器vue和react详细介绍
前端·javascript·vue.js
guokanglun32 分钟前
Vue.js动态组件使用
前端·javascript·vue.js
Go4doom35 分钟前
vue-cli3+qiankun迁移至rsbuild
前端
-seventy-1 小时前
Ajax 与 Vue 框架应用点——随笔谈
前端
我认不到你1 小时前
antd proFromSelect 懒加载+模糊查询
前端·javascript·react.js·typescript
集成显卡1 小时前
axios平替!用浏览器自带的fetch处理AJAX(兼容表单/JSON/文件上传)
前端·ajax·json
焚琴煮鹤的熊熊野火1 小时前
前端垂直居中的多种实现方式及应用分析
前端
我是苏苏2 小时前
C# Main函数中调用异步方法
前端·javascript·c#
转角羊儿2 小时前
uni-app文章列表制作⑧
前端·javascript·uni-app