Mixed-up CSS on Production Build of vue-cli - vue.js

I have an issue in which the production build, which is created by npm run build differs from the development build, which is provided by npm run serve. The generated CSS on production differs from CSS on development.
Background
Due to changes in standard, some part of an existing application that was developed using Vuetify framework needs to be migrated to Quasar. Vuetify was installed first using vue-cli. Quasar was added later using npm. Application is in multi-page mode.
Problem
When viewing the page through npm run serve, pages that have been migrated to Quasar is shown correctly. However, when trying to deploy it in production through npm run build, the same page breaks. All spacing and margin in the page is off. After inspecting the resulting CSS, it turns out some CSS class is overriden to Vuetify's values.
I have tried to split the chunk for each page, to no avail.
The following is the snippet of my vue.config.js
module.exports = {
publicPath: process.env.NODE_ENV === 'production' ? '/static/dist/' : '/',
outputDir: '../flask/web/static/dist',
transpileDependencies: ['quasar', 'vuetify'],
pages: {
dashboard: { // vuetify page, no problem.
entry: 'src/pages/dashboard/index.js',
template: 'public/index.html',
filename: 'dashboard.html',
title: 'Dashboard',
chunks: ['chunk-vendors', 'chunk-common', 'chunk-dashboard-vendors', 'dashboard']
},
page2: { // quasar page, the one with problem
entry: 'src/pages/page2/index.js',
template: 'public/quasar.html',
filename: 'page2.html',
title: 'Page2',
chunks: ['chunk-vendors', 'chunk-common', 'chunk-page2-vendors', 'page2']
},
page3: {
entry: 'src/pages/page3/index.js',
template: 'public/index.html',
filename: 'page3.html',
title: 'Page3',
chunks: ['chunk-vendors', 'chunk-common', 'chunk-page3-vendors', 'page3']
},
// other page definitions...
},
pluginOptions: {
quasar: {
importStrategy: 'kebab',
rtlSupport: false
}
},
chainWebpack: config => {
const options = module.exports
const pages = options.pages
const pageKeys = Object.keys(pages)
// Long-term caching
const IS_VENDOR = /[\\/]node_modules[\\/]/
config.optimization
.splitChunks({
cacheGroups: {
vendors: {
name: 'chunk-vendors',
priority: -10,
chunks: 'initial',
minChunks: 2,
test: IS_VENDOR,
enforce: true
},
...pageKeys.map(key => ({
name: `chunk-${key}-vendors`,
priority: -11,
chunks: chunk => chunk.name === key,
test: IS_VENDOR,
enforce: true
})),
common: {
name: 'chunk-common',
priority: -20,
chunks: 'initial',
minChunks: 2,
reuseExistingChunk: true,
enforce: true
}
}
})
}
}
The following are snippets from each of the page's entry file.
src/pages/page2/index.js
import Vue from 'vue'
import Page2 from './Page2.vue'
import '#/plugins/quasar'
Vue.config.productionTip = false
new Vue({
render: h => h(Page2)
}).$mount('#app')
src/plugins/quasar.js
import Vue from 'vue'
import '#/styles/quasar.scss'
import iconSet from 'quasar/icon-set/mdi-v5.js'
import '#quasar/extras/mdi-v5/mdi-v5.css'
import { Quasar, Notify } from 'quasar'
Vue.use(Quasar, {
config: {},
components: {},
directives: {},
plugins: {
Notify
},
iconSet: iconSet
})
src/page3/index.js
import Vue from 'vue'
import Page3 from './Page3.vue'
import vuetify from '#/plugins/vuetify'
Vue.config.productionTip = false
new Vue({
vuetify,
render: h => h(Page3)
}).$mount('#app')
src/plugins/vuetify.js
import Vue from 'vue'
import Vuetify from 'vuetify/lib'
Vue.use(Vuetify)
export default new Vuetify({
})
Any idea how to split the code properly between different pages, both in production and development build, so that the CSS does not override each other?

What solved my problem was adding:
css:{
extract:false
}
To the vue.config.js file in root directory

Related

How to use Environment Variables inside Vue3+Vite component library?

I have created a component as part of my component library that I am building with Vue3 and Vite. Everything works well, except when I try to use environment variables. I want the app that consumes this component library to be able to provide the component with environment specific data.
I have played around and found that if I have a .env file as part of the component library project, I am able to access those variables, but I want to be able to provide that during runtime and not during build time.
Here is my vite.config.ts
import { defineConfig } from "vite";
import { resolve } from "path";
import vue from "#vitejs/plugin-vue";
import dts from "vite-plugin-dts";
export default ({ mode }) => {
return defineConfig({
optimizeDeps: {
exclude: ["vue-demi"],
},
plugins: [
vue(),
dts({
insertTypesEntry: true,
}),
],
server: {
open: true,
},
build: {
lib: {
entry: resolve(__dirname, "src/lib.ts"),
name: "complib",
fileName: "complib",
},
rollupOptions: {
external: ["vue"],
output: {
globals: {
vue: "Vue",
},
exports: "named",
},
},
},
});
};
The entry looks like:
import { App, install } from "vue-demi";
import TestComp from "./components/TestComp.vue";
import "./tailwind.css";
install();
export default {
install: (app: App) => {
app.component("TestComp", TestComp);
},
};
export { Header };
And here is a minimal component TestComp.vue:
<script setup lang="ts">
import { onMounted } from "vue";
onMounted(() => {
console.log(import.meta.env.VITE_TEST_VAR);
});
</script>
<template>
<span>Test Comp</span>
</template>

Using vue slots in library gives currentRenderingInstance is null

I am creating a Vue component library with Rollup, but when I use slots it gives me the following error:
Uncaught (in promise) TypeError: currentRenderingInstance is null
I made a very simple component in my library:
<script setup></script>
<template>
<button>
<slot></slot>
</button>
</template>
<style scoped></style>
Then I simply use it like this:
<ExampleComponent>
Text
</ExampleComponent>
If I remove the slot and replace it with a prop or hard-coded text, everything works fine.
This is my rollup.config.js:
import { defineConfig } from 'rollup';
import path from 'path';
import resolve from '#rollup/plugin-node-resolve';
import commonjs from '#rollup/plugin-commonjs';
import postcss from 'rollup-plugin-postcss';
import vue from 'rollup-plugin-vue';
// the base configuration
const baseConfig = {
input: 'src/entry.js',
};
// plugins
const plugins = [
vue(),
resolve({
extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue'],
}),
// process only `<style module>` blocks.
postcss({
modules: {
generateScopedName: '[local]___[hash:base64:5]',
},
include: /&module=.*\.css$/,
}),
// process all `<style>` blocks except `<style module>`.
postcss({ include: /(?<!&module=.*)\.css$/ }),
commonjs(),
];
const external = ['vue'];
const globals = {
vue: 'Vue',
};
export default [
// esm
defineConfig({
...baseConfig,
input: 'src/entry.esm.js',
external,
output: {
file: 'dist/vue-my-lib.esm.js',
format: 'esm',
exports: 'named',
},
plugins,
}),
// cjs
defineConfig({
...baseConfig,
external,
output: {
compact: true,
file: 'dist/vue-my-lib.ssr.js',
format: 'cjs',
name: 'VueMyLib',
exports: 'auto',
globals,
},
plugins,
}),
// iife
defineConfig({
...baseConfig,
external,
output: {
compact: true,
file: 'dist/vue-my-lib.min.js',
format: 'iife',
name: 'VueMyLib',
exports: 'auto',
globals,
},
plugins,
}),
];
Any idea about the problem?
After a whole day of searching, I found the solution (here and here). It's a problem with using a library locally (e.g., through npm link) where it seems there are two instances of Vue at the same time (one of the project and one of the library). So, the solution is to tell the project to use specifically its own vue through webpack.
In my case, I use Jetstream + Inertia, so I edited webpack.mix.js:
const path = require('path');
// ...
mix.webpackConfig({
resolve: {
symlinks: false,
alias: {
vue: path.resolve("./node_modules/vue"),
},
},
});
Or if you used vue-cli to create your project, edit the vue.config.js:
const { defineConfig } = require("#vue/cli-service");
const path = require("path");
module.exports = defineConfig({
// ...
chainWebpack(config) {
config.resolve.symlinks(false);
config.resolve.alias.set("vue", path.resolve("./node_modules/vue"));
},
});
Thanks to #mikelplhts
On vite + esbuild I used:
export default defineConfig({
...
resolve: {
alias: [
...
{
find: 'vue',
replacement: path.resolve("./node_modules/vue"),
},
],
},
...

Wrong import path after building vue proect as a "Library"

I am currently coding a small vue component library for company's internal use. However, I faced some difficulties on building and deploy the project. This component library contains several components inside. And it will finally build by vue-cli-service build --target lib. The problem I am facing right now is that I have a component (i.e. called Audio.vue), and it will import a .mp3 file inside the component. The component is look like this
<template>
<audio controls :src="soundSrc" />
</template>
<script>
import { defineComponent } from 'vue';
import sound from './assets/sound.mp3';
export default defineComponent({
name: 'Audio',
props: {},
setup() {
return { soundSrc: sound };
},
});
</script>
<style scoped></style>
However, I use this component by serving (vue-cli-service serve") my project is fine. But if I build this project by running vue-cli-service build --target lib --name project-name-here. And I use this built version as a git submodule of my library in another vue project by importing project-name-here.common built before. The sound.mp3 import from './assets/sound.mp3' could not be found. It seems it is using a relative path of my another vue project (i.e. localhost:8080) instead of the library project
Here is my vue.config.js
const path = require('path');
module.exports = {
css: {
extract: false,
},
lintOnSave: false,
productionSourceMap: false,
configureWebpack: {
resolve: {
alias: {
mediaAsset: path.resolve(__dirname, './src/components/Audio/assets'),
},
},
},
chainWebpack: (config) => {
const imgRule = config.module.rule('images');
imgRule
.use('url-loader')
.loader('url-loader')
.tap((options) => Object.assign(options, { limit: Infinity }));
const svgRule = config.module.rule('svg');
svgRule.uses.clear();
svgRule
.test(/\.svg$/)
.use('svg-url-loader') // npm install --save-dev svg-url-loader
.loader('svg-url-loader');
config.module
.rule('raw')
.test(/\.txt$/)
.use('raw-loader')
.loader('raw-loader');
config.module
.rule('media')
.test(/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/)
.use('url-loader')
.loader('url-loader')
.tap((options) =>
Object.assign(options, {
limit: 4096,
fallback: {
loader: 'file-loader',
options: {
name: 'media/[name].[hash:8].[ext]',
},
},
})
);
},
};
Appreciated for answering this question. Thanks.

Vue 3 Components Not Loading

I've spent the last 5 hours trying to figure out what's not working with my setup.
What I basically want to do is have a "wrapper" app that loads my common codebase (which is a Vue component).
App.js
import { createApp } from "vue";
import AppWrapper from "#src/js/AppWrapper.vue";
import Router from "#common/libraries/Router.js";
const vm = createApp(AppWrapper);
vm.use(Router);
vm.mount("#app");
AppWrapper.vue
<template>
<div>
<app></app>
</div>
</template>
<script>
import App from "#common/App.vue";
export default {
created() {},
components: {
App,
},
};
</script>
<style lang="scss" scoped></style>
App.vue
<template>
<div>
<h1>Ok</h1>
</div>
</template>
<script>
export default {
created() {},
};
</script>
Somehow, the app tag isn't replaced and my component is not loading in the page.
Here's the webpack file:
const Webpack = require("webpack");
const path = require("path");
const { VueLoaderPlugin } = require("vue-loader");
const LiveReloadPlugin = require("webpack-livereload-plugin");
module.exports = (env) => {
return {
entry: {
app: ["./app/src/common/App.js"],
},
output: {
path: path.resolve(__dirname, "public/"),
filename: "js/[name].js",
sourceMapFilename: "js/[name].js.map",
},
resolve: {
alias: {
"#src": path.resolve(__dirname, "app/src/"),
"#common": path.resolve(__dirname, "app/src/common/"),
},
},
module: {
rules: [
{
test: /\.vue$/,
use: ["vue-loader"],
},
],
},
watchOptions: {
aggregateTimeout: 300,
poll: 500,
ignored: /node_modules/,
},
plugins: [new VueLoaderPlugin(), new LiveReloadPlugin()],
};
};
Am I missing something?
Thanks!
Alright, I figured it out. Vue was loaded twice, once in my app package.json and once in the common sources. Removing it from one location worked!

Using Vue and Webpack to create an MPA

I am trying to learn how to use Vue and Webpack 4 to create a multi-page application. The main reason for this is that is that the server is not stateless and I need the server to handle routing because of the complexity of permissions and protected routes.
However, I want to use Vue.js to leverage the power of single file components to create reusable and maintainable code. I understand how to modify my wepack.config.js to set up multiple entry points and I assume that I would just be serving a different bundle that would be injected into a single index.html but there is where the things start becoming unclear to me.
How would I handle this with Vue.js? Just to be clear, I am not using the Vue CLI and I want to use single file components to design the front end. A simple project skeleton/boilerplate code would be much appreciated with an emphasis on configuration.
My main entry point (main.js)
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import $ from 'jquery';
import 'bootstrap/dist/js/bootstrap.js';
import 'bootstrap/dist/css/bootstrap.min.css';
import '#fortawesome/fontawesome-free/js/all.js'
Vue.config.productionTip = false;
new Vue({
el: '#app',
router,
render: h => h(App),
});
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const CopyPlugin = require('copy-webpack-plugin');
const webpack = require('webpack');
const path = require('path');
module.exports = {
entry: './src/main.js',
module: {
rules: [
{ test: /\.js$/, use: 'babel-loader', exclude: '/node_modules'},
{ test: /\.vue$/, use: 'vue-loader' },
{ test: /\.css$/, use: ['vue-style-loader', 'css-loader']},
//{ test: /\.(png|svg|jpg|gif)$/, use: 'file-loader'},
]
},
devServer: {
open: true,
hot: true,
},
resolve: {
alias: {
'#': path.resolve(__dirname, 'src')
}
},
plugins: [
new HtmlWebpackPlugin({template: './src/index.html'}),
new VueLoaderPlugin(),
//new webpack.HotModuleReplacementPlugin(),
new CopyPlugin([{from: 'src/images', to: 'images'}])
]
};