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.

434 lines
11 KiB

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