'use strict'; /* eslint-disable @typescript-eslint/no-var-requires */ const fs = require('fs'); const path = require('path'); const glob = require('glob'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const { CleanWebpackPlugin: CleanPlugin } = require('clean-webpack-plugin'); const CircularDependencyPlugin = require('circular-dependency-plugin'); const CspHtmlPlugin = require('csp-html-webpack-plugin'); // const ESLintPlugin = require('eslint-webpack-plugin'); const ForkTsCheckerPlugin = require('fork-ts-checker-webpack-plugin'); const HtmlExcludeAssetsPlugin = require('html-webpack-exclude-assets-plugin'); const HtmlInlineSourcePlugin = require('html-webpack-inline-source-plugin'); const HtmlPlugin = require('html-webpack-plugin'); const ImageminPlugin = require('imagemin-webpack-plugin').default; const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const TerserPlugin = require('terser-webpack-plugin'); module.exports = function (env, argv) { env = env || {}; env.analyzeBundle = Boolean(env.analyzeBundle); env.analyzeDeps = Boolean(env.analyzeDeps); env.production = env.analyzeBundle || Boolean(env.production); env.optimizeImages = Boolean(env.optimizeImages) || (env.production && !env.analyzeBundle); if (!env.optimizeImages && !fs.existsSync(path.resolve(__dirname, 'images/settings'))) { env.optimizeImages = true; } return [getExtensionConfig(env), getWebviewsConfig(env)]; }; function getExtensionConfig(env) { /** * @type any[] */ const plugins = [ new CleanPlugin({ cleanOnceBeforeBuildPatterns: ['**/*', '!**/webviews/**'] }), // new ESLintPlugin({ // context: path.resolve(__dirname, 'src'), // files: '**/*.ts', // lintDirtyModulesOnly: true // }) new ForkTsCheckerPlugin({ async: false, eslint: true, useTypescriptIncrementalApi: true, }), ]; if (env.analyzeDeps) { plugins.push( new CircularDependencyPlugin({ cwd: __dirname, exclude: /node_modules/, failOnError: false, onDetected: function ({ module: webpackModuleRecord, paths, compilation }) { if (paths.some(p => p.includes('container.ts'))) return; compilation.warnings.push(new Error(paths.join(' -> '))); }, }), ); } if (env.analyzeBundle) { plugins.push(new BundleAnalyzerPlugin()); } return { name: 'extension', entry: './src/extension.ts', mode: env.production ? 'production' : 'development', target: 'node', node: { __dirname: false, }, devtool: 'source-map', output: { libraryTarget: 'commonjs2', filename: 'extension.js', chunkFilename: 'feature-[name].js', }, optimization: { minimizer: [ new TerserPlugin({ cache: true, parallel: true, sourceMap: true, terserOptions: { ecma: 8, // Keep the class names otherwise @log won't provide a useful name // eslint-disable-next-line @typescript-eslint/camelcase keep_classnames: true, module: true, }, }), ], splitChunks: { cacheGroups: { vendors: false, }, chunks: 'async', }, }, externals: { vscode: 'commonjs vscode', }, module: { rules: [ { exclude: /\.d\.ts$/, include: path.resolve(__dirname, 'src'), test: /\.tsx?$/, use: { loader: 'ts-loader', options: { experimentalWatchApi: true, transpileOnly: true, }, }, }, ], }, resolve: { alias: { 'universal-user-agent': path.resolve(__dirname, 'node_modules/universal-user-agent/dist-node/index.js'), }, extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'], symlinks: false, }, plugins: plugins, stats: { all: false, assets: true, builtAt: true, env: true, errors: true, timings: true, warnings: true, }, }; } function getWebviewsConfig(env) { const clean = ['**/*']; if (env.optimizeImages) { console.log('Optimizing images (src/webviews/apps/images/settings/*.png)...'); clean.push(path.resolve(__dirname, 'images/settings/*')); } const cspPolicy = { 'default-src': "'none'", 'img-src': ['vscode-resource:', 'https:', 'data:'], 'script-src': ['vscode-resource:', "'nonce-Z2l0bGVucy1ib290c3RyYXA='"], 'style-src': ['vscode-resource:'], }; if (!env.production) { cspPolicy['script-src'].push("'unsafe-eval'"); } /** * @type any[] */ const plugins = [ new CleanPlugin({ cleanOnceBeforeBuildPatterns: clean }), // new ESLintPlugin({ // context: path.resolve(__dirname, 'src/webviews/apps'), // files: '**/*.ts', // lintDirtyModulesOnly: true // }), new ForkTsCheckerPlugin({ tsconfig: path.resolve(__dirname, 'tsconfig.webviews.json'), async: false, eslint: true, useTypescriptIncrementalApi: true, }), new MiniCssExtractPlugin({ filename: '[name].css', }), new HtmlPlugin({ excludeAssets: [/.+-styles\.js/], excludeChunks: ['welcome'], template: 'settings/index.html', filename: path.resolve(__dirname, 'dist/webviews/settings.html'), inject: true, inlineSource: env.production ? '.css$' : undefined, cspPlugin: { enabled: true, policy: cspPolicy, nonceEnabled: { 'script-src': true, 'style-src': true, }, }, minify: env.production ? { removeComments: true, collapseWhitespace: true, removeRedundantAttributes: false, useShortDoctype: true, removeEmptyAttributes: true, removeStyleLinkTypeAttributes: true, keepClosingSlash: true, minifyCSS: true, } : false, }), new HtmlPlugin({ excludeAssets: [/.+-styles\.js/], excludeChunks: ['settings'], template: 'welcome/index.html', filename: path.resolve(__dirname, 'dist/webviews/welcome.html'), inject: true, inlineSource: env.production ? '.css$' : undefined, cspPlugin: { enabled: true, policy: cspPolicy, nonceEnabled: { 'script-src': true, 'style-src': true, }, }, minify: env.production ? { removeComments: true, collapseWhitespace: true, removeRedundantAttributes: false, useShortDoctype: true, removeEmptyAttributes: true, removeStyleLinkTypeAttributes: true, keepClosingSlash: true, minifyCSS: true, } : false, }), new HtmlExcludeAssetsPlugin(), new CspHtmlPlugin(), new ImageminPlugin({ disable: !env.optimizeImages, externalImages: { context: path.resolve(__dirname, 'src/webviews/apps/images'), sources: glob.sync('src/webviews/apps/images/settings/*.png'), destination: path.resolve(__dirname, 'images'), }, cacheFolder: path.resolve(__dirname, 'node_modules', '.cache', 'imagemin-webpack-plugin'), gifsicle: null, jpegtran: null, optipng: null, pngquant: { quality: '85-100', speed: env.production ? 1 : 10, }, svgo: null, }), new HtmlInlineSourcePlugin(), ]; return { name: 'webviews', context: path.resolve(__dirname, 'src/webviews/apps'), entry: { 'main-styles': ['./scss/main.scss'], settings: ['./settings/index.ts'], welcome: ['./welcome/index.ts'], }, mode: env.production ? 'production' : 'development', devtool: env.production ? undefined : 'eval-source-map', output: { filename: '[name].js', path: path.resolve(__dirname, 'dist/webviews'), publicPath: '#{root}/dist/webviews/', }, module: { rules: [ { exclude: /\.d\.ts$/, include: path.resolve(__dirname, 'src'), test: /\.tsx?$/, use: { loader: 'ts-loader', options: { configFile: 'tsconfig.webviews.json', experimentalWatchApi: true, transpileOnly: true, }, }, }, { test: /\.scss$/, use: [ { loader: MiniCssExtractPlugin.loader, }, { loader: 'css-loader', options: { sourceMap: true, url: false, }, }, { loader: 'sass-loader', options: { sourceMap: true, }, }, ], exclude: /node_modules/, }, ], }, resolve: { extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'], modules: [path.resolve(__dirname, 'src/webviews/apps'), 'node_modules'], symlinks: false, }, plugins: plugins, stats: { all: false, assets: true, builtAt: true, env: true, errors: true, timings: true, warnings: true, }, }; }