S
SolidJS7mo ago
Paleo

Solid as a global variable

I have several small SolidJS JavaScript applications in the same webpage. Currently, each application bundle contains an copy of the SolidJs framework. I would like to use the framework more like with jQuery: is it possible to put the SolidJS framework in a global variable, and then all the SolidJS applications rely on the same global variable?
10 Replies
peerreynders
peerreynders7mo ago
Make the following modifications to your vite.config.ts:
// file: vite.config.ts
import { defineConfig } from 'vite';
import solidPlugin from 'vite-plugin-solid';
// 1. import the solid-js package.json so we can get the version
import solidjsPkg from './node_modules/solid-js/package.json';

interface PreRenderedChunk {
exports: string[];
facadeModuleId: string | null;
isDynamicEntry: boolean;
isEntry: boolean;
isImplicitEntry: boolean;
moduleIds: string[];
name: string;
type: 'chunk';
}

// 2. Name we are using for the solid-js chunk
// The version needs to be blatantly obvious to avoid
// downstream issues
const SOLIDJS = `solidjs@${solidjsPkg.version}`;

export default defineConfig({
plugins: [solidPlugin()],
server: {
port: 3000,
},
build: {
target: 'esnext',
rollupOptions: {
output: {
// 3. This makes sure that the solid-js chunk doesn't
// include a hash in the name
chunkFileNames(chunkInfo: PreRenderedChunk) {
return chunkInfo.name === SOLIDJS ? '[name].js' : '[name]-[hash].js';
},
// 4. This diverts the `solid-js` imports to the
// solid-js chunk
manualChunks(id: string) {
if (id.includes('solid-js')) return SOLIDJS;
},
},
},
},
});
// file: vite.config.ts
import { defineConfig } from 'vite';
import solidPlugin from 'vite-plugin-solid';
// 1. import the solid-js package.json so we can get the version
import solidjsPkg from './node_modules/solid-js/package.json';

interface PreRenderedChunk {
exports: string[];
facadeModuleId: string | null;
isDynamicEntry: boolean;
isEntry: boolean;
isImplicitEntry: boolean;
moduleIds: string[];
name: string;
type: 'chunk';
}

// 2. Name we are using for the solid-js chunk
// The version needs to be blatantly obvious to avoid
// downstream issues
const SOLIDJS = `solidjs@${solidjsPkg.version}`;

export default defineConfig({
plugins: [solidPlugin()],
server: {
port: 3000,
},
build: {
target: 'esnext',
rollupOptions: {
output: {
// 3. This makes sure that the solid-js chunk doesn't
// include a hash in the name
chunkFileNames(chunkInfo: PreRenderedChunk) {
return chunkInfo.name === SOLIDJS ? '[name].js' : '[name]-[hash].js';
},
// 4. This diverts the `solid-js` imports to the
// solid-js chunk
manualChunks(id: string) {
if (id.includes('solid-js')) return SOLIDJS;
},
},
},
},
});
You also need to add resolveJsonModule to the ts.config.json for the JSON import to work.
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"types": ["vite/client"],
"noEmit": true,
"isolatedModules": true,
"resolveJsonModule": true
}
}
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"types": ["vite/client"],
"noEmit": true,
"isolatedModules": true,
"resolveJsonModule": true
}
}
The idea is to separate the Solid chunk into a separate vendor bundle while the "app bits" go into their own asset bundle. Now the page can load the Solid bundle separately to be reused by the various asset bundles. Of course all the asset bundles have to use the same version of SolidJS.
vite v5.2.12 building for production...
✓ 11 modules transformed.
dist/index.html 2.24 kB │ gzip: 0.91 kB
dist/assets/index-CfZAY7ah.js 1.80 kB │ gzip: 0.91 kB
dist/[email protected] 9.79 kB │ gzip: 4.00 kB
vite v5.2.12 building for production...
✓ 11 modules transformed.
dist/index.html 2.24 kB │ gzip: 0.91 kB
dist/assets/index-CfZAY7ah.js 1.80 kB │ gzip: 0.91 kB
dist/[email protected] 9.79 kB │ gzip: 4.00 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="shortcut icon" type="image/ico" href="/solidjs.svg" />
<title>Hello World</title>
<script type="module" crossorigin src="/assets/index-CfZAY7ah.js"></script>
<link rel="modulepreload" crossorigin href="/[email protected]">
</head>
<!-- … -->
<html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="shortcut icon" type="image/ico" href="/solidjs.svg" />
<title>Hello World</title>
<script type="module" crossorigin src="/assets/index-CfZAY7ah.js"></script>
<link rel="modulepreload" crossorigin href="/[email protected]">
</head>
<!-- … -->
<html>
Paleo
PaleoOP7mo ago
Thank you! I'm going to try that. So, each application will generate a distinct dist/[email protected] file, but all these files are equal/interchangeable, then I can serve only one? I'm definitely going to try that. Thanks a lot!
peerreynders
peerreynders7mo ago
all these files are equal/interchangeable, then I can serve only one?
Turns out, there's one wrinkle; even manualChunks bundles are subject to treeshaking. So when generating the [email protected] bundle to be shared I'd temporarily set:
rollupOptions: {
output: {
chunkFileNames(chunkInfo: PreRenderedChunk) {
return chunkInfo.name === SOLIDJS ? '[name].js' : '[name]-[hash].js';
},
manualChunks(id: string) {
if (id.includes('solid-js')) return SOLIDJS;
},
},
// 5. Turn treeshaking off when generating the shared bundle
treeshake: false,
}
rollupOptions: {
output: {
chunkFileNames(chunkInfo: PreRenderedChunk) {
return chunkInfo.name === SOLIDJS ? '[name].js' : '[name]-[hash].js';
},
manualChunks(id: string) {
if (id.includes('solid-js')) return SOLIDJS;
},
},
// 5. Turn treeshaking off when generating the shared bundle
treeshake: false,
}
11 modules transformed.
dist/index.html 2.24 kB │ gzip: 0.91 kB
dist/assets/index-C9yZU3MY.js 1.80 kB │ gzip: 0.91 kB
dist/[email protected] 13.18 kB │ gzip: 5.22 kB
11 modules transformed.
dist/index.html 2.24 kB │ gzip: 0.91 kB
dist/assets/index-C9yZU3MY.js 1.80 kB │ gzip: 0.91 kB
dist/[email protected] 13.18 kB │ gzip: 5.22 kB
From tree-shaken index-hash.js:
import {
c as v,
a as o,
b as y,
u as $,
d as b,
e as x,
t as c,
S as d,
i as O,
r as f,
} from '../[email protected]';
import {
c as v,
a as o,
b as y,
u as $,
d as b,
e as x,
t as c,
S as d,
i as O,
r as f,
} from '../[email protected]';
From NON-treeshaken index-hash.js:
import {
c as y,
a as v,
b as o,
u as _,
t as s,
d as x,
e as b,
S as f,
i as $,
r as m,
} from '../[email protected]';
import {
c as y,
a as v,
b as o,
u as _,
t as s,
d as x,
e as b,
S as f,
i as $,
r as m,
} from '../[email protected]';
Rollup seems to generate stable deterministic names (not so for the order) for the exports (and it also bothers to remap those names to internally more volatile ones) so this could work.
Plans are useless but planning is indispensible.
Dwight D. Eisenhower In that spirit here is plan Z: Get the ESM modules from a CDN:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="shortcut icon" type="image/ico" href="/solidjs.svg" />
<link rel="modulepreload" crossorigin href="https://esm.sh/[email protected]">
<link rel="modulepreload" crossorigin href="https://esm.sh/[email protected]/web">
<title>Hello World</title>
</head>
<!-- …-->
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="shortcut icon" type="image/ico" href="/solidjs.svg" />
<link rel="modulepreload" crossorigin href="https://esm.sh/[email protected]">
<link rel="modulepreload" crossorigin href="https://esm.sh/[email protected]/web">
<title>Hello World</title>
</head>
<!-- …-->
</html>
Configure Vite/Rollup to ignore solid-js
// file: vite.config.ts
import { defineConfig } from 'vite';
import solidPlugin from 'vite-plugin-solid';

export default defineConfig({
plugins: [solidPlugin()],
server: {
port: 3000,
},
build: {
target: 'esnext',
rollupOptions: {
// Exclude any solid-import
external(id: string) {
return id.includes('solid-js') ? true : false;
},
},
},
});
// file: vite.config.ts
import { defineConfig } from 'vite';
import solidPlugin from 'vite-plugin-solid';

export default defineConfig({
plugins: [solidPlugin()],
server: {
port: 3000,
},
build: {
target: 'esnext',
rollupOptions: {
// Exclude any solid-import
external(id: string) {
return id.includes('solid-js') ? true : false;
},
},
},
});
Build
vite v5.2.12 building for production...
9 modules transformed.
dist/index.html 2.34 kB │ gzip: 0.93 kB
dist/assets/index-CvzHezLc.js 1.91 kB │ gzip: 0.95 kB
built in 226ms
vite v5.2.12 building for production...
9 modules transformed.
dist/index.html 2.34 kB │ gzip: 0.93 kB
dist/assets/index-CvzHezLc.js 1.91 kB │ gzip: 0.95 kB
built in 226ms
Patch asset to use the CDN exports
sed -i -e 's/from"solid-js"/from"https:\/\/esm.sh\/[email protected]"/' -e 's/from"solid-js\/web"/from"https:\/\/esm.sh\/[email protected]\/web"/' dist/assets/index-CvzHezLc.js
sed -i -e 's/from"solid-js"/from"https:\/\/esm.sh\/[email protected]"/' -e 's/from"solid-js\/web"/from"https:\/\/esm.sh\/[email protected]\/web"/' dist/assets/index-CvzHezLc.js
mdynnl
mdynnl7mo ago
it's probably better to combine alias + external
{
external: v => /https?:/.test(v),
plugins: [
alias({
'solid-js': 'https://esm.sh/[email protected]',
}),
// or
(id) => {
if (id.split('/')[0] === 'solid-js') {
return 'https://esm.sh/[email protected]'
}
}
]
}
{
external: v => /https?:/.test(v),
plugins: [
alias({
'solid-js': 'https://esm.sh/[email protected]',
}),
// or
(id) => {
if (id.split('/')[0] === 'solid-js') {
return 'https://esm.sh/[email protected]'
}
}
]
}
module federation also comes to mind if you're willing to go to that effort
peerreynders
peerreynders7mo ago
Doesn't work (the names are aliased in the output but the non-aliased names are presented to external and the solid code still ends up in the bundle). I wrote the above because I gave up on @rollup/plugin-alias. But I did a little more digging and this does work (without patching with sed):
// file: vite.config.ts
import { defineConfig } from 'vite';
import solidPlugin from 'vite-plugin-solid';

export default defineConfig({
plugins: [solidPlugin()],
server: {
port: 3000,
},
build: {
target: 'esnext',
rollupOptions: {
external: (moduleId) => moduleId.includes('solid-js'),
output: {
paths: {
'solid-js': 'https://esm.sh/[email protected]',
'solid-js/web': 'https://esm.sh/[email protected]/web',
},
},
},
},
});
// file: vite.config.ts
import { defineConfig } from 'vite';
import solidPlugin from 'vite-plugin-solid';

export default defineConfig({
plugins: [solidPlugin()],
server: {
port: 3000,
},
build: {
target: 'esnext',
rollupOptions: {
external: (moduleId) => moduleId.includes('solid-js'),
output: {
paths: {
'solid-js': 'https://esm.sh/[email protected]',
'solid-js/web': 'https://esm.sh/[email protected]/web',
},
},
},
},
});
mdynnl
mdynnl7mo ago
the alias does work. just gave it the wrong options the correct option is
alias({
entries: {
'solid-js': 'https://esm.sh/[email protected]',
}
})
alias({
entries: {
'solid-js': 'https://esm.sh/[email protected]',
}
})
the latter should have been
{
resolveId: (id) => {
if (id.split('/')[0] === 'solid-js') {
return 'https://esm.sh/[email protected]'
}
}
}
{
resolveId: (id) => {
if (id.split('/')[0] === 'solid-js') {
return 'https://esm.sh/[email protected]'
}
}
}
it's basically handwritten alias plugin output.paths seems to be the one we need here though
Paleo
PaleoOP7mo ago
Thanks a lot @mdynnl ! Sorry for the late answer. I will implement your solution on our plugins in a few day. I don't know if this is something we should suggest here but if you want, I could write the question on StackOverflow and you copy/paste your answer on it?
mdynnl
mdynnl7mo ago
this server uses https://www.answeroverflow.com/ so support threads should already be indexed
output.paths seems to be the one we need here though
Paleo
PaleoOP7mo ago
But then I'm not sure what is the easiest strategy? Should I use the resolveId property somewhere?
mdynnl
mdynnl7mo ago
this
Want results from more Discord servers?
Add your server