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.

483 lines
12 KiB

пре 3 година
пре 5 година
пре 3 година
пре 3 година
пре 5 година
пре 3 година
пре 5 година
пре 5 година
пре 5 година
пре 4 година
пре 4 година
пре 5 година
пре 5 година
пре 5 година
пре 5 година
пре 5 година
пре 5 година
пре 5 година
пре 5 година
пре 5 година
пре 5 година
пре 3 година
пре 3 година
пре 4 година
пре 5 година
пре 5 година
пре 5 година
пре 5 година
пре 3 година
пре 5 година
пре 4 година
пре 5 година
пре 5 година
пре 5 година
пре 4 година
пре 5 година
пре 4 година
пре 5 година
пре 5 година
пре 5 година
пре 5 година
пре 5 година
пре 4 година
пре 4 година
пре 5 година
пре 5 година
пре 4 година
пре 4 година
пре 5 година
пре 4 година
пре 5 година
пре 5 година
пре 5 година
пре 4 година
пре 5 година
пре 4 година
пре 5 година
пре 5 година
пре 5 година
пре 5 година
пре 4 година
пре 5 година
пре 5 година
пре 5 година
пре 5 година
пре 5 година
пре 5 година
пре 4 година
пре 5 година
пре 5 година
пре 5 година
пре 3 година
пре 5 година
пре 5 година
пре 5 година
пре 5 година
пре 5 година
пре 5 година
пре 5 година
  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 BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
  11. const { CleanWebpackPlugin: CleanPlugin } = require('clean-webpack-plugin');
  12. const CircularDependencyPlugin = require('circular-dependency-plugin');
  13. const CopyPlugin = require('copy-webpack-plugin');
  14. const CspHtmlPlugin = require('csp-html-webpack-plugin');
  15. const esbuild = require('esbuild');
  16. const { ESBuildMinifyPlugin } = require('esbuild-loader');
  17. const ForkTsCheckerPlugin = require('fork-ts-checker-webpack-plugin');
  18. const HtmlPlugin = require('html-webpack-plugin');
  19. const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
  20. const MiniCssExtractPlugin = require('mini-css-extract-plugin');
  21. const TerserPlugin = require('terser-webpack-plugin');
  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. // @ts-ignore
  128. env.esbuild
  129. ? new ESBuildMinifyPlugin({
  130. format: 'cjs',
  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 cspPolicy = {
  217. 'default-src': "'none'",
  218. 'img-src': ['#{cspSource}', 'https:', 'data:'],
  219. 'script-src': ['#{cspSource}', "'nonce-Z2l0bGVucy1ib290c3RyYXA='"],
  220. 'style-src': ['#{cspSource}', "'nonce-Z2l0bGVucy1ib290c3RyYXA='"],
  221. 'font-src': ['#{cspSource}'],
  222. };
  223. if (mode !== 'production') {
  224. cspPolicy['script-src'].push("'unsafe-eval'");
  225. }
  226. /**
  227. * @type WebpackConfig['plugins'] | any
  228. */
  229. const plugins = [
  230. new CleanPlugin(
  231. mode === 'production'
  232. ? {
  233. cleanOnceBeforeBuildPatterns: [
  234. path.posix.join(__dirname.replace(/\\/g, '/'), 'images', 'settings', '**'),
  235. ],
  236. dangerouslyAllowCleanPatternsOutsideProject: true,
  237. dry: false,
  238. }
  239. : undefined,
  240. ),
  241. new ForkTsCheckerPlugin({
  242. async: false,
  243. eslint: {
  244. enabled: true,
  245. files: path.join(basePath, '**', '*.ts'),
  246. options: { cache: true },
  247. },
  248. formatter: 'basic',
  249. typescript: {
  250. configFile: path.join(basePath, 'tsconfig.json'),
  251. },
  252. }),
  253. new MiniCssExtractPlugin({
  254. filename: '[name].css',
  255. }),
  256. new HtmlPlugin({
  257. template: 'rebase/rebase.html',
  258. chunks: ['rebase'],
  259. filename: path.join(__dirname, 'dist', 'webviews', 'rebase.html'),
  260. inject: true,
  261. inlineSource: mode === 'production' ? '.css$' : undefined,
  262. cspPlugin: {
  263. enabled: true,
  264. policy: cspPolicy,
  265. nonceEnabled: {
  266. 'script-src': true,
  267. 'style-src': true,
  268. },
  269. },
  270. minify:
  271. mode === 'production'
  272. ? {
  273. removeComments: true,
  274. collapseWhitespace: true,
  275. removeRedundantAttributes: false,
  276. useShortDoctype: true,
  277. removeEmptyAttributes: true,
  278. removeStyleLinkTypeAttributes: true,
  279. keepClosingSlash: true,
  280. minifyCSS: true,
  281. }
  282. : false,
  283. }),
  284. new HtmlPlugin({
  285. template: 'settings/settings.html',
  286. chunks: ['settings'],
  287. filename: path.join(__dirname, 'dist', 'webviews', 'settings.html'),
  288. inject: true,
  289. inlineSource: mode === 'production' ? '.css$' : undefined,
  290. cspPlugin: {
  291. enabled: true,
  292. policy: cspPolicy,
  293. nonceEnabled: {
  294. 'script-src': true,
  295. 'style-src': true,
  296. },
  297. },
  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. cspPlugin: {
  319. enabled: true,
  320. policy: cspPolicy,
  321. nonceEnabled: {
  322. 'script-src': true,
  323. 'style-src': true,
  324. },
  325. },
  326. minify:
  327. mode === 'production'
  328. ? {
  329. removeComments: true,
  330. collapseWhitespace: true,
  331. removeRedundantAttributes: false,
  332. useShortDoctype: true,
  333. removeEmptyAttributes: true,
  334. removeStyleLinkTypeAttributes: true,
  335. keepClosingSlash: true,
  336. minifyCSS: true,
  337. }
  338. : false,
  339. }),
  340. new CspHtmlPlugin(),
  341. new InlineChunkHtmlPlugin(HtmlPlugin, mode === 'production' ? ['\\.css$'] : []),
  342. new CopyPlugin({
  343. patterns: [
  344. {
  345. from: path.posix.join(basePath.replace(/\\/g, '/'), 'images', 'settings', '*.png'),
  346. to: __dirname.replace(/\\/g, '/'),
  347. },
  348. {
  349. from: path.posix.join(
  350. __dirname.replace(/\\/g, '/'),
  351. 'node_modules',
  352. 'vscode-codicons',
  353. 'dist',
  354. 'codicon.ttf',
  355. ),
  356. to: path.posix.join(__dirname.replace(/\\/g, '/'), 'dist', 'webviews'),
  357. },
  358. ],
  359. }),
  360. new ImageMinimizerPlugin({
  361. test: /\.(png)$/i,
  362. filename: '[path][name].webp',
  363. loader: false,
  364. deleteOriginalAssets: true,
  365. minimizerOptions: {
  366. plugins: [
  367. [
  368. 'imagemin-webp',
  369. {
  370. lossless: true,
  371. nearLossless: 0,
  372. quality: 100,
  373. method: mode === 'production' ? 4 : 0,
  374. },
  375. ],
  376. ],
  377. },
  378. }),
  379. ];
  380. return {
  381. name: 'webviews',
  382. context: basePath,
  383. entry: {
  384. rebase: './rebase/rebase.ts',
  385. settings: './settings/settings.ts',
  386. welcome: './welcome/welcome.ts',
  387. },
  388. mode: mode,
  389. target: 'web',
  390. devtool: 'source-map',
  391. output: {
  392. filename: '[name].js',
  393. path: path.join(__dirname, 'dist', 'webviews'),
  394. publicPath: '#{root}/dist/webviews/',
  395. },
  396. module: {
  397. rules: [
  398. {
  399. exclude: /\.d\.ts$/,
  400. include: path.join(__dirname, 'src'),
  401. test: /\.tsx?$/,
  402. use: env.esbuild
  403. ? {
  404. loader: 'esbuild-loader',
  405. options: {
  406. implementation: esbuild,
  407. loader: 'ts',
  408. target: 'es2019',
  409. tsconfigRaw: require(path.join(basePath, 'tsconfig.json')),
  410. },
  411. }
  412. : {
  413. loader: 'ts-loader',
  414. options: {
  415. configFile: path.join(basePath, 'tsconfig.json'),
  416. experimentalWatchApi: true,
  417. transpileOnly: true,
  418. },
  419. },
  420. },
  421. {
  422. test: /\.scss$/,
  423. use: [
  424. {
  425. loader: MiniCssExtractPlugin.loader,
  426. },
  427. {
  428. loader: 'css-loader',
  429. options: {
  430. sourceMap: true,
  431. url: false,
  432. },
  433. },
  434. {
  435. loader: 'sass-loader',
  436. options: {
  437. sourceMap: true,
  438. },
  439. },
  440. ],
  441. exclude: /node_modules/,
  442. },
  443. ],
  444. },
  445. resolve: {
  446. extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
  447. modules: [basePath, 'node_modules'],
  448. symlinks: false,
  449. },
  450. plugins: plugins,
  451. stats: {
  452. preset: 'errors-warnings',
  453. assets: true,
  454. colors: true,
  455. env: true,
  456. errorsCount: true,
  457. warningsCount: true,
  458. timings: true,
  459. },
  460. };
  461. }