Skip to content

Commit a29a9eb

Browse files
add fingerprint for app (#80)
* feat: add fingerprint for app Signed-off-by: Yingchun <[email protected]> * fix: validate router key (part of widget directory in compiling) Signed-off-by: Yingchun <[email protected]> --------- Signed-off-by: Yingchun <[email protected]>
1 parent 2b53971 commit a29a9eb

File tree

4 files changed

+183
-66
lines changed

4 files changed

+183
-66
lines changed

packages/hap-packager/src/common/constant.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ const COMPRESS_OPTS = {
2727
}
2828
}
2929

30+
const LOADER_PATH_APP = {
31+
path: '/app-loader.js',
32+
type: ''
33+
}
34+
35+
const LOADER_PATH_DEVICETYPE = {
36+
path: '/device-type-loader.js',
37+
type: ''
38+
}
39+
3040
const LOADER_PATH_UX = {
3141
path: '/ux-loader.js',
3242
type: 'ux'
@@ -75,6 +85,8 @@ export {
7585
FULL_PKG_SIZE,
7686
COMPRESS_OPTS,
7787
BUILD_INFO_FILE,
88+
LOADER_PATH_DEVICETYPE,
89+
LOADER_PATH_APP,
7890
LOADER_PATH_UX,
7991
LOADER_PATH_STYLE,
8092
LOADER_PATH_TEMPLATE,

packages/hap-packager/src/plugins/widget-fingerprint-plugin.js

Lines changed: 141 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66
import fs from 'fs-extra'
77
import path from 'path'
88
import { calcDataDigest, getLastLoaderPath } from '../common/utils'
9-
import { LOADER_INFO_LIST, LOADER_PATH_UX } from '../common/constant'
9+
import {
10+
LOADER_INFO_LIST,
11+
LOADER_PATH_UX,
12+
LOADER_PATH_APP,
13+
LOADER_PATH_DEVICETYPE
14+
} from '../common/constant'
1015

1116
const MANIFEST_PATH = 'manifest.json'
1217

@@ -24,6 +29,10 @@ const EXCLUDE_FILE_EXTS = [
2429
''
2530
] // .gitignore .DS_Store
2631

32+
const EXCLUDE_FILES = ['manifest.json', 'sitemap.json']
33+
34+
const APP = 'app'
35+
2736
/**
2837
* Generate a fingerprint string for a widget, based on the source code and config of the widget.
2938
* The fingerprint can be used to detect whether any changes on the widget.
@@ -56,67 +65,108 @@ class WidgetFingerprintPlugin {
5665
}
5766
const widgets = options.widgets || {}
5867
const widgetNameKeyMap = {}
59-
const widgetEntries = Object.keys(widgets).map((key) => {
68+
const widgetKeys = Object.keys(widgets) || {}
69+
const widgetEntries = widgetKeys.map((key) => {
6070
const widget = widgets[key]
6171
const name = path.join(key, widget.component)
6272
widgetNameKeyMap[name] = key
6373
return name
6474
})
75+
const allAssetsDigestMap = this.calculateAllAssetsDigest(pathSrc, pathSrc, {}, widgetKeys)
76+
const appDigestMap = {}
6577
for (const chunk of compilation.chunks) {
6678
const chunkName = chunk.name
6779
if (widgetEntries.indexOf(chunkName) >= 0) {
6880
// widget chunk
69-
const widgetKey = widgetNameKeyMap[chunkName]
7081
const widgetDigestMap = {}
71-
const arr = []
72-
for (const module of compilation.chunkGraph.getChunkModules(chunk)) {
73-
const { _source, rawRequest, request } = module
74-
arr.push(rawRequest)
75-
if (!request) {
76-
continue
77-
}
78-
const reqPath = request.replace(/\\/g, '/')
79-
const lastLoaderPath = getLastLoaderPath(reqPath)
80-
if (lastLoaderPath === LOADER_PATH_UX.path) {
81-
// skip ux-loader.js
82-
continue
83-
}
84-
const { absPath } = this.parseABSPath(reqPath, pathSrc)
85-
const { _valueAsString, _valueAsBuffer } = _source || {}
86-
const sourceValueStr = _valueAsString || _valueAsBuffer?.toString() || ''
87-
let digestStr = ''
88-
if (sourceValueStr !== '') {
89-
const digest = calcDataDigest(Buffer.from(sourceValueStr, 'utf-8'))
90-
digestStr = digest.toString('hex')
91-
}
92-
// module digest
93-
widgetDigestMap[absPath] = digestStr
94-
}
95-
// assets digests
96-
const assetsDigestMap = this.calculateAssetsDigest(path.join(pathSrc, widgetKey), pathSrc)
97-
Object.assign(widgetDigestMap, assetsDigestMap)
98-
99-
// widget manifest digest
100-
const manifestDigest = this.getManifestDigest(widgetKey, manifestContent)
101-
widgetDigestMap[MANIFEST_PATH] = manifestDigest
102-
103-
const orderK = Object.keys(widgetDigestMap).sort()
104-
let contentStr = '' // ordered path & digest string
105-
orderK.forEach((filePath) => {
106-
contentStr += `${filePath}:${widgetDigestMap[filePath]};`
107-
})
108-
// generate widget digest, save it in compilation
109-
const widgetDigest = calcDataDigest(Buffer.from(contentStr, 'utf-8')).toString('hex')
110-
const { _widgetDigestMap = {} } = compilation
111-
_widgetDigestMap[`widget:${widgetKey}`] = widgetDigest
112-
if (!compilation._widgetDigestMap) {
113-
compilation._widgetDigestMap = _widgetDigestMap
114-
}
82+
this.getModuleDigestFromChunk(compilation, chunk, widgetDigestMap, pathSrc)
83+
const widgetKey = widgetNameKeyMap[chunkName]
84+
this.updateDigest(
85+
compilation,
86+
widgetKey,
87+
widgetDigestMap,
88+
allAssetsDigestMap,
89+
manifestContent
90+
)
91+
} else {
92+
// appDigestMap
93+
this.getModuleDigestFromChunk(compilation, chunk, appDigestMap, pathSrc)
11594
}
11695
}
96+
this.updateDigest(compilation, APP, appDigestMap, allAssetsDigestMap, manifestContent)
11797
})
11898
}
11999

100+
/**
101+
* combine assets and manifest digests, save result on compilation
102+
* @param {*} compilation
103+
* @param {*} key app or widgets key
104+
* @param {*} digestMap all digest for app & widgets
105+
* @param {*} allAssetsDigestMap all assets digets
106+
* @param {*} manifestContent manifest conentent
107+
*/
108+
updateDigest(compilation, key, digestMap, allAssetsDigestMap, manifestContent) {
109+
// assets digest
110+
const assetsDigestMap = allAssetsDigestMap[key] || {}
111+
Object.assign(digestMap, assetsDigestMap)
112+
// manifest digest
113+
digestMap[MANIFEST_PATH] = this.getManifestDigest(key, manifestContent)
114+
115+
const orderK = Object.keys(digestMap).sort()
116+
let contentStr = '' // ordered path & digest string
117+
orderK.forEach((filePath) => {
118+
contentStr += `${filePath}:${digestMap[filePath]};`
119+
})
120+
// generate widget digest, save it in compilation
121+
const digest = calcDataDigest(Buffer.from(contentStr, 'utf-8')).toString('hex')
122+
123+
const { _widgetDigestMap = {} } = compilation
124+
if (key === APP) {
125+
_widgetDigestMap[`app:${key}`] = digest
126+
} else {
127+
_widgetDigestMap[`widget:${key}`] = digest
128+
}
129+
if (!compilation._widgetDigestMap) {
130+
compilation._widgetDigestMap = _widgetDigestMap
131+
}
132+
}
133+
134+
/**
135+
* get all module digests in chunk
136+
* @param {*} compilation
137+
* @param {*} chunk
138+
* @param {*} moduleDigestMap
139+
* @param {*} pathSrc
140+
*/
141+
getModuleDigestFromChunk(compilation, chunk, moduleDigestMap, pathSrc) {
142+
for (const module of compilation.chunkGraph.getChunkModules(chunk)) {
143+
const { _source, request } = module
144+
if (!request) {
145+
continue
146+
}
147+
const reqPath = request.replace(/\\/g, '/')
148+
const lastLoaderPath = getLastLoaderPath(reqPath)
149+
if (
150+
lastLoaderPath === LOADER_PATH_UX.path ||
151+
lastLoaderPath === LOADER_PATH_APP.path ||
152+
lastLoaderPath === LOADER_PATH_DEVICETYPE.path
153+
) {
154+
// skip
155+
continue
156+
}
157+
const { absPath } = this.parseABSPath(reqPath, pathSrc)
158+
const { _valueAsString, _valueAsBuffer } = _source || {}
159+
const sourceValueStr = _valueAsString || _valueAsBuffer?.toString() || ''
160+
let digestStr = ''
161+
if (sourceValueStr !== '') {
162+
const digest = calcDataDigest(Buffer.from(sourceValueStr, 'utf-8'))
163+
digestStr = digest.toString('hex')
164+
}
165+
// module digest
166+
moduleDigestMap[absPath] = digestStr
167+
}
168+
}
169+
120170
/**
121171
* parse absolute path based by project src
122172
* @param {*} reqPath
@@ -147,24 +197,32 @@ class WidgetFingerprintPlugin {
147197
}
148198

149199
/**
150-
* 返回与当前卡片相关的 manifest 配置摘要
151-
* @param string widgetKey
200+
* 返回与当前app或widget相关的 manifest 配置摘要
201+
* @param string key
152202
* @param string manifestContent
153203
*/
154-
getManifestDigest(widgetKey, manifestContent) {
155-
if (!widgetKey || !manifestContent) {
204+
getManifestDigest(key, manifestContent) {
205+
if (!key || !manifestContent) {
156206
return new Date().getTime()
157207
}
158208
const manifestJsonNew = JSON.parse(manifestContent)
159209
manifestJsonNew.versionName = ''
160210
manifestJsonNew.versionCode = ''
161-
manifestJsonNew.router.entry = ''
162-
if (manifestJsonNew.router.pages) {
163-
manifestJsonNew.router.pages = ''
164-
}
165-
manifestJsonNew.router.widgets = {
166-
[widgetKey]: manifestJsonNew.router.widgets[widgetKey]
211+
manifestJsonNew['template/official'] = ''
212+
if (key === APP) {
213+
if (manifestJsonNew.router.widgets) {
214+
manifestJsonNew.router.widgets = ''
215+
}
216+
} else {
217+
manifestJsonNew.router.entry = ''
218+
if (manifestJsonNew.router.pages) {
219+
manifestJsonNew.router.pages = ''
220+
}
221+
manifestJsonNew.router.widgets = {
222+
[key]: manifestJsonNew.router.widgets[key]
223+
}
167224
}
225+
168226
const manifestJsonNewStr = JSON.stringify(manifestJsonNew)
169227
const digest = calcDataDigest(Buffer.from(manifestJsonNewStr, 'utf-8')).toString('hex')
170228
return digest
@@ -176,20 +234,38 @@ class WidgetFingerprintPlugin {
176234
* @param {string[]} allFiles
177235
* @returns {Object} relative file path and its digest
178236
*/
179-
calculateAssetsDigest(dir, basePath, assetsDigestMap = {}) {
237+
calculateAllAssetsDigest(dir, basePath, assetsDigestMap, widgetKeys) {
180238
const files = fs.readdirSync(dir)
181239
for (let i = 0; i < files.length; i++) {
182-
let name = path.join(dir, files[i])
183-
if (fs.statSync(name).isDirectory()) {
184-
this.calculateAssetsDigest(name, basePath, assetsDigestMap)
240+
let fileName = path.join(dir, files[i])
241+
if (fs.statSync(fileName).isDirectory()) {
242+
// directory
243+
this.calculateAllAssetsDigest(fileName, basePath, assetsDigestMap, widgetKeys)
185244
} else {
186-
const ext = path.extname(name)
245+
// file
246+
const ext = path.extname(fileName)
187247
if (EXCLUDE_FILE_EXTS.includes(ext)) {
188248
continue
189249
}
190-
const digest = calcDataDigest(fs.readFileSync(name)).toString('hex')
191-
const relativePath = path.relative(basePath, name)
192-
assetsDigestMap[relativePath] = digest
250+
const relativePath = path.relative(basePath, fileName)
251+
if (EXCLUDE_FILES.includes(relativePath)) {
252+
continue
253+
}
254+
const digest = calcDataDigest(fs.readFileSync(fileName)).toString('hex')
255+
const widgetKey = widgetKeys.find((item) => relativePath.startsWith(item + '/'))
256+
if (widgetKey) {
257+
// widget
258+
if (!assetsDigestMap[widgetKey]) {
259+
assetsDigestMap[widgetKey] = {}
260+
}
261+
assetsDigestMap[widgetKey][relativePath] = digest
262+
} else {
263+
// app
264+
if (!assetsDigestMap[APP]) {
265+
assetsDigestMap[APP] = {}
266+
}
267+
assetsDigestMap[APP][relativePath] = digest
268+
}
193269
}
194270
}
195271
return assetsDigestMap

packages/hap-packager/src/plugins/zip-plugin.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ ZipPlugin.prototype.apply = function (compiler) {
372372

373373
const { _widgetDigestMap } = stats.compilation
374374
if (_widgetDigestMap && Object.keys(_widgetDigestMap)?.length) {
375-
console.log('widget fingerprint:', _widgetDigestMap)
375+
console.log('fingerprint:', _widgetDigestMap)
376376
}
377377

378378
// 遍历文件分配文件资源到每个package里面, 包括digest, file hash

packages/hap-toolkit/src/utils.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,26 @@ import { colorconsole, compileOptionsObject } from '@hap-toolkit/shared-utils'
88
import { ENTRY_TYPE } from '@hap-toolkit/compiler'
99
import { resolveFile } from '@hap-toolkit/packager'
1010

11+
const NOT_ALLOW_CHARS = [
12+
'@',
13+
'$',
14+
'.',
15+
'&',
16+
'+',
17+
'(',
18+
')',
19+
' ',
20+
'-',
21+
'\\',
22+
'//',
23+
'?',
24+
':',
25+
'|',
26+
'<',
27+
'>',
28+
'='
29+
]
30+
1131
/**
1232
* 提取其中的应用,页面,worker的脚本文件
1333
* @return {Array}
@@ -66,6 +86,15 @@ export function resolveEntries(manifest, basedir, cwd) {
6686
const conf = confs[routePath]
6787
const entryKey = path.join(routePath, conf.component)
6888
const filepath = resolveFile(path.join(basedir, entryKey))
89+
if (type === 'card') {
90+
for (let ch of NOT_ALLOW_CHARS) {
91+
if (routePath.indexOf(ch) > -1) {
92+
colorconsole.throw(
93+
`编译失败:manifest.json中卡片router配置的key不能包含 字母、数字、/、_ 以外的特殊字符:${entryKey}`
94+
)
95+
}
96+
}
97+
}
6998

7099
if (!filepath) {
71100
colorconsole.throw(`编译失败:请确认manifest.json中配置的文件路径存在:${entryKey}`)

0 commit comments

Comments
 (0)