Using Vue and Webpack to create an MPA - vue.js

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'}])
]
};

Related

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"),
},
],
},
...

VueJS: Imported css file showing as inline instead of external

When i include css,scss files in my entry app file, system adds these css file contents as inline css. But i want them to compiled in one file and import in page automatically like other js files instead of inline css.
Before: page content is like this https://prnt.sc/sj2wde.
After: Something like this
<link rel="stylesheet" href="/assets/css/0.styles.7462b628.css">
// Include scss file
import './assets/index.scss';
export function createApp() {
const router = createRouter();
const store = createStore();
sync(store, router);
const app = new Vue({
router,
store,
render: (h) => h(App),
});
return { app, router, store };
}
I tried to use mini-css-extract-plugin plugin but it works only for standalone vuejs components.
//webpack module rule
{
test: /\.css$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'vue-style-loader',
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[local]_[hash:base64:8]',
},
},
},
],
}

Mixed-up CSS on Production Build of vue-cli

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

vuetify2 installation problem and css/scss obligatory

I tried to add Vuetify to my project in several ways, but it caused a lot of errors (vue cli, vue ui, with according to doc using vuetify plugin file and specific webpack configuration). So in the end, I decided to do it in a more traditional way. But then it looks like I don't use all styles. I can't use justify="center" with v-row (there are no style for .justify-center), container full-height don't have full height and v-text-field with outlined looks bad (label should be on the left, but it's on the right and then you click on it, its move top to bad position)
So maybe someone helps me add it correctly? Maybe I need some additional loaders or import more CSS styles. Which of them is obligatory?
How I did it?
1) npm install vuetify --save
2) in index.js imported vuetify and vuetify.min.css
import Vue from 'vue';
import App from './src/App.vue';
import router from './router';
import Vuetify from 'vuetify';
import './style.scss';
import './node_modules/bootstrap/dist/css/bootstrap-grid.css';
import './node_modules/vuetify/dist/vuetify.min.css';
import 'material-design-icons-iconfont/dist/material-design-icons.css'
Vue.use(Vuetify);
new Vue({
el: '#app',
router,
render: h => h(App)
})
3) simple webpack configuration
const path = require('path');
const webpack = require('webpack');
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
devtool: "source-map",
entry: './index.js',
output: {
path: path.resolve(__dirname, 'build'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['#babel/preset-env']
}
},
{
test: /\.vue$/,
exclude: /node_modules/,
loader: 'vue-loader'
},
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.s[ac]ss$/i,
loaders: [
'style-loader',
'css-loader',
'sass-loader',
],
},
{
test: /\.(woff2?|eot|ttf|otf)$/,
loader: 'file-loader',
options: {
limit: 10000,
name: '[name].[hash:7].[ext]'
}
}
]
},
plugins: [
new VueLoaderPlugin()
]
};
I have no errors. Only problem is that vuetify components are not properly rendered.
Example:
outline input should look that:
but it looks that:
This is what must be on App.js to include Vuetify:
import Vuetify from 'vuetify'
import colors from 'vuetify/lib/util/colors'
import 'vuetify/dist/vuetify.min.css'
Vue.use(Vuetify);
new Vue({
el: '#app',
vuetify: new Vuetify(),
router,
render: h => h(App)
})
This way will work

How can I configure my Webpack file in Storybook correctly to compile my CSS with PostCSS?

I have a Vue CLI up and running successfully with PostCSS and all of the plugins I require. However, when it comes to setting up the same PostCSS config for Storybook, no matter which combination of things I try from various sources that I've come across, I cannot get my Webpack file to compile correctly.
.storybook/postcss.config.js
module.exports = {
plugins: {
autoprefixer: {},
'postcss-nested': {},
'postcss-custom-media': {},
'postcss-advanced-variables': {},
},
};
.storybook/webpack.config.js
const path = require('path');
const rootPath = path.resolve(__dirname, '../src');
module.exports = ({ config, mode }) => {
config.module.rules.push({
test: /\.css$/,
use: [
'style-loader',
{ loader: 'css-loader', options: { importLoaders: 1 } },
'postcss-loader',
],
});
config.resolve.alias['#'] = rootPath;
return config;
};
./storybook.config.js
import { configure } from '#storybook/vue';
import Vue from 'vue';
import Vuex from 'vuex';
import '../src/css/main.css';
import '../src/stories/main.css';
Vue.use(Vuex);
function loadStories() {
require('../src/stories');
}
configure(loadStories, module);
Error
ERROR in ./src/css/main.css (./node_modules/#storybook/core/node_modules/css-loader/dist/cjs.js??ref--3-1!./node_modules/postcss-loader/src??postcss!./node_modules/style-loader!./node_modules/css-loader??ref--6-1!./node_modules/postcss-loader/src!./src/css/main.css)
Module build failed (from ./node_modules/postcss-loader/src/index.js):
SyntaxError
(2:1) Unknown word
1 |
> 2 | var content = require("!!../../node_modules/css-loader/index.js??ref--6-1!../../node_modules/postcss-loader/src/index.js!./main.css");
| ^