I created a VS Code extension which uses a Webview. In the Webview I have a link to a file in the node_modules
folder which I added to the html as recommended by various sources (e.g. vscode-codicons-sample):
const codiconsUri = webview.asWebviewUri(
Uri.joinPath(this.context.extensionUri, 'node_modules', '@vscode/codicons', 'dist', 'codicon.css')
);
and in the html:
<link href="${codiconsUri}" rel="stylesheet" />
Now I'd like to use webpack for bundling. I followed the instructions from Visual Studio Code here.
In the .vscodeignore
file I exclude the node_modules
folder as instructed. (That's the main reason for bundling)
And when I package the project now, of course, the node_modules
is not present in the .vsix file and thus the Uri is invalid.
How would you solve that? (I use this method to insert many more links besides codicons)
Thanks in advance!
CodePudding user response:
I decided to go with @MikeLischke's suggestion and copy the required files into the dist
folder, which is packaged. But I didn't want to do it manually, so I did the following.
NodeModulesAccessor
Firstly, I created a class which does the mapping between the files in the node_modules
folder and the destination in the packaged dist
folder.
import { NodeModulesKeys } from './nodeModulesKeys';
import { NodeModulesValue } from './nodeModulesValue';
export class NodeModulesAccessor {
static readonly outputPath = 'dist';
private static readonly pathMapping = new Map<NodeModulesKeys, NodeModulesValue>([
[
NodeModulesKeys.ffmpegMinJs,
{
sourcePath: ['node_modules', '@ffmpeg', 'ffmpeg', 'dist'],
destinationPath: ['libs', '@ffmpeg', 'ffmpeg', 'dist'],
fileName: 'ffmpeg.min.js',
},
],
[
NodeModulesKeys.ffmpegCoreJs,
{
sourcePath: ['node_modules', '@ffmpeg', 'core', 'dist'],
destinationPath: ['libs', '@ffmpeg', 'core', 'dist'],
fileName: 'ffmpeg-core.js',
includeFolder: true,
},
],
[
NodeModulesKeys.codiconCss,
{
sourcePath: ['node_modules', '@vscode', 'codicons', 'dist'],
destinationPath: ['libs', '@vscode', 'codicons', 'dist'],
fileName: 'codicon.css',
includeFolder: true,
},
],
]);
static getPathToOutputFile(key: NodeModulesKeys): string[] {
const path = this.getMappedValue(key);
return [this.outputPath, ...path.destinationPath, path.fileName];
}
static getPathToNodeModulesFile(key: NodeModulesKeys): NodeModulesValue {
return this.getMappedValue(key);
}
private static getMappedValue(key: NodeModulesKeys): NodeModulesValue {
const value = this.pathMapping.get(key);
if (!value) {
throw Error(`Path to "${key}" is not mapped.`);
}
return value;
}
}
NodeModulesKeys
is a simple enum of all the files I want to use:
export enum NodeModulesKeys {
ffmpegMinJs,
ffmpegCoreJs,
codiconCss,
}
and NodeModulesValue
is an interface:
export interface NodeModulesValue {
sourcePath: string[];
destinationPath: string[];
fileName: string;
includeFolder?: boolean;
}
Some libraries (e.g. codicons) require multiple files inside the folder. That is why NodeModulesValue
has an optional field includeFolder
.
webpack.config.ts
Here is where the magic happens (don't worry, it's not that complicated).
You can use the CopyWebpackPlugin to copy files while bundling:
import * as path from 'path';
import { NodeModulesAccessor } from './src/node-modules-accessor/nodeModulesAccessor';
import { NodeModulesKeys } from './src/node-modules-accessor/nodeModulesKeys';
import { Configuration } from 'webpack';
import CopyPlugin = require('copy-webpack-plugin');
const config: Configuration = {
// omitted, nothing special here
plugins: [copyNodeModulesFiles()],
};
function copyNodeModulesFiles(): CopyPlugin {
const files: NodeModulesKeys[] = Object.keys(NodeModulesKeys)
.filter((key) => !isNaN(Number(key)))
.map((key) => Number(key));
const copies: CopyPlugin.ObjectPattern[] = files.map((file) => {
const value = NodeModulesAccessor.getPathToNodeModulesFile(file);
let sourcePath;
let destinationPath;
if (value.includeFolder) {
sourcePath = path.join(...value.sourcePath);
destinationPath = path.join(...value.destinationPath);
} else {
sourcePath = path.join(...value.sourcePath, value.fileName);
destinationPath = path.join(...value.destinationPath, value.fileName);
}
return {
from: sourcePath,
to: destinationPath,
};
});
return new CopyPlugin({
patterns: copies,
});
}
module.exports = config;
Here we iterate over all the values of the NodeModulesKeys
enum. (How to iterate over enum values) and add a copy instruction for each of them.
We can also copy entire folders if we need to.
Webview integration
To get the Uris and use them in the webview html, we use the NodeModulesAccessor
again.
const codiconsUri = webview.asWebviewUri(
Uri.joinPath(this.context.extensionUri, ...NodeModulesAccessor.getPathToOutputFile(NodeModulesKeys.codiconCss))
);
Important
For the webview to be able to access the dist/libs
directory, you have define the directory as a localResourceRoot
when you create the webview:
this.viewPanel = window.createWebviewPanel('sampleId', 'Sample Webview', ViewColumn.Beside, {
enableScripts: true,
retainContextWhenHidden: true,
localResourceRoots: [
Uri.joinPath(this.context.extensionUri, NodeModulesAccessor.outputPath, 'libs'), // <--- Important
Uri.joinPath(this.context.extensionUri, 'media'),
],
});
I hope that will come in handy for someone else.