@servicetitan/startup 31.4.0 → 31.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/dist/cli/commands/build.d.ts +0 -7
  2. package/dist/cli/commands/build.d.ts.map +1 -1
  3. package/dist/cli/commands/build.js +28 -17
  4. package/dist/cli/commands/build.js.map +1 -1
  5. package/dist/cli/commands/bundle-package.d.ts +1 -1
  6. package/dist/cli/commands/bundle-package.d.ts.map +1 -1
  7. package/dist/cli/commands/bundle-package.js +13 -21
  8. package/dist/cli/commands/bundle-package.js.map +1 -1
  9. package/dist/cli/commands/clean.js +1 -1
  10. package/dist/cli/commands/clean.js.map +1 -1
  11. package/dist/cli/commands/eslint.d.ts +0 -1
  12. package/dist/cli/commands/eslint.d.ts.map +1 -1
  13. package/dist/cli/commands/eslint.js +0 -3
  14. package/dist/cli/commands/eslint.js.map +1 -1
  15. package/dist/cli/commands/get-command.d.ts.map +1 -1
  16. package/dist/cli/commands/get-command.js +3 -1
  17. package/dist/cli/commands/get-command.js.map +1 -1
  18. package/dist/cli/commands/get-user-commands.js +3 -2
  19. package/dist/cli/commands/get-user-commands.js.map +1 -1
  20. package/dist/cli/commands/init.js.map +1 -1
  21. package/dist/cli/commands/install.js.map +1 -1
  22. package/dist/cli/commands/lint.js.map +1 -1
  23. package/dist/cli/commands/mfe-package-clean.d.ts +3 -4
  24. package/dist/cli/commands/mfe-package-clean.d.ts.map +1 -1
  25. package/dist/cli/commands/mfe-package-clean.js +0 -3
  26. package/dist/cli/commands/mfe-package-clean.js.map +1 -1
  27. package/dist/cli/commands/mfe-package-publish.d.ts +9 -4
  28. package/dist/cli/commands/mfe-package-publish.d.ts.map +1 -1
  29. package/dist/cli/commands/mfe-package-publish.js +70 -47
  30. package/dist/cli/commands/mfe-package-publish.js.map +1 -1
  31. package/dist/cli/commands/mfe-publish.d.ts +3 -3
  32. package/dist/cli/commands/mfe-publish.d.ts.map +1 -1
  33. package/dist/cli/commands/mfe-publish.js +7 -1
  34. package/dist/cli/commands/mfe-publish.js.map +1 -1
  35. package/dist/cli/commands/prepare-package.d.ts +0 -1
  36. package/dist/cli/commands/prepare-package.d.ts.map +1 -1
  37. package/dist/cli/commands/prepare-package.js +0 -3
  38. package/dist/cli/commands/prepare-package.js.map +1 -1
  39. package/dist/cli/commands/review/review.js.map +1 -1
  40. package/dist/cli/commands/review/rules/require-one-anvil-uikit-contrib-version.js.map +1 -1
  41. package/dist/cli/commands/review/rules/require-one-collection-version.js.map +1 -1
  42. package/dist/cli/commands/review/rules/require-one-uikit-version.js.map +1 -1
  43. package/dist/cli/commands/run-task.d.ts +0 -1
  44. package/dist/cli/commands/run-task.d.ts.map +1 -1
  45. package/dist/cli/commands/run-task.js +0 -3
  46. package/dist/cli/commands/run-task.js.map +1 -1
  47. package/dist/cli/commands/start.d.ts +0 -8
  48. package/dist/cli/commands/start.d.ts.map +1 -1
  49. package/dist/cli/commands/start.js +28 -16
  50. package/dist/cli/commands/start.js.map +1 -1
  51. package/dist/cli/commands/styles-check.d.ts +0 -1
  52. package/dist/cli/commands/styles-check.d.ts.map +1 -1
  53. package/dist/cli/commands/styles-check.js +40 -99
  54. package/dist/cli/commands/styles-check.js.map +1 -1
  55. package/dist/cli/commands/tests.js.map +1 -1
  56. package/dist/cli/commands/types.d.ts +1 -1
  57. package/dist/cli/commands/types.d.ts.map +1 -1
  58. package/dist/cli/commands/upload-sourcemaps.d.ts +22 -0
  59. package/dist/cli/commands/upload-sourcemaps.d.ts.map +1 -0
  60. package/dist/cli/commands/upload-sourcemaps.js +179 -0
  61. package/dist/cli/commands/upload-sourcemaps.js.map +1 -0
  62. package/dist/cli/tasks/cli-task.js.map +1 -1
  63. package/dist/cli/tasks/swc-compile-package.js.map +1 -1
  64. package/dist/cli/tasks/task.js.map +1 -1
  65. package/dist/cli/tasks/tsc-compile-package.js.map +1 -1
  66. package/dist/cli/tasks/tsc-compile.js.map +1 -1
  67. package/dist/cli/utils/bundle.d.ts +4 -1
  68. package/dist/cli/utils/bundle.d.ts.map +1 -1
  69. package/dist/cli/utils/bundle.js +67 -74
  70. package/dist/cli/utils/bundle.js.map +1 -1
  71. package/dist/cli/utils/cli-os.js +2 -2
  72. package/dist/cli/utils/cli-os.js.map +1 -1
  73. package/dist/cli/utils/ts-config.js.map +1 -1
  74. package/dist/utils/find-packages.d.ts.map +1 -1
  75. package/dist/utils/find-packages.js +3 -4
  76. package/dist/utils/find-packages.js.map +1 -1
  77. package/dist/utils/find-up.d.ts +2 -0
  78. package/dist/utils/find-up.d.ts.map +1 -0
  79. package/dist/utils/find-up.js +28 -0
  80. package/dist/utils/find-up.js.map +1 -0
  81. package/dist/utils/get-configuration.d.ts +3 -1
  82. package/dist/utils/get-configuration.d.ts.map +1 -1
  83. package/dist/utils/get-configuration.js +1 -0
  84. package/dist/utils/get-configuration.js.map +1 -1
  85. package/dist/utils/index.d.ts +1 -0
  86. package/dist/utils/index.d.ts.map +1 -1
  87. package/dist/utils/index.js +1 -0
  88. package/dist/utils/index.js.map +1 -1
  89. package/dist/utils/log.js.map +1 -1
  90. package/dist/webpack/configs/cache-config.d.ts +6 -0
  91. package/dist/webpack/configs/cache-config.d.ts.map +1 -0
  92. package/dist/webpack/configs/cache-config.js +52 -0
  93. package/dist/webpack/configs/cache-config.js.map +1 -0
  94. package/dist/webpack/configs/dev-server-config.js +1 -1
  95. package/dist/webpack/configs/dev-server-config.js.map +1 -1
  96. package/dist/webpack/configs/entry.config.d.ts.map +1 -1
  97. package/dist/webpack/configs/entry.config.js +15 -6
  98. package/dist/webpack/configs/entry.config.js.map +1 -1
  99. package/dist/webpack/configs/externals-config.d.ts.map +1 -1
  100. package/dist/webpack/configs/externals-config.js +6 -2
  101. package/dist/webpack/configs/externals-config.js.map +1 -1
  102. package/dist/webpack/configs/index.d.ts +1 -0
  103. package/dist/webpack/configs/index.d.ts.map +1 -1
  104. package/dist/webpack/configs/index.js +1 -0
  105. package/dist/webpack/configs/index.js.map +1 -1
  106. package/dist/webpack/configs/optimization-config.d.ts.map +1 -1
  107. package/dist/webpack/configs/optimization-config.js +7 -11
  108. package/dist/webpack/configs/optimization-config.js.map +1 -1
  109. package/dist/webpack/configs/output-config.d.ts.map +1 -1
  110. package/dist/webpack/configs/output-config.js +25 -4
  111. package/dist/webpack/configs/output-config.js.map +1 -1
  112. package/dist/webpack/configs/plugins/assets-manifest-plugin.d.ts +6 -0
  113. package/dist/webpack/configs/plugins/assets-manifest-plugin.d.ts.map +1 -1
  114. package/dist/webpack/configs/plugins/assets-manifest-plugin.js +50 -8
  115. package/dist/webpack/configs/plugins/assets-manifest-plugin.js.map +1 -1
  116. package/dist/webpack/configs/plugins/bundle-analyser-plugin.d.ts.map +1 -1
  117. package/dist/webpack/configs/plugins/bundle-analyser-plugin.js +3 -7
  118. package/dist/webpack/configs/plugins/bundle-analyser-plugin.js.map +1 -1
  119. package/dist/webpack/configs/plugins/define-exposed-dependencies-plugin.d.ts.map +1 -1
  120. package/dist/webpack/configs/plugins/define-exposed-dependencies-plugin.js +3 -2
  121. package/dist/webpack/configs/plugins/define-exposed-dependencies-plugin.js.map +1 -1
  122. package/dist/webpack/configs/plugins/define-exposed-instance-dependencies-plugin.js +2 -2
  123. package/dist/webpack/configs/plugins/define-exposed-instance-dependencies-plugin.js.map +1 -1
  124. package/dist/webpack/configs/plugins/html-plugin.d.ts +1 -1
  125. package/dist/webpack/configs/plugins/html-plugin.d.ts.map +1 -1
  126. package/dist/webpack/configs/plugins/html-plugin.js +2 -3
  127. package/dist/webpack/configs/plugins/html-plugin.js.map +1 -1
  128. package/dist/webpack/configs/plugins/html-tags-plugin.d.ts +4 -0
  129. package/dist/webpack/configs/plugins/html-tags-plugin.d.ts.map +1 -0
  130. package/dist/webpack/configs/plugins/html-tags-plugin.js +49 -0
  131. package/dist/webpack/configs/plugins/html-tags-plugin.js.map +1 -0
  132. package/dist/webpack/configs/plugins/index.d.ts +2 -0
  133. package/dist/webpack/configs/plugins/index.d.ts.map +1 -1
  134. package/dist/webpack/configs/plugins/index.js +2 -0
  135. package/dist/webpack/configs/plugins/index.js.map +1 -1
  136. package/dist/webpack/configs/plugins/remove-empty-scripts-plugin.d.ts +4 -0
  137. package/dist/webpack/configs/plugins/remove-empty-scripts-plugin.d.ts.map +1 -0
  138. package/dist/webpack/configs/plugins/remove-empty-scripts-plugin.js +25 -0
  139. package/dist/webpack/configs/plugins/remove-empty-scripts-plugin.js.map +1 -0
  140. package/dist/webpack/configs/plugins/virtual-modules-plugin.d.ts +1 -0
  141. package/dist/webpack/configs/plugins/virtual-modules-plugin.d.ts.map +1 -1
  142. package/dist/webpack/configs/plugins/virtual-modules-plugin.js +23 -14
  143. package/dist/webpack/configs/plugins/virtual-modules-plugin.js.map +1 -1
  144. package/dist/webpack/configs/plugins-config.d.ts.map +1 -1
  145. package/dist/webpack/configs/plugins-config.js +2 -0
  146. package/dist/webpack/configs/plugins-config.js.map +1 -1
  147. package/dist/webpack/configs/rules/css-rules.d.ts.map +1 -1
  148. package/dist/webpack/configs/rules/css-rules.js +13 -18
  149. package/dist/webpack/configs/rules/css-rules.js.map +1 -1
  150. package/dist/webpack/configs/utils/get-bundle-type.d.ts +3 -0
  151. package/dist/webpack/configs/utils/get-bundle-type.d.ts.map +1 -0
  152. package/dist/webpack/configs/utils/get-bundle-type.js +24 -0
  153. package/dist/webpack/configs/utils/get-bundle-type.js.map +1 -0
  154. package/dist/webpack/configs/utils/index.d.ts +1 -0
  155. package/dist/webpack/configs/utils/index.d.ts.map +1 -1
  156. package/dist/webpack/configs/utils/index.js +1 -0
  157. package/dist/webpack/configs/utils/index.js.map +1 -1
  158. package/dist/webpack/create-webpack-config.d.ts.map +1 -1
  159. package/dist/webpack/create-webpack-config.js +37 -48
  160. package/dist/webpack/create-webpack-config.js.map +1 -1
  161. package/dist/webpack/types.d.ts +4 -0
  162. package/dist/webpack/types.d.ts.map +1 -1
  163. package/dist/webpack/utils/index.d.ts +1 -0
  164. package/dist/webpack/utils/index.d.ts.map +1 -1
  165. package/dist/webpack/utils/index.js +1 -0
  166. package/dist/webpack/utils/index.js.map +1 -1
  167. package/dist/webpack/utils/stringify-config.d.ts +2 -0
  168. package/dist/webpack/utils/stringify-config.d.ts.map +1 -0
  169. package/dist/webpack/utils/stringify-config.js +35 -0
  170. package/dist/webpack/utils/stringify-config.js.map +1 -0
  171. package/package.json +16 -14
  172. package/src/cli/commands/__tests__/build.test.ts +19 -2
  173. package/src/cli/commands/__tests__/bundle-package.test.ts +29 -8
  174. package/src/cli/commands/__tests__/clean.test.ts +2 -0
  175. package/src/cli/commands/__tests__/get-user-commands.test.ts +1 -1
  176. package/src/cli/commands/__tests__/mfe-package-publish.test.ts +91 -15
  177. package/src/cli/commands/__tests__/mfe-publish.test.ts +2 -0
  178. package/src/cli/commands/__tests__/start.test.ts +15 -1
  179. package/src/cli/commands/__tests__/styles-check.test.ts +27 -80
  180. package/src/cli/commands/__tests__/upload-sourcemaps.test.ts +127 -0
  181. package/src/cli/commands/build.ts +33 -17
  182. package/src/cli/commands/bundle-package.ts +10 -19
  183. package/src/cli/commands/clean.ts +1 -1
  184. package/src/cli/commands/eslint.ts +0 -4
  185. package/src/cli/commands/get-command.ts +2 -0
  186. package/src/cli/commands/get-user-commands.ts +1 -1
  187. package/src/cli/commands/mfe-package-clean.ts +2 -6
  188. package/src/cli/commands/mfe-package-publish.ts +104 -70
  189. package/src/cli/commands/mfe-publish.ts +8 -5
  190. package/src/cli/commands/prepare-package.ts +0 -4
  191. package/src/cli/commands/run-task.ts +0 -4
  192. package/src/cli/commands/start.ts +22 -5
  193. package/src/cli/commands/styles-check.ts +28 -131
  194. package/src/cli/commands/types.ts +1 -1
  195. package/src/cli/commands/upload-sourcemaps.ts +108 -0
  196. package/src/cli/utils/__tests__/bundle.test.ts +119 -9
  197. package/src/cli/utils/__tests__/cli-os.test.ts +2 -2
  198. package/src/cli/utils/__tests__/compile.test.ts +2 -0
  199. package/src/cli/utils/__tests__/type-check.test.ts +2 -0
  200. package/src/cli/utils/bundle.ts +76 -54
  201. package/src/cli/utils/cli-os.ts +2 -2
  202. package/src/utils/__tests__/get-configuration.test.ts +1 -1
  203. package/src/utils/find-packages.ts +3 -5
  204. package/src/utils/find-up.ts +12 -0
  205. package/src/utils/get-configuration.ts +2 -0
  206. package/src/utils/index.ts +1 -0
  207. package/src/webpack/__mocks__/style-rules.ts +1 -1
  208. package/src/webpack/__tests__/create-webpack-config-shared-dependencies.test.ts +274 -45
  209. package/src/webpack/__tests__/create-webpack-config-web-component.test.ts +25 -1
  210. package/src/webpack/__tests__/create-webpack-config.test.ts +9 -57
  211. package/src/webpack/configs/cache-config.ts +37 -0
  212. package/src/webpack/configs/dev-server-config.ts +1 -1
  213. package/src/webpack/configs/entry.config.ts +18 -8
  214. package/src/webpack/configs/externals-config.ts +7 -2
  215. package/src/webpack/configs/index.ts +1 -0
  216. package/src/webpack/configs/optimization-config.ts +7 -11
  217. package/src/webpack/configs/output-config.ts +23 -7
  218. package/src/webpack/configs/plugins/assets-manifest-plugin.ts +46 -10
  219. package/src/webpack/configs/plugins/bundle-analyser-plugin.ts +1 -6
  220. package/src/webpack/configs/plugins/define-exposed-dependencies-plugin.ts +3 -2
  221. package/src/webpack/configs/plugins/define-exposed-instance-dependencies-plugin.ts +2 -2
  222. package/src/webpack/configs/plugins/html-plugin.ts +2 -3
  223. package/src/webpack/configs/plugins/html-tags-plugin.ts +28 -0
  224. package/src/webpack/configs/plugins/index.ts +2 -0
  225. package/src/webpack/configs/plugins/remove-empty-scripts-plugin.ts +11 -0
  226. package/src/webpack/configs/plugins/virtual-modules-plugin.ts +27 -16
  227. package/src/webpack/configs/plugins-config.ts +4 -0
  228. package/src/webpack/configs/rules/css-rules.ts +19 -20
  229. package/src/webpack/configs/utils/get-bundle-type.ts +22 -0
  230. package/src/webpack/configs/utils/index.ts +1 -0
  231. package/src/webpack/create-webpack-config.ts +46 -52
  232. package/src/webpack/types.ts +4 -0
  233. package/src/webpack/utils/index.ts +1 -0
  234. package/src/webpack/utils/stringify-config.ts +19 -0
@@ -1,5 +1,13 @@
1
+ import { fs, vol } from 'memfs';
2
+ import * as mockFS from 'fs';
3
+ import HtmlWebpackPlugin from 'html-webpack-plugin';
4
+ import HtmlWebpackTagsPlugin from 'html-webpack-tags-plugin';
1
5
  import MiniCssExtractPlugin from 'mini-css-extract-plugin';
2
- import { DefinePlugin } from 'webpack';
6
+ import path from 'path';
7
+ import { DefinePlugin, EntryObject } from 'webpack';
8
+ import { WebpackAssetsManifest } from 'webpack-assets-manifest';
9
+ import RemoveEmptyScriptsPlugin from 'webpack-remove-empty-scripts';
10
+ import VirtualModulesPlugin from 'webpack-virtual-modules';
3
11
  import {
4
12
  getFolders,
5
13
  getPackageData,
@@ -9,12 +17,18 @@ import {
9
17
  isCustomStyleRules,
10
18
  isExposeSharedDependencies,
11
19
  loadSharedDependencies,
20
+ log,
21
+ pick,
12
22
  } from '../../utils';
23
+ import { styleRules } from '../__mocks__';
13
24
  import { getLaunchDarklySdkVersion } from '../configs/utils/get-launchdarkly-sdk-version';
14
25
  import { getModuleEntryPath } from '../utils';
15
26
 
16
27
  import { createWebpackConfig } from '../index';
17
28
 
29
+ jest.mock('fs', () => fs);
30
+ jest.mock('html-webpack-plugin', () => jest.fn());
31
+ jest.mock('html-webpack-tags-plugin', () => jest.fn());
18
32
  jest.mock('mini-css-extract-plugin', () =>
19
33
  Object.assign(jest.fn(), { loader: 'MiniCssExtractPlugin.loader' })
20
34
  );
@@ -23,6 +37,9 @@ jest.mock('webpack', () => ({
23
37
  ...jest.requireActual('webpack'),
24
38
  DefinePlugin: jest.fn(),
25
39
  }));
40
+ jest.mock('webpack-assets-manifest', () => ({ WebpackAssetsManifest: jest.fn() }));
41
+ jest.mock('webpack-remove-empty-scripts', () => jest.fn());
42
+ jest.mock('webpack-virtual-modules', () => jest.fn());
26
43
 
27
44
  jest.mock('../../utils', () => ({
28
45
  ...jest.requireActual('../../utils'),
@@ -56,22 +73,40 @@ describe(`[startup] ${createWebpackConfig.name}`, () => {
56
73
  };
57
74
 
58
75
  let overrides: Parameters<typeof createWebpackConfig>[0];
76
+ let options: Parameters<typeof createWebpackConfig>[1];
59
77
 
60
78
  function mockPlugIn(name: string) {
61
79
  return (options: Record<string, any>): any => ({ [name]: options });
62
80
  }
63
81
 
82
+ function volumeFromJSON(overrides: Record<string, any> = {}) {
83
+ return vol.fromJSON({
84
+ 'package.json': JSON.stringify({}),
85
+ ...Object.fromEntries(
86
+ Object.entries(overrides).map(([key, value]) => [key, JSON.stringify(value)])
87
+ ),
88
+ });
89
+ }
90
+
64
91
  beforeEach(() => {
65
92
  overrides = { plugins: {} };
93
+ options = { name: 'app' };
66
94
 
67
95
  jest.resetAllMocks();
68
- jest.mocked(MiniCssExtractPlugin).mockImplementation(mockPlugIn('MiniCssExtractPlugin'));
69
96
  jest.mocked(DefinePlugin).mockImplementation(mockPlugIn('DefinePlugin'));
97
+ jest.mocked(HtmlWebpackPlugin).mockImplementation(mockPlugIn('HtmlWebpackPlugin'));
98
+ jest.mocked(HtmlWebpackTagsPlugin).mockImplementation(mockPlugIn('HtmlWebpackTagsPlugin'));
99
+ jest.mocked(MiniCssExtractPlugin).mockImplementation(mockPlugIn('MiniCssExtractPlugin'));
100
+ jest.mocked(VirtualModulesPlugin).mockImplementation(mockPlugIn('VirtualModulesPlugin'));
101
+ jest.mocked(WebpackAssetsManifest).mockImplementation(mockPlugIn('WebpackAssetsManifest'));
102
+ jest.mocked(RemoveEmptyScriptsPlugin).mockImplementation(
103
+ mockPlugIn('WebpackRemoveEmptyScriptsPlugin')
104
+ );
70
105
 
71
106
  jest.mocked(getFolders).mockReturnValue({ source, destination });
72
107
  jest.mocked(getModuleEntryPath).mockImplementation(name => `${name}/entryPath`);
73
108
  jest.mocked(getPackageData).mockReturnValue({
74
- name: '',
109
+ name: options.name!,
75
110
  version: '',
76
111
  dependencies,
77
112
  sharedDependencies,
@@ -82,9 +117,13 @@ describe(`[startup] ${createWebpackConfig.name}`, () => {
82
117
  jest.mocked(getPackages).mockReturnValue([]);
83
118
  jest.mocked(getTsConfig).mockReturnValue(tsConfig);
84
119
  jest.mocked(loadSharedDependencies).mockReturnValue(sharedDependencies);
120
+
121
+ volumeFromJSON();
85
122
  });
86
123
 
87
- const subject = () => createWebpackConfig(overrides);
124
+ afterEach(() => vol.reset());
125
+
126
+ const subject = () => createWebpackConfig(overrides, options);
88
127
 
89
128
  describe('when package exposes shared dependencies', () => {
90
129
  const launchDarklySdkVersion = '3.0';
@@ -94,25 +133,6 @@ describe(`[startup] ${createWebpackConfig.name}`, () => {
94
133
  jest.mocked(getLaunchDarklySdkVersion).mockReturnValue(launchDarklySdkVersion);
95
134
  });
96
135
 
97
- test('configures "optimization.splitChunks.cacheGroups.design-system"', () => {
98
- expect(
99
- (subject().optimization?.splitChunks as any).cacheGroups['design-system']
100
- ).toEqual({
101
- test: /[\\/]node_modules[\\/]@servicetitan[\\/](tokens|anvil-fonts|design-system)[\\/].*\.css$/,
102
- name: 'design-system',
103
- chunks: 'all',
104
- enforce: true,
105
- });
106
- });
107
-
108
- test('configures shared dependency entry points', () => {
109
- expect(subject().entry).toEqual({
110
- main: expect.arrayContaining(
111
- Object.keys(sharedDependencies).map(getModuleEntryPath)
112
- ),
113
- });
114
- });
115
-
116
136
  test('configures shared dependencies to use expose-loader', () => {
117
137
  Object.entries(sharedDependencies).forEach(([name, value]) => {
118
138
  expect(subject().module?.rules).toContainEqual({
@@ -124,6 +144,17 @@ describe(`[startup] ${createWebpackConfig.name}`, () => {
124
144
  });
125
145
  });
126
146
 
147
+ test('configures "externals"', () => {
148
+ expect(subject().externals).toEqual(
149
+ Object.fromEntries(
150
+ Object.entries(sharedDependencies).map(([name, value]) => [
151
+ name,
152
+ `${value}['${options!.name}']`,
153
+ ])
154
+ )
155
+ );
156
+ });
157
+
127
158
  test('configures EXPOSE_DEPENDENCIES', () => {
128
159
  const exposedDependencies = Object.fromEntries(
129
160
  Object.entries(sharedDependencies).map(([name, variable]) => [
@@ -153,39 +184,237 @@ describe(`[startup] ${createWebpackConfig.name}`, () => {
153
184
  );
154
185
  });
155
186
 
156
- test('configures "design-system.css" rules', () => {
157
- expect(subject().module?.rules).toContainEqual({
158
- test: /design-system\.css$/,
159
- use: [MiniCssExtractPlugin.loader, 'css-loader'],
160
- });
161
- });
187
+ test('configures rule that reports fatal error when app imports design-system.css', () => {
188
+ const mockError = jest.spyOn(log, 'error').mockImplementation(jest.fn());
189
+ const mockExit = jest.spyOn(process, 'exit').mockImplementation(jest.fn() as any);
190
+ const rule: any = subject().module?.rules?.find(
191
+ rule => typeof rule === 'object' && typeof rule?.use === 'function'
192
+ );
162
193
 
163
- test('configures anvil2 css" rules', () => {
164
- expect(subject().module?.rules).toContainEqual({
165
- test: /@servicetitan[\\/]anvil2[\\/].*\.css$/,
166
- use: [MiniCssExtractPlugin.loader, 'css-loader'],
167
- });
168
- });
194
+ rule.use({ issuer: './foo.js' });
169
195
 
170
- test('configures ".css" rule to also exclude design-system.css', () => {
171
- const cssRule: any = subject().module?.rules?.find(({ test }: { test: RegExp }) =>
172
- '.css'.match(test)
196
+ expect(rule).toEqual({ test: /[\\/]design-system.css$/, use: expect.any(Function) });
197
+ expect(mockError).toHaveBeenCalledWith(
198
+ expect.stringMatching(/do not import design-system.css/)
173
199
  );
174
- expect(
175
- ['design-system.css', 'foo.module.css', 'foo.css'].filter(path =>
176
- cssRule.exclude(path)
177
- )
178
- ).toEqual(['design-system.css', 'foo.module.css']);
200
+ expect(mockExit).toHaveBeenCalledWith(1);
179
201
  });
180
202
 
181
203
  describe('when package has custom style rules', () => {
182
204
  beforeEach(() => jest.mocked(isCustomStyleRules).mockReturnValue(true));
183
205
 
184
- test('does not configure MiniCssExtractPlugIn plugin', () => {
206
+ test('does not configure MiniCssExtractPlugin plugin', () => {
185
207
  expect(subject().plugins).not.toContainEqual(
186
- expect.objectContaining({ MiniCssExtractPlugIn: expect.anything() })
208
+ expect.objectContaining({ MiniCssExtractPlugin: expect.anything() })
209
+ );
210
+ });
211
+
212
+ describe('when emitting exposed dependencies', () => {
213
+ beforeEach(() => (options = { emitExposedDependencies: true }));
214
+
215
+ test('configures MiniCssExtractPlugin plugin', () => {
216
+ expect(subject().plugins).toContainEqual(
217
+ expect.objectContaining({ MiniCssExtractPlugin: expect.anything() })
218
+ );
219
+ });
220
+
221
+ Object.entries(styleRules).forEach(([extension, rule]) => {
222
+ test(`configures "${extension}" rules`, () => {
223
+ expect(subject().module?.rules).toContainEqual(rule);
224
+ });
225
+ });
226
+ });
227
+ });
228
+
229
+ describe('when in production', () => {
230
+ beforeEach(() => (overrides.configuration = { mode: 'production' }));
231
+
232
+ test('configures "output.clean" to keep shared dependencies', () => {
233
+ expect(subject().output?.clean).toEqual({ keep: /shared/ });
234
+ });
235
+ });
236
+
237
+ describe('with bundled exposed dependencies', () => {
238
+ const entryPointsPath = `${destination}/bundle/shared/entrypoints.json`;
239
+ const entryPoints = {
240
+ css: ['anvil2.bundle.css', 'design-system.bundle.css'],
241
+ js: ['anvil2.bundle.js', 'shared.bundle.js', 'design-system..bundle.js'],
242
+ };
243
+
244
+ beforeEach(() => {
245
+ volumeFromJSON({
246
+ [`${destination}/exposed-dependencies-metadata.json`]: { entryPointsPath },
247
+ [entryPointsPath]: entryPoints,
248
+ });
249
+ });
250
+
251
+ test('configures HtmlWebpackTagsPlugin plug', () => {
252
+ expect(subject().plugins).toContainEqual(
253
+ new HtmlWebpackTagsPlugin({
254
+ tags: [...entryPoints.css, ...entryPoints.js].map(name => `shared/${name}`),
255
+ append: false,
256
+ })
257
+ );
258
+ });
259
+ });
260
+
261
+ describe('when emitting exposed dependencies', () => {
262
+ beforeEach(() => (options = { ...options, emitExposedDependencies: true }));
263
+
264
+ test('configures shared dependencies entry points', () => {
265
+ expect(subject().entry).toEqual(
266
+ expect.objectContaining({
267
+ shared: expect.arrayContaining(
268
+ Object.keys(sharedDependencies).map(getModuleEntryPath)
269
+ ),
270
+ })
271
+ );
272
+ });
273
+
274
+ test('configures design-system.css entry point', () => {
275
+ const { entry, plugins } = subject();
276
+
277
+ expect(entry).toEqual(
278
+ expect.objectContaining({
279
+ 'design-system': expect.stringMatching(/design-system.css/),
280
+ })
281
+ );
282
+ expect(plugins).toContainEqual(
283
+ new VirtualModulesPlugin({
284
+ [(entry as EntryObject)?.['design-system'] as string]: [
285
+ `@import '~@servicetitan/tokens/core/tokens.css';`,
286
+ `@import '~@servicetitan/anvil-fonts/dist/css/anvil-fonts.css';`,
287
+ `@import '~@servicetitan/design-system/dist/system.min.css';`,
288
+ ].join('\n'),
289
+ })
187
290
  );
188
291
  });
292
+
293
+ describe('when application does not share design system', () => {
294
+ beforeEach(() => {
295
+ jest.mocked(loadSharedDependencies).mockReturnValue(
296
+ pick(sharedDependencies, ['react'])
297
+ );
298
+ });
299
+
300
+ test('omits design-system.css entry point', () => {
301
+ expect((subject().entry as EntryObject)?.['design-system']).toBeUndefined();
302
+ });
303
+ });
304
+
305
+ test('configures "optimization.splitChunks.cacheGroups.anvil2"', () => {
306
+ expect((subject().optimization?.splitChunks as any).cacheGroups.anvil2).toEqual({
307
+ test: /@servicetitan[\\/]anvil2[\\/].*\.css$/,
308
+ name: 'anvil2',
309
+ chunks: 'all',
310
+ enforce: true,
311
+ });
312
+ });
313
+
314
+ test('configures "output.chunkLoadingGlobal"', () => {
315
+ expect(subject().output?.chunkLoadingGlobal).toBe(`sharedChunk${options!.name}`);
316
+ });
317
+
318
+ test('configures "output.path"', () => {
319
+ expect(subject().output?.path).toBe(
320
+ path.join(process.cwd(), destination, 'bundle', 'shared')
321
+ );
322
+ });
323
+
324
+ describe('with custom "output.path"', () => {
325
+ const output = { path: path.resolve(process.cwd(), 'foo') };
326
+
327
+ beforeEach(() => (options = { emitExposedDependencies: { output } }));
328
+
329
+ test('use custom "output.path"', () => {
330
+ expect(subject().output?.path).toBe(path.join(output.path, 'shared'));
331
+ });
332
+ });
333
+
334
+ test('configures WebpackAssetsManifest plugin', () => {
335
+ expect(subject().plugins).toContainEqual(
336
+ new WebpackAssetsManifest({
337
+ entrypoints: true,
338
+ output: 'entrypoints.json',
339
+ transform: expect.any(Function),
340
+ done: expect.any(Function),
341
+ })
342
+ );
343
+ });
344
+
345
+ test('generates exposed-dependencies-metadata.json when WebpackAssetsManifest plugin is done', () => {
346
+ const entryPointsPath = path.join(process.cwd(), destination, 'entrypoints.json');
347
+ const manifest = { getOutputPath: () => entryPointsPath };
348
+
349
+ subject();
350
+ const done: any = jest.mocked(WebpackAssetsManifest).mock.calls[0][0]?.done;
351
+ done(manifest);
352
+
353
+ const metadataPath = `${destination}/exposed-dependencies-metadata.json`;
354
+ const metadata = JSON.parse(mockFS.readFileSync(metadataPath, 'utf-8'));
355
+ expect(metadata).toEqual({ entryPointsPath });
356
+ });
357
+
358
+ test('configures RemoveEmptyScriptsPlugin plugin', () => {
359
+ expect(subject().plugins).toContainEqual(new RemoveEmptyScriptsPlugin());
360
+ });
361
+
362
+ test('configures "cache"', () => {
363
+ expect(subject().cache).toEqual({
364
+ type: 'filesystem',
365
+ version: expect.any(String),
366
+ });
367
+ });
368
+
369
+ describe('with package-lock.json', () => {
370
+ beforeEach(() => volumeFromJSON({ '../package-lock.json': {} }));
371
+
372
+ test('configures "cache.buildDependencies"', () => {
373
+ expect(subject().cache).toEqual(
374
+ expect.objectContaining({
375
+ buildDependencies: {
376
+ packageLock: [path.resolve('../package-lock.json')],
377
+ },
378
+ })
379
+ );
380
+ });
381
+ });
382
+
383
+ test('configures "infrastructureLogging', () => {
384
+ expect(subject().infrastructureLogging).toEqual({ level: 'error' });
385
+ });
386
+
387
+ test('sets "optimization.runtimeChunk" to false', () => {
388
+ expect(subject().optimization?.runtimeChunk).toBe(false);
389
+ });
390
+
391
+ test('omits "devServer" configuration', () => {
392
+ expect(subject().devServer).toBeUndefined();
393
+ });
394
+
395
+ test('omits "externals" configuration', () => {
396
+ expect(subject().externals).toBeUndefined();
397
+ });
398
+
399
+ test('omits EXPOSED_* globals ', () => {
400
+ subject();
401
+
402
+ expect(DefinePlugin).not.toHaveBeenCalled();
403
+ });
404
+
405
+ test('omits HtmlWebpackPlugin plugin', () => {
406
+ subject();
407
+
408
+ expect(HtmlWebpackPlugin).not.toHaveBeenCalled();
409
+ });
410
+
411
+ describe('when in production', () => {
412
+ beforeEach(() => (overrides.configuration = { mode: 'production' }));
413
+
414
+ test('configures "output.clean" to true', () => {
415
+ expect(subject().output?.clean).toBe(true);
416
+ });
417
+ });
189
418
  });
190
419
  });
191
420
  });
@@ -196,7 +196,6 @@ describe(`[startup] ${createWebpackConfig.name}`, () => {
196
196
  expect(subject().plugins).toContainEqual(
197
197
  new HtmlWebpackPlugin({
198
198
  title: 'ServiceTitan',
199
- hash: true,
200
199
  templateParameters: { splitByEntry },
201
200
  ...overrides.plugins?.HtmlWebpackPlugin,
202
201
  inject: false,
@@ -307,6 +306,17 @@ describe(`[startup] ${createWebpackConfig.name}`, () => {
307
306
  );
308
307
  });
309
308
 
309
+ test('configures "externals"', () => {
310
+ expect(subject().externals).toEqual(
311
+ Object.fromEntries(
312
+ Object.entries(sharedDependencies).map(([name, value]) => [
313
+ name,
314
+ `${value}['${options!.name}']`,
315
+ ])
316
+ )
317
+ );
318
+ });
319
+
310
320
  describe('when package does not share design system', () => {
311
321
  beforeEach(() => delete sharedDependencies['@servicetitan/design-system']);
312
322
 
@@ -335,6 +345,12 @@ describe(`[startup] ${createWebpackConfig.name}`, () => {
335
345
  })
336
346
  );
337
347
  });
348
+
349
+ test('appends "/headless" to output path', () => {
350
+ expect(subject().output!.path).toEqual(
351
+ path.join(process.cwd(), destination, 'bundle', 'headless')
352
+ );
353
+ });
338
354
  });
339
355
 
340
356
  describe.each([webpackDevConfigFileName, webpackProdConfigFileName])(
@@ -383,6 +399,14 @@ describe(`[startup] ${createWebpackConfig.name}`, () => {
383
399
  test('configures "output.uniqueName"', () => {
384
400
  expect(subject().output?.uniqueName).toEqual(options.name);
385
401
  });
402
+
403
+ describe('when headless option is set to true', () => {
404
+ beforeEach(() => (options.headless = true));
405
+
406
+ test('omits optimization.runtimeChunk', () => {
407
+ expect(subject().optimization?.runtimeChunk).toBeUndefined();
408
+ });
409
+ });
386
410
  });
387
411
  });
388
412
  });
@@ -446,15 +446,6 @@ describe(`[startup] ${createWebpackConfig.name}`, () => {
446
446
  itConfiguresCodeCoverage();
447
447
  });
448
448
 
449
- test('configures ".css" rule to exclude .module.css files', () => {
450
- const cssRule: any = subject().module?.rules?.find(({ test: regexp }: { test: RegExp }) =>
451
- regexp.test('.css')
452
- );
453
- expect(
454
- ['design-system.css', 'foo.module.css', 'foo.css'].filter(path => cssRule.exclude(path))
455
- ).toEqual(['foo.module.css']);
456
- });
457
-
458
449
  test('configures MomentLocalesPlugin plugin', () => {
459
450
  expect(subject().plugins).toContainEqual(
460
451
  new MomentLocalesPlugin({ localesToKeep: ['en-au', 'en-ca', 'en-gb'] })
@@ -473,7 +464,6 @@ describe(`[startup] ${createWebpackConfig.name}`, () => {
473
464
  expect(subject().plugins).toContainEqual(
474
465
  new HtmlWebpackPlugin({
475
466
  title: 'ServiceTitan',
476
- hash: true,
477
467
  templateParameters: { splitByEntry },
478
468
  ...overrides.plugins?.HtmlWebpackPlugin,
479
469
  })
@@ -547,52 +537,6 @@ describe(`[startup] ${createWebpackConfig.name}`, () => {
547
537
  );
548
538
  });
549
539
  });
550
-
551
- describe('headless option is true', () => {
552
- beforeEach(() => setOptions({ headless: true }));
553
-
554
- test('configures "output.path"', () => {
555
- expect(subject().output!.path).toEqual(
556
- path.join(process.cwd(), destination, 'bundle', 'headless')
557
- );
558
- });
559
-
560
- describe('when mode is "production"', () => {
561
- beforeEach(() => {
562
- overrides.configuration ??= {};
563
- Object.assign(overrides.configuration, { mode: 'production' });
564
- });
565
-
566
- test('omits optimization.runtimeChunk', () => {
567
- expect(subject().optimization?.runtimeChunk).toBeUndefined();
568
- });
569
- });
570
- });
571
- });
572
- });
573
-
574
- describe('when embed option is set to true', () => {
575
- beforeEach(() => setOptions({ embed: true, name: packageName }));
576
-
577
- test('configures "externals"', () => {
578
- expect(subject().externals).toEqual(
579
- Object.fromEntries(
580
- Object.entries(sharedDependencies).map(([name, value]) => [
581
- name,
582
- `${value}['${options!.name}']`,
583
- ])
584
- )
585
- );
586
- });
587
-
588
- describe('when package is web component', () => {
589
- beforeEach(() => jest.mocked(isWebComponent).mockReturnValue(true));
590
-
591
- test('configures "output.path"', () => {
592
- expect(subject().output!.path).toEqual(
593
- path.join(process.cwd(), destination, 'bundle', 'light')
594
- );
595
- });
596
540
  });
597
541
  });
598
542
 
@@ -622,7 +566,7 @@ describe(`[startup] ${createWebpackConfig.name}`, () => {
622
566
  expect(subject().output?.filename).toBe('[name].[contenthash].bundle.js');
623
567
  });
624
568
 
625
- test('configures "output.clean"', () => {
569
+ test('configures "output.clean" to true', () => {
626
570
  expect(subject().output?.clean).toBe(true);
627
571
  });
628
572
 
@@ -712,4 +656,12 @@ describe(`[startup] ${createWebpackConfig.name}`, () => {
712
656
  });
713
657
  });
714
658
  });
659
+
660
+ describe('with "emitExposedDependencies"', () => {
661
+ beforeEach(() => (options = { emitExposedDependencies: true }));
662
+
663
+ test('throws error', () => {
664
+ expect(subject).toThrow('package does not expose shared dependencies');
665
+ });
666
+ });
715
667
  });
@@ -0,0 +1,37 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import { Configuration } from 'webpack';
4
+ import { findUp } from '../../utils';
5
+ import { Context, Overrides } from './types';
6
+
7
+ type Config = Configuration['cache'];
8
+ type Result = Pick<Configuration, 'cache' | 'infrastructureLogging'> | undefined;
9
+
10
+ const CACHE_VERSION = '1';
11
+
12
+ export function cacheConfig(context: Context, _overrides: Overrides): Result {
13
+ const { emitExposedDependencies } = context;
14
+ if (!emitExposedDependencies) {
15
+ return;
16
+ }
17
+
18
+ const packageLock = findPackageLock();
19
+ const cache: Config = {
20
+ type: 'filesystem',
21
+ version: CACHE_VERSION,
22
+ ...(packageLock ? { buildDependencies: { packageLock: [packageLock] } } : {}),
23
+ };
24
+
25
+ return {
26
+ cache,
27
+ // Suppress "Serializing big strings impacts deserialization performance" warnings
28
+ infrastructureLogging: { level: 'error' },
29
+ };
30
+ }
31
+
32
+ function findPackageLock() {
33
+ return findUp(directory => {
34
+ const lockFile = path.resolve(path.join(directory), 'package-lock.json');
35
+ return fs.existsSync(lockFile) ? lockFile : undefined;
36
+ });
37
+ }
@@ -15,7 +15,7 @@ type DevServerConfig = NonNullable<Configuration['devServer']>;
15
15
  type Result = Pick<Configuration, 'devServer'> | undefined;
16
16
 
17
17
  export function devServerConfig(context: Context, overrides: Overrides): Result {
18
- if (context.isProduction || isDevServerDisabled()) {
18
+ if (context.isProduction || context.emitExposedDependencies || isDevServerDisabled()) {
19
19
  return;
20
20
  }
21
21
 
@@ -1,23 +1,33 @@
1
1
  import fs from 'fs';
2
2
  import { Configuration } from 'webpack';
3
3
  import { getModuleEntryPath } from '../utils';
4
+ import { getDesignSystemPath } from './plugins';
4
5
  import { Context, Overrides } from './types';
5
6
 
6
7
  type Config = Configuration['entry'];
7
8
  type Result = Pick<Configuration, 'entry'>;
8
9
 
9
10
  export function entryConfig(context: Context, _: Overrides): Result {
10
- const { destination, isExposeSharedDependencies, sharedDependencies, source } = context;
11
+ const entry: Config = context.emitExposedDependencies
12
+ ? getSharedEntryPoints(context)
13
+ : getIndexEntryPoint(context);
11
14
 
15
+ return { entry };
16
+ }
17
+
18
+ function getIndexEntryPoint({ source, destination }: Context): Config {
12
19
  const index = fs.existsSync(`./${source}/index.js`)
13
20
  ? `./${source}/index`
14
21
  : `./${destination}/index`;
22
+ return { main: [index] };
23
+ }
15
24
 
16
- const entry: Config = { main: [index] };
17
-
18
- if (isExposeSharedDependencies) {
19
- (entry.main as string[]).push(...Object.keys(sharedDependencies).map(getModuleEntryPath));
20
- }
21
-
22
- return { entry };
25
+ function getSharedEntryPoints(context: Context): Config {
26
+ const { sharedDependencies } = context;
27
+ return {
28
+ shared: Object.keys(sharedDependencies).map(getModuleEntryPath),
29
+ ...(sharedDependencies['@servicetitan/design-system']
30
+ ? { 'design-system': getDesignSystemPath(context) }
31
+ : {}),
32
+ };
23
33
  }
@@ -5,11 +5,12 @@ type Config = Configuration['externals'];
5
5
  type Result = Pick<Configuration, 'externals'> | undefined;
6
6
 
7
7
  export function externalsConfig(context: Context, _: Overrides): Result {
8
- const { embed, name, sharedDependencies } = context;
9
- if (!embed) {
8
+ if (!needsExternals(context)) {
10
9
  return;
11
10
  }
12
11
 
12
+ const { sharedDependencies } = context;
13
+ const name = context.name || context.packageData.name;
13
14
  const externals: Config = Object.fromEntries(
14
15
  Object.entries(sharedDependencies).map(([dependency, variable]) => [
15
16
  dependency,
@@ -19,3 +20,7 @@ export function externalsConfig(context: Context, _: Overrides): Result {
19
20
 
20
21
  return { externals };
21
22
  }
23
+
24
+ function needsExternals({ embed, emitExposedDependencies, isExposeSharedDependencies }: Context) {
25
+ return !!embed || (isExposeSharedDependencies && !emitExposedDependencies);
26
+ }
@@ -1,4 +1,5 @@
1
1
  export * from './amd-config';
2
+ export * from './cache-config';
2
3
  export * from './dev-server-config';
3
4
  export * from './devtool-config';
4
5
  export * from './entry.config';