Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to load Vue 3 #2272

Closed
1 of 10 tasks
joeldenning opened this issue Nov 2, 2020 · 42 comments
Closed
1 of 10 tasks

How to load Vue 3 #2272

joeldenning opened this issue Nov 2, 2020 · 42 comments

Comments

@joeldenning
Copy link
Collaborator

joeldenning commented Nov 2, 2020

  • SystemJS Version: 6.7.1
  • Which library are you using?
    • system.js
    • s.js
    • system-node.cjs
  • Which extras are you using?
    • AMD extra
    • Named Exports
    • Named Register
    • Transform
    • Use Default
    • Global
    • Dynamic Import Maps
  • Are you using any custom hooks?
    Yes - the ESM extra in ESM Extra #2187

Question

Vue 3 no longer publishes a UMD build. Instead, it publishes CJS, global, and ESM builds. Unfortunately, the global build creates multiple global variables - one for Vue and one for its devtools - and SystemJS selects the wrong global variable when determining which one to use as the module. See line 7826 of https://unpkg.com/browse/vue@3.0.2/dist/vue.runtime.global.js for where the devtools global variable is created.

The only way I'm able to load Vue 3 now is to load the ESM build of Vue, and then use a modified version of SystemJS and the esm custom extra shown in #2187. This code sandbox shows that that extra works.

I think being able to load Vue 3 is pretty important - do you have thoughts on best way to do so without having to use a modified version of SystemJS?

@privatenumber
Copy link

I asked for UMD to be supported, but it was closed with a misunderstanding of SystemJS supporting ESM.

I think this could potentially be easily solved by re-requesting it and supporting it with further use-cases.

@joeldenning
Copy link
Collaborator Author

Another workaround perhaps is to create the other global variables manually before loading Vue 3 - that way systemjs can tell which global variable to use:

https://codesandbox.io/s/gallant-snow-6jil4?file=/index.html

@guybedford
Copy link
Member

guybedford commented Nov 2, 2020 via email

@joeldenning
Copy link
Collaborator Author

Perhaps we could implement some default sorting rules to the global
selector, like that the shortest name wins or names starting with ‘_‘ get
used last?

Sounds like a bit of a slippery slope, but I'm open to it. I think deprioritizing variables that start with _ would be a decent assumption.

@joeldenning
Copy link
Collaborator Author

joeldenning commented Nov 3, 2020

I just debugged this further, and have identified that this is caused by variable declaration/hoisting. The following simple change makes systemjs correctly load Vue:

// This doesn't work, since Vue is declared / hoisted
// before the iife is executed.
var Vue = (function() {...})()

// This DOES work, since Vue is not declared/hoisted
// before the iife is executed
window.Vue = (function() {...})()

@joeldenning
Copy link
Collaborator Author

This is ultimately caused by rollup's iife output. See the following:

https://github.com/vuejs/vue-next/blob/f28ca556925147bb109d5ba77c5dafaf17d57322/rollup.config.js#L34-L38

This rollup repl is a simple demonstration of it.

@guybedford do you have thoughts on any way we could work around this? Would a PR to rollup make sense here? Not sure if we have a case for rollup's current behavior being incorrect.

@guybedford
Copy link
Member

I don't think getting RollupJS to adapt to that is a general solution to the problem, because there will be some tools that define a global early and some tools that define a global late, and multiple global definitions will always have this problem if based on definition ordering.

Agreed an ad-hoc / non-standard lexographical sorting may not be ideal either.

Perhaps we can base the choice on value type? Where functions take precedence, followed by objects, followed by arrays and then primitives? That seems like it would possibly work for Vue as well, although I'm not sure if it would scale to other cases of these problems or not.

This would need to be a major release to fix though.

@guybedford
Copy link
Member

When comparing two objects, we could take the object with the highest key count?

@guybedford
Copy link
Member

Another obvious one - give precedence to Symbol.toStringTag === 'Module'.

@joeldenning
Copy link
Collaborator Author

Out of curiosity, why does the global loading extra prefer the first created global variable over the most recently created one?

@guybedford
Copy link
Member

It was just an arbitrary logic, hence why I'm suggesting a better logic here.

@joeldenning
Copy link
Collaborator Author

I see - I wasn't sure if there were tools/conventions in the ecosystem that resulted in the first global variable often being the desireable one rather than the last global variable created.

My thought here is that it might be worth exploring whether changing to the last global variable created would break a lot of popular modules? And if so, it would need to be a breaking change to systemjs, or a new extra/configuration option.

@guybedford
Copy link
Member

Strictly speaking, any ordering change is a breaking change and a major release. So we should make sure to carefully test first but we can do such a break definitely.

As mentioned previously my concern with ordering-only changes is that as shown bundle output is not consistent here.

I would still recommend the value ordering proposal I mention above.

@joeldenning
Copy link
Collaborator Author

joeldenning commented Nov 6, 2020

As mentioned previously my concern with ordering-only changes is that as shown bundle output is not consistent here.

Agreed. Perhaps none of the implementation options would really be very solid to rely on, then. Using a heuristic to determine which global concerns me since loading behavior could change for arbitrary reasons (library added a new property to their module, now it changes whether it's considered the systemjs module).

For Vue 3 specifically, I think I may just create an esm-bundle repo that publishes a System.register version automatically as new versions of vue are published to npm

@guybedford
Copy link
Member

Can this be closed as resolved?

@joeldenning
Copy link
Collaborator Author

It's not resolved, since the only way to load Vue 3 is to either alter SystemJS or use the ESM custom extra that isn't one of the officially supported ones.

@privatenumber
Copy link

privatenumber commented Nov 20, 2020

I think this should be solved in Vue or SystemJS if possible, but for anyone blocked by this, using this on-demand UMD package should suffice for prototyping purposes:

System.import('https://repkg.now.sh/vue@3.0.2/dist/vue.esm-browser.js');

@joeldenning
Copy link
Collaborator Author

joeldenning commented Nov 20, 2020

@guybedford what would you think of a change that prioritizes global variables that have the same name as the module specifier (case-insensitive, removing - characters)? I guess the problem with that approach is that it relies on module specifiers and wouldn't work if loaded via unmapped URL. But it would work if vue is in an import map as a bare specifier.

@guybedford
Copy link
Member

@joeldenning that would be very fragile - changing the module name should not change its behaviour.

If I may be as bold as to discuss more than the specifics at hand, I do think this issue represents part of the phase shift to true modular delivery with version management that I have been working on with jspm for 7 years. Eg System.import('https://system-dev.jspm.io/npm:vue@3.0.2/dist/vue.global.js').

@joeldenning
Copy link
Collaborator Author

Is the jspm.io domain going to remain free to use? Also, is the system-dev subdomain the correct one to use in production?

@guybedford
Copy link
Member

The new CDN is at https://ga.system.jspm.io/npm:vue@3.0.2/index.js and encourages installing @vue/shared, @vue/runtime-dom and @vue/compiler-dom as separate modules. This is why jspm is a package manager and the beta supports this workflow with jspm install vue@3 setting up the SystemJS import map for these. Join the beta if you are interested by requesting an invite to the private beta channel in the Discord (https://discord.gg/dNRweUu). Now is the time the project needs feedback towards launch and I cannot do it alone.

@joeldenning
Copy link
Collaborator Author

joeldenning commented Nov 20, 2020

My concern with the jspm CDN is that it may become a paid service. With other libraries that have been difficult to load with systemjs, such as rxjs, I have created mirrors that are loadable by systemjs and automatically publish to npm every time a new version is used. For example, https://github.com/esm-bundle/rxjs is installable as an npm package and loadable via unpkg/jsdelivr. I think I will do the same for Vue 3

@guybedford
Copy link
Member

Is the jspm.io domain going to remain free to use?

It will always be free, and in fact the goal of the project is to design models for ensuring CDN selection is an open protocol-based process.

@joeldenning
Copy link
Collaborator Author

It will always be free, and in fact the goal of the project is to design models for ensuring CDN selection is an open protocol-based process.

Ah that's good to hear. Sounds like a good option to encourage people to use then. I may also create an esm-bundle repo for Vue 3 as another way. Looks like we have a few options then for loading Vue 3, between pika cdn, repkg.now.sh, jspm.io, and esm-bundle.

@guybedford
Copy link
Member

guybedford commented Nov 20, 2020

If anyone is interested, here is the new jspm 3 (unreleased) CDN import map for Vue3 (generated by jspm install vue@3):

<script src="https://ga.system.jspm.io/npm:systemjs@6.7.1/dist/s.min.js"></script>
<script type="systemjs-importmap">
{
  "imports": {
    "vue": "https://ga.system.jspm.io/npm:vue@3.0.2/dev.index.js"
  },
  "scopes": {
    "https://ga.system.jspm.io/": {
      "@babel/parser": "https://ga.system.jspm.io/npm:@babel/parser@7.12.5/lib/index.js",
      "@vue/compiler-core": "https://ga.system.jspm.io/npm:@vue/compiler-core@3.0.2/dev.index.js",
      "@vue/compiler-dom": "https://ga.system.jspm.io/npm:@vue/compiler-dom@3.0.2/dev.index.js",
      "@vue/reactivity": "https://ga.system.jspm.io/npm:@vue/reactivity@3.0.2/dev.index.js",
      "@vue/runtime-core": "https://ga.system.jspm.io/npm:@vue/runtime-core@3.0.2/dev.index.js",
      "@vue/runtime-dom": "https://ga.system.jspm.io/npm:@vue/runtime-dom@3.0.2/dev.index.js",
      "@vue/shared": "https://ga.system.jspm.io/npm:@vue/shared@3.0.2/dev.index.js",
      "estree-walker": "https://ga.system.jspm.io/npm:estree-walker@2.0.1/dist/estree-walker.umd.js",
      "source-map": "https://ga.system.jspm.io/npm:source-map@0.6.1/source-map.js"
    }
  }
}
</script>
<script>
  System.import('vue').then(m => {
    console.log(m);
  });
</script>

(remove the dev. prefixes for the production install of jspm install vue@3 --production)

And please do consider joining the beta by requesting access at https://discord.gg/dNRweUu, the project is only as strong as its contributors.

@guybedford
Copy link
Member

I'm happy to look into global reordering techniques further, but lets open a new issue for that rather.

@joeldenning
Copy link
Collaborator Author

It's now possible to load Vue 3 with SystemJS@6.8.0, via the firstGlobalProp configuration. See the following code sandbox, which shows Vue 3 properly loading.

https://codesandbox.io/s/focused-williams-4791s?file=/index.html

@joeldenning
Copy link
Collaborator Author

To clarify, firstGlobalProp needs to be set on the System prototype, not on the instance. Sharing this because it tripped up someone I was talking to:

// This works
Object.getPrototypeOf(System).firstGlobalProp = true

// This also works
System.__proto__.firstGlobalProp = true

// This DOESN'T work
System.firstGlobalProp = true

@joeldenning
Copy link
Collaborator Author

joeldenning commented Nov 29, 2020

Another thing to note - since global variables aren't capable of waiting for their dependencies to be defined, you have to ensure correct order of loading. vue needs to be loaded before vue-router:

System.import('vue').then(() => System.import('vue-router')).then(() => System.import('app-that-uses-vue'))

It might be worth looking into using the SystemJS auto-import feature here, to avoid the waterfall loading problem. Another alternative could be to load the libraries with normal script tags in the html file, then use System.set() to set the modules.

@joeldenning
Copy link
Collaborator Author

As of systemjs@6.8.1, it's now possible to set SystemJS on the instance:

// This works in 6.8.1
System.firstGlobalProp = true;

@joeldenning
Copy link
Collaborator Author

I've seen some race conditions with the firstGlobalProp approach, where sometimes it selects the wrong global variable. So here's another way to load Vue 3 that avoids those race conditions:

<script type="systemjs-importmap">
    {
      "imports": {
        "vue": "https://cdn.jsdelivr.net/npm/vue@3.0.3/dist/vue.global.min.js",
        "vue-router": "https://cdn.jsdelivr.net/npm/vue-router@4.0.0-rc.5/dist/vue-router.global.min.js"
      }
    }
</script>
<script src="https://cdn.jsdelivr.net/npm/vue@3.0.3/dist/vue.global.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-router@4.0.0-rc.5/dist/vue-router.global.min.js"></script>
<script>
    System.set(System.resolve('vue'), window.Vue)
    System.set(System.resolve('vue-router'), window.VueRouter)
</script>

@guybedford
Copy link
Member

@joeldenning interesting to hear about timing issues - is that due to other Vue libraries using globals, or only happening in specific browsers, or something else? Always worth digging into these things when we can.

@joeldenning
Copy link
Collaborator Author

The case I saw with the race condition was one where systemjs was choosing the global variables created by the react chrome extension, and it occurred more frequently when everything was loaded from browser cache.

I think the cause is that there's nothing preventing another script from creating a global variable after loading begins but before instantiation finishes.

@guybedford
Copy link
Member

Right that makes sense, some races are always possible with the global detection system.

@joeldenning
Copy link
Collaborator Author

some races are always possible with the global detection system.

I think that this is not the case when using the last global prop, though, since the script load event fires synchronously after execution? I have only seen races when using first global prop.

@cah-james-arnold01
Copy link

cah-james-arnold01 commented Sep 1, 2021

@joeldenning Does this implementation break a Vue 2 app living in the same single-spa environment? We've been unsuccessful in getting both to load using this configuration after pouring over these support threads - but I haven't seen any examples of this. We're receiving an Object(...) is not a function error in our best case with our Vue 3 app.

@sfrendpax8
Copy link

@joeldenning Does this implementation break a Vue 2 app living in the same single-spa environment? We've been unsuccessful in getting both to load using this configuration after pouring over these support threads - but I haven't seen any examples of this. We're receiving an Object(...) is not a function error in our best case with our Vue 3 app.

@cah-jamesarnold Did you find a solution for this? I implemented the above approach to load Vue3 and it works, but if I go to a separate route that loads a Vue 2 app, it breaks

@cah-james-arnold01
Copy link

cah-james-arnold01 commented Nov 29, 2021

@joeldenning Does this implementation break a Vue 2 app living in the same single-spa environment? We've been unsuccessful in getting both to load using this configuration after pouring over these support threads - but I haven't seen any examples of this. We're receiving an Object(...) is not a function error in our best case with our Vue 3 app.

@cah-jamesarnold Did you find a solution for this? I implemented the above approach to load Vue3 and it works, but if I go to a separate route that loads a Vue 2 app, it breaks

@sfrendpax8 Yeah we were able to get it working. Within the importmap, you'll go ahead and specify your default vue import to be whatever your common version is amongst single-spa. You'll then add a scopes section in addition to your imports and override the vue import for that specific scope or single-spa.

imports: { 
   "vue": "/polyfill/vue/dist/vue.min.js", 
},
scopes: {
   "specific-single-spa-that-uses-vue-3.js": {
      "vue": "/polyfill/vue-3/dist/vue.runtime.global.prod.js"
   }
}

@xiaowanfeng
Copy link

<script type="systemjs-importmap">
    {
      "imports": {
        "vue": "https://cdn.jsdelivr.net/npm/vue@3.2.27/dist/vue.global.js",
        "vue-router": "https://cdn.jsdelivr.net/npm/vue-router@4.0.12/dist/vue-router.global.js"
      } 
    }
  </script>
  <script src="https://cdn.jsdelivr.net/npm/vue@3.2.27/dist/vue.global.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-router@4.0.12/dist/vue-router.global.js"></script>
<script>
    System.set(System.resolve('vue'), window.Vue)
    System.set(System.resolve('vue-router'), window.VueRouter)
    System.import('@dawn/boot');
  </script>

I use this method to load vue 3, but I got an error like this

system.js:698 Uncaught Error: Unable to resolve bare specifier 'vue' from http://localhost:8000/ 

@joeldenning
Copy link
Collaborator Author

system.js:698 Uncaught Error: Unable to resolve bare specifier 'vue' from http://localhost:8000/

This can happen if you call System.resolve() synchronously in an inline script (like what you have in the example code) when the systemjs-importmap is after the <script> for loading systemjs. To fix it, just move the <script type="systemjs-importmap"> to be above the <script src="system.js">. The reason is that SystemJS processes import maps twice - once right when it is loaded and once on the window load event. Your code appears to be calling System.resolve() in between those two times, before systemjs has had a chance to process the import map.

If rearranging the script tags is not an option, you can also manually tell systemjs to process import maps via System.prepareImport(true):

<script>
    System.prepareImport(true).then(function () {
      System.set(System.resolve('vue'), window.Vue)
      System.set(System.resolve('vue-router'), window.VueRouter)
      System.import('@dawn/boot');
    });
  </script>

@rpersche
Copy link

I was struggling for a long time to get this to work in Safari on Mac. This finally worked for me:

 <script>
    Object.getPrototypeOf(System).firstGlobalProp = true;
    System.import("vue").then(() => {
      System.set(System.resolve('vue'), window.Vue);
      System.import("vue-router").then(() => {
        System.set(System.resolve('vue-router'), window.VueRouter);
        System.import('@myapp/root-config');
      });
    });
  </script>

@ddavid93
Copy link

If anyone is interested, here is the new jspm 3 (unreleased) CDN import map for Vue3 (generated by jspm install vue@3):

<script src="https://ga.system.jspm.io/npm:systemjs@6.7.1/dist/s.min.js"></script>
<script type="systemjs-importmap">
{
  "imports": {
    "vue": "https://ga.system.jspm.io/npm:vue@3.0.2/dev.index.js"
  },
  "scopes": {
    "https://ga.system.jspm.io/": {
      "@babel/parser": "https://ga.system.jspm.io/npm:@babel/parser@7.12.5/lib/index.js",
      "@vue/compiler-core": "https://ga.system.jspm.io/npm:@vue/compiler-core@3.0.2/dev.index.js",
      "@vue/compiler-dom": "https://ga.system.jspm.io/npm:@vue/compiler-dom@3.0.2/dev.index.js",
      "@vue/reactivity": "https://ga.system.jspm.io/npm:@vue/reactivity@3.0.2/dev.index.js",
      "@vue/runtime-core": "https://ga.system.jspm.io/npm:@vue/runtime-core@3.0.2/dev.index.js",
      "@vue/runtime-dom": "https://ga.system.jspm.io/npm:@vue/runtime-dom@3.0.2/dev.index.js",
      "@vue/shared": "https://ga.system.jspm.io/npm:@vue/shared@3.0.2/dev.index.js",
      "estree-walker": "https://ga.system.jspm.io/npm:estree-walker@2.0.1/dist/estree-walker.umd.js",
      "source-map": "https://ga.system.jspm.io/npm:source-map@0.6.1/source-map.js"
    }
  }
}
</script>
<script>
  System.import('vue').then(m => {
    console.log(m);
  });
</script>

(remove the dev. prefixes for the production install of jspm install vue@3 --production)

And please do consider joining the beta by requesting access at https://discord.gg/dNRweUu, the project is only as strong as its contributors.

This worked for me 100%

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants