From 0c745a6eeecce5cc8369d62a4d40cb67d8ad5dec Mon Sep 17 00:00:00 2001 From: Rijk van Zanten Date: Tue, 19 May 2020 17:16:44 -0400 Subject: [PATCH] Switch to history mode in app (#594) * Switch to history mode in pp * Remove broken tests * Install asset manifest * Only build to modern * Remove override files in favor of settings * Build assets json * Use dynamic routing based on api basepath * Remove override files * Set webpack public path based on passed window var * Fix tests --- babel.config.js | 10 ++- package.json | 3 +- public/index.html | 3 - public/script.js | 5 -- public/style.css | 7 -- src/api.test.ts | 14 +--- src/api.ts | 9 +-- src/components/v-notice/v-notice.test.ts | 79 ------------------- src/main.ts | 2 + src/router.ts | 14 +++- src/utils/get-root-path/get-root-path.test.ts | 15 ++++ src/utils/get-root-path/get-root-path.ts | 7 ++ src/utils/get-root-path/index.ts | 4 + vue.config.js | 12 ++- yarn.lock | 34 +++++++- 15 files changed, 96 insertions(+), 122 deletions(-) delete mode 100644 public/script.js delete mode 100644 public/style.css delete mode 100644 src/components/v-notice/v-notice.test.ts create mode 100644 src/utils/get-root-path/get-root-path.test.ts create mode 100644 src/utils/get-root-path/get-root-path.ts create mode 100644 src/utils/get-root-path/index.ts diff --git a/babel.config.js b/babel.config.js index ad85a2f375..7da92c5fa0 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,4 +1,12 @@ module.exports = { - presets: [['@vue/cli-plugin-babel/preset', { jsx: false }]], + presets: [ + [ + '@vue/app', + { + targets: { esmodules: true }, + polyfills: [], + }, + ], + ], plugins: ['@babel/plugin-proposal-optional-chaining'], }; diff --git a/package.json b/package.json index 3108f31b99..49e048b1b3 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "author": "Rijk van Zanten ", "scripts": { "serve": "vue-cli-service serve", - "build": "vue-cli-service build --modern", + "build": "vue-cli-service build", "test": "vue-cli-service test:unit", "lint": "vue-cli-service lint", "lint:styles": "stylelint \"**/*.{vue,scss}\"", @@ -104,6 +104,7 @@ "vue-template-compiler": "^2.6.10", "vuepress": "^1.5.0", "webpack": "^4.43.0", + "webpack-assets-manifest": "^3.1.1", "webpack-merge": "^4.2.2" }, "gitHooks": { diff --git a/public/index.html b/public/index.html index db8227526f..afdd7f8093 100644 --- a/public/index.html +++ b/public/index.html @@ -7,8 +7,6 @@ Directus - -
- diff --git a/public/script.js b/public/script.js deleted file mode 100644 index f25d93371b..0000000000 --- a/public/script.js +++ /dev/null @@ -1,5 +0,0 @@ -/* - * This is a custom script file that is loaded for each instance on each page. - * - * Use to it add whatever client scripts your project might need - */ diff --git a/public/style.css b/public/style.css deleted file mode 100644 index 5f8e96d176..0000000000 --- a/public/style.css +++ /dev/null @@ -1,7 +0,0 @@ -/* - Place your custom style overrides here. - - NOTE: The main application's styling is included through JavaScript. Therefore, it gets added to - the head _after_ this file. If you want to override the globally used CSS variables, you can either - use !important, or increase the specificity from :root -*/ diff --git a/src/api.test.ts b/src/api.test.ts index 88de140ea2..bd33c53e2e 100644 --- a/src/api.test.ts +++ b/src/api.test.ts @@ -1,6 +1,6 @@ import Vue from 'vue'; import VueCompositionAPI from '@vue/composition-api'; -import { onRequest, onResponse, onError, getRootPath, RequestError } from './api'; +import { onRequest, onResponse, onError, RequestError } from './api'; import * as auth from '@/auth'; import { useRequestsStore } from '@/stores/requests'; @@ -29,18 +29,6 @@ describe('API', () => { window = Object.create(window); }); - it('Calculates the correct API root URL based on window', () => { - Object.defineProperty(window, 'location', { - value: { - pathname: '/api/nested/admin', - }, - writable: true, - }); - - const result = getRootPath(); - expect(result).toBe('/api/nested/'); - }); - it('Calls startRequest on the store on any request', () => { const store = useRequestsStore({}); const spy = jest.spyOn(store, 'startRequest'); diff --git a/src/api.ts b/src/api.ts index 5a5343c4dd..b10a8f61bc 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,6 +1,7 @@ import axios, { AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'; import { useRequestsStore } from '@/stores/requests'; import { LogoutReason, logout, checkAuth } from '@/auth'; +import getRootPath from '@/utils/get-root-path'; const api = axios.create({ baseURL: getRootPath(), @@ -65,12 +66,4 @@ export const onError = async (error: RequestError) => { api.interceptors.request.use(onRequest); api.interceptors.response.use(onResponse, onError); -export function getRootPath(): string { - const path = window.location.pathname; - const parts = path.split('/'); - const adminIndex = parts.indexOf('admin'); - const rootPath = parts.slice(0, adminIndex).join('/') + '/'; - return rootPath; -} - export default api; diff --git a/src/components/v-notice/v-notice.test.ts b/src/components/v-notice/v-notice.test.ts deleted file mode 100644 index de2f314979..0000000000 --- a/src/components/v-notice/v-notice.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { mount, createLocalVue, Wrapper } from '@vue/test-utils'; -import VueCompositionAPI from '@vue/composition-api'; -import VNotice from './v-notice.vue'; -import VIcon from '@/components/v-icon/'; - -const localVue = createLocalVue(); -localVue.use(VueCompositionAPI); -localVue.component('v-icon', VIcon); - -describe('Notice', () => { - let component: Wrapper; - - beforeEach(() => { - component = mount(VNotice, { - localVue, - slots: { - default: 'I like pizza', - }, - }); - }); - - it('Renders the default slot in the notice', () => { - expect(component.text()).toContain('I like pizza'); - }); - - it('Uses the right color / icon combo for success', async () => { - component.setProps({ - success: true, - }); - - await component.vm.$nextTick(); - - expect(component.classes()).toContain('success'); - expect((component.vm as any).iconName).toBe('check_circle'); - }); - - it('Uses the right color / icon combo for warning', async () => { - component.setProps({ - warning: true, - }); - - await component.vm.$nextTick(); - - expect(component.classes()).toContain('warning'); - expect((component.vm as any).iconName).toBe('warning'); - }); - - it('Uses the right color / icon combo for danger', async () => { - component.setProps({ - danger: true, - }); - - await component.vm.$nextTick(); - - expect(component.classes()).toContain('danger'); - expect((component.vm as any).iconName).toBe('error'); - }); - - it('Defaults to success if all props are given', async () => { - component.setProps({ - success: true, - warning: true, - danger: true, - }); - - await component.vm.$nextTick(); - - expect((component.vm as any).iconName).toBe('check_circle'); - expect(component.classes()).toContain('success'); - }); - - it('Allows setting a custom icon', async () => { - component.setProps({ - icon: 'person', - }); - await component.vm.$nextTick(); - expect((component.vm as any).iconName).toBe('person'); - }); -}); diff --git a/src/main.ts b/src/main.ts index a9a75b81a8..73cccefe76 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,3 +1,5 @@ +__webpack_public_path__ = (window as any).$directusAssetBasePath || '/admin/'; + import Vue from 'vue'; import { version } from '../package.json'; diff --git a/src/router.ts b/src/router.ts index afad668506..97fd6bcd1f 100644 --- a/src/router.ts +++ b/src/router.ts @@ -11,6 +11,8 @@ import useAppStore from '@/stores/app'; import useUserStore from '@/stores/user'; import PrivateNotFoundRoute from '@/routes/private-not-found'; +import getRootPath from '@/utils/get-root-path'; + export const onBeforeEnterProjectChooser: NavigationGuard = (to, from, next) => { const projectsStore = useProjectsStore(); projectsStore.state.currentProjectKey = null; @@ -43,6 +45,9 @@ export const defaultRoutes: RouteConfig[] = [ name: 'login', path: '/:project/login', component: LoginRoute, + props: (route) => ({ + ssoErrorCode: route.query.error, + }), meta: { public: true, }, @@ -83,13 +88,18 @@ export const defaultRoutes: RouteConfig[] = [ ]; const router = new VueRouter({ - mode: 'hash', + mode: 'history', + base: getRootPath() + 'admin/', routes: defaultRoutes, }); export function replaceRoutes(routeFilter: (routes: RouteConfig[]) => RouteConfig[]): void { const newRoutes = routeFilter([...defaultRoutes]); - const newRouter = new VueRouter({ routes: newRoutes }); + const newRouter = new VueRouter({ + mode: 'history', + base: getRootPath() + 'admin/', + routes: newRoutes, + }); // @ts-ignore - Matcher is not officially part of the public API (https://github.com/vuejs/vue-router/issues/2844#issuecomment-509529927) router.matcher = newRouter.matcher; diff --git a/src/utils/get-root-path/get-root-path.test.ts b/src/utils/get-root-path/get-root-path.test.ts new file mode 100644 index 0000000000..29eba5a386 --- /dev/null +++ b/src/utils/get-root-path/get-root-path.test.ts @@ -0,0 +1,15 @@ +import getRootPath from './get-root-path'; + +describe('Utils / get root path', () => { + it('Calculates the correct API root URL based on window', () => { + Object.defineProperty(window, 'location', { + value: { + pathname: '/api/nested/admin', + }, + writable: true, + }); + + const result = getRootPath(); + expect(result).toBe('/api/nested/'); + }); +}); diff --git a/src/utils/get-root-path/get-root-path.ts b/src/utils/get-root-path/get-root-path.ts new file mode 100644 index 0000000000..893076da6d --- /dev/null +++ b/src/utils/get-root-path/get-root-path.ts @@ -0,0 +1,7 @@ +export default function getRootPath(): string { + const path = window.location.pathname; + const parts = path.split('/'); + const adminIndex = parts.indexOf('admin'); + const rootPath = parts.slice(0, adminIndex).join('/') + '/'; + return rootPath; +} diff --git a/src/utils/get-root-path/index.ts b/src/utils/get-root-path/index.ts new file mode 100644 index 0000000000..4f5a3065f4 --- /dev/null +++ b/src/utils/get-root-path/index.ts @@ -0,0 +1,4 @@ +import getRootPath from './get-root-path'; + +export default getRootPath; +export { getRootPath }; diff --git a/vue.config.js b/vue.config.js index 342f5c7d27..8880738c4d 100644 --- a/vue.config.js +++ b/vue.config.js @@ -1,3 +1,8 @@ +/* @tslint:disable */ +/* eslint-disable */ + +const WebpackAssetsManifest = require('webpack-assets-manifest'); + if (!process.env.API_URL && process.env.NODE_ENV === 'development') { console.log(` ⚠️ No API URL passed. Using the demo API as a fallback. @@ -6,7 +11,7 @@ if (!process.env.API_URL && process.env.NODE_ENV === 'development') { module.exports = { lintOnSave: false, - publicPath: process.env.NODE_ENV === 'production' ? '' : '/admin/', + publicPath: '/admin/', devServer: { allowedHosts: ['localhost', '.gitpod.io'], @@ -15,10 +20,15 @@ module.exports = { '/': { target: process.env.API_URL ? process.env.API_URL : 'https://demo.directus.io/', changeOrigin: true, + bypass: (req) => (req.url.startsWith('/admin') ? req.url : null), }, }, }, + configureWebpack: { + plugins: [new WebpackAssetsManifest({ output: 'assets.json' })], + }, + // There are so many chunks (from all the interfaces / layouts) that we need to make sure to not // prefetch them all. Prefetching them all will cause the server to apply rate limits in most cases chainWebpack: (config) => { diff --git a/yarn.lock b/yarn.lock index 18878d39bb..0bcd8524d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4240,7 +4240,7 @@ ccount@^1.0.0: resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.5.tgz#ac82a944905a65ce204eb03023157edf29425c17" integrity sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw== -chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: +chalk@2.4.2, chalk@^2.0, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -9369,6 +9369,16 @@ lodash.defaultsdeep@^4.6.1: resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz#512e9bd721d272d94e3d3a63653fa17516741ca6" integrity sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA== +lodash.get@^4.0: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + +lodash.has@^4.0: + version "4.5.2" + resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" + integrity sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI= + lodash.kebabcase@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" @@ -10044,6 +10054,13 @@ mkdirp@0.x, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1, mkdirp@~0.5.x: dependencies: minimist "^1.2.5" +mkdirp@^0.5: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + mockdate@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-2.0.5.tgz#70c6abf9ed4b2dae65c81dfc170dd1a5cec53620" @@ -15309,6 +15326,19 @@ webidl-conversions@^4.0.2: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== +webpack-assets-manifest@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/webpack-assets-manifest/-/webpack-assets-manifest-3.1.1.tgz#39bbc3bf2ee57fcd8ba07cda51c9ba4a3c6ae1de" + integrity sha512-JV9V2QKc5wEWQptdIjvXDUL1ucbPLH2f27toAY3SNdGZp+xSaStAgpoMcvMZmqtFrBc9a5pTS1058vxyMPOzRQ== + dependencies: + chalk "^2.0" + lodash.get "^4.0" + lodash.has "^4.0" + mkdirp "^0.5" + schema-utils "^1.0.0" + tapable "^1.0.0" + webpack-sources "^1.0.0" + webpack-bundle-analyzer@^3.6.1: version "3.6.1" resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.6.1.tgz#bdb637c2304424f2fbff9a950c7be42a839ae73b" @@ -15419,7 +15449,7 @@ webpack-merge@^4.1.2, webpack-merge@^4.2.2: dependencies: lodash "^4.17.15" -webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: +webpack-sources@^1.0.0, webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==