Skip to content

Commit 036348a

Browse files
authored
feat: support aliases in playground (#1390)
* feat: support aliases in playground supports a new `aliases` property when using the REPL instance programmatically, so you can e.g. alias `$lib` as `src/lib` * fix, hopefully
1 parent 898654e commit 036348a

File tree

5 files changed

+98
-19
lines changed

5 files changed

+98
-19
lines changed

packages/repl/src/lib/Repl.svelte

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,22 @@
6969
return {
7070
imports: bundler!.result?.imports ?? [],
7171
files: workspace.files,
72-
tailwind: workspace.tailwind
72+
tailwind: workspace.tailwind,
73+
aliases: workspace.aliases
7374
};
7475
}
7576
7677
// TODO get rid
77-
export async function set(data: { files: File[]; tailwind?: boolean }) {
78-
workspace.reset(data.files, { tailwind: data.tailwind ?? false }, 'App.svelte');
78+
export async function set(data: {
79+
files: File[];
80+
tailwind?: boolean;
81+
aliases?: Record<string, string>;
82+
}) {
83+
workspace.reset(
84+
data.files,
85+
{ tailwind: data.tailwind ?? false, aliases: data.aliases },
86+
'App.svelte'
87+
);
7988
}
8089
8190
// TODO get rid
@@ -88,7 +97,8 @@
8897
async function rebundle() {
8998
bundler!.bundle(workspace.files as File[], {
9099
tailwind: workspace.tailwind,
91-
fragments: workspace.compiler_options.fragments
100+
fragments: workspace.compiler_options.fragments,
101+
aliases: workspace.aliases
92102
});
93103
}
94104

packages/repl/src/lib/Workspace.svelte.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ export class Workspace {
117117
#files = $state.raw<Item[]>([]);
118118
#current = $state.raw() as File;
119119
#vim = $state(false);
120+
#aliases = $state.raw(undefined) as undefined | Record<string, string>;
120121
#tailwind = $state(false);
121122

122123
#handlers = {
@@ -400,13 +401,18 @@ export class Workspace {
400401
this.#onreset?.(this.#files);
401402
}
402403

403-
reset(new_files: Item[], options: { tailwind: boolean }, selected?: string) {
404+
reset(
405+
new_files: Item[],
406+
options: { tailwind: boolean; aliases?: Record<string, string> },
407+
selected?: string
408+
) {
404409
this.states.clear();
405410
this.set(new_files, selected);
406411

407412
this.mark_saved();
408413

409414
this.#tailwind = options.tailwind;
415+
this.#aliases = options.aliases;
410416

411417
this.#onreset(new_files);
412418
this.#reset_diagnostics();
@@ -475,6 +481,15 @@ export class Workspace {
475481
}
476482
}
477483

484+
get aliases() {
485+
return this.#aliases;
486+
}
487+
488+
set aliases(value) {
489+
this.#aliases = value;
490+
this.#onupdate(this.#current);
491+
}
492+
478493
get tailwind() {
479494
return this.#tailwind;
480495
}

packages/repl/src/lib/workers/bundler/index.ts

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import image from './plugins/image';
1212
import svg from './plugins/svg';
1313
import replace from './plugins/replace';
1414
import loop_protect from './plugins/loop-protect';
15+
import alias_plugin, { resolve } from './plugins/alias';
1516
import type { Plugin, RollupCache, TransformResult } from '@rollup/browser';
1617
import type { BundleMessageData, BundleOptions } from '../workers';
1718
import type { Warning } from '../../types';
@@ -195,20 +196,7 @@ async function get_bundle(
195196
// importing a relative file
196197
if (importee[0] === '.') {
197198
if (importer.startsWith(VIRTUAL)) {
198-
const url = new URL(importee, importer);
199-
200-
for (const suffix of ['', '.js', '.json', '.ts']) {
201-
const with_suffix = `${url.href.slice(VIRTUAL.length + 1)}${suffix}`;
202-
const file = virtual.get(with_suffix);
203-
204-
if (file) {
205-
return url.href + suffix;
206-
}
207-
}
208-
209-
throw new Error(
210-
`'${importee}' (imported by ${importer.replace(VIRTUAL + '/', '')}) does not exist`
211-
);
199+
return resolve(virtual, importee, importer);
212200
}
213201

214202
if (current) {
@@ -420,6 +408,7 @@ async function get_bundle(
420408
input: './__entry.js',
421409
cache: previous?.key === key && previous.cache,
422410
plugins: [
411+
alias_plugin(options.aliases, virtual),
423412
typescript_strip_types,
424413
repl_plugin,
425414
commonjs,
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { VIRTUAL } from '../../constants';
2+
import type { File } from '$lib/Workspace.svelte';
3+
import type { Plugin } from '@rollup/browser';
4+
5+
/**
6+
* Alias plugin for resolving import aliases (e.g., $lib -> src/lib).
7+
* This will also run on npm packages, so that e.g. SvelteKit libraries using $app/environment can also make use of this.
8+
*
9+
* @example
10+
* // With aliases: { '$lib': 'src/lib' }
11+
* // import foo from '$lib/foo' -> import foo from 'src/lib/foo'
12+
* // import bar from '$lib' -> import bar from 'src/lib'
13+
*/
14+
function alias_plugin(aliases: Record<string, string> = {}, virtual: Map<string, File>): Plugin {
15+
// Sort aliases by length (longest first) to avoid conflicts
16+
const alias_entries = Object.entries(aliases).sort((a, b) => b[0].length - a[0].length);
17+
18+
return {
19+
name: 'alias',
20+
resolveId(importee, importer) {
21+
// Skip if no aliases configured / this is a relative import
22+
if (alias_entries.length === 0 || importee[0] === '.') {
23+
return null;
24+
}
25+
26+
// Check if the import matches any alias
27+
for (const [alias_key, alias_path] of alias_entries) {
28+
if (importee === alias_key) {
29+
// Exact match - replace with alias path
30+
return resolve(virtual, `${VIRTUAL}/${alias_path}`, importer);
31+
} else if (importee.startsWith(alias_key + '/')) {
32+
// Partial match - replace the prefix
33+
const relative_path = importee.slice(alias_key.length + 1);
34+
return resolve(virtual, `${VIRTUAL}/${alias_path}/${relative_path}`, importer);
35+
}
36+
}
37+
38+
// No alias match, let other plugins handle it
39+
return null;
40+
}
41+
};
42+
}
43+
44+
/**
45+
* Tries to resolve the import path based on the virtual file map, trying different suffixes.
46+
*/
47+
export function resolve(virtual: Map<string, File>, importee: string, importer: string): string {
48+
const url = new URL(importee, importer);
49+
50+
for (const suffix of ['', '.js', '.json', '.ts', '/index.js', '/index.ts']) {
51+
const with_suffix = `${url.href.slice(VIRTUAL.length + 1)}${suffix}`;
52+
const file = virtual.get(with_suffix);
53+
54+
if (file) {
55+
return url.href + suffix;
56+
}
57+
}
58+
59+
throw new Error(
60+
`'${importee}' (imported by ${importer.replace(VIRTUAL + '/', '')}) does not exist`
61+
);
62+
}
63+
64+
export default alias_plugin;

packages/repl/src/lib/workers/workers.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export interface BundleOptions {
5353
tailwind?: boolean;
5454
runes?: boolean;
5555
fragments?: 'html' | 'tree';
56+
aliases?: Record<string, string>;
5657
}
5758

5859
export type BundleMessageData = {

0 commit comments

Comments
 (0)