bencodezen / vue-enterprise-boilerplate

An ever-evolving, very opinionated architecture and dev environment for new Vue SPA projects using Vue CLI.
7.78k stars 1.32k forks source link

Typescript version #174

Closed ghost closed 4 years ago

ghost commented 4 years ago

Hi Chris. Do you plan on creating vue-enterprise-boilerplate with typescript? It would be pretty neat in my opinion since it is a perfect usecase for the enterprises nowadays.

marceloavf commented 4 years ago

I think it would be better to wait Vue 3.0, since typescript integration with Vue 2 has many drawbacks.

ffxsam commented 4 years ago

I also wanted to point out this line:

https://github.com/chrisvfritz/vue-enterprise-boilerplate/blob/master/src/router/routes.js#L48

This isn't valid if your project is using TypeScript, since params is defined as such:

params: Dictionary<string>

Thought @chrisvfritz should know about that.

rickyruiz commented 4 years ago

@ffxsam params is a string dictionary because they must appear in the url.

@chrisvfritz We could create a route meta field to store the user object.

meta?: any
    meta: {
      authRequired: true,
      beforeResolve(routeTo, routeFrom, next) {
        store
          // Try to fetch the user's information by their username
          .dispatch('users/fetchUser', { username: routeTo.params.username })
          .then((user) => {
            // Add the user to the route meta, so that it can
            // be provided as a prop for the view component below.
            routeTo.meta ? routeTo.meta.user = user : routeTo.meta = { user }
            // Continue to the route.
            next()
          })
          .catch(() => {
            // If a user with the provided username could not be
            // found, redirect to the 404 page.
            next({ name: '404', params: { resource: 'User' } })
          })
      },
    },
    // Set the user from route meta, once it's set in the
    // beforeResolve route guard.
    props: (route) => ({ user: route.meta && route.meta.user }),
ffxsam commented 4 years ago

@ffxsam params is a string dictionary because they must appear in the url.

Yes, exactly. And I'm saying that Chris's use of assigning a user object to the param throws an error in TypeScript.

chrisvfritz commented 4 years ago

@MartinTuroci No plans currently. I provide some reasons here for using Babel instead of TypeScript for this project. If you prefer not to convert to TypeScript manually, you may also enjoy some of these other scaffolding/boilerplate projects, many of which do start with TypeScript.

@ffxsam It definitely is a hack using params this way. 😅 To make TypeScript happier, I just updated to use a tmp object in meta instead. It's still a hack, since meta isn't really for temporary storage either, but until Vue Router provides a nicer way of passing data around between hooks, this is probably the best we can do.

beamsies commented 4 years ago

@ffxsam Did you successfully convert this repo to a typescript project?

ffxsam commented 4 years ago

@beamsies Yep I did. Well, I wouldn't say I converted it verbatim.. I made some of my own changes. Why, did you need help with something?

beamsies commented 4 years ago

@ffxsam Well, I really like this boilerplate and think it will be great for my app. But I also think people will question me later as to why I didn't use TS. So, I think I'm going to convert this to TS even though I think it will slow down dev time on a very tight schedule.

I've never used TS w/ Vue/Vuex.

beamsies commented 4 years ago

@ffxsam I am struggling to convert this to a TS app. Is your conversion public? Thanks.

ffxsam commented 4 years ago

It's not public, no. But this is how I wound up modifying the router logic:

import Vue from 'vue';
import VueRouter from 'vue-router';
import VueMeta from 'vue-meta';
import NProgress from 'nprogress';
import routes, { RouteName } from './routes';
import store from '../store';

NProgress.configure({
  showSpinner: false,
});

Vue.use(VueRouter);
Vue.use(VueMeta);

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes,
});

router.beforeEach((routeTo, routeFrom, next) => {
  const redirectToLogin = () => {
    // Pass the original route to the login component
    next({ name: RouteName.LOGIN, query: { redirect: routeTo.fullPath } });
  };

  // If this isn't an initial page load and we're lazy loading a route
  if (routeFrom.name !== null && routeTo.meta.lazy) {
    // Start the route progress bar.
    NProgress.start();
  }

  // Check if auth is required on this route
  // (including nested routes).
  const authRequired = routeTo.matched.some(route => route.meta.authRequired);

  // If auth isn't required for the route, just continue.
  if (!authRequired) return next();

  /**
   * Vuex doesn't think the user is logged in. Check with Cognito (and bypass
   * the cache) to see if this is still a valid user, and store their user info
   * in Vuex.
   */
  if (!store.getters['auth/loggedIn']) {
    return store.dispatch('auth/validate').then(user => {
      user ? next() : redirectToLogin();
    });
  }

  next();
});

router.beforeResolve(async (routeTo, routeFrom, next) => {
  // Create a `beforeResolve` hook, which fires whenever
  // `beforeRouteEnter` and `beforeRouteUpdate` would. This
  // allows us to ensure data is fetched even when params change,
  // but the resolved route does not. We put it in `meta` to
  // indicate that it's a hook we created, rather than part of
  // Vue Router (yet?).
  try {
    // For each matched route...
    for (const route of routeTo.matched) {
      // eslint-disable-next-line no-await-in-loop
      await new Promise((resolve, reject) => {
        // If a `beforeResolve` hook is defined, call it with
        // the same arguments as the `beforeEnter` hook.
        if (route.meta && route.meta.beforeResolve) {
          route.meta.beforeResolve(routeTo, routeFrom, (...args: any[]) => {
            // If the user chose to redirect...
            if (args.length) {
              // If redirecting to the same route we're coming from...
              if (routeFrom.name === args[0].name || args[0] === false) {
                // Complete the animation of the route progress bar.
                NProgress.done();
              }
              // Complete the redirect.
              if (args[0] !== false) {
                next(...args);
                reject(new Error('Redirected'));
              } else {
                reject(new Error('Abort'));
              }
            } else {
              next();
              resolve();
            }
          });
        } else {
          // Otherwise, continue resolving the route.
          next();
          resolve();
        }
      });
    }
    // If a `beforeResolve` hook chose to redirect, just return.
  } catch (error) {
    return;
  }

  // If we reach this point, continue resolving the route.
  // NProgress.done();
  next();
});

// When each route is finished evaluating...
router.afterEach(() => {
  // Complete the animation of the route progress bar.
  NProgress.done();
});

export default router;

I do wanna point out that there's a bug here, and I'm not sure if it exists in @chrisvfritz's original code or not. But when I'm on a login page and I log in, the Login.vue view/page's created() method gets fired before it leaves that route. Not really sure what's going on there.

beamsies commented 4 years ago

@ffxsam Interesting. Thanks for sharing. Did you get all the unit tests to work w/ typescript? I got this architecture converted to typescript.

ffxsam commented 4 years ago

Nope, I didn't really convert anything or work on tests. I just copied the code that I needed and made sure it worked with TypeScript.