0

In sveltekit I have my common js files that run on both client and server. This will reuse the same code on both the server and client side.

This uses the moment.js module.

(This is just an example where both CDN and npm exist. I actually use date-fns.)

Anyway, I want to run it as a CDN in the client environment and bundle and operate the node package on the server.

So my common js file originally looked like this:

import moment from 'moment';

export const mylib = () => {
  return moment().format();
};

The sveltekit page also uses moment.js:

<script>    
    import moment from 'moment';
</script>
<div>
    {moment().format()}
</div>

This works fine. Just, my bundle includes moment.js.

So I tried several things. And I succeeded in the following way. But it's incredibly dirty.

First, I imported the CDN in app.html and created an onload event:

<script async defer src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js" onload="momentLoad()"></script>
<script>
    window.momentWaits = [];
    const momentLoad = () => {
        for(const momentWait of window.momentWaits){
            momentWait();
        }
        delete window.momentWaits;
    };
</script>

And change moment.js in package.json from devDependencies to runtime dependencies.

{
    "dependencies": {
        "moment": "^2.29.4"
    }
}

And set build.rollupOptions.external in vite.config.js.

export default defineConfig({
    build: {
        rollupOptions: {
            external: ['moment']
        }
    },
});

And my common js file became like this:

let moment = null;
if(typeof window == 'undefined') {
  (async () => import('moment').then((res) => moment = res.default))();
}else {
  // eslint-disable-next-line no-undef
  const w = window;

  if(w.moment){
    moment = w.moment;
  }else{
    w.momentWaits.push(() => {
      moment = w.moment;
    });
  }
}

export const mylib = () => {
  if(!moment){
    console.log('not yet.');
    return;
  }
  return moment().format();
};

As above, if typeof window == 'undefined' is used when checking the browser environment, an error occurs saying that await was used at the top level when building vite, so it was set as a function. Changing browser to $app/environment solved it:

import { browser } from '$app/environment';
let moment;
if(!browser) {
  moment = (await import('moment')).default;
}else {
  // ...
}

However, my js file can be executed in an environment other than svelte, so I do not do it.

Finally, the svelte page changes:

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

    let moment = null;
    onMount(async () => {

        if(window.moment){
            moment = window.moment;
        }else{
            window.momentWaits.push(() => {
                moment = window.moment;
            });
        }

    });
</script>
<div>
    {#if moment}
        {moment().format()}
    {:else}
        wait...
    {/if}
</div>

As you can see, it's very dirty. But it works. I'm sure there is a much easier way than this. I want to find it.

In particular, if there is an option to ignore some errors during the build process and keep building, it seems to save a lot of code.

I also suspect that the reason there is no moment in the onMount event yet is because of the script tag's async and defer attribute. Is there a better event than onMount?

enoeht
  • 241
  • 2
  • 9

0 Answers0