webpack单独提取css做cacheGroups无效?

作者站长头像
站长
· 阅读数 6

是这样的:我想用张鑫旭老师说的 这个方法 实现网页换肤功能。

这个方法就是先将皮肤样式文件先用link标签预加载,然后用JS动态切换disabled得值来切换样式文件,实现换肤。

然后我就想,先编写好样式文件,然后在vue工程中的入口文件main.js中导入皮肤样式文件,如下:

import 'light.scss'; 
import 'dark.scss';

,在结合webpack,让webpack单独对这两个样式文件打包,然后dist/css文件夹下就会有light.css和dark.css文件,再通过配置webpack对link标签增加alternate属性,然后我就能在项目内动态通过JS来控制light.css和dark.css的加载了。

但是我在实现的过程中遇到了一些问题,我发现我没办法让webpack单独生成light.css和dark.css。我是写了一些webpack配置的,但是就是没法生成出来,以下是我的代码。

const { defineConfig } = require("@vue/cli-service");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const distName = () => {
  return `dist_${Math.round(Math.random() * 100)}`;
};

module.exports = defineConfig({
  outputDir: distName(),
  configureWebpack: {
    optimization: {
      splitChunks: {
        cacheGroups: {
          lightStyle: {
            priority: 20,
            name: "lightStyle",
            test(module) {
              if (/light\.scss/.test(module.rawRequest)) {
                return true
              }
              return false
            },
            enforce: true
          },
          darkStyle: {
            priority: 20,
            name: "darkStyle",
            test(module) {
              if (/dark\.scss/.test(module.rawRequest)) {
                return true
              }
              return false
            },
            enforce: true
          }
        },
      },
    },
    plugins: [
      new MiniCssExtractPlugin({
        filename: (params, p2) => {
          //console.log("filename-----------", params, p2);
          return "css/[name].[contenthash:8].css";
        },
        chunkFilename: (params) => {
          //console.log("chunkFilename-----------", params);
          return "css/[name].[contenthash:8].css";
        },
      }),
    ],
  },
});

一开始我是想通过MiniCssExtractPlugin中的filename和chunkFileName来给皮肤样式文件生成固定的名称来实现的,但是我实践的过程中发现他们的传参params里面都是一些chunk信息,根本没法知道那个chunk是light.sccs和dark.scss。

然后我又想到了使用cacheGroups来做这件事,通过test函数我可以获取到module的信息,因为module里面的数据可以让我清楚知道那个module是light.scss和dark.scss。所以编码很顺利,但是当我执行npm run build之后,结果却是意料之外,dist文件夹下并没有light.css和dark.css文件,而是被打包进dist/css/app.122323as.css文件中,请问这是怎么回事?


我找到问题了,是因为我没有写chunks属性,我修改了下原来代码:

        cacheGroups: {
          lightStyle: {
            priority: 20,
            name: "lightStyle",
            chunks: (chunks) => {
              return chunks.name === 'app'
            },
            test(module) {
              if (/light\.scss/.test(module.rawRequest)) {
                return true
              }
              return false
            },
            enforce: true
          },
          darkStyle: {
            priority: 20,
            name: "darkStyle",
            chunks: (chunks) => {
              return chunks.name === 'app'
            },
            test(module) {
              if (/dark\.scss/.test(module.rawRequest)) {
                return true
              }
              return false
            },
            enforce: true
          }
        },

但是结果还是不是我想要的,我发现它生成的不是light.css和dark.css而是light.js和dark.js。要怎么样配置才能达到我想要的?

回复
1个回答
avatar
test
2024-07-10

这个问题经过几天的研究,我已经解决了,我使用的方法是:“基于webpack打包入口提取CSS”,

这是我在官方文档中看到的一个办法。

answer image

以下是我的webpack代码:


const { defineConfig } = require("@vue/cli-service");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const htmlWebpackInjectAttributesPlugin = require("html-webpack-inject-attributes-plugin");
const path = require("path");
const HtmlWebpackExcludeAssetsPlugin = require("html-webpack-exclude-assets-plugin-webpack5");


const themeReg = /^(.*)((light|dark|default)\..+\.css)$/;
const themeRegJS = /^(.*)((light|dark|default).*\.js)$/;
const themeLinkJudge = (tagName, attributes) => {
  return (
    tagName === "link" && attributes.href && themeReg.test(attributes.href)
  );
};

module.exports = defineConfig({
  outputDir: "ThemeResearch",
  publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
  chainWebpack: (config) => {

    // dev环境下vue使用的不是MiniCssExtractPlugin来提取css生成css文件,而是使用vue-style-loader来生成style标签,
    // 因为我们需要生成文件,所以这里即使dev环境下也使用MiniCssExtractPlugin
    config
      .plugin("MiniCssExtractPlugin")
      .use(MiniCssExtractPlugin, [
        {
          filename: () => {
            return "css/[name].[contenthash:8].css";
          },
          chunkFilename: () => {
            return "css/[name].[contenthash:8].css";
          },
        },
      ])
      .end();
    

      // 让normal这个module不去匹配我们的主题文件
    config.module
      .rule("scss")
      .oneOf("normal")
      .test(/^((?!light\.scss|dark\.scss|default\.scss).)*$/)
      .end();

      // 定义自己的module来使用我们自己的loader
    const theme = config.module
      .rule("scss")
      .oneOf("ownTheme")
      .test(/.*(light|dark|default)\.scss$/);

      // 清除vue-cli写好的loader,改用自己写的
    theme.uses.clear();

    theme
      .use(MiniCssExtractPlugin.loader)
      .loader(MiniCssExtractPlugin.loader)
      .end()
      .use("css-loader")
      .loader("css-loader")
      .end()
      .use("postcss-loader")
      .loader("postcss-loader")
      .end()
      .use("sass-loader")
      .loader("sass-loader")
      .end();


      // 增加自定义标签属性
    config
      .plugin("html")
      .tap((args) => {
        args[0].attributes = {
          'data-theme': function (tag) {
            const { attributes, tagName } = tag;
            if (themeLinkJudge(tagName, attributes)) {
              return 'true'
            }
            return false
          },
          rel: function (tag) {
            const { attributes, tagName } = tag;
            if (themeLinkJudge(tagName, attributes)) {
              if (attributes.href.indexOf('default') > -1) {
                return "stylesheet"
              }
              return "alternate stylesheet";
            } else if (tagName === 'link') {
              return "stylesheet"
            }
            return false;
          },
          title: function (tag) {
            const { attributes, tagName } = tag;
            if (themeLinkJudge(tagName, attributes)) {
              if (attributes.href.indexOf('light') > -1) {
                return 'light'
              } else if (attributes.href.indexOf('dark') > -1) {
                return 'dark'
              } else if (attributes.href.indexOf('default') > -1) {
                return 'default'
              } else {
                return false
              }
            }
          }
        };

        args[0].excludeAssets = [themeRegJS];
        return args;
      })
      .end();


      // HtmlWebpackPlugin生成HTML文件时,这个插件能给生成的标签注入自己需要的属性
    config
      .plugin("htmlInjectAttribute")
      .after("html")
      .use(htmlWebpackInjectAttributesPlugin)
      .end();


      // 移除多入口生成的无用script标签和JS文件
    config
      .plugin("ExcludeAssets")
      .after("htmlInjectAttribute")
      .use(HtmlWebpackExcludeAssetsPlugin)
      .end();
  },
  configureWebpack: {
    entry: {
      app: path.resolve(__dirname, "src/main.js"),
      default: path.resolve(__dirname, "src/theme/default.js"),
      light: path.resolve(__dirname, "src/theme/light"),
      dark: path.resolve(__dirname, "src/theme/dark"),
    },
  },
});
 
回复
likes
适合作为回答的
  • 经过验证的有效解决办法
  • 自己的经验指引,对解决问题有帮助
  • 遵循 Markdown 语法排版,代码语义正确
不该作为回答的
  • 询问内容细节或回复楼层
  • 与题目无关的内容
  • “赞”“顶”“同问”“看手册”“解决了没”等毫无意义的内容