Skip to content

Commit 803cc66

Browse files
committed
Better logic to set webpack public path in AMD modules.
Fixes #3474. This is another take on #3464, since that approach did not work to set the public path in AMD modules. Essentially, since AMD modules are executed asynchronously, the webpack 'auto' public path logic does not work. Instead, we use the requirejs-specific 'module' magic dependency, which allows us to get the path to the current AMD module, and use that to get the public path. We do this public path computation in a separate script to isolate it from any library code.
1 parent 3ec8ed8 commit 803cc66

File tree

6 files changed

+245
-26
lines changed

6 files changed

+245
-26
lines changed

examples/embed-amd/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Using the HTML widget manager as a RequireJS AMD module
2+
3+
## Description
4+
5+
This is an example project showing how to embed widgets in an HTML document using a RequireJS AMD module.
6+
7+
In order to test the current development repo, make a symbolic link from the `packages/html-manager` directory to this directory and uncomment the `html-manager` paths config in `index.html`.
8+
9+
The widget data in this example was generated from the following code:
10+
11+
```python
12+
from ipywidgets import VBox, jsdlink, IntSlider, Button
13+
14+
s1, s2 = IntSlider(max=200, value=100), IntSlider(value=40)
15+
b = Button(icon='legal')
16+
jsdlink((s1, 'value'), (s2, 'max'))
17+
VBox([s1, s2, b])
18+
```
19+
20+
## Try it
21+
22+
1. Start with a repository checkout, and run `yarn install` in the root directory.
23+
2. Run `yarn run build:examples` in the root directory.
24+
3. Open the `index.html` file in this directory.

examples/embed-amd/index.html

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
<html>
2+
<head>
3+
<meta http-equiv="content-type" content="text/html; charset=UTF8">
4+
<style>
5+
.jupyter-widgetarea {
6+
margin: 5px;
7+
margin-left: auto;
8+
margin-right: auto;
9+
max-width: 900px;
10+
}
11+
</style>
12+
</head>
13+
<body>
14+
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.js"></script>
15+
<script>
16+
require.config({
17+
bundles: {
18+
'@jupyter-widgets/html-manager/dist/embed-amd': [
19+
'@jupyter-widgets/html-manager',
20+
'@jupyter-widgets/base',
21+
'@jupyter-widgets/controls'
22+
]
23+
},
24+
paths: {
25+
'@jupyter-widgets/html-manager': [
26+
// 'html-manager', // if a symbolic link is set up to the html-manager package for local dev
27+
'https://cdn.jsdelivr.net/npm/@jupyter-widgets/html-manager',
28+
]
29+
},
30+
})
31+
require(["@jupyter-widgets/html-manager/dist/embed-amd"], () => {
32+
console.log('Processing widgets on page');
33+
});
34+
</script>
35+
<script type="application/vnd.jupyter.widget-state+json">
36+
{
37+
"version_major": 2,
38+
"version_minor": 0,
39+
"state": {
40+
"1d915e54eff54fd89e505a46ccabdabd": {
41+
"model_name": "LayoutModel",
42+
"model_module": "@jupyter-widgets/base",
43+
"model_module_version": "2.0.0",
44+
"state": {}
45+
},
46+
"48a42260652f4b7eb7851c65cd155604": {
47+
"model_name": "SliderStyleModel",
48+
"model_module": "@jupyter-widgets/controls",
49+
"model_module_version": "2.0.0",
50+
"state": {
51+
"description_width": ""
52+
}
53+
},
54+
"105655a5e8dc4b7bb19d824cc3ff7770": {
55+
"model_name": "IntSliderModel",
56+
"model_module": "@jupyter-widgets/controls",
57+
"model_module_version": "2.0.0",
58+
"state": {
59+
"value": 100,
60+
"max": 200,
61+
"style": "IPY_MODEL_48a42260652f4b7eb7851c65cd155604",
62+
"behavior": "drag-tap",
63+
"layout": "IPY_MODEL_1d915e54eff54fd89e505a46ccabdabd"
64+
}
65+
},
66+
"cb13b25cf84542ba882ab2a9c6e57c6d": {
67+
"model_name": "LayoutModel",
68+
"model_module": "@jupyter-widgets/base",
69+
"model_module_version": "2.0.0",
70+
"state": {}
71+
},
72+
"f0479b348e2441cd87e1bd856fac5c22": {
73+
"model_name": "SliderStyleModel",
74+
"model_module": "@jupyter-widgets/controls",
75+
"model_module_version": "2.0.0",
76+
"state": {
77+
"description_width": ""
78+
}
79+
},
80+
"2182c1a3fe4a410f9b0a5306ae05c530": {
81+
"model_name": "IntSliderModel",
82+
"model_module": "@jupyter-widgets/controls",
83+
"model_module_version": "2.0.0",
84+
"state": {
85+
"value": 40,
86+
"style": "IPY_MODEL_f0479b348e2441cd87e1bd856fac5c22",
87+
"behavior": "drag-tap",
88+
"layout": "IPY_MODEL_cb13b25cf84542ba882ab2a9c6e57c6d"
89+
}
90+
},
91+
"5f2da4ad981b467cb2d4f07efe5141f4": {
92+
"model_name": "LayoutModel",
93+
"model_module": "@jupyter-widgets/base",
94+
"model_module_version": "2.0.0",
95+
"state": {}
96+
},
97+
"e5f63e1e06af400aac8135ff3394b856": {
98+
"model_name": "ButtonStyleModel",
99+
"model_module": "@jupyter-widgets/controls",
100+
"model_module_version": "2.0.0",
101+
"state": {
102+
"font_family": null,
103+
"font_size": null,
104+
"font_style": null,
105+
"font_variant": null,
106+
"font_weight": null,
107+
"text_color": null,
108+
"text_decoration": null
109+
}
110+
},
111+
"891a12a9856949b4be2e520f732dcca9": {
112+
"model_name": "ButtonModel",
113+
"model_module": "@jupyter-widgets/controls",
114+
"model_module_version": "2.0.0",
115+
"state": {
116+
"tooltip": null,
117+
"icon": "legal",
118+
"style": "IPY_MODEL_e5f63e1e06af400aac8135ff3394b856",
119+
"layout": "IPY_MODEL_5f2da4ad981b467cb2d4f07efe5141f4"
120+
}
121+
},
122+
"e8a6db8ff7bd4645b5b23ccb797dee9c": {
123+
"model_name": "DirectionalLinkModel",
124+
"model_module": "@jupyter-widgets/controls",
125+
"model_module_version": "2.0.0",
126+
"state": {
127+
"target": [
128+
"IPY_MODEL_2182c1a3fe4a410f9b0a5306ae05c530",
129+
"max"
130+
],
131+
"source": [
132+
"IPY_MODEL_105655a5e8dc4b7bb19d824cc3ff7770",
133+
"value"
134+
]
135+
}
136+
},
137+
"b9445ea442bc4a5aae73c1e2241c3922": {
138+
"model_name": "LayoutModel",
139+
"model_module": "@jupyter-widgets/base",
140+
"model_module_version": "2.0.0",
141+
"state": {}
142+
},
143+
"a08a1974ba01461c8d9b91b8bfa0f6ce": {
144+
"model_name": "VBoxModel",
145+
"model_module": "@jupyter-widgets/controls",
146+
"model_module_version": "2.0.0",
147+
"state": {
148+
"children": [
149+
"IPY_MODEL_105655a5e8dc4b7bb19d824cc3ff7770",
150+
"IPY_MODEL_2182c1a3fe4a410f9b0a5306ae05c530",
151+
"IPY_MODEL_891a12a9856949b4be2e520f732dcca9"
152+
],
153+
"layout": "IPY_MODEL_b9445ea442bc4a5aae73c1e2241c3922"
154+
}
155+
}
156+
}
157+
}
158+
</script>
159+
<script type="application/vnd.jupyter.widget-view+json">
160+
{
161+
"version_major": 2,
162+
"version_minor": 0,
163+
"model_id": "a08a1974ba01461c8d9b91b8bfa0f6ce"
164+
}
165+
</script>
166+
</body>
167+
</html>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// In an AMD module, we set the public path using the magic requirejs 'module' dependency
2+
// See https://github.com/requirejs/requirejs/wiki/Differences-between-the-simplified-CommonJS-wrapper-and-standard-AMD-define#module
3+
// Since 'module' is a requirejs magic module, we must include 'module' in the webpack externals configuration.
4+
var module = require('module');
5+
var url = new URL(module.uri, document.location);
6+
// Using lastIndexOf('/')+1 gives us the empty string if there is no '/', so pathname becomes '/'
7+
url.pathname = url.pathname.slice(0, url.pathname.lastIndexOf('/') + 1);
8+
__webpack_public_path__ = `${url.origin}${url.pathname}`;

packages/html-manager/webpack.config.js

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,18 @@
55

66
var path = require('path');
77

8-
var rules = [
9-
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
10-
// required to load font-awesome
11-
{ test: /\.(woff|woff2|eot|ttf|otf)$/i, type: 'asset/resource' },
12-
{ test: /\.svg$/i, type: 'asset' },
13-
];
8+
var options = {
9+
devtool: 'source-map',
10+
mode: 'production',
11+
module: {
12+
rules: [
13+
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
14+
// required to load font-awesome
15+
{ test: /\.(woff|woff2|eot|ttf|otf)$/i, type: 'asset/resource' },
16+
{ test: /\.svg$/i, type: 'asset' },
17+
],
18+
},
19+
};
1420

1521
module.exports = [
1622
{
@@ -20,9 +26,7 @@ module.exports = [
2026
filename: 'embed.js',
2127
path: path.resolve(__dirname, 'dist'),
2228
},
23-
devtool: 'source-map',
24-
module: { rules: rules },
25-
mode: 'production',
29+
...options,
2630
},
2731
{
2832
// script that renders widgets using the amd embedding and can render third-party custom widgets
@@ -31,57 +35,62 @@ module.exports = [
3135
filename: 'embed-amd-render.js',
3236
path: path.resolve(__dirname, 'dist', 'amd'),
3337
},
34-
module: { rules: rules },
35-
mode: 'production',
38+
...options,
3639
},
3740
{
3841
// embed library that depends on requirejs, and can load third-party widgets dynamically
39-
entry: './lib/libembed-amd.js',
42+
entry: ['./amd-public-path.js', './lib/libembed-amd.js'],
4043
output: {
4144
library: '@jupyter-widgets/html-manager/dist/libembed-amd',
4245
filename: 'libembed-amd.js',
4346
path: path.resolve(__dirname, 'dist', 'amd'),
4447
libraryTarget: 'amd',
48+
publicPath: '', // Set in amd-public-path.js
4549
},
46-
module: { rules: rules },
47-
mode: 'production',
50+
// 'module' is the magic requirejs dependency used to set the publicPath
51+
externals: ['module'],
52+
...options,
4853
},
4954
{
5055
// @jupyter-widgets/html-manager
51-
entry: './lib/index.js',
56+
entry: ['./amd-public-path.js', './lib/index.js'],
5257
output: {
5358
library: '@jupyter-widgets/html-manager',
5459
filename: 'index.js',
5560
path: path.resolve(__dirname, 'dist', 'amd'),
5661
libraryTarget: 'amd',
62+
publicPath: '', // Set in amd-public-path.js
5763
},
58-
module: { rules: rules },
59-
externals: ['@jupyter-widgets/base', '@jupyter-widgets/controls'],
60-
mode: 'production',
64+
// 'module' is the magic requirejs dependency used to set the publicPath
65+
externals: ['@jupyter-widgets/base', '@jupyter-widgets/controls', 'module'],
66+
...options,
6167
},
6268
{
6369
// @jupyter-widgets/base
64-
entry: '@jupyter-widgets/base/lib/index',
70+
entry: ['./amd-public-path.js', '@jupyter-widgets/base/lib/index'],
6571
output: {
6672
library: '@jupyter-widgets/base',
6773
filename: 'base.js',
6874
path: path.resolve(__dirname, 'dist', 'amd'),
6975
libraryTarget: 'amd',
76+
publicPath: '', // Set in amd-public-path.js
7077
},
71-
module: { rules: rules },
72-
mode: 'production',
78+
// 'module' is the magic requirejs dependency used to set the publicPath
79+
externals: ['module'],
80+
...options,
7381
},
7482
{
7583
// @jupyter-widgets/controls
76-
entry: '@jupyter-widgets/controls/lib/index',
84+
entry: ['./amd-public-path.js', '@jupyter-widgets/controls/lib/index'],
7785
output: {
7886
library: '@jupyter-widgets/controls',
7987
filename: 'controls.js',
8088
path: path.resolve(__dirname, 'dist', 'amd'),
8189
libraryTarget: 'amd',
90+
publicPath: '', // Set in amd-public-path.js
8291
},
83-
module: { rules: rules },
84-
externals: ['@jupyter-widgets/base'],
85-
mode: 'production',
92+
// 'module' is the magic requirejs dependency used to set the publicPath
93+
externals: ['@jupyter-widgets/base', 'module'],
94+
...options,
8695
},
8796
];
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// In an AMD module, we set the public path using the magic requirejs 'module' dependency
2+
// See https://github.com/requirejs/requirejs/wiki/Differences-between-the-simplified-CommonJS-wrapper-and-standard-AMD-define#module
3+
// Since 'module' is a requirejs magic module, we must include 'module' in the webpack externals configuration.
4+
var module = require('module');
5+
var url = new URL(module.uri, document.location);
6+
// Using lastIndexOf('/')+1 gives us the empty string if there is no '/', so pathname becomes '/'
7+
url.pathname = url.pathname.slice(0, url.pathname.lastIndexOf('/') + 1);
8+
__webpack_public_path__ = `${url.origin}${url.pathname}`;

python/widgetsnbextension/webpack.config.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
var path = require('path');
22
module.exports = {
3-
entry: './src/extension.js',
3+
entry: ['./amd-public-path.js', './src/extension.js'],
44
output: {
55
filename: 'extension.js',
66
path: path.resolve(__dirname, 'widgetsnbextension', 'static'),
77
libraryTarget: 'amd',
8+
publicPath: '', // Set in amd-public-path.js
89
},
910
devtool: 'source-map',
1011
module: {
@@ -15,4 +16,6 @@ module.exports = {
1516
{ test: /\.svg$/i, type: 'asset' },
1617
],
1718
},
19+
// 'module' is the magic requirejs dependency used to set the publicPath
20+
externals: ['module'],
1821
};

0 commit comments

Comments
 (0)