Move frontend to Vue

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2019-05-10 23:12:00 +02:00
parent e4097a42af
commit a2c7b10418
No known key found for this signature in database
GPG Key ID: DC2729FDD34BE99E
12 changed files with 7702 additions and 792 deletions

View File

@ -5,18 +5,16 @@ VER_GIBBERISH_AES=1.0.0
VER_JQUERY=3.4.1
VER_POPPER=1.15.0
VER_VUE=2.6.10
VER_VUE_I18N=8.11.2
default: generate
generate: l10n download_libs
generate: download_libs
docker run --rm -ti -v $(CURDIR):$(CURDIR) -w $(CURDIR)/src node:10-alpine \
sh -exc "npm ci && npm run build && rm -rf node_modules && chown -R $(shell id -u) ../frontend"
go generate
l10n:
cd frontend/locale && goi18n *
publish:
curl -sSLo golang.sh https://raw.githubusercontent.com/Luzifer/github-publish/master/golang.sh
bash golang.sh
@ -38,4 +36,4 @@ libs_css:
libs_js:
mkdir -p frontend/js
curl -sSfLo frontend/js/bundle.js "https://cdn.jsdelivr.net/combine/npm/jquery@$(VER_JQUERY),npm/popper.js@$(VER_POPPER),npm/bootstrap@$(VER_BOOTSTRAP)/dist/js/bootstrap.min.js,npm/gibberish-aes@$(VER_GIBBERISH_AES)/dist/gibberish-aes-$(VER_GIBBERISH_AES).min.js,npm/vue@$(VER_VUE)"
curl -sSfLo frontend/js/bundle.js "https://cdn.jsdelivr.net/combine/npm/jquery@$(VER_JQUERY),npm/popper.js@$(VER_POPPER),npm/bootstrap@$(VER_BOOTSTRAP)/dist/js/bootstrap.min.js,npm/gibberish-aes@$(VER_GIBBERISH_AES)/dist/gibberish-aes-$(VER_GIBBERISH_AES).min.js,npm/vue@$(VER_VUE),npm/vue-i18n@$(VER_VUE_I18N)/dist/vue-i18n.min.js"

7372
assets.go

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -6,124 +6,15 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="/css/bundle.css" integrity="{{SRIHash `/css/bundle.css`}}" />
<link rel="stylesheet" href="/css/all.min.css" integrity="{{SRIHash `/css/all.min.css`}}" />
<link rel="stylesheet" href="css/all.min.css"/>
<title>OTS - One Time Secrets</title>
<style>
#somethingwrong, #notfound, #cardReadSecret, #cardSecretURL, #cardReadSecretPre { display: none; }
.footer { color: #2f2f2f; font-size: 0.9em; text-align: center; }
textarea { font-family: monospace; }
</style>
</head>
<body>
<div id="app"></div>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<a class="navbar-brand" href="#"><i class="fas fa-user-secret"></i> OTS - One Time Secrets</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ml-auto">
<li class="nav-item"></li>
<li class="nav-item"><a href="#" id="newSecret"><i class="fas fa-plus"></i> {{T "btn-new-secret"}}</a></li>
</ul>
</div>
</nav>
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="alert alert-danger" role="alert" id="notfound">
<i class="fas fa-question-circle" aria-hidden="true"></i>
{{T "alert-secret-not-found"}}
</div>
<div class="alert alert-danger" role="alert" id="somethingwrong">
<i class="fas fa-question-circle" aria-hidden="true"></i>
{{T "alert-something-went-wrong"}}
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="card border-primary" id="cardNewSecret">
<div class="card-header bg-primary text-white">
{{T "title-new-secret"}}
</div>
<div class="card-body">
<form id="formCreateSecret">
<div class="form-group">
<label for="secret">{{T "label-secret-data"}}</label>
<textarea class="form-control" rows="5" id="secret"></textarea>
</div>
<input class="btn btn-success" type="submit" value="{{T "btn-create-secret"}}">
</form>
</div>
</div>
<div class="card border-success" id="cardSecretURL">
<div class="card-header bg-success text-white">
{{T "title-secret-created"}}
</div>
<div class="card-body">
<p>
{{T "text-pre-url"}}
</p>
<div class="form-group">
<input type="text" class="form-control" readonly>
</div>
<p>
{{T "text-burn-hint"}}
</p>
</div>
</div>
<div class="card border-primary" id="cardReadSecretPre">
<div class="card-header bg-primary text-white">
{{T "title-reading-secret"}}
</div>
<div class="card-body">
<p>
{{T "text-pre-reveal-hint"}}
</p>
<p>
<button class="btn btn-success" id="revealSecret">{{T "btn-reveal-secret"}}</button>
</p>
</div>
</div>
<div class="card border-primary" id="cardReadSecret">
<div class="card-header bg-primary text-white">
{{T "title-reading-secret"}}
</div>
<div class="card-body">
<div class="form-group">
<textarea class="form-control" rows="5" readonly></textarea>
</div>
<p>
{{T "text-hint-burned"}}
</p>
</div>
</div>
</div> <!-- /.col-md-12 -->
</div> <!-- /.row -->
<div class="row mt-5">
<div class="col-md-12 footer">
{{T "text-powered-by"}} <a href="https://github.com/Luzifer/ots"><i class="fab fa-github"></i> Luzifer/OTS</a> {{.version}}
</div>
</div>
</div>
<script src="/js/bundle.js" integrity="{{SRIHash `/js/bundle.js`}}"></script>
<script src="app.js" integrity="{{SRIHash `app.js`}}"></script>
<script src="vars.js"></script>
<script src="app.js"></script>
</body>
</html>

59
main.go
View File

@ -9,7 +9,6 @@ import (
"os"
"path"
"strings"
"text/template"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
@ -56,6 +55,7 @@ func main() {
r := mux.NewRouter()
api.Register(r.PathPrefix("/api").Subrouter())
r.HandleFunc("/vars.js", handleVars)
r.PathPrefix("/").HandlerFunc(http_helpers.GzipFunc(assetDelivery))
log.Fatalf("HTTP server quit: %s", http.ListenAndServe(cfg.Listen, http_helpers.NewHTTPLogHandler(r)))
@ -67,6 +67,12 @@ func assetDelivery(res http.ResponseWriter, r *http.Request) {
assetName = "/index.html"
}
if strings.LastIndex(assetName, ".") < 0 {
// There are no assets with no dot in it
http.Error(res, "404 not found", http.StatusNotFound)
return
}
ext := assetName[strings.LastIndex(assetName, "."):]
assetData, err := Asset(path.Join("frontend", assetName))
if err != nil {
@ -75,21 +81,38 @@ func assetDelivery(res http.ResponseWriter, r *http.Request) {
}
res.Header().Set("Content-Type", mime.TypeByExtension(ext))
if assetName != "/index.html" {
// Do not use template engine on other files than index.html
res.Write(assetData)
return
}
tpl, err := template.New(assetName).Funcs(addTranslateFunc(tplFuncs, r)).Parse(string(assetData))
if err != nil {
log.Errorf("Template for asset %q has an error: %s", assetName, err)
return
}
tpl.Execute(res, map[string]interface{}{
"version": version,
})
res.Write(assetData)
}
func handleVars(w http.ResponseWriter, r *http.Request) {
cookie, _ := r.Cookie("lang")
cookieLang := ""
if cookie != nil {
cookieLang = cookie.Value
}
acceptLang := r.Header.Get("Accept-Language")
defaultLang := "en" // known valid language
vars := map[string]string{
"version": version,
}
switch {
case cookieLang != "":
vars["locale"] = normalizeLang(cookieLang)
case acceptLang != "":
vars["locale"] = normalizeLang(strings.Split(acceptLang, ",")[0])
default:
vars["locale"] = defaultLang
}
w.Header().Set("Content-Type", "application/javascript")
for k, v := range vars {
fmt.Fprintf(w, "var %s = %q\n", k, v)
}
}
func normalizeLang(lang string) string {
return strings.ToLower(strings.Split(lang, "-")[0])
}

83
src/.eslintrc.js Normal file
View File

@ -0,0 +1,83 @@
// https://eslint.org/docs/user-guide/configuring
module.exports = {
'root': true,
'parserOptions': {
parser: 'babel-eslint',
sourceType:'module',
},
'env': {
node: true,
},
'extends': [
// https://github.com/standard/standard/blob/master/docs/RULES-en.md
'eslint:recommended',
],
// required to lint *.vue files
'globals': {
locale: true,
process: true,
version: true,
},
// add your custom rules here
'rules': {
'array-bracket-newline': ['error', { multiline: true }],
'array-bracket-spacing': ['error'],
'arrow-body-style': ['error', 'as-needed'],
'arrow-parens': ['error', 'as-needed'],
'arrow-spacing': ['error', { before: true, after: true }],
'block-spacing': ['error'],
'brace-style': ['error', '1tbs'],
'comma-dangle': ['error', 'always-multiline'], // Apply Contentflow rules
'comma-spacing': ['error'],
'comma-style': ['error', 'last'],
'curly': ['error'],
'dot-location': ['error', 'property'],
'dot-notation': ['error'],
'eol-last': ['error', 'always'],
'eqeqeq': ['error', 'always', { 'null': 'ignore' }],
'func-call-spacing': ['error', 'never'],
'function-paren-newline': ['error', 'multiline'],
'generator-star-spacing': ['off'], // allow async-await
'implicit-arrow-linebreak': ['error'],
'indent': ['error', 2],
'key-spacing': ['error', { beforeColon: false, afterColon: true, mode: 'strict' }],
'keyword-spacing': ['error'],
'linebreak-style': ['error', 'unix'],
'lines-between-class-members': ['error'],
'multiline-comment-style': ['warn'],
'newline-per-chained-call': ['error'],
'no-console': ['off'],
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', // allow debugger during development
'no-else-return': ['error'],
'no-extra-parens': ['error'],
'no-implicit-coercion': ['error'],
'no-lonely-if': ['error'],
'no-multiple-empty-lines': ['warn', { max: 2, maxEOF: 0, maxBOF: 0 }],
'no-multi-spaces': ['error'],
'no-trailing-spaces': ['error'],
'no-unneeded-ternary': ['error'],
'no-useless-return': ['error'],
'no-whitespace-before-property': ['error'],
'object-curly-newline': ['error', { consistent: true }],
'object-curly-spacing': ['error', 'always'],
'object-shorthand': ['error'],
'padded-blocks': ['error', 'never'],
'prefer-arrow-callback': ['error'],
'prefer-const': ['error'],
'prefer-object-spread': ['error'],
'prefer-template': ['error'],
'quote-props': ['error', 'consistent-as-needed', { keywords: true }],
'quotes': ['error', 'single', { allowTemplateLiterals: true }],
'semi': ['error', 'never'],
'space-before-blocks': ['error', 'always'],
'spaced-comment': ['warn', 'always'],
'space-infix-ops': ['error'],
'space-in-parens': ['error', 'never'],
'space-unary-ops': ['error', { words: true, nonwords: false }],
'switch-colon-spacing': ['error'],
'unicode-bom': ['error', 'never'],
'wrap-iife': ['error'],
'yoda': ['error'],
},
}

View File

@ -1,146 +0,0 @@
let securePassword = null
// bindResizeTextarea attaches resize triggers to all available text areas
function bindResizeTextarea() {
$('textarea').each((idx, text) => {
let doResize = () => {
text.style.height = text.scrollHeight + 'px'
}
let delayedResize = () => {
window.setTimeout(doResize, 0)
}
text.setAttribute('style', "height: #{this.scrollHeight}px; min-height: #{this.scrollHeight}px; overflow-y:hidden;")
$(text)
.on('change', doResize)
.on('cut', delayedResize)
.on('paste', delayedResize)
.on('drop', delayedResize)
.on('keydown', delayedResize)
})
}
// createSecret executes the secret creation after encrypting the secret
function createSecret() {
let secret = $('#formCreateSecret').find('textarea').val()
securePassword = Math.random().toString(36).substring(2)
secret = GibberishAES.enc(secret, securePassword)
$.ajax('api/create', {
method: "post",
data: {
secret: secret,
},
dataType: "json",
statusCode: {
201: secretCreated,
400: somethingWrong,
500: somethingWrong,
404: () => {
// Mock for interface testing
secretCreated({
secret_id: 'foobar',
})
},
},
})
return false
}
// dataNotFound displays the not-found error
function dataNotFound() {
$('#notfound').show()
}
// hashLoad reacts on a changed window hash an starts the diplaying of the secret
function hashLoad() {
let hash = window.location.hash
if (hash.length === 0) return
$('#cardNewSecret').hide()
$('#cardSecretURL').hide()
$('#notfound').hide()
$('#somethingwrong').hide()
$('#cardReadSecretPre').show()
}
// initBinds attaches functions to frontend elements
function initBinds() {
$('#formCreateSecret').bind('submit', createSecret)
$('#newSecret, .navbar-brand').bind('click', newSecret)
$(window).bind('hashchange', hashLoad)
$('#revealSecret').bind('click', requestSecret)
bindResizeTextarea()
}
// newSecret removes the window hash and therefore returns to "new secret" mode
function newSecret() {
location.href = location.href.split('#')[0]
}
// requestSecret requests the encrypted secret from the backend
function requestSecret() {
let hash = window.location.hash
hash = decodeURIComponent(hash)
let parts = hash.split('|')
if (parts.length === 2) {
hash = parts[0]
securePassword = parts[1]
}
let id = hash.substring(1)
$.ajax(`api/get/${id}`, {
dataType: "json",
statusCode: {
404: dataNotFound,
200: showData,
}
})
}
// secretCreated generates the share URLs and displays them to the user
function secretCreated(data) {
let secretHash = data.secret_id
if (securePassword !== null) secretHash = `${secretHash}|${securePassword}`
let url = `${location.href.split('#')[0]}#${secretHash}`
$('#cardNewSecret').hide()
$('#cardReadSecretPre').hide()
$('#cardSecretURL').show()
$('#cardSecretURL').find('input').val(url)
$('#cardSecretURL').find('input').focus()
$('#cardSecretURL').find('input').select()
securePassword = null
}
// showData takes the backend answer, decrypts the secret and shows it
function showData(data) {
let secret = data.secret
if (securePassword !== null) secret = GibberishAES.dec(secret, securePassword)
$('#cardNewSecret').hide()
$('#cardSecretURL').hide()
$('#notfound').hide()
$('#somethingwrong').hide()
$('#cardReadSecretPre').hide()
$('#cardReadSecret').show()
$('#cardReadSecret').find('textarea').val(secret)
$('#cardReadSecret').find('textarea').trigger('change')
}
// somethingWrong shows the "something went wrong" screen
function somethingWrong() {
$('#somethingwrong').show()
}
// Trigger initialization functions
$(() => {
initBinds()
hashLoad()
})

208
src/app.vue Normal file
View File

@ -0,0 +1,208 @@
<template>
<div id="app">
<b-navbar toggleable="lg" type="dark" variant="primary">
<b-navbar-brand href="#" @click="newSecret">
<i class="fas fa-user-secret"></i> OTS - One Time Secrets
</b-navbar-brand>
<b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
<b-collapse id="nav-collapse" is-nav>
<b-navbar-nav class="ml-auto">
<b-nav-item @click="newSecret"><i class="fas fa-plus"></i> {{ $t('btn-new-secret') }}</b-nav-item>
</b-navbar-nav>
</b-collapse>
</b-navbar>
<b-container class="mt-4">
<b-row class="justify-content-center">
<b-col md="8">
<b-alert v-model="showError" variant="danger" dismissible v-html="error"></b-alert>
</b-col>
</b-row>
<b-row>
<b-col>
<b-card
border-variant="primary"
header-bg-variant="primary"
header-text-variant="white"
v-if="mode == 'create' && !secretId"
>
<span slot="header" v-html="$t('title-new-secret')"></span>
<b-form-group :label="$t('label-secret-data')">
<b-form-textarea
id="secret"
max-rows="25"
rows="5"
v-model="secret"
>
</b-form-textarea>
</b-form-group>
<b-button variant="success" @click="createSecret">{{ $t('btn-create-secret') }}</b-button>
</b-card>
<b-card
border-variant="success"
header-bg-variant="success"
header-text-variant="white"
v-if="mode == 'create' && secretId"
>
<span slot="header" v-html="$t('title-secret-created')"></span>
<p v-html="$t('text-pre-url')"></p>
<b-form-group>
<b-form-input :value="secretUrl" readonly></b-form-input>
</b-form-group>
<p v-html="$t('text-burn-hint')"></p>
</b-card>
<b-card
border-variant="primary"
header-bg-variant="primary"
header-text-variant="white"
v-if="mode == 'view'"
>
<span slot="header" v-html="$t('title-reading-secret')"></span>
<template v-if="!secret">
<p v-html="$t('text-pre-reveal-hint')"></p>
<b-button variant="success" @click=requestSecret>{{ $t('btn-reveal-secret') }}</b-button>
</template>
<template v-else>
<b-form-group>
<b-form-textarea
max-rows="25"
readonly
rows="5"
:value="secret"
>
</b-form-textarea>
</b-form-group>
<p v-html="$t('text-hint-burned')"></p>
</template>
</b-card>
</b-col>
</b-row>
<b-row class="mt-5">
<b-col class="footer">
{{ $t('text-powered-by') }} <a href="https://github.com/Luzifer/ots"><i class="fab fa-github"></i> Luzifer/OTS</a> {{ $root.version }}
</b-col>
</b-row>
</b-container>
</div>
</template>
<script>
import axios from 'axios'
import AES from 'gibberish-aes/src/gibberish-aes'
export default {
name: 'app',
computed: {
secretUrl() {
return `${window.location.href}#${this.secretId}|${this.securePassword}`
},
},
data() {
return {
error: '',
mode: 'create',
secret: '',
securePassword:'',
secretId: '',
showError: false,
}
},
methods: {
// createSecret executes the secret creation after encrypting the secret
createSecret() {
this.securePassword = Math.random().toString(36).substring(2)
const secret = AES.enc(this.secret, this.securePassword)
axios.post('api/create', { secret })
.then(resp=>{
this.secretId = resp.data.secret_id
this.secret = ''
})
.catch(err => {
switch (err.response.status) {
case 404:
// Mock for interface testing
this.secretId = 'foobar'
break
default:
this.error = this.$t('alert-something-went-wrong')
this.showError = true
}
})
return false
},
// hashLoad reacts on a changed window hash an starts the diplaying of the secret
hashLoad() {
const hash = window.location.hash
if (hash.length === 0) return
const parts = hash.substring(1).split('|')
if (parts.length == 2) {
this.securePassword = parts[1]
}
this.secretId = parts[0]
this.mode = 'view'
},
// newSecret removes the window hash and therefore returns to "new secret" mode
newSecret() {
location.href = location.href.split('#')[0]
},
// requestSecret requests the encrypted secret from the backend
requestSecret() {
axios.get(`api/get/${this.secretId}`)
.then(resp => {
let secret = resp.data.secret
if (this.securePassword) {
secret = AES.dec(secret, this.securePassword)
}
this.secret = secret
})
.catch(err => {
switch(err.response.status) {
case 404:
this.error = this.$t('alert-secret-not-found')
this.showError = true
break
default:
this.error = this.$t('alert-something-went-wrong')
this.showError = true
}
})
},
},
// Trigger initialization functions
mounted() {
window.onhashchange = this.hashLoad
this.hashLoad()
},
}
</script>
<style>
textarea {
font-family: monospace;
}
.footer {
color: #2f2f2f;
font-size: 0.9em;
text-align: center;
}
</style>

38
src/main.js Normal file
View File

@ -0,0 +1,38 @@
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import 'bootswatch/dist/flatly/bootstrap.css'
import app from './app.vue'
import messages from './langs/langs.js'
console.log(['app.js loaded',app,messages])
Vue.use(BootstrapVue)
Vue.use(VueI18n)
const i18n = new VueI18n({
locale,
fallbackLocale: 'en',
messages,
})
new Vue({
components: { app },
data: {
error: null,
secret: '',
securePassword: null,
view: 'create',
version,
},
el: '#app',
i18n,
render: createElement => createElement('app'),
})

351
src/package-lock.json generated
View File

@ -3,6 +3,120 @@
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"@nuxt/opencollective": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@nuxt/opencollective/-/opencollective-0.2.2.tgz",
"integrity": "sha512-ie50SpS47L+0gLsW4yP23zI/PtjsDRglyozX2G09jeiUazC1AJlGPZo0JUs9iuCDUoIgsDEf66y7/bSfig0BpA==",
"requires": {
"chalk": "^2.4.1",
"consola": "^2.3.0",
"node-fetch": "^2.3.0"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"requires": {
"color-convert": "^1.9.0"
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"@vue/component-compiler-utils": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-2.6.0.tgz",
"integrity": "sha512-IHjxt7LsOFYc0DkTncB7OXJL7UzwOLPPQCfEUNyxL2qt+tF12THV+EO33O1G2Uk4feMSWua3iD39Itszx0f0bw==",
"dev": true,
"requires": {
"consolidate": "^0.15.1",
"hash-sum": "^1.0.2",
"lru-cache": "^4.1.2",
"merge-source-map": "^1.1.0",
"postcss": "^7.0.14",
"postcss-selector-parser": "^5.0.0",
"prettier": "1.16.3",
"source-map": "~0.6.1",
"vue-template-es2015-compiler": "^1.9.0"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"postcss": {
"version": "7.0.16",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.16.tgz",
"integrity": "sha512-MOo8zNSlIqh22Uaa3drkdIAgUGEL+AD1ESiSdmElLUmE2uVDo1QloiT/IfW9qRw8Gw+Y/w69UVMGwbufMSftxA==",
"dev": true,
"requires": {
"chalk": "^2.4.2",
"source-map": "^0.6.1",
"supports-color": "^6.1.0"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"@webassemblyjs/ast": {
"version": "1.8.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz",
@ -419,6 +533,15 @@
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
"dev": true
},
"axios": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz",
"integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=",
"requires": {
"follow-redirects": "^1.3.0",
"is-buffer": "^1.1.5"
}
},
"babel-code-frame": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
@ -1152,6 +1275,29 @@
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
"dev": true
},
"bootstrap": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.3.1.tgz",
"integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag=="
},
"bootstrap-vue": {
"version": "2.0.0-rc.19",
"resolved": "https://registry.npmjs.org/bootstrap-vue/-/bootstrap-vue-2.0.0-rc.19.tgz",
"integrity": "sha512-OCbRwqKb0F+RGr162m+RyKI4yNM0VjfxOGI32CMgHfCnnc0MZ0wF2Svg2E3Q7AWCq0N8LgD/EsF/K7Vg3kdDyw==",
"requires": {
"@nuxt/opencollective": "^0.2.2",
"bootstrap": "^4.3.1",
"core-js": ">=2.6.5 <3.0.0",
"popper.js": "^1.15.0",
"portal-vue": "^2.1.1",
"vue-functional-data-merge": "^2.0.7"
}
},
"bootswatch": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/bootswatch/-/bootswatch-4.3.1.tgz",
"integrity": "sha512-kNdpo/TnhO++aic1IODLIe1V0lx6pXwHMpwXMacpANDnuVDtgU1MUgUbVMC3rSWm4UcbImfwPraNYgjKDT0BtA=="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -1521,7 +1667,6 @@
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": {
"color-name": "1.1.3"
}
@ -1529,8 +1674,7 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"combined-stream": {
"version": "1.0.7",
@ -1577,6 +1721,11 @@
"typedarray": "^0.0.6"
}
},
"consola": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/consola/-/consola-2.6.1.tgz",
"integrity": "sha512-vt35owQG6OxYDJVaViQ4aFgOK+b98hIvs+R5CWkKgpO8rTPyaYwlMadZ7oZcjnWz1/+u4czDnrcogFr5AtrRug=="
},
"console-browserify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
@ -1592,6 +1741,15 @@
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
"dev": true
},
"consolidate": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz",
"integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==",
"dev": true,
"requires": {
"bluebird": "^3.1.1"
}
},
"constants-browserify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
@ -1630,8 +1788,7 @@
"core-js": {
"version": "2.6.5",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz",
"integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==",
"dev": true
"integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A=="
},
"core-util-is": {
"version": "1.0.2",
@ -1785,6 +1942,12 @@
"integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=",
"dev": true
},
"de-indent": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
"integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=",
"dev": true
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@ -1991,8 +2154,7 @@
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"eslint-scope": {
"version": "4.0.3",
@ -2313,6 +2475,29 @@
"readable-stream": "^2.3.6"
}
},
"follow-redirects": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz",
"integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==",
"requires": {
"debug": "^3.2.6"
},
"dependencies": {
"debug": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
}
}
},
"for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@ -3003,6 +3188,11 @@
"assert-plus": "^1.0.0"
}
},
"gibberish-aes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/gibberish-aes/-/gibberish-aes-1.0.0.tgz",
"integrity": "sha1-9kHEWPuCLgrWHDwN6hWOC5Q+n8U="
},
"glob": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
@ -3113,8 +3303,7 @@
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"has-unicode": {
"version": "2.0.1",
@ -3164,6 +3353,12 @@
"safe-buffer": "^5.0.1"
}
},
"hash-sum": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz",
"integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=",
"dev": true
},
"hash.js": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
@ -3174,6 +3369,12 @@
"minimalistic-assert": "^1.0.1"
}
},
"he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true
},
"hmac-drbg": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
@ -3345,6 +3546,12 @@
"repeating": "^2.0.0"
}
},
"indexes-of": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
"integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=",
"dev": true
},
"indexof": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
@ -3432,8 +3639,7 @@
"is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
"dev": true
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
},
"is-data-descriptor": {
"version": "0.1.4",
@ -3880,6 +4086,23 @@
}
}
},
"merge-source-map": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz",
"integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==",
"dev": true,
"requires": {
"source-map": "^0.6.1"
},
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
}
}
},
"micromatch": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
@ -4088,6 +4311,11 @@
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
"dev": true
},
"node-fetch": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.5.0.tgz",
"integrity": "sha512-YuZKluhWGJwCcUu4RlZstdAxr8bFfOVHakc1mplwHkk8J+tqM1Y5yraYvIUpeX8aY7+crCwiELJq7Vl0o0LWXw=="
},
"node-gyp": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
@ -4539,6 +4767,16 @@
"find-up": "^2.1.0"
}
},
"popper.js": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.15.0.tgz",
"integrity": "sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA=="
},
"portal-vue": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/portal-vue/-/portal-vue-2.1.4.tgz",
"integrity": "sha512-Mr2h+RvoOOGHS7N0E3QPP+UQMt1OhSjQ7eMSGTXqkLiO0AjGEDw2x4kzmHATsZfDqQumiaYSDRzlUP2By3lvsA=="
},
"posix-character-classes": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@ -4632,12 +4870,37 @@
"postcss": "^6.0.1"
}
},
"postcss-selector-parser": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz",
"integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==",
"dev": true,
"requires": {
"cssesc": "^2.0.0",
"indexes-of": "^1.0.1",
"uniq": "^1.0.1"
},
"dependencies": {
"cssesc": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz",
"integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==",
"dev": true
}
}
},
"postcss-value-parser": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
"dev": true
},
"prettier": {
"version": "1.16.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.16.3.tgz",
"integrity": "sha512-kn/GU6SMRYPxUakNXhpP0EedT/KmaPzr0H5lIsDogrykbaxOpOfAFfk5XA7DZrJyMAv1wlMV3CPcZruGXVVUZw==",
"dev": true
},
"private": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
@ -5941,6 +6204,12 @@
}
}
},
"uniq": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
"integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=",
"dev": true
},
"unique-filename": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
@ -6101,6 +6370,66 @@
"indexof": "0.0.1"
}
},
"vue": {
"version": "2.6.10",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.10.tgz",
"integrity": "sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ=="
},
"vue-functional-data-merge": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/vue-functional-data-merge/-/vue-functional-data-merge-2.0.7.tgz",
"integrity": "sha512-pvLc+H+x2prwBj/uSEIITyxjz/7ZUVVK8uYbrYMmhDvMXnzh9OvQvVEwcOSBQjsubd4Eq41/CSJaWzy4hemMNQ=="
},
"vue-hot-reload-api": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.3.tgz",
"integrity": "sha512-KmvZVtmM26BQOMK1rwUZsrqxEGeKiYSZGA7SNWE6uExx8UX/cj9hq2MRV/wWC3Cq6AoeDGk57rL9YMFRel/q+g==",
"dev": true
},
"vue-i18n": {
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.11.2.tgz",
"integrity": "sha512-STcpmxqBrG77SyWi7e0Yn/B3DjKR6mSDwYS4F/V7zoi+e/+CPbVb2TaBqFwnrkoDcPmRfjM7nTwsiRQQOGdifw=="
},
"vue-loader": {
"version": "15.7.0",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.7.0.tgz",
"integrity": "sha512-x+NZ4RIthQOxcFclEcs8sXGEWqnZHodL2J9Vq+hUz+TDZzBaDIh1j3d9M2IUlTjtrHTZy4uMuRdTi8BGws7jLA==",
"dev": true,
"requires": {
"@vue/component-compiler-utils": "^2.5.1",
"hash-sum": "^1.0.2",
"loader-utils": "^1.1.0",
"vue-hot-reload-api": "^2.3.0",
"vue-style-loader": "^4.1.0"
}
},
"vue-style-loader": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.2.tgz",
"integrity": "sha512-0ip8ge6Gzz/Bk0iHovU9XAUQaFt/G2B61bnWa2tCcqqdgfHs1lF9xXorFbE55Gmy92okFT+8bfmySuUOu13vxQ==",
"dev": true,
"requires": {
"hash-sum": "^1.0.2",
"loader-utils": "^1.0.2"
}
},
"vue-template-compiler": {
"version": "2.6.10",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz",
"integrity": "sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg==",
"dev": true,
"requires": {
"de-indent": "^1.0.2",
"he": "^1.1.0"
}
},
"vue-template-es2015-compiler": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz",
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
"dev": true
},
"watchpack": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz",

View File

@ -7,6 +7,8 @@
"node-sass": "^4.9.2",
"sass-loader": "^7.0.3",
"style-loader": "^0.21.0",
"vue-loader": "^15.7.0",
"vue-template-compiler": "^2.6.10",
"webpack": "^4.16.2",
"webpack-cli": "^3.1.0",
"webpack-dev-middleware": "^1.4.0",
@ -16,5 +18,15 @@
"private": true,
"scripts": {
"build": "webpack -p"
},
"dependencies": {
"axios": "^0.18.0",
"bootstrap": "^4.3.1",
"bootstrap-vue": "^2.0.0-rc.19",
"bootswatch": "^4.3.1",
"gibberish-aes": "^1.0.0",
"popper.js": "^1.15.0",
"vue": "^2.6.10",
"vue-i18n": "^8.11.2"
}
}

View File

@ -1,12 +1,14 @@
const path = require('path')
const webpack = require('webpack');
const VueLoaderPlugin = require('vue-loader/lib/plugin')
function resolve(dir) {
return path.join(__dirname, dir)
}
module.exports = {
entry: './app.js',
entry: './main.js',
output: {
filename: 'app.js',
path: path.resolve(__dirname, '..', 'frontend')
@ -17,13 +19,16 @@ module.exports = {
'process.env': {
'NODE_ENV': JSON.stringify('production')
}
})
}),
new VueLoaderPlugin(),
],
optimization: {
minimize: true
},
module: {
rules: [{
rules: [
{
test: /\.(s?)css$/,
use: [
'style-loader',
@ -31,6 +36,7 @@ module.exports = {
'sass-loader',
],
},
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
@ -38,15 +44,17 @@ module.exports = {
loader: 'babel-loader',
options: {
presets: [
['env', {
"targets": {
"browsers": [">0.25%", "not ie 11", "not op_mini all"]
}
}]
['env', { "targets": { "browsers": [">0.25%", "not ie 11", "not op_mini all"] } }]
]
}
}
},
{
test: /\.vue$/,
loader: 'vue-loader',
},
]
}
}