Compare commits

..

21 Commits

Author SHA1 Message Date
d3d0e7c44d Upgrade to Vue 3 2023-09-24 11:57:57 +02:00
d9b5af7388 Improve warning and embed block 2021-01-06 17:26:46 +01:00
cefdf76045 Improve attaches block 2020-11-09 17:04:46 +01:00
92933de4bd Adjustments to attaches block 2020-11-09 10:18:20 +01:00
5dd1b7c0ef Optimize for server side rendering 2020-11-02 13:11:22 +01:00
945bef0523 Update for lazy loading images 2020-10-20 18:42:43 +02:00
fcf12f7dcf Add anchor ids to headers 2020-10-11 12:22:14 +02:00
91b7922464 Add math block 2020-09-30 22:06:01 +02:00
540333d431 Improve and fix button block 2020-09-28 22:06:47 +02:00
7639ed84b3 Add button block 2020-09-27 15:09:58 +02:00
1cb252b952 Allow html tags in headers and lists 2020-09-26 19:06:30 +02:00
2684c5d58d Fix link block image 2020-08-24 22:23:33 +02:00
539421bcd3 Update to version 0.1.9 2020-08-22 13:29:32 +02:00
9120ad7a8d Add icon to attaches download button 2020-08-22 13:28:27 +02:00
8df79b00a0 Remove styling of attaches file icon 2020-08-22 13:27:18 +02:00
76a56ddc68 Update to version 0.1.8 2020-08-22 13:23:56 +02:00
fd208f2b41 Embed file icons inline in attaches block 2020-08-22 13:23:24 +02:00
ea225d0f41 Update to version 0.1.7 2020-08-22 13:12:24 +02:00
e4a73ad77b Add missing link to link block 2020-08-22 13:11:38 +02:00
0a63d935e7 Fix package.json 2020-08-22 12:58:05 +02:00
4fd2ddf837 Build 0.1.5 2020-08-21 22:11:10 +02:00
27 changed files with 2717 additions and 11952 deletions

View File

@@ -1,14 +1,44 @@
module.exports = { module.exports = {
root: true, root: true,
env: { env: {
node: true node: true,
}, },
extends: ["plugin:vue/essential", "eslint:recommended", "@vue/prettier"],
parserOptions: { parserOptions: {
parser: "babel-eslint" parser: 'babel-eslint',
}, },
rules: { rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off", semi: [
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off" 'error',
} 'always',
],
quotes: [
2,
'single',
],
'comma-dangle': [
'error',
{
'arrays': 'always-multiline',
'objects': 'always-multiline',
'imports': 'always-multiline',
'exports': 'always-multiline',
'functions': 'never',
},
],
'space-before-function-paren': [
'error',
'never',
],
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'max-len': ['warn', {
'code': 100,
'ignoreStrings': true,
}],
},
extends: [
'plugin:vue/recommended',
'eslint:recommended',
],
}; };

3
.gitignore vendored
View File

@@ -1,7 +1,6 @@
.DS_Store .DS_Store
node_modules node_modules
/dist dist
# local env files # local env files
.env.local .env.local

View File

@@ -1,3 +1,3 @@
module.exports = { module.exports = {
presets: ["@vue/cli-plugin-babel/preset"] presets: ['@vue/cli-plugin-babel/preset'],
}; };

261
dist/block-renderer.umd.js vendored Normal file

File diff suppressed because one or more lines are too long

13748
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,33 +1,46 @@
{ {
"name": "block-renderer", "name": "block-renderer",
"description": "Content renderer for JSON blocks from Editor.js", "description": "Content renderer for JSON blocks from Editor.js",
"version": "0.1.3", "version": "0.7.7",
"author": "KingOfDog <info@kingofdog.de>", "author": "KingOfDog <info@kingofdog.de>",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://git.kingofdog.de/KingOfDog/block-renderer" "url": "https://git.kingofdog.de/KingOfDog/block-renderer"
}, },
"homepage": "https://git.kingofdog.de/KingOfDog/block-renderer", "homepage": "https://git.kingofdog.de/KingOfDog/block-renderer",
"type": "module",
"files": [
"dist"
],
"main": "./dist/block-renderer.umd.js",
"module": "./dist/block-renderer.es.js",
"exports": {
".": {
"import": "./dist/block-renderer.es.js",
"require": "./dist/block-renderer.umd.js"
},
"./dist/block-renderer.css": {
"import": "./dist/block-renderer.css",
"require": "./dist/block-renderer.css"
}
},
"types": "./dist/main.d.ts",
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "dev": "vite",
"build": "vue-cli-service build", "build": "vue-tsc && vite build",
"build-package": "vue-cli-service build --target lib --name block-renderer ./src/main.js", "preview": "vite preview"
"lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"core-js": "^3.6.5", "katex": "^0.16.8",
"vue": "^2.6.11" "vue": "^3.3.4"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0", "@vitejs/plugin-vue": "^4.0.0",
"@vue/cli-plugin-eslint": "~4.5.0", "path": "^0.12.7",
"@vue/cli-service": "~4.5.0", "rollup-plugin-typescript2": "^0.34.1",
"@vue/eslint-config-prettier": "^6.0.0", "typescript": "^4.9.3",
"babel-eslint": "^10.1.0", "vite": "^4.0.0",
"eslint": "^6.7.2", "vite-plugin-dts": "^1.7.1",
"eslint-plugin-prettier": "^3.1.3", "vue-tsc": "^1.0.11"
"eslint-plugin-vue": "^6.2.2",
"prettier": "^1.19.1",
"vue-template-compiler": "^2.6.11"
} }
} }

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="40">
<path d="M17 0l15 14V3v34a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h20-6zm0 2H3a1 1 0 0 0-1 1v34a1 1 0 0 0 1 1h26a1 1 0 0 0 1-1V14H17V2zm2 10h7.926L19 4.602V12z"/>
</svg>

Before

Width:  |  Height:  |  Size: 240 B

View File

@@ -1,6 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="40">
<g fill="#A8ACB8" fill-rule="evenodd">
<path fill-rule="nonzero" d="M17 0l15 14V3v34a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h20-6zm0 2H3a1 1 0 0 0-1 1v34a1 1 0 0 0 1 1h26a1 1 0 0 0 1-1V14H17V2zm2 10h7.926L19 4.602V12z"/>
<path d="M7 22h18v2H7zm0 4h18v2H7zm0 4h18v2H7z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 364 B

View File

@@ -1,81 +1,84 @@
<template> <template>
<div class="attaches-block"> <div class="attaches-block">
<div <a :href="url" target="blank">
:data-extension="data.file.extension" <div :data-extension="data.file.extension" class="attaches-block-file-icon" :style="{ color: color }">
:style="{ color: color }" <svg width="32" height="40">
class="attaches-block-file-icon" <path v-if="color"
> d="M17 0l15 14V3v34a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h20-6zm0 2H3a1 1 0 0 0-1 1v34a1 1 0 0 0 1 1h26a1 1 0 0 0 1-1V14H17V2zm2 10h7.926L19 4.602V12z" />
<svg> <g v-else fill="#A8ACB8" fill-rule="evenodd">
<use <path fill-rule="nonzero"
xmlns:xlink="http://www.w3.org/1999/xlink" d="M17 0l15 14V3v34a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h20-6zm0 2H3a1 1 0 0 0-1 1v34a1 1 0 0 0 1 1h26a1 1 0 0 0 1-1V14H17V2zm2 10h7.926L19 4.602V12z" />
v-bind:xlink:href="color ? customIcon : standardIcon" <path d="M7 22h18v2H7zm0 4h18v2H7zm0 4h18v2H7z" />
/> </g>
</svg> </svg>
</div> </div>
</a>
<div class="attaches-block-file-info"> <div class="attaches-block-file-info">
<p class="attaches-block-title">{{ data.file.name }}</p> <a :href="url" target="blank">
<p class="attaches-block-size">{{ data.file.size | byteFormatter }}</p> <p class="attaches-block-title">{{ data.title }}</p>
</a>
<p class="attaches-block-size">
{{ fileSize }}
</p>
</div> </div>
<a :href="data.file.url" class="attaches-block-file download-button"></a> <a :href="url" target="_blank" class="attaches-block-file-download-button">
<svg xmlns="http://www.w3.org/2000/svg" width="17pt" height="17pt" viewBox="0 0 17 17">
<path
d="M9.457 8.945V2.848A.959.959 0 0 0 8.5 1.89a.959.959 0 0 0-.957.957v6.097L4.488 5.891a.952.952 0 0 0-1.351 0 .952.952 0 0 0 0 1.351l4.687 4.688a.955.955 0 0 0 1.352 0l4.687-4.688a.952.952 0 0 0 0-1.351.952.952 0 0 0-1.351 0zM3.59 14.937h9.82a.953.953 0 0 0 .953-.957.952.952 0 0 0-.953-.953H3.59a.952.952 0 0 0-.953.953c0 .532.425.957.953.957zm0 0"
fill-rule="evenodd" />
</svg>
</a>
</div> </div>
</template> </template>
<script> <script>
import byteFormatter from "../filters/byteFormatter"; import byteFormatter from '../filters/byteFormatter';
const extensions = { const extensions = {
doc: "#3e74da", doc: '#3e74da',
docx: "#3e74da", docx: '#3e74da',
odt: "#3e74da", odt: '#3e74da',
pdf: "#d47373", pdf: '#d47373',
rtf: "#656ecd", rtf: '#656ecd',
tex: "#5a5a5b", tex: '#5a5a5b',
txt: "#5a5a5b", txt: '#5a5a5b',
pptx: "#e07066", pptx: '#e07066',
ppt: "#e07066", ppt: '#e07066',
mp3: "#eab456", mp3: '#eab456',
mp4: "#f676a6", mp4: '#f676a6',
xls: "#3f9e64", xls: '#3f9e64',
html: "#2988f0", html: '#2988f0',
htm: "#2988f0", htm: '#2988f0',
png: "#f676a6", png: '#f676a6',
jpg: "#f67676", jpg: '#f67676',
jpeg: "#f67676", jpeg: '#f67676',
gif: "#f6af76", gif: '#f6af76',
zip: "#4f566f", zip: '#4f566f',
rar: "#4f566f", rar: '#4f566f',
exe: "#e26f6f", exe: '#e26f6f',
svg: "#bf5252", svg: '#bf5252',
key: "#e07066", key: '#e07066',
sketch: "#df821c", sketch: '#df821c',
ai: "#df821c", ai: '#df821c',
psd: "#388ae5", psd: '#388ae5',
dmg: "#e26f6f", dmg: '#e26f6f',
json: "#2988f0", json: '#2988f0',
csv: "#3f9e64" csv: '#3f9e64',
}; };
export default { export default {
filters: { props: ['data'],
byteFormatter inject: ['$baseFileUrl'],
},
props: ["data"],
data() {
return {
standardIcon: require("@/assets/svg/standard.svg"),
customIcon: require("@/assets/svg/custom.svg")
};
},
computed: { computed: {
color() { color() {
return extensions[this.data.file.extension]; return extensions[this.data.file.extension.toLowerCase()];
},
url() {
return `${this.$baseFileUrl}/${this.data.file.id}?name=${this.data.file.name}`;
},
fileSize() {
return byteFormatter(this.data.file.size)
} }
} },
}; };
</script> </script>
<style scoped>
.attaches-block-file-icon::before {
content: attr(data-extension);
}
</style>

View File

@@ -1,70 +1,50 @@
<template> <template>
<div> <div>
<component <component :is="block.component" v-for="(block, index) in blocks" :key="index" :data="block.data" />
v-for="(block, index) in blocks"
:key="index"
:is="block.component"
:data="block.data"
/>
</div> </div>
</template> </template>
<script> <script>
import AttachesBlock from "./AttachesBlock"; import AttachesBlock from './AttachesBlock.vue';
import EmbedBlock from "./EmbedBlock"; import ButtonBlock from './ButtonBlock.vue';
import HeaderBlock from "./HeaderBlock"; import EmbedBlock from './EmbedBlock.vue';
import ImageBlock from "./ImageBlock"; import HeaderBlock from './HeaderBlock.vue';
import LinkBlock from "./LinkBlock"; import ImageBlock from './ImageBlock.vue';
import ListBlock from "./ListBlock"; import LinkBlock from './LinkBlock.vue';
import ParagraphBlock from "./ParagraphBlock"; import ListBlock from './ListBlock.vue';
import QuoteBlock from "./QuoteBlock"; import MathBlock from './MathBlock.vue';
import WarningBlock from "./WarningBlock"; import ParagraphBlock from './ParagraphBlock.vue';
import QuoteBlock from './QuoteBlock.vue';
import WarningBlock from './WarningBlock.vue';
const componentTypes = { const componentTypes = {
attaches: AttachesBlock, attaches: AttachesBlock,
button: ButtonBlock,
embed: EmbedBlock, embed: EmbedBlock,
header: HeaderBlock, header: HeaderBlock,
image: ImageBlock, image: ImageBlock,
link: LinkBlock, linkEmbed: LinkBlock,
list: ListBlock, list: ListBlock,
math: MathBlock,
paragraph: ParagraphBlock, paragraph: ParagraphBlock,
quote: QuoteBlock, quote: QuoteBlock,
warning: WarningBlock warning: WarningBlock,
}; };
export default { export default {
props: ["content"], props: ['content'],
data() { computed: {
return { blocks() {
blocks: []
};
},
mounted() {
if (this.content) {
this.prepareBlocks();
}
},
methods: {
prepareBlocks() {
if (!this.content.blocks) { if (!this.content.blocks) {
return; return [];
} }
this.blocks = this.content.blocks.map(block => ({
return this.content.blocks.map(block => ({
component: componentTypes[block.type], component: componentTypes[block.type],
type: block.type, type: block.type,
data: block.data data: block.data,
})); }));
} },
}, },
watch: {
content() {
if (this.content) {
this.prepareBlocks();
}
}
}
}; };
</script> </script>
<style>
</style>

View File

@@ -0,0 +1,15 @@
<template>
<div class="button-block">
<a
:href="data.url"
target="_blank"
v-html="data.label"
/>
</div>
</template>
<script>
export default {
props: ['data'],
};
</script>

View File

@@ -1,16 +1,54 @@
<template> <template>
<figure> <figure class="embed-block">
<iframe :src="data.embed" :height="data.height" frameborder="0" allowfullscreen class="w-full"></iframe> <div class="embed-container">
<iframe
v-if="accepted"
:src="data.embed"
:height="data.height"
frameborder="0"
allowfullscreen
/>
<div
v-else
class="embed-accept"
>
<h2>Externer Inhalt</h2>
<h3>
Originaler Link: <a
:href="data.source"
target="_blank"
>{{ displayUrl }}</a>
</h3>
<p>
An dieser Stelle ist externer Inhalt eingebunden. Um den Datenschutz
zu gewährleisten, ist er derzeit ausgeblendet. Deine Privatsphäre kann
auf externen Seiten nicht sichergestellt werden. Möchtest du den
Inhalt dennoch anzeigen?
</p>
<button @click="accepted = true">
Anzeigen
</button>
</div>
</div>
<figcaption v-html="data.caption"></figcaption> <figcaption v-html="data.caption" />
</figure> </figure>
</template> </template>
<script> <script>
export default { export default {
props: ["data"], props: ['data'],
data() {
return { accepted: false };
},
computed: {
displayUrl() {
if (!this.data) {
return '';
}
const url = new URL(this.data.source);
return url.hostname;
},
},
}; };
</script> </script>
<style>
</style>

View File

@@ -1,11 +1,11 @@
<template>
<component :is="`h${data.level}`" :id="data.anchor">
<span v-html="data.text" />
</component>
</template>
<script> <script>
export default { export default {
props: ["data"], props: ['data'],
render(createElement) {
return createElement("h" + this.data.level, this.data.text);
},
}; };
</script> </script>
<style>
</style>

View File

@@ -1,16 +1,27 @@
<template> <template>
<figure> <figure>
<img :src="data.file.url" /> <img v-lazyload :src="lowQualityUrl" :data-src="highQualityUrl">
<figcaption v-html="data.caption"></figcaption> <figcaption v-html="data.caption" />
</figure> </figure>
</template> </template>
<script> <script>
import LazyLoadImage from '../directives/lazy-load-image';
export default { export default {
props: ["data"], directives: {
lazyload: LazyLoadImage,
},
props: ['data'],
inject: ['$baseFileUrl'],
computed: {
lowQualityUrl() {
return `${this.$baseFileUrl}/${this.data.file.id}?type=jpg&w=100&b=50`;
},
highQualityUrl() {
return `${this.$baseFileUrl}/${this.data.file.id}?w=1200`;
},
},
}; };
</script> </script>
<style>
</style>

View File

@@ -1,22 +1,31 @@
<template> <template>
<div class="link-block"> <div class="link-block">
<div class="link-block-meta"> <a
<div class="link-block-title">{{data.meta.title}}</div> :href="data.link"
<p class="link-block-description">{{data.meta.description}}</p> target="_blank"
<div class="link-block-anchor">{{domain}}</div> class="link-block-href"
</div> >
<div class="link-block-meta">
<div class="link-block-title">{{ data.meta.title }}</div>
<p class="link-block-description">{{ data.meta.description }}</p>
<div class="link-block-anchor">{{ domain }}</div>
</div>
<div class="link-block-image" :style="{ 'background-image': data.meta.image.url }"></div> <div
class="link-block-image"
:style="{ backgroundImage: `url(${data.meta.image.url}` }"
/>
</a>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
props: ["data"], props: ['data'],
computed: { computed: {
domain() { domain() {
return this.data.link.replace(/(https|http):\/\//, "").split(/[/?#]/)[0]; return this.data.link.replace(/(https|http):\/\//, '').split(/[/?#]/)[0];
} },
} },
}; };
</script> </script>

View File

@@ -1,14 +1,12 @@
<template>
<component :is="data.style === 'ordered' ? 'ol' : 'ul'">
<li v-for="item in data.items" :key="item" v-html="item"></li>
</component>
</template>
<script> <script>
export default { export default {
props: ["data"], props: ['data'],
render(createElement) {
return createElement(
this.data.style === "ordered" ? "ol" : "ul",
this.data.items.map((item) => createElement("li", item))
);
},
}; };
</script> </script>
<style>
</style>

View File

@@ -0,0 +1,23 @@
<template>
<figure>
<div v-html="content" />
</figure>
</template>
<script>
import katex from 'katex';
export default {
props: ['data'],
data() {
return {
content: '',
};
},
mounted() {
this.content = katex.renderToString(this.data.text, {
throwOnError: false,
});
},
};
</script>

View File

@@ -1,10 +1,10 @@
<template> <template>
<p v-html="data.text"></p> <p v-html="data.text" />
</template> </template>
<script> <script>
export default { export default {
props: ["data"], props: ['data'],
}; };
</script> </script>

View File

@@ -1,14 +1,14 @@
<template> <template>
<blockquote> <blockquote>
<p v-html="data.text"></p> <p v-html="data.text" />
<footer v-html="data.caption"></footer> <footer v-html="data.caption" />
</blockquote> </blockquote>
</template> </template>
<script> <script>
export default { export default {
props: ["data"], props: ['data'],
}; };
</script> </script>

View File

@@ -1,15 +1,17 @@
<template> <template>
<div class="warning-block"> <div class="warning-block">
<p class="font-semibold mb-4 text-white">{{data.title}}</p> <b v-html="title" />
<p v-html="data.message" class="text-white"></p> <p v-html="data.message" />
</div> </div>
</template> </template>
<script> <script>
export default { export default {
props: ["data"], props: ['data'],
computed: {
title() {
return this.data.title ? this.data.title.replace(/<br>/g, '') : '';
},
},
}; };
</script> </script>
<style>
</style>

View File

@@ -0,0 +1,38 @@
export default {
mounted: (el) => {
function loadImage() {
const image = new Image();
image.src = el.dataset.src;
image.onload = () => {
el.src = el.dataset.src;
};
}
function handleIntersect(entries, observer) {
entries.forEach((entry) => {
if (!entry.isIntersecting) {
return;
}
loadImage();
observer.unobserve(el);
});
}
function createObserver() {
const options = {
root: null,
threshold: "0",
};
const observer = new IntersectionObserver(handleIntersect, options);
observer.observe(el);
}
if (!window.IntersectionObserver) {
loadImage();
} else {
createObserver();
}
},
};

View File

@@ -1,4 +1,4 @@
module.exports = function(size) { export default function (size) {
var result; var result;
switch (true) { switch (true) {
@@ -27,4 +27,4 @@ module.exports = function(size) {
} }
return result; return result;
}; }

View File

@@ -1,4 +0,0 @@
import BlockContentRenderer from './components/BlockContentRenderer.vue';
export default BlockContentRenderer;
export { BlockContentRenderer };

13
src/main.ts Normal file
View File

@@ -0,0 +1,13 @@
import type { App } from 'vue';
import BlockContentRenderer from './components/BlockContentRenderer.vue';
export default {
install: (app: App, options: {
baseFileUrl?: string
}) => {
app.provide('$baseFileUrl', options.baseFileUrl);
app.component('BlockContentRenderer', BlockContentRenderer);
},
};
export { BlockContentRenderer };

5
src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

38
tsconfig.json Normal file
View File

@@ -0,0 +1,38 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": [
"ESNext",
"DOM"
],
"skipLibCheck": true,
"noEmit": true,
"allowJs": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"declaration": true,
"outDir": "dist",
"declarationDir": "dist",
"baseUrl": ".",
"paths": {
"@/*": [
"./src/*"
]
}
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue"
],
}

60
vite.config.ts Normal file
View File

@@ -0,0 +1,60 @@
import * as path from 'path'
import vue from '@vitejs/plugin-vue'
import typescript2 from 'rollup-plugin-typescript2';
import { defineConfig } from 'vite';
import dts from "vite-plugin-dts";
export default defineConfig({
plugins: [
vue(),
dts({
insertTypesEntry: true,
}),
typescript2({
check: false,
include: ["src/components/**/*.vue"],
tsconfigOverride: {
compilerOptions: {
outDir: "dist",
sourceMap: true,
declaration: true,
declarationMap: true,
},
},
exclude: ["vite.config.ts"]
})
],
build: {
cssCodeSplit: true,
lib: {
// Could also be a dictionary or array of multiple entry points
entry: "src/main.ts",
name: 'blockRenderer',
formats: ["es", "cjs", "umd"],
fileName: format => `block-renderer.${format}.js`
},
rollupOptions: {
// make sure to externalize deps that should not be bundled
// into your library
input: {
main: path.resolve(__dirname, "src/main.ts")
},
external: ['vue'],
output: {
assetFileNames: (assetInfo) => {
if (assetInfo.name === 'main.css') return 'block-renderer.css';
return assetInfo.name;
},
exports: "named",
globals: {
vue: 'Vue',
},
},
},
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
})