Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

397 рядки
10 KiB

5 роки тому
5 роки тому
5 роки тому
4 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
5 роки тому
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 роки тому
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/no-var-requires */
  4. /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
  5. /* eslint-disable @typescript-eslint/strict-boolean-expressions */
  6. 'use strict';
  7. const fs = require('fs');
  8. const path = require('path');
  9. const glob = require('glob');
  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 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 ImageminPlugin = require('imagemin-webpack-plugin').default;
  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; optimizeImages?: 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. optimizeImages: mode === 'production',
  71. ...env,
  72. };
  73. if (env.analyzeBundle || env.analyzeDeps) {
  74. env.optimizeImages = false;
  75. } else if (!env.optimizeImages && !fs.existsSync(path.resolve(__dirname, 'images/settings'))) {
  76. env.optimizeImages = true;
  77. }
  78. return [getExtensionConfig(mode, env), getWebviewsConfig(mode, env)];
  79. };
  80. /**
  81. * @param { 'production' | 'development' | 'none' } mode
  82. * @param {{ analyzeBundle?: boolean; analyzeDeps?: boolean; optimizeImages?: boolean; }} env
  83. * @returns { WebpackConfig }
  84. */
  85. function getExtensionConfig(mode, env) {
  86. /**
  87. * @type WebpackConfig['plugins']
  88. */
  89. const plugins = [
  90. new CleanPlugin({ cleanOnceBeforeBuildPatterns: ['**/*', '!**/webviews/**'] }),
  91. new ForkTsCheckerPlugin({
  92. async: false,
  93. eslint: { enabled: true, files: 'src/**/*.ts', options: { cache: true } },
  94. formatter: 'basic',
  95. }),
  96. ];
  97. if (env.analyzeDeps) {
  98. plugins.push(
  99. new CircularDependencyPlugin({
  100. cwd: __dirname,
  101. exclude: /node_modules/,
  102. failOnError: false,
  103. onDetected: function ({ module: _webpackModuleRecord, paths, compilation }) {
  104. if (paths.some(p => p.includes('container.ts'))) return;
  105. compilation.warnings.push(new Error(paths.join(' -> ')));
  106. },
  107. }),
  108. );
  109. }
  110. if (env.analyzeBundle) {
  111. plugins.push(new BundleAnalyzerPlugin());
  112. }
  113. return {
  114. name: 'extension',
  115. entry: './src/extension.ts',
  116. mode: mode,
  117. target: 'node',
  118. node: {
  119. __dirname: false,
  120. },
  121. devtool: 'source-map',
  122. output: {
  123. libraryTarget: 'commonjs2',
  124. filename: 'gitlens.js',
  125. chunkFilename: 'feature-[name].js',
  126. },
  127. optimization: {
  128. minimizer: [
  129. new TerserPlugin({
  130. cache: true,
  131. parallel: true,
  132. sourceMap: true,
  133. terserOptions: {
  134. ecma: 8,
  135. // Keep the class names otherwise @log won't provide a useful name
  136. keep_classnames: true,
  137. module: true,
  138. },
  139. }),
  140. ],
  141. splitChunks: {
  142. cacheGroups: {
  143. vendors: false,
  144. },
  145. chunks: 'async',
  146. },
  147. },
  148. externals: {
  149. vscode: 'commonjs vscode',
  150. },
  151. module: {
  152. rules: [
  153. {
  154. exclude: /\.d\.ts$/,
  155. include: path.resolve(__dirname, 'src'),
  156. test: /\.tsx?$/,
  157. use: {
  158. loader: 'ts-loader',
  159. options: {
  160. experimentalWatchApi: true,
  161. transpileOnly: true,
  162. },
  163. },
  164. },
  165. ],
  166. },
  167. resolve: {
  168. alias: {
  169. 'universal-user-agent': path.resolve(__dirname, 'node_modules/universal-user-agent/dist-node/index.js'),
  170. },
  171. extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
  172. symlinks: false,
  173. },
  174. plugins: plugins,
  175. stats: {
  176. all: false,
  177. assets: true,
  178. builtAt: true,
  179. env: true,
  180. errors: true,
  181. timings: true,
  182. warnings: true,
  183. },
  184. };
  185. }
  186. /**
  187. * @param { 'production' | 'development' | 'none' } mode
  188. * @param {{ analyzeBundle?: boolean; analyzeDeps?: boolean; optimizeImages?: boolean; }} env
  189. * @returns { WebpackConfig }
  190. */
  191. function getWebviewsConfig(mode, env) {
  192. const clean = ['**/*'];
  193. if (env.optimizeImages) {
  194. console.log('Optimizing images (src/webviews/apps/images/settings/*.png)...');
  195. clean.push(path.resolve(__dirname, 'images/settings/*'));
  196. }
  197. const cspPolicy = {
  198. 'default-src': "'none'",
  199. 'img-src': ['#{cspSource}', 'https:', 'data:'],
  200. 'script-src': ['#{cspSource}', "'nonce-Z2l0bGVucy1ib290c3RyYXA='"],
  201. 'style-src': ['#{cspSource}'],
  202. };
  203. if (mode !== 'production') {
  204. cspPolicy['script-src'].push("'unsafe-eval'");
  205. }
  206. /**
  207. * @type WebpackConfig['plugins']
  208. */
  209. const plugins = [
  210. new CleanPlugin({ cleanOnceBeforeBuildPatterns: clean }),
  211. new ForkTsCheckerPlugin({
  212. async: false,
  213. eslint: { enabled: true, files: path.resolve(__dirname, 'src/**/*.ts'), options: { cache: true } },
  214. formatter: 'basic',
  215. typescript: {
  216. configFile: path.resolve(__dirname, 'tsconfig.webviews.json'),
  217. },
  218. }),
  219. new MiniCssExtractPlugin({
  220. filename: '[name].css',
  221. }),
  222. new HtmlPlugin({
  223. excludeAssets: [/.+-styles\.js/],
  224. excludeChunks: ['welcome'],
  225. template: 'settings/settings.ejs',
  226. filename: path.resolve(__dirname, 'dist/webviews/settings.html'),
  227. inject: true,
  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. excludeAssets: [/.+-styles\.js/],
  252. excludeChunks: ['settings'],
  253. template: 'welcome/welcome.ejs',
  254. filename: path.resolve(__dirname, 'dist/webviews/welcome.html'),
  255. inject: true,
  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 HtmlSkipAssetsPlugin({}),
  279. new CspHtmlPlugin(),
  280. new ImageminPlugin({
  281. disable: !env.optimizeImages,
  282. externalImages: {
  283. context: path.resolve(__dirname, 'src/webviews/apps/images'),
  284. sources: glob.sync('src/webviews/apps/images/settings/*.png'),
  285. destination: path.resolve(__dirname, 'images'),
  286. },
  287. cacheFolder: path.resolve(__dirname, 'node_modules', '.cache', 'imagemin-webpack-plugin'),
  288. gifsicle: null,
  289. jpegtran: null,
  290. optipng: null,
  291. pngquant: {
  292. quality: '85-100',
  293. speed: mode === 'production' ? 1 : 10,
  294. },
  295. svgo: null,
  296. }),
  297. new InlineChunkHtmlPlugin(HtmlPlugin, mode === 'production' ? ['\\.css$'] : []),
  298. ];
  299. return {
  300. name: 'webviews',
  301. context: path.resolve(__dirname, 'src/webviews/apps'),
  302. entry: {
  303. 'main-styles': ['./scss/main.scss'],
  304. settings: ['./settings/settings.ts'],
  305. welcome: ['./welcome/welcome.ts'],
  306. },
  307. mode: mode,
  308. target: 'web',
  309. devtool: mode === 'production' ? undefined : 'eval-source-map',
  310. output: {
  311. filename: '[name].js',
  312. path: path.resolve(__dirname, 'dist/webviews'),
  313. publicPath: '#{root}/dist/webviews/',
  314. },
  315. module: {
  316. rules: [
  317. {
  318. exclude: /\.d\.ts$/,
  319. include: path.resolve(__dirname, 'src'),
  320. test: /\.tsx?$/,
  321. use: {
  322. loader: 'ts-loader',
  323. options: {
  324. configFile: 'tsconfig.webviews.json',
  325. experimentalWatchApi: true,
  326. transpileOnly: true,
  327. },
  328. },
  329. },
  330. {
  331. test: /\.scss$/,
  332. use: [
  333. {
  334. loader: MiniCssExtractPlugin.loader,
  335. },
  336. {
  337. loader: 'css-loader',
  338. options: {
  339. sourceMap: true,
  340. url: false,
  341. },
  342. },
  343. {
  344. loader: 'sass-loader',
  345. options: {
  346. sourceMap: true,
  347. },
  348. },
  349. ],
  350. exclude: /node_modules/,
  351. },
  352. {
  353. test: /\.ejs$/,
  354. loader: 'ejs-loader',
  355. },
  356. ],
  357. },
  358. resolve: {
  359. extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
  360. modules: [path.resolve(__dirname, 'src/webviews/apps'), 'node_modules'],
  361. symlinks: false,
  362. },
  363. plugins: plugins,
  364. stats: {
  365. all: false,
  366. assets: true,
  367. builtAt: true,
  368. env: true,
  369. errors: true,
  370. timings: true,
  371. warnings: true,
  372. },
  373. };
  374. }