diff --git a/package-lock.json b/package-lock.json index aa888275..8b940916 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "@types/react-datepicker": "^4.8.0", "@types/react-dom": "17.0.0", "@types/vscode": "^1.90.0", + "@types/webpack-bundle-analyzer": "^4.7.0", "@typescript-eslint/eslint-plugin": "^5.50.0", "@typescript-eslint/parser": "^5.50.0", "@vscode/l10n": "^0.0.14", @@ -96,13 +97,14 @@ "uniforms-bridge-json-schema": "^3.10.2", "uniforms-unstyled": "^3.10.2", "url-join-ts": "^1.0.5", - "vscrui": "^0.1.0-beta.1092162", + "vscrui": "^0.1.0-beta.1093954", "wc-react": "github:estruyf/wc-react", "webpack": "^5.75.0", - "webpack-bundle-analyzer": "^4.7.0", + "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "^4.10.0", "webpack-dev-server": "^4.11.1", "webpack-ignore-dynamic-require": "^1.0.0", + "webpack-manifest-plugin": "^5.0.0", "yaml": "^2.2.1", "yawn-yaml": "^1.5.0" }, @@ -2075,6 +2077,26 @@ "source-map": "^0.6.0" } }, + "node_modules/@types/webpack-bundle-analyzer": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@types/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.7.0.tgz", + "integrity": "sha512-c5i2ThslSNSG8W891BRvOd/RoCjI2zwph8maD22b1adtSns20j+0azDDMCK06DiVrzTgnwiDl5Ntmu1YRJw8Sg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "tapable": "^2.2.0", + "webpack": "^5" + } + }, + "node_modules/@types/webpack-bundle-analyzer/node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/@types/webpack-sources": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.3.tgz", @@ -11015,6 +11037,12 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -12311,9 +12339,9 @@ } }, "node_modules/vscrui": { - "version": "0.1.0-beta.1092162", - "resolved": "https://registry.npmjs.org/vscrui/-/vscrui-0.1.0-beta.1092162.tgz", - "integrity": "sha512-fEMEtNbGo96kZX+0mBDMFAFh9qMpzw6yHxEKT5/ujM6GqZ4fpqlW4GWzeA4jsdNJNPpyy/eW7uwjbPaYjXeQQw==", + "version": "0.1.0-beta.1093954", + "resolved": "https://registry.npmjs.org/vscrui/-/vscrui-0.1.0-beta.1093954.tgz", + "integrity": "sha512-ncAOeVL7b48yckb4qqXUTeHMaGmrcZrFo1Yuyq09t1qn4QPDQsWqfYTwuWupVjoEfnCVCWohj9kaGFSHz6xJ8Q==", "dev": true, "funding": { "type": "github", @@ -12415,9 +12443,9 @@ } }, "node_modules/webpack-bundle-analyzer": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.1.tgz", - "integrity": "sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==", + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", + "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", "dev": true, "dependencies": { "@discoveryjs/json-ext": "0.5.7", @@ -12428,7 +12456,6 @@ "escape-string-regexp": "^4.0.0", "gzip-size": "^6.0.0", "html-escaper": "^2.0.2", - "is-plain-object": "^5.0.0", "opener": "^1.5.2", "picocolors": "^1.0.0", "sirv": "^2.0.3", @@ -12653,6 +12680,44 @@ "integrity": "sha512-WeGFPgwDochKPwizAu5XsHcPq3aaQLl2E+n1piD/VPGNUo5HIwrtURWNMfrPDfkHVOx+flkAihXbUiILAv5x4Q==", "dev": true }, + "node_modules/webpack-manifest-plugin": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-5.0.0.tgz", + "integrity": "sha512-8RQfMAdc5Uw3QbCQ/CBV/AXqOR8mt03B6GJmRbhWopE8GzRfEpn+k0ZuWywxW+5QZsffhmFDY1J6ohqJo+eMuw==", + "dev": true, + "dependencies": { + "tapable": "^2.0.0", + "webpack-sources": "^2.2.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "peerDependencies": { + "webpack": "^5.47.0" + } + }, + "node_modules/webpack-manifest-plugin/node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-manifest-plugin/node_modules/webpack-sources": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz", + "integrity": "sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==", + "dev": true, + "dependencies": { + "source-list-map": "^2.0.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/webpack-merge": { "version": "5.10.0", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", diff --git a/package.json b/package.json index 911984bc..bc8951ad 100644 --- a/package.json +++ b/package.json @@ -2853,6 +2853,7 @@ "@types/react-datepicker": "^4.8.0", "@types/react-dom": "17.0.0", "@types/vscode": "^1.90.0", + "@types/webpack-bundle-analyzer": "^4.7.0", "@typescript-eslint/eslint-plugin": "^5.50.0", "@typescript-eslint/parser": "^5.50.0", "@vscode/l10n": "^0.0.14", @@ -2917,13 +2918,14 @@ "uniforms-bridge-json-schema": "^3.10.2", "uniforms-unstyled": "^3.10.2", "url-join-ts": "^1.0.5", - "vscrui": "^0.1.0-beta.1092162", + "vscrui": "^0.1.0-beta.1093954", "wc-react": "github:estruyf/wc-react", "webpack": "^5.75.0", - "webpack-bundle-analyzer": "^4.7.0", + "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "^4.10.0", "webpack-dev-server": "^4.11.1", "webpack-ignore-dynamic-require": "^1.0.0", + "webpack-manifest-plugin": "^5.0.0", "yaml": "^2.2.1", "yawn-yaml": "^1.5.0" }, diff --git a/src/commands/Chatbot.ts b/src/commands/Chatbot.ts index 7210dd75..629d4fa6 100644 --- a/src/commands/Chatbot.ts +++ b/src/commands/Chatbot.ts @@ -6,6 +6,7 @@ import { WebviewHelper } from '@estruyf/vscode'; import { getLocalizationFile } from '../utils/getLocalizationFile'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../localization'; +import { getWebviewJsFiles } from '../utils'; export class Chatbot { /** @@ -56,7 +57,7 @@ export class Chatbot { } }); - const dashboardFile = 'dashboardWebView.js'; + const webviewFile = 'dashboard.main.js'; const localPort = `9000`; const localServerUrl = `localhost:${localPort}`; @@ -66,7 +67,6 @@ export class Chatbot { const isProd = ext.isProductionMode; const version = ext.getVersion(); const isBeta = ext.isBetaVersion(); - const extensionUri = ext.extensionPath; const csp = [ `default-src 'none';`, @@ -82,13 +82,11 @@ export class Chatbot { }` ]; - let scriptUri = ''; + let scriptUris = []; if (isProd) { - scriptUri = webView.webview - .asWebviewUri(Uri.joinPath(extensionUri, 'dist', dashboardFile)) - .toString(); + scriptUris = await getWebviewJsFiles('dashboard', webView.webview); } else { - scriptUri = `http://${localServerUrl}/${dashboardFile}`; + scriptUris.push(`http://${localServerUrl}/${webviewFile}`); } // By default, the chatbot is seen as experimental @@ -111,7 +109,11 @@ export class Chatbot { experimental ? `data-experimental="${experimental}"` : '' } style="width:100%;height:100%;margin:0;padding:0;"> - + ${scriptUris + .map((uri) => ``) + .join('\n')} + + Daily usage `; diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index 42860f14..a6d1cdd3 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -30,7 +30,7 @@ import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../localization'; import { DashboardMessage } from '../dashboardWebView/DashboardMessage'; import { NavigationType } from '../dashboardWebView/models'; -import { getExtensibilityScripts, ignoreMsgCommand } from '../utils'; +import { getExtensibilityScripts, getWebviewJsFiles, ignoreMsgCommand } from '../utils'; export class Dashboard { private static webview: WebviewPanel | null = null; @@ -274,18 +274,17 @@ export class Dashboard { * @param webView */ private static async getWebviewContent(webView: Webview, extensionPath: Uri): Promise { - const dashboardFile = 'dashboardWebView.js'; + const webviewFile = 'dashboard.main.js'; const localPort = `9000`; const localServerUrl = `localhost:${localPort}`; - let scriptUri = ''; const isProd = Extension.getInstance().isProductionMode; + + let scriptUris = []; if (isProd) { - scriptUri = webView - .asWebviewUri(Uri.joinPath(extensionPath, 'dist', dashboardFile)) - .toString(); + scriptUris = await getWebviewJsFiles('dashboard', webView); } else { - scriptUri = `http://${localServerUrl}/${dashboardFile}`; + scriptUris.push(`http://${localServerUrl}/${webviewFile}`); } const nonce = WebviewHelper.getNonce(); @@ -351,7 +350,9 @@ export class Dashboard { }) .join('')} - + ${scriptUris + .map((uri) => ``) + .join('\n')} Daily usage diff --git a/src/commands/Preview.ts b/src/commands/Preview.ts index b4842d38..c568d017 100644 --- a/src/commands/Preview.ts +++ b/src/commands/Preview.ts @@ -29,7 +29,7 @@ import { ParsedFrontMatter } from '../parsers'; import { getLocalizationFile } from '../utils/getLocalizationFile'; import * as l10n from '@vscode/l10n'; import { LocalizationKey } from '../localization'; -import { getTitleField, joinUrl } from '../utils'; +import { getTitleField, getWebviewJsFiles, joinUrl } from '../utils'; import { i18n } from './i18n'; export class Preview { @@ -134,7 +134,7 @@ export class Preview { } }); - const dashboardFile = 'dashboardWebView.js'; + const webviewFile = 'dashboard.main.js'; const localPort = `9000`; const localServerUrl = `localhost:${localPort}`; @@ -144,7 +144,6 @@ export class Preview { const isProd = ext.isProductionMode; const version = ext.getVersion(); const isBeta = ext.isBetaVersion(); - const extensionUri = ext.extensionPath; const csp = [ `default-src 'none';`, @@ -161,13 +160,11 @@ export class Preview { `frame-src ${localhostUrl} ${cspSource} http: https:;` ]; - let scriptUri = ''; + let scriptUris = []; if (isProd) { - scriptUri = webView.webview - .asWebviewUri(Uri.joinPath(extensionUri, 'dist', dashboardFile)) - .toString(); + scriptUris = await getWebviewJsFiles('dashboard', webView.webview); } else { - scriptUri = `http://${localServerUrl}/${dashboardFile}`; + scriptUris.push(`http://${localServerUrl}/${webviewFile}`); } // Get experimental setting @@ -193,7 +190,11 @@ export class Preview { experimental ? `data-experimental="${experimental}"` : '' } style="width:100%;height:100%;margin:0;padding:0;"> - + ${scriptUris + .map((uri) => ``) + .join('\n')} + + Daily usage `; diff --git a/src/panelWebView/PanelProvider.ts b/src/panelWebView/PanelProvider.ts index 90453d5e..ec92b12b 100644 --- a/src/panelWebView/PanelProvider.ts +++ b/src/panelWebView/PanelProvider.ts @@ -28,7 +28,7 @@ import { Extension } from '../helpers/Extension'; import { Telemetry } from '../helpers/Telemetry'; import { GitListener, ModeListener } from '../listeners/general'; import { basename } from 'path'; -import { getExtensibilityScripts, ignoreMsgCommand } from '../utils'; +import { getExtensibilityScripts, getWebviewJsFiles, ignoreMsgCommand } from '../utils'; export class PanelProvider implements WebviewViewProvider, Disposable { public static readonly viewType = 'frontMatter.explorer'; @@ -89,7 +89,7 @@ export class PanelProvider implements WebviewViewProvider, Disposable { enableCommandUris: true }; - webviewView.webview.html = this.getWebviewContent(webviewView.webview); + webviewView.webview.html = await this.getWebviewContent(webviewView.webview); this.disposable = Disposable.from( webviewView.onDidDispose(() => { @@ -206,12 +206,11 @@ export class PanelProvider implements WebviewViewProvider, Disposable { * Retrieve the webview HTML contents * @param webView */ - private getWebviewContent(webView: Webview): string { + private async getWebviewContent(webView: Webview): Promise { const ext = Extension.getInstance(); - const dashboardFile = 'panelWebView.js'; + const webviewFile = 'panel.main.js'; const localPort = `9001`; const localServerUrl = `localhost:${localPort}`; - const extensionPath = ext.extensionPath; const styleVSCodeUri = webView.asWebviewUri( Uri.joinPath(this.extPath, 'assets/media', 'vscode.css') @@ -228,14 +227,12 @@ export class PanelProvider implements WebviewViewProvider, Disposable { const version = ext.getVersion(); const isBeta = ext.isBetaVersion(); - let scriptUri = ''; const isProd = Extension.getInstance().isProductionMode; + let scriptUris = []; if (isProd) { - scriptUri = webView - .asWebviewUri(Uri.joinPath(extensionPath, 'dist', dashboardFile)) - .toString(); + scriptUris = await getWebviewJsFiles('panel', webView); } else { - scriptUri = `http://${localServerUrl}/${dashboardFile}`; + scriptUris.push(`http://${localServerUrl}/${webviewFile}`); } // Get experimental setting @@ -252,7 +249,7 @@ export class PanelProvider implements WebviewViewProvider, Disposable { isProd ? `'nonce-${nonce}'` : `http://${localServerUrl} http://0.0.0.0:${localPort}` }`, `style-src ${webView.cspSource} 'self' 'unsafe-inline' https://*`, - `font-src ${webView.cspSource}`, + `font-src ${webView.cspSource} data:;`, `connect-src https://o1022172.ingest.sentry.io https://* ${ isProd ? `` @@ -285,7 +282,9 @@ export class PanelProvider implements WebviewViewProvider, Disposable { }) .join('')} - + ${scriptUris + .map((uri) => ``) + .join('\n')} Daily usage diff --git a/src/utils/getWebviewJsFiles.ts b/src/utils/getWebviewJsFiles.ts new file mode 100644 index 00000000..be87ba65 --- /dev/null +++ b/src/utils/getWebviewJsFiles.ts @@ -0,0 +1,17 @@ +import { readFileAsync } from './readFileAsync'; +import { Uri, Webview } from 'vscode'; +import { Extension } from '../helpers'; + +export const getWebviewJsFiles = async (name: string, webview: Webview) => { + const context = Extension.getInstance(); + const extensionPath = context.extensionPath; + const webviewFolder = Uri.joinPath(extensionPath, 'dist'); + const manifestPath = Uri.joinPath(webviewFolder, `${name}.manifest.json`); + const manifest = await readFileAsync(manifestPath.fsPath, 'utf8'); + const manifestJson = JSON.parse(manifest); + const entries = Object.entries(manifestJson).filter(([key]) => key.endsWith('.js')); + const files = entries.map(([_, value]) => + webview.asWebviewUri(Uri.joinPath(webviewFolder, value)).toString() + ); + return files; +}; diff --git a/src/utils/index.ts b/src/utils/index.ts index b584b09d..b7802850 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -9,6 +9,7 @@ export * from './getDescriptionField'; export * from './getExtensibilityScripts'; export * from './getLocalizationFile'; export * from './getTitleField'; +export * from './getWebviewJsFiles'; export * from './ignoreMsgCommand'; export * from './isWindows'; export * from './joinUrl'; diff --git a/webpack/dashboard.config.js b/webpack/dashboard.config.js index 8d66d882..ff65fe0d 100644 --- a/webpack/dashboard.config.js +++ b/webpack/dashboard.config.js @@ -1,5 +1,3 @@ -//@ts-check - 'use strict'; const path = require('path'); @@ -7,13 +5,15 @@ const { ProvidePlugin } = require('webpack'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; +const WebpackManifestPlugin = require('webpack-manifest-plugin').WebpackManifestPlugin; const config = [{ name: 'dashboard', target: 'web', entry: './src/dashboardWebView/index.tsx', output: { - filename: 'dashboardWebView.js', + filename: 'dashboard.[name].js', + chunkFilename: '[name].[contenthash].js', path: path.resolve(__dirname, '../dist') }, devtool: 'source-map', @@ -81,6 +81,17 @@ module.exports = (env, argv) => { reportFilename: "dashboard.html", openAnalyzer: false })); + + configItem.plugins.push(new WebpackManifestPlugin({ + publicPath: "", + fileName: "dashboard.manifest.json" + })); + + configItem.optimization = { + splitChunks: { + chunks: 'all', + }, + }; } } diff --git a/webpack/panel.config.js b/webpack/panel.config.js index 3a5ef3a0..37afa12f 100644 --- a/webpack/panel.config.js +++ b/webpack/panel.config.js @@ -1,22 +1,22 @@ -//@ts-check - 'use strict'; const path = require('path'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; +const WebpackManifestPlugin = require('webpack-manifest-plugin').WebpackManifestPlugin; const config = [{ name: 'panel', target: 'web', entry: './src/panelWebView/index.tsx', output: { - filename: 'panelWebView.js', + filename: 'panel.[name].js', + chunkFilename: '[name].[contenthash].js', path: path.resolve(__dirname, '../dist') }, devtool: 'source-map', resolve: { extensions: ['.ts', '.js', '.tsx', '.jsx'], - fallback: { + fallback: { "path": require.resolve("path-browserify") } }, @@ -73,7 +73,7 @@ const config = [{ if (error.message === "ResizeObserver loop limit exceeded") { return false; } - + return true; } } @@ -87,12 +87,23 @@ module.exports = (env, argv) => { if (argv.mode === 'production') { configItem.devtool = "hidden-source-map"; - + configItem.plugins.push(new BundleAnalyzerPlugin({ analyzerMode: 'static', reportFilename: "viewpanel.html", openAnalyzer: false })); + + configItem.plugins.push(new WebpackManifestPlugin({ + publicPath: "", + fileName: "panel.manifest.json" + })); + + configItem.optimization = { + splitChunks: { + chunks: 'all', + }, + }; } }