sveltejs / svelte

Cybernetically enhanced web apps
https://svelte.dev
MIT License
78.57k stars 4.12k forks source link

Looking for a cleaner way #3951

Closed viper6277 closed 4 years ago

viper6277 commented 4 years ago

Hi Everyone, I put this example together :

https://svelte.dev/repl/1418c437b1b04e469dac9c5737baae85?version=3.15.0

It works ... but I feel like there has to be a cleaner way of loading multiple remote js modules.

Just curious how else people are solving these issues, I do understand I could simply install the package via NPM...and then do a standard import... to which I have no real objection to that idea, except maybe keeping the bundle file small. Any comments or thoughts are much appreciated.

aphitiel commented 4 years ago

I think you could use dynamic imports. What if you used normal imports in your component and in your app loaded that component dynamically? Depending on your bundler it should split the code intelligently.

From memory:

<script>
  let Comp;
  import('Comp.svelte').then(module => (Comp = module.default))
</script>

{#if Comp}
  <svelte:component this={Comp}/>
{:else}
  Loading...
{/if}
viper6277 commented 4 years ago

I think you could use dynamic imports. What if you used normal imports in your component and in your app loaded that component dynamically? Depending on your bundler it should split the code intelligently.

From memory:

<script>
  let Comp;
  import('Comp.svelte').then(module => (Comp = module.default))
</script>

{#if Comp}
  <svelte:component this={Comp}/>
{:else}
  Loading...
{/if}

Hi aphitiel, thanks for the response.... I was specifically looking for a cleaner way to load multiple CDN based JS modules...I'll give this method a try, but I'm not sure it will work with remote JS files.

vipero07 commented 4 years ago

Why not just put them in the head like the CSS?

<script>
  import { onMount } from "svelte";

  let calEvents = [
    {
      title: "All Day Event",
      start: "2019-11-01",
    },
    {
      title: "Long Event",
      start: "2019-11-07",
      end: "2019-11-10",
    },
    {
      groupId: "999",
      title: "Repeating Event",
      start: "2019-11-09T16:00:00",
    },
    {
      groupId: "999",
      title: "Repeating Event",
      start: "2019-11-16T16:00:00",
    },
    {
      title: "Conference",
      start: "2019-11-11",
      end: "2019-11-13",
    },
    {
      title: "Meeting",
      start: "2019-11-12T10:30:00",
      end: "2019-11-12T12:30:00",
    },
    {
      title: "Lunch",
      start: "2019-11-12T12:00:00",
    },
    {
      title: "Meeting",
      start: "2019-11-12T14:30:00",
    },
    {
      title: "Birthday Party",
      start: "2019-11-13T07:00:00",
    },
    {
      title: "Click for Google",
      url: "http://google.com/",
      start: "2019-11-28",
    },
  ];

  let calendarElement;

  onMount(() => {
    let calendar = new FullCalendar.Calendar(calendarElement, {
      plugins: ["interaction", "dayGrid", "timeGrid"],
      defaultView: "dayGridMonth",
      selectable: true,
      defaultDate: "2019-11-07",
      header: {
        left: "prev,next today",
        center: "title",
        right: "dayGridMonth,timeGridWeek,timeGridDay",
      },
      events: calEvents,
    });
    calendar.render();
  });
</script>

<style>
  #calendar {
    max-width: 900px;
    margin: 40px auto;
  }
</style>

<svelte:head>
  <link
    rel="stylesheet"
    href="https://unpkg.com/@fullcalendar/core@4.3.1/main.min.css" />
  <link
    rel="stylesheet"
    href="https://unpkg.com/@fullcalendar/daygrid@4.3.0/main.min.css" />
  <link
    rel="stylesheet"
    href="https://unpkg.com/@fullcalendar/timegrid@4.3.0/main.min.css" />
  <script src="https://unpkg.com/@fullcalendar/core@4.3.1/main.min.js">

  </script>
  <script src="https://unpkg.com/@fullcalendar/interaction@4.3.0/main.min.js">

  </script>
  <script src="https://unpkg.com/@fullcalendar/daygrid@4.3.0/main.min.js">

  </script>
  <script src="https://unpkg.com/@fullcalendar/timegrid@4.3.0/main.min.js">

  </script>
</svelte:head>

<h3>Eddie Florea - viper6277@gmail.com</h3>
<div id="calendar" bind:this={calendarElement} />

Really though you'd be better off importing the package through NPM since it will tree shake the unnecessary things, and the total bundle is split by route anyhow.

viper6277 commented 4 years ago

hi vipero07.... There was a scenario about a week ago or so, where I attempted your suggested solution, with a different 3rd party module, and I think the reason I decided to use ....script.onload = function() {} was because the 3rd party module was large and I believed it had not finished loading prior to the onMount() call...so using script.onload was just a way to ensure the script was retrieved and loaded.

I'll refactor this code and try again... thanks for the suggestion... I guess one question I would have would be.... in the execution... does svelte ensure that all scripts or css references are loaded prior to executing the onMount() ... ??? If the answer is yes...then I did something wrong.

vipero07 commented 4 years ago

All svelte:head does (to my knowledge) is place whatever is in those tags inside the head tag on the page. So as long as the script tags don't have defer or async attributes they will be render blocking resources meaning the rest of the page won't even be drawn until they are read.

Really though your best solution is to install the NPM package and import it in the script. In this way it isn't render blocking on an initial load (putting the scripts, and the CSS in the head like that will hinder the initial performance of the page).

I understand your concern is with keeping the package size down, but I think you have more potential for unforeseen problems doing it any other way. Consider this, Svelte:head may or may not dedupe already existing in the head being read twice by the browser. Alternatively using the append onmount onload stuff, that definitely doesn't dedupe so you are guaranteed multiples. Add to all of that merely unmounting and remounting that element, say on a page change, could also add more script tags.

viper6277 commented 4 years ago

All svelte:head does (to my knowledge) is place whatever is in those tags inside the head tag on the page. So as long as the script tags don't have defer or async attributes they will be render blocking resources meaning the rest of the page won't even be drawn until they are read.

Really though your best solution is to install the NPM package and import it in the script. In this way it isn't render blocking on an initial load (putting the scripts, and the CSS in the head like that will hinder the initial performance of the page).

I understand your concern is with keeping the package size down, but I think you have more potential for unforeseen problems doing it any other way. Consider this, Svelte:head may or may not dedupe already existing in the head being read twice by the browser. Alternatively using the append onmount onload stuff, that definitely doesn't dedupe so you are guaranteed multiples. Add to all of that merely unmounting and remounting that element, say on a page change, could also add more script tags.

Thanks for the feedback vipero07... I'm going to run some tests on using the head section again... but I do agree on the use of an NPM package...there are a handful of 3rd party packages that I've come to use that are smaller ...20k and less...and those I would have no issue doing a straight import... but there also have been some packages like Plotly...which is 3.3 megs. Don't want to import that.

So far my time with Svelte has been a real pleasure, loving it's easy of use, quick to work and very easy learning curve... I have some experience with React, but I come from a Python / C++ background. I've been advocating for svelte to everyone I meet.

Thanks again.

Eddie.

viper6277 commented 4 years ago

All svelte:head does (to my knowledge) is place whatever is in those tags inside the head tag on the page. So as long as the script tags don't have defer or async attributes they will be render blocking resources meaning the rest of the page won't even be drawn until they are read.

Really though your best solution is to install the NPM package and import it in the script. In this way it isn't render blocking on an initial load (putting the scripts, and the CSS in the head like that will hinder the initial performance of the page).

I understand your concern is with keeping the package size down, but I think you have more potential for unforeseen problems doing it any other way. Consider this, Svelte:head may or may not dedupe already existing in the head being read twice by the browser. Alternatively using the append onmount onload stuff, that definitely doesn't dedupe so you are guaranteed multiples. Add to all of that merely unmounting and remounting that element, say on a page change, could also add more script tags.

So I went back to my Plotly test...to see if adding let Plot = new Plotly.... would help initialize properly...and no dice...

Here is a link to the REPL https://svelte.dev/repl/fd99eae453e84027ba244eb72cf4667e?version=3.15.0

Immediately you get a "Plotly is not defined" error...but if you add a space somewhere or move some line, the chart renders... this is clearly telling me the plotly.js file has not finished loading yet... now I added a header h3 tag to see what happening with the onMount() and the result looks like the error window is blurring the tag almost like a modal overlay... but onMount() seems to be called even so the Plotly module is not done downloading and/or loading. ???

vipero07 commented 4 years ago

So I went back to my Plotly test...to see if adding let Plot = new Plotly.... would help initialize properly...and no dice...

Here is a link to the REPL https://svelte.dev/repl/fd99eae453e84027ba244eb72cf4667e?version=3.15.0

Immediately you get a "Plotly is not defined" error...but if you add a space somewhere or move some line, the chart renders... this is clearly telling me the plotly.js file has not finished loading yet... now I added a header h3 tag to see what happening with the onMount() and the result looks like the error window is blurring the tag almost like a modal overlay... but onMount() seems to be called even so the Plotly module is not done downloading and/or loading. ???

Interesting, my best guess for why this occurs has to do with Plotly's bundler making a UMD, and that is handling some further script loading asynchronously. Theoretically if the treeshaking is working correctly you should be able to import the plotly install from npm and use it while it not taking up all 3.3 megs (this really depends on how the plotly team bundled their npm package). So if you haven't tried doing that, I'd say give it a shot and just revert the changes if it turns out terrible.

A solution that may work for you if that turns out poorly... albeit hacky:

<svelte:head>
  <script src="https://cdn.plot.ly/plotly-latest.min.js" type="text/javascript" on:load={plotlyLoaded}></script>
</svelte:head>

<script>
  import { onMount } from 'svelte';
    let plotlyValid = false;
    let mounted = false;
    let headerText;
    let plotDiv;

  let plotHeader = '';

  const data = [{
    x: ['giraffes', 'orangutans', 'monkeys'],
    y: [20, 14, 23],
    type: 'bar'
  }];

    const loadPlots = () => {
        let Plot = new Plotly.newPlot(plotDiv, data, {}, {showSendToCloud:true}); 
    }

    const plotlyLoaded = () => {
        plotlyValid = true;
        if(mounted){
            loadPlots();
        }
    }

  onMount(() => {
        headerText = 'On Mount Called !';
        mounted = true;
        if(plotlyValid){
            loadPlots();
        }
  });

</script>

<h3>{headerText}</h3>
<div id="plotly">
  <div>
    <h1>{plotHeader}</h1>
  </div>
  <div id="plotDiv" bind:this={plotDiv}><!-- Plotly chart will be drawn inside this DIV --></div>
</div>

Alternatively you could try and use <script context="module>... append script to head here using JS</script> To ensure a single load of plotly (following the above pattern). about context module

You may also wish to look into the plotly partial bundles to help a bit with their total size

antony commented 4 years ago

This is the exact reason I wrote: https://www.npmjs.com/package/@beyonk/async-script-loader

tomByrer commented 4 years ago

@viper6277 With jsDelivr CDN, you can combine many requests into 1, as long as they are the same type (eg JS with JS, CSS with CSS) I prefer jsDelivr over unpkg for this & more reasons. eg https://cdn.jsdelivr.net/combine/npm/@fullcalendar/core@4.4/main.min.js,npm/@fullcalendar/interaction@4.4/main.min.js,npm/@fullcalendar/daygrid@4.4/main.min.js,npm/@fullcalendar/timegrid@4.4/main.min.js

quinnhornblow commented 3 years ago

does anyone have a working example of how to import plotly npm module into a svelte component? I've had success with other npm modules but I just can't figure out plotly. It would be great to avoid the svelte:head / cdn / onMount method because it's it kinda slow with a big library like plotly

AlexanderZvyagin commented 3 years ago

Check this page Search for // vi src/Plotly.svelte (there are two blocks of code, take a big one). It does work for me. The solution is buggy and slow, but it works somehow.

Judging from the quality of Plotly library in general and the young age of Svelte, it may take years before a production quality solution will be available.

I am going to check also froala, d3 and maybe something else ...

BlauesMonster commented 2 years ago
<script lang="ts">
    import { onMount } from 'svelte';

    onMount(async () => {
        //npm install --save-dev @types/plotly.js-dist-min
        //npm i plotly.js-dist-min
        const { newPlot } = await import('plotly.js-dist-min');

        const trace1: Plotly.Data = {
            x: [1, 2, 3, 4],
            y: [10, 15, 13, 17],
            type: 'scatter'
        };

        const trace2: Plotly.Data = {
            x: [1, 2, 3, 4],
            y: [16, 5, 11, 9],
            type: 'scatter'
        };

        const data: Plotly.Data[] = [trace1, trace2];

        newPlot('myDiv', data);
    });
</script>

<div id="myDiv" />

This works for me when using plotlyjs in combination with svelte. It still uses the onMount-method, but at least it uses the npm package instead of the head-cdn-approach. These are the packages, I have installed (both v2.12.1):

//npm install --save-dev @types/plotly.js-dist-min
//npm i plotly.js-dist-min