Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/dedupe-solid-in-builds.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@solidjs/vite-plugin': patch
---

Production builds now dedupe `solid-js`, `@solidjs/web` and `@solidjs/signals` the same way dev does. A dependency with its own nested copy of Solid no longer adds a second runtime to the bundle, which broke context lookups (#112).
66 changes: 66 additions & 0 deletions src/dedupe.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { build, type Rolldown } from 'vite';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import solidPlugin from './index.js';

let root: string;

async function writePackage(dir: string, pkg: Record<string, unknown>, files: Record<string, string>) {
await mkdir(dir, { recursive: true });
await writeFile(path.join(dir, 'package.json'), JSON.stringify(pkg));
for (const [name, code] of Object.entries(files)) {
await writeFile(path.join(dir, name), code);
}
}

beforeAll(async () => {
root = await mkdtemp(path.join(tmpdir(), 'solid-dedupe-'));
const fakeSolid = (copy: string) => [
{ name: 'solid-js', version: '2.0.0', type: 'module', exports: { '.': './index.js' } },
{ 'index.js': `export const copy = ${JSON.stringify(copy)};\n` },
] as const;
await writePackage(path.join(root, 'node_modules/solid-js'), ...fakeSolid('root'));
// A dependency that ships its own nested copy of solid-js.
const lib = path.join(root, 'node_modules/lib');
await writePackage(
lib,
{ name: 'lib', version: '1.0.0', type: 'module', exports: { '.': './index.js' } },
{ 'index.js': "export { copy as libCopy } from 'solid-js';\n" },
);
await writePackage(path.join(lib, 'node_modules/solid-js'), ...fakeSolid('nested'));
await writeFile(path.join(root, 'package.json'), JSON.stringify({ name: 'app', type: 'module' }));
await writeFile(
path.join(root, 'entry.js'),
"import { copy } from 'solid-js';\nimport { libCopy } from 'lib';\nexport const copies = [copy, libCopy];\n",
);
});

afterAll(async () => {
await rm(root, { recursive: true, force: true });
});

describe('resolve.dedupe', () => {
// #112: dev deduped solid-js, but production builds bundled the nested copy too.
it('bundles a single solid-js copy in production builds', async () => {
const result = (await build({
root,
configFile: false,
logLevel: 'silent',
plugins: [solidPlugin()],
build: {
write: false,
minify: false,
rolldownOptions: { input: path.join(root, 'entry.js'), preserveEntrySignatures: 'strict' },
},
})) as Rolldown.RolldownOutput | Rolldown.RolldownOutput[];
const output = Array.isArray(result) ? result[0] : result;
const code = output.output
.filter((chunk): chunk is Rolldown.OutputChunk => chunk.type === 'chunk')
.map((chunk) => chunk.code)
.join('\n');
expect(code).toContain('"root"');
expect(code).not.toContain('"nested"');
});
});
14 changes: 10 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1521,10 +1521,16 @@ export default function solidPlugin(options: Partial<Options> = {}): Plugin[] {
// added to `optimizeDeps.include`: the optimizer already reaches
// signals through `solid-js`, and an include entry that doesn't
// resolve from the root logs a warning on every start.
const dedupe =
replaceDev && findPackageDir('@solidjs/signals', path.resolve(projectRoot || process.cwd()))
? [...nestedDeps, '@solidjs/signals']
: nestedDeps;
//
// Dedupe applies to builds as well as dev. A dependency that ships its
// own nested copy of `solid-js` would otherwise put a second runtime
// in the production bundle, with its own owner tree and contexts. #112
// Each package is only listed when the root can resolve it, for the
// reason given above for `@solidjs/signals`.
const dedupeRoot = path.resolve(projectRoot || process.cwd());
const dedupe = ['solid-js', '@solidjs/web', '@solidjs/signals'].filter((name) =>
findPackageDir(name, dedupeRoot),
);

const userTest = (userConfig as any).test ?? {};
const test = {} as any;
Expand Down
Loading