Skip to content

Commit acc29e4

Browse files
claudefrankslin
authored andcommitted
Add TypeScript support and reorganize build structure with semantic separation
This commit enhances the opencc-wasm library with TypeScript support and implements a cleaner build architecture with semantic separation between intermediate build artifacts and publishable distribution. TypeScript Support: - Add comprehensive type definitions (index.d.ts) with full JSDoc documentation - Define interfaces: ConverterOptions, ConverterFunction, OpenCCNamespace, etc. - Provide complete type safety for better IDE support and developer experience Build Architecture Redesign (semantic separation): - build/ - Intermediate WASM artifacts (gitignored, for tests/development) * build/opencc-wasm.esm.js - ESM WASM glue * build/opencc-wasm.cjs - CJS WASM glue * build/opencc-wasm.wasm - WASM binary - dist/ - Publishable distribution (committed, for npm) * dist/esm/ - ESM package entry * dist/cjs/ - CJS package entry * dist/data/ - OpenCC config and dictionary files Invariants and Semantics: - Tests import source (index.js) → loads from build/ - Published package exports dist/ only - build/ = internal intermediate artifacts - dist/ = publishable artifacts - Clear separation ensures tests validate actual build output Enhanced .gitignore: - Add build/ to gitignore (intermediate artifacts) - Add node_modules/, logs, OS-specific files (.DS_Store, Thumbs.db) - Exclude editor configurations (.vscode/, .idea/) - Add cache and temporary file exclusions Two-Stage Build Process: Stage 1 (build.sh): - Compiles C++ to WASM using Emscripten - Outputs to build/ directory Stage 2 (build-api.js): - Copies WASM artifacts from build/ to dist/ - Transforms source paths for production - Generates API wrappers for ESM and CJS - Copies data files Package Configuration (package.json): - Add "types" field pointing to index.d.ts - Update "main" and "module" to point to API wrappers in dist/ - Add comprehensive "exports" map: * "." - Main API (ESM/CJS wrappers) * "./wasm" - Direct access to WASM glue for advanced users * "./dist/*" - Wildcard for flexible file access - Include LICENSE and NOTICE in published files Documentation: - Add comprehensive README section explaining build architecture - Document project structure with invariants - Explain semantic separation between build/ and dist/ Benefits: - Better TypeScript integration and IDE autocomplete - Cleaner, more maintainable directory structure - Tests validate actual build output, not stale dist files - Clear semantic separation between internal and publishable artifacts - Professional project setup following modern npm best practices - Long-term maintainability through clear invariants
1 parent 4f99fef commit acc29e4

9 files changed

Lines changed: 282 additions & 29 deletions

File tree

wasm-lib/.gitignore

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,39 @@
1+
# Emscripten cache
12
.emcache
3+
4+
# Node modules
5+
node_modules/
6+
7+
# Build intermediates (not committed)
8+
build/
9+
10+
# Dist outputs (keep in git for npm package)
11+
12+
# Logs
13+
*.log
14+
npm-debug.log*
15+
yarn-debug.log*
16+
yarn-error.log*
17+
lerna-debug.log*
18+
19+
# OS files
20+
.DS_Store
21+
Thumbs.db
22+
23+
# Editor directories and files
24+
.vscode/*
25+
!.vscode/extensions.json
26+
.idea
27+
*.swp
28+
*.swo
29+
*~
30+
31+
# Optional npm cache directory
32+
.npm
33+
34+
# Optional eslint cache
35+
.eslintcache
36+
37+
# Temporary files
38+
*.tmp
39+
.cache/

wasm-lib/README.md

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,83 @@ console.log(custom("悟空道:“师父又来了。怎么叫做‘水中捞月
3737
const OpenCC = require("opencc-wasm").default;
3838
```
3939

40-
## Files and entry points
41-
- ESM: `dist/opencc-wasm.js`
42-
- CJS: `dist/opencc-wasm.cjs`
43-
- Data: `dist/data/config/*.json`, `dist/data/dict/*.ocd2` (copied during `npm run build`)
40+
## Build
4441

45-
The package `exports` map is set so bundlers and Node can pick the right build automatically.
42+
The project uses a two-stage build process with semantic separation:
43+
44+
### Stage 1: Build WASM (intermediate artifacts)
45+
46+
```bash
47+
./build.sh
48+
```
49+
50+
Compiles OpenCC + marisa-trie to WASM and generates intermediate build artifacts in `build/`:
51+
- `build/opencc-wasm.esm.js` - ESM WASM glue (for tests/development)
52+
- `build/opencc-wasm.cjs` - CJS WASM glue (for tests/development)
53+
- `build/opencc-wasm.wasm` - WASM binary
54+
55+
**Semantic: `build/` = internal intermediate artifacts, not for publishing**
56+
57+
### Stage 2: Build API wrappers (publishable dist)
58+
59+
```bash
60+
node scripts/build-api.js
61+
```
62+
63+
Generates publishable distribution in `dist/`:
64+
- Copies WASM artifacts from `build/` to `dist/esm/` and `dist/cjs/`
65+
- Transforms source `index.js` to `dist/esm/index.js` with production paths
66+
- Generates `dist/cjs/index.cjs` with CJS-compatible wrapper
67+
- Copies data files to `dist/data/`
68+
69+
**Semantic: `dist/` = publishable artifacts for npm**
70+
71+
### Complete build
72+
73+
```bash
74+
npm run build
75+
```
76+
77+
Runs both stages automatically.
4678

4779
## Testing
4880
```bash
49-
cd wasm-lib
5081
npm test
5182
```
83+
84+
Tests import from source `index.js`, which references `build/` artifacts.
85+
This ensures tests validate the actual build output, not stale dist files.
86+
5287
Runs the upstream OpenCC testcases (converted to JSON) against the WASM build.
5388

89+
## Project Structure
90+
91+
```
92+
wasm-lib/
93+
├── build/ ← Intermediate WASM artifacts (gitignored, for tests)
94+
│ ├── opencc-wasm.esm.js
95+
│ ├── opencc-wasm.cjs
96+
│ └── opencc-wasm.wasm
97+
├── dist/ ← Publishable distribution (committed to git)
98+
│ ├── esm/
99+
│ │ ├── index.js
100+
│ │ └── opencc-wasm.js
101+
│ ├── cjs/
102+
│ │ ├── index.cjs
103+
│ │ └── opencc-wasm.cjs
104+
│ ├── opencc-wasm.wasm
105+
│ └── data/ ← OpenCC config + dict files
106+
├── index.js ← Source API (references build/ for tests)
107+
├── index.d.ts ← TypeScript definitions
108+
└── scripts/
109+
└── build-api.js ← Transforms build/ → dist/
110+
```
111+
112+
**Invariants:**
113+
- Tests import source (`index.js`) → loads from `build/`
114+
- Published package exports `dist/` only
115+
- `build/` = internal, `dist/` = publishable
116+
54117
## Notes
55118
- Internally uses persistent OpenCC handles (`opencc_create/convert/destroy`) to avoid reloading configs.
56119
- Dictionaries are written under `/data/dict/` in the virtual FS; configs under `/data/config/`. Paths inside configs are rewritten automatically.

wasm-lib/build.sh

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#!/usr/bin/env bash
22
set -euo pipefail
33

4-
# 输出目录
5-
OUT_DIR="dist"
6-
mkdir -p "${OUT_DIR}"
4+
# 输出目录(中间构建产物)
5+
BUILD_DIR="build"
6+
mkdir -p "${BUILD_DIR}"
77

88
# 单独的 emcc 缓存目录,避免权限问题
99
export EM_CACHE="$(pwd)/.emcache"
@@ -83,13 +83,19 @@ COMMON_FLAGS=(
8383
em++ \
8484
"${COMMON_FLAGS[@]}" \
8585
-s EXPORT_ES6=1 \
86-
-o "${OUT_DIR}/opencc-wasm.js"
86+
-o "${BUILD_DIR}/opencc-wasm.esm.js"
8787

8888
# CommonJS(适合 Node.js require)
8989
em++ \
9090
"${COMMON_FLAGS[@]}" \
9191
-s EXPORT_ES6=0 \
9292
-s ENVIRONMENT='node' \
93-
-o "${OUT_DIR}/opencc-wasm.cjs"
93+
-o "${BUILD_DIR}/opencc-wasm.cjs"
9494

95-
echo "Build complete. Files in ${OUT_DIR}/"
95+
# WASM 文件由 emcc 自动生成
96+
echo "Build complete. Intermediate files in ${BUILD_DIR}/"
97+
echo " - ${BUILD_DIR}/opencc-wasm.esm.js (ESM glue for tests/rebuild)"
98+
echo " - ${BUILD_DIR}/opencc-wasm.cjs (CJS glue for tests/rebuild)"
99+
echo " - ${BUILD_DIR}/opencc-wasm.wasm (WASM binary)"
100+
echo ""
101+
echo "Run 'node scripts/build-api.js' to generate dist/ for publishing."

wasm-lib/index.d.ts

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/**
2+
* OpenCC WASM - WebAssembly backend for OpenCC
3+
*
4+
* @packageDocumentation
5+
*/
6+
7+
/**
8+
* Options for creating a converter
9+
*/
10+
export interface ConverterOptions {
11+
/**
12+
* Source locale: 'cn' (Simplified Chinese), 'tw' (Traditional Taiwan),
13+
* 'hk' (Traditional Hong Kong), 't' (Traditional), 'jp' (Japanese)
14+
*/
15+
from?: string;
16+
17+
/**
18+
* Target locale: 'cn' (Simplified Chinese), 'tw' (Traditional Taiwan),
19+
* 'hk' (Traditional Hong Kong), 't' (Traditional), 'jp' (Japanese)
20+
*/
21+
to?: string;
22+
23+
/**
24+
* Config file name (e.g., 's2t.json', 't2s.json')
25+
* If specified, 'from' and 'to' will be ignored
26+
*/
27+
config?: string;
28+
}
29+
30+
/**
31+
* Async converter function that transforms text
32+
*/
33+
export type ConverterFunction = (text: string) => Promise<string>;
34+
35+
/**
36+
* Synchronous custom converter function (for custom dictionaries)
37+
*/
38+
export type CustomConverterFunction = (text: string) => string;
39+
40+
/**
41+
* Custom dictionary entry: [source, target]
42+
*/
43+
export type DictEntry = [string, string];
44+
45+
/**
46+
* Custom dictionary: array of entries or pipe-separated string
47+
*/
48+
export type CustomDict = DictEntry[] | string;
49+
50+
/**
51+
* Locale mappings
52+
*/
53+
export interface LocaleMap {
54+
cn: string;
55+
tw: string;
56+
hk: string;
57+
jp: string;
58+
t: string;
59+
}
60+
61+
/**
62+
* OpenCC namespace with all conversion functions
63+
*/
64+
export interface OpenCCNamespace {
65+
/**
66+
* Create a converter with the given options
67+
*
68+
* @example
69+
* ```typescript
70+
* const converter = OpenCC.Converter({ from: 'cn', to: 'tw' });
71+
* const result = await converter('简体中文');
72+
* ```
73+
*/
74+
Converter(opts: ConverterOptions): ConverterFunction;
75+
76+
/**
77+
* Create a custom converter with user-defined dictionary
78+
*
79+
* @param dict - Array of [source, target] pairs or pipe-separated string
80+
*
81+
* @example
82+
* ```typescript
83+
* const custom = OpenCC.CustomConverter([
84+
* ['"', '「'],
85+
* ['"', '」'],
86+
* ]);
87+
* const result = custom('He said "hello"');
88+
* ```
89+
*/
90+
CustomConverter(dict: CustomDict): CustomConverterFunction;
91+
92+
/**
93+
* Create a converter with additional custom dictionaries
94+
*
95+
* @param fromLocale - Source locale
96+
* @param toLocale - Target locale
97+
* @param extraDicts - Additional custom dictionaries to apply after conversion
98+
*
99+
* @example
100+
* ```typescript
101+
* const converter = OpenCC.ConverterFactory('cn', 'tw', [
102+
* [['"', '「'], ['"', '」']]
103+
* ]);
104+
* const result = await converter('简体中文 "test"');
105+
* ```
106+
*/
107+
ConverterFactory(
108+
fromLocale: string,
109+
toLocale: string,
110+
extraDicts?: CustomDict[]
111+
): ConverterFunction;
112+
113+
/**
114+
* Locale constants for 'from' and 'to' options
115+
*/
116+
Locale: {
117+
from: LocaleMap;
118+
to: LocaleMap;
119+
};
120+
}
121+
122+
/**
123+
* OpenCC main export
124+
*/
125+
export const OpenCC: OpenCCNamespace;
126+
127+
/**
128+
* Default export
129+
*/
130+
export default OpenCC;

wasm-lib/index.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,18 @@ async function getModule() {
4747
if (modulePromise) return modulePromise;
4848

4949
// 1) 先确定包根目录(一定要以 / 结尾)
50-
const pkgBase = new URL("../", import.meta.url);
50+
const pkgBase = new URL("./", import.meta.url);
5151
// 如果这段代码在 HTML inline script 里,没有 import.meta.url,那就用绝对路径:
5252
// const pkgBase = new URL("/vendor/opencc-wasm/", window.location.origin);
5353

54-
// 2) import glue
55-
const glueUrl = new URL("opencc-wasm.js", pkgBase);
54+
// 2) import glue (from build/ for testing/development)
55+
const glueUrl = new URL("build/opencc-wasm.esm.js", pkgBase);
5656

5757
const { default: create } = await import(glueUrl.href);
5858

5959
// 3) locateFile 必须相对 pkgBase,而不是 glueUrl
6060
modulePromise = create({
61-
locateFile: (p) => new URL(p, pkgBase).href
61+
locateFile: (p) => new URL(`build/${p}`, pkgBase).href
6262
});
6363

6464
return modulePromise;

wasm-lib/package.json

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,27 @@
1111
"converter"
1212
],
1313
"type": "module",
14-
"main": "./dist/opencc-wasm.cjs",
15-
"module": "./dist/opencc-wasm.js",
14+
"main": "./dist/cjs/index.cjs",
15+
"module": "./dist/esm/index.js",
16+
"types": "./index.d.ts",
1617
"exports": {
1718
".": {
18-
"import": "./dist/opencc-wasm.js",
19-
"require": "./dist/opencc-wasm.cjs"
20-
}
19+
"types": "./index.d.ts",
20+
"import": "./dist/esm/index.js",
21+
"require": "./dist/cjs/index.cjs"
22+
},
23+
"./wasm": {
24+
"import": "./dist/esm/opencc-wasm.js",
25+
"require": "./dist/cjs/opencc-wasm.cjs"
26+
},
27+
"./dist/*": "./dist/*"
2128
},
2229
"files": [
2330
"dist/",
24-
"README.md"
31+
"index.d.ts",
32+
"README.md",
33+
"LICENSE",
34+
"NOTICE"
2535
],
2636
"scripts": {
2737
"build": "./build.sh && node scripts/build-api.js",

0 commit comments

Comments
 (0)