@apollion-dsi/scripts 0.7.7

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 (121) hide show
  1. package/.prettierignore +6 -0
  2. package/.prettierrc.js +1 -0
  3. package/CHANGELOG.md +8 -0
  4. package/README.MD +91 -0
  5. package/audit-ci.json +5 -0
  6. package/eslint.config.js +33 -0
  7. package/lib/bin.d.ts +2 -0
  8. package/lib/bin.js +34 -0
  9. package/lib/bin.js.map +1 -0
  10. package/lib/command/build.d.ts +3 -0
  11. package/lib/command/build.js +145 -0
  12. package/lib/command/build.js.map +1 -0
  13. package/lib/command/create/helper.d.ts +7 -0
  14. package/lib/command/create/helper.js +98 -0
  15. package/lib/command/create/helper.js.map +1 -0
  16. package/lib/command/create/index.d.ts +1 -0
  17. package/lib/command/create/index.js +149 -0
  18. package/lib/command/create/index.js.map +1 -0
  19. package/lib/command/dev.d.ts +3 -0
  20. package/lib/command/dev.js +85 -0
  21. package/lib/command/dev.js.map +1 -0
  22. package/lib/command/test.d.ts +1 -0
  23. package/lib/command/test.js +32 -0
  24. package/lib/command/test.js.map +1 -0
  25. package/lib/config/env.d.ts +15 -0
  26. package/lib/config/env.js +78 -0
  27. package/lib/config/env.js.map +1 -0
  28. package/lib/config/modules.d.ts +15 -0
  29. package/lib/config/modules.js +92 -0
  30. package/lib/config/modules.js.map +1 -0
  31. package/lib/config/paths.d.ts +22 -0
  32. package/lib/config/paths.js +61 -0
  33. package/lib/config/paths.js.map +1 -0
  34. package/lib/config/shared.d.ts +24 -0
  35. package/lib/config/shared.js +14 -0
  36. package/lib/config/shared.js.map +1 -0
  37. package/lib/config/webpack.config.d.ts +187 -0
  38. package/lib/config/webpack.config.js +300 -0
  39. package/lib/config/webpack.config.js.map +1 -0
  40. package/lib/config/webpackDevServer.config.d.ts +34 -0
  41. package/lib/config/webpackDevServer.config.js +67 -0
  42. package/lib/config/webpackDevServer.config.js.map +1 -0
  43. package/lib/index.d.ts +3 -0
  44. package/lib/index.js +10 -0
  45. package/lib/index.js.map +1 -0
  46. package/lib/utils/FileSizeReporter.d.ts +12 -0
  47. package/lib/utils/FileSizeReporter.js +151 -0
  48. package/lib/utils/FileSizeReporter.js.map +1 -0
  49. package/lib/utils/InlineChunkHtmlPlugin.d.ts +8 -0
  50. package/lib/utils/InlineChunkHtmlPlugin.js +54 -0
  51. package/lib/utils/InlineChunkHtmlPlugin.js.map +1 -0
  52. package/lib/utils/InterpolateHtmlPlugin.d.ts +7 -0
  53. package/lib/utils/InterpolateHtmlPlugin.js +21 -0
  54. package/lib/utils/InterpolateHtmlPlugin.js.map +1 -0
  55. package/lib/utils/ModuleNotFoundPlugin.d.ts +5 -0
  56. package/lib/utils/ModuleNotFoundPlugin.js +11 -0
  57. package/lib/utils/ModuleNotFoundPlugin.js.map +1 -0
  58. package/lib/utils/ModuleScopePlugin.d.ts +5 -0
  59. package/lib/utils/ModuleScopePlugin.js +11 -0
  60. package/lib/utils/ModuleScopePlugin.js.map +1 -0
  61. package/lib/utils/WebpackDevServerUtils.d.ts +22 -0
  62. package/lib/utils/WebpackDevServerUtils.js +154 -0
  63. package/lib/utils/WebpackDevServerUtils.js.map +1 -0
  64. package/lib/utils/checkRequiredFiles.d.ts +1 -0
  65. package/lib/utils/checkRequiredFiles.js +28 -0
  66. package/lib/utils/checkRequiredFiles.js.map +1 -0
  67. package/lib/utils/devServerMiddleware.d.ts +8 -0
  68. package/lib/utils/devServerMiddleware.js +19 -0
  69. package/lib/utils/devServerMiddleware.js.map +1 -0
  70. package/lib/utils/formatWebpackMessages.d.ts +5 -0
  71. package/lib/utils/formatWebpackMessages.js +32 -0
  72. package/lib/utils/formatWebpackMessages.js.map +1 -0
  73. package/lib/utils/getPublicUrlOrPath.d.ts +1 -0
  74. package/lib/utils/getPublicUrlOrPath.js +39 -0
  75. package/lib/utils/getPublicUrlOrPath.js.map +1 -0
  76. package/lib/utils/printBuildError.d.ts +1 -0
  77. package/lib/utils/printBuildError.js +14 -0
  78. package/lib/utils/printBuildError.js.map +1 -0
  79. package/lib/utils/printHostingInstructions.d.ts +1 -0
  80. package/lib/utils/printHostingInstructions.js +12 -0
  81. package/lib/utils/printHostingInstructions.js.map +1 -0
  82. package/package.json +81 -0
  83. package/scripts/validate.sh +16 -0
  84. package/src/bin.ts +41 -0
  85. package/src/command/build.ts +188 -0
  86. package/src/command/create/helper.ts +97 -0
  87. package/src/command/create/index.ts +200 -0
  88. package/src/command/dev.ts +95 -0
  89. package/src/command/test.ts +33 -0
  90. package/src/config/env.ts +82 -0
  91. package/src/config/modules.ts +115 -0
  92. package/src/config/paths.ts +88 -0
  93. package/src/config/shared.ts +27 -0
  94. package/src/config/webpack.config.js +308 -0
  95. package/src/config/webpackDevServer.config.ts +78 -0
  96. package/src/index.ts +3 -0
  97. package/src/utils/FileSizeReporter.ts +168 -0
  98. package/src/utils/InlineChunkHtmlPlugin.js +67 -0
  99. package/src/utils/InterpolateHtmlPlugin.js +27 -0
  100. package/src/utils/ModuleNotFoundPlugin.js +13 -0
  101. package/src/utils/ModuleScopePlugin.js +13 -0
  102. package/src/utils/WebpackDevServerUtils.ts +179 -0
  103. package/src/utils/checkRequiredFiles.ts +22 -0
  104. package/src/utils/devServerMiddleware.ts +20 -0
  105. package/src/utils/formatWebpackMessages.ts +30 -0
  106. package/src/utils/getPublicUrlOrPath.ts +39 -0
  107. package/src/utils/printBuildError.ts +8 -0
  108. package/src/utils/printHostingInstructions.ts +12 -0
  109. package/template/.editorconfig +43 -0
  110. package/template/.prettierignore +6 -0
  111. package/template/.prettierrc.js +1 -0
  112. package/template/audit-ci.json +5 -0
  113. package/template/commitlint.config.js +1 -0
  114. package/template/eslint.config.js +3 -0
  115. package/template/jest.config.js +4 -0
  116. package/template/public/index.html +20 -0
  117. package/template/src/App.test.tsx +10 -0
  118. package/template/src/App.tsx +35 -0
  119. package/template/src/index.tsx +10 -0
  120. package/template/tsconfig.json +25 -0
  121. package/tsconfig.json +18 -0
@@ -0,0 +1,308 @@
1
+ import path from 'path';
2
+
3
+ import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
4
+ import InlineChunkHtmlPlugin from '../utils/InlineChunkHtmlPlugin';
5
+ import InterpolateHtmlPlugin from '../utils/InterpolateHtmlPlugin';
6
+ import ModuleNotFoundPlugin from '../utils/ModuleNotFoundPlugin';
7
+ import ModuleScopePlugin from '../utils/ModuleScopePlugin';
8
+ import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
9
+ import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin';
10
+ import GitRevision from 'git-revision-webpack-plugin';
11
+ import HtmlWebpackPlugin from 'html-webpack-plugin';
12
+ import resolve from 'resolve';
13
+ import TerserPlugin from 'terser-webpack-plugin';
14
+ import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin';
15
+ import webpack from 'webpack';
16
+ import { WebpackManifestPlugin } from 'webpack-manifest-plugin';
17
+
18
+ import getClientEnvironment from './env';
19
+ import modules from './modules';
20
+
21
+ export default (webpackEnv, paths, options = {}) => {
22
+ const appPackageJson = require(paths.appPackageJson);
23
+
24
+ const GitRevisionPlugin = new GitRevision({ branch: true });
25
+
26
+ const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
27
+
28
+ const imageInlineSizeLimit = parseInt(process.env.IMAGE_INLINE_SIZE_LIMIT || '10000');
29
+
30
+ const useTypeScript = true;
31
+
32
+ const hasJsxRuntime = (() => {
33
+ if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') {
34
+ return false;
35
+ }
36
+ try {
37
+ require.resolve('react/jsx-runtime');
38
+ return true;
39
+ } catch (e) {
40
+ return false;
41
+ }
42
+ })();
43
+
44
+ const isEnvDevelopment = webpackEnv === 'development';
45
+ const isEnvProduction = webpackEnv === 'production';
46
+ const shouldUseSourceMap = isEnvDevelopment;
47
+
48
+ const isEnvProductionProfile = isEnvProduction && process.argv.includes('--profile');
49
+
50
+ const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1), options);
51
+
52
+ const shouldUseReactRefresh = env.raw.FAST_REFRESH;
53
+
54
+ return {
55
+ mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
56
+ bail: isEnvProduction,
57
+ devtool: isEnvProduction
58
+ ? shouldUseSourceMap
59
+ ? 'source-map'
60
+ : false
61
+ : isEnvDevelopment && 'cheap-module-source-map',
62
+ // WDS v5 injects the HMR client automatically — no need to prepend it here
63
+ entry: paths.appIndexJs,
64
+ output: {
65
+ path: isEnvProduction ? paths.appBuild : undefined,
66
+ pathinfo: isEnvDevelopment,
67
+ filename: isEnvProduction ? 'static/js/[name].[contenthash:8].js' : isEnvDevelopment && 'static/js/bundle.js',
68
+ chunkFilename: isEnvProduction
69
+ ? 'static/js/[name].[contenthash:8].chunk.js'
70
+ : isEnvDevelopment && 'static/js/[name].chunk.js',
71
+ publicPath: paths.publicUrlOrPath,
72
+ devtoolModuleFilenameTemplate: isEnvProduction
73
+ ? (info) => path.relative(paths.appSrc, info.absoluteResourcePath).replace(/\\/g, '/')
74
+ : isEnvDevelopment && ((info) => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
75
+ // Replaces deprecated jsonpFunction
76
+ chunkLoadingGlobal: `webpackJsonp${appPackageJson.name}`,
77
+ globalObject: 'this',
78
+ },
79
+ optimization: {
80
+ minimize: isEnvProduction,
81
+ minimizer: [
82
+ new TerserPlugin({
83
+ terserOptions: {
84
+ parse: { ecma: 8 },
85
+ compress: {
86
+ ecma: 5,
87
+ warnings: false,
88
+ comparisons: false,
89
+ inline: 2,
90
+ },
91
+ mangle: { safari10: true },
92
+ keep_classnames: isEnvProductionProfile,
93
+ keep_fnames: isEnvProductionProfile,
94
+ output: {
95
+ ecma: 5,
96
+ comments: false,
97
+ ascii_only: true,
98
+ },
99
+ },
100
+ }),
101
+ ],
102
+ splitChunks: isEnvProduction
103
+ ? {
104
+ chunks: 'all',
105
+ name: false,
106
+ cacheGroups: {
107
+ commons: {
108
+ test: /[\\/]node_modules[\\/]/,
109
+ minSize: 50000,
110
+ name: 'vendors',
111
+ chunks: 'all',
112
+ },
113
+ },
114
+ }
115
+ : { chunks: 'all', name: false },
116
+ runtimeChunk: {
117
+ name: (entrypoint) => `runtime-${entrypoint.name}`,
118
+ },
119
+ },
120
+ resolve: {
121
+ modules: ['node_modules', paths.appNodeModules].concat(modules.additionalModulePaths || []),
122
+ extensions: paths.moduleFileExtensions
123
+ .map((ext) => `.${ext}`)
124
+ .filter((ext) => useTypeScript || !ext.includes('ts')),
125
+ alias: {
126
+ 'react-native': 'react-native-web',
127
+ ...(isEnvProductionProfile && {
128
+ 'react-dom$': 'react-dom/profiling',
129
+ 'scheduler/tracing': 'scheduler/tracing-profiling',
130
+ }),
131
+ ...(modules.webpackAliases || {}),
132
+ },
133
+ // Replaces Webpack 4 `node` polyfill shims — disable browser-irrelevant Node built-ins
134
+ fallback: {
135
+ dgram: false,
136
+ fs: false,
137
+ net: false,
138
+ tls: false,
139
+ child_process: false,
140
+ },
141
+ plugins: [
142
+ new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
143
+ new TsconfigPathsPlugin(),
144
+ ],
145
+ },
146
+ module: {
147
+ strictExportPresence: true,
148
+ rules: [
149
+ {
150
+ oneOf: [
151
+ // Webpack 5 asset modules replace url-loader and file-loader
152
+ {
153
+ test: /\.avif$/,
154
+ type: 'asset/inline',
155
+ mimetype: 'image/avif',
156
+ },
157
+ {
158
+ test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
159
+ type: 'asset',
160
+ parser: {
161
+ dataUrlCondition: { maxSize: imageInlineSizeLimit },
162
+ },
163
+ generator: {
164
+ filename: 'static/media/[name].[hash:8][ext]',
165
+ },
166
+ },
167
+ {
168
+ test: /\.(js|mjs|jsx|ts|tsx)$/,
169
+ include: paths.appSrc,
170
+ loader: require.resolve('babel-loader'),
171
+ options: {
172
+ babelrc: false,
173
+ configFile: false,
174
+ presets: [
175
+ [
176
+ require.resolve('@babel/preset-env'),
177
+ {
178
+ targets: 'defaults',
179
+ useBuiltIns: 'usage',
180
+ corejs: 3,
181
+ },
182
+ ],
183
+ [
184
+ require.resolve('@babel/preset-react'),
185
+ {
186
+ runtime: hasJsxRuntime ? 'automatic' : 'classic',
187
+ },
188
+ ],
189
+ require.resolve('@babel/preset-typescript'),
190
+ ],
191
+ plugins: [
192
+ !!options.relayArtifactDirectory && ['relay', { artifactDirectory: options.relayArtifactDirectory }],
193
+ [
194
+ 'babel-plugin-styled-components',
195
+ {
196
+ displayName: !isEnvProduction,
197
+ ssr: false,
198
+ transpileTemplateLiterals: isEnvProduction,
199
+ pure: true,
200
+ },
201
+ ],
202
+ isEnvDevelopment && shouldUseReactRefresh && require.resolve('react-refresh/babel'),
203
+ ].filter(Boolean),
204
+ cacheDirectory: true,
205
+ cacheCompression: false,
206
+ compact: isEnvProduction,
207
+ },
208
+ },
209
+ {
210
+ test: /\.(js|mjs)$/,
211
+ exclude: /@babel(?:\/|\\{1,2})runtime/,
212
+ loader: require.resolve('babel-loader'),
213
+ options: {
214
+ babelrc: false,
215
+ configFile: false,
216
+ compact: false,
217
+ presets: [[require.resolve('@babel/preset-env'), { targets: 'defaults' }]],
218
+ cacheDirectory: true,
219
+ cacheCompression: false,
220
+ sourceMaps: shouldUseSourceMap,
221
+ inputSourceMap: shouldUseSourceMap,
222
+ },
223
+ },
224
+ // Catch-all: replaces file-loader
225
+ {
226
+ exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
227
+ type: 'asset/resource',
228
+ generator: {
229
+ filename: 'static/media/[name].[hash:8][ext]',
230
+ },
231
+ },
232
+ ],
233
+ },
234
+ ],
235
+ },
236
+ plugins: [
237
+ new HtmlWebpackPlugin({
238
+ title: options.head.title,
239
+ inject: true,
240
+ template: paths.appHtml,
241
+ ...(isEnvProduction
242
+ ? {
243
+ minify: {
244
+ removeComments: true,
245
+ collapseWhitespace: true,
246
+ removeRedundantAttributes: true,
247
+ useShortDoctype: true,
248
+ removeEmptyAttributes: true,
249
+ removeStyleLinkTypeAttributes: true,
250
+ keepClosingSlash: true,
251
+ minifyJS: true,
252
+ minifyCSS: true,
253
+ minifyURLs: true,
254
+ },
255
+ }
256
+ : undefined),
257
+ }),
258
+ isEnvProduction && shouldInlineRuntimeChunk && new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
259
+ new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
260
+ new ModuleNotFoundPlugin(paths.appPath),
261
+ GitRevisionPlugin,
262
+ new webpack.DefinePlugin(
263
+ Object.assign(env.stringified, {
264
+ PACKAGE_VERSION: JSON.stringify(appPackageJson.version),
265
+ SERVICE: JSON.stringify(appPackageJson.name),
266
+ IS_PROD_MODE: !isEnvProduction,
267
+ VERSION: JSON.stringify(GitRevisionPlugin.version()),
268
+ COMMITHASH: JSON.stringify(GitRevisionPlugin.commithash()),
269
+ BRANCH: JSON.stringify(GitRevisionPlugin.branch()),
270
+ CONSTANTS: JSON.stringify(options.constants),
271
+ I18N_CONFIG: JSON.stringify(options.i18nConfig),
272
+ }),
273
+ ),
274
+ isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
275
+ isEnvDevelopment && shouldUseReactRefresh && new ReactRefreshWebpackPlugin(),
276
+ isEnvDevelopment && new CaseSensitivePathsPlugin(),
277
+ new WebpackManifestPlugin({
278
+ fileName: 'asset-manifest.json',
279
+ publicPath: paths.publicUrlOrPath,
280
+ generate: (seed, files, entrypoints) => {
281
+ const manifestFiles = files.reduce((manifest, file) => {
282
+ manifest[file.name] = file.path;
283
+ return manifest;
284
+ }, seed);
285
+ const entrypointFiles = entrypoints.main.filter((fileName) => !fileName.endsWith('.map'));
286
+ return { files: manifestFiles, entrypoints: entrypointFiles };
287
+ },
288
+ }),
289
+ // Updated Webpack 5 API — was: new webpack.IgnorePlugin(/pattern/, /context/)
290
+ new webpack.IgnorePlugin({ resourceRegExp: /^\.\/locale$/, contextRegExp: /moment$/ }),
291
+ useTypeScript &&
292
+ new ForkTsCheckerWebpackPlugin({
293
+ async: isEnvDevelopment,
294
+ typescript: {
295
+ typescriptPath: resolve.sync('typescript', {
296
+ basedir: paths.appNodeModules,
297
+ }),
298
+ configFile: paths.appTsConfig,
299
+ diagnosticOptions: {
300
+ semantic: true,
301
+ syntactic: true,
302
+ },
303
+ },
304
+ }),
305
+ ].filter(Boolean),
306
+ performance: false,
307
+ };
308
+ };
@@ -0,0 +1,78 @@
1
+ import fs from 'fs';
2
+
3
+ import { AppPaths } from './paths';
4
+
5
+ import {
6
+ evalSourceMapMiddleware,
7
+ ignoredFiles,
8
+ noopServiceWorkerMiddleware,
9
+ redirectServedPathMiddleware as redirectServedPath,
10
+ } from '../utils/devServerMiddleware';
11
+
12
+ export default (proxy: any, allowedHost: any, paths: AppPaths) => {
13
+ const host = process.env.HOST || '0.0.0.0';
14
+ const sockHost = process.env.WDS_SOCKET_HOST;
15
+ const sockPath = process.env.WDS_SOCKET_PATH;
16
+ const sockPort = process.env.WDS_SOCKET_PORT;
17
+
18
+ return {
19
+ // Replaces deprecated disableHostCheck
20
+ allowedHosts:
21
+ !proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true'
22
+ ? ('all' as const)
23
+ : ([allowedHost].filter(Boolean) as string[]),
24
+ compress: true,
25
+ // Replaces clientLogLevel, injectClient, sockHost/sockPath/sockPort, overlay
26
+ client: {
27
+ logging: 'none' as const,
28
+ overlay: false,
29
+ webSocketURL: {
30
+ hostname: sockHost,
31
+ pathname: sockPath,
32
+ port: sockPort ? Number(sockPort) : undefined,
33
+ },
34
+ },
35
+ // Replaces contentBase + contentBasePublicPath + watchContentBase
36
+ static: [
37
+ {
38
+ directory: paths.appPublic,
39
+ publicPath: [paths.publicUrlOrPath],
40
+ watch: {
41
+ ignored: ignoredFiles(paths.appSrc),
42
+ },
43
+ },
44
+ ],
45
+ hot: true,
46
+ // Replaces transportMode
47
+ webSocketServer: 'ws' as const,
48
+ // Replaces publicPath at root level
49
+ devMiddleware: {
50
+ publicPath: paths.publicUrlOrPath.slice(0, -1),
51
+ },
52
+ host,
53
+ historyApiFallback: {
54
+ disableDotRule: true as const,
55
+ index: paths.publicUrlOrPath,
56
+ },
57
+ // Replaces public
58
+ ...(allowedHost && !(!proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true')
59
+ ? { allowedHosts: [allowedHost] }
60
+ : {}),
61
+ proxy,
62
+ // Replaces before() + after() hooks
63
+ setupMiddlewares(middlewares: any[], devServer: any) {
64
+ devServer.app.use(evalSourceMapMiddleware(devServer));
65
+
66
+ if (fs.existsSync(paths.proxySetup)) {
67
+ require(paths.proxySetup)(devServer.app);
68
+ }
69
+
70
+ middlewares.push(
71
+ { name: 'redirect-served-path', middleware: redirectServedPath(paths.publicUrlOrPath) },
72
+ { name: 'noop-service-worker', middleware: noopServiceWorkerMiddleware(paths.publicUrlOrPath) },
73
+ );
74
+
75
+ return middlewares;
76
+ },
77
+ };
78
+ };
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { createAppPaths, AppPaths } from './config/paths';
2
+ export { createDevServer } from './command/dev';
3
+ export { createProductionBuild } from './command/build';
@@ -0,0 +1,168 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import zlib from 'zlib';
4
+
5
+ import chalk from 'chalk';
6
+
7
+ function filesize(bytes: number): string {
8
+ if (!Number.isFinite(bytes)) return '0 B';
9
+ const sign = bytes < 0 ? '-' : '';
10
+ const abs = Math.abs(bytes);
11
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
12
+ let i = 0;
13
+ let n = abs;
14
+ while (n >= 1024 && i < units.length - 1) {
15
+ n /= 1024;
16
+ i++;
17
+ }
18
+ const value = i === 0 ? n.toFixed(0) : n.toFixed(2);
19
+ return `${sign}${value} ${units[i]}`;
20
+ }
21
+
22
+ type SizeMap = Record<string, number>;
23
+
24
+ export type PreviousFileSizes = {
25
+ root: string;
26
+ sizes: SizeMap;
27
+ };
28
+
29
+ function canReadAsset(asset: string): boolean {
30
+ return (
31
+ /\.(js|css)$/.test(asset) && !/service-worker\.js/.test(asset) && !/precache-manifest\.[0-9a-f]+\.js/.test(asset)
32
+ );
33
+ }
34
+
35
+ function gzipSize(buf: Buffer): number {
36
+ return zlib.gzipSync(buf, { level: 6 }).length;
37
+ }
38
+
39
+ function walkSync(dir: string, out: string[] = []): string[] {
40
+ if (!fs.existsSync(dir)) return out;
41
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
42
+ const full = path.join(dir, entry.name);
43
+ if (entry.isDirectory()) {
44
+ walkSync(full, out);
45
+ } else if (entry.isFile()) {
46
+ out.push(full);
47
+ }
48
+ }
49
+ return out;
50
+ }
51
+
52
+ function removeFileNameHash(buildFolder: string, fileName: string): string {
53
+ return fileName
54
+ .replace(buildFolder, '')
55
+ .replace(/\\/g, '/')
56
+ .replace(/\/?(.*)(\.[0-9a-f]+)(\.chunk)?(\.js|\.css)/, (_match, p1, _p2, p3, p4) => p1 + (p3 || '') + p4);
57
+ }
58
+
59
+ function getDifferenceLabel(currentSize: number, previousSize: number): string {
60
+ const FIFTY_KILOBYTES = 1024 * 50;
61
+ const difference = currentSize - previousSize;
62
+ const fileSize = !Number.isNaN(difference) ? filesize(difference) : '';
63
+ if (difference >= FIFTY_KILOBYTES) {
64
+ return chalk.red('+' + fileSize);
65
+ } else if (difference < FIFTY_KILOBYTES && difference > 0) {
66
+ return chalk.yellow('+' + fileSize);
67
+ } else if (difference < 0) {
68
+ return chalk.green(fileSize);
69
+ }
70
+ return '';
71
+ }
72
+
73
+ export function measureFileSizesBeforeBuild(buildFolder: string): Promise<PreviousFileSizes> {
74
+ return new Promise((resolve) => {
75
+ try {
76
+ const files = walkSync(buildFolder).filter(canReadAsset);
77
+ const sizes: SizeMap = {};
78
+ files.forEach((file) => {
79
+ try {
80
+ const contents = fs.readFileSync(file);
81
+ const key = removeFileNameHash(buildFolder, file);
82
+ sizes[key] = gzipSize(contents);
83
+ } catch {
84
+ /* ignore */
85
+ }
86
+ });
87
+ resolve({ root: buildFolder, sizes });
88
+ } catch {
89
+ resolve({ root: buildFolder, sizes: {} });
90
+ }
91
+ });
92
+ }
93
+
94
+ export function printFileSizesAfterBuild(
95
+ webpackStats: any,
96
+ previousSizeMap: PreviousFileSizes,
97
+ buildFolder: string,
98
+ maxBundleGzipSize: number,
99
+ maxChunkGzipSize: number,
100
+ ): void {
101
+ const root = previousSizeMap?.root || buildFolder;
102
+ const sizes = previousSizeMap?.sizes || {};
103
+
104
+ const statsArr = webpackStats && typeof webpackStats.stats !== 'undefined' ? webpackStats.stats : [webpackStats];
105
+
106
+ const assets: Array<{ folder: string; name: string; size: number; sizeLabel: string }> = [];
107
+
108
+ statsArr.forEach((stats: any) => {
109
+ if (!stats) return;
110
+ const statsAssets = stats.toJson({ all: false, assets: true }).assets || [];
111
+ statsAssets
112
+ .filter((a: any) => canReadAsset(a.name))
113
+ .forEach((asset: any) => {
114
+ const filePath = path.join(root, asset.name);
115
+ let size = 0;
116
+ try {
117
+ const contents = fs.readFileSync(filePath);
118
+ size = gzipSize(contents);
119
+ } catch {
120
+ size = asset.size || 0;
121
+ }
122
+ const previousSize = sizes[removeFileNameHash(root, filePath)];
123
+ const difference = getDifferenceLabel(size, previousSize);
124
+ assets.push({
125
+ folder: path.join(path.basename(buildFolder), path.dirname(asset.name)),
126
+ name: path.basename(asset.name),
127
+ size,
128
+ sizeLabel: filesize(size) + (difference ? ' (' + difference + ')' : ''),
129
+ });
130
+ });
131
+ });
132
+
133
+ assets.sort((a, b) => b.size - a.size);
134
+
135
+ const longestSizeLabelLength = assets.length ? Math.max(...assets.map((a) => stripAnsi(a.sizeLabel).length)) : 0;
136
+ let suggestBundleSplitting = false;
137
+
138
+ assets.forEach((asset) => {
139
+ let { sizeLabel } = asset;
140
+ const sizeLength = stripAnsi(sizeLabel).length;
141
+ if (sizeLength < longestSizeLabelLength) {
142
+ sizeLabel += ' '.repeat(longestSizeLabelLength - sizeLength);
143
+ }
144
+ const isMainBundle = asset.name.indexOf('main.') === 0;
145
+ const maxRecommendedSize = isMainBundle ? maxBundleGzipSize : maxChunkGzipSize;
146
+ const isLarge = maxRecommendedSize && asset.size > maxRecommendedSize;
147
+ if (isLarge && /\.js$/.test(asset.name)) suggestBundleSplitting = true;
148
+ console.log(
149
+ ' ' +
150
+ (isLarge ? chalk.yellow(sizeLabel) : sizeLabel) +
151
+ ' ' +
152
+ chalk.dim(asset.folder + path.sep) +
153
+ chalk.cyan(asset.name),
154
+ );
155
+ });
156
+
157
+ if (suggestBundleSplitting) {
158
+ console.log();
159
+ console.log(chalk.yellow('The bundle size is significantly larger than recommended.'));
160
+ }
161
+ }
162
+
163
+ function stripAnsi(s: string): string {
164
+ // eslint-disable-next-line no-control-regex
165
+ return s.replace(/\x1B\[[0-9;]*m/g, '');
166
+ }
167
+
168
+ export default { measureFileSizesBeforeBuild, printFileSizesAfterBuild };
@@ -0,0 +1,67 @@
1
+ 'use strict';
2
+
3
+ class InlineChunkHtmlPlugin {
4
+ constructor(htmlWebpackPlugin, tests) {
5
+ this.htmlWebpackPlugin = htmlWebpackPlugin;
6
+ this.tests = tests || [];
7
+ }
8
+
9
+ getInlinedTag(publicPath, assets, tag) {
10
+ if (tag.tagName !== 'script' || !(tag.attributes && tag.attributes.src)) {
11
+ return tag;
12
+ }
13
+ const scriptName = publicPath
14
+ ? tag.attributes.src.replace(publicPath, '')
15
+ : tag.attributes.src;
16
+
17
+ if (!this.tests.some((test) => scriptName.match(test))) {
18
+ return tag;
19
+ }
20
+
21
+ const asset = assets[scriptName];
22
+ if (asset == null) {
23
+ return tag;
24
+ }
25
+
26
+ return {
27
+ tagName: 'script',
28
+ innerHTML: asset.source(),
29
+ closeTag: true,
30
+ };
31
+ }
32
+
33
+ apply(compiler) {
34
+ let publicPath = compiler.options.output.publicPath || '';
35
+ if (publicPath && !publicPath.endsWith('/')) {
36
+ publicPath += '/';
37
+ }
38
+
39
+ compiler.hooks.compilation.tap('InlineChunkHtmlPlugin', (compilation) => {
40
+ const tagFunction = (tag) =>
41
+ this.getInlinedTag(publicPath, compilation.assets, tag);
42
+
43
+ const hooks = this.htmlWebpackPlugin.getHooks(compilation);
44
+ hooks.alterAssetTagGroups.tap('InlineChunkHtmlPlugin', (assets) => {
45
+ assets.headTags = assets.headTags.map(tagFunction);
46
+ assets.bodyTags = assets.bodyTags.map(tagFunction);
47
+ });
48
+
49
+ // Remove inlined assets from the output (best-effort)
50
+ compilation.hooks.processAssets.tap(
51
+ {
52
+ name: 'InlineChunkHtmlPlugin',
53
+ stage: compilation.constructor.PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE || 700,
54
+ },
55
+ (assets) => {
56
+ Object.keys(assets).forEach((assetName) => {
57
+ if (this.tests.some((test) => assetName.match(test))) {
58
+ delete compilation.assets[assetName];
59
+ }
60
+ });
61
+ },
62
+ );
63
+ });
64
+ }
65
+ }
66
+
67
+ module.exports = InlineChunkHtmlPlugin;
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+
3
+ const escapeStringRegexp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
4
+
5
+ class InterpolateHtmlPlugin {
6
+ constructor(htmlWebpackPlugin, replacements) {
7
+ this.htmlWebpackPlugin = htmlWebpackPlugin;
8
+ this.replacements = replacements || {};
9
+ }
10
+
11
+ apply(compiler) {
12
+ compiler.hooks.compilation.tap('InterpolateHtmlPlugin', (compilation) => {
13
+ const hooks = this.htmlWebpackPlugin.getHooks(compilation);
14
+ hooks.afterTemplateExecution.tap('InterpolateHtmlPlugin', (data) => {
15
+ Object.keys(this.replacements).forEach((key) => {
16
+ const value = this.replacements[key];
17
+ data.html = data.html.replace(
18
+ new RegExp('%' + escapeStringRegexp(key) + '%', 'g'),
19
+ value == null ? '' : String(value),
20
+ );
21
+ });
22
+ });
23
+ });
24
+ }
25
+ }
26
+
27
+ module.exports = InterpolateHtmlPlugin;
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ class ModuleNotFoundPlugin {
4
+ constructor(_appPath, _yarnLockFile) {
5
+ // no-op: original plugin provided nicer error messages.
6
+ }
7
+
8
+ apply(_compiler) {
9
+ // no-op
10
+ }
11
+ }
12
+
13
+ module.exports = ModuleNotFoundPlugin;
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ class ModuleScopePlugin {
4
+ constructor(_appSrc, _allowedFiles) {
5
+ // no-op
6
+ }
7
+
8
+ apply(_resolver) {
9
+ // no-op
10
+ }
11
+ }
12
+
13
+ module.exports = ModuleScopePlugin;