tl;dr: (how) can I use an ES6 module that imports other modules’ exports, in a VS Code extension creating a Webview?
Situation
I’m trying to update and improve a VS Code extension first written 4 years ago. The extension creates a webview, using HTML and JavaScript modules. This is the code that used to work:
<head>
<!-- ... -->
<script type='module'>
'use strict';
import { loadFromString as loadSCXML } from '${scxmlDomJs}';
import SCXMLEditor from '${scxmlEditorJs}';
import NeatXML from '${neatXMLJs}';
// …
</script>
</head>
…where the contents of the ${…}
strings were replaced with URIs generated via:
path.join(extensionContext.extensionPath, 'resources', 'scxmlDOM.js')
These days the Webview in VS Code is now locked down for security, and (as I understand it) I need to replace the inline <script>
element with something like the following:
<head>
<!-- ... -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; script-src 'nonce-${nonce}'">
<script nonce="${nonce}" src="${mainJS}"></script>
where mainJS
is a path like above, further wrapped in a call to webview.asWebviewUri(…)
.
The Question
If I move my module code into a separate file main.js
, how can it import other modules, when the paths to those modules need to be generated?
I’ve found several working examples (including the one linked above) for how to make script in webviews work with CORS and nonces, but I cannot find a resource on how to make it work when those scripts are modules. The closest I’ve found is this question which only might be related, but which is also unanswered.
2
Answers
One solution that works (tested) is to use an import map to map simple names to the URI in the HTML, and modify the
main.js
to import by the simple names.Webview HTML:
main.js:
I don't know if the
nonce
is strictly needed on the import map<script>
element, but it certainly works with it present.Note that the
${…}
URIs are not literals, but expected to be replaced with the output from theasWebviewUri()
function.In my extension vscode-antlr4 I don’t use an import map. Instead I set up my project such that for the webview contents I have an own tsconfig.json file which causes tsc to produce ES2022 modules (while for the extension itself CommonJS is used).
This setup allows me to import 3rd party libs, like antlr4ts and d3 from the node_modules folder. I can now import these webview scripts in my webview content code like shown for example in the railroad diagram provider.
As you can see I set a base href in the code, which helps with relative imports. The entire implementation is split into two parts. One is in the tag where the
graphExport
variable is declared, to allow it to be used by event handling code. This variable is then initialized in the tag, where theGraphExport
class is imported.