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.

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