diff --git a/babel.config.js b/babel.config.js index 8c25f6f5..9e6f6ec4 100644 --- a/babel.config.js +++ b/babel.config.js @@ -5,6 +5,7 @@ module.exports = api => ({ useBuiltIns: false, loose: true, }], + '@babel/preset-typescript', ], plugins: [ '@babel/plugin-syntax-dynamic-import', @@ -19,6 +20,7 @@ module.exports = api => ({ ['@babel/preset-env', { targets: { esmodules: true }, }], + '@babel/preset-typescript', ], plugins: [ '@babel/plugin-syntax-dynamic-import', @@ -29,6 +31,7 @@ module.exports = api => ({ ['@babel/preset-env', { targets: { node: 'current' }, }], + '@babel/preset-typescript', ], plugins: [ 'babel-plugin-dynamic-import-node', diff --git a/package.json b/package.json index 0b675155..62733ce4 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,8 @@ "scripts": { "dev": "webpack-dev-server", "build": "rimraf public/app && webpack --mode=production", - "lint": "eslint --ext=.js,.vue -f=beauty .", - "test": "jest", + "lint": "eslint --ext=.js,.vue,.ts -f=beauty .", + "test": "tsc -p . && jest", "codecov": "codecov -F js" }, "dependencies": { @@ -36,8 +36,13 @@ "@babel/plugin-syntax-dynamic-import": "^7.2.0", "@babel/plugin-transform-runtime": "^7.3.4", "@babel/preset-env": "^7.3.4", + "@babel/preset-typescript": "^7.3.3", + "@gplane/tsconfig": "^1.0.0", "@types/jest": "^24.0.11", "@types/jquery": "^3.3.9", + "@types/toastr": "^2.1.36", + "@typescript-eslint/eslint-plugin": "^1.4.2", + "@typescript-eslint/parser": "^1.4.2", "@vue/test-utils": "^1.0.0-beta.29", "autoprefixer": "^9.5.0", "babel-eslint": "^10.0.1", @@ -65,6 +70,7 @@ "style-loader": "^0.23.1", "stylus": "^0.54.5", "stylus-loader": "^3.0.2", + "typescript": "^3.3.3333", "url-loader": "^1.1.2", "vue-jest": "^4.0.0-beta.2", "vue-loader": "^15.7.0", @@ -130,18 +136,37 @@ "prefer-object-spread": 0, "import/no-unresolved": 0, "project/linebreak-between-tests": 2 - } + }, + "overrides": [ + { + "files": [ + "*.ts" + ], + "parserOptions": { + "parser": "@typescript-eslint/parser" + }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "no-undef": 0, + "no-unused-vars": 0, + "no-invalid-this": 0 + } + } + ] }, "jest": { "resetMocks": true, "timers": "fake", "transform": { - "^.+\\.js$": "babel-jest", + ".*\\.(t|j)s$": "babel-jest", ".*\\.vue$": "vue-jest" }, "moduleFileExtensions": [ "js", "vue", + "ts", "json", "node" ], @@ -158,6 +183,6 @@ "/resources/assets/tests/setup", "/resources/assets/tests/utils" ], - "testRegex": "resources/assets/tests/.*\\.(spec|test)\\.js$" + "testRegex": "resources/assets/tests/.*\\.(spec|test)\\.(t|j)s$" } } diff --git a/resources/assets/src/js/check-updates.js b/resources/assets/src/js/check-updates.ts similarity index 56% rename from resources/assets/src/js/check-updates.js rename to resources/assets/src/js/check-updates.ts index ce03b8bb..58c55f31 100644 --- a/resources/assets/src/js/check-updates.js +++ b/resources/assets/src/js/check-updates.ts @@ -1,13 +1,13 @@ import { init } from './net' -export async function checkForUpdates() { +export async function checkForUpdates(): Promise { const response = await fetch(`${blessing.base_url}/admin/update/check`, init) if (response.ok) { const data = await response.json() - if (data.available) { - document.querySelector(`[href="${blessing.base_url}/admin/update"]`) - .innerHTML += ` + const el = document.querySelector(`[href="${blessing.base_url}/admin/update"]`) + if (data.available && el) { + el.innerHTML += ` v${data.latest} ` @@ -15,14 +15,14 @@ export async function checkForUpdates() { } } -export async function checkForPluginUpdates() { +export async function checkForPluginUpdates(): Promise { const response = await fetch(`${blessing.base_url}/admin/plugins/market/check`, init) if (response.ok) { const data = await response.json() - if (data.available) { - document.querySelector(`[href="${blessing.base_url}/admin/plugins/market"]`) - .innerHTML += ` + const el = document.querySelector(`[href="${blessing.base_url}/admin/plugins/market"]`) + if (data.available && el) { + el.innerHTML += ` ${data.plugins.length} ` @@ -30,5 +30,7 @@ export async function checkForPluginUpdates() { } } -window.checkForUpdates = checkForUpdates -window.checkForPluginUpdates = checkForPluginUpdates +Object.assign(window, { + checkForUpdates, + checkForPluginUpdates, +}) diff --git a/resources/assets/src/js/event.js b/resources/assets/src/js/event.js deleted file mode 100644 index 83178efb..00000000 --- a/resources/assets/src/js/event.js +++ /dev/null @@ -1,20 +0,0 @@ -/** @type {{ [name: string]: Function[] }} */ -const bus = Object.create(null) - -/** - * @param {string} eventName - * @param {Function} listener - */ -export function on(eventName, listener) { - (bus[eventName] || (bus[eventName] = [])).push(listener) -} - -/** - * @param {string} eventName - * @param {any} payload - */ -export function emit(eventName, payload) { - bus[eventName] && bus[eventName].forEach(listener => listener(payload)) -} - -blessing.event = { on, emit } diff --git a/resources/assets/src/js/event.ts b/resources/assets/src/js/event.ts new file mode 100644 index 00000000..2dd86af8 --- /dev/null +++ b/resources/assets/src/js/event.ts @@ -0,0 +1,11 @@ +const bus: { [name: string]: CallableFunction[] } = Object.create(null) + +export function on(eventName: string, listener: CallableFunction) { + (bus[eventName] || (bus[eventName] = [])).push(listener) +} + +export function emit(eventName: string, payload?: any) { + bus[eventName] && bus[eventName].forEach(listener => listener(payload)) +} + +blessing.event = { on, emit } diff --git a/resources/assets/src/js/i18n.js b/resources/assets/src/js/i18n.ts similarity index 63% rename from resources/assets/src/js/i18n.js rename to resources/assets/src/js/i18n.ts index 18905d3e..87bbafe9 100644 --- a/resources/assets/src/js/i18n.js +++ b/resources/assets/src/js/i18n.ts @@ -1,27 +1,26 @@ import Vue from 'vue' -/** - * Translate according to given key. - * - * @param {string} key - * @param {object} parameters - * @return {string} - */ -export function trans(key, parameters = Object.create(null)) { +export function trans(key: string, parameters = Object.create(null)): string { const segments = key.split('.') - let temp = blessing.i18n || Object.create(null) + let temp = (blessing.i18n || Object.create(null)) as { [k: string]: string | { [k: string]: string } } + let result = '' for (const segment of segments) { if (!temp[segment]) { return key } - temp = temp[segment] + const middle = temp[segment] + if (typeof middle === 'string') { + result = middle + } else { + temp = middle + } } Object.keys(parameters) - .forEach(slot => (temp = temp.replace(`:${slot}`, parameters[slot]))) + .forEach(slot => (result = result.replace(`:${slot}`, parameters[slot]))) - return temp + return result } Vue.use(_Vue => { @@ -41,4 +40,5 @@ Vue.use(_Vue => { } }) }) + window.trans = trans diff --git a/resources/assets/src/js/index.js b/resources/assets/src/js/index.ts similarity index 100% rename from resources/assets/src/js/index.js rename to resources/assets/src/js/index.ts diff --git a/resources/assets/src/js/logout.js b/resources/assets/src/js/logout.ts similarity index 90% rename from resources/assets/src/js/logout.js rename to resources/assets/src/js/logout.ts index d4be2a2a..9cd88e6e 100644 --- a/resources/assets/src/js/logout.js +++ b/resources/assets/src/js/logout.ts @@ -15,7 +15,7 @@ export async function logout() { } const { msg } = await post('/auth/logout') - setTimeout(() => (window.location = blessing.base_url), 1000) + setTimeout(() => (window.location.href = blessing.base_url), 1000) swal({ type: 'success', text: msg }) } diff --git a/resources/assets/src/js/net.js b/resources/assets/src/js/net.ts similarity index 71% rename from resources/assets/src/js/net.js rename to resources/assets/src/js/net.ts index f9b98b68..d41e3306 100644 --- a/resources/assets/src/js/net.js +++ b/resources/assets/src/js/net.ts @@ -4,30 +4,28 @@ import { queryStringify } from './utils' import { showAjaxError } from './notify' class HTTPError extends Error { - constructor(message, response) { + response: Response + + constructor(message: string, response: Response) { super(message) this.response = response } } const empty = Object.create(null) -/** @type Request */ -export const init = { +export const init: RequestInit = { credentials: 'same-origin', - headers: { + headers: new Headers({ Accept: 'application/json', - }, + }), } function retrieveToken() { - const csrfField = document.querySelector('meta[name="csrf-token"]') - return csrfField && csrfField.content + const csrfField: HTMLMetaElement | null = document.querySelector('meta[name="csrf-token"]') + return (csrfField && csrfField.content) || '' } -/** - * @param {Request} request - */ -export async function walkFetch(request) { +export async function walkFetch(request: Request): Promise { request.headers.set('X-CSRF-TOKEN', retrieveToken()) try { @@ -45,7 +43,7 @@ export async function walkFetch(request) { } } -export function get(url, params = empty) { +export function get(url: string, params = empty): Promise { emit('beforeFetch', { method: 'GET', url, @@ -57,7 +55,7 @@ export function get(url, params = empty) { return walkFetch(new Request(`${blessing.base_url}${url}${qs && `?${qs}`}`, init)) } -export function post(url, data = empty) { +export function post(url: string, data = empty): Promise { emit('beforeFetch', { method: 'POST', url, @@ -77,10 +75,9 @@ export function post(url, data = empty) { } Vue.use(_Vue => { - _Vue.prototype.$http = { - get, - post, - } + Object.defineProperty(_Vue.prototype, '$http', { + get: () => ({ get, post }), + }) }) blessing.fetch = { get, post } diff --git a/resources/assets/src/js/notify.js b/resources/assets/src/js/notify.ts similarity index 62% rename from resources/assets/src/js/notify.js rename to resources/assets/src/js/notify.ts index a4380a0f..ce1e26d5 100644 --- a/resources/assets/src/js/notify.js +++ b/resources/assets/src/js/notify.ts @@ -2,31 +2,23 @@ import $ from 'jquery' import Swal from 'sweetalert2' import toastr from 'toastr' +import { ModalOptions } from '../shims' import { trans } from './i18n' -/** - * Show modal if error occured when sending an ajax request. - * - * @param {Error} error - * @return {void} - */ -export function showAjaxError(error) { +export function showAjaxError(error: Error): void { showModal(error.message.replace(/\n/g, '
'), trans('general.fatalError'), 'danger') } -/** - * Show a bootstrap modal. - * - * @param {string} msg Modal content - * @param {string} title Modal title - * @param {string} type Modal type, default|info|success|warning|error - * @param {object} options All $.fn.modal options, plus { btnText, callback, destroyOnClose } - * @return {void} - */ -export function showModal(msg, title = 'Message', type = 'default', options = {}) { +export function showModal( + msg: string, title = 'Message', + type = 'default', + options: ModalOptions = {} +): void { const btnType = type === 'default' ? 'btn-primary' : 'btn-outline' const btnText = options.btnText || 'OK' - const onClick = options.callback === undefined ? 'data-dismiss="modal"' : `onclick="${options.callback}"` + const onClick = options.callback === undefined + ? 'data-dismiss="modal"' + : `onclick="${options.callback}"` const destroyOnClose = options.destroyOnClose !== false const dom = ` @@ -51,9 +43,9 @@ export function showModal(msg, title = 'Message', type = 'default', options = {} $(dom) .on('hidden.bs.modal', /* istanbul ignore next */ function modal() { - // eslint-disable-next-line no-invalid-this destroyOnClose && $(this).remove() }) + // @ts-ignore .modal(options) } @@ -62,13 +54,9 @@ const swalInstance = Swal.mixin({ cancelButtonText: trans('general.cancel'), }) -/** - * @param {import('sweetalert2').SweetAlertOptions} options - */ -export function swal(options) { +export function swal(options: import('sweetalert2').SweetAlertOptions) { return swalInstance.fire(options) } -window.toastr = toastr -window.swal = swal -blessing.notify = { showModal } +Object.assign(window, { toastr, swal }) +Object.assign(blessing, { notify: { showModal } }) diff --git a/resources/assets/src/js/utils.js b/resources/assets/src/js/utils.js deleted file mode 100644 index f97709eb..00000000 --- a/resources/assets/src/js/utils.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * @param {Function} func - * @param {number} delay - */ -export function debounce(func, delay) { - let timer - return () => { - clearTimeout(timer) - timer = setTimeout(func, delay) - } -} - -/** - * Get parameters in query string with key. - * - * @param {string} key - * @param {string} defaultValue - * @return {string} - */ -export function queryString(key, defaultValue) { - const result = location.search.match(new RegExp(`[?&]${key}=([^&]+)`, 'i')) - - if (result === null || result.length < 1) { - return defaultValue - } - return result[1] -} - -/** - * Serialize data to URL query string - * - * @param {object} data - * @returns {string} - */ -export function queryStringify(params) { - return Object - .keys(params) - .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`) - .join('&') -} diff --git a/resources/assets/src/js/utils.ts b/resources/assets/src/js/utils.ts new file mode 100644 index 00000000..7fe5d7f9 --- /dev/null +++ b/resources/assets/src/js/utils.ts @@ -0,0 +1,23 @@ +export function debounce(func: CallableFunction, delay: number) { + let timer: number + return () => { + clearTimeout(timer) + timer = setTimeout(func, delay) + } +} + +export function queryString(key: string, defaultValue: string = ''): string { + const result = location.search.match(new RegExp(`[?&]${key}=([^&]+)`, 'i')) + + if (result === null || result.length < 1) { + return defaultValue + } + return result[1] +} + +export function queryStringify(params: { [key: string]: string }): string { + return Object + .keys(params) + .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`) + .join('&') +} diff --git a/resources/assets/src/shims.d.ts b/resources/assets/src/shims.d.ts index 0a59ff63..df70a46c 100644 --- a/resources/assets/src/shims.d.ts +++ b/resources/assets/src/shims.d.ts @@ -1,7 +1,9 @@ +/* eslint-disable camelcase */ import Vue from 'vue' +import * as JQuery from 'jquery' declare global { - var blessing: { + let blessing: { base_url: string debug: boolean env: string @@ -48,3 +50,9 @@ declare module 'vue/types/vue' { $route: string[] } } + +interface ModalOptions { + btnText?: string, + callback?: CallableFunction, + destroyOnClose?: boolean +} diff --git a/resources/assets/tests/.eslintrc.yml b/resources/assets/tests/.eslintrc.yml index baa447d7..dbe42d41 100644 --- a/resources/assets/tests/.eslintrc.yml +++ b/resources/assets/tests/.eslintrc.yml @@ -4,3 +4,4 @@ env: rules: no-empty-function: 0 func-names: 0 + no-extra-parens: 0 diff --git a/resources/assets/tests/js/check-updates.test.js b/resources/assets/tests/js/check-updates.test.ts similarity index 55% rename from resources/assets/tests/js/check-updates.test.js rename to resources/assets/tests/js/check-updates.test.ts index f1ef3474..e97de187 100644 --- a/resources/assets/tests/js/check-updates.test.js +++ b/resources/assets/tests/js/check-updates.test.ts @@ -16,19 +16,17 @@ test('check for BS updates', async () => { json: () => Promise.resolve({ available: true, latest: '4.0.0' }), }) - document.body.innerHTML = ` - - ` + document.body.innerHTML = '' await checkForUpdates() expect(window.fetch).toBeCalledWith('/admin/update/check', init) - expect(document.querySelector('a').innerHTML).toBe('') + expect(document.querySelector('a')!.innerHTML).toBe('') await checkForUpdates() - expect(document.querySelector('a').innerHTML).toBe('') + expect(document.querySelector('a')!.innerHTML).toBe('') await checkForUpdates() - expect(document.querySelector('a').innerHTML).toContain('4.0.0') + expect(document.querySelector('a')!.innerHTML).toContain('4.0.0') }) test('check for plugins updates', async () => { @@ -43,17 +41,29 @@ test('check for plugins updates', async () => { json: () => Promise.resolve({ available: true, plugins: [{}] }), }) - document.body.innerHTML = ` - - ` + document.body.innerHTML = '' await checkForPluginUpdates() expect(window.fetch).toBeCalledWith('/admin/plugins/market/check', init) - expect(document.querySelector('a').innerHTML).toBe('') + expect(document.querySelector('a')!.innerHTML).toBe('') await checkForPluginUpdates() - expect(document.querySelector('a').innerHTML).toBe('') + expect(document.querySelector('a')!.innerHTML).toBe('') await checkForPluginUpdates() - expect(document.querySelector('a').innerHTML).toContain('1') + expect(document.querySelector('a')!.innerHTML).toContain('1') +}) + +test('do not update anything if element not found', async () => { + window.fetch = jest.fn() + .mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ available: true, latest: '4.0.0' }), + }) + .mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ available: true, plugins: [{}] }), + }) + + await Promise.all([checkForUpdates, checkForPluginUpdates]) }) diff --git a/resources/assets/tests/js/event.test.js b/resources/assets/tests/js/event.test.ts similarity index 76% rename from resources/assets/tests/js/event.test.js rename to resources/assets/tests/js/event.test.ts index ea1dc67a..865a1a9f 100644 --- a/resources/assets/tests/js/event.test.js +++ b/resources/assets/tests/js/event.test.ts @@ -1,9 +1,5 @@ import * as emitter from '@/js/event' -test('mount variable to global', () => { - expect(window.bsEmitter).toBeFrozen() -}) - test('add listener and emit event', () => { const mockA = jest.fn() const mockB = jest.fn() diff --git a/resources/assets/tests/js/i18n.test.js b/resources/assets/tests/js/i18n.test.ts similarity index 100% rename from resources/assets/tests/js/i18n.test.js rename to resources/assets/tests/js/i18n.test.ts diff --git a/resources/assets/tests/js/logout.test.js b/resources/assets/tests/js/logout.test.ts similarity index 100% rename from resources/assets/tests/js/logout.test.js rename to resources/assets/tests/js/logout.test.ts diff --git a/resources/assets/tests/js/net.test.js b/resources/assets/tests/js/net.test.ts similarity index 80% rename from resources/assets/tests/js/net.test.js rename to resources/assets/tests/js/net.test.ts index 5d2190f1..03171ffe 100644 --- a/resources/assets/tests/js/net.test.js +++ b/resources/assets/tests/js/net.test.ts @@ -1,13 +1,18 @@ +import Vue from 'vue' import * as net from '@/js/net' import { on } from '@/js/event' import { showAjaxError } from '@/js/notify' jest.mock('@/js/notify') -window.Request = function Request(url, init) { - this.url = url - Object.keys(init).forEach(key => (this[key] = init[key])) - this.headers = new Map(Object.entries(init.headers)) +;(window as Window & { Request: any }).Request = class { + headers: Map + + constructor(public url: string, init: Request) { + this.url = url + Object.assign(this, init) + this.headers = new Map(Object.entries(init.headers)) + } } test('the GET method', async () => { @@ -96,23 +101,28 @@ test('low level fetch', async () => { text: () => Promise.resolve('text'), }) - const request = { headers: new Map() } + const request: RequestInit = { headers: new Headers() } const stub = jest.fn() on('fetchError', stub) - await net.walkFetch(request) + await net.walkFetch(request as Request) expect(showAjaxError.mock.calls[0][0]).toBeInstanceOf(Error) expect(showAjaxError.mock.calls[0][0]).toHaveProperty('message', 'network') expect(stub).toBeCalledWith(expect.any(Error)) - await net.walkFetch(request) + await net.walkFetch(request as Request) expect(showAjaxError.mock.calls[1][0]).toBeInstanceOf(Error) expect(stub.mock.calls[1][0]).toHaveProperty('message', '404') expect(stub.mock.calls[1][0]).toHaveProperty('response') - await net.walkFetch(request) + await net.walkFetch(request as Request) expect(json).toBeCalled() - expect(await net.walkFetch(request)).toBe('text') + expect(await net.walkFetch(request as Request)).toBe('text') +}) + +test('inject to Vue instance', () => { + expect(typeof Vue.prototype.$http.get).toBe('function') + expect(typeof Vue.prototype.$http.post).toBe('function') }) diff --git a/resources/assets/tests/js/notify.test.js b/resources/assets/tests/js/notify.test.ts similarity index 97% rename from resources/assets/tests/js/notify.test.js rename to resources/assets/tests/js/notify.test.ts index e712c10f..01e6f6a7 100644 --- a/resources/assets/tests/js/notify.test.js +++ b/resources/assets/tests/js/notify.test.ts @@ -10,6 +10,7 @@ jest.mock('sweetalert2', () => ({ })) test('show AJAX error', () => { + // @ts-ignore $.fn.modal = function () { document.body.innerHTML = this.html() } diff --git a/resources/assets/tests/js/types.d.ts b/resources/assets/tests/js/types.d.ts new file mode 100644 index 00000000..be749566 --- /dev/null +++ b/resources/assets/tests/js/types.d.ts @@ -0,0 +1,15 @@ +class Request { + url: string + + headers: Map +} + +interface Window { + trans(key: string, parameters: object): string + + blessing: { + i18n: object + } + + fetch: jest.Mock +} diff --git a/resources/assets/tests/js/utils.test.js b/resources/assets/tests/js/utils.test.ts similarity index 91% rename from resources/assets/tests/js/utils.test.js rename to resources/assets/tests/js/utils.test.ts index 1ec18194..c44c3730 100644 --- a/resources/assets/tests/js/utils.test.js +++ b/resources/assets/tests/js/utils.test.ts @@ -14,7 +14,7 @@ test('debounce', () => { test('queryString', () => { history.pushState({}, 'page', `${location.href}?key=value`) expect(utils.queryString('key')).toBe('value') - expect(utils.queryString('a')).toBeUndefined() + expect(utils.queryString('a')).toBe('') expect(utils.queryString('a', 'b')).toBe('b') }) diff --git a/resources/assets/tests/setup.js b/resources/assets/tests/setup.js index 7894cc5e..e2051813 100644 --- a/resources/assets/tests/setup.js +++ b/resources/assets/tests/setup.js @@ -8,6 +8,12 @@ window.blessing = { version: '4.0.0', } +window.Headers = class extends Map { + constructor(headers = {}) { + super(Object.entries(headers)) + } +} + const noop = () => undefined // eslint-disable-next-line no-console Object.keys(console).forEach(method => (console[method] = noop)) diff --git a/resources/assets/tests/ts-shims/net.ts b/resources/assets/tests/ts-shims/net.ts new file mode 100644 index 00000000..7e204e81 --- /dev/null +++ b/resources/assets/tests/ts-shims/net.ts @@ -0,0 +1,18 @@ +import * as net from '../../src/js/net' + +export const init = {} as typeof net.init + +export const walkFetch = {} as jest.Mock< + ReturnType, + Parameters +> + +export const get = {} as jest.Mock< + ReturnType, + Parameters +> + +export const post = {} as jest.Mock< + ReturnType, + Parameters +> diff --git a/resources/assets/tests/ts-shims/notify.ts b/resources/assets/tests/ts-shims/notify.ts new file mode 100644 index 00000000..fe2d6ca7 --- /dev/null +++ b/resources/assets/tests/ts-shims/notify.ts @@ -0,0 +1,16 @@ +import * as notify from '../../src/js/notify' + +export const showAjaxError = {} as jest.Mock< + ReturnType, + Parameters +> + +export const showModal = {} as jest.Mock< + ReturnType, + Parameters +> + +export const swal = {} as jest.Mock< + ReturnType, + Parameters +> diff --git a/resources/assets/tests/utils.js b/resources/assets/tests/utils.ts similarity index 100% rename from resources/assets/tests/utils.js rename to resources/assets/tests/utils.ts diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..48a228c3 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "@gplane/tsconfig", + "compilerOptions": { + "declaration": false, + "allowJs": true, + "target": "es2015", + "module": "esnext", + "moduleResolution": "node", + "noEmit": true, + "baseUrl": ".", + "paths": { + "@/js/*": [ + "./resources/assets/tests/ts-shims/*", + "./resources/assets/src/js/*" + ] + } + }, + "exclude": [ + "node_modules", + "vendor" + ] +} diff --git a/webpack.config.js b/webpack.config.js index 5d669bf1..d4d0fd15 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -36,7 +36,7 @@ const config = { module: { rules: [ { - test: /\.js$/, + test: /\.(t|j)s$/, exclude: /node_modules/, use: ['cache-loader', 'babel-loader'], }, @@ -103,8 +103,7 @@ const config = { flatten: true, }, { - from: - 'node_modules/admin-lte/dist/css/skins/_all-skins.min.css', + from: 'node_modules/admin-lte/dist/css/skins/_all-skins.min.css', to: 'skins', flatten: true, }, @@ -114,7 +113,7 @@ const config = { ]), ], resolve: { - extensions: ['.js', '.vue', '.json'], + extensions: ['.js', '.ts', '.vue', '.json'], }, devtool: devMode ? 'cheap-module-eval-source-map' : false, devServer: { diff --git a/yarn.lock b/yarn.lock index f0a4f821..3357af5b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -347,6 +347,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-syntax-typescript@^7.2.0": + version "7.3.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.3.3.tgz#a7cc3f66119a9f7ebe2de5383cce193473d65991" + integrity sha512-dGwbSMA1YhVS8+31CnPR7LB4pcbrzcV99wQzby4uAfrkZPYZlQ7ImwdpzLqi6Z6IL02b8IAL379CaMwo0x5Lag== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-arrow-functions@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz#9aeafbe4d6ffc6563bf8f8372091628f00779550" @@ -548,6 +555,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-typescript@^7.3.2": + version "7.3.2" + resolved "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.3.2.tgz#59a7227163e55738842f043d9e5bd7c040447d96" + integrity sha512-Pvco0x0ZSCnexJnshMfaibQ5hnK8aUHSvjCQhC1JR8eeg+iBwt0AtCO7gWxJ358zZevuf9wPSO5rv+WJcbHPXQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-typescript" "^7.2.0" + "@babel/plugin-transform-unicode-regex@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.2.0.tgz#4eb8db16f972f8abb5062c161b8b115546ade08b" @@ -605,6 +620,14 @@ js-levenshtein "^1.1.3" semver "^5.3.0" +"@babel/preset-typescript@^7.3.3": + version "7.3.3" + resolved "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.3.3.tgz#88669911053fa16b2b276ea2ede2ca603b3f307a" + integrity sha512-mzMVuIP4lqtn4du2ynEfdO0+RYcslwrZiJHXu4MGaC1ctJiW2fyaeDrtjJGs7R/KebZ1sgowcIoWf4uRpEfKEg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-typescript" "^7.3.2" + "@babel/runtime@^7.3.4": version "7.3.4" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.3.4.tgz#73d12ba819e365fcf7fd152aed56d6df97d21c83" @@ -736,6 +759,11 @@ version "5.7.2" resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.7.2.tgz#1498c3eb78ee7c78c5488418707de90aaf58d5d7" +"@gplane/tsconfig@^1.0.0": + version "1.0.0" + resolved "https://registry.npmjs.org/@gplane/tsconfig/-/tsconfig-1.0.0.tgz#c7e6d56ece3d045abe46d00aece990d8412440f0" + integrity sha512-x0Lxt+oBXGVK8qYYLQvohFYQZlt9KgeVfkGAEXWOq38Gxu5KdcsGzPvyWPMhz5Og/U2pWXbjw4+rN4Qa7CPiWQ== + "@jest/console@^24.3.0": version "24.3.0" resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.3.0.tgz#7bd920d250988ba0bf1352c4493a48e1cb97671e" @@ -923,6 +951,13 @@ dependencies: "@types/jest-diff" "*" +"@types/jquery@*": + version "3.3.29" + resolved "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.29.tgz#680a2219ce3c9250483722fccf5570d1e2d08abd" + integrity sha512-FhJvBninYD36v3k6c+bVk1DSZwh7B5Dpb/Pyk3HKVsiohn0nhbefZZ+3JXbWQhFyt0MxSl2jRDdGQPHeOHFXrQ== + dependencies: + "@types/sizzle" "*" + "@types/jquery@^3.3.9": version "3.3.9" resolved "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.9.tgz#91f2aaf5c1e91fd79598fc00ecb4504d78b51fd7" @@ -932,16 +967,55 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.3.tgz#7c6b0f8eaf16ae530795de2ad1b85d34bf2f5c58" integrity sha512-wp6IOGu1lxsfnrD+5mX6qwSwWuqsdkKKxTN4aQc4wByHAKZJf9/D4KXPQ1POUjEbnCP5LMggB0OEFNY9OTsMqg== +"@types/sizzle@*": + version "2.3.2" + resolved "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47" + integrity sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg== + "@types/stack-utils@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== +"@types/toastr@^2.1.36": + version "2.1.36" + resolved "https://registry.npmjs.org/@types/toastr/-/toastr-2.1.36.tgz#6b692ec384147324cffe13892e9e5b26bd1e69e7" + integrity sha512-mz4eBVCNrH0AyGpt+6DLl/xE+CPrSqMfQs/cMTAGJl8W9W375DNOTYOon1GsuSJqVU1QqZ4jCuARzo4TNQNrDg== + dependencies: + "@types/jquery" "*" + "@types/yargs@^12.0.2", "@types/yargs@^12.0.9": version "12.0.9" resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.9.tgz#693e76a52f61a2f1e7fb48c0eef167b95ea4ffd0" integrity sha512-sCZy4SxP9rN2w30Hlmg5dtdRwgYQfYRiLo9usw8X9cxlf+H4FqM1xX7+sNH7NNKVdbXMJWqva7iyy+fxh/V7fA== +"@typescript-eslint/eslint-plugin@^1.4.2": + version "1.4.2" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.4.2.tgz#370bc32022d1cc884a5dcf62624ef2024182769d" + integrity sha512-6WInypy/cK4rM1dirKbD5p7iFW28DbSRKT/+PGn+DYzBWEvHq5KnZAqQ5cX25JBc0qMkFxJNxNfBbFXJyyzVcw== + dependencies: + "@typescript-eslint/parser" "1.4.2" + "@typescript-eslint/typescript-estree" "1.4.2" + requireindex "^1.2.0" + tsutils "^3.7.0" + +"@typescript-eslint/parser@1.4.2", "@typescript-eslint/parser@^1.4.2": + version "1.4.2" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-1.4.2.tgz#acfdee2019958a41d308d768e53ded975ef90ce8" + integrity sha512-OqLkY9295DXXaWToItUv3olO2//rmzh6Th6Sc7YjFFEpEuennsm5zhygLLvHZjPxPlzrQgE8UDaOPurDylaUuw== + dependencies: + "@typescript-eslint/typescript-estree" "1.4.2" + eslint-scope "^4.0.0" + eslint-visitor-keys "^1.0.0" + +"@typescript-eslint/typescript-estree@1.4.2": + version "1.4.2" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.4.2.tgz#b16bc36c9a4748a7fca92cba4c2d73c5325c8a85" + integrity sha512-wKgi/w6k1v3R4b6oDc20cRWro2gBzp0wn6CAeYC8ExJMfvXMfiaXzw2tT9ilxdONaVWMCk7B9fMdjos7bF/CWw== + dependencies: + lodash.unescape "4.0.1" + semver "5.5.0" + "@vue/component-compiler-utils@^2.4.0", "@vue/component-compiler-utils@^2.5.1": version "2.5.2" resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-2.5.2.tgz#a8d57e773354ab10e4742c7d6a8dd86184d4d7be" @@ -5427,6 +5501,11 @@ lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" +lodash.unescape@4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c" + integrity sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw= + lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" @@ -7193,6 +7272,11 @@ require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" +requireindex@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef" + integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww== + requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -7376,6 +7460,11 @@ selfsigned@^1.9.1: version "5.5.1" resolved "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" +semver@5.5.0: + version "5.5.0" + resolved "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" + integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA== + semver@^5.5, semver@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" @@ -8146,10 +8235,17 @@ ts-jest@^23.10.5: semver "^5.5" yargs-parser "10.x" -tslib@^1.9.0: +tslib@^1.8.1, tslib@^1.9.0: version "1.9.3" resolved "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" +tsutils@^3.7.0: + version "3.9.1" + resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.9.1.tgz#2a40dc742943c71eca6d5c1994fcf999956be387" + integrity sha512-hrxVtLtPqQr//p8/msPT1X1UYXUjizqSit5d9AQ5k38TcV38NyecL5xODNxa73cLe/5sdiJ+w1FqzDhRBA/anA== + dependencies: + tslib "^1.8.1" + tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" @@ -8185,6 +8281,11 @@ typedarray@^0.0.6, typedarray@~0.0.5: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" +typescript@^3.3.3333: + version "3.3.3333" + resolved "https://registry.npmjs.org/typescript/-/typescript-3.3.3333.tgz#171b2c5af66c59e9431199117a3bcadc66fdcfd6" + integrity sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw== + uglify-js@^3.1.4: version "3.4.9" resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3"