Skip to content

Commit 5ac64a9

Browse files
committed
jupyter lite in progresss
1 parent 1a202d0 commit 5ac64a9

File tree

352 files changed

+220738
-29
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

352 files changed

+220738
-29
lines changed

.jupyterlite.doit.db

68 KB
Binary file not shown.

_output/api/translations/all.json

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"data": {
3+
"en": {
4+
"displayName": "English",
5+
"nativeName": "English"
6+
}
7+
},
8+
"message": ""
9+
}

_output/api/translations/en.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"data": {},
3+
"message": ""
4+
}

_output/bootstrap.js

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*-----------------------------------------------------------------------------
2+
| Copyright (c) Jupyter Development Team.
3+
| Distributed under the terms of the Modified BSD License.
4+
|----------------------------------------------------------------------------*/
5+
6+
// We copy some of the pageconfig parsing logic in @jupyterlab/coreutils
7+
// below, since this must run before any other files are loaded (including
8+
// @jupyterlab/coreutils).
9+
10+
/**
11+
* Get global configuration data for the Jupyter application.
12+
*
13+
* @param name - The name of the configuration option.
14+
*
15+
* @returns The config value or an empty string if not found.
16+
*
17+
* #### Notes
18+
* All values are treated as strings. For browser based applications, it is
19+
* assumed that the page HTML includes a script tag with the id
20+
* `jupyter-config-data` containing the configuration as valid JSON.
21+
*/
22+
23+
let _CONFIG_DATA = null;
24+
function getOption(name) {
25+
if (_CONFIG_DATA === null) {
26+
let configData = {};
27+
// Use script tag if available.
28+
if (typeof document !== 'undefined' && document) {
29+
const el = document.getElementById('jupyter-config-data');
30+
31+
if (el) {
32+
configData = JSON.parse(el.textContent || '{}');
33+
}
34+
}
35+
_CONFIG_DATA = configData;
36+
}
37+
38+
return _CONFIG_DATA[name] || '';
39+
}
40+
41+
// eslint-disable-next-line no-undef
42+
__webpack_public_path__ = getOption('fullStaticUrl') + '/';
43+
44+
function loadScript(url) {
45+
return new Promise((resolve, reject) => {
46+
const newScript = document.createElement('script');
47+
newScript.onerror = reject;
48+
newScript.onload = resolve;
49+
newScript.async = true;
50+
document.head.appendChild(newScript);
51+
newScript.src = url;
52+
});
53+
}
54+
55+
async function loadComponent(url, scope) {
56+
await loadScript(url);
57+
58+
// From https://webpack.js.org/concepts/module-federation/#dynamic-remote-containers
59+
await __webpack_init_sharing__('default');
60+
const container = window._JUPYTERLAB[scope];
61+
// Initialize the container, it may provide shared modules and may need ours
62+
await container.init(__webpack_share_scopes__.default);
63+
}
64+
65+
void (async function bootstrap() {
66+
// This is all the data needed to load and activate plugins. This should be
67+
// gathered by the server and put onto the initial page template.
68+
const extension_data = getOption('federated_extensions');
69+
70+
// We first load all federated components so that the shared module
71+
// deduplication can run and figure out which shared modules from all
72+
// components should be actually used. We have to do this before importing
73+
// and using the module that actually uses these components so that all
74+
// dependencies are initialized.
75+
let labExtensionUrl = getOption('fullLabextensionsUrl');
76+
const extensions = await Promise.allSettled(
77+
extension_data.map(async data => {
78+
await loadComponent(`${labExtensionUrl}/${data.name}/${data.load}`, data.name);
79+
})
80+
);
81+
82+
extensions.forEach(p => {
83+
if (p.status === 'rejected') {
84+
// There was an error loading the component
85+
console.error(p.reason);
86+
}
87+
});
88+
89+
// Now that all federated containers are initialized with the main
90+
// container, we can import the main function.
91+
let main = (await import('./index.js')).main;
92+
void main();
93+
})();

_output/config-utils.js

+267
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
/**
2+
* configuration utilities for jupyter-lite
3+
*
4+
* this file may not import anything else, and exposes no API
5+
*/
6+
7+
/*
8+
* An `index.html` should `await import('../config-utils.js')` after specifying
9+
* the key `script` tags...
10+
*
11+
* ```html
12+
* <script id="jupyter-config-data" type="application/json" data-jupyter-lite-root="..">
13+
* {}
14+
* </script>
15+
* ```
16+
*/
17+
const JUPYTER_CONFIG_ID = 'jupyter-config-data';
18+
19+
/*
20+
* The JS-mangled name for `data-jupyter-lite-root`
21+
*/
22+
const LITE_ROOT_ATTR = 'jupyterLiteRoot';
23+
24+
/**
25+
* The well-known filename that contains `#jupyter-config-data` and other goodies
26+
*/
27+
const LITE_FILES = ['jupyter-lite.json', 'jupyter-lite.ipynb'];
28+
29+
/**
30+
* And this link tag, used like so to load a bundle after configuration.
31+
*
32+
* ```html
33+
* <link
34+
* id="jupyter-lite-main"
35+
* rel="preload"
36+
* href="../build/bundle.js?_=bad4a54"
37+
* main="index"
38+
* as="script"
39+
* />
40+
* ```
41+
*/
42+
const LITE_MAIN = 'jupyter-lite-main';
43+
44+
/**
45+
* The current page, with trailing server junk stripped
46+
*/
47+
const HERE = `${window.location.origin}${window.location.pathname.replace(
48+
/(\/|\/index.html)?$/,
49+
'',
50+
)}/`;
51+
52+
/**
53+
* The computed composite configuration
54+
*/
55+
let _JUPYTER_CONFIG;
56+
57+
/**
58+
* A handle on the config script, must exist, and will be overridden
59+
*/
60+
const CONFIG_SCRIPT = document.getElementById(JUPYTER_CONFIG_ID);
61+
62+
/**
63+
* The relative path to the root of this JupyterLite
64+
*/
65+
const RAW_LITE_ROOT = CONFIG_SCRIPT.dataset[LITE_ROOT_ATTR];
66+
67+
/**
68+
* The fully-resolved path to the root of this JupyterLite
69+
*/
70+
const FULL_LITE_ROOT = new URL(RAW_LITE_ROOT, HERE).toString();
71+
72+
/**
73+
* Paths that are joined with baseUrl to derive full URLs
74+
*/
75+
const UNPREFIXED_PATHS = ['licensesUrl', 'themesUrl'];
76+
77+
/* a DOM parser for reading html files */
78+
const parser = new DOMParser();
79+
80+
/**
81+
* Merge `jupyter-config-data` on the current page with:
82+
* - the contents of `.jupyter-lite#/jupyter-config-data`
83+
* - parent documents, and their `.jupyter-lite#/jupyter-config-data`
84+
* ...up to `jupyter-lite-root`.
85+
*/
86+
async function jupyterConfigData() {
87+
/**
88+
* Return the value if already cached for some reason
89+
*/
90+
if (_JUPYTER_CONFIG != null) {
91+
return _JUPYTER_CONFIG;
92+
}
93+
94+
let parent = new URL(HERE).toString();
95+
let promises = [getPathConfig(HERE)];
96+
while (parent != FULL_LITE_ROOT) {
97+
parent = new URL('..', parent).toString();
98+
promises.unshift(getPathConfig(parent));
99+
}
100+
101+
const configs = (await Promise.all(promises)).flat();
102+
103+
let finalConfig = configs.reduce(mergeOneConfig);
104+
105+
// apply any final patches
106+
finalConfig = dedupFederatedExtensions(finalConfig);
107+
108+
// hoist to cache
109+
_JUPYTER_CONFIG = finalConfig;
110+
111+
return finalConfig;
112+
}
113+
114+
/**
115+
* Merge a new configuration on top of the existing config
116+
*/
117+
function mergeOneConfig(memo, config) {
118+
for (const [k, v] of Object.entries(config)) {
119+
switch (k) {
120+
// this list of extension names is appended
121+
case 'disabledExtensions':
122+
case 'federated_extensions':
123+
memo[k] = [...(memo[k] || []), ...v];
124+
break;
125+
// these `@org/pkg:plugin` are merged at the first level of values
126+
case 'litePluginSettings':
127+
case 'settingsOverrides':
128+
if (!memo[k]) {
129+
memo[k] = {};
130+
}
131+
for (const [plugin, defaults] of Object.entries(v || {})) {
132+
memo[k][plugin] = { ...(memo[k][plugin] || {}), ...defaults };
133+
}
134+
break;
135+
default:
136+
memo[k] = v;
137+
}
138+
}
139+
return memo;
140+
}
141+
142+
function dedupFederatedExtensions(config) {
143+
const originalList = Object.keys(config || {})['federated_extensions'] || [];
144+
const named = {};
145+
for (const ext of originalList) {
146+
named[ext.name] = ext;
147+
}
148+
let allExtensions = [...Object.values(named)];
149+
allExtensions.sort((a, b) => a.name.localeCompare(b.name));
150+
return config;
151+
}
152+
153+
/**
154+
* Load jupyter config data from (this) page and merge with
155+
* `jupyter-lite.json#jupyter-config-data`
156+
*/
157+
async function getPathConfig(url) {
158+
let promises = [getPageConfig(url)];
159+
for (const fileName of LITE_FILES) {
160+
promises.unshift(getLiteConfig(url, fileName));
161+
}
162+
return Promise.all(promises);
163+
}
164+
165+
/**
166+
* The current normalized location
167+
*/
168+
function here() {
169+
return window.location.href.replace(/(\/|\/index.html)?$/, '/');
170+
}
171+
172+
/**
173+
* Maybe fetch an `index.html` in this folder, which must contain the trailing slash.
174+
*/
175+
export async function getPageConfig(url = null) {
176+
let script = CONFIG_SCRIPT;
177+
178+
if (url != null) {
179+
const text = await (await window.fetch(`${url}index.html`)).text();
180+
const doc = parser.parseFromString(text, 'text/html');
181+
script = doc.getElementById(JUPYTER_CONFIG_ID);
182+
}
183+
return fixRelativeUrls(url, JSON.parse(script.textContent));
184+
}
185+
186+
/**
187+
* Fetch a jupyter-lite JSON or Notebook in this folder, which must contain the trailing slash.
188+
*/
189+
export async function getLiteConfig(url, fileName) {
190+
let text = '{}';
191+
let config = {};
192+
const liteUrl = `${url || HERE}${fileName}`;
193+
try {
194+
text = await (await window.fetch(liteUrl)).text();
195+
const json = JSON.parse(text);
196+
const liteConfig = fileName.endsWith('.ipynb')
197+
? json['metadata']['jupyter-lite']
198+
: json;
199+
config = liteConfig[JUPYTER_CONFIG_ID] || {};
200+
} catch (err) {
201+
console.warn(`failed get ${JUPYTER_CONFIG_ID} from ${liteUrl}`);
202+
}
203+
return fixRelativeUrls(url, config);
204+
}
205+
206+
export function fixRelativeUrls(url, config) {
207+
let urlBase = new URL(url || here()).pathname;
208+
for (const [k, v] of Object.entries(config)) {
209+
config[k] = fixOneRelativeUrl(k, v, url, urlBase);
210+
}
211+
return config;
212+
}
213+
214+
export function fixOneRelativeUrl(key, value, url, urlBase) {
215+
if (key === 'litePluginSettings' || key === 'settingsOverrides') {
216+
// these are plugin id-keyed objects, fix each plugin
217+
return Object.entries(value || {}).reduce((m, [k, v]) => {
218+
m[k] = fixRelativeUrls(url, v);
219+
return m;
220+
}, {});
221+
} else if (
222+
!UNPREFIXED_PATHS.includes(key) &&
223+
key.endsWith('Url') &&
224+
value.startsWith('./')
225+
) {
226+
// themesUrls, etc. are joined in code with baseUrl, leave as-is: otherwise, clean
227+
return `${urlBase}${value.slice(2)}`;
228+
} else if (key.endsWith('Urls') && Array.isArray(value)) {
229+
return value.map((v) => (v.startsWith('./') ? `${urlBase}${v.slice(2)}` : v));
230+
}
231+
return value;
232+
}
233+
234+
/**
235+
* Update with the as-configured favicon
236+
*/
237+
function addFavicon(config) {
238+
const favicon = document.createElement('link');
239+
favicon.rel = 'icon';
240+
favicon.type = 'image/x-icon';
241+
favicon.href = config.faviconUrl;
242+
document.head.appendChild(favicon);
243+
}
244+
245+
/**
246+
* The main entry point.
247+
*/
248+
async function main() {
249+
const config = await jupyterConfigData();
250+
if (config.baseUrl === new URL(here()).pathname) {
251+
window.location.href = config.appUrl.replace(/\/?$/, '/index.html');
252+
return;
253+
}
254+
// rewrite the config
255+
CONFIG_SCRIPT.textContent = JSON.stringify(config, null, 2);
256+
addFavicon(config);
257+
const preloader = document.getElementById(LITE_MAIN);
258+
const bundle = document.createElement('script');
259+
bundle.src = preloader.href;
260+
bundle.main = preloader.attributes.main;
261+
document.head.appendChild(bundle);
262+
}
263+
264+
/**
265+
* TODO: consider better pattern for invocation.
266+
*/
267+
await main();

_output/consoles/favicon.ico

1.12 KB
Binary file not shown.

0 commit comments

Comments
 (0)