Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

485 строки
12 KiB

5 лет назад
5 лет назад
5 лет назад
5 лет назад
4 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
4 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
4 лет назад
5 лет назад
5 лет назад
4 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
5 лет назад
  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 path = require('path');
  9. const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
  10. const { CleanWebpackPlugin: CleanPlugin } = require('clean-webpack-plugin');
  11. const CircularDependencyPlugin = require('circular-dependency-plugin');
  12. const CopyPlugin = require('copy-webpack-plugin');
  13. const CspHtmlPlugin = require('csp-html-webpack-plugin');
  14. const { ESBuildPlugin, ESBuildMinifyPlugin } = require('esbuild-loader');
  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 MiniCssExtractPlugin = require('mini-css-extract-plugin');
  19. const TerserPlugin = require('terser-webpack-plugin');
  20. class InlineChunkHtmlPlugin {
  21. constructor(htmlPlugin, patterns) {
  22. this.htmlPlugin = htmlPlugin;
  23. this.patterns = patterns;
  24. }
  25. getInlinedTag(publicPath, assets, tag) {
  26. if (
  27. (tag.tagName !== 'script' || !(tag.attributes && tag.attributes.src)) &&
  28. (tag.tagName !== 'link' || !(tag.attributes && tag.attributes.href))
  29. ) {
  30. return tag;
  31. }
  32. let chunkName = tag.tagName === 'link' ? tag.attributes.href : tag.attributes.src;
  33. if (publicPath) {
  34. chunkName = chunkName.replace(publicPath, '');
  35. }
  36. if (!this.patterns.some(pattern => chunkName.match(pattern))) {
  37. return tag;
  38. }
  39. const asset = assets[chunkName];
  40. if (asset == null) {
  41. return tag;
  42. }
  43. return { tagName: tag.tagName === 'link' ? 'style' : tag.tagName, innerHTML: asset.source(), closeTag: true };
  44. }
  45. apply(compiler) {
  46. let publicPath = compiler.options.output.publicPath || '';
  47. if (publicPath && !publicPath.endsWith('/')) {
  48. publicPath += '/';
  49. }
  50. compiler.hooks.compilation.tap('InlineChunkHtmlPlugin', compilation => {
  51. const getInlinedTagFn = tag => this.getInlinedTag(publicPath, compilation.assets, tag);
  52. this.htmlPlugin.getHooks(compilation).alterAssetTagGroups.tap('InlineChunkHtmlPlugin', assets => {
  53. assets.headTags = assets.headTags.map(getInlinedTagFn);
  54. assets.bodyTags = assets.bodyTags.map(getInlinedTagFn);
  55. });
  56. });
  57. }
  58. }
  59. module.exports =
  60. /**
  61. * @param {{ analyzeBundle?: boolean; analyzeDeps?: boolean; esbuild?: boolean; } | undefined } env
  62. * @param {{ mode: 'production' | 'development' | 'none' | undefined; }} argv
  63. * @returns { WebpackConfig[] }
  64. */
  65. function (env, argv) {
  66. const mode = argv.mode || 'none';
  67. env = {
  68. analyzeBundle: false,
  69. analyzeDeps: false,
  70. ...env,
  71. };
  72. return [getExtensionConfig(mode, env), getWebviewsConfig(mode, env)];
  73. };
  74. /**
  75. * @param { 'production' | 'development' | 'none' } mode
  76. * @param {{ analyzeBundle?: boolean; analyzeDeps?: boolean; esbuild?: boolean; }} env
  77. * @returns { WebpackConfig }
  78. */
  79. function getExtensionConfig(mode, env) {
  80. /**
  81. * @type WebpackConfig['plugins'] | any
  82. */
  83. const plugins = [
  84. new CleanPlugin({ cleanOnceBeforeBuildPatterns: ['!webviews/**'] }),
  85. new ForkTsCheckerPlugin({
  86. async: false,
  87. eslint: { enabled: true, files: 'src/**/*.ts', options: { cache: true } },
  88. formatter: 'basic',
  89. }),
  90. ];
  91. if (env.esbuild) {
  92. plugins.push(new ESBuildPlugin());
  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. minify: true,
  132. treeShaking: true,
  133. // Keep the class names otherwise @log won't provide a useful name
  134. keepNames: true,
  135. target: 'es2019',
  136. })
  137. : new TerserPlugin({
  138. parallel: true,
  139. terserOptions: {
  140. ecma: 2019,
  141. // Keep the class names otherwise @log won't provide a useful name
  142. keep_classnames: true,
  143. module: true,
  144. },
  145. }),
  146. ],
  147. splitChunks: {
  148. cacheGroups: {
  149. defaultVendors: false,
  150. },
  151. },
  152. },
  153. externals: {
  154. vscode: 'commonjs vscode',
  155. },
  156. module: {
  157. rules: [
  158. {
  159. exclude: /\.d\.ts$/,
  160. include: path.join(__dirname, 'src'),
  161. test: /\.tsx?$/,
  162. use: env.esbuild
  163. ? {
  164. loader: 'esbuild-loader',
  165. options: {
  166. loader: 'ts',
  167. target: 'es2019',
  168. tsconfigRaw: require('./tsconfig.json'),
  169. },
  170. }
  171. : {
  172. loader: 'ts-loader',
  173. options: {
  174. experimentalWatchApi: true,
  175. transpileOnly: true,
  176. },
  177. },
  178. },
  179. ],
  180. },
  181. resolve: {
  182. alias: {
  183. 'universal-user-agent': path.join(
  184. __dirname,
  185. 'node_modules',
  186. 'universal-user-agent',
  187. 'dist-node',
  188. 'index.js',
  189. ),
  190. },
  191. extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
  192. symlinks: false,
  193. },
  194. plugins: plugins,
  195. stats: {
  196. preset: 'errors-warnings',
  197. assets: true,
  198. colors: true,
  199. env: true,
  200. errorsCount: true,
  201. warningsCount: true,
  202. timings: true,
  203. },
  204. };
  205. }
  206. /**
  207. * @param { 'production' | 'development' | 'none' } mode
  208. * @param {{ analyzeBundle?: boolean; analyzeDeps?: boolean; esbuild?: boolean; }} env
  209. * @returns { WebpackConfig }
  210. */
  211. function getWebviewsConfig(mode, env) {
  212. const basePath = path.join(__dirname, 'src', 'webviews', 'apps');
  213. const cspPolicy = {
  214. 'default-src': "'none'",
  215. 'img-src': ['#{cspSource}', 'https:', 'data:'],
  216. 'script-src': ['#{cspSource}', "'nonce-Z2l0bGVucy1ib290c3RyYXA='"],
  217. 'style-src': ['#{cspSource}', "'nonce-Z2l0bGVucy1ib290c3RyYXA='"],
  218. 'font-src': ['#{cspSource}'],
  219. };
  220. if (mode !== 'production') {
  221. cspPolicy['script-src'].push("'unsafe-eval'");
  222. }
  223. /**
  224. * @type WebpackConfig['plugins'] | any
  225. */
  226. const plugins = [
  227. new CleanPlugin(
  228. mode === 'production'
  229. ? {
  230. cleanOnceBeforeBuildPatterns: [
  231. path.posix.join(__dirname.replace(/\\/g, '/'), 'images', 'settings', '**'),
  232. ],
  233. dangerouslyAllowCleanPatternsOutsideProject: true,
  234. dry: false,
  235. }
  236. : undefined,
  237. ),
  238. new ForkTsCheckerPlugin({
  239. async: false,
  240. eslint: {
  241. enabled: true,
  242. files: path.join(basePath, '**', '*.ts'),
  243. options: { cache: true },
  244. },
  245. formatter: 'basic',
  246. typescript: {
  247. configFile: path.join(basePath, 'tsconfig.json'),
  248. },
  249. }),
  250. new MiniCssExtractPlugin({
  251. filename: '[name].css',
  252. }),
  253. new HtmlPlugin({
  254. template: 'rebase/rebase.html',
  255. chunks: ['rebase'],
  256. filename: path.join(__dirname, 'dist', 'webviews', 'rebase.html'),
  257. inject: true,
  258. inlineSource: mode === 'production' ? '.css$' : undefined,
  259. cspPlugin: {
  260. enabled: true,
  261. policy: cspPolicy,
  262. nonceEnabled: {
  263. 'script-src': true,
  264. 'style-src': true,
  265. },
  266. },
  267. minify:
  268. mode === 'production'
  269. ? {
  270. removeComments: true,
  271. collapseWhitespace: true,
  272. removeRedundantAttributes: false,
  273. useShortDoctype: true,
  274. removeEmptyAttributes: true,
  275. removeStyleLinkTypeAttributes: true,
  276. keepClosingSlash: true,
  277. minifyCSS: true,
  278. }
  279. : false,
  280. }),
  281. new HtmlPlugin({
  282. template: 'settings/settings.html',
  283. chunks: ['settings'],
  284. filename: path.join(__dirname, 'dist', 'webviews', 'settings.html'),
  285. inject: true,
  286. inlineSource: mode === 'production' ? '.css$' : undefined,
  287. cspPlugin: {
  288. enabled: true,
  289. policy: cspPolicy,
  290. nonceEnabled: {
  291. 'script-src': true,
  292. 'style-src': true,
  293. },
  294. },
  295. minify:
  296. mode === 'production'
  297. ? {
  298. removeComments: true,
  299. collapseWhitespace: true,
  300. removeRedundantAttributes: false,
  301. useShortDoctype: true,
  302. removeEmptyAttributes: true,
  303. removeStyleLinkTypeAttributes: true,
  304. keepClosingSlash: true,
  305. minifyCSS: true,
  306. }
  307. : false,
  308. }),
  309. new HtmlPlugin({
  310. template: 'welcome/welcome.html',
  311. chunks: ['welcome'],
  312. filename: path.join(__dirname, 'dist', 'webviews', 'welcome.html'),
  313. inject: true,
  314. inlineSource: mode === 'production' ? '.css$' : undefined,
  315. cspPlugin: {
  316. enabled: true,
  317. policy: cspPolicy,
  318. nonceEnabled: {
  319. 'script-src': true,
  320. 'style-src': true,
  321. },
  322. },
  323. minify:
  324. mode === 'production'
  325. ? {
  326. removeComments: true,
  327. collapseWhitespace: true,
  328. removeRedundantAttributes: false,
  329. useShortDoctype: true,
  330. removeEmptyAttributes: true,
  331. removeStyleLinkTypeAttributes: true,
  332. keepClosingSlash: true,
  333. minifyCSS: true,
  334. }
  335. : false,
  336. }),
  337. new CspHtmlPlugin(),
  338. new InlineChunkHtmlPlugin(HtmlPlugin, mode === 'production' ? ['\\.css$'] : []),
  339. new CopyPlugin({
  340. patterns: [
  341. {
  342. from: path.posix.join(basePath.replace(/\\/g, '/'), 'images', 'settings', '*.png'),
  343. to: __dirname.replace(/\\/g, '/'),
  344. },
  345. {
  346. from: path.posix.join(
  347. __dirname.replace(/\\/g, '/'),
  348. 'node_modules',
  349. 'vscode-codicons',
  350. 'dist',
  351. 'codicon.ttf',
  352. ),
  353. to: path.posix.join(__dirname.replace(/\\/g, '/'), 'dist', 'webviews'),
  354. },
  355. ],
  356. }),
  357. new ImageMinimizerPlugin({
  358. test: /\.(png)$/i,
  359. filename: '[path][name].webp',
  360. loader: false,
  361. deleteOriginalAssets: true,
  362. minimizerOptions: {
  363. plugins: [
  364. [
  365. 'imagemin-webp',
  366. {
  367. lossless: true,
  368. nearLossless: 0,
  369. quality: 100,
  370. method: mode === 'production' ? 4 : 0,
  371. },
  372. ],
  373. ],
  374. },
  375. }),
  376. ];
  377. if (env.esbuild) {
  378. plugins.push(new ESBuildPlugin());
  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. loader: 'ts',
  407. target: 'es2019',
  408. // eslint-disable-next-line import/no-dynamic-require
  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. }