solid-start: How to import browser-only depencency

I'm trying to include a browser-only dependency into a solid-start project that is partly server-side rendered. I.e. my vite.config:
export default defineConfig({
plugins: [solid({ adapter: awsAdapter() }), suidPlugin(), eslint()],
export default defineConfig({
plugins: [solid({ adapter: awsAdapter() }), suidPlugin(), eslint()],
Now after doing the usual npm install rudder-sdk-js , when I do npm run dev, I get:
/../solidjs-site/node_modules/rudder-sdk-js/index.es.js:1
function e(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return ...

SyntaxError: Unexpected token 'export'
at internalCompileFunction (node:internal/vm:73:18)
at wrapSafe (node:internal/modules/cjs/loader:1149:20)
at Module._compile (node:internal/modules/cjs/loader:1190:27)
at Module._extensions..js (node:internal/modules/cjs/loader:1280:10)
at Module.load (node:internal/modules/cjs/loader:1089:32)
at Module._load (node:internal/modules/cjs/loader:930:12)
at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:169:29)
at ModuleJob.run (node:internal/modules/esm/module_job:194:25)
/../solidjs-site/node_modules/rudder-sdk-js/index.es.js:1
function e(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return ...

SyntaxError: Unexpected token 'export'
at internalCompileFunction (node:internal/vm:73:18)
at wrapSafe (node:internal/modules/cjs/loader:1149:20)
at Module._compile (node:internal/modules/cjs/loader:1190:27)
at Module._extensions..js (node:internal/modules/cjs/loader:1280:10)
at Module.load (node:internal/modules/cjs/loader:1089:32)
at Module._load (node:internal/modules/cjs/loader:930:12)
at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:169:29)
at ModuleJob.run (node:internal/modules/esm/module_job:194:25)
35 Replies
Bersaelor
Bersaelor11mo ago
This happens when running dev, I haven't even gotten to prod yet
Tommypop
Tommypop11mo ago
You should just be able to use a dynamic import, so the dependency doesn't get included in the server bundle
Bersaelor
Bersaelor11mo ago
i.e. not just plopping the import into a Tracking.ts file, like:
import * as rudderanalytics from 'rudder-sdk-js';
import { createSignal } from 'solid-js';

// ...
import * as rudderanalytics from 'rudder-sdk-js';
import { createSignal } from 'solid-js';

// ...
Tommypop
Tommypop11mo ago
yeah
Bersaelor
Bersaelor11mo ago
so vite https://vitejs.dev/guide/features.html#dynamic-import says
[tracking.ts]
const module = await import(`./dir/${file}.js`)
[tracking.ts]
const module = await import(`./dir/${file}.js`)
I guess that is again not it, so what you are saying what I should do is:
[TrackingContext.tsx]

const TrackingContextProvider: ParentComponent = (props) => {

const rudderanalytics = await import('rudder-sdk-js');
const handleTrackingCall = () => {
rudderanalytics.track()
}

return (
<TrackingContext.Provider value={{
handleTrackingCall
}}>
{props.children}
</TrackingContext.Provider>
)
}
[TrackingContext.tsx]

const TrackingContextProvider: ParentComponent = (props) => {

const rudderanalytics = await import('rudder-sdk-js');
const handleTrackingCall = () => {
rudderanalytics.track()
}

return (
<TrackingContext.Provider value={{
handleTrackingCall
}}>
{props.children}
</TrackingContext.Provider>
)
}
is that it? I didn't even know It's allows to import inside a jsx component, never imported anything beyond the beginning of a js file before
Tommypop
Tommypop11mo ago
You're not able to await within a component as they're not async But you can import it in onMount, and set a variable to it
let analytics = null;
onMount(async () => {
analytics = await import("./file.js")
})
const handleTrackingCall = () => {
if(!analytics) return;
analytics.track()
}
let analytics = null;
onMount(async () => {
analytics = await import("./file.js")
})
const handleTrackingCall = () => {
if(!analytics) return;
analytics.track()
}
Something like this could work Or you can make handleTrackingCall async, and dynamically import the module if it doesn't exist
let analytics = null;
const handleTrackingCall = () => {
if(!analytics){
analytics = await import("./file.js")
};
analytics.track()
}
let analytics = null;
const handleTrackingCall = () => {
if(!analytics){
analytics = await import("./file.js")
};
analytics.track()
}
The latter has the added benefit of making it lazy: the analytics are only imported when they are actually used
Bersaelor
Bersaelor11mo ago
i see do you also know a typeascript trick to set the type correct for TS?:
let analytics: (typeof import("./file.js")) = null;
const handleTrackingCall = () => {
if(!analytics){
analytics = await import("./file.js")
};
analytics.track()
}
let analytics: (typeof import("./file.js")) = null;
const handleTrackingCall = () => {
if(!analytics){
analytics = await import("./file.js")
};
analytics.track()
}
?
Tommypop
Tommypop11mo ago
I think it'd need to be Awaited<(typeof import("./file.js"))> As the import itself returns a promise
Bersaelor
Bersaelor11mo ago
type RudderStackType = typeof import('rudder-sdk-js');

let rudderanalytics: RudderStackType | undefined = undefined;

const initTracking = async () => {
if (!rudderanalytics)
rudderanalytics = await import('rudder-sdk-js');
type RudderStackType = typeof import('rudder-sdk-js');

let rudderanalytics: RudderStackType | undefined = undefined;

const initTracking = async () => {
if (!rudderanalytics)
rudderanalytics = await import('rudder-sdk-js');
seems to work
Tommypop
Tommypop11mo ago
Oh perfect, I guess Awaited<T> isn't needed
Bersaelor
Bersaelor11mo ago
mhmm now the site loads but it shows strange errors
Tommypop
Tommypop11mo ago
Can you see where exactly it fails? Like the line numbers and what's actually on that line
Bersaelor
Bersaelor11mo ago
tryuing to but since it's all one line its hard
value:function(){window.rudderanalytics.errorReporting=this}}
value:function(){window.rudderanalytics.errorReporting=this}}
thats the line I think i'll try loading it via cdn, and only use
type RudderStackType = typeof import('rudder-sdk-js');
type RudderStackType = typeof import('rudder-sdk-js');
in the code I mean, if the dependency uses a lot of this stuff ( window.rudderanalytics.errorReporting=this ), maybe it'll still fail even if I do the dynamic import
Tommypop
Tommypop11mo ago
It shouldn't, unless something depends on rudderanalytics which is imported normally
Bersaelor
Bersaelor11mo ago
mhmm do you have an idea how to fix the error in the line https://discord.com/channels/722131463138705510/1127918107508342894/1127932019922436259 then? my code so far is:
let rudderanalytics: RudderStackType;

onMount(async () => {
await initTracking();
});

const initTracking = async () => {
if (!rudderanalytics) {
rudderanalytics = await import('rudder-sdk-js');
}

rudderanalytics.load(rudderstackKey, dataPlaneURL);

rudderanalytics?.ready(() => {
console.log('rudderstack initialized');
let rudderanalytics: RudderStackType;

onMount(async () => {
await initTracking();
});

const initTracking = async () => {
if (!rudderanalytics) {
rudderanalytics = await import('rudder-sdk-js');
}

rudderanalytics.load(rudderstackKey, dataPlaneURL);

rudderanalytics?.ready(() => {
console.log('rudderstack initialized');
right after that, it'll encounter the error, trying to do window.rudderanalytics.errorReporting=this when I log console.log('window: ' + window.rudderanalytics); after the last line in the above code, it's undefined
Tommypop
Tommypop11mo ago
Maybe try binding it to the window manually? After importing, just do window.rudderanalytics = rudderanalytics
Bersaelor
Bersaelor11mo ago
yes, already on it, unfortunately it seems to crash my chrome now, or at least make it unusefully slow their documentation says they recommend importing the code via cdn, and only offer the npm module as a fallback. Maybe it's just not very well done
Tommypop
Tommypop11mo ago
Yeah, possibly I'm honestly not really sure what the root cause is Have you tried it through a CDN yet?
Bersaelor
Bersaelor11mo ago
doing that atm, example:
const loadScript = `
console.log('loading rudderstack script');
rudderanalytics = window.rudderanalytics = [];
...
rudderanalytics.load('***', '***');
rudderanalytics.ready(() => {
console.log('rudderstack initialized in script');
});
rudderanalytics.page();
`

return <>
<script type="text/javascript">{loadScript}</script>
<script src="https://cdn.rudderlabs.com/v1.1/rudder-analytics.min.js" />
<TrackingContext.Provider value={{
trackStartPlayback,
trackPlaybackProgress,
trackFinishPlayback,
}}>
{props.children}
</TrackingContext.Provider>
</>
const loadScript = `
console.log('loading rudderstack script');
rudderanalytics = window.rudderanalytics = [];
...
rudderanalytics.load('***', '***');
rudderanalytics.ready(() => {
console.log('rudderstack initialized in script');
});
rudderanalytics.page();
`

return <>
<script type="text/javascript">{loadScript}</script>
<script src="https://cdn.rudderlabs.com/v1.1/rudder-analytics.min.js" />
<TrackingContext.Provider value={{
trackStartPlayback,
trackPlaybackProgress,
trackFinishPlayback,
}}>
{props.children}
</TrackingContext.Provider>
</>
thats just the script they say you're supposed to copy+paste to your own page but honestly, i'm not even seeing the console.log('loading rudderstack script'). Is adding <script> to components supported by solid-start?
Tommypop
Tommypop11mo ago
Maybe not within the components themselves It's definitely fine within root.tsx I use it in one of my projects for google gsi
Bersaelor
Bersaelor11mo ago
mhmm but root.tsx is not necessarily meant to have that tracking everywhere i also have a different project where I have scripts only in the subfolder [id].tsx and there stuff works
Tommypop
Tommypop11mo ago
right So it'll be something about having it here
Tommypop
Tommypop11mo ago
Rudderstack
How to event stream data from your Svelte app to Google Analytics u...
This article will walk you through the process of integrating your Svelte app with RudderStack using our JavaScript SDK
Tommypop
Tommypop11mo ago
A lot of the same practices will probably apply to solid It looks like they're placing it in the root So ig that doesn't work for you
Bersaelor
Bersaelor11mo ago
mhmm, yeah, so I could at least isolate now that doing:
onMount(async () => {
console.log('Dynamically loading rudderanalytics');
rudderanalytics = await import('rudder-sdk-js');
window.rudderanalytics = rudderanalytics;
rudderanalytics.load('2OKDodEtHfKf2wBMzK82b0OP2i8', 'https://thefikaenowsn.dataplane.rudderstack.com');
});
}
onMount(async () => {
console.log('Dynamically loading rudderanalytics');
rudderanalytics = await import('rudder-sdk-js');
window.rudderanalytics = rudderanalytics;
rudderanalytics.load('2OKDodEtHfKf2wBMzK82b0OP2i8', 'https://thefikaenowsn.dataplane.rudderstack.com');
});
}
brings the whole browser down (I'm assuming some kind of infinite loop). And without the window.rudderanalytics = rudderanalytics it'll fail with the above discussed line sanity check, shouldn't:
<script>console.log('hello world')</script>
<script>console.log('hello world')</script>
at least print a hello world? it does when I stick it in root.tsx
Bersaelor
Bersaelor11mo ago
when I stick the same script into (app.tsx) it also prints hello (app), but the one script I put into (app)/(csr)/index.tsx does not print in the browser
Bersaelor
Bersaelor11mo ago
which is probably due to the <ClientOnly> component sitting below it
Tommypop
Tommypop11mo ago
Probably Maybe just try adding manually with the normal DOM api
Bersaelor
Bersaelor11mo ago
I think I can just move <TrackingContextProvider> outside of <ClientOnly>
Tommypop
Tommypop11mo ago
That's probably better
Bersaelor
Bersaelor11mo ago
yup, that seems to fix the CDN
Tommypop
Tommypop11mo ago
Awesome :)
Bersaelor
Bersaelor11mo ago
so, when I first tried it, I did a quick test with CDN, which didn't work. Then I tried for hours with the dynamic imports, which doesn't work with the rudderstack code the way it's built. turns out the cdn didn't work because of our SSR/CSR split thank you @tommypop !
Tommypop
Tommypop11mo ago
You're welcome :)