You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

478 lines
12 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. //@ts-check
  2. /** @typedef {import('webpack').Configuration} WebpackConfig **/
  3. /* eslint-disable import/no-dynamic-require */
  4. /* eslint-disable @typescript-eslint/ban-ts-comment */
  5. /* eslint-disable @typescript-eslint/no-var-requires */
  6. /* eslint-disable @typescript-eslint/strict-boolean-expressions */
  7. /* eslint-disable @typescript-eslint/prefer-optional-chain */
  8. 'use strict';
  9. const path = require('path');
  10. const CircularDependencyPlugin = require('circular-dependency-plugin');
  11. const { CleanWebpackPlugin: CleanPlugin } = require('clean-webpack-plugin');
  12. const CopyPlugin = require('copy-webpack-plugin');
  13. const CspHtmlPlugin = require('csp-html-webpack-plugin');
  14. const esbuild = require('esbuild');
  15. const { ESBuildMinifyPlugin } = require('esbuild-loader');
  16. const ForkTsCheckerPlugin = require('fork-ts-checker-webpack-plugin');
  17. const HtmlPlugin = require('html-webpack-plugin');
  18. const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
  19. const MiniCssExtractPlugin = require('mini-css-extract-plugin');
  20. const TerserPlugin = require('terser-webpack-plugin');
  21. const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
  22. class InlineChunkHtmlPlugin {
  23. constructor(htmlPlugin, patterns) {
  24. this.htmlPlugin = htmlPlugin;
  25. this.patterns = patterns;
  26. }
  27. getInlinedTag(publicPath, assets, tag) {
  28. if (
  29. (tag.tagName !== 'script' || !(tag.attributes && tag.attributes.src)) &&
  30. (tag.tagName !== 'link' || !(tag.attributes && tag.attributes.href))
  31. ) {
  32. return tag;
  33. }
  34. let chunkName = tag.tagName === 'link' ? tag.attributes.href : tag.attributes.src;
  35. if (publicPath) {
  36. chunkName = chunkName.replace(publicPath, '');
  37. }
  38. if (!this.patterns.some(pattern => chunkName.match(pattern))) {
  39. return tag;
  40. }
  41. const asset = assets[chunkName];
  42. if (asset == null) {
  43. return tag;
  44. }
  45. return { tagName: tag.tagName === 'link' ? 'style' : tag.tagName, innerHTML: asset.source(), closeTag: true };
  46. }
  47. apply(compiler) {
  48. let publicPath = compiler.options.output.publicPath || '';
  49. if (publicPath && !publicPath.endsWith('/')) {
  50. publicPath += '/';
  51. }
  52. compiler.hooks.compilation.tap('InlineChunkHtmlPlugin', compilation => {
  53. const getInlinedTagFn = tag => this.getInlinedTag(publicPath, compilation.assets, tag);
  54. this.htmlPlugin.getHooks(compilation).alterAssetTagGroups.tap('InlineChunkHtmlPlugin', assets => {
  55. assets.headTags = assets.headTags.map(getInlinedTagFn);
  56. assets.bodyTags = assets.bodyTags.map(getInlinedTagFn);
  57. });
  58. });
  59. }
  60. }
  61. module.exports =
  62. /**
  63. * @param {{ analyzeBundle?: boolean; analyzeDeps?: boolean; esbuild?: boolean; } | undefined } env
  64. * @param {{ mode: 'production' | 'development' | 'none' | undefined; }} argv
  65. * @returns { WebpackConfig[] }
  66. */
  67. function (env, argv) {
  68. const mode = argv.mode || 'none';
  69. env = {
  70. analyzeBundle: false,
  71. analyzeDeps: false,
  72. esbuild: true,
  73. ...env,
  74. };
  75. return [getExtensionConfig(mode, env), getWebviewsConfig(mode, env)];
  76. };
  77. /**
  78. * @param { 'production' | 'development' | 'none' } mode
  79. * @param {{ analyzeBundle?: boolean; analyzeDeps?: boolean; esbuild?: boolean; }} env
  80. * @returns { WebpackConfig }
  81. */
  82. function getExtensionConfig(mode, env) {
  83. /**
  84. * @type WebpackConfig['plugins'] | any
  85. */
  86. const plugins = [
  87. new CleanPlugin({ cleanOnceBeforeBuildPatterns: ['!webviews/**'] }),
  88. new ForkTsCheckerPlugin({
  89. async: false,
  90. eslint: { enabled: true, files: 'src/**/*.ts', options: { cache: true } },
  91. formatter: 'basic',
  92. }),
  93. ];
  94. if (env.analyzeDeps) {
  95. plugins.push(
  96. new CircularDependencyPlugin({
  97. cwd: __dirname,
  98. exclude: /node_modules/,
  99. failOnError: false,
  100. onDetected: function ({ module: _webpackModuleRecord, paths, compilation }) {
  101. if (paths.some(p => p.includes('container.ts'))) return;
  102. compilation.warnings.push(new Error(paths.join(' -> ')));
  103. },
  104. }),
  105. );
  106. }
  107. if (env.analyzeBundle) {
  108. plugins.push(new BundleAnalyzerPlugin());
  109. }
  110. return {
  111. name: 'extension',
  112. entry: './src/extension.ts',
  113. mode: mode,
  114. target: 'node',
  115. node: {
  116. __dirname: false,
  117. },
  118. devtool: 'source-map',
  119. output: {
  120. path: path.join(__dirname, 'dist'),
  121. libraryTarget: 'commonjs2',
  122. filename: 'gitlens.js',
  123. chunkFilename: 'feature-[name].js',
  124. },
  125. optimization: {
  126. minimizer: [
  127. env.esbuild
  128. ? new ESBuildMinifyPlugin({
  129. format: 'cjs',
  130. // @ts-ignore
  131. implementation: esbuild,
  132. minify: true,
  133. treeShaking: true,
  134. // Keep the class names otherwise @log won't provide a useful name
  135. keepNames: true,
  136. target: 'es2019',
  137. })
  138. : new TerserPlugin({
  139. extractComments: false,
  140. parallel: true,
  141. terserOptions: {
  142. ecma: 2019,
  143. // Keep the class names otherwise @log won't provide a useful name
  144. keep_classnames: true,
  145. module: true,
  146. },
  147. }),
  148. ],
  149. splitChunks: {
  150. cacheGroups: {
  151. defaultVendors: false,
  152. },
  153. },
  154. },
  155. externals: {
  156. vscode: 'commonjs vscode',
  157. },
  158. module: {
  159. rules: [
  160. {
  161. exclude: /\.d\.ts$/,
  162. include: path.join(__dirname, 'src'),
  163. test: /\.tsx?$/,
  164. use: env.esbuild
  165. ? {
  166. loader: 'esbuild-loader',
  167. options: {
  168. implementation: esbuild,
  169. loader: 'ts',
  170. target: 'es2019',
  171. tsconfigRaw: require('./tsconfig.json'),
  172. },
  173. }
  174. : {
  175. loader: 'ts-loader',
  176. options: {
  177. experimentalWatchApi: true,
  178. transpileOnly: true,
  179. },
  180. },
  181. },
  182. ],
  183. },
  184. resolve: {
  185. alias: {
  186. 'universal-user-agent': path.join(
  187. __dirname,
  188. 'node_modules',
  189. 'universal-user-agent',
  190. 'dist-node',
  191. 'index.js',
  192. ),
  193. },
  194. extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
  195. symlinks: false,
  196. },
  197. plugins: plugins,
  198. stats: {
  199. preset: 'errors-warnings',
  200. assets: true,
  201. colors: true,
  202. env: true,
  203. errorsCount: true,
  204. warningsCount: true,
  205. timings: true,
  206. },
  207. };
  208. }
  209. /**
  210. * @param { 'production' | 'development' | 'none' } mode
  211. * @param {{ analyzeBundle?: boolean; analyzeDeps?: boolean; esbuild?: boolean; }} env
  212. * @returns { WebpackConfig }
  213. */
  214. function getWebviewsConfig(mode, env) {
  215. const basePath = path.join(__dirname, 'src', 'webviews', 'apps');
  216. const cspHtmlPlugin = new CspHtmlPlugin(
  217. {
  218. 'default-src': "'none'",
  219. 'img-src': ['#{cspSource}', 'https:', 'data:'],
  220. 'script-src':
  221. mode !== 'production'
  222. ? ['#{cspSource}', "'nonce-#{cspNonce}'", "'unsafe-eval'"]
  223. : ['#{cspSource}', "'nonce-#{cspNonce}'"],
  224. 'style-src': ['#{cspSource}', "'nonce-#{cspNonce}'"],
  225. 'font-src': ['#{cspSource}'],
  226. },
  227. {
  228. enabled: true,
  229. hashingMethod: 'sha256',
  230. hashEnabled: {
  231. 'script-src': true,
  232. 'style-src': true,
  233. },
  234. nonceEnabled: {
  235. 'script-src': true,
  236. 'style-src': true,
  237. },
  238. },
  239. );
  240. // Override the nonce creation so we can dynamically generate them at runtime
  241. cspHtmlPlugin.createNonce = () => '#{cspNonce}';
  242. /**
  243. * @type WebpackConfig['plugins'] | any
  244. */
  245. const plugins = [
  246. new CleanPlugin(
  247. mode === 'production'
  248. ? {
  249. cleanOnceBeforeBuildPatterns: [
  250. path.posix.join(__dirname.replace(/\\/g, '/'), 'images', 'settings', '**'),
  251. ],
  252. dangerouslyAllowCleanPatternsOutsideProject: true,
  253. dry: false,
  254. }
  255. : undefined,
  256. ),
  257. new ForkTsCheckerPlugin({
  258. async: false,
  259. eslint: {
  260. enabled: true,
  261. files: path.join(basePath, '**', '*.ts'),
  262. options: { cache: true },
  263. },
  264. formatter: 'basic',
  265. typescript: {
  266. configFile: path.join(basePath, 'tsconfig.json'),
  267. },
  268. }),
  269. new MiniCssExtractPlugin({
  270. filename: '[name].css',
  271. }),
  272. new HtmlPlugin({
  273. template: 'rebase/rebase.html',
  274. chunks: ['rebase'],
  275. filename: path.join(__dirname, 'dist', 'webviews', 'rebase.html'),
  276. inject: true,
  277. inlineSource: mode === 'production' ? '.css$' : undefined,
  278. minify:
  279. mode === 'production'
  280. ? {
  281. removeComments: true,
  282. collapseWhitespace: true,
  283. removeRedundantAttributes: false,
  284. useShortDoctype: true,
  285. removeEmptyAttributes: true,
  286. removeStyleLinkTypeAttributes: true,
  287. keepClosingSlash: true,
  288. minifyCSS: true,
  289. }
  290. : false,
  291. }),
  292. new HtmlPlugin({
  293. template: 'settings/settings.html',
  294. chunks: ['settings'],
  295. filename: path.join(__dirname, 'dist', 'webviews', 'settings.html'),
  296. inject: true,
  297. inlineSource: mode === 'production' ? '.css$' : undefined,
  298. minify:
  299. mode === 'production'
  300. ? {
  301. removeComments: true,
  302. collapseWhitespace: true,
  303. removeRedundantAttributes: false,
  304. useShortDoctype: true,
  305. removeEmptyAttributes: true,
  306. removeStyleLinkTypeAttributes: true,
  307. keepClosingSlash: true,
  308. minifyCSS: true,
  309. }
  310. : false,
  311. }),
  312. new HtmlPlugin({
  313. template: 'welcome/welcome.html',
  314. chunks: ['welcome'],
  315. filename: path.join(__dirname, 'dist', 'webviews', 'welcome.html'),
  316. inject: true,
  317. inlineSource: mode === 'production' ? '.css$' : undefined,
  318. minify:
  319. mode === 'production'
  320. ? {
  321. removeComments: true,
  322. collapseWhitespace: true,
  323. removeRedundantAttributes: false,
  324. useShortDoctype: true,
  325. removeEmptyAttributes: true,
  326. removeStyleLinkTypeAttributes: true,
  327. keepClosingSlash: true,
  328. minifyCSS: true,
  329. }
  330. : false,
  331. }),
  332. cspHtmlPlugin,
  333. new InlineChunkHtmlPlugin(HtmlPlugin, mode === 'production' ? ['\\.css$'] : []),
  334. new CopyPlugin({
  335. patterns: [
  336. {
  337. from: path.posix.join(basePath.replace(/\\/g, '/'), 'images', 'settings', '*.png'),
  338. to: __dirname.replace(/\\/g, '/'),
  339. },
  340. {
  341. from: path.posix.join(
  342. __dirname.replace(/\\/g, '/'),
  343. 'node_modules',
  344. '@vscode',
  345. 'codicons',
  346. 'dist',
  347. 'codicon.ttf',
  348. ),
  349. to: path.posix.join(__dirname.replace(/\\/g, '/'), 'dist', 'webviews'),
  350. },
  351. ],
  352. }),
  353. new ImageMinimizerPlugin({
  354. test: /\.(png)$/i,
  355. filename: '[path][name].webp',
  356. loader: false,
  357. deleteOriginalAssets: true,
  358. minimizerOptions: {
  359. plugins: [
  360. [
  361. 'imagemin-webp',
  362. {
  363. lossless: true,
  364. nearLossless: 0,
  365. quality: 100,
  366. method: mode === 'production' ? 4 : 0,
  367. },
  368. ],
  369. ],
  370. },
  371. }),
  372. ];
  373. return {
  374. name: 'webviews',
  375. context: basePath,
  376. entry: {
  377. rebase: './rebase/rebase.ts',
  378. settings: './settings/settings.ts',
  379. welcome: './welcome/welcome.ts',
  380. },
  381. mode: mode,
  382. target: 'web',
  383. devtool: 'source-map',
  384. output: {
  385. filename: '[name].js',
  386. path: path.join(__dirname, 'dist', 'webviews'),
  387. publicPath: '#{root}/dist/webviews/',
  388. },
  389. module: {
  390. rules: [
  391. {
  392. exclude: /\.d\.ts$/,
  393. include: path.join(__dirname, 'src'),
  394. test: /\.tsx?$/,
  395. use: env.esbuild
  396. ? {
  397. loader: 'esbuild-loader',
  398. options: {
  399. implementation: esbuild,
  400. loader: 'ts',
  401. target: 'es2019',
  402. tsconfigRaw: require(path.join(basePath, 'tsconfig.json')),
  403. },
  404. }
  405. : {
  406. loader: 'ts-loader',
  407. options: {
  408. configFile: path.join(basePath, 'tsconfig.json'),
  409. experimentalWatchApi: true,
  410. transpileOnly: true,
  411. },
  412. },
  413. },
  414. {
  415. test: /\.scss$/,
  416. use: [
  417. {
  418. loader: MiniCssExtractPlugin.loader,
  419. },
  420. {
  421. loader: 'css-loader',
  422. options: {
  423. sourceMap: true,
  424. url: false,
  425. },
  426. },
  427. {
  428. loader: 'sass-loader',
  429. options: {
  430. sourceMap: true,
  431. },
  432. },
  433. ],
  434. exclude: /node_modules/,
  435. },
  436. ],
  437. },
  438. resolve: {
  439. extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
  440. modules: [basePath, 'node_modules'],
  441. symlinks: false,
  442. },
  443. plugins: plugins,
  444. stats: {
  445. preset: 'errors-warnings',
  446. assets: true,
  447. colors: true,
  448. env: true,
  449. errorsCount: true,
  450. warningsCount: true,
  451. timings: true,
  452. },
  453. infrastructureLogging: {
  454. level: 'log', // enables logging required for problem matchers
  455. },
  456. };
  457. }