TypeScript + Vue 3

This commit is contained in:
Jérémy Munsch 2022-06-13 13:37:00 +02:00
parent 81c8cbde69
commit c6e15928f1
No known key found for this signature in database
GPG key ID: 18361AF3BFEC0FEC
17 changed files with 877 additions and 815 deletions

1098
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -11,20 +11,21 @@
},
"devDependencies": {
"@fortawesome/fontawesome-free": "^6.1.1",
"axios": "^0.26.1",
"bootstrap": "^4.0.0",
"@types/bootstrap": "^5.1.12",
"@types/webpack-env": "^1.17.0",
"axios": "^0.21",
"bootstrap": "^5.1.3",
"clean-webpack-plugin": "^4.0.0",
"cross-env": "^7.0",
"jquery": "^3.5.0",
"laravel-mix": "^6.0.43",
"laravel-mix": "^6.0.6",
"laravel-mix-clean": "^0.1.0",
"laravel-mix-polyfill": "^3.0.1",
"lodash": "^4.17.13",
"popper.js": "^1.12",
"lodash": "^4.17.19",
"resolve-url-loader": "^5.0.0",
"sass": "^1.20.1",
"sass-loader": "^8.0.2",
"vue": "^2.6.14",
"vue-loader": "^15.9.8",
"vue-template-compiler": "^2.6.14"
"sass": "^1.52.1",
"sass-loader": "^12.6.0",
"ts-loader": "^9.3.0",
"typescript": "^4.7.3",
"vue": "^3.2.36",
"vue-loader": "^16.8.3"
}
}

39
resources/js/app.js vendored
View file

@ -1,39 +0,0 @@
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./bootstrap');
import Vue from 'vue';
window.Vue = Vue;
if (window.vueDebug) {
Vue.config.devtools = true;
Vue.config.debug = true;
}
/**
* The following block of code may be used to automatically register your
* Vue components. It will recursively scan this directory for the Vue
* components and automatically register them with their "basename".
*
* Eg. ./components/ExampleComponent.vue -> <example-component></example-component>
*/
// const files = require.context('./', true, /\.vue$/i)
// files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default))
Vue.component('login-id', require('./components/LoginIDComponent.vue').default);
Vue.component('wp-auto-login', require('./components/WPAutoLoginComponent.vue').default);
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
const app = new Vue({
el: '#app',
});

View file

@ -1,55 +0,0 @@
window._ = require('lodash');
/**
* We'll load jQuery and the Bootstrap jQuery plugin which provides support
* for JavaScript based Bootstrap features such as modals and tabs. This
* code may be modified to fit the specific needs of your application.
*/
try {
window.Popper = require('popper.js').default;
window.$ = window.jQuery = require('jquery');
require('bootstrap');
} catch (e) {}
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
window.axios = require('axios');
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
/**
* Next we will register the CSRF Token as a common header with Axios so that
* all outgoing HTTP requests automatically have it attached. This is just
* a simple convenience so we don't have to attach every token manually.
*/
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}
/**
* Echo exposes an expressive API for subscribing to channels and listening
* for events that are broadcast by Laravel. Echo and event broadcasting
* allows your team to easily build robust real-time web applications.
*/
// import Echo from 'laravel-echo'
// window.Pusher = require('pusher-js');
// window.Echo = new Echo({
// broadcaster: 'pusher',
// key: process.env.MIX_PUSHER_APP_KEY,
// cluster: process.env.MIX_PUSHER_APP_CLUSTER,
// encrypted: true
// });

View file

@ -1,61 +0,0 @@
<template>
<div>
<div class="row" style="margin: 10px;">
<div v-if="datas.length > 0" class="col-md-12">
<div class="row" v-for="(item, index) in datas">
<div :class="type === 2 ? 'col-md-4' : 'col-md-5'">
<div class="form-group">
<input v-model="item.login" type="text" placeholder="Login" class="form-control" required>
</div>
</div>
<div :class="type === 2 ? 'col-md-4' : 'col-md-5'">
<div class="form-group">
<input v-model="item.pwd" type="text" placeholder="Password" class="form-control" required>
</div>
</div>
<div class="col-md-2">
<a @click="removeItem(index)" href="#" class="btn btn-danger"><i class="fa fa-user-times"></i></a>
</div>
<div v-if="type === 2" class="col-md-2">
<a :href="'/projects/wp-auto-login?url=' + url + 'wp-login.php&login=' + item.login + '&pwd=' + item.pwd" target="_blank" class="btn btn-success"><i class="fa fa-sign-in-alt"></i></a>
</div>
</div>
</div>
<div class="col-md-12 text-center">
<button @click="newItem" class="btn btn-primary" type="button"><i class="fa fa-user-plus"></i></button>
<input type="hidden" :name="name" :value="JSON.stringify(datas)" required>
</div>
</div>
</div>
</template>
<script>
export default {
mounted() {
console.log('Login ID Component mounted.');
this.datas = JSON.parse(this.dbdatas);
},
props: [
'name',
'dbdatas',
'url',
'type'
],
data() {
return {
datas: []
}
},
watch: {
},
methods: {
newItem() {
this.datas.push({login: '', pwd: ''});
},
removeItem(index) {
this.datas.splice(index, 1);
}
}
}
</script>

View file

@ -1,20 +0,0 @@
<template>
<form ref="login" :action="url" method="POST">
<input type="hidden" name="log" :value="login">
<input type="hidden" name="pwd" :value="pwd">
</form>
</template>
<script>
export default {
mounted() {
console.log('WP Auto Login Component mounted.');
this.$refs.login.submit();
},
props: [
'login',
'pwd',
'url'
],
}
</script>

3
resources/ts/back.ts Normal file
View file

@ -0,0 +1,3 @@
// LIBS
import "./common";
import "./vue";

32
resources/ts/common.ts Normal file
View file

@ -0,0 +1,32 @@
import axios from "axios";
import * as bootstrap from "bootstrap";
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
window.axios = axios;
window.bootstrap = bootstrap;
// enable bs tooltips
var tooltipTriggerList = [].slice.call(
document.querySelectorAll('[data-bs-toggle="tooltip"]')
);
tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
const token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common["X-CSRF-TOKEN"] = String(
token.textContent
);
} else {
console.error(
"CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token"
);
}

View file

@ -0,0 +1,94 @@
<template>
<div>
<div class="row" style="margin: 10px">
<div v-if="dataList.length > 0" class="col-md-12">
<div class="row" v-for="(item, index) in dataList">
<div :class="type === 2 ? 'col-md-4' : 'col-md-5'">
<div class="form-group">
<input
v-model="item.login"
type="text"
placeholder="Login"
class="form-control"
required
/>
</div>
</div>
<div :class="type === 2 ? 'col-md-4' : 'col-md-5'">
<div class="form-group">
<input
v-model="item.pwd"
type="text"
placeholder="Password"
class="form-control"
required
/>
</div>
</div>
<div class="col-md-2">
<a @click="removeItem(index)" href="#" class="btn btn-danger"
><i class="fa fa-user-times"></i
></a>
</div>
<div v-if="type === 2" class="col-md-2">
<a
:href="`/projects/wp-auto-login?url=${url}wp-login.php&login=${String(
item.login
)}&pwd=${String(item.pwd)}`"
target="_blank"
class="btn btn-success"
><i class="fa fa-sign-in-alt"></i
></a>
</div>
</div>
</div>
<div class="col-md-12 text-center">
<button @click="newItem" class="btn btn-primary" type="button">
<i class="fa fa-user-plus"></i>
</button>
<input type="hidden" :name="name" :value="JSON.stringify(dataList)" required />
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
beforeMount() {
if (!this.$attrs.json) {
throw new Error("this component requires json");
}
},
mounted() {
const json = String(this.$attrs.json ?? ""),
data = JSON.parse(json);
this.name = data.name;
this.url = data.url;
this.type = data.type;
this.dataList = data.dataList;
},
data(): {
name: string;
url: string;
dataList: LaravelModelList;
type: number;
} {
return {
name: "",
url: "",
type: 2,
dataList: [],
};
},
watch: {},
methods: {
newItem() {
this.dataList.push({ login: "", pwd: "" });
},
removeItem(index: number) {
this.dataList.splice(index, 1);
},
},
});
</script>

View file

@ -0,0 +1,36 @@
<template>
<form ref="login" :action="url" method="POST">
<input type="hidden" name="log" :value="login" />
<input type="hidden" name="pwd" :value="pwd" />
</form>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
beforeMount() {
if (!this.$attrs.json) {
throw new Error("this component requires json");
}
},
mounted() {
const json = String(this.$attrs.json ?? ""),
data = JSON.parse(json);
this.login = data.login;
this.url = data.url;
this.pwd = data.pwd;
(this.$refs.login as HTMLFormElement).submit();
},
data(): {
login: string;
url: string;
pwd: string;
} {
return {
login: "",
pwd: "",
url: "",
};
},
});
</script>

39
resources/ts/index.d.ts vendored Normal file
View file

@ -0,0 +1,39 @@
export {};
import { AxiosStatic } from "axios";
// * Recursive List<string>
type StringList = Record<PropertyKey, string>;
interface NestedStringListInterface
extends Record<PropertyKey, NestedStringListInterface | StringList> {}
// * Recursive List<number>
type NumberList = Record<PropertyKey, number>;
interface NestedNumberListInterface
extends Record<PropertyKey, NestedNumberListInterface | NumberList> {}
// * Recursive List<Model>
type Model = Record<
string,
| string
| number
| object
| NestedStringList
| NestedNumberList
| NestedModelInterface
>;
interface NestedModelInterface extends Model {}
type NestedStringList = NestedStringListInterface | StringList | string;
type NestedNumberList = NestedNumberListInterface | NumberList | number;
type ModelList = Array<Model>;
declare global {
type LaravelModel = Model;
type LaravelModelList = ModelList;
interface Window {
axios: AxiosStatic;
Swal: Function;
}
}

46
resources/ts/vue.ts Normal file
View file

@ -0,0 +1,46 @@
import { App, createApp } from "vue";
document.addEventListener("DOMContentLoaded", () => {
const setupApp = (app: App<Element>) => {
app.config.globalProperties.window = window;
app.config.globalProperties.document = document;
};
// Register components
const vues = require.context("./components", true, /\.vue$/i);
vues.keys().map((key) => {
let htmlKey = key.split("/").pop();
if (!htmlKey) {
throw new Error(`Cannot load ${key}`);
}
htmlKey = htmlKey
.split(".")[0]
.replace(/\.?([A-Z])/g, function (x, y) {
return "-" + y.toLowerCase();
})
.replace(/^-/, "")
.replace("-component", "");
let el = document.getElementById(htmlKey),
elGroup = document.getElementsByClassName(htmlKey);
// * Init using ID
if (el) {
const app = createApp(vues(key).default, {
json: el.dataset.json ?? {},
});
setupApp(app);
app.mount(el);
}
// * Init Using class
for (const grpEl of elGroup) {
// * Hack to prevent loop weirdness
setTimeout(() => {
const app = createApp(vues(key).default, {
json: (grpEl as HTMLElement).dataset.json ?? {},
});
setupApp(app);
app.mount(grpEl);
}, 1000);
}
});
});

View file

@ -14,11 +14,8 @@
<link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">
<!-- Styles -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
<link href="{{ asset('css/solid.css') }}?ver=1.0" rel="stylesheet">
<link href="{{ asset('css/brands.css') }}?ver=1.0" rel="stylesheet">
<link href="{{ asset('css/regular.css') }}?ver=1.0" rel="stylesheet">
<link href="{{ asset('css/fontawesome.css') }}?ver=1.0" rel="stylesheet">
<link href="{{ mix('css/back.css') }}" rel="stylesheet">
<link href="{{ mix('css/fontawesome.min.css') }} rel="stylesheet">
</head>
<body>
<div id="app">
@ -81,11 +78,7 @@
<main class="py-4">
@yield('content')
</main>
<script>
let vueDebug = {{ config('app.debug') == 1 ? 'true' : 'false' }};
</script>
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}" defer></script>
<script src="{{ mix('js/back.js') }}" defer></script>
</div>
</body>
</html>

26
tsconfig.json Normal file
View file

@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "esnext",
"lib": [
"es5", "es6", "dom", "dom.iterable"
],
"module": "esnext",
// "isolatedModules": false,
// "experimentalDecorators": true,
// "strictNullChecks": true,
// "removeComments": true,
// "allowSyntheticDefaultImports": true,
// this enables stricter inference for data properties on `this`
"strict": true,
"jsx": "preserve",
"moduleResolution": "node",
"typeRoots": ["resources/ts/@types", "node_modules/@types"],
},
"include": [
"resources/**/*.js",
"resources/**/*.ts",
"resources/**/*.vue"
],
"compileOnSave": false
}

105
webpack.mix.js vendored
View file

@ -1,52 +1,87 @@
const path = require('path');
const mix = require('laravel-mix');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const path = require('path')
const mix = require('laravel-mix')
require('laravel-mix-polyfill')
require('laravel-mix-clean')
const assetDir = 'assets'
const polyfill = {
enabled: true,
bugfixes: true,
useBuiltIns: "usage",
targets: 'last 3 version, not dead, >0.3%'
}
require('laravel-mix-polyfill');
mix.webpackConfig({
resolve: {
extensions: ["*", ".js", ".vue", ".ts"],
alias: {
'@': path.resolve('resources/assets')
'@': path.resolve('resources/assets'),
'vue$': 'vue/dist/vue.runtime.esm-bundler.js',
}
},
plugins: [
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: [
'css/*',
'js/*',
'images/*',
'fonts/*',
'assets/*'
]
})
],
});
module: {
rules: [
{
test: /\.ts$/,
loader: 'ts-loader',
options: {
appendTsSuffixTo: [/\.vue$/],
}
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
esModule: true
}
},
],
},
// Pour debug compilation errors
// stats: {
// children: true,
// warningsFilter: [
// /\-\-underline\-color/,
// ]
// },
})
let assetDir = 'assets';
mix.options({
fileLoaderDirs: {
images: `${assetDir}/images`,
fonts: `${assetDir}/fonts`
fonts: `${assetDir}/fonts`,
images: `${assetDir}/images`
},
postCss: [
require('postcss-discard-comments')({
removeAll: true
})
]
});
],
})
mix.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css')
.vue()
.polyfill({
corejs: 3,
enabled: true,
useBuiltIns: "usage",
targets: { "firefox": "50", "ie": 11 }
});
mix.clean({
cleanOnceBeforeBuildPatterns: [
'css/*',
'js/*',
'images/*',
'fonts/*',
'assets/*'
]
})
// * BO
mix.ts('resources/ts/back.ts', 'public/js/back.js', { transpileOnly: true })
.vue({
runtimeOnly: true,
extractStyles: true,
globalStyles: false
})
.sass('resources/scss/back.scss', 'public/css/back.css')
.polyfill(polyfill)
// * Copy Blade Images Assets
mix.copyDirectory('resources/assets', 'public/assets')
// * FontAwesome compile
mix.sass('node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss', 'public/css')
.sass('node_modules/@fortawesome/fontawesome-free/scss/brands.scss', 'public/css')
.sass('node_modules/@fortawesome/fontawesome-free/scss/regular.scss', 'public/css')
.sass('node_modules/@fortawesome/fontawesome-free/scss/solid.scss', 'public/css');
mix.copy('node_modules/@fortawesome/fontawesome-free/js/all.min.js', 'public/js/fontawesome.min.js')
.copy('node_modules/@fortawesome/fontawesome-free/css/fontawesome.min.css', 'public/css/fontawesome.min.css')