UNPKG

7.32 kBJavaScriptView Raw
1const fs = require('fs')
2const path = require('path')
3const filesize = require('filesize')
4const chalk = require('chalk')
5const recursive = require('recursive-readdir')
6const { stripAnsi } = require('@mara/devkit')
7const gzipSize = require('gzip-size').sync
8const { groupBy } = require('lodash')
9
10// assetsData:{<Object>: <Array>}
11function printBuildAssets(
12 assetsData,
13 previousSizeMap,
14 maxBundleGzipSize = Infinity,
15 maxChunkGzipSize = Infinity
16) {
17 // https://raw.githubusercontent.com/webpack/analyse/master/app/pages/upload/example.json
18 let labelLengthArr = []
19 let libPathLengthArr = []
20 let suggestBundleSplitting = false
21 // const root = previousSizeMap.root
22 const preSizes = previousSizeMap.sizes
23 const isJS = val => /\.js$/.test(val)
24 const isCSS = val => /\.css$/.test(val)
25 const isMinJS = val => /\.min\.js$/.test(val)
26
27 function mainAssetInfo(info, type) {
28 // __format 属性为组件资源特有
29 const isMainBundle =
30 type === 'view' && info.name.indexOf(`${info.folder}.`) === 0
31 const maxRecommendedSize = isMainBundle
32 ? maxBundleGzipSize
33 : maxChunkGzipSize
34 const isLarge = maxRecommendedSize && info.size > maxRecommendedSize
35
36 if (isLarge && isJS(info.name)) {
37 suggestBundleSplitting = true
38 }
39
40 if (type === 'lib') {
41 libPathLengthArr.push(stripAnsi(info.name).length)
42 }
43
44 printAssetPath(info, type, isLarge)
45 }
46
47 function printAssetPath(info, type, isLarge = false) {
48 let sizeLabel = info.sizeLabel
49 const sizeLength = stripAnsi(sizeLabel).length
50 const longestSizeLabelLength = Math.max.apply(null, labelLengthArr)
51 const longestLibPathLength = Math.max.apply(null, libPathLengthArr)
52 let assetPath = chalk.dim(info.folder + path.sep)
53
54 // path.normalize 跨平台格式化路径
55 if (isJS(info.name)) {
56 // lib 脚本文件添加模块格式标识
57 let formatLabel = info.format ? ` [${info.format}]` : ''
58 const libPathLength = stripAnsi(info.name).length
59
60 if (formatLabel && libPathLength < longestLibPathLength) {
61 const leftPadding = ' '.repeat(longestLibPathLength - libPathLength)
62
63 formatLabel = leftPadding + formatLabel
64 }
65
66 assetPath += chalk.yellowBright(info.name + formatLabel)
67 } else if (isCSS(info.name)) {
68 assetPath += chalk.blueBright(info.name)
69 } else {
70 assetPath += chalk.cyan(info.name)
71 }
72
73 if (sizeLength < longestSizeLabelLength) {
74 const rightPadding = ' '.repeat(longestSizeLabelLength - sizeLength)
75
76 sizeLabel += rightPadding
77 }
78
79 console.log(
80 ` ${isLarge ? chalk.yellow(sizeLabel) : sizeLabel} ${assetPath}`
81 )
82 }
83
84 // Input: 1024, 2048
85 // Output: "(+1 KB)"
86 function getDifferenceLabel(currentSize, previousSize) {
87 const FIFTY_KILOBYTES = 1024 * 50
88 const difference = currentSize - previousSize
89 const fileSize = !Number.isNaN(difference) ? filesize(difference) : 0
90
91 if (difference >= FIFTY_KILOBYTES) {
92 return chalk.red('+' + fileSize)
93 } else if (difference < FIFTY_KILOBYTES && difference > 0) {
94 return chalk.yellow('+' + fileSize)
95 } else if (difference < 0) {
96 return chalk.green(fileSize)
97 } else {
98 return ''
99 }
100 }
101
102 function parseAssets(assets) {
103 const seenNames = new Map()
104 const assetsInfo = groupBy(
105 assets
106 .filter(a =>
107 seenNames.has(a.name) ? false : seenNames.set(a.name, true)
108 )
109 .map(asset => {
110 const buildDir = assets['__dist'] || asset['__dist']
111 const fileContents = fs.readFileSync(path.join(buildDir, asset.name))
112 const size = gzipSize(fileContents)
113 const previousSize = preSizes[removeFileNameHash(asset.name)]
114 const difference = getDifferenceLabel(size, previousSize)
115 const sizeLabel =
116 filesize(size) + (difference ? ' (' + difference + ')' : '')
117
118 labelLengthArr.push(stripAnsi(sizeLabel).length)
119
120 return {
121 folder: path.join(
122 path.basename(buildDir),
123 path.dirname(asset.name)
124 ),
125 name: path.basename(asset.name),
126 format: asset['__format'],
127 size: size,
128 sizeLabel
129 }
130 }),
131 asset => (/\.(js|css)$/.test(asset.name) ? 'main' : 'other')
132 )
133
134 assetsInfo.main = assetsInfo.main || []
135 assetsInfo.other = assetsInfo.other || []
136
137 return assetsInfo
138 }
139
140 const assetList = Object.keys(assetsData).map(type => {
141 let assets = assetsData[type]
142 let output
143
144 if (type === 'lib') {
145 assets = [].concat.apply([], assets)
146 output = [parseAssets(assets)]
147 } else {
148 output = assets.map(a => parseAssets(a))
149 }
150
151 return { type, output }
152 })
153
154 assetList.forEach(item => {
155 if (item.type === 'demos' && item.output.length) {
156 console.log(`\nDEMO${item.output.length > 1 ? 'S' : ''}:`)
157 }
158
159 item.output.forEach(assetsInfo => {
160 // add new line
161 if (item.type === 'demos') console.log()
162
163 assetsInfo.main
164 .sort((a, b) => {
165 if (isJS(a.name) && isCSS(b.name)) return -1
166 if (isCSS(a.name) && isJS(b.name)) return 1
167 if (isMinJS(a.name) && !isMinJS(b.name)) return -1
168 if (!isMinJS(a.name) && isMinJS(b.name)) return 1
169
170 return b.size - a.size
171 })
172 .forEach(info => mainAssetInfo(info, item.type))
173
174 assetsInfo.other
175 .sort((a, b) => b.size - a.size)
176 .forEach(info => printAssetPath(info))
177 })
178 })
179
180 if (suggestBundleSplitting) {
181 console.log()
182 console.log(
183 chalk.yellow('The bundle size is significantly larger than recommended.')
184 )
185 console.log(
186 chalk.yellow(
187 'Consider reducing it with code splitting: https://goo.gl/9VhYWB'
188 )
189 )
190 console.log(
191 chalk.yellow(
192 'You can also analyze the project dependencies: https://goo.gl/LeUzfb'
193 )
194 )
195 }
196}
197
198function canReadAsset(asset) {
199 return (
200 /\.(js|css|php)$/.test(asset) &&
201 !/service-worker\.js/.test(asset) &&
202 !/precache-manifest\.[0-9a-f]+\.js/.test(asset)
203 )
204}
205
206function removeFileNameHash(fileName, buildFolder = '') {
207 return fileName
208 .replace(buildFolder, '')
209 .replace(/\\/g, '/')
210 .replace(/^\//, '')
211 .replace(
212 /\/?(.*)(\.[0-9a-f]+)(\.chunk)?(\.js|\.css)/,
213 (match, p1, p2, p3, p4) => p1 + p4
214 )
215}
216
217function getBuildSizeOfFileMap(fileMap = {}) {
218 if (!Object.keys(fileMap).length) return {}
219
220 return Object.entries(fileMap).reduce((sizes, [file, originFile]) => {
221 const contents = fs.readFileSync(originFile)
222
223 sizes[file] = gzipSize(contents)
224
225 return sizes
226 }, {})
227}
228
229function getLastBuildSize(buildFolder) {
230 return new Promise(resolve => {
231 recursive(buildFolder, (err, fileNames) => {
232 let sizes = {}
233
234 if (!err && fileNames) {
235 sizes = fileNames.filter(canReadAsset).reduce((memo, fileName) => {
236 const contents = fs.readFileSync(fileName)
237 const key = removeFileNameHash(fileName, buildFolder)
238
239 memo[key] = gzipSize(contents)
240
241 return memo
242 }, {})
243 }
244
245 resolve({
246 root: buildFolder,
247 sizes: sizes
248 })
249 })
250 })
251}
252
253module.exports = {
254 getLastBuildSize,
255 printBuildAssets,
256 getBuildSizeOfFileMap
257}