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.

442 lines
11 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
5 years ago
5 years ago
  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 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 ForkTsCheckerPlugin = require('fork-ts-checker-webpack-plugin');
  15. const HtmlPlugin = require('html-webpack-plugin');
  16. const HtmlSkipAssetsPlugin = require('html-webpack-skip-assets-plugin').HtmlWebpackSkipAssetsPlugin;
  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; } | 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; }} 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.analyzeDeps) {
  92. plugins.push(
  93. new CircularDependencyPlugin({
  94. cwd: __dirname,
  95. exclude: /node_modules/,
  96. failOnError: false,
  97. onDetected: function ({ module: _webpackModuleRecord, paths, compilation }) {
  98. if (paths.some(p => p.includes('container.ts'))) return;
  99. compilation.warnings.push(new Error(paths.join(' -> ')));
  100. },
  101. }),
  102. );
  103. }
  104. if (env.analyzeBundle) {
  105. plugins.push(new BundleAnalyzerPlugin());
  106. }
  107. return {
  108. name: 'extension',
  109. entry: './src/extension.ts',
  110. mode: mode,
  111. target: 'node',
  112. node: {
  113. __dirname: false,
  114. },
  115. devtool: 'source-map',
  116. output: {
  117. path: path.join(__dirname, 'dist'),
  118. libraryTarget: 'commonjs2',
  119. filename: 'gitlens.js',
  120. chunkFilename: 'feature-[name].js',
  121. },
  122. optimization: {
  123. minimizer: [
  124. // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  125. // @ts-ignore
  126. new TerserPlugin({
  127. parallel: true,
  128. terserOptions: {
  129. ecma: 2019,
  130. // Keep the class names otherwise @log won't provide a useful name
  131. keep_classnames: true,
  132. module: true,
  133. },
  134. }),
  135. ],
  136. splitChunks: {
  137. cacheGroups: {
  138. defaultVendors: false,
  139. },
  140. },
  141. },
  142. externals: {
  143. vscode: 'commonjs vscode',
  144. },
  145. module: {
  146. rules: [
  147. {
  148. exclude: /\.d\.ts$/,
  149. include: path.join(__dirname, 'src'),
  150. test: /\.tsx?$/,
  151. use: {
  152. loader: 'ts-loader',
  153. options: {
  154. experimentalWatchApi: true,
  155. transpileOnly: true,
  156. },
  157. },
  158. },
  159. ],
  160. },
  161. resolve: {
  162. alias: {
  163. 'universal-user-agent': path.join(
  164. __dirname,
  165. 'node_modules',
  166. 'universal-user-agent',
  167. 'dist-node',
  168. 'index.js',
  169. ),
  170. },
  171. extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
  172. symlinks: false,
  173. },
  174. plugins: plugins,
  175. stats: {
  176. preset: 'errors-warnings',
  177. assets: true,
  178. colors: true,
  179. env: true,
  180. errorsCount: true,
  181. warningsCount: true,
  182. timings: true,
  183. },
  184. };
  185. }
  186. /**
  187. * @param { 'production' | 'development' | 'none' } mode
  188. * @param {{ analyzeBundle?: boolean; analyzeDeps?: boolean; }} _env
  189. * @returns { WebpackConfig }
  190. */
  191. function getWebviewsConfig(mode, _env) {
  192. const basePath = path.join(__dirname, 'src', 'webviews', 'apps');
  193. const cspPolicy = {
  194. 'default-src': "'none'",
  195. 'img-src': ['#{cspSource}', 'https:', 'data:'],
  196. 'script-src': ['#{cspSource}', "'nonce-Z2l0bGVucy1ib290c3RyYXA='"],
  197. 'style-src': ['#{cspSource}', "'nonce-Z2l0bGVucy1ib290c3RyYXA='"],
  198. 'font-src': ['#{cspSource}'],
  199. };
  200. if (mode !== 'production') {
  201. cspPolicy['script-src'].push("'unsafe-eval'");
  202. }
  203. /**
  204. * @type WebpackConfig['plugins'] | any
  205. */
  206. const plugins = [
  207. new CleanPlugin(),
  208. new ForkTsCheckerPlugin({
  209. async: false,
  210. eslint: {
  211. enabled: true,
  212. files: path.join(basePath, '**', '*.ts'),
  213. options: { cache: true },
  214. },
  215. formatter: 'basic',
  216. typescript: {
  217. configFile: path.join(basePath, 'tsconfig.json'),
  218. },
  219. }),
  220. new MiniCssExtractPlugin({
  221. filename: '[name].css',
  222. }),
  223. new HtmlPlugin({
  224. template: 'rebase/rebase.html',
  225. chunks: ['rebase'],
  226. filename: path.join(__dirname, 'dist', 'webviews', 'rebase.html'),
  227. inject: true,
  228. inlineSource: mode === 'production' ? '.css$' : undefined,
  229. cspPlugin: {
  230. enabled: true,
  231. policy: cspPolicy,
  232. nonceEnabled: {
  233. 'script-src': true,
  234. 'style-src': true,
  235. },
  236. },
  237. minify:
  238. mode === 'production'
  239. ? {
  240. removeComments: true,
  241. collapseWhitespace: true,
  242. removeRedundantAttributes: false,
  243. useShortDoctype: true,
  244. removeEmptyAttributes: true,
  245. removeStyleLinkTypeAttributes: true,
  246. keepClosingSlash: true,
  247. minifyCSS: true,
  248. }
  249. : false,
  250. }),
  251. new HtmlPlugin({
  252. template: 'settings/settings.html',
  253. chunks: ['settings'],
  254. filename: path.join(__dirname, 'dist', 'webviews', 'settings.html'),
  255. inject: true,
  256. inlineSource: mode === 'production' ? '.css$' : undefined,
  257. cspPlugin: {
  258. enabled: true,
  259. policy: cspPolicy,
  260. nonceEnabled: {
  261. 'script-src': true,
  262. 'style-src': true,
  263. },
  264. },
  265. minify:
  266. mode === 'production'
  267. ? {
  268. removeComments: true,
  269. collapseWhitespace: true,
  270. removeRedundantAttributes: false,
  271. useShortDoctype: true,
  272. removeEmptyAttributes: true,
  273. removeStyleLinkTypeAttributes: true,
  274. keepClosingSlash: true,
  275. minifyCSS: true,
  276. }
  277. : false,
  278. }),
  279. new HtmlPlugin({
  280. template: 'welcome/welcome.html',
  281. chunks: ['welcome'],
  282. filename: path.join(__dirname, 'dist', 'webviews', 'welcome.html'),
  283. inject: true,
  284. inlineSource: mode === 'production' ? '.css$' : undefined,
  285. cspPlugin: {
  286. enabled: true,
  287. policy: cspPolicy,
  288. nonceEnabled: {
  289. 'script-src': true,
  290. 'style-src': true,
  291. },
  292. },
  293. minify:
  294. mode === 'production'
  295. ? {
  296. removeComments: true,
  297. collapseWhitespace: true,
  298. removeRedundantAttributes: false,
  299. useShortDoctype: true,
  300. removeEmptyAttributes: true,
  301. removeStyleLinkTypeAttributes: true,
  302. keepClosingSlash: true,
  303. minifyCSS: true,
  304. }
  305. : false,
  306. }),
  307. new HtmlSkipAssetsPlugin({}),
  308. new CspHtmlPlugin(),
  309. new InlineChunkHtmlPlugin(HtmlPlugin, mode === 'production' ? ['\\.css$'] : []),
  310. new CopyPlugin({
  311. patterns: [
  312. {
  313. from: path.posix.join(basePath.replace(/\\/g, '/'), 'images', 'settings', '*.png'),
  314. to: __dirname.replace(/\\/g, '/'),
  315. },
  316. {
  317. from: path.posix.join(
  318. __dirname.replace(/\\/g, '/'),
  319. 'node_modules',
  320. 'vscode-codicons',
  321. 'dist',
  322. 'codicon.ttf',
  323. ),
  324. to: path.posix.join(__dirname.replace(/\\/g, '/'), 'dist', 'webviews'),
  325. },
  326. ],
  327. }),
  328. new ImageMinimizerPlugin({
  329. test: /\.(png)$/i,
  330. filename: '[path][name].webp',
  331. cache: true,
  332. loader: false,
  333. deleteOriginalAssets: true,
  334. minimizerOptions: {
  335. plugins: [
  336. [
  337. 'imagemin-webp',
  338. {
  339. lossless: true,
  340. nearLossless: mode === 'production' ? 0 : 100,
  341. quality: 100,
  342. method: mode === 'production' ? 6 : 0,
  343. },
  344. ],
  345. ],
  346. },
  347. }),
  348. ];
  349. return {
  350. name: 'webviews',
  351. context: basePath,
  352. entry: {
  353. rebase: './rebase/rebase.ts',
  354. settings: './settings/settings.ts',
  355. welcome: './welcome/welcome.ts',
  356. },
  357. mode: mode,
  358. target: 'web',
  359. devtool: 'source-map',
  360. output: {
  361. filename: '[name].js',
  362. path: path.join(__dirname, 'dist', 'webviews'),
  363. publicPath: '#{root}/dist/webviews/',
  364. },
  365. module: {
  366. rules: [
  367. {
  368. exclude: /\.d\.ts$/,
  369. include: path.join(__dirname, 'src'),
  370. test: /\.tsx?$/,
  371. use: {
  372. loader: 'ts-loader',
  373. options: {
  374. configFile: path.join(basePath, 'tsconfig.json'),
  375. experimentalWatchApi: true,
  376. transpileOnly: true,
  377. },
  378. },
  379. },
  380. {
  381. test: /\.scss$/,
  382. use: [
  383. {
  384. loader: MiniCssExtractPlugin.loader,
  385. },
  386. {
  387. loader: 'css-loader',
  388. options: {
  389. sourceMap: true,
  390. url: false,
  391. },
  392. },
  393. {
  394. loader: 'sass-loader',
  395. options: {
  396. sourceMap: true,
  397. },
  398. },
  399. ],
  400. exclude: /node_modules/,
  401. },
  402. ],
  403. },
  404. resolve: {
  405. extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
  406. modules: [basePath, 'node_modules'],
  407. symlinks: false,
  408. },
  409. plugins: plugins,
  410. stats: {
  411. preset: 'errors-warnings',
  412. assets: true,
  413. colors: true,
  414. env: true,
  415. errorsCount: true,
  416. warningsCount: true,
  417. timings: true,
  418. },
  419. };
  420. }