Switch view to ESBuild

This switches the view code from Webpack to ESBuild to unify the build
systems for the extension and view code. There are no changes in
behavior, except that some features are now not supported (like dynamic
require/import) and importing the `classnames` package fails. However,
these were really easy to fix and don't hinder the further development
of the view code, so I've just fixed those instances.
This commit is contained in:
Koen Vlaswinkel
2023-12-22 11:28:55 +01:00
parent c210a7fdf4
commit c1a3c2f6e5
8 changed files with 82 additions and 145 deletions

View File

@@ -8,9 +8,14 @@ import {
copyWasmFiles,
} from "./typescript";
import { compileTextMateGrammar } from "./textmate";
import { compileView, watchView } from "./webpack";
import { packageExtension } from "./package";
import { injectAppInsightsKey } from "./appInsights";
import {
checkViewTypeScript,
compileViewEsbuild,
watchViewCheckTypeScript,
watchViewEsbuild,
} from "./view";
export const buildWithoutPackage = series(
cleanOutput,
@@ -19,23 +24,30 @@ export const buildWithoutPackage = series(
copyWasmFiles,
checkTypeScript,
compileTextMateGrammar,
compileView,
compileViewEsbuild,
checkViewTypeScript,
),
);
export const watch = parallel(watchEsbuild, watchCheckTypeScript, watchView);
export const watch = parallel(
watchEsbuild,
watchCheckTypeScript,
watchViewEsbuild,
watchViewCheckTypeScript,
);
export {
cleanOutput,
compileTextMateGrammar,
watchEsbuild,
watchCheckTypeScript,
watchView,
watchViewEsbuild,
compileEsbuild,
copyWasmFiles,
checkTypeScript,
injectAppInsightsKey,
compileView,
compileViewEsbuild,
checkViewTypeScript,
};
export default series(
buildWithoutPackage,

View File

@@ -4,7 +4,7 @@ import esbuild from "gulp-esbuild";
import { createProject, reporter } from "gulp-typescript";
import del from "del";
function goodReporter(): reporter.Reporter {
export function goodReporter(): reporter.Reporter {
return {
error: (error, typescript) => {
if (error.tsFile) {

View File

@@ -0,0 +1,40 @@
import { dest, src, watch } from "gulp";
import esbuild from "gulp-esbuild";
import { createProject } from "gulp-typescript";
import { goodReporter } from "./typescript";
const tsProject = createProject("src/view/tsconfig.json");
export function compileViewEsbuild() {
return src("./src/view/webview.tsx")
.pipe(
esbuild({
outfile: "webview.js",
bundle: true,
format: "iife",
platform: "browser",
target: "chrome114", // Electron 25, VS Code 1.85
jsx: "automatic",
sourcemap: "linked",
sourceRoot: "..",
loader: {
".ttf": "file",
},
}),
)
.pipe(dest("out"));
}
export function watchViewEsbuild() {
watch("src/view/**/*.ts", compileViewEsbuild);
}
export function checkViewTypeScript() {
// This doesn't actually output the TypeScript files, it just
// runs the TypeScript compiler and reports any errors.
return tsProject.src().pipe(tsProject(goodReporter()));
}
export function watchViewCheckTypeScript() {
watch("src/view/**/*.ts", checkViewTypeScript);
}

View File

@@ -1,73 +0,0 @@
import { resolve } from "path";
import * as webpack from "webpack";
import MiniCssExtractPlugin from "mini-css-extract-plugin";
import { isDevBuild } from "./dev";
export const config: webpack.Configuration = {
mode: isDevBuild ? "development" : "production",
entry: {
webview: "./src/view/webview.tsx",
},
output: {
path: resolve(__dirname, "..", "out"),
filename: "[name].js",
},
devtool: isDevBuild ? "inline-source-map" : "source-map",
resolve: {
extensions: [".js", ".ts", ".tsx", ".json"],
},
module: {
rules: [
{
test: /\.(ts|tsx)$/,
loader: "ts-loader",
options: {
configFile: "src/view/tsconfig.json",
},
},
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
importLoaders: 1,
sourceMap: true,
},
},
{
loader: "less-loader",
options: {
javascriptEnabled: true,
sourceMap: true,
},
},
],
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
sourceMap: true,
},
},
],
},
{
test: /\.(woff(2)?|ttf|eot)$/,
type: "asset/resource",
generator: {
filename: "fonts/[hash][ext][query]",
},
},
],
},
performance: {
hints: false,
},
plugins: [new MiniCssExtractPlugin()],
};

View File

@@ -1,57 +0,0 @@
import { Configuration, Stats, webpack } from "webpack";
import { config } from "./webpack.config";
export function compileView(cb: (err?: Error) => void) {
doWebpack(config, true, cb);
}
export function watchView(cb: (err?: Error) => void) {
const watchConfig = {
...config,
watch: true,
watchOptions: {
aggregateTimeout: 200,
poll: 1000,
},
};
doWebpack(watchConfig, false, cb);
}
function doWebpack(
internalConfig: Configuration,
failOnError: boolean,
cb: (err?: Error) => void,
) {
const resultCb = (error: Error | undefined, stats?: Stats) => {
if (error) {
cb(error);
}
if (stats) {
console.log(
stats.toString({
errorDetails: true,
colors: true,
assets: false,
builtAt: false,
version: false,
hash: false,
entrypoints: false,
timings: false,
modules: false,
errors: true,
}),
);
if (stats.hasErrors()) {
if (failOnError) {
cb(new Error("Compilation errors detected."));
return;
} else {
console.error("Compilation errors detected.");
}
}
cb();
}
};
webpack(internalConfig, resultCb);
}

View File

@@ -1,6 +1,5 @@
import * as React from "react";
import { styled } from "styled-components";
import * as classNames from "classnames";
type Props = {
name: string;
@@ -18,7 +17,7 @@ export const Codicon = ({ name, label, className, slot }: Props) => (
role="img"
aria-label={label}
title={label}
className={classNames("codicon", `codicon-${name}`, className)}
className={`codicon codicon-${name}${className ? ` ${className}` : ""}`}
slot={slot}
/>
);

View File

@@ -1,6 +1,5 @@
import * as React from "react";
import { Codicon } from "./Codicon";
import * as classNames from "classnames";
type Props = {
label?: string;
@@ -11,6 +10,6 @@ export const LoadingIcon = ({ label = "Loading...", className }: Props) => (
<Codicon
name="loading"
label={label}
className={classNames(className, "codicon-modifier-spin")}
className={`${className ? `${className} ` : ""}codicon-modifier-spin`}
/>
);

View File

@@ -3,11 +3,27 @@ import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { vscode } from "./vscode-api";
import { registerUnhandledErrorListener } from "./common/errors";
import { WebviewDefinition } from "./webview-definition";
import compareView from "./compare";
import dataFlowPathsView from "./data-flow-paths";
import methodModelingView from "./method-modeling";
import modelEditorView from "./model-editor";
import resultsView from "./results";
import variantAnalysisView from "./variant-analysis";
// Allow all views to use Codicons
import "@vscode/codicons/dist/codicon.css";
import { registerUnhandledErrorListener } from "./common/errors";
const views: Record<string, WebviewDefinition> = {
compare: compareView,
"data-flow-paths": dataFlowPathsView,
"method-modeling": methodModelingView,
"model-editor": modelEditorView,
results: resultsView,
"variant-analysis": variantAnalysisView,
};
const render = () => {
registerUnhandledErrorListener();
@@ -25,10 +41,11 @@ const render = () => {
return;
}
// It's a lot harder to use dynamic imports since those don't import the CSS
// and require a less strict CSP policy
// eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-dynamic-require
const view: WebviewDefinition = require(`./${viewName}/index.tsx`).default;
const view: WebviewDefinition = views[viewName];
if (!view) {
console.error(`Could not find view with name "${viewName}"`);
return;
}
const root = createRoot(element);
root.render(