Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

565 linhas
14 KiB

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