2

I have a project using Vue.js 2.4 + TypeScript + RequireJS stack that needs to upgrade to the latest Vue.js. Upgrade to Vue.js breaks it and I could not fix this after making changes per documentation.

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Vue.js Scratchpad</title>
  <link rel="icon" type="image/png" href="favicon.png">
  <meta charset="UTF-8">

  <script src="node_modules/requirejs/require.js"></script>
</head>
<body>

<h1>Vue.js Scratchpad</h1>

<div v-show="true" id="app" style="display: none">
  <input v-model="user" autofocus="true" placeholder="Enter any user name">
  <!--<button @click.prevent="onRenderComponent">Render</button>-->

  <p v-show="loading">Loading...</p>
  <router-view v-show="!loading" :user="user"
               @loading="onLoading" @success="onLoaded"
               @loading-error="onLoadingError"></router-view>
</div>

<script>
  requirejs.config({
    // By default load modules from `./`
    baseUrl: '.',

    /*
    By default, modules would be loaded from {module}.js files (filename = module).
    Specify {module} (or "{module}"): "{filepath_no_extension}" mapping if filename != module.
    Specify fall-backs ([filepath1, filepath2]) for minimized / normal version consumption pattern.
    */
    paths: {
      // All third-party libraries must be included here. Use path fall-backs for minimized libs.
      axios: "node_modules/axios/dist/axios.min",
      vue: "node_modules/vue/dist/vue.min",
      "vue-router": "node_modules/vue-router/dist/vue-router.min"
    }
  });

  require(["app-pure-vue.js"]);
</script>
</body>
</html>

app-pure-vue.ts:

import * as Vue from "vue";
import * as VueRouter from "vue-router";
import { AxiosError } from "axios";

//region App components
import MessageComponent from "./messageComponent-pure-vue";
//endregion

Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    component: MessageComponent
  }
];

const router = new VueRouter({
  routes
});

const appOptions = {
  router,
  data() {
    return {
      user: null,
      loading: false
    };
  },
  methods: {
    onLoading() {
      // @ts-ignore
      this.loading = true;
      console.log("Loading...");
    },

    onLoaded() {
      // @ts-ignore
      this.loading = false;
      console.log("Loaded.");
    },

    onLoadingError(error: AxiosError, serviceUrl: string) {
      // @ts-ignore
      this.loading = false;
      console.log("Loading error for", serviceUrl, error.response);
    }
  }
};

new Vue(appOptions).$mount("#app");

messageComponent-pure-vue.ts:

import * as Vue from "vue";
import * as VueRouter from "vue-router";
import { AxiosError } from "axios";

//region App components
import MessageComponent from "./messageComponent-pure-vue";
//endregion

Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    component: MessageComponent
  }
];

const router = new VueRouter({
  routes
});

const appOptions = {
  router,
  data() {
    return {
      user: null,
      loading: false
    };
  },
  methods: {
    onLoading() {
      // @ts-ignore
      this.loading = true;
      console.log("Loading...");
    },

    onLoaded() {
      // @ts-ignore
      this.loading = false;
      console.log("Loaded.");
    },

    onLoadingError(error: AxiosError, serviceUrl: string) {
      // @ts-ignore
      this.loading = false;
      console.log("Loading error for", serviceUrl, error.response);
    }
  }
};

new Vue(appOptions).$mount("#app");

This example works fine. Now, to upgrade to Vue.js 2.5.0 + the latest vue-router, here are the required documented changes:

  1. package.json: update to "vue": "~2.5.0" + "vue-router": "~3.1.5"
  2. *.ts: import * as Vue from "vue"; => import Vue from "vue";
  3. app-pure-vue.ts: import * as VueRouter from "vue-router"; => import VueRouter from "vue-router";

The code would compile, but blow up on Vue.extend() in messageComponent-pure-vue.ts:

Uncaught TypeError: Cannot read property 'extend' of undefined
    at Object.<anonymous> (messageComponent-pure-vue.ts:5)

Could you help me to fix this? The minimal reproducible example is available here: https://github.com/DKroot/Scratchpad/tree/master/Client_Side/Vue.js-2.5. The working 2.4 code is at https://github.com/DKroot/Scratchpad/tree/master/Client_Side/Vue.js-2.4.

What I did so far on this:

  1. Isolated the issue to 2.5.0 upgrade
  2. Carefully reviewed 2.5.0 release notes (https://github.com/vuejs/vue/releases/tag/v2.5.0) and the corresponding blog post (https://medium.com/the-vue-point/upcoming-typescript-changes-in-vue-2-5-e9bd7e2ecf08)
  3. Reviewed changes in TypeScript declarations in 2.5.0. The exports are a bit too complex for me to figure out what the root cause might be.
11
  • Previously, we already recommend using ES-style imports (import Vue from ‘vue’) everywhere with “allowSyntheticDefaultImports”: true in tsconfig.json. The new typings will officially move to ES-style import/export syntax, so that config is no longer necessary, and users are required to use ES-style imports in all cases. So you need to use import Vue from 'vue', as that's the official ES-style import sytnax Commented Jan 29, 2020 at 0:45
  • @Ohgodwhy This is exactly what I did with 2.5.0: import Vue from "vue"; import VueRouter from "vue-router"; This allows code to compile. It blows up at runtime though. Commented Jan 29, 2020 at 13:21
  • What version of vue-template-compiler do you have installed? Commented Jan 29, 2020 at 17:34
  • @Ohgodwhy None. All of the dependencies in this minimal example are listed in package.json. Commented Jan 29, 2020 at 20:32
  • Can you verify that the vue-template-compiler version in your lock file matches the installed Vue version? Commented Jan 29, 2020 at 20:34

2 Answers 2

2

TL;DR:

Try this instead:

import Vue from "vue";
import VueRouter from "vue-router";

Explanation:

When a module is written this way:

// module.ts
export function x() { ... }
export function y() { ... }

you would import it like so:

import * as Module from "./module";

but when it is written like this:

// module-with-default.ts
export default class Module {
  public static function x() { ... }
  public static function y() { ... }
}

you would import it like this:

import Module from "./module-with-default";

in both cases you will be able to use it like this:

Module.x();
Module.y();

Changes in Vue.js:

This is how it is exported in v2.5.0:

export default Vue

and this is is how it is exported in v2.4.0:

export = Vue;
Sign up to request clarification or add additional context in comments.

7 Comments

It helps more if you supply an explanation why this is the preferred solution and explain how it works. We want to educate, not just provide code.
Excellent! Thank you! This site is special, and a place where we can make a difference. It's about paying it forward!
@DoronG This is exactly what I did in github.com/DKroot/Scratchpad/tree/master/Client_Side/Vue.js-2.5, but it blows up: see #2 and #3 in the original question.
@DoronG I realized that I probably should have posted the non-working 2.5 version rather than the working 2.4 + changes. Let me know how you want to handle this. I can clarify the question or just leave it be. I just would like to get the fix ultimately.
@DKroot I'll take a look at your repos
|
0
+50

TL; DR

Add these to the project:

// vue-loader.ts
import * as AllVue from "Vue";
import Defaults from "Vue";

export const Vue = AllVue as unknown as Defaults;
export default Vue;
// vue-router-loader.ts
import * as AllVueRouter from "Vue-router";
import Defaults from "Vue-router";

export const Vue = AllVueRouter as unknown as Defaults;
export default Vue;

configure RequireJS like this:

paths: {
  Vue: "node_modules/vue/dist/vue.min",
  vue: "vue-loader",
  "Vue-router": "node_modules/vue-router/dist/vue-router.min",
  "vue-router": "vue-router-loader"
}

Explanation

The issue is caused by how TypeScript generates the JavaScript for an amd module with default exports as well as how RequireJS loads those default exports.

When you do:

import Vue from "vue";

Vue.extend(...);

TypeScript translates it into:

define(["require", "exports", "vue"], function (require, exports, vue_1) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    vue_1.default.extend(..);
});

Note that .default part? the issue is that RequireJS, sets vue_1 to be the actual default, so vue_1.default ends up being undefined (hence your original error)

When you use the loaders above, you are re-exporting the * which is actually the default export as both the non-default export AND the default export. This is the code that is generated for the vue-loader.ts:

define(["require", "exports", "Vue"], function (require, exports, AllVue) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.Vue = AllVue; // <--- this resolves your problem
    exports.default = exports.Vue;
});

in order to use our loaders, we're "re-routing" the resolution. So if real code tries to require vue it will go to our loader, which will load Vue, which is the real Vue code.

Recommendation

I guess the best way to avoid this issue is by not using RequireJS - if you don't need IE support, you can just do native module loading or use Webpack

Another possible solution

Look here: Missing default export when using SystemJS or RequireJS makes Vue unusable with TypeScript

2 Comments

This is absolutely fantastic! You helped a lot with TypeScript skills that go far beyond my level. We do need IE 11 (at least) and did prototype Webpack, but decided against using it because the value-add was not high enough for the existing codebase, which uses RequireJS.
A small addition: this solution worked perfectly on Mac and Windows. On Linux TypeScript compiler paths have to be added otherwise tsc can't resolve imports from "Vue" because of file system case sensitivity. To fix: tsconfig.json: ``` "compilerOptions": { // Base directory to resolve non-relative module names. This must be specified if paths is. "baseUrl": "node_modules", "paths": { // Mappings are relative to baseUrl "Vue": ["vue"], "Vue-router": ["vue-router"] } } ```

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.