Skip to content
Open
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
24 changes: 24 additions & 0 deletions src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,9 @@ export class ImportResolver {
if (normalizedResolvedPath.endsWith('.css')) {
return this._loadLocalStyle(resolvedPath, content);
}
if (normalizedResolvedPath.endsWith('.json')) {
return this._loadLocalJson(resolvedPath, content);
}

return await this._options.dynamicLoader(content);
}
Expand All @@ -571,15 +574,36 @@ export class ImportResolver {
candidates.add(`${baseCandidate}.tsx`);
candidates.add(`${baseCandidate}.js`);
candidates.add(`${baseCandidate}.css`);
candidates.add(`${baseCandidate}.json`);
candidates.add(PathExt.join(baseCandidate, 'index.ts'));
candidates.add(PathExt.join(baseCandidate, 'index.tsx'));
candidates.add(PathExt.join(baseCandidate, 'index.js'));
candidates.add(PathExt.join(baseCandidate, 'index.css'));
candidates.add(PathExt.join(baseCandidate, 'index.json'));
}

return Array.from(candidates);
}

private _loadLocalJson(path: string, content: string): IModule {
let parsed: unknown;
try {
parsed = JSON.parse(content);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Invalid JSON file "${path}": ${message}`);
}

const jsonModule: Record<string, unknown> = {
__esModule: true,
default: parsed
};
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
Object.assign(jsonModule, parsed);
}
return jsonModule as unknown as IModule;
}

private _loadLocalStyle(path: string, css: string): IModule {
this._snapshotLocalStyle(path);
this._loadedLocalStylePaths.add(path);
Expand Down
84 changes: 84 additions & 0 deletions ui-tests/tests/plugin-playground.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ const CSS_IMPORT_TEST_MARKER_ID = 'playground-css-import-test-marker';
const CSS_IMPORT_TEST_COLOR = 'rgb(17, 34, 51)';
const CSS_IMPORT_TEST_UPDATED_COLOR = 'rgb(34, 68, 102)';
const CSS_IMPORT_TEST_BROKEN_MODULE_ERROR = 'css import rollback test failure';
const JSON_IMPORT_TEST_PLUGIN_ID = 'playground-json-import-test:plugin';
const JSON_IMPORT_TEST_MARKER_ID = 'playground-json-import-test-marker';
const COMMAND_COMPLETION_FILE = 'command-completion.ts';
const INVOKE_FILE_COMPLETER_COMMAND = 'completer:invoke-file';
const JUPYTERLITE_AI_OPEN_OR_REVEAL_CHAT_COMMAND =
Expand Down Expand Up @@ -248,6 +250,39 @@ const CSS_IMPORT_TEST_PACKAGE_JSON_WITH_STYLE = JSON.stringify(
2
);

const JSON_IMPORT_TEST_DATA = JSON.stringify(
{
greeting: 'Hello from local JSON',
version: '1.2.3'
},
null,
2
);
const JSON_IMPORT_TEST_SOURCE = `import { JupyterFrontEnd, JupyterFrontEndPlugin } from '@jupyterlab/application';
import config, { version } from './config.json';

const plugin: JupyterFrontEndPlugin<void> = {
id: '${JSON_IMPORT_TEST_PLUGIN_ID}',
autoStart: true,
activate: (app: JupyterFrontEnd) => {
const existing = document.getElementById('${JSON_IMPORT_TEST_MARKER_ID}');
if (existing) {
existing.remove();
}
const marker = document.createElement('div');
marker.id = '${JSON_IMPORT_TEST_MARKER_ID}';
marker.dataset.greeting = String(config.greeting || '');
marker.dataset.version = String(version || '');
document.body.appendChild(marker);
},
deactivate: () => {
document.getElementById('${JSON_IMPORT_TEST_MARKER_ID}')?.remove();
}
};

export default plugin;
`;

const DYNAMIC_SETTINGS_PERSIST_TEST_PLUGIN_ID =
'dynamic-settings-persist-test:plugin';
const DYNAMIC_SETTINGS_PERSIST_TEST_SET_COMMAND =
Expand Down Expand Up @@ -1929,6 +1964,55 @@ test('rolls back CSS changes when plugin schema parsing fails', async ({
).resolves.toBe(CSS_IMPORT_TEST_COLOR);
});

test('loads plugin importing local .json file', async ({ page, tmpPath }) => {
const projectRoot = `${tmpPath}/json-import-test`;
const sourcePath = `${projectRoot}/index.ts`;
const jsonPath = `${projectRoot}/config.json`;

await page.contents.uploadContent(JSON_IMPORT_TEST_DATA, 'text', jsonPath);
await page.contents.uploadContent(
JSON_IMPORT_TEST_SOURCE,
'text',
sourcePath
);
await page.goto();

await page.filebrowser.open(sourcePath);
expect(await page.activity.activateTab('index.ts')).toBe(true);

await page.waitForCondition(() =>
page.evaluate((id: string) => {
return window.jupyterapp.commands.hasCommand(id);
}, LOAD_COMMAND)
);
const loadResult = await page.evaluate((id: string) => {
return window.jupyterapp.commands.execute(id);
}, LOAD_COMMAND);
expect(loadResult.ok).toBe(true);
expect(loadResult.status).toBe('loaded');
expect(loadResult.pluginIds).toContain(JSON_IMPORT_TEST_PLUGIN_ID);

await page.waitForCondition(() =>
page.evaluate(
({ markerId, greeting, version }) => {
const marker = document.getElementById(markerId);
if (!marker) {
return false;
}
return (
marker.dataset.greeting === greeting &&
marker.dataset.version === version
);
},
{
markerId: JSON_IMPORT_TEST_MARKER_ID,
greeting: 'Hello from local JSON',
version: '1.2.3'
}
)
);
});

test('persists dynamic plugin settings in browser storage', async ({
page,
tmpPath
Expand Down
Loading