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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

### Added

- Auto-detect a dependency's export subpaths via the new `subpaths` option on `DependencyConfig`. For esm.sh dependencies, `subpaths: true` reads the package's `package.json` `exports` (disk `node_modules` first, npm registry fallback) and adds each subpath to the import map as its own esm.sh URL, while `subpaths: ['./utils']` uses an explicit list. For local self-hosted dependencies, `subpaths: true` derives the multi-entry code-split `entries` from `exports` (each subpath maps to its `.ts` target), and is ignored when an explicit `entries` map is set ([#75](https://github.com/studiometa/playground/pull/75), [5c0c244](https://github.com/studiometa/playground/commit/5c0c244))
- Support multiple entry points for a single self-hosted dependency via the new `entries` map on `DependencyConfig`. All subpaths of a workspace package (e.g. a barrel `.` and a lazy `./manifest`) are built together in one code-split tsdown build, so modules shared between entries are emitted once as a shared chunk and referenced by every entry — a single runtime instance, eliminating the singleton/identity hazard of bundling each subpath separately ([#74](https://github.com/studiometa/playground/pull/74))

## v0.3.11 - 2026.08.01
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ npm run test:ci # Tests + coverage

The `dependencies` option in `playgroundPreset()` goes through:

1. **`resolveDependencies()`** (`src/lib/utils/resolve-dependencies.ts`) — resolves each dependency into either an esm.sh URL or a self-hosted bundle path. Produces an import map + self-hosted metadata.
1. **`resolveDependencies()`** (`src/lib/utils/resolve-dependencies.ts`) — resolves each dependency into either an esm.sh URL or a self-hosted bundle path. Produces an import map + self-hosted metadata. Supports a `subpaths` option that auto-detects export subpaths from a package's `package.json` `exports` (esm.sh: disk-first then npm registry; local: derives the multi-entry `entries`).
2. **`PlaygroundDependenciesPlugin`** (`src/lib/plugins/PlaygroundDependenciesPlugin.ts`) — webpack plugin that bundles self-hosted dependencies with tsdown into `.js` + `.d.ts`. Emits `_headers` file for `x-typescript-types`.
3. **`playground.ts` preset** (`src/lib/presets/playground.ts`) — orchestrates everything: merges import maps, instantiates plugins, configures webpack.

Expand Down
8 changes: 8 additions & 0 deletions packages/demo/lib/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "demo-lib",
"type": "module",
"exports": {
".": "./index.ts",
"./manifest": "./manifest.ts"
}
}
6 changes: 2 additions & 4 deletions packages/demo/meta.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,12 @@ export default defineWebpackConfig({
{
specifier: '@studiometa/js-toolkit',
esmSh: { bundle: false },
subpaths: true,
},
{
specifier: 'demo-lib',
source: './lib/**/*.ts',
entries: {
'.': './lib/index.ts',
'./manifest': './lib/manifest.ts',
},
subpaths: true,
},
],
loaders: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,35 @@ describe('PlaygroundDependenciesPlugin', () => {
);
});

it('prefixes alias specifiers of a multi-entry dependency', () => {
const deps: ResolvedDependency[] = [
{
specifier: 'demo',
importMapValue: '/static/deps/demo/index.js',
type: 'bundle',
entries: [
{
subpath: './Foo',
specifier: 'demo/Foo',
name: 'Foo',
source: '../demo/Foo.ts',
importMapValue: '/static/deps/demo/Foo.js',
},
],
aliasSpecifiers: ['demo/Foo.js'],
},
];
const importMap = {
'demo/Foo': '/static/deps/demo/Foo.js',
'demo/Foo.js': '/static/deps/demo/Foo.js',
};

applyAndGetImportMap(deps, importMap, '/play');

expect(importMap['demo/Foo']).toBe('/play/static/deps/demo/Foo.js');
expect(importMap['demo/Foo.js']).toBe('/play/static/deps/demo/Foo.js');
});

it('infers publicPath from webpack output.publicPath', () => {
const deps: ResolvedDependency[] = [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,11 @@ export class PlaygroundDependenciesPlugin {
for (const dep of this.dependencies) {
// A multi-entry dependency contributes one import-map key per entry
// subpath; single-entry ones contribute just their own specifier.
// Alias specifiers (e.g. `.js` export aliases collapsed onto a
// canonical entry) are prefixed too — they resolve to the same
// emitted file as their canonical entry.
const specifiers = dep.entries?.length
? dep.entries.map((entry) => entry.specifier)
? [...dep.entries.map((entry) => entry.specifier), ...(dep.aliasSpecifiers ?? [])]
: [dep.specifier];
for (const specifier of specifiers) {
const currentValue = this.importMap[specifier];
Expand Down
2 changes: 1 addition & 1 deletion packages/playground/src/lib/presets/playground.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export function playgroundPreset(options?: PartialDeep<PlaygroundPresetOptions>)

if (options?.dependencies?.length) {
const packageJsonPath = resolve(configDir, 'package.json');
const resolved = resolveDependencies(options.dependencies, packageJsonPath);
const resolved = await resolveDependencies(options.dependencies, packageJsonPath);
// Dependencies go first, manual importMap entries take precedence
mergedImportMap = { ...resolved.importMap, ...mergedImportMap };
selfHostedDeps = resolved.selfHosted;
Expand Down
Loading
Loading