tribute2ro
tribute2ro

Reputation: 390

How to setup rspack with Vuejs SFC to use css modules?

I have a project set up that uses rspack with VueJS 3 and I would like to be able to use CSS modules within the single file components. However I am unable to get this to work and I don't understand why.

My rspack.config.js file is as follows:

const rspack = require('@rspack/core')
const refreshPlugin = require('@rspack/plugin-react-refresh')
const isDev = process.env.NODE_ENV === 'development'
const { VueLoaderPlugin } = require('vue-loader');

/**
 * @type {import('@rspack/cli').Configuration}
 */
module.exports = {
  context: __dirname,
  entry: {
    main: './src/index.ts',
  },
  devServer: {
    historyApiFallback: true,
    port: 8081,
  },
  resolve: {
    extensions: ['.js','.ts','.vue','.json']
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          experimentalInlineMatchResource: true,
        },
      },
      {
        test: /\.svg$/,
        //type: 'asset',
        loader: 'svg-inline-loader'
      },
      {
        test: /\.css$/,
        use: [
          {
            loader: "vue-style-loader",
            options: {
              esModule: true
            }
          },
          {
            loader: "css-loader",
            options: {
              module: {
                localIdentName: "[local]__[hash:base64:8]",
                namedExport: false
              }
            }
          }
        ],
        type: "javascript/auto",
      },
      {
        test: /\.(jsx?|tsx?)$/,
        use: [
          {
            loader: 'builtin:swc-loader',
            options: {
              sourceMap: true,
              jsc: {
                parser: {
                  syntax: 'typescript',
                  tsx: true,
                },
                transform: {
                  react: {
                    runtime: 'automatic',
                    development: isDev,
                    refresh: isDev,
                  },
                },
              },
              env: {
                targets: [
                  'chrome >= 87',
                  'edge >= 88',
                  'firefox >= 78',
                  'safari >= 14',
                ],
              },
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new VueLoaderPlugin(),
    new rspack.container.ModuleFederationPlugin({
      name: 'ActiveComms',
      filename: 'remoteEntry.js',
      remotes: {
        Account: 'Account@http://localhost:8080/remoteEntry.js',
        //vite_host: 'vite_host@http://localhost:8084/assets/remoteEntry.js'
      },
      exposes: {
        './moduleB': './src/components/moduleB.vue',
        './NavbarLinks': './src/components/NavbarLinks.vue',
        './OrangeBoxComms': './src/components/OrangeBoxComms.vue'
      },
      shared: {
        vue: { eager: true }
      },
    }),
    new rspack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
    }),
    new rspack.ProgressPlugin({}),
    new rspack.HtmlRspackPlugin({
      template: './src/index.html',
    }),
    isDev ? new refreshPlugin() : null,
  ].filter(Boolean),
}

My tsconfig.json is as follows:

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "strict": true,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "baseUrl": ".",
    "types": [],
    "paths": {
      "@/*": ["src/*"]
    },
    "lib": ["ESNext", "DOM", "DOM.Iterable", "ScriptHost"]
  },
  "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "tests/**/*.ts", "tests/**/*.tsx"],
  "exclude": ["node_modules", "dist"]
}

My package.json is as follows:

{
  "name": "rs-host",
  "version": "1.0.0",
  "scripts": {
    "build": "NODE_ENV=production rspack build",
    "build:dev": "NODE_ENV=development rspack build",
    "build:start": "cd dist && rspack serve",
    "start": "NODE_ENV=development rspack serve"
  },
  "devDependencies": {
    "@rspack/cli": "0.5.4",
    "@rspack/core": "0.5.4",
    "@rspack/plugin-react-refresh": "0.5.4",
    "@types/vue": "^2.0.0",
    "autoprefixer": "^10.1.0",
    "css-loader": "^7.1.1",
    "react-refresh": "^0.14.0",
    "style-loader": "^4.0.0",
    "svg-inline-loader": "^0.8.2",
    "vue-loader": "^16.8.1",
    "vue-style-loader": "^4.1.3",
    "vue-template-compiler": "^2.6.14"
  },
  "dependencies": {
    "vue": "^3.2.19"
  }
}

And an example of one of my single file components is as follows:

<template>
    <div :class="$style.contentArea">
        <div v-if="tab == 'default'">
            <div>Name: rs-host</div>
            <div>Framework: vue3</div>
            <div>Language: TypeScript</div>
            <div>CSS: Empty CSS</div>

            <h2>ActiveComm's Server</h2>
        </div>
        <ChannelManagement v-if="tab == 'ChannelManagement'" />
        <FeatureSelections v-if="tab == 'FeatureSelections'" />
        <RadioBridgeConfiguration v-if="tab == 'RadioBridgeConfiguration'" />
    </div>
</template>
  
<script lang="ts">
    import * as Vue from 'vue';

    import WebUtil from './util/WebUtil';

    import ChannelManagement from './pages/ChannelManagement.vue';
    import FeatureSelections from './pages/FeatureSelections.vue';
    import RadioBridgeConfiguration from './pages/RadioBridgeConfiguration.vue';

    export default {
        components: {
            ChannelManagement,
            FeatureSelections,
            RadioBridgeConfiguration
        },
        created() {
            let tabParam : string | null = WebUtil.getUrlParam('tab');
            if(tabParam) {
                this.tab = tabParam;
            }
        },
        data() : {
            tab : string
        } {
            return {
                tab: 'default'
            }
        },
        methods: {
            
        },
        computed: {
            
        },
        props: [
            
        ]
    }
</script>

<style module lang="css">
    #contentArea {
        position: absolute;
        left: 280px;
        right: 0px;
        top: 0px;
        height: 100vh;
        overflow-y: auto;
    }

    .contentArea {
        position: absolute;
        left: 280px;
        right: 0px;
        top: 0px;
        height: 100vh;
        overflow-y: auto;
    }
</style>

When the div in the single file component is rendered, it has a class attribute with no value like so: <div class>.

I'd like to know what I'm doing incorrectly so that I can have css modules working correctly.

Upvotes: 1

Views: 278

Answers (1)

tribute2ro
tribute2ro

Reputation: 390

I'm not sure why what I had before wasn't working, but I finally got it to work as expected with the following rspack.config.js.

const rspack = require('@rspack/core')
const refreshPlugin = require('@rspack/plugin-react-refresh')
const isDev = process.env.NODE_ENV === 'development'
const { VueLoaderPlugin } = require('vue-loader');

/**
 * @type {import('@rspack/cli').Configuration}
 */
module.exports = {
  context: __dirname,
  entry: {
    main: './src/index.ts',
  },
  devServer: {
    historyApiFallback: true,
    port: 8081,
  },
  resolve: {
    extensions: ['.js','.ts','.vue','.json']
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          experimentalInlineMatchResource: true,
        },
      },
      {
        test: /\.svg$/,
        //type: 'asset',
        loader: 'svg-inline-loader'
      },
      {
        test: /\.module\.css$/i,
        type: "css/module", // this is enabled by default for module.css,   so you don't need to specify it
      },
      {
        test: /\.css$/,
        use: [
          "vue-style-loader",
          {
            loader: "css-loader",
            options: {
              modules: {
                localIdentName: "[local]__[hash:base64:8]",
                namedExport: false,
              },
            },
          },
        ],
      },
      {
        test: /\.(jsx?|tsx?)$/,
        use: [
          {
            loader: 'builtin:swc-loader',
            options: {
              sourceMap: true,
              jsc: {
                parser: {
                  syntax: 'typescript',
                  tsx: true,
                },
                transform: {
                  react: {
                    runtime: 'automatic',
                    development: isDev,
                    refresh: isDev,
                  },
                },
              },
              env: {
                targets: [
                  'chrome >= 87',
                  'edge >= 88',
                  'firefox >= 78',
                  'safari >= 14',
                ],
              },
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new VueLoaderPlugin(),
    new rspack.container.ModuleFederationPlugin({
      name: 'ActiveComms',
      filename: 'remoteEntry.js',
      remotes: {
        Account: 'Account@http://localhost:8080/remoteEntry.js',
        //vite_host: 'vite_host@http://localhost:8084/assets/remoteEntry.js'
      },
      exposes: {
        './moduleB': './src/components/moduleB.vue',
        './NavbarLinks': './src/components/NavbarLinks.vue',
        './PinkBox': './src/components/PinkBox.vue',
        './OrangeBoxComms': './src/components/OrangeBox.vue'
      },
      shared: {
        vue: { eager: true }
      },
    }),
    new rspack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
    }),
    new rspack.ProgressPlugin({}),
    new rspack.HtmlRspackPlugin({
      template: './src/index.html',
    }),
    isDev ? new refreshPlugin() : null,
  ].filter(Boolean),
}

The main difference in the new config is that the rule for .css is changed to:

{
 test: /\.css$/,
 use: [
   "vue-style-loader",
   {
     loader: "css-loader",
     options: {
       modules: {
         localIdentName: "[local]__[hash:base64:8]",
         namedExport: false,
       },
     },
   },
 ],
},

Upvotes: 2

Related Questions