Skip to content

Commit 8ee29c2

Browse files
authored
Publish single-threaded pagx-viewer SDK alongside multi-threaded build (#3436)
1 parent 05f0806 commit 8ee29c2

7 files changed

Lines changed: 153 additions & 67 deletions

File tree

playground/pagx-viewer/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Build outputs
22
wasm-mt/
3+
wasm/
34

45
# Build cache
56
build-pagx-viewer/

playground/pagx-viewer/CMakeLists.txt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,28 @@ if (DEFINED EMSCRIPTEN)
4242
-sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency
4343
-sEXIT_RUNTIME=0 -sINVOKE_RUN=0 -sMALLOC=dlmalloc)
4444
list(APPEND VIEWER_COMPILE_OPTIONS -fPIC -pthread)
45+
set(VIEWER_OUTPUT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/wasm-mt)
4546
else ()
4647
list(APPEND VIEWER_LINK_OPTIONS -sALLOW_MEMORY_GROWTH=1)
48+
set(VIEWER_OUTPUT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/wasm)
4749
endif ()
4850
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
4951
list(APPEND VIEWER_COMPILE_OPTIONS -O0 -g3)
5052
list(APPEND VIEWER_LINK_OPTIONS -O0 -g3 -sSAFE_HEAP=1 -Wno-limited-postlink-optimizations)
5153
else ()
5254
list(APPEND VIEWER_COMPILE_OPTIONS -Oz)
53-
list(APPEND VIEWER_LINK_OPTIONS -Oz)
55+
list(APPEND VIEWER_LINK_OPTIONS -Oz --emit-symbol-map)
56+
# vendor_tools/lib-build wipes the intermediate build dir and only copies *.js / *.wasm
57+
# to the arch output dir, so the emscripten-generated *.symbols would be lost. Copy it
58+
# out manually right after link (POST_BUILD) so subsequent cleanup can't reach it.
59+
# TODO(symbols): drop this workaround once vendor_tools exposes an extension point for
60+
# extra artifacts (or once *.symbols is added to the web copy whitelist upstream).
61+
add_custom_command(TARGET pagx-viewer POST_BUILD
62+
COMMAND ${CMAKE_COMMAND} -E make_directory ${VIEWER_OUTPUT_DIR}
63+
COMMAND ${CMAKE_COMMAND} -E copy_if_different
64+
$<TARGET_FILE_DIR:pagx-viewer>/pagx-viewer.js.symbols
65+
${VIEWER_OUTPUT_DIR}/pagx-viewer.wasm.symbols
66+
COMMENT "Copying wasm symbol map to ${VIEWER_OUTPUT_DIR}/")
5467
endif ()
5568
else ()
5669
add_library(pagx-viewer SHARED ${VIEWER_FILES})

playground/pagx-viewer/README.md

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -97,17 +97,25 @@ npm run build:debug
9797
# Release build: minified JS, stripped WASM, optimized for production.
9898
# Use this when integrating the SDK into a shipping product.
9999
npm run build:release
100+
101+
# Single-threaded variants (no pthread / SharedArrayBuffer requirement).
102+
# Useful when the hosting page cannot enable cross-origin isolation.
103+
npm run build:debug:st
104+
npm run build:release:st
100105
```
101106

107+
> Note: both flavors coexist in `lib/`. The multi-threaded build keeps the canonical
108+
> filenames (`pagx-viewer.*`); the single-threaded build adds a `.st` infix (`pagx-viewer.st.*`).
109+
102110
Both commands generate the following artifacts under `lib/`:
103111

104112
| File | Format | Usage |
105113
|------|--------|-------|
106-
| `pagx-viewer.esm.js` | ESM | `import { PAGXInit } from 'pagx-viewer'` |
107-
| `pagx-viewer.cjs.js` | CJS | `const { PAGXInit } = require('pagx-viewer')` |
108-
| `pagx-viewer.umd.js` | UMD | Browser `<script>` tag |
109-
| `pagx-viewer.min.js` | UMD (minified) | Production use |
110-
| `pagx-viewer.wasm` | WebAssembly | Runtime dependency |
114+
| `pagx-viewer.esm.js` / `pagx-viewer.st.esm.js` | ESM | `import { PAGXInit } from 'pagx-viewer'` (mt) / `'pagx-viewer/st'` (st) |
115+
| `pagx-viewer.cjs.js` / `pagx-viewer.st.cjs.js` | CJS | `const { PAGXInit } = require('pagx-viewer')` (mt) / `require('pagx-viewer/st')` (st) |
116+
| `pagx-viewer.umd.js` / `pagx-viewer.st.umd.js` | UMD | Browser `<script>` tag |
117+
| `pagx-viewer.min.js` / `pagx-viewer.st.min.js` | UMD (minified) | Production use |
118+
| `pagx-viewer.wasm` / `pagx-viewer.st.wasm` | WebAssembly | Runtime dependency |
111119

112120
To debug the C++ side, install the
113121
[C/C++ DevTools Support (DWARF)](https://chrome.google.com/webstore/detail/cc%20%20-devtools-support-dwa/pdcpmagijalfljmkmjngeonclgbbannb)
@@ -122,10 +130,14 @@ bundles), you can invoke the individual steps:
122130

123131
| Command | Description |
124132
|---------|-------------|
125-
| `npm run build:wasm` | Build only the WebAssembly binary (release) |
126-
| `npm run build:wasm:debug` | Build only the WebAssembly binary (debug) |
127-
| `npm run build:js` | Build only the JavaScript bundles (debug) |
128-
| `npm run build:js:release` | Build only the JavaScript bundles (release) |
133+
| `npm run build:wasm` | Build only the WebAssembly binary (multi-threaded, release) |
134+
| `npm run build:wasm:debug` | Build only the WebAssembly binary (multi-threaded, debug) |
135+
| `npm run build:wasm:st` | Build only the WebAssembly binary (single-threaded, release) |
136+
| `npm run build:wasm:st:debug` | Build only the WebAssembly binary (single-threaded, debug) |
137+
| `npm run build:js` | Build only the JavaScript bundles (multi-threaded, debug) |
138+
| `npm run build:js:release` | Build only the JavaScript bundles (multi-threaded, release) |
139+
| `npm run build:js:st` | Build only the JavaScript bundles (single-threaded, debug) |
140+
| `npm run build:js:st:release` | Build only the JavaScript bundles (single-threaded, release) |
129141
| `npm run build:types` | Emit TypeScript declaration files |
130142
| `npm run clean` | Remove build artifacts and caches |
131143

playground/pagx-viewer/README.zh_CN.md

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,25 @@ npm run build:debug
9595
# Release 构建:压缩 JS、剥离 WASM 调试信息,针对生产环境优化。
9696
# 适合将 SDK 集成到正式产品时使用。
9797
npm run build:release
98+
99+
# 单线程版本(不依赖 pthread / SharedArrayBuffer)。
100+
# 适合宿主页面无法启用跨域隔离(COOP/COEP)的场景。
101+
npm run build:debug:st
102+
npm run build:release:st
98103
```
99104

105+
> 提示:两种构建产物共存于 `lib/`,多线程版本沿用 `pagx-viewer.*` 命名,单线程版本通过
106+
> `.st` 中缀(`pagx-viewer.st.*`)区分,互不覆盖。
107+
100108
两个命令都会在 `lib/` 目录下生成以下产物:
101109

102110
| 文件 | 格式 | 用途 |
103111
|------|------|------|
104-
| `pagx-viewer.esm.js` | ESM | `import { PAGXInit } from 'pagx-viewer'` |
105-
| `pagx-viewer.cjs.js` | CJS | `const { PAGXInit } = require('pagx-viewer')` |
106-
| `pagx-viewer.umd.js` | UMD | 浏览器 `<script>` 标签引入 |
107-
| `pagx-viewer.min.js` | UMD(已压缩) | 生产环境使用 |
108-
| `pagx-viewer.wasm` | WebAssembly | 运行时依赖 |
112+
| `pagx-viewer.esm.js` / `pagx-viewer.st.esm.js` | ESM | `import { PAGXInit } from 'pagx-viewer'`(mt)/ `'pagx-viewer/st'`(st)|
113+
| `pagx-viewer.cjs.js` / `pagx-viewer.st.cjs.js` | CJS | `const { PAGXInit } = require('pagx-viewer')`(mt)/ `require('pagx-viewer/st')`(st)|
114+
| `pagx-viewer.umd.js` / `pagx-viewer.st.umd.js` | UMD | 浏览器 `<script>` 标签引入 |
115+
| `pagx-viewer.min.js` / `pagx-viewer.st.min.js` | UMD(已压缩) | 生产环境使用 |
116+
| `pagx-viewer.wasm` / `pagx-viewer.st.wasm` | WebAssembly | 运行时依赖 |
109117

110118
如需调试 C++ 端代码,请安装
111119
[C/C++ DevTools Support (DWARF)](https://chrome.google.com/webstore/detail/cc%20%20-devtools-support-dwa/pdcpmagijalfljmkmjngeonclgbbannb)
@@ -118,10 +126,14 @@ Chrome 扩展,然后打开 DevTools → Settings → Experiments,勾选
118126

119127
| 命令 | 说明 |
120128
|------|------|
121-
| `npm run build:wasm` | 仅构建 WebAssembly 二进制(release) |
122-
| `npm run build:wasm:debug` | 仅构建 WebAssembly 二进制(debug) |
123-
| `npm run build:js` | 仅构建 JavaScript 产物(debug) |
124-
| `npm run build:js:release` | 仅构建 JavaScript 产物(release) |
129+
| `npm run build:wasm` | 仅构建 WebAssembly 二进制(多线程,release) |
130+
| `npm run build:wasm:debug` | 仅构建 WebAssembly 二进制(多线程,debug) |
131+
| `npm run build:wasm:st` | 仅构建 WebAssembly 二进制(单线程,release) |
132+
| `npm run build:wasm:st:debug` | 仅构建 WebAssembly 二进制(单线程,debug) |
133+
| `npm run build:js` | 仅构建 JavaScript 产物(多线程,debug) |
134+
| `npm run build:js:release` | 仅构建 JavaScript 产物(多线程,release) |
135+
| `npm run build:js:st` | 仅构建 JavaScript 产物(单线程,debug) |
136+
| `npm run build:js:st:release` | 仅构建 JavaScript 产物(单线程,release) |
125137
| `npm run build:types` | 输出 TypeScript 声明文件 |
126138
| `npm run clean` | 清理构建产物和缓存 |
127139

playground/pagx-viewer/package.json

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,33 @@
1212
"import": "./lib/pagx-viewer.esm.js",
1313
"require": "./lib/pagx-viewer.cjs.js",
1414
"default": "./lib/pagx-viewer.umd.js"
15+
},
16+
"./st": {
17+
"types": "./types/playground/pagx-viewer/src/ts/pagx.d.ts",
18+
"import": "./lib/pagx-viewer.st.esm.js",
19+
"require": "./lib/pagx-viewer.st.cjs.js",
20+
"default": "./lib/pagx-viewer.st.umd.js"
1521
}
1622
},
1723
"files": [
1824
"lib",
1925
"types"
2026
],
2127
"scripts": {
22-
"clean": "rimraf lib types wasm-mt build-pagx-viewer .*.md5",
28+
"clean": "rimraf lib types wasm wasm-mt build-pagx-viewer .*.md5",
2329
"build:wasm": "node script/cmake.js -a wasm-mt",
2430
"build:wasm:debug": "node script/cmake.js -a wasm-mt --debug",
31+
"build:wasm:st": "node script/cmake.js -a wasm",
32+
"build:wasm:st:debug": "node script/cmake.js -a wasm --debug",
2533
"build:types": "tsc -p tsconfig.type.json",
26-
"build:js": "rollup -c script/rollup.config.js && npm run build:types && node script/fix-wasm-imports.js -a wasm-mt",
27-
"build:js:release": "BUILD_MODE=release rollup -c script/rollup.config.js && npm run build:types && node script/fix-wasm-imports.js -a wasm-mt",
34+
"build:js": "rollup -c script/rollup.config.js --environment ARCH:wasm-mt && npm run build:types && node script/fix-wasm-imports.js -a wasm-mt",
35+
"build:js:release": "BUILD_MODE=release rollup -c script/rollup.config.js --environment ARCH:wasm-mt && npm run build:types && node script/fix-wasm-imports.js -a wasm-mt",
36+
"build:js:st": "rollup -c script/rollup.config.js --environment ARCH:wasm && npm run build:types && node script/fix-wasm-imports.js -a wasm",
37+
"build:js:st:release": "BUILD_MODE=release rollup -c script/rollup.config.js --environment ARCH:wasm && npm run build:types && node script/fix-wasm-imports.js -a wasm",
2838
"build:debug": "npm run build:wasm:debug && npm run build:js",
29-
"build:release": "npm run build:wasm && npm run build:js:release"
39+
"build:release": "npm run build:wasm && npm run build:js:release",
40+
"build:debug:st": "npm run build:wasm:st:debug && npm run build:js:st",
41+
"build:release:st": "npm run build:wasm:st && npm run build:js:st:release"
3042
},
3143
"devDependencies": {
3244
"@rollup/plugin-alias": "~5.1.1",

playground/pagx-viewer/script/fix-wasm-imports.js

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,46 +15,62 @@ for (let i = 0; i < args.length; i++) {
1515
}
1616
}
1717

18-
function getFilesInDir(dir, extension) {
19-
let filesList = [];
20-
21-
const files = fs.readdirSync(dir);
22-
files.forEach(file => {
23-
const fullPath = path.join(dir, file);
24-
const stat = fs.statSync(fullPath);
25-
26-
if (stat.isFile() && fullPath.endsWith(extension)) {
27-
filesList.push(fullPath);
28-
}
29-
else if (stat.isDirectory()) {
30-
filesList = filesList.concat(getFilesInDir(fullPath, extension));
31-
}
32-
});
33-
34-
return filesList;
18+
const SUPPORTED_ARCHS = ['wasm-mt', 'wasm'];
19+
if (!SUPPORTED_ARCHS.includes(argv.a)) {
20+
console.error(`Unsupported -a ${argv.a}. Expected one of ${SUPPORTED_ARCHS.join(', ')}.`);
21+
process.exit(1);
3522
}
3623

3724
function replaceInFile(filePath, searchString, replacement) {
3825
const data = fs.readFileSync(filePath, 'utf-8');
39-
if (data.includes(searchString)) {
40-
const updatedData = data.replaceAll(searchString, replacement);
41-
fs.writeFileSync(filePath, updatedData, 'utf-8');
42-
console.log(`In file ${filePath}, "${searchString}" was replaced with "${replacement}"`);
26+
if (!data.includes(searchString)) {
27+
return false;
4328
}
29+
fs.writeFileSync(filePath, data.replaceAll(searchString, replacement), 'utf-8');
30+
console.log(`In ${filePath}: "${searchString}" -> "${replacement}"`);
31+
return true;
4432
}
4533

46-
function replaceFileNameInFiles() {
47-
const dir = path.resolve(__dirname, "../lib/");
48-
const extension = '.js';
49-
const searchString = 'pagx-viewer.js';
50-
const files = getFilesInDir(dir, extension);
34+
// Multi-threaded build keeps canonical filenames (pagx-viewer.umd.js, pagx-viewer.wasm).
35+
// Single-threaded build adds a `.st` infix; the wasm reference baked into the emcc glue
36+
// must be rewritten so each bundle loads its matching wasm.
37+
const libDir = path.resolve(__dirname, '../lib');
38+
const isSt = argv.a === 'wasm';
39+
const nameInfix = isSt ? '.st' : '';
40+
const bundleNames = ['umd', 'esm', 'cjs', 'min'].map(
41+
(kind) => `pagx-viewer${nameInfix}.${kind}.js`,
42+
);
5143

52-
files.forEach(file => {
53-
const fileName = path.basename(file);
54-
replaceInFile(file, searchString, fileName);
55-
});
44+
let replacedCount = 0;
45+
for (const bundleName of bundleNames) {
46+
const filePath = path.join(libDir, bundleName);
47+
if (!fs.existsSync(filePath)) {
48+
continue;
49+
}
50+
if (isSt) {
51+
// The emcc glue resolves `new URL("pagx-viewer.wasm", import.meta.url)`; redirect
52+
// it to the renamed single-threaded wasm.
53+
if (replaceInFile(filePath, 'pagx-viewer.wasm', 'pagx-viewer.st.wasm')) {
54+
replacedCount++;
55+
}
56+
} else {
57+
// The multi-threaded glue spawns pthread workers via
58+
// `new URL("pagx-viewer.js", import.meta.url)`; redirect that to the actual bundle
59+
// filename so the worker re-loads the same module.
60+
if (replaceInFile(filePath, 'pagx-viewer.js', bundleName)) {
61+
replacedCount++;
62+
}
63+
}
5664
}
5765

58-
if(argv.a ==="wasm-mt"){
59-
replaceFileNameInFiles();
66+
if (replacedCount === 0) {
67+
// No replacement means either the bundles were not built yet, or the emcc glue's
68+
// hard-coded URL string changed (for example after an emscripten upgrade). Fail loudly
69+
// so CI catches the regression instead of shipping bundles that 404 at runtime.
70+
console.error(
71+
`fix-wasm-imports: no occurrences rewritten in lib/. Expected to patch the ` +
72+
`${isSt ? 'wasm URL ("pagx-viewer.wasm")' : 'pthread worker URL ("pagx-viewer.js")'} ` +
73+
`inside ${bundleNames.join(', ')}.`,
74+
);
75+
process.exit(1);
6076
}

playground/pagx-viewer/script/rollup.config.js

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,25 @@ const __dirname = path.dirname(__filename);
3131
const fileHeaderPath = path.resolve(__dirname, '../../../.idea/fileTemplates/includes/PAG File Header.h');
3232
const banner = readFileSync(fileHeaderPath, 'utf-8');
3333

34-
// Copy WASM files to lib
34+
// ARCH selects the wasm output flavor: 'wasm-mt' (multi-threaded) or 'wasm' (single-threaded).
35+
// Both flavors publish to the same lib/ directory; the multi-threaded build keeps the
36+
// canonical filenames (pagx-viewer.umd.js, pagx-viewer.wasm), while the single-threaded
37+
// build adds a `.st` infix (pagx-viewer.st.umd.js, pagx-viewer.st.wasm) to disambiguate.
38+
const SUPPORTED_ARCHS = ['wasm-mt', 'wasm'];
39+
const arch = process.env.ARCH || 'wasm-mt';
40+
if (!SUPPORTED_ARCHS.includes(arch)) {
41+
throw new Error(`Unsupported ARCH: ${arch}. Expected one of ${SUPPORTED_ARCHS.join(', ')}.`);
42+
}
43+
const nameInfix = arch === 'wasm-mt' ? '' : '.st';
44+
const libDir = path.resolve(__dirname, '../lib');
45+
46+
// Copy the wasm next to the bundle. For the single-threaded build, the wasm is renamed
47+
// with the `.st` infix; the emcc glue's hard-coded `new URL("pagx-viewer.wasm", ...)` is
48+
// rewritten to the matching name later by script/fix-wasm-imports.js.
3549
const copyWasmPlugin = {
3650
name: 'copy-wasm',
3751
writeBundle() {
38-
const wasmDir = path.resolve(__dirname, '../wasm-mt');
39-
const libDir = path.resolve(__dirname, '../lib');
52+
const wasmDir = path.resolve(__dirname, `../${arch}`);
4053

4154
if (!existsSync(libDir)) {
4255
mkdirSync(libDir, { recursive: true });
@@ -45,20 +58,28 @@ const copyWasmPlugin = {
4558
// Copy wasm file
4659
const wasmFile = path.join(wasmDir, 'pagx-viewer.wasm');
4760
if (existsSync(wasmFile)) {
48-
copyFileSync(wasmFile, path.join(libDir, 'pagx-viewer.wasm'));
61+
copyFileSync(wasmFile, path.join(libDir, `pagx-viewer${nameInfix}.wasm`));
4962
}
5063
},
5164
};
5265

5366
const plugins = [
54-
esbuild({ tsconfig: path.resolve(__dirname, '../tsconfig.json'), minify: false }),
55-
resolve({ extensions: ['.ts', '.js'] }),
56-
commonJs(),
67+
// MUST stay before resolve()/esbuild() — see PR #3436. Otherwise node-resolve will
68+
// resolve `../../wasm-mt/pagx-viewer` to a concrete file before alias rewrites it,
69+
// and the single-threaded bundle will silently inline the multi-threaded glue.
5770
alias({
5871
entries: [
5972
{ find: '@tgfx', replacement: path.resolve(__dirname, '../../../third_party/tgfx/web/src') },
73+
// Redirect the wasm glue import in pagx.ts to the requested arch's output directory.
74+
{
75+
find: '../../wasm-mt/pagx-viewer',
76+
replacement: path.resolve(__dirname, `../${arch}/pagx-viewer`),
77+
},
6078
],
6179
}),
80+
esbuild({ tsconfig: path.resolve(__dirname, '../tsconfig.json'), minify: false }),
81+
resolve({ extensions: ['.ts', '.js'] }),
82+
commonJs(),
6283
{
6384
name: 'preserve-import-meta-url',
6485
resolveImportMeta(property) {
@@ -71,7 +92,6 @@ const plugins = [
7192
];
7293

7394
const input = path.resolve(__dirname, '../src/ts/pagx.ts');
74-
const libDir = path.resolve(__dirname, '../lib');
7595

7696
// UMD format (for script tag usage, mounts to window.pagxViewer)
7797
const umdConfig = {
@@ -82,7 +102,7 @@ const umdConfig = {
82102
format: 'umd',
83103
exports: 'named',
84104
sourcemap: true,
85-
file: path.join(libDir, 'pagx-viewer.umd.js'),
105+
file: path.join(libDir, `pagx-viewer${nameInfix}.umd.js`),
86106
},
87107
plugins: [...plugins],
88108
};
@@ -96,7 +116,7 @@ const umdMinConfig = {
96116
format: 'umd',
97117
exports: 'named',
98118
sourcemap: true,
99-
file: path.join(libDir, 'pagx-viewer.min.js'),
119+
file: path.join(libDir, `pagx-viewer${nameInfix}.min.js`),
100120
},
101121
plugins: [...plugins, terser()],
102122
};
@@ -105,8 +125,8 @@ const umdMinConfig = {
105125
const moduleConfig = {
106126
input,
107127
output: [
108-
{ banner, file: path.join(libDir, 'pagx-viewer.esm.js'), format: 'esm', sourcemap: true },
109-
{ banner, file: path.join(libDir, 'pagx-viewer.cjs.js'), format: 'cjs', exports: 'named', sourcemap: true },
128+
{ banner, file: path.join(libDir, `pagx-viewer${nameInfix}.esm.js`), format: 'esm', sourcemap: true },
129+
{ banner, file: path.join(libDir, `pagx-viewer${nameInfix}.cjs.js`), format: 'cjs', exports: 'named', sourcemap: true },
110130
],
111131
plugins: [...plugins, copyWasmPlugin],
112132
};

0 commit comments

Comments
 (0)