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
?