S
SolidJS2mo 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
peerreynders2mo 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/solidjs@1.8.17.js 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/solidjs@1.8.17.js 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="/solidjs@1.8.17.js">
</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="/solidjs@1.8.17.js">
</head>
<!-- … -->
<html>
Paleo
Paleo2mo ago
Thank you! I'm going to try that. So, each application will generate a distinct dist/solidjs@1.8.17.js 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
peerreynders2mo 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 solidjs@x.xx.x 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/solidjs@1.8.17.js 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/solidjs@1.8.17.js 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 '../solidjs@1.8.17.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 '../solidjs@1.8.17.js';
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 '../solidjs@1.8.17.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 '../solidjs@1.8.17.js';
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/solid-js@1.8.17">
<link rel="modulepreload" crossorigin href="https://esm.sh/solid-js@1.8.17/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/solid-js@1.8.17">
<link rel="modulepreload" crossorigin href="https://esm.sh/solid-js@1.8.17/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\/solid-js@1.8.17"/' -e 's/from"solid-js\/web"/from"https:\/\/esm.sh\/solid-js@1.8.17\/web"/' dist/assets/index-CvzHezLc.js
sed -i -e 's/from"solid-js"/from"https:\/\/esm.sh\/solid-js@1.8.17"/' -e 's/from"solid-js\/web"/from"https:\/\/esm.sh\/solid-js@1.8.17\/web"/' dist/assets/index-CvzHezLc.js
mdynnl
mdynnl2mo ago
it's probably better to combine alias + external
{
external: v => /https?:/.test(v),
plugins: [
alias({
'solid-js': 'https://esm.sh/solid-js@1.8.17',
}),
// or
(id) => {
if (id.split('/')[0] === 'solid-js') {
return 'https://esm.sh/solid-js@1.8.17'
}
}
]
}
{
external: v => /https?:/.test(v),
plugins: [
alias({
'solid-js': 'https://esm.sh/solid-js@1.8.17',
}),
// or
(id) => {
if (id.split('/')[0] === 'solid-js') {
return 'https://esm.sh/solid-js@1.8.17'
}
}
]
}
module federation also comes to mind if you're willing to go to that effort
peerreynders
peerreynders2mo 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/solid-js@1.8.17',
'solid-js/web': 'https://esm.sh/solid-js@1.8.17/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/solid-js@1.8.17',
'solid-js/web': 'https://esm.sh/solid-js@1.8.17/web',
},
},
},
},
});
mdynnl
mdynnl2mo ago
the alias does work. just gave it the wrong options the correct option is
alias({
entries: {
'solid-js': 'https://esm.sh/solid-js@1.8.17',
}
})
alias({
entries: {
'solid-js': 'https://esm.sh/solid-js@1.8.17',
}
})
the latter should have been
{
resolveId: (id) => {
if (id.split('/')[0] === 'solid-js') {
return 'https://esm.sh/solid-js@1.8.17'
}
}
}
{
resolveId: (id) => {
if (id.split('/')[0] === 'solid-js') {
return 'https://esm.sh/solid-js@1.8.17'
}
}
}
it's basically handwritten alias plugin output.paths seems to be the one we need here though
Paleo
Paleo2mo 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
mdynnl2mo 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
Paleo2mo ago
But then I'm not sure what is the easiest strategy? Should I use the resolveId property somewhere?
mdynnl
mdynnl5w ago
this