How can I eject quasar cli and use Vue + Quasar UMD? - vue.js

I'm using Quasar for SPA only. But I don't need all the things that come with Quasar CLI.
Is there a way to eject Quasar CLI and start using Vue + Quasar UMD?
Basically, stop using quasar dev and start using vue serve?

Yes, you can. The following changes should be made:
// vue.config.js
const QuasarLoader = require('./quasar-loader/index');
module.exports =
{
css:
{
loaderOptions:
{
scss:
{
prependData: `#import "~quasar/src/css/variables.sass";`
}
}
},
transpileDependencies: ['quasar'],
configureWebpack: (config) =>
{
if (!config.plugins) config.plugins = [];
config.plugins.push(new QuasarLoader());
},
}
// quasar-loader/auto-import.js
/**
* Quasar runtime for auto-importing
* components or directives.
*
* Warning! This file does NOT get transpiled by Babel
* but is included into the UI code.
*
* #param component {Vue} Component object
* #param type {String} One of 'components' or 'directives'
* #param items {Object} Object containing components or directives
*/
module.exports = function quasarLoader(component, type, items)
{
/* we use a workaround in functional templates
<template>
<component :is="$options.components.QBtn" ... />
</template>
*/
var target, i;
var opt = component.options;
if (!opt[type])
{
opt[type] = items
}
else
{
target = opt[type];
for (i in items)
{
if (!target[i])
{
target[i] = items[i]
}
}
}
};
// quasar-loader/index.js
const RuleSet = require('webpack/lib/RuleSet');
let vueLoaderPath;
try
{
vueLoaderPath = require.resolve('vue-loader');
}
catch (err)
{}
function isVueLoader(use)
{
return use.ident === 'vue-loader-options' ||
use.loader === 'vue-loader' ||
(vueLoaderPath && use.loader === vueLoaderPath)
}
class QuasarLoaderPlugin
{
constructor(options)
{
this.options = options || {}
}
apply(compiler)
{
// use webpack's RuleSet utility to normalize user rules
const rawRules = compiler.options.module.rules;
const { rules } = new RuleSet(rawRules);
// find the rule that applies to vue files
const vueRuleIndex = rules.findIndex(rule => rule.use && rule.use.find(isVueLoader));
const vueRule = rules[vueRuleIndex];
if (!vueRule)
{
throw new Error(
`[QuasarLoaderPlugin Error] No matching rule for vue-loader found.\n` +
`Make sure there is at least one root-level rule that uses vue-loader.`
)
}
vueRule.use.unshift({
loader: require.resolve('./loader'),
options: this.options || { nameCase: 'kebab' }
});
compiler.options.module.rules = rules;
}
}
module.exports = QuasarLoaderPlugin;
// quasar-loader/loader.js
const path = require('path');
const compiler = require('vue-template-compiler');
// const loaderOptions = require('loader-utils/lib/getOptions');
const stringifyRequest = require('loader-utils/lib/stringifyRequest');
const importData = require('quasar/dist/babel-transforms/auto-import.json');
const importTransform = require('quasar/dist/babel-transforms/imports.js');
const runtimePath = require.resolve('./auto-import.js');
// regex to match functional components
const funcCompRegex = new RegExp(
'var\\s+component\\s*=\\s*normalizer\\((?:[^,]+,){3}\\s*true,'
);
function transform(itemArray)
{
return itemArray
.map(name => `import ${name} from '${importTransform(name)}'`)
.join(`\n`)
}
module.exports = async function (content, sourceMap)
{
this.async();
this.cacheable();
if (!this.resourceQuery)
{
const readFile = path => new Promise((resolve, reject) =>
{
this.fs.readFile(path, function (err, data)
{
if (err) reject(err);
else resolve(data)
})
});
this.addDependency(this.resourcePath);
const tags = new Set();
const directives = new Set();
const file = (await readFile(this.resourcePath)).toString('utf8');
const component = compiler.parseComponent(file);
if (component.template)
{
if (component.template.src)
{
const externalFile = (await new Promise((resolve, reject) =>
this.resolve(path.dirname(this.resourcePath), component.template.src, (err, result) =>
{
if (err) reject(err);
else resolve(result)
})
));
component.template.content = (await readFile(externalFile)).toString('utf8'); // external content
}
const compRegexKebab = new RegExp('^' + importData.regex.kebabComponents + '$');
const compRegexPascal = new RegExp('^' + importData.regex.pascalComponents + '$');
const dirRegex = new RegExp('^' + importData.regex.directives + '$');
compiler.compile(component.template.content, {
modules: [{
postTransformNode: node =>
{
if ("directives" in node)
{
node.directives.forEach(({ name }) =>
{
if (dirRegex.test('v-' + name))
{
directives.add(importData.importName['v-' + name]);
}
});
}
if (compRegexKebab.test(node.tag))
{
tags.add(importData.importName[node.tag]);
}
else if (compRegexPascal.test(node.tag))
{
tags.add(node.tag);
}
}
}]
});
const arrTags = [...tags];
const arrDirs = [...directives];
if (arrTags.length > 0 || arrDirs.length > 0)
{
const functional = funcCompRegex.test(content);
let newContent = '/* quasar-loader */\n';
newContent += `import qInstall from ${stringifyRequest(this, runtimePath)}\n`;
if (arrTags.length > 0)
{
if (functional) console.error('Using workaround for local Vue components (' + arrTags.join(',') + ') in a functional template.');
newContent += transform(arrTags) + '\n';
newContent += `qInstall(component, "components", {${arrTags.join(",")}})\n`;
}
if (arrDirs.length > 0)
{
if (functional) console.error('Using local Vue directive (' + arrDirs.join(',') + ') in a functional component is not supported.');
newContent += transform(arrDirs) + '\n';
newContent += `qInstall(component, "directives", {${arrDirs.join(",")}})\n`;
}
// Insert our modification before the HMR code
const hotReload = content.indexOf('/* hot reload */');
if (hotReload > -1)
{
content = content.slice(0, hotReload) + newContent + '\n\n' + content.slice(hotReload)
}
else
{
content += '\n\n' + newContent
}
}
}
}
this.callback(null, content, sourceMap);
};
// src/main.js
import QuasarInstall from 'quasar/src/vue-plugin'
import iconSet from 'quasar/icon-set/svg-mdi-v5'
// import QuasarDialog from 'quasar/src/plugins/Dialog'
import '#/assets/scss/quasar.scss'
QuasarInstall.install(Vue, {
iconSet,
plugins:
{
// Dialog: QuasarDialog,
},
});
// src/assets/scss/quasar.scss
$primary : #409EFF;
$secondary : #26A69A;
$accent : #9C27B0;
$dark : #1D1D1D;
$positive : #20A835;
$negative : #F04025;
$info : #1975D0;
$warning : #F2C037;
#import '~quasar/src/css/index.sass';

If you want to use Quasar and Vue without CLI at all, you can check this starter project.
It's just Quasar UMD (ver.2) and vanilla Vue (ver.3).
Github: https://github.com/SaleCar/Quasar-UMD-Template
Demo: http://quasar.rf.gd/

Related

Making a web component using Vue

I am currently working on web components and shadow DOM.
I can see that it is possible to create a native web component using Vue3 here
Vue docs. But I am currently facing issues building the native component file from vuejs files. I have googled for some time and found there are not many helpful content for it.
Building Web Components with Vue 3.2 is by far the most helpful blog I have found. Still I am unable to do production build of my files.
Currently I am getting 2 files after build.
import {
r as c,
c as l,
o as a,
a as u,
b as d,
d as f,
t as m,
u as p,
e as g,
} from "./vendor.21fe8919.js";
const y = function () {
const r = document.createElement("link").relList;
if (r && r.supports && r.supports("modulepreload")) return;
for (const e of document.querySelectorAll('link[rel="modulepreload"]')) o(e);
new MutationObserver((e) => {
for (const t of e)
if (t.type === "childList")
for (const s of t.addedNodes)
s.tagName === "LINK" && s.rel === "modulepreload" && o(s);
}).observe(document, { childList: !0, subtree: !0 });
function i(e) {
const t = {};
return (
e.integrity && (t.integrity = e.integrity),
e.referrerpolicy && (t.referrerPolicy = e.referrerpolicy),
e.crossorigin === "use-credentials"
? (t.credentials = "include")
: e.crossorigin === "anonymous"
? (t.credentials = "omit")
: (t.credentials = "same-origin"),
t
);
}
function o(e) {
if (e.ep) return;
e.ep = !0;
const t = i(e);
fetch(e.href, t);
}
};
y();
const h = {
props: { timeZone: { type: String, default: "America/Los_Angeles" } },
emits: ["datechange"],
setup(n, { emit: r }) {
const i = n,
o = c(new Date()),
e = l(() => o.value.toLocaleString("en-US", { timeZone: i.timeZone }));
return (
setInterval(() => {
(o.value = new Date()), r("datechange", e);
}, 1e3),
(t, s) => (
a(), u("div", null, [d(t.$slots, "prefix"), f(" " + m(p(e)), 1)])
)
);
},
},
v = g(h);
customElements.define("current-time", v);
document.querySelector("current-time").addEventListener("datechange", L);
function L(n) {
console.log(n.detail[0].value);
}
But i would like the build file to be in following format for my use case.
class CurrentTime extends HTMLElement {
connectedCallback() {
this.innerHTML = new Date();
setInterval(() => this.innerHTML = new Date(), 1000)
}
}
// Define it as a custom element
customElements.define('current-time', CurrentTime);
vite config file
import { defineConfig } from "vite";
import vue from "#vitejs/plugin-vue";
export default defineConfig({
plugins: [vue({ customElement: true })],
});

integrate vue-i18n in vue-beautiful-chat

I try to integrate (vue-i18n) at this library (https://github.com/mattmezza/vue-beautiful-chat) in src folder but I have some integration problems
so,
in this file ./i18n/translations.js => we have the translations
in src/index.js
import Launcher from './Launcher.vue'
import VTooltip from 'v-tooltip'
import VueI18n from 'vue-i18n'
import messages from './i18n/translations.js'
const defaultComponentName = 'beautiful-chat'
const Plugin = {
install (Vue, options = {}) {
/**
* Makes sure that plugin can be installed only once
*/
if (this.installed) {
return
}
Vue.use(VueI18n)
const locale = navigator.language
const i18n = new VueI18n({
fallbackLocale: 'fr',
locale: locale,
messages
})
this.installed = true
this.event = new Vue({i18n})
this.dynamicContainer = null
this.componentName = options.componentName || defaultComponentName
/**
* Plugin API
*/
Vue.prototype.$chat = {
_setDynamicContainer (dynamicContainer) {
Plugin.dynamicContainer = dynamicContainer
}
}
/**
* Sets custom component name (if provided)
*/
Vue.component(this.componentName, Launcher)
Vue.use(VTooltip)
Vue.use(VueI18n)
}
}
export default Plugin
And i start to change in the file "src/Launcher.vue" "you" in the header of the chat
computed: {
chatWindowTitle() {
if (this.title !== '') {
return this.title
}
if (this.participants.length === 0) {
return $t('participant.you_only')
} else if (this.participants.length > 1) {
return $t('participant.you_and_participants', { participant: 'this.participants[0].name' })
} else {
return 'You & ' + this.participants[0].name
}
}
},
but i receive this error
i have try few others methods as this.$i18n and others.
Can you help me please ?
Thanks a lot.
Are you possible missing the "this" when referring to "$t" on the computed property?
computed: {
chatWindowTitle() {
if (this.title !== '') {
return this.title
}
if (this.participants.length === 0) {
return this.$t('participant.you_only')
} else if (this.participants.length > 1) {
return this.$t('participant.you_and_participants', { participant: 'this.participants[0].name' })
} else {
return 'You & ' + this.participants[0].name
}
}
},

How to write unit test component with FileReader.addEventListener in angular 8?

I use angular 8 and i want to test my component with FileReader.
I can not test a FileReader in my processFile function.
Maybe my work is badly written? Can you help me please to understand.
IF I understand correctly, I have to test a class (Filereader) in a process function
my component
processFile(imageInput: any) {
const file: File = imageInput.files[0];
const reader = new FileReader();
let size: number = 2097152
if (file) {
if (file.size <= size) {
this.sharingDataService.setUploadIsOk(true)
reader.addEventListener('progress', (event:any) =>{
this.progressValue = this.progressBar(event)
if (event.lengthComputable) {
// console.log(event.loaded+ " / " + event.total)
}
})
reader.addEventListener('loadstart', (event:any) =>{
this.progressValue =0;
this.textDuringUploadBefore = "No"
this.textDuringUploadAfter = ''
// console.log('start');
})
reader.addEventListener('loadend', (event:any) =>{
// console.log('end');
})
reader.addEventListener('load', (event: any) => {
console.log(event);
this.selectedFile = new ImageSnippet(event.target.result, file);
this.fileName = this.selectedFile.file.name;
this.fileNameExt =this.fileName.split('.').pop();
this.displayAddPhoto = false;
this.selectedFile.status = 'ok';
this.getLoadCallBack(file)
// this.ng2ImgMax.resizeImage(file, 900,600).subscribe(
// result =>{
// // console.log('compress', );
// this.textDuringUploadAfter= "Yes!!!"
// this.textDuringUploadBefore= ''
// this.fileForm.patchValue({
// image: new File([result], result.name)
// });
// this.imageIsLoad = true
// this.sharingDataService.setUploadIsOk(false)
// }
// )
// this.imageOutput.emit(this.fileForm)
});
reader.readAsDataURL(file);
} else {
const msg ="This picture is too big."
+ '<br/>' + "Please upload an image of less than 2MB."
// this.sharedFunctionService.openDialogAlert(msg, 'home')
this.errorService.openDialogError(msg)
this.imageIsLoad = false
this.sharingDataService.setUploadIsOk(false)
}
}
}
getLoadCallBack(file:File){
this.ng2ImgMax.resizeImage(file, 900,600).subscribe(
result =>{
// console.log('compress', );
this.textDuringUploadAfter= "Yes"
this.textDuringUploadBefore= ''
this.fileForm.patchValue({
image: new File([result], result.name)
});
console.log(this.fileForm);
this.imageIsLoad = true
this.sharingDataService.setUploadIsOk(false)
}
)
this.imageOutput.emit(this.fileForm)
}
my spec.ts
it('processFile', () => {
// const mockEvt = { target: { files: [fileInput] } };
// const mockReader: FileReader = jasmine.createSpyObj('FileReader', ['readAsDataURL', 'onload']);
// spyOn(window as any, 'FileReader').and.returnValue(mockReader);
// spyOn(component, 'getLoadCallBack').and.callThrough();
const file = new File([''], 'test-file.jpg', { lastModified: null, type: 'image/jpeg' });
const fileInput = { files: [file] };
const eventListener = jasmine.createSpy();
spyOn(window as any, "FileReader").and.returnValue({
addEventListener: eventListener
})
component.processFile(fileInput);
i have got an error
TypeError: reader.readAsDataURL is not a function
how to test my processFile function?
I trie many way but no sucess

Wrong queryString when building navigation plan in aurelia-router

We're using aurelia-open-id-connect in our project and after successful login the redirect is removing the querystring from the url. I've debugged my way through the aurelia-router and I think I found something. Should'nt the queryString in redirectInstruction be used there?
export const buildRedirectPlan = (instruction: NavigationInstruction) => {
const config = instruction.config;
const router = instruction.router;
return router
._createNavigationInstruction(config.redirect)
.then(redirectInstruction => {
const params: Record<string, any> = {};
const originalInstructionParams = instruction.params;
const redirectInstructionParams = redirectInstruction.params;
for (let key in redirectInstructionParams) {
// If the param on the redirect points to another param, e.g. { route: first/:this, redirect: second/:this }
let val = redirectInstructionParams[key];
if (typeof val === 'string' && val[0] === ':') {
val = val.slice(1);
// And if that param is found on the original instruction then use it
if (val in originalInstructionParams) {
params[key] = originalInstructionParams[val];
}
} else {
params[key] = redirectInstructionParams[key];
}
}
let redirectLocation = router.generate(redirectInstruction.config, params, instruction.options);
// Special handling for child routes
for (let key in originalInstructionParams) {
redirectLocation = redirectLocation.replace(`:${key}`, originalInstructionParams[key]);
}
let queryString = instruction.queryString;
// use redirectInstruction.queryString instead?
if (queryString) {
redirectLocation += '?' + queryString;
}
return Promise.resolve(new Redirect(redirectLocation));
});
};

How to hide onScroll header in ionic 4?

I want to hide the header on scroll in Ionic 4 Beta 5.
I tried all the directives solutions, but none of them work for me.
So, are there any methods that work?
Use below directive
import { IonContent, DomController } from '#ionic/angular';
import { Directive, ElementRef, Input, Renderer2, SimpleChanges } from '#angular/core';
#Directive({
selector: '[scrollHide]'
})
export class ScrollHideDirective {
#Input('scrollHide') config: ScrollHideConfig;
#Input('scrollContent') scrollContent: IonContent;
contentHeight: number;
scrollHeight: number;
lastScrollPosition: number;
lastValue: number = 0;
constructor(private element: ElementRef, private renderer: Renderer2, private domCtrl: DomController) {
}
ngOnChanges(changes: SimpleChanges) {
if(this.scrollContent && this.config) {
this.scrollContent.scrollEvents = true;
let scrollStartFunc = async (ev) => {
const el = await this.scrollContent.getScrollElement();
this.contentHeight = el.offsetHeight;
this.scrollHeight = el.scrollHeight;
if (this.config.maxValue === undefined) {
this.config.maxValue = this.element.nativeElement.offsetHeight;
}
this.lastScrollPosition = el.scrollTop;
};
if(this.scrollContent && this.scrollContent instanceof IonContent) {
this.scrollContent.ionScrollStart.subscribe(scrollStartFunc);
this.scrollContent.ionScroll.subscribe(async (ev) => this.adjustElementOnScroll(ev));
this.scrollContent.ionScrollEnd.subscribe(async (ev) => this.adjustElementOnScroll(ev));
} else if(this.scrollContent instanceof HTMLElement) {
(this.scrollContent as HTMLElement).addEventListener('ionScrollStart', scrollStartFunc);
(this.scrollContent as HTMLElement).addEventListener('ionScroll',async (ev) => this.adjustElementOnScroll(ev));
(this.scrollContent as HTMLElement).addEventListener('ionScrollEnd',async (ev) => this.adjustElementOnScroll(ev));
}
}
}
private adjustElementOnScroll(ev) {
if (ev) {
this.domCtrl.write(async () => {
const el = await this.scrollContent.getScrollElement();
let scrollTop: number = el.scrollTop > 0 ? el.scrollTop : 0;
let scrolldiff: number = scrollTop - this.lastScrollPosition;
this.lastScrollPosition = scrollTop;
let newValue = this.lastValue + scrolldiff;
newValue = Math.max(0, Math.min(newValue, this.config.maxValue));
this.renderer.setStyle(this.element.nativeElement, this.config.cssProperty, `-${newValue}px`);
this.lastValue = newValue;
});
}
}
}
export interface ScrollHideConfig {
cssProperty: string;
maxValue: number;
}
Steps to use:
In your HTML
<ion-header [scrollHide]="headerScrollConfig" [scrollContent]="pageContent">
.
.
.
<ion-content #pageContent>
In your controller: Add config variables
footerScrollConfig: ScrollHideConfig = { cssProperty: 'margin-bottom', maxValue: undefined };
headerScrollConfig: ScrollHideConfig = { cssProperty: 'margin-top', maxValue: 54 };