package-build-stats 7.3.8 → 8.0.0-beta.1

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 (47) hide show
  1. package/.parcelrc +37 -0
  2. package/package.json +96 -68
  3. package/src/common.types.ts +3 -10
  4. package/src/config/{config.ts → index.ts} +1 -0
  5. package/{build → src}/errors/CustomError.d.ts +0 -0
  6. package/{build → src}/errors/CustomError.js +1 -0
  7. package/src/errors/CustomError.js.map +1 -0
  8. package/src/fixed/parseReference.js +762 -727
  9. package/src/getPackageExportSizes.ts +33 -14
  10. package/src/getPackageStats.ts +55 -16
  11. package/src/typings/is-valid-npm-name.d.ts +3 -0
  12. package/src/utils/build.utils.ts +258 -230
  13. package/src/utils/common.utils.ts +138 -0
  14. package/src/utils/exports.utils.ts +34 -21
  15. package/src/utils/installation.utils.ts +28 -5
  16. package/src/utils/telemetry.utils.ts +0 -21
  17. package/LICENSE +0 -21
  18. package/README.md +0 -67
  19. package/build/common.types.d.ts +0 -34
  20. package/build/common.types.js +0 -2
  21. package/build/config/config.d.ts +0 -4
  22. package/build/config/config.js +0 -10
  23. package/build/config/makeWebpackConfig.d.ts +0 -11
  24. package/build/config/makeWebpackConfig.js +0 -225
  25. package/build/fixed/parseReference.js +0 -5353
  26. package/build/getDependencySizeTree.d.ts +0 -6
  27. package/build/getDependencySizeTree.js +0 -238
  28. package/build/getPackageExportSizes.d.ts +0 -44
  29. package/build/getPackageExportSizes.js +0 -77
  30. package/build/getPackageStats.d.ts +0 -73
  31. package/build/getPackageStats.js +0 -90
  32. package/build/getParseTime.d.ts +0 -8
  33. package/build/getParseTime.js +0 -49
  34. package/build/index.d.ts +0 -5
  35. package/build/index.js +0 -24
  36. package/build/utils/build.utils.d.ts +0 -84
  37. package/build/utils/build.utils.js +0 -261
  38. package/build/utils/common.utils.d.ts +0 -19
  39. package/build/utils/common.utils.js +0 -118
  40. package/build/utils/exports.utils.d.ts +0 -17
  41. package/build/utils/exports.utils.js +0 -238
  42. package/build/utils/installation.utils.d.ts +0 -8
  43. package/build/utils/installation.utils.js +0 -122
  44. package/build/utils/telemetry.utils.d.ts +0 -15
  45. package/build/utils/telemetry.utils.js +0 -127
  46. package/src/config/makeWebpackConfig.ts +0 -251
  47. package/src/getDependencySizeTree.ts +0 -266
@@ -1,15 +1,12 @@
1
1
  import path from 'path'
2
2
 
3
3
  const log = require('debug')('bp:worker')
4
- import webpack, { Entry } from 'webpack'
5
- import MemoryFS from 'memory-fs'
6
4
  import isValidNPMName from 'is-valid-npm-name'
7
- import { gzipSync } from 'zlib'
8
5
  import fs from 'fs'
9
- import getDependencySizes from '../getDependencySizeTree'
10
6
  import getParseTime from '../getParseTime'
11
- import makeWebpackConfig from '../config/makeWebpackConfig'
12
7
  import { performance } from 'perf_hooks'
8
+ import { gzip } from 'node-gzip'
9
+ import { Parcel } from '@parcel/core'
13
10
 
14
11
  import {
15
12
  BuildError,
@@ -20,24 +17,33 @@ import {
20
17
  } from '../errors/CustomError'
21
18
  import {
22
19
  Externals,
23
- WebpackError,
24
20
  BuildPackageOptions,
25
21
  CreateEntryPointOptions,
26
22
  } from '../common.types'
27
23
  import Telemetry from './telemetry.utils'
24
+ import {
25
+ printDiagnosticError,
26
+ updateProjectPeerDependencies,
27
+ } from './common.utils'
28
+ import ThrowableDiagnostic, { Diagnostic } from '@parcel/diagnostic'
29
+ import config from '../config'
30
+ import pAll from 'p-all'
28
31
 
29
32
  type CompilePackageArgs = {
30
33
  name: string
31
34
  externals: Externals
32
- entry: Entry
35
+ entry: EntryObject
33
36
  debug?: boolean
34
37
  minifier: 'terser' | 'esbuild'
38
+ installPath: string
35
39
  }
36
40
 
37
- type CompilePackageReturn = {
38
- stats: webpack.Stats
39
- error: WebpackError
40
- memoryFileSystem: MemoryFS
41
+ type CompileEntryArgs = {
42
+ name: string
43
+ externals: Externals
44
+ entry: any
45
+ installPath: string
46
+ measureComposition: boolean
41
47
  }
42
48
 
43
49
  type BuildPackageArgs = {
@@ -47,18 +53,115 @@ type BuildPackageArgs = {
47
53
  options: BuildPackageOptions
48
54
  }
49
55
 
50
- type WebpackStatsAsset = NonNullable<webpack.Stats.ToJsonOutput['assets']>[0]
56
+ type EntryObject = {
57
+ [key: string]: string
58
+ }
59
+
60
+ type CompiledAssetStat = {
61
+ file: string
62
+ size: number
63
+ }
64
+ type CompilePackageReturn = {
65
+ assets: CompiledAssetStat[]
66
+ }
67
+
68
+ type BuiltAssetStat = {
69
+ name: string
70
+ type: string
71
+ size: number
72
+ gzip: number
73
+ parse: { baseParseTime?: number; scriptParseTime?: number } | null
74
+ }
75
+
76
+ type BuildPackageReturn = {
77
+ assets: BuiltAssetStat[]
78
+ }
79
+
80
+ function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
81
+ return value !== null && value !== undefined
82
+ }
83
+
84
+ function isDiagnosticError(error: any): error is ThrowableDiagnostic {
85
+ return 'diagnostics' in error
86
+ }
87
+
88
+ function parseMissingModules(errors: Diagnostic[]) {
89
+ const missingModuleErrors = errors.filter(
90
+ error =>
91
+ error.message.startsWith('Failed to resolve') &&
92
+ error.origin === '@parcel/core'
93
+ )
94
+ if (!missingModuleErrors.length) {
95
+ return []
96
+ }
97
+ // There's a better way to get the missing module's name, maybe ?
98
+ const missingModuleRegex = /Failed to resolve '(.+)' from/
99
+ const missingModules = missingModuleErrors.map(err => {
100
+ const matches = err.message.match(missingModuleRegex)
101
+ if (!matches) {
102
+ throw new UnexpectedBuildError(
103
+ 'Expected to find a file path in the module not found error, but found none. Regex for this might be out of date.'
104
+ )
105
+ }
106
+ const missingFilePath = matches[1]
107
+ let packageNameMatch
108
+ if (missingFilePath.startsWith('@')) {
109
+ packageNameMatch = missingFilePath.match(/@[^\/]+\/[^\/]+/) // @babel/runtime/object/create -> @babel/runtime
110
+ } else {
111
+ packageNameMatch = missingFilePath.match(/[^\/]+/) // babel-runtime/object/create -> babel-runtime
112
+ }
113
+ if (!packageNameMatch) {
114
+ throw new UnexpectedBuildError(
115
+ 'Failed to resolve the missing package name. Regex for this might be out of date.'
116
+ )
117
+ }
118
+ return packageNameMatch[0]
119
+ })
120
+ let uniqueMissingModules = Array.from(new Set(missingModules))
121
+ uniqueMissingModules = uniqueMissingModules.filter(
122
+ mod => !mod.startsWith(`${uniqueMissingModules[0]}/`)
123
+ )
124
+ return uniqueMissingModules
125
+ }
126
+
127
+ async function getAssetStats(
128
+ assets: CompiledAssetStat[],
129
+ options: BuildPackageOptions
130
+ ) {
131
+ const getAssetStats = async (asset: CompiledAssetStat) => {
132
+ if (!asset.file) return null
133
+ const bundleContents = fs.readFileSync(asset.file, 'utf8')
134
+ let parseTimes = null
135
+ if (options.calcParse) {
136
+ parseTimes = getParseTime(bundleContents)
137
+ }
138
+ const gzipSize = (await gzip(bundleContents)).length
139
+ const { ext, name } = path.parse(asset.file)
140
+ return {
141
+ name: name,
142
+ type: ext.slice(1),
143
+ size: asset.size,
144
+ gzip: gzipSize,
145
+ parse: parseTimes,
146
+ }
147
+ }
148
+ const assetStatsPromises = assets
149
+ .filter(asset => !!asset.file)
150
+ .map(getAssetStats)
151
+
152
+ const assetStats = (await Promise.all(assetStatsPromises)).filter(notEmpty)
51
153
 
52
- const BuildUtils = {
53
- createEntryPoint(
154
+ return assetStats
155
+ }
156
+
157
+ class BuildUtils {
158
+ static createEntryPoint(
54
159
  packageName: string,
55
160
  installPath: string,
56
161
  options: CreateEntryPointOptions
57
162
  ) {
58
- const entryPath = path.join(
59
- installPath,
60
- options.entryFilename || 'index.js'
61
- )
163
+ const entryFilename = options.entryFilename || 'index.js'
164
+ const entryPath = path.join(installPath, entryFilename)
62
165
 
63
166
  let importStatement: string
64
167
 
@@ -83,113 +186,127 @@ const BuildUtils = {
83
186
  importStatement = `const p = require('${packageName}'); console.log(p)`
84
187
  }
85
188
  }
86
-
87
189
  try {
88
190
  fs.writeFileSync(entryPath, importStatement, 'utf-8')
89
191
  return entryPath
90
192
  } catch (err) {
91
193
  throw new EntryPointError(err)
92
194
  }
93
- },
195
+ }
94
196
 
95
- compilePackage({
96
- name,
197
+ static async compileEntry({
97
198
  entry,
98
199
  externals,
99
- debug,
100
- minifier,
101
- }: CompilePackageArgs) {
102
- const startTime = performance.now()
103
- const compiler = webpack(
104
- makeWebpackConfig({
105
- packageName: name,
106
- entry,
107
- externals,
108
- debug,
109
- minifier,
110
- })
200
+ installPath,
201
+ measureComposition,
202
+ }: CompileEntryArgs): Promise<CompilePackageReturn> {
203
+ await updateProjectPeerDependencies(
204
+ installPath,
205
+ Object.fromEntries(
206
+ [...externals.externalBuiltIns, ...externals.externalPackages].map(
207
+ pack => [pack, '*']
208
+ )
209
+ )
111
210
  )
112
- const memoryFileSystem = new MemoryFS()
113
- compiler.outputFileSystem = memoryFileSystem
114
-
115
- return new Promise<CompilePackageReturn>(resolve => {
116
- compiler.run((err, stats) => {
117
- const error = (err as unknown) as WebpackError // Webpack types incorrect
118
- // stats object can be empty if there are build errors
119
- resolve({ stats, error, memoryFileSystem })
120
-
121
- if (error) {
122
- console.error(error)
123
- Telemetry.compilePackage(name, false, startTime, { minifier }, error)
124
- } else {
125
- Telemetry.compilePackage(name, true, startTime, { minifier })
126
- }
127
- })
128
- })
129
- },
130
211
 
131
- _parseMissingModules(errors: Array<WebpackError>) {
132
- const missingModuleErrors = errors.filter(
133
- error => error.name === 'ModuleNotFoundError'
134
- )
212
+ // We use this as a way to pass options to the reporter plugin
213
+ process.env.MEASURE_COMPOSISION = `${measureComposition}`
214
+
215
+ let bundler = new Parcel({
216
+ entries: [entry],
217
+ mode: 'production',
218
+ env: Object.assign(Object.assign({}, process.env), {
219
+ NODE_ENV: 'production',
220
+ }),
221
+ defaultConfig: '@parcel/config-default',
222
+ shouldAutoInstall: false,
223
+ cacheDir: path.join(config.tmp, 'parcel-cache'),
224
+ config: require.resolve(path.join(__dirname, '..', '..', '.parcelrc')),
225
+ shouldDisableCache: true,
226
+ shouldContentHash: false,
227
+ defaultTargetOptions: {
228
+ sourceMaps: measureComposition,
229
+ outputFormat: 'global',
230
+ shouldOptimize: true,
231
+ isLibrary: false,
232
+ engines: {
233
+ browsers: [
234
+ 'last 5 Chrome versions',
235
+ 'last 5 Firefox versions',
236
+ 'Safari >= 9',
237
+ 'edge >= 12',
238
+ ],
239
+ },
240
+ },
241
+ })
135
242
 
136
- if (!missingModuleErrors.length) {
137
- return []
243
+ const assets = []
244
+ let { bundleGraph } = await bundler.run()
245
+ for (let bundle of bundleGraph.getBundles()) {
246
+ assets.push({
247
+ file: bundle.filePath,
248
+ size: bundle.stats.size,
249
+ })
138
250
  }
139
251
 
140
- // There's a better way to get the missing module's name, maybe ?
141
- const missingModuleRegex = /Can't resolve '(.+)' in/
252
+ return { assets }
253
+ }
142
254
 
143
- const missingModules = missingModuleErrors.map(err => {
144
- const matches = err.error.toString().match(missingModuleRegex)
255
+ static async compilePackage({
256
+ name,
257
+ entry,
258
+ externals,
259
+ minifier,
260
+ installPath,
261
+ }: CompilePackageArgs): Promise<CompilePackageReturn> {
262
+ const startTime = performance.now()
145
263
 
146
- if (!matches) {
147
- throw new UnexpectedBuildError(
148
- 'Expected to find a file path in the module not found error, but found none. Regex for this might be out of date.'
149
- )
150
- }
264
+ let allAssets: CompiledAssetStat[] = []
265
+ const entries = Object.values(entry)
266
+ const entryPromises = entries.map(entry => async () => {
267
+ const { assets } = await BuildUtils.compileEntry({
268
+ name,
269
+ entry,
270
+ measureComposition: entries.length === 1,
271
+ externals,
272
+ installPath,
273
+ })
274
+ log('Building entry %s', entry)
275
+ allAssets = [...allAssets, ...assets]
276
+ })
151
277
 
152
- const missingFilePath = matches[1]
153
- let packageNameMatch
154
- if (missingFilePath.startsWith('@')) {
155
- packageNameMatch = missingFilePath.match(/@[^\/]+\/[^\/]+/) // @babel/runtime/object/create -> @babel/runtime
278
+ try {
279
+ await pAll(entryPromises, { concurrency: 1 })
280
+ Telemetry.compilePackage(name, true, startTime, { minifier })
281
+ return { assets: allAssets }
282
+ } catch (err) {
283
+ if (isDiagnosticError(err)) {
284
+ printDiagnosticError(err)
156
285
  } else {
157
- packageNameMatch = missingFilePath.match(/[^\/]+/) // babel-runtime/object/create -> babel-runtime
158
- }
159
-
160
- if (!packageNameMatch) {
161
- throw new UnexpectedBuildError(
162
- 'Failed to resolve the missing package name. Regex for this might be out of date.'
163
- )
286
+ console.error(err)
164
287
  }
288
+ Telemetry.compilePackage(name, false, startTime, { minifier }, err)
289
+ throw err
290
+ }
291
+ }
165
292
 
166
- return packageNameMatch[0]
167
- })
168
-
169
- let uniqueMissingModules = Array.from(new Set(missingModules))
170
- uniqueMissingModules = uniqueMissingModules.filter(
171
- mod => !mod.startsWith(`${uniqueMissingModules[0]}/`)
172
- )
173
-
174
- return uniqueMissingModules
175
- },
176
-
177
- async buildPackage({
293
+ static async buildPackage({
178
294
  name,
179
295
  installPath,
180
296
  externals,
181
297
  options,
182
- }: BuildPackageArgs) {
183
- let entry: Entry = {}
298
+ }: BuildPackageArgs): Promise<BuildPackageReturn> {
299
+ let entry: EntryObject = {}
184
300
 
185
301
  if (options.splitCustomImports) {
186
302
  if (!options.customImports || !options.customImports.length) {
187
303
  return { assets: [] }
188
304
  }
189
- options.customImports.forEach(importt => {
305
+
306
+ options.customImports.forEach((importt: string) => {
190
307
  entry[importt] = BuildUtils.createEntryPoint(name, installPath, {
191
308
  customImports: [importt],
192
- entryFilename: importt,
309
+ entryFilename: `${importt}.js`,
193
310
  esm: true,
194
311
  })
195
312
  })
@@ -201,139 +318,49 @@ const BuildUtils = {
201
318
  }
202
319
 
203
320
  log('build start %s', name)
204
- const { stats, error, memoryFileSystem } = await BuildUtils.compilePackage({
205
- name,
206
- entry,
207
- externals,
208
- debug: options.debug,
209
- minifier: options.minifier,
210
- })
211
321
 
212
- log('build end %s', name)
213
-
214
- const jsonStatsStartTime = performance.now()
215
- let jsonStats = stats.toJson({
216
- assets: true,
217
- children: false,
218
- chunks: false,
219
- chunkGroups: false,
220
- chunkModules: false,
221
- chunkOrigins: false,
222
- modules: true,
223
- errorDetails: false,
224
- entrypoints: false,
225
- reasons: false,
226
- maxModules: 500,
227
- performance: false,
228
- source: true,
229
- depth: true,
230
- providedExports: true,
231
- warnings: false,
232
- modulesSort: 'depth',
233
- })
234
-
235
- if (!jsonStats) {
236
- Telemetry.parseWebpackStats(name, false, jsonStatsStartTime)
237
- throw new UnexpectedBuildError(
238
- 'Expected webpack json stats to be non-null, but was null'
239
- )
240
- } else {
241
- Telemetry.parseWebpackStats(name, true, jsonStatsStartTime)
242
- }
243
-
244
- if (error && !stats) {
245
- throw new BuildError(error)
246
- } else if (stats.compilation.errors && stats.compilation.errors.length) {
247
- const missingModules = BuildUtils._parseMissingModules(
248
- stats.compilation.errors
249
- )
250
-
251
- if (missingModules.length) {
252
- if (missingModules.length === 1 && missingModules[0] === name) {
253
- throw new EntryPointError(
254
- stats.compilation.errors.map(err => err.toString())
255
- )
256
- } else {
257
- throw new MissingDependencyError(
258
- stats.compilation.errors.map(err => err.toString()),
259
- { missingModules }
260
- )
261
- }
262
- } else if (jsonStats.errors && jsonStats.errors.length > 0) {
263
- if (
264
- jsonStats.errors.some(error =>
265
- error.includes("Unexpected character '#'")
266
- )
267
- ) {
268
- throw new CLIBuildError(jsonStats.errors)
269
- } else {
270
- throw new BuildError(jsonStats.errors)
271
- }
272
- } else {
273
- throw new UnexpectedBuildError(
274
- 'The webpack stats object was unexpectedly empty'
275
- )
276
- }
277
- } else {
278
- const getAssetStats = (asset: WebpackStatsAsset) => {
279
- const bundle = path.join(process.cwd(), 'dist', asset.name)
280
- const bundleContents = memoryFileSystem.readFileSync(bundle)
281
- let parseTimes = null
282
- if (options.calcParse) {
283
- parseTimes = getParseTime(bundleContents)
284
- }
285
-
286
- const gzip = gzipSync(bundleContents, {}).length
287
- const matches = asset.name.match(/(.+?)\.bundle\.(.+)$/)
288
-
289
- if (!matches) {
290
- throw new UnexpectedBuildError(
291
- 'Found an asset without the `.bundle` suffix. ' +
292
- 'A loader customization might be needed to recognize this asset type' +
293
- asset.name
294
- )
295
- }
296
-
297
- const [, entryName, extension] = matches
298
-
299
- return {
300
- name: entryName,
301
- type: extension,
302
- size: asset.size,
303
- gzip,
304
- parse: parseTimes,
305
- }
306
- }
307
-
308
- const assetsGzipStartTime = performance.now()
309
- const assetStats = jsonStats?.assets
310
- ?.filter(asset => !asset.chunkNames.includes('runtime'))
311
- .filter(asset => !asset.name.endsWith('LICENSE.txt'))
312
- .map(getAssetStats)
313
- Telemetry.assetsGZIPParseTime(name, assetsGzipStartTime)
322
+ try {
323
+ const { assets } = await BuildUtils.compilePackage({
324
+ name,
325
+ entry,
326
+ installPath,
327
+ externals,
328
+ debug: options.debug,
329
+ minifier: options.minifier,
330
+ })
314
331
 
315
- log('build result %O', assetStats)
332
+ log('build end %s', name)
333
+ log('after compile assets %o', assets)
316
334
 
335
+ const assetStats = await getAssetStats(assets, options)
317
336
  return {
318
337
  assets: assetStats || [],
319
338
  ...(options.includeDependencySizes && {
320
- dependencySizes: await getDependencySizes(
321
- name,
322
- jsonStats,
323
- options.minifier
324
- ),
339
+ dependencySizes: require(path.join(installPath, 'composition.json')),
325
340
  }),
326
341
  }
342
+ } catch (error) {
343
+ if (isDiagnosticError(error)) {
344
+ const missingModules = parseMissingModules(error.diagnostics)
345
+ if (missingModules.length) {
346
+ if (missingModules.length === 1 && missingModules[0] === name) {
347
+ throw new EntryPointError(error.diagnostics)
348
+ } else {
349
+ throw new MissingDependencyError(error.diagnostics, {
350
+ missingModules,
351
+ })
352
+ }
353
+ }
354
+ }
355
+ throw new BuildError(error)
327
356
  }
328
- },
329
- async buildPackageIgnoringMissingDeps({
330
- name,
331
- externals,
332
- installPath,
333
- options,
334
- }: BuildPackageArgs) {
357
+ }
358
+
359
+ static async buildPackageIgnoringMissingDeps(
360
+ { name, externals, installPath, options }: BuildPackageArgs,
361
+ buildIteration: number
362
+ ): Promise<BuildPackageReturn> {
335
363
  const buildStartTime = performance.now()
336
- let buildIteration = 1
337
364
 
338
365
  try {
339
366
  const buildResult = await BuildUtils.buildPackage({
@@ -351,8 +378,8 @@ const BuildUtils = {
351
378
  buildIteration++
352
379
  if (
353
380
  e instanceof MissingDependencyError &&
354
- e.missingModules.length <= 6 &&
355
- e.missingModules.every(mod => isValidNPMName(mod))
381
+ e.missingModules.every(mod => isValidNPMName(mod)) &&
382
+ buildIteration < 4
356
383
  ) {
357
384
  const { missingModules } = e.extra
358
385
  const newExternals = {
@@ -364,12 +391,15 @@ const BuildUtils = {
364
391
  name,
365
392
  missingModules
366
393
  )
367
- const rebuiltResult = await BuildUtils.buildPackage({
368
- name,
369
- externals: newExternals,
370
- installPath,
371
- options,
372
- })
394
+ const rebuiltResults = await BuildUtils.buildPackageIgnoringMissingDeps(
395
+ {
396
+ name,
397
+ externals: newExternals,
398
+ installPath,
399
+ options,
400
+ },
401
+ buildIteration
402
+ )
373
403
 
374
404
  Telemetry.buildPackage(name, true, buildStartTime, {
375
405
  ...options,
@@ -377,10 +407,7 @@ const BuildUtils = {
377
407
  missingModules,
378
408
  })
379
409
 
380
- return {
381
- ignoredMissingDependencies: missingModules,
382
- ...rebuiltResult,
383
- }
410
+ return rebuiltResults
384
411
  } else {
385
412
  Telemetry.buildPackage(
386
413
  name,
@@ -395,6 +422,7 @@ const BuildUtils = {
395
422
  throw e
396
423
  }
397
424
  }
398
- },
425
+ }
399
426
  }
427
+
400
428
  export default BuildUtils